Compare commits
2 Commits
4a07eb4ed9
...
197ee0d5b1
| Author | SHA1 | Date | |
|---|---|---|---|
| 197ee0d5b1 | |||
| 3d8597869d |
@ -367,7 +367,7 @@ public class CompetitionService {
|
|||||||
c.getBanMembre().remove(combModel.getId());
|
c.getBanMembre().remove(combModel.getId());
|
||||||
return Panache.withTransaction(() -> repository.persist(c));
|
return Panache.withTransaction(() -> repository.persist(c));
|
||||||
})
|
})
|
||||||
.chain(combModel -> updateRegister(data, c, combModel, true)))
|
.chain(combModel -> updateRegister(data, c, combModel, true, false)))
|
||||||
.map(r -> SimpleRegisterComb.fromModel(r, r.getMembre().getLicences())
|
.map(r -> SimpleRegisterComb.fromModel(r, r.getMembre().getLicences())
|
||||||
.setCategorieInscrite(r.getCategoriesInscrites()));
|
.setCategorieInscrite(r.getCategoriesInscrites()));
|
||||||
} else {
|
} else {
|
||||||
@ -390,9 +390,9 @@ public class CompetitionService {
|
|||||||
model.setClub(data.getClub());
|
model.setClub(data.getClub());
|
||||||
model.setCountry(data.getCountry());
|
model.setCountry(data.getCountry());
|
||||||
model.setWeightReal(data.getWeightReal());
|
model.setWeightReal(data.getWeightReal());
|
||||||
|
model.setCategorie(data.getCategorie());
|
||||||
if (model.getCompetition().getRequiredWeight().contains(model.getCategorie()))
|
if (model.getCompetition().getRequiredWeight().contains(model.getCategorie()))
|
||||||
model.setWeight(data.getWeight());
|
model.setWeight(data.getWeight());
|
||||||
model.setCategorie(data.getCategorie());
|
|
||||||
})
|
})
|
||||||
.call(g -> Mutiny.fetch(g.getCategoriesInscrites()))
|
.call(g -> Mutiny.fetch(g.getCategoriesInscrites()))
|
||||||
.call(g -> catPresetRepository.list("competition = ?1 AND id IN ?2", g.getCompetition(),
|
.call(g -> catPresetRepository.list("competition = ?1 AND id IN ?2", g.getCompetition(),
|
||||||
@ -427,7 +427,7 @@ public class CompetitionService {
|
|||||||
if (c.getBanMembre().contains(model.getId()))
|
if (c.getBanMembre().contains(model.getId()))
|
||||||
throw new DForbiddenException(trad.t("insc.err1"));
|
throw new DForbiddenException(trad.t("insc.err1"));
|
||||||
}))
|
}))
|
||||||
.chain(combModel -> updateRegister(data, c, combModel, false)))
|
.chain(combModel -> updateRegister(data, c, combModel, false, false)))
|
||||||
.map(r -> SimpleRegisterComb.fromModel(r, r.getMembre().getLicences())
|
.map(r -> SimpleRegisterComb.fromModel(r, r.getMembre().getLicences())
|
||||||
.setCategorieInscrite(r.getCategoriesInscrites()));
|
.setCategorieInscrite(r.getCategoriesInscrites()));
|
||||||
|
|
||||||
@ -443,19 +443,101 @@ public class CompetitionService {
|
|||||||
if (c.getBanMembre().contains(model.getId()))
|
if (c.getBanMembre().contains(model.getId()))
|
||||||
throw new DForbiddenException(trad.t("insc.err2"));
|
throw new DForbiddenException(trad.t("insc.err2"));
|
||||||
}))
|
}))
|
||||||
.chain(combModel -> updateRegister(data, c, combModel, false)))
|
.chain(combModel -> updateRegister(data, c, combModel, false, false)))
|
||||||
.map(r -> SimpleRegisterComb.fromModel(r, List.of()).setCategorieInscrite(r.getCategoriesInscrites()));
|
.map(r -> SimpleRegisterComb.fromModel(r, List.of()).setCategorieInscrite(r.getCategoriesInscrites()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Uni<List<SimpleRegisterComb>> addRegistersComb(SecurityCtx securityCtx, Long id,
|
||||||
|
List<RegisterRequestData> datas,
|
||||||
|
String source) {
|
||||||
|
if (!"admin".equals(source))
|
||||||
|
return Uni.createFrom().failure(new DForbiddenException());
|
||||||
|
|
||||||
|
return Multi.createFrom().iterable(datas).onItem().transformToUni(data ->
|
||||||
|
makeImportUpdate(securityCtx, id, data).onFailure().recoverWithItem(t -> {
|
||||||
|
SimpleRegisterComb errorComb = new SimpleRegisterComb();
|
||||||
|
errorComb.setLicence(-42);
|
||||||
|
errorComb.setFname("ERROR");
|
||||||
|
errorComb.setLname(t.getMessage());
|
||||||
|
return errorComb;
|
||||||
|
})).concatenate().collect().asList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Uni<SimpleRegisterComb> makeImportUpdate(SecurityCtx securityCtx, Long id, RegisterRequestData data) {
|
||||||
|
if (data.getLicence() == null || data.getLicence() != -1) { // not a guest
|
||||||
|
return permService.hasEditPerm(securityCtx, id)
|
||||||
|
.chain(c -> findComb(data.getLicence(), data.getFname(), data.getLname())
|
||||||
|
.call(combModel -> Mutiny.fetch(combModel.getLicences()))
|
||||||
|
.call(combModel -> {
|
||||||
|
if (c.getBanMembre() == null)
|
||||||
|
c.setBanMembre(new ArrayList<>());
|
||||||
|
c.getBanMembre().remove(combModel.getId());
|
||||||
|
return Panache.withTransaction(() -> repository.persist(c));
|
||||||
|
})
|
||||||
|
.chain(combModel -> updateRegister(data, c, combModel, true, true)))
|
||||||
|
.map(r -> SimpleRegisterComb.fromModel(r, r.getMembre().getLicences())
|
||||||
|
.setCategorieInscrite(r.getCategoriesInscrites()));
|
||||||
|
} else {
|
||||||
|
return permService.hasEditPerm(securityCtx, id)
|
||||||
|
.chain(c -> findGuestOrInit(data.getFname(), data.getLname(), c))
|
||||||
|
.invoke(Unchecked.consumer(model -> {
|
||||||
|
if (data.getCategorie() == null)
|
||||||
|
throw new DBadRequestException(trad.t("categorie.requise"));
|
||||||
|
model.setCategorie(data.getCategorie());
|
||||||
|
|
||||||
|
if (data.getGenre() == null) {
|
||||||
|
if (model.getGenre() == null)
|
||||||
|
data.setGenre(Genre.NA);
|
||||||
|
} else
|
||||||
|
model.setGenre(data.getGenre());
|
||||||
|
|
||||||
|
if (data.getClub() == null) {
|
||||||
|
if (model.getClub() == null)
|
||||||
|
data.setClub("");
|
||||||
|
} else
|
||||||
|
model.setClub(data.getClub());
|
||||||
|
|
||||||
|
if (data.getCountry() == null) {
|
||||||
|
if (model.getCountry() == null)
|
||||||
|
data.setCountry("FR");
|
||||||
|
} else
|
||||||
|
model.setCountry(data.getCountry());
|
||||||
|
|
||||||
|
if (model.getCompetition().getRequiredWeight().contains(model.getCategorie())) {
|
||||||
|
if (data.getCountry() != null)
|
||||||
|
model.setWeight(data.getWeight());
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.call(g -> Mutiny.fetch(g.getCategoriesInscrites()))
|
||||||
|
.call(g -> catPresetRepository.list("competition = ?1 AND id IN ?2", g.getCompetition(),
|
||||||
|
data.getCategoriesInscrites())
|
||||||
|
.invoke(cats -> {
|
||||||
|
g.getCategoriesInscrites().clear();
|
||||||
|
g.getCategoriesInscrites().addAll(cats);
|
||||||
|
g.getCategoriesInscrites().removeIf(cat -> cat.getCategories().stream()
|
||||||
|
.noneMatch(e -> e.getCategorie().equals(g.getCategorie())));
|
||||||
|
}))
|
||||||
|
.chain(model -> Panache.withTransaction(() -> competitionGuestRepository.persist(model))
|
||||||
|
.call(r -> model.getCompetition().getSystem() == CompetitionSystem.INTERNAL ?
|
||||||
|
sRegister.sendRegister(model.getCompetition().getUuid(),
|
||||||
|
r) : Uni.createFrom().voidItem()))
|
||||||
|
.map(g -> SimpleRegisterComb.fromModel(g).setCategorieInscrite(g.getCategoriesInscrites()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private Uni<RegisterModel> updateRegister(RegisterRequestData data, CompetitionModel c,
|
private Uni<RegisterModel> updateRegister(RegisterRequestData data, CompetitionModel c,
|
||||||
MembreModel combModel, boolean admin) {
|
MembreModel combModel, boolean admin, boolean append) {
|
||||||
return registerRepository.find("competition = ?1 AND membre = ?2", c, combModel).firstResult()
|
return registerRepository.find("competition = ?1 AND membre = ?2", c, combModel).firstResult()
|
||||||
.onFailure().recoverWithNull()
|
.onFailure().recoverWithNull()
|
||||||
.map(Unchecked.function(r -> {
|
.map(Unchecked.function(r -> {
|
||||||
if (r != null) {
|
if (r != null) {
|
||||||
if (!admin && r.isLockEdit())
|
if (!admin && r.isLockEdit())
|
||||||
throw new DForbiddenException(trad.t("insc.err3"));
|
throw new DForbiddenException(trad.t("insc.err3"));
|
||||||
r.setOverCategory(data.getOverCategory());
|
if (data.getOverCategory() != null || !append)
|
||||||
|
if (data.getOverCategory() == null)
|
||||||
|
r.setOverCategory(0);
|
||||||
|
else
|
||||||
|
r.setOverCategory(data.getOverCategory());
|
||||||
r.setCategorie(
|
r.setCategorie(
|
||||||
(combModel.getBirth_date() == null) ? combModel.getCategorie() :
|
(combModel.getBirth_date() == null) ? combModel.getCategorie() :
|
||||||
Utils.getCategoryFormBirthDate(combModel.getBirth_date(),
|
Utils.getCategoryFormBirthDate(combModel.getBirth_date(),
|
||||||
@ -464,7 +546,8 @@ public class CompetitionService {
|
|||||||
if (days > -7)
|
if (days > -7)
|
||||||
r.setClub(combModel.getClub());
|
r.setClub(combModel.getClub());
|
||||||
if (c.getRequiredWeight().contains(r.getCategorie2()))
|
if (c.getRequiredWeight().contains(r.getCategorie2()))
|
||||||
r.setWeight(data.getWeight());
|
if (data.getCountry() != null || !append)
|
||||||
|
r.setWeight(data.getWeight());
|
||||||
if (admin) {
|
if (admin) {
|
||||||
r.setWeightReal(data.getWeightReal());
|
r.setWeightReal(data.getWeightReal());
|
||||||
r.setLockEdit(data.isLockEdit());
|
r.setLockEdit(data.isLockEdit());
|
||||||
@ -510,6 +593,27 @@ public class CompetitionService {
|
|||||||
sRegister.sendRegister(c.getUuid(), r) : Uni.createFrom().voidItem());
|
sRegister.sendRegister(c.getUuid(), r) : Uni.createFrom().voidItem());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Uni<CompetitionGuestModel> findGuestOrInit(String fname, String lname, CompetitionModel competition) {
|
||||||
|
if (fname == null || lname == null)
|
||||||
|
return Uni.createFrom().failure(new DBadRequestException(trad.t("nom.et.prenom.requis")));
|
||||||
|
return competitionGuestRepository.find(
|
||||||
|
"unaccent(lname) ILIKE unaccent(?1) AND unaccent(fname) ILIKE unaccent(?2) AND competition = ?3",
|
||||||
|
lname, fname, competition).firstResult()
|
||||||
|
.map(guestModel -> {
|
||||||
|
if (guestModel == null) {
|
||||||
|
CompetitionGuestModel model = new CompetitionGuestModel();
|
||||||
|
model.setFname(fname);
|
||||||
|
if (lname.equals("__team"))
|
||||||
|
model.setLname("_team");
|
||||||
|
else
|
||||||
|
model.setLname(lname);
|
||||||
|
model.setCompetition(competition);
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
return guestModel;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private Uni<MembreModel> findComb(Long licence, String fname, String lname) {
|
private Uni<MembreModel> findComb(Long licence, String fname, String lname) {
|
||||||
if (licence != null && licence > 0) {
|
if (licence != null && licence > 0) {
|
||||||
return combRepository.find("licence = ?1", licence).firstResult()
|
return combRepository.find("licence = ?1", licence).firstResult()
|
||||||
@ -821,7 +925,7 @@ public class CompetitionService {
|
|||||||
.call(m -> Panache.withTransaction(() ->
|
.call(m -> Panache.withTransaction(() ->
|
||||||
helloAssoRepository.persist(
|
helloAssoRepository.persist(
|
||||||
new HelloAssoRegisterModel(cm, m, data.getId()))))
|
new HelloAssoRegisterModel(cm, m, data.getId()))))
|
||||||
.chain(m -> updateRegister(req, cm, m, true)))
|
.chain(m -> updateRegister(req, cm, m, true, true)))
|
||||||
.onFailure().recoverWithItem(throwable -> {
|
.onFailure().recoverWithItem(throwable -> {
|
||||||
fail.add("%s %s - licence n°%d".formatted(item.getUser().getLastName(),
|
fail.add("%s %s - licence n°%d".formatted(item.getUser().getLastName(),
|
||||||
item.getUser().getFirstName(), optional.get()));
|
item.getUser().getFirstName(), optional.get()));
|
||||||
|
|||||||
@ -50,6 +50,16 @@ public class CompetitionEndpoints {
|
|||||||
return service.addRegisterComb(securityCtx, id, data, source);
|
return service.addRegisterComb(securityCtx, id, data, source);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("{id}/registers/{source}")
|
||||||
|
@Authenticated
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
@Operation(hidden = true)
|
||||||
|
public Uni<List<SimpleRegisterComb>> addRegistersComb(@PathParam("id") Long id, @PathParam("source") String source,
|
||||||
|
List<RegisterRequestData> data) {
|
||||||
|
return service.addRegistersComb(securityCtx, id, data, source);
|
||||||
|
}
|
||||||
|
|
||||||
@DELETE
|
@DELETE
|
||||||
@Path("{id}/register/{comb_id}/{source}")
|
@Path("{id}/register/{comb_id}/{source}")
|
||||||
@Authenticated
|
@Authenticated
|
||||||
|
|||||||
@ -20,7 +20,7 @@ public class RegisterRequestData {
|
|||||||
|
|
||||||
private Integer weight;
|
private Integer weight;
|
||||||
private Integer weightReal;
|
private Integer weightReal;
|
||||||
private int overCategory;
|
private Integer overCategory;
|
||||||
private boolean lockEdit = false;
|
private boolean lockEdit = false;
|
||||||
private List<Long> categoriesInscrites;
|
private List<Long> categoriesInscrites;
|
||||||
|
|
||||||
|
|||||||
@ -8,12 +8,14 @@ import fr.titionfire.ffsaf.utils.Utils;
|
|||||||
import io.quarkus.runtime.annotations.RegisterForReflection;
|
import io.quarkus.runtime.annotations.RegisterForReflection;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
@RegisterForReflection
|
@RegisterForReflection
|
||||||
public class SimpleRegisterComb {
|
public class SimpleRegisterComb {
|
||||||
private long id;
|
private long id;
|
||||||
|
|||||||
55
src/main/webapp/package-lock.json
generated
55
src/main/webapp/package-lock.json
generated
@ -18,7 +18,7 @@
|
|||||||
"@fortawesome/react-fontawesome": "^3.1.1",
|
"@fortawesome/react-fontawesome": "^3.1.1",
|
||||||
"axios": "^1.13.2",
|
"axios": "^1.13.2",
|
||||||
"browser-image-compression": "^2.0.2",
|
"browser-image-compression": "^2.0.2",
|
||||||
"i18next": "^25.7.4",
|
"i18next": "^25.8.0",
|
||||||
"i18next-browser-languagedetector": "^8.2.0",
|
"i18next-browser-languagedetector": "^8.2.0",
|
||||||
"i18next-http-backend": "^3.0.2",
|
"i18next-http-backend": "^3.0.2",
|
||||||
"jszip": "^3.10.1",
|
"jszip": "^3.10.1",
|
||||||
@ -33,12 +33,12 @@
|
|||||||
"react-loader-spinner": "^8.0.2",
|
"react-loader-spinner": "^8.0.2",
|
||||||
"react-router-dom": "^7.12.0",
|
"react-router-dom": "^7.12.0",
|
||||||
"react-toastify": "^11.0.5",
|
"react-toastify": "^11.0.5",
|
||||||
"recharts": "^3.6.0",
|
"recharts": "^3.7.0",
|
||||||
"xlsx": "^0.18.5",
|
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz",
|
||||||
"xlsx-js-style": "^1.2.0"
|
"xlsx-js-style": "^1.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/react": "^19.2.8",
|
"@types/react": "^19.2.9",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
"@vitejs/plugin-react": "^5.1.2",
|
"@vitejs/plugin-react": "^5.1.2",
|
||||||
"eslint": "^9.39.2",
|
"eslint": "^9.39.2",
|
||||||
@ -1606,9 +1606,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/react": {
|
"node_modules/@types/react": {
|
||||||
"version": "19.2.8",
|
"version": "19.2.9",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.8.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.9.tgz",
|
||||||
"integrity": "sha512-3MbSL37jEchWZz2p2mjntRZtPt837ij10ApxKfgmXCTuHWagYg7iA5bqPw6C8BMPfwidlvfPI/fxOc42HLhcyg==",
|
"integrity": "sha512-Lpo8kgb/igvMIPeNV2rsYKTgaORYdO1XGVZ4Qz3akwOj0ySGYMPlQWa8BaLn0G63D1aSaAQ5ldR06wCpChQCjA==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
@ -2048,14 +2048,6 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/codepage": {
|
|
||||||
"version": "1.15.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz",
|
|
||||||
"integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/combined-stream": {
|
"node_modules/combined-stream": {
|
||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||||
@ -3357,9 +3349,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/i18next": {
|
"node_modules/i18next": {
|
||||||
"version": "25.7.4",
|
"version": "25.8.0",
|
||||||
"resolved": "https://registry.npmjs.org/i18next/-/i18next-25.7.4.tgz",
|
"resolved": "https://registry.npmjs.org/i18next/-/i18next-25.8.0.tgz",
|
||||||
"integrity": "sha512-hRkpEblXXcXSNbw8mBNq9042OEetgyB/ahc/X17uV/khPwzV+uB8RHceHh3qavyrkPJvmXFKXME2Sy1E0KjAfw==",
|
"integrity": "sha512-urrg4HMFFMQZ2bbKRK7IZ8/CTE7D8H4JRlAwqA2ZwDRFfdd0K/4cdbNNLgfn9mo+I/h9wJu61qJzH7jCFAhUZQ==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "individual",
|
"type": "individual",
|
||||||
@ -3374,6 +3366,7 @@
|
|||||||
"url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
|
"url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"license": "MIT",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.28.4"
|
"@babel/runtime": "^7.28.4"
|
||||||
@ -4674,9 +4667,13 @@
|
|||||||
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
|
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
|
||||||
},
|
},
|
||||||
"node_modules/recharts": {
|
"node_modules/recharts": {
|
||||||
"version": "3.6.0",
|
"version": "3.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/recharts/-/recharts-3.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/recharts/-/recharts-3.7.0.tgz",
|
||||||
"integrity": "sha512-L5bjxvQRAe26RlToBAziKUB7whaGKEwD3znoM6fz3DrTowCIC/FnJYnuq1GEzB8Zv2kdTfaxQfi5GoH0tBinyg==",
|
"integrity": "sha512-l2VCsy3XXeraxIID9fx23eCb6iCBsxUQDnE8tWm6DFdszVAO7WVY/ChAD9wVit01y6B2PMupYiMmQwhgPHc9Ew==",
|
||||||
|
"license": "MIT",
|
||||||
|
"workspaces": [
|
||||||
|
"www"
|
||||||
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@reduxjs/toolkit": "1.x.x || 2.x.x",
|
"@reduxjs/toolkit": "1.x.x || 2.x.x",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
@ -5735,18 +5732,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/xlsx": {
|
"node_modules/xlsx": {
|
||||||
"version": "0.18.5",
|
"version": "0.20.3",
|
||||||
"resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz",
|
"resolved": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz",
|
||||||
"integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==",
|
"integrity": "sha512-oLDq3jw7AcLqKWH2AhCpVTZl8mf6X2YReP+Neh0SJUzV/BdZYjth94tG5toiMB1PPrYtxOCfaoUCkvtuH+3AJA==",
|
||||||
"dependencies": {
|
"license": "Apache-2.0",
|
||||||
"adler-32": "~1.3.0",
|
|
||||||
"cfb": "~1.2.1",
|
|
||||||
"codepage": "~1.15.0",
|
|
||||||
"crc-32": "~1.2.1",
|
|
||||||
"ssf": "~0.11.2",
|
|
||||||
"wmf": "~1.0.1",
|
|
||||||
"word": "~0.3.0"
|
|
||||||
},
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"xlsx": "bin/xlsx.njs"
|
"xlsx": "bin/xlsx.njs"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -20,7 +20,7 @@
|
|||||||
"@fortawesome/react-fontawesome": "^3.1.1",
|
"@fortawesome/react-fontawesome": "^3.1.1",
|
||||||
"axios": "^1.13.2",
|
"axios": "^1.13.2",
|
||||||
"browser-image-compression": "^2.0.2",
|
"browser-image-compression": "^2.0.2",
|
||||||
"i18next": "^25.7.4",
|
"i18next": "^25.8.0",
|
||||||
"i18next-browser-languagedetector": "^8.2.0",
|
"i18next-browser-languagedetector": "^8.2.0",
|
||||||
"i18next-http-backend": "^3.0.2",
|
"i18next-http-backend": "^3.0.2",
|
||||||
"jszip": "^3.10.1",
|
"jszip": "^3.10.1",
|
||||||
@ -35,12 +35,12 @@
|
|||||||
"react-loader-spinner": "^8.0.2",
|
"react-loader-spinner": "^8.0.2",
|
||||||
"react-router-dom": "^7.12.0",
|
"react-router-dom": "^7.12.0",
|
||||||
"react-toastify": "^11.0.5",
|
"react-toastify": "^11.0.5",
|
||||||
"recharts": "^3.6.0",
|
"recharts": "^3.7.0",
|
||||||
"xlsx": "^0.18.5",
|
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz",
|
||||||
"xlsx-js-style": "^1.2.0"
|
"xlsx-js-style": "^1.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/react": "^19.2.8",
|
"@types/react": "^19.2.9",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
"@vitejs/plugin-react": "^5.1.2",
|
"@vitejs/plugin-react": "^5.1.2",
|
||||||
"eslint": "^9.39.2",
|
"eslint": "^9.39.2",
|
||||||
|
|||||||
@ -124,6 +124,7 @@
|
|||||||
"catégorie": "Category",
|
"catégorie": "Category",
|
||||||
"catégorieàAjouter": "Category to add",
|
"catégorieàAjouter": "Category to add",
|
||||||
"certificatMédical": "Medical certificate",
|
"certificatMédical": "Medical certificate",
|
||||||
|
"champAttendu": "Expected field",
|
||||||
"chargement...": "Loading...",
|
"chargement...": "Loading...",
|
||||||
"chargerLexcel": "Load Excel",
|
"chargerLexcel": "Load Excel",
|
||||||
"chargerLexcel.msg": "Please use the file above as a template; do not rename the columns or modify the license numbers.",
|
"chargerLexcel.msg": "Please use the file above as a template; do not rename the columns or modify the license numbers.",
|
||||||
@ -154,6 +155,7 @@
|
|||||||
"club_one": "Club",
|
"club_one": "Club",
|
||||||
"club_other": "Clubs",
|
"club_other": "Clubs",
|
||||||
"club_zero": "No club",
|
"club_zero": "No club",
|
||||||
|
"colonneDansLeFichier": "Column in the file",
|
||||||
"combattant": "fighter",
|
"combattant": "fighter",
|
||||||
"comp.aff.blason": "Display the club's coat of arms on screens",
|
"comp.aff.blason": "Display the club's coat of arms on screens",
|
||||||
"comp.aff.flag": "Display the fighter's country on screens",
|
"comp.aff.flag": "Display the fighter's country on screens",
|
||||||
@ -231,6 +233,8 @@
|
|||||||
"comp.toast.register.add.error": "Fighter not found",
|
"comp.toast.register.add.error": "Fighter not found",
|
||||||
"comp.toast.register.add.pending": "Search in progress",
|
"comp.toast.register.add.pending": "Search in progress",
|
||||||
"comp.toast.register.add.success": "Fighter found and added/updated",
|
"comp.toast.register.add.success": "Fighter found and added/updated",
|
||||||
|
"comp.toast.register.addMultiple.success_one": "Successful import for 1 fighter",
|
||||||
|
"comp.toast.register.addMultiple.success_other": "Successful import for {{count}} fighters",
|
||||||
"comp.toast.register.ban.error": "Error",
|
"comp.toast.register.ban.error": "Error",
|
||||||
"comp.toast.register.ban.pending": "Unregistration in progress",
|
"comp.toast.register.ban.pending": "Unregistration in progress",
|
||||||
"comp.toast.register.ban.success": "Fighter unregistered and banned",
|
"comp.toast.register.ban.success": "Fighter unregistered and banned",
|
||||||
@ -300,6 +304,7 @@
|
|||||||
"erreurDePaiement": "Payment error😕",
|
"erreurDePaiement": "Payment error😕",
|
||||||
"erreurDePaiement.detail": "Error message:",
|
"erreurDePaiement.detail": "Error message:",
|
||||||
"erreurDePaiement.msg": "An error occurred while processing your payment. Please try again later.",
|
"erreurDePaiement.msg": "An error occurred while processing your payment. Please try again later.",
|
||||||
|
"erreurPourLinscription": "Registration error",
|
||||||
"espaceAdministration": "Administration space",
|
"espaceAdministration": "Administration space",
|
||||||
"f": "F",
|
"f": "F",
|
||||||
"faitPar": "Done by",
|
"faitPar": "Done by",
|
||||||
@ -322,6 +327,9 @@
|
|||||||
},
|
},
|
||||||
"homme": "Male",
|
"homme": "Male",
|
||||||
"horairesD'entraînements": "Training schedules",
|
"horairesD'entraînements": "Training schedules",
|
||||||
|
"importationDuFichier": "Importing the file",
|
||||||
|
"importerDesCombattants": "Import fighters",
|
||||||
|
"importerDesInvités": "Import guests",
|
||||||
"information": "Information",
|
"information": "Information",
|
||||||
"invité": "guest",
|
"invité": "guest",
|
||||||
"keepEmpty": "Leave blank to make no changes.",
|
"keepEmpty": "Leave blank to make no changes.",
|
||||||
@ -330,6 +338,8 @@
|
|||||||
"licenceNo": "License no. {{no}}",
|
"licenceNo": "License no. {{no}}",
|
||||||
"lieu": "Place",
|
"lieu": "Place",
|
||||||
"lieuxDentraînements": "Training locations",
|
"lieuxDentraînements": "Training locations",
|
||||||
|
"ligneIgnorée1": "Line ignored: missing name, first name or category.",
|
||||||
|
"ligneIgnorée2": "Line ignored: missing first name or license.",
|
||||||
"loading": "Loading...",
|
"loading": "Loading...",
|
||||||
"me": {
|
"me": {
|
||||||
"result": {
|
"result": {
|
||||||
@ -458,6 +468,8 @@
|
|||||||
"nouveauClub": "New club",
|
"nouveauClub": "New club",
|
||||||
"nouveauMembre": "New member",
|
"nouveauMembre": "New member",
|
||||||
"nouvelEmail": "New email",
|
"nouvelEmail": "New email",
|
||||||
|
"numéroDeLaLigneDentête": "Header line number",
|
||||||
|
"numéroDeLigne": "Line number",
|
||||||
"ou": "or",
|
"ou": "or",
|
||||||
"oui": "Yes",
|
"oui": "Yes",
|
||||||
"outdated_session": {
|
"outdated_session": {
|
||||||
@ -493,6 +505,7 @@
|
|||||||
"peutSinscrire": "Can register?",
|
"peutSinscrire": "Can register?",
|
||||||
"photos": "Photos",
|
"photos": "Photos",
|
||||||
"plastron": "Breastplate",
|
"plastron": "Breastplate",
|
||||||
|
"poids": "Weight",
|
||||||
"poidsDemandéPour": "Weight required for",
|
"poidsDemandéPour": "Weight required for",
|
||||||
"prenom": "First name",
|
"prenom": "First name",
|
||||||
"protectionDeBras": "Arm protection",
|
"protectionDeBras": "Arm protection",
|
||||||
@ -577,9 +590,75 @@
|
|||||||
"validerLicence_other": "Validate the {{count}} selected licenses",
|
"validerLicence_other": "Validate the {{count}} selected licenses",
|
||||||
"validerLicence_zero": "$t(validerLicence_other)",
|
"validerLicence_zero": "$t(validerLicence_other)",
|
||||||
"validée": "Validated",
|
"validée": "Validated",
|
||||||
|
"veuillezAssocierChaqueChampàUneColonneDuFichier": "Please associate each field with a column in the file",
|
||||||
|
"veuillezIndiqueràQuelle": "Please indicate on which line the headers are located in the file",
|
||||||
|
"veuillezMapperLesColonnesSuivantes": "Please map the following columns",
|
||||||
"voir/modifierLesParticipants": "View/Edit participants",
|
"voir/modifierLesParticipants": "View/Edit participants",
|
||||||
"voirLesStatues": "View statues",
|
"voirLesStatues": "View statues",
|
||||||
"vousNêtesPasEncoreInscrit": "You are not yet registered or your registration has not yet been entered on the intranet",
|
"vousNêtesPasEncoreInscrit": "You are not yet registered or your registration has not yet been entered on the intranet",
|
||||||
"à": "at",
|
"à": "at",
|
||||||
"étatDeLaDemande": "Request status"
|
"étatDeLaDemande": "Request status",
|
||||||
|
"fileImport.variants": {
|
||||||
|
"licence": [
|
||||||
|
"license",
|
||||||
|
"licence",
|
||||||
|
"license number",
|
||||||
|
"license ID",
|
||||||
|
"ID license",
|
||||||
|
"licence no"
|
||||||
|
],
|
||||||
|
"pays": [
|
||||||
|
"country",
|
||||||
|
"pays",
|
||||||
|
"country of residence",
|
||||||
|
"origin country"
|
||||||
|
],
|
||||||
|
"nom": [
|
||||||
|
"last name",
|
||||||
|
"nom",
|
||||||
|
"family name",
|
||||||
|
"surname",
|
||||||
|
"lastname"
|
||||||
|
],
|
||||||
|
"prenom": [
|
||||||
|
"first name",
|
||||||
|
"prénom",
|
||||||
|
"given name",
|
||||||
|
"first given name"
|
||||||
|
],
|
||||||
|
"genre": [
|
||||||
|
"gender",
|
||||||
|
"genre",
|
||||||
|
"sex",
|
||||||
|
"civility"
|
||||||
|
],
|
||||||
|
"weight": [
|
||||||
|
"weight",
|
||||||
|
"poids",
|
||||||
|
"weight (kg)",
|
||||||
|
"actual weight",
|
||||||
|
"mass"
|
||||||
|
],
|
||||||
|
"categorie": [
|
||||||
|
"category",
|
||||||
|
"catégorie",
|
||||||
|
"weight category",
|
||||||
|
"age category"
|
||||||
|
],
|
||||||
|
"overCategory": [
|
||||||
|
"over category",
|
||||||
|
"surclassement",
|
||||||
|
"category override",
|
||||||
|
"over classification"
|
||||||
|
],
|
||||||
|
"club": [
|
||||||
|
"club",
|
||||||
|
"club name",
|
||||||
|
"association",
|
||||||
|
"association name"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"comp.toast.registers.addMultiple.error": "Import failed",
|
||||||
|
"comp.toast.registers.addMultiple.pending": "Import in progress",
|
||||||
|
"comp.toast.registers.addMultiple.success": "Import completed successfully 🎉"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -124,6 +124,7 @@
|
|||||||
"catégorie": "Catégorie",
|
"catégorie": "Catégorie",
|
||||||
"catégorieàAjouter": "Catégorie à ajouter",
|
"catégorieàAjouter": "Catégorie à ajouter",
|
||||||
"certificatMédical": "Certificat médical",
|
"certificatMédical": "Certificat médical",
|
||||||
|
"champAttendu": "Champ attendu",
|
||||||
"chargement...": "Chargement...",
|
"chargement...": "Chargement...",
|
||||||
"chargerLexcel": "Charger l'Excel",
|
"chargerLexcel": "Charger l'Excel",
|
||||||
"chargerLexcel.msg": "Merci d'utiliser le fichier ci-dessus comme base, ne pas renommer les colonnes ni modifier les n° de licences.",
|
"chargerLexcel.msg": "Merci d'utiliser le fichier ci-dessus comme base, ne pas renommer les colonnes ni modifier les n° de licences.",
|
||||||
@ -154,6 +155,7 @@
|
|||||||
"club_one": "Club",
|
"club_one": "Club",
|
||||||
"club_other": "Clubs",
|
"club_other": "Clubs",
|
||||||
"club_zero": "Sans club",
|
"club_zero": "Sans club",
|
||||||
|
"colonneDansLeFichier": "Colonne dans le fichier",
|
||||||
"combattant": "combattant",
|
"combattant": "combattant",
|
||||||
"comp.aff.blason": "Afficher le blason du club sur les écrans",
|
"comp.aff.blason": "Afficher le blason du club sur les écrans",
|
||||||
"comp.aff.flag": "Afficher le pays du combattant sur les écrans",
|
"comp.aff.flag": "Afficher le pays du combattant sur les écrans",
|
||||||
@ -173,7 +175,7 @@
|
|||||||
"comp.error1": "La date de fin doit être postérieure à la date de début.",
|
"comp.error1": "La date de fin doit être postérieure à la date de début.",
|
||||||
"comp.error2": "Veuillez renseigner les dates de début et de fin d'inscription.",
|
"comp.error2": "Veuillez renseigner les dates de début et de fin d'inscription.",
|
||||||
"comp.error3": "La date de fin d'inscription doit être postérieure à la date de début d'inscription.",
|
"comp.error3": "La date de fin d'inscription doit être postérieure à la date de début d'inscription.",
|
||||||
"comp.exporterLesInscription": "Exporter les inscription",
|
"comp.exporterLesInscription": "Exporter les inscriptions",
|
||||||
"comp.ha.emailDeRéceptionDesInscriptionséchoué": "Email de réception des inscriptions échoué",
|
"comp.ha.emailDeRéceptionDesInscriptionséchoué": "Email de réception des inscriptions échoué",
|
||||||
"comp.ha.error1": "Veuillez renseigner l'URL de la billetterie HelloAsso et les tarifs associés.",
|
"comp.ha.error1": "Veuillez renseigner l'URL de la billetterie HelloAsso et les tarifs associés.",
|
||||||
"comp.ha.error2": "L'URL de la billetterie HelloAsso n'est pas valide. Veuillez vérifier le format de l'URL.",
|
"comp.ha.error2": "L'URL de la billetterie HelloAsso n'est pas valide. Veuillez vérifier le format de l'URL.",
|
||||||
@ -231,6 +233,8 @@
|
|||||||
"comp.toast.register.add.error": "Combattant non trouvé",
|
"comp.toast.register.add.error": "Combattant non trouvé",
|
||||||
"comp.toast.register.add.pending": "Recherche en cours",
|
"comp.toast.register.add.pending": "Recherche en cours",
|
||||||
"comp.toast.register.add.success": "Combattant trouvé et ajouté/mis à jour",
|
"comp.toast.register.add.success": "Combattant trouvé et ajouté/mis à jour",
|
||||||
|
"comp.toast.register.addMultiple.success_one": "Importation réussie pour 1 combattant",
|
||||||
|
"comp.toast.register.addMultiple.success_other": "Importation réussie pour {{count}} combattants",
|
||||||
"comp.toast.register.ban.error": "Erreur",
|
"comp.toast.register.ban.error": "Erreur",
|
||||||
"comp.toast.register.ban.pending": "Désinscription en cours",
|
"comp.toast.register.ban.pending": "Désinscription en cours",
|
||||||
"comp.toast.register.ban.success": "Combattant désinscrit et bannie",
|
"comp.toast.register.ban.success": "Combattant désinscrit et bannie",
|
||||||
@ -300,6 +304,7 @@
|
|||||||
"erreurDePaiement": "Erreur de paiement😕",
|
"erreurDePaiement": "Erreur de paiement😕",
|
||||||
"erreurDePaiement.detail": "Message d'erreur :",
|
"erreurDePaiement.detail": "Message d'erreur :",
|
||||||
"erreurDePaiement.msg": "Une erreur est survenue lors du traitement de votre paiement. Veuillez réessayer plus tard.",
|
"erreurDePaiement.msg": "Une erreur est survenue lors du traitement de votre paiement. Veuillez réessayer plus tard.",
|
||||||
|
"erreurPourLinscription": "Erreur pour l'inscription",
|
||||||
"espaceAdministration": "Espace administration",
|
"espaceAdministration": "Espace administration",
|
||||||
"f": "F",
|
"f": "F",
|
||||||
"faitPar": "Fait par",
|
"faitPar": "Fait par",
|
||||||
@ -322,6 +327,9 @@
|
|||||||
},
|
},
|
||||||
"homme": "Homme",
|
"homme": "Homme",
|
||||||
"horairesD'entraînements": "Horaires d'entraînements",
|
"horairesD'entraînements": "Horaires d'entraînements",
|
||||||
|
"importationDuFichier": "Importation du fichier",
|
||||||
|
"importerDesCombattants": "Importer des combattants",
|
||||||
|
"importerDesInvités": "Importer des invités",
|
||||||
"information": "Information",
|
"information": "Information",
|
||||||
"invité": "invité",
|
"invité": "invité",
|
||||||
"keepEmpty": "Laissez vide pour ne rien changer.",
|
"keepEmpty": "Laissez vide pour ne rien changer.",
|
||||||
@ -330,6 +338,8 @@
|
|||||||
"licenceNo": "Licence n°{{no}}",
|
"licenceNo": "Licence n°{{no}}",
|
||||||
"lieu": "Lieu",
|
"lieu": "Lieu",
|
||||||
"lieuxDentraînements": "Lieux d'entraînements",
|
"lieuxDentraînements": "Lieux d'entraînements",
|
||||||
|
"ligneIgnorée1": "Ligne ignorée : nom, prénom ou catégorie manquante.",
|
||||||
|
"ligneIgnorée2": "Ligne ignorée : nom prénom ou licence manquante.",
|
||||||
"loading": "Chargement...",
|
"loading": "Chargement...",
|
||||||
"me": {
|
"me": {
|
||||||
"result": {
|
"result": {
|
||||||
@ -458,6 +468,8 @@
|
|||||||
"nouveauClub": "Nouveau club",
|
"nouveauClub": "Nouveau club",
|
||||||
"nouveauMembre": "Nouveau membre",
|
"nouveauMembre": "Nouveau membre",
|
||||||
"nouvelEmail": "Nouvel email",
|
"nouvelEmail": "Nouvel email",
|
||||||
|
"numéroDeLaLigneDentête": "Numéro de la ligne d'en-tête",
|
||||||
|
"numéroDeLigne": "Numéro de ligne",
|
||||||
"ou": "Ou",
|
"ou": "Ou",
|
||||||
"oui": "Oui",
|
"oui": "Oui",
|
||||||
"outdated_session": {
|
"outdated_session": {
|
||||||
@ -493,6 +505,7 @@
|
|||||||
"peutSinscrire": "Peut s'inscrire?",
|
"peutSinscrire": "Peut s'inscrire?",
|
||||||
"photos": "Photos",
|
"photos": "Photos",
|
||||||
"plastron": "Plastron",
|
"plastron": "Plastron",
|
||||||
|
"poids": "Poids",
|
||||||
"poidsDemandéPour": "Poids demandé pour",
|
"poidsDemandéPour": "Poids demandé pour",
|
||||||
"prenom": "Prénom",
|
"prenom": "Prénom",
|
||||||
"protectionDeBras": "Protection de bras",
|
"protectionDeBras": "Protection de bras",
|
||||||
@ -577,9 +590,79 @@
|
|||||||
"validerLicence_other": "Valider les {{count}} licences sélectionnées",
|
"validerLicence_other": "Valider les {{count}} licences sélectionnées",
|
||||||
"validerLicence_zero": "$t(validerLicence_other)",
|
"validerLicence_zero": "$t(validerLicence_other)",
|
||||||
"validée": "Validée",
|
"validée": "Validée",
|
||||||
|
"veuillezAssocierChaqueChampàUneColonneDuFichier": "Veuillez associer chaque champ à une colonne du fichier",
|
||||||
|
"veuillezIndiqueràQuelle": "Veuillez indiquer à quelle ligne se trouvent les en-têtes dans le fichier",
|
||||||
|
"veuillezMapperLesColonnesSuivantes": "Veuillez mapper les colonnes suivantes",
|
||||||
"voir/modifierLesParticipants": "Voir/Modifier les participants",
|
"voir/modifierLesParticipants": "Voir/Modifier les participants",
|
||||||
"voirLesStatues": "Voir les statues",
|
"voirLesStatues": "Voir les statues",
|
||||||
"vousNêtesPasEncoreInscrit": "Vous n'êtes pas encore inscrit ou votre inscription n'a pas encore été rentrée sur l'intranet",
|
"vousNêtesPasEncoreInscrit": "Vous n'êtes pas encore inscrit ou votre inscription n'a pas encore été rentrée sur l'intranet",
|
||||||
"à": "à",
|
"à": "à",
|
||||||
"étatDeLaDemande": "État de la demande"
|
"étatDeLaDemande": "État de la demande",
|
||||||
|
"fileImport.variants": {
|
||||||
|
"licence": [
|
||||||
|
"licence",
|
||||||
|
"n° licence",
|
||||||
|
"num licence",
|
||||||
|
"id licence",
|
||||||
|
"license",
|
||||||
|
"licence id"
|
||||||
|
],
|
||||||
|
"pays": [
|
||||||
|
"pays",
|
||||||
|
"country",
|
||||||
|
"pays de résidence",
|
||||||
|
"pays d'origine"
|
||||||
|
],
|
||||||
|
"nom": [
|
||||||
|
"nom",
|
||||||
|
"nom de famille",
|
||||||
|
"lastname",
|
||||||
|
"family name",
|
||||||
|
"nom complet"
|
||||||
|
],
|
||||||
|
"prenom": [
|
||||||
|
"prénom",
|
||||||
|
"prenom",
|
||||||
|
"first name",
|
||||||
|
"given name",
|
||||||
|
"prénom usuel"
|
||||||
|
],
|
||||||
|
"genre": [
|
||||||
|
"genre",
|
||||||
|
"sexe",
|
||||||
|
"gender",
|
||||||
|
"sex",
|
||||||
|
"civilité"
|
||||||
|
],
|
||||||
|
"weight": [
|
||||||
|
"poids",
|
||||||
|
"weight",
|
||||||
|
"poids (kg)",
|
||||||
|
"poids réel",
|
||||||
|
"masse"
|
||||||
|
],
|
||||||
|
"categorie": [
|
||||||
|
"catégorie",
|
||||||
|
"category",
|
||||||
|
"catégorie de poids",
|
||||||
|
"weight category",
|
||||||
|
"catégorie d'âge"
|
||||||
|
],
|
||||||
|
"overCategory": [
|
||||||
|
"surclassement",
|
||||||
|
"over category",
|
||||||
|
"surcatégorie",
|
||||||
|
"surclassement de catégorie"
|
||||||
|
],
|
||||||
|
"club": [
|
||||||
|
"club",
|
||||||
|
"nom du club",
|
||||||
|
"club name",
|
||||||
|
"association",
|
||||||
|
"nom de l'association"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"comp.toast.registers.addMultiple.error": "Erreur lors de l'importation des combattants",
|
||||||
|
"comp.toast.registers.addMultiple.pending": "Importation des combattants en cours...",
|
||||||
|
"comp.toast.registers.addMultiple.success": "Importation des combattants réussie 🎉"
|
||||||
}
|
}
|
||||||
|
|||||||
241
src/main/webapp/src/components/FileImport.jsx
Normal file
241
src/main/webapp/src/components/FileImport.jsx
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
import React, {useId, useRef, useState} from "react";
|
||||||
|
import {toast} from "react-toastify";
|
||||||
|
import * as XLSX from "xlsx";
|
||||||
|
import {useTranslation} from "react-i18next";
|
||||||
|
|
||||||
|
const parseValue = (value, type) => {
|
||||||
|
if (value === undefined || value === null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'Integer':
|
||||||
|
if (value === '')
|
||||||
|
return null;
|
||||||
|
const parsedInt = parseInt(value, 10);
|
||||||
|
return isNaN(parsedInt) ? null : parsedInt;
|
||||||
|
case 'Boolean':
|
||||||
|
if (typeof value === 'boolean')
|
||||||
|
return value;
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
const lowerValue = value.toLowerCase().trim();
|
||||||
|
if (lowerValue === 'oui' || lowerValue === 'true' || lowerValue === '1' || lowerValue === 'x') {
|
||||||
|
return true;
|
||||||
|
} else if (lowerValue === 'non' || lowerValue === 'false' || lowerValue === '0' || lowerValue === '') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
case 'Date':
|
||||||
|
if (value === '')
|
||||||
|
return null;
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
const date = new Date(value);
|
||||||
|
return isNaN(date.getTime()) ? null : date;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
case 'String':
|
||||||
|
default:
|
||||||
|
return String(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export function FileImport({onDataMapped, expectedFields, textButton}) {
|
||||||
|
const id = useId();
|
||||||
|
const [headerLineNumber, setHeaderLineNumber] = useState(1);
|
||||||
|
const [fileData, setFileData] = useState([]);
|
||||||
|
const [fileHeaders, setFileHeaders] = useState([]);
|
||||||
|
const [fileName, setFileName] = useState('');
|
||||||
|
const [selectedFile, setSelectedFile] = useState(null);
|
||||||
|
const [columnMappings, setColumnMappings] = useState({});
|
||||||
|
const fileChooser = useRef(null);
|
||||||
|
const openMappingModal = useRef(null);
|
||||||
|
const closeMappingModal = useRef(null);
|
||||||
|
const openHeaderLineModal = useRef(null);
|
||||||
|
const {t} = useTranslation();
|
||||||
|
|
||||||
|
|
||||||
|
// Fonction pour trouver la meilleure correspondance
|
||||||
|
const findBestMatch = (fileHeaders, expectedField) => {
|
||||||
|
const fieldLabel = expectedField.label.toLowerCase();
|
||||||
|
const fieldKey = expectedField.key.toLowerCase();
|
||||||
|
|
||||||
|
// Variantes possibles pour chaque champ (ex: "Nom" peut être "nom", "Nom de famille", etc.)
|
||||||
|
const variants = {
|
||||||
|
licence: t('fileImport.variants.licence', {returnObjects: true}),
|
||||||
|
pays: t('fileImport.variants.pays', {returnObjects: true}),
|
||||||
|
nom: t('fileImport.variants.nom', {returnObjects: true}),
|
||||||
|
prenom: t('fileImport.variants.prenom', {returnObjects: true}),
|
||||||
|
genre: t('fileImport.variants.genre', {returnObjects: true}),
|
||||||
|
weight: t('fileImport.variants.weight', {returnObjects: true}),
|
||||||
|
categorie: t('fileImport.variants.categorie', {returnObjects: true}),
|
||||||
|
overCategory: t('fileImport.variants.overCategory', {returnObjects: true}),
|
||||||
|
club: t('fileImport.variants.club', {returnObjects: true}),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Recherche de la meilleure correspondance
|
||||||
|
for (const header of fileHeaders) {
|
||||||
|
const lowerHeader = header.toLowerCase();
|
||||||
|
if (lowerHeader === fieldLabel || lowerHeader === fieldKey || (variants[fieldKey] && variants[fieldKey].includes(lowerHeader))) {
|
||||||
|
return header;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aucune correspondance trouvée
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Gestion du fichier sélectionné
|
||||||
|
const handleFileChange = (e) => {
|
||||||
|
const file = e.target.files[0];
|
||||||
|
if (!file) return;
|
||||||
|
|
||||||
|
setSelectedFile(file);
|
||||||
|
setFileName(file.name);
|
||||||
|
openHeaderLineModal.current.click();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Valider le numéro de la ligne d'en-tête et lire le fichier
|
||||||
|
const handleHeaderLineSubmit = () => {
|
||||||
|
if (!selectedFile) return;
|
||||||
|
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = (event) => {
|
||||||
|
const data = event.target.result;
|
||||||
|
const workbook = XLSX.read(data, {type: 'binary'});
|
||||||
|
const sheetName = workbook.SheetNames[0];
|
||||||
|
const sheet = workbook.Sheets[sheetName];
|
||||||
|
const jsonData = XLSX.utils.sheet_to_json(sheet, {header: 1});
|
||||||
|
|
||||||
|
// Extraire les en-têtes et les données en fonction du numéro de ligne
|
||||||
|
const headers = jsonData[headerLineNumber - 1];
|
||||||
|
const rows = jsonData.slice(headerLineNumber);
|
||||||
|
setFileHeaders(headers);
|
||||||
|
setFileData(rows);
|
||||||
|
|
||||||
|
// Initialiser le mapping avec pré-remplissage intelligent
|
||||||
|
const initialMappings = {};
|
||||||
|
expectedFields.forEach(field => {
|
||||||
|
const bestMatch = findBestMatch(headers, field);
|
||||||
|
initialMappings[field.key] = bestMatch || '';
|
||||||
|
});
|
||||||
|
setColumnMappings(initialMappings);
|
||||||
|
|
||||||
|
openMappingModal.current.click();
|
||||||
|
fileChooser.current.value = '';
|
||||||
|
};
|
||||||
|
reader.readAsBinaryString(selectedFile);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Mettre à jour le mapping d'une colonne
|
||||||
|
const handleMappingChange = (fieldKey, header) => {
|
||||||
|
setColumnMappings({
|
||||||
|
...columnMappings,
|
||||||
|
[fieldKey]: header,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Valider le mapping et envoyer les données
|
||||||
|
const handleSubmit = () => {
|
||||||
|
// Vérifier que tous les champs requis sont mappés
|
||||||
|
const missingMappings = expectedFields
|
||||||
|
.filter(field => field.mandatory && !columnMappings[field.key])
|
||||||
|
.map(field => field.label);
|
||||||
|
|
||||||
|
if (missingMappings.length > 0) {
|
||||||
|
toast.error(`${t('veuillezMapperLesColonnesSuivantes')} : ${missingMappings.join(', ')}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Préparer les données mappées et parsées
|
||||||
|
const mappedData = fileData.map(row => {
|
||||||
|
const mappedRow = {};
|
||||||
|
expectedFields.forEach(field => {
|
||||||
|
const headerIndex = fileHeaders.indexOf(columnMappings[field.key]);
|
||||||
|
const rawValue = headerIndex !== -1 ? row[headerIndex] : '';
|
||||||
|
mappedRow[field.key] = parseValue(rawValue, field.type);
|
||||||
|
});
|
||||||
|
return mappedRow;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Envoyer les données au parent ou au backend
|
||||||
|
onDataMapped(mappedData);
|
||||||
|
closeMappingModal.current.click();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFileChooser = () => {
|
||||||
|
fileChooser.current.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div>
|
||||||
|
<button type="button" className="btn btn-primary" onClick={handleFileChooser}>{textButton}</button>
|
||||||
|
|
||||||
|
<input ref={fileChooser} type="file" accept=".xlsx, .xls, .csv" onChange={handleFileChange} hidden={true}/>
|
||||||
|
<button ref={openMappingModal} type="button" className="btn btn-primary" data-bs-toggle="modal" data-bs-target={"#mappingModal" + id}
|
||||||
|
hidden={true}>A
|
||||||
|
</button>
|
||||||
|
<button ref={openHeaderLineModal} type="button" className="btn btn-primary" data-bs-toggle="modal"
|
||||||
|
data-bs-target={"#headerLineModal" + id} hidden={true}>B
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div className="modal fade" id={"mappingModal" + id} tabIndex="-1" aria-labelledby="mappingModalLabel" aria-hidden="true">
|
||||||
|
<div className="modal-dialog modal-dialog-scrollable modal-lg modal-fullscreen-lg-down">
|
||||||
|
<div className="modal-content">
|
||||||
|
<div className="modal-header">
|
||||||
|
<h1 className="modal-title fs-5" id="mappingModalLabel">{t('importationDuFichier')} {fileName}</h1>
|
||||||
|
<button ref={closeMappingModal} type="button" className="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div className="modal-body">
|
||||||
|
<p>{t('veuillezAssocierChaqueChampàUneColonneDuFichier')} :</p>
|
||||||
|
<table className="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">{t('champAttendu')}</th>
|
||||||
|
<th scope="col">{t('colonneDansLeFichier')}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{expectedFields.map(field => <tr key={field.key}>
|
||||||
|
<td>{field.label}</td>
|
||||||
|
<td>
|
||||||
|
<select className="form-select" value={columnMappings[field.key]}
|
||||||
|
onChange={(e) => handleMappingChange(field.key, e.target.value)}>
|
||||||
|
<option value="">{t('sélectionner...')}</option>
|
||||||
|
{fileHeaders.map(header => (<option key={header} value={header}>{header}</option>))}
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div className="modal-footer">
|
||||||
|
<button type="button" className="btn btn-secondary" data-bs-dismiss="modal">{t('button.annuler')}</button>
|
||||||
|
<button type="button" className="btn btn-primary" onClick={handleSubmit}>{t('button.confirmer')}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="modal fade" id={"headerLineModal" + id} tabIndex="-1" aria-labelledby="headerLineModalLabel" aria-hidden="true">
|
||||||
|
<div className="modal-dialog modal-dialog-scrollable">
|
||||||
|
<div className="modal-content">
|
||||||
|
<div className="modal-header">
|
||||||
|
<h1 className="modal-title fs-5" id="headerLineModalLabel">{t('numéroDeLaLigneDentête')}</h1>
|
||||||
|
<button type="button" className="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div className="modal-body">
|
||||||
|
<p>{t('veuillezIndiqueràQuelle')} :</p>
|
||||||
|
<div className="input-group mb-3">
|
||||||
|
<span className="input-group-text" id={id + "basic-addon1"}>{t('numéroDeLigne')}</span>
|
||||||
|
<input type="number" className="form-control" aria-describedby={id + "basic-addon1"} min="1" value={headerLineNumber}
|
||||||
|
onChange={(e) => setHeaderLineNumber(parseInt(e.target.value) || 1)}/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="modal-footer">
|
||||||
|
<button type="button" className="btn btn-secondary" data-bs-dismiss="modal">{t('button.annuler')}</button>
|
||||||
|
<button type="button" className="btn btn-primary" onClick={handleHeaderLineSubmit}>{t('button.confirmer')}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
@ -4,7 +4,7 @@ import {useFetch} from "../../hooks/useFetch.js";
|
|||||||
import {AxiosError} from "../../components/AxiosError.jsx";
|
import {AxiosError} from "../../components/AxiosError.jsx";
|
||||||
import {ThreeDots} from "react-loader-spinner";
|
import {ThreeDots} from "react-loader-spinner";
|
||||||
import React, {useEffect, useId, useReducer, useRef, useState} from "react";
|
import React, {useEffect, useId, useReducer, useRef, useState} from "react";
|
||||||
import {apiAxios, applyOverCategory, CatList, getCatName, getToastMessage} from "../../utils/Tools.js";
|
import {apiAxios, applyOverCategory, CatList, getCatFromName, getCatName, getToastMessage} from "../../utils/Tools.js";
|
||||||
import {toast} from "react-toastify";
|
import {toast} from "react-toastify";
|
||||||
import {SimpleReducer} from "../../utils/SimpleReducer.jsx";
|
import {SimpleReducer} from "../../utils/SimpleReducer.jsx";
|
||||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||||
@ -14,6 +14,7 @@ import * as XLSX from "xlsx-js-style";
|
|||||||
import {useCountries} from "../../hooks/useCountries.jsx";
|
import {useCountries} from "../../hooks/useCountries.jsx";
|
||||||
import {Trans, useTranslation} from "react-i18next";
|
import {Trans, useTranslation} from "react-i18next";
|
||||||
import {Checkbox} from "../../components/MemberCustomFiels.jsx";
|
import {Checkbox} from "../../components/MemberCustomFiels.jsx";
|
||||||
|
import {FileImport} from "../../components/FileImport.jsx";
|
||||||
|
|
||||||
export function CompetitionRegisterAdmin({source}) {
|
export function CompetitionRegisterAdmin({source}) {
|
||||||
const {id} = useParams()
|
const {id} = useParams()
|
||||||
@ -55,6 +56,26 @@ export function CompetitionRegisterAdmin({source}) {
|
|||||||
return response.data
|
return response.data
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
const sendRegisters = (new_state) => {
|
||||||
|
toast.promise(apiAxios.post(`/competition/${id}/registers/${source}`, new_state), getToastMessage("comp.toast.registers.addMultiple")
|
||||||
|
).then((response) => {
|
||||||
|
if (response.data.error)
|
||||||
|
return;
|
||||||
|
|
||||||
|
let i = 0;
|
||||||
|
response.data.forEach((d) => {
|
||||||
|
if (d.licence === -42) {
|
||||||
|
toast.warn(t('erreurPourLinscription') + " :" + d.lname, {autoClose: false});
|
||||||
|
} else {
|
||||||
|
dispatch({type: 'UPDATE_OR_ADD', payload: {id: d.id, data: d}})
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (i > 0)
|
||||||
|
toast.success(t('comp.toast.register.addMultiple.success', {count: i}))
|
||||||
|
dispatch({type: 'SORT', payload: sortName})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return <div>
|
return <div>
|
||||||
<h2>{t('comp.combattantsInscrits')}</h2>
|
<h2>{t('comp.combattantsInscrits')}</h2>
|
||||||
@ -98,12 +119,14 @@ export function CompetitionRegisterAdmin({source}) {
|
|||||||
filterNotWeight={filterNotWeight} setFilterNotWeight={setFilterNotWeight} source={source}/>
|
filterNotWeight={filterNotWeight} setFilterNotWeight={setFilterNotWeight} source={source}/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{source === "admin" && <FileOutput data={data}/>}
|
{source === "admin" && <div className="mb-2"><FileOutput data={data} data2={data2}/></div>}
|
||||||
|
{source === "admin" && <div className="mb-2"><FileImportComb data2={data2} sendRegisters={sendRegisters}/></div>}
|
||||||
|
{source === "admin" && <div className="mb-2"><FileImportGuest data2={data2} sendRegisters={sendRegisters}/></div>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Modal data2={data2} error2={error2} data3={data3} sendRegister={sendRegister} modalState={modalState} setModalState={setModalState}
|
<Modal_ data2={data2} error2={error2} data3={data3} sendRegister={sendRegister} modalState={modalState} setModalState={setModalState}
|
||||||
source={source}/>
|
source={source}/>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -346,7 +369,7 @@ function CategoriesList({error2, availableCats, fistCatInput, categories, setCat
|
|||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|
||||||
function Modal({data2, data3, error2, sendRegister, modalState, setModalState, source}) {
|
function Modal_({data2, data3, error2, sendRegister, modalState, setModalState, source}) {
|
||||||
const country = useCountries('fr')
|
const country = useCountries('fr')
|
||||||
const {t} = useTranslation();
|
const {t} = useTranslation();
|
||||||
const closeBtn = useRef(null);
|
const closeBtn = useRef(null);
|
||||||
@ -721,29 +744,78 @@ function MakeCentralPanel({data, data2, data3, dispatch, id, setModalState, sour
|
|||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|
||||||
function FileOutput({data}) {
|
function FileOutput({data, data2}) {
|
||||||
const {t} = useTranslation();
|
const {t} = useTranslation();
|
||||||
|
|
||||||
const handleFileDownload = () => {
|
const handleFileDownload = () => {
|
||||||
|
const catColumns = {}
|
||||||
|
for (const cat of data2) {
|
||||||
|
catColumns[cat.id] = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
const columnOrder = [
|
||||||
|
"licence", "pays", "nom", "prenom", "genre", "weight",
|
||||||
|
"categorie", "overCategory", "categorie2", "club",
|
||||||
|
...Object.keys(catColumns)
|
||||||
|
];
|
||||||
|
|
||||||
const dataOut = []
|
const dataOut = []
|
||||||
for (const e of data) {
|
for (const e of data) {
|
||||||
const tmp = {
|
const tmp = {
|
||||||
licence: e.licence,
|
licence: e.id <= 0 ? -1 : e.licence,
|
||||||
|
pays: e.country,
|
||||||
nom: e.lname,
|
nom: e.lname,
|
||||||
prenom: e.fname,
|
prenom: e.fname,
|
||||||
genre: e.genre,
|
genre: e.genre,
|
||||||
weight: e.weight,
|
weight: e.weightReal ? e.weightReal : e.weight,
|
||||||
categorie: e.categorie,
|
categorie: getCatName(e.categorie),
|
||||||
overCategory: e.overCategory,
|
overCategory: e.overCategory,
|
||||||
|
categorie2: getCatName(applyOverCategory(e.categorie, e.overCategory)),
|
||||||
club: e.club ? e.club.name : '',
|
club: e.club ? e.club.name : '',
|
||||||
|
...catColumns
|
||||||
|
}
|
||||||
|
for (const c of e.categoriesInscrites) {
|
||||||
|
tmp[c] = "X"
|
||||||
}
|
}
|
||||||
dataOut.push(tmp)
|
dataOut.push(tmp)
|
||||||
}
|
}
|
||||||
|
dataOut.sort((a, b) => a.prenom.localeCompare(b.prenom) || a.nom.localeCompare(b.nom));
|
||||||
|
|
||||||
|
const secondHeaders = [
|
||||||
|
"Licence", "Pays", "Nom", "Prénom", "Genre", "Poids",
|
||||||
|
"Catégorie normalizer", "Surclassement", "Catégorie d'inscription", "Club",
|
||||||
|
...Object.keys(catColumns).map(id => data2.find(p => p.id === Number(id))?.name)
|
||||||
|
];
|
||||||
|
const headers = [
|
||||||
|
"", "", "", "", "", "", "", "", "", "", "Catégories",
|
||||||
|
...Object.keys(catColumns).map(() => "")
|
||||||
|
];
|
||||||
|
|
||||||
|
const orderedData = dataOut.map(row => columnOrder.map(col => row[col]));
|
||||||
|
|
||||||
const wb = XLSX.utils.book_new();
|
const wb = XLSX.utils.book_new();
|
||||||
const ws = XLSX.utils.json_to_sheet(dataOut);
|
const ws = XLSX.utils.json_to_sheet([], {skipHeader: true});
|
||||||
XLSX.utils.sheet_add_aoa(ws, [["Licence", "Nom", "Prénom", "Genre", "Poids", "Catégorie normalizer", "Surclassement", "Club"]], {origin: 'A1'});
|
|
||||||
|
|
||||||
ws["!cols"] = [{wch: 7}, {wch: 16}, {wch: 16}, {wch: 6}, {wch: 6}, {wch: 10}, {wch: 10}, {wch: 60}]
|
XLSX.utils.sheet_add_aoa(ws, [headers, secondHeaders, ...orderedData], {origin: "A1"});
|
||||||
|
|
||||||
|
// Fusionner les cellules pour le titre "Catégories"
|
||||||
|
const mergeStart = XLSX.utils.encode_cell({r: 0, c: 10}); // Ligne 1, colonne K (index 10)
|
||||||
|
const mergeEnd = XLSX.utils.encode_cell({r: 0, c: 10 + Object.keys(catColumns).length - 1});
|
||||||
|
ws["!merges"] = [{s: mergeStart, e: mergeEnd}];
|
||||||
|
|
||||||
|
// 10. Appliquer une rotation de 45° aux en-têtes
|
||||||
|
const headerRow = ws["!rows"] || (ws["!rows"] = {});
|
||||||
|
headerRow[1] = {hpt: 70}; // Hauteur de la première ligne
|
||||||
|
for (let i = 0; i < headers.length; i++) {
|
||||||
|
const cellRef = XLSX.utils.encode_cell({r: 1, c: i});
|
||||||
|
if (!ws[cellRef]) ws[cellRef] = {};
|
||||||
|
ws[cellRef].s = {
|
||||||
|
alignment: {textRotation: 45, vertical: "bottom", wrapText: true}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
ws["!cols"] = [{wch: 5}, {wch: 4}, {wch: 16}, {wch: 16}, {wch: 4}, {wch: 4}, {wch: 10}, {wch: 4}, {wch: 10}, {wch: 60},
|
||||||
|
...Object.keys(catColumns).map(() => ({wch: 2}))]
|
||||||
|
|
||||||
XLSX.utils.book_append_sheet(wb, ws, "Feuille 1");
|
XLSX.utils.book_append_sheet(wb, ws, "Feuille 1");
|
||||||
XLSX.writeFile(wb, "output.xlsx");
|
XLSX.writeFile(wb, "output.xlsx");
|
||||||
@ -756,6 +828,113 @@ function FileOutput({data}) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function FileImportGuest({data2, sendRegisters}) {
|
||||||
|
const {t} = useTranslation();
|
||||||
|
|
||||||
|
const expectedFields = [
|
||||||
|
{key: 'nom', label: t('nom'), mandatory: true, type: 'String'},
|
||||||
|
{key: 'prenom', label: t('prenom'), mandatory: true, type: 'String'},
|
||||||
|
{key: 'pays', label: t('pays'), mandatory: false, type: 'String'},
|
||||||
|
{key: 'genre', label: t('genre'), mandatory: false, type: 'String'},
|
||||||
|
{key: 'weight', label: t('poids'), mandatory: false, type: 'Integer'},
|
||||||
|
{key: 'categorie', label: t('catégorie'), mandatory: true, type: 'String'},
|
||||||
|
{key: 'club', label: t('club', {count: 1}), mandatory: false, type: 'String'},
|
||||||
|
];
|
||||||
|
|
||||||
|
if (data2)
|
||||||
|
data2.forEach(row => {
|
||||||
|
expectedFields.push({key: "__" + row.id, label: row.name, mandatory: false, type: 'Boolean'})
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const onDataMapped = (mappedData) => {
|
||||||
|
const out = []
|
||||||
|
mappedData.forEach(row => {
|
||||||
|
if (!row.nom || !row.prenom || !row.categorie) {
|
||||||
|
toast.warn(t('ligneIgnorée1'))
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const categoriesInscrites = []
|
||||||
|
data2.forEach(cat => {
|
||||||
|
if (row["__" + cat.id]) {
|
||||||
|
categoriesInscrites.push(cat.id)
|
||||||
|
}
|
||||||
|
delete row["__" + cat.id]
|
||||||
|
})
|
||||||
|
out.push({
|
||||||
|
id: 0,
|
||||||
|
licence: -1,
|
||||||
|
fname: row.prenom.trim(),
|
||||||
|
lname: row.nom.trim(),
|
||||||
|
country: row.pays ? row.pays.trim() : "FR",
|
||||||
|
genre: row.genre ? row.genre.trim() : "NA",
|
||||||
|
categorie: getCatFromName(row.categorie.trim()),
|
||||||
|
club: row.club ? row.club.trim() : "",
|
||||||
|
weight: row.weight,
|
||||||
|
overCategory: 0,
|
||||||
|
lockEdit: false,
|
||||||
|
categoriesInscrites: categoriesInscrites
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
sendRegisters(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
return <FileImport onDataMapped={onDataMapped} expectedFields={expectedFields} textButton={t('importerDesInvités')}/>
|
||||||
|
}
|
||||||
|
|
||||||
|
function FileImportComb({data2, sendRegisters}) {
|
||||||
|
const {t} = useTranslation();
|
||||||
|
|
||||||
|
const expectedFields = [
|
||||||
|
{key: 'licence', label: t('licence'), mandatory: true, type: 'Integer'},
|
||||||
|
{key: 'nom', label: t('nom'), mandatory: true, type: 'String'},
|
||||||
|
{key: 'prenom', label: t('prenom'), mandatory: true, type: 'String'},
|
||||||
|
{key: 'weight', label: t('poids'), mandatory: false, type: 'Integer'},
|
||||||
|
{key: 'overCategory', label: t('comp.modal.surclassement'), mandatory: false, type: 'Integer'},
|
||||||
|
];
|
||||||
|
|
||||||
|
if (data2)
|
||||||
|
data2.forEach(row => {
|
||||||
|
expectedFields.push({key: "__" + row.id, label: row.name, mandatory: false, type: 'Boolean'})
|
||||||
|
})
|
||||||
|
|
||||||
|
const onDataMapped = (mappedData) => {
|
||||||
|
const out = []
|
||||||
|
mappedData.forEach(row => {
|
||||||
|
if (row.licence && row.licence <= 0)
|
||||||
|
return;
|
||||||
|
if (!(row.licence || (row.nom && row.prenom))) {
|
||||||
|
toast.warn(t('ligneIgnorée2'))
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const categoriesInscrites = []
|
||||||
|
data2.forEach(cat => {
|
||||||
|
if (row["__" + cat.id]) {
|
||||||
|
categoriesInscrites.push(cat.id)
|
||||||
|
}
|
||||||
|
delete row["__" + cat.id]
|
||||||
|
})
|
||||||
|
out.push({
|
||||||
|
id: 0,
|
||||||
|
licence: row.licence,
|
||||||
|
fname: row.prenom.trim(),
|
||||||
|
lname: row.nom.trim(),
|
||||||
|
weight: row.weight,
|
||||||
|
overCategory: row.overCategory,
|
||||||
|
lockEdit: false,
|
||||||
|
categoriesInscrites: categoriesInscrites
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
sendRegisters(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
return <FileImport onDataMapped={onDataMapped} expectedFields={expectedFields} textButton={t('importerDesCombattants')}/>
|
||||||
|
}
|
||||||
|
|
||||||
function Def() {
|
function Def() {
|
||||||
return <div className="list-group">
|
return <div className="list-group">
|
||||||
<li className="list-group-item"><ThreeDots/></li>
|
<li className="list-group-item"><ThreeDots/></li>
|
||||||
|
|||||||
@ -137,6 +137,35 @@ export function getCatName(cat) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getCatFromName(name) {
|
||||||
|
switch (name.toLowerCase()) {
|
||||||
|
case i18n.t('cat.superMini').toLowerCase():
|
||||||
|
return "SUPER_MINI";
|
||||||
|
case i18n.t('cat.miniPoussin').toLowerCase():
|
||||||
|
return "MINI_POUSSIN";
|
||||||
|
case i18n.t('cat.poussin').toLowerCase():
|
||||||
|
return "POUSSIN";
|
||||||
|
case i18n.t('cat.benjamin').toLowerCase():
|
||||||
|
return "BENJAMIN";
|
||||||
|
case i18n.t('cat.minime').toLowerCase():
|
||||||
|
return "MINIME";
|
||||||
|
case i18n.t('cat.cadet').toLowerCase():
|
||||||
|
return "CADET";
|
||||||
|
case i18n.t('cat.junior').toLowerCase():
|
||||||
|
return "JUNIOR";
|
||||||
|
case i18n.t('cat.senior1').toLowerCase():
|
||||||
|
return "SENIOR1";
|
||||||
|
case i18n.t('cat.senior2').toLowerCase():
|
||||||
|
return "SENIOR2";
|
||||||
|
case i18n.t('cat.vétéran1').toLowerCase():
|
||||||
|
return "VETERAN1";
|
||||||
|
case i18n.t('cat.vétéran2').toLowerCase():
|
||||||
|
return "VETERAN2";
|
||||||
|
default:
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const SwordList = [
|
export const SwordList = [
|
||||||
"NONE",
|
"NONE",
|
||||||
"ONE_HAND",
|
"ONE_HAND",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user