diff --git a/src/main/java/fr/titionfire/ffsaf/FrontendForwardingFilter.java b/src/main/java/fr/titionfire/ffsaf/FrontendForwardingFilter.java index b8392bc..ec29c4a 100644 --- a/src/main/java/fr/titionfire/ffsaf/FrontendForwardingFilter.java +++ b/src/main/java/fr/titionfire/ffsaf/FrontendForwardingFilter.java @@ -44,7 +44,9 @@ public class FrontendForwardingFilter implements ContainerResponseFilter { final String path = info.getPath(); final String address = request.remoteAddress().toString(); - LOG.infof("Request %s %s from IP %s", method, path, address); + if (!path.equals("/api")) { + LOG.infof("Request %s %s from IP %s", method, path, address); + } int status = responseContext.getStatus(); if (status != 404 && !(status == 405 && "GET".equals(requestContext.getMethod()))) { diff --git a/src/main/java/fr/titionfire/ffsaf/data/model/CombModel.java b/src/main/java/fr/titionfire/ffsaf/data/model/CombModel.java index f8fe1f7..a409206 100644 --- a/src/main/java/fr/titionfire/ffsaf/data/model/CombModel.java +++ b/src/main/java/fr/titionfire/ffsaf/data/model/CombModel.java @@ -1,9 +1,11 @@ package fr.titionfire.ffsaf.data.model; +import fr.titionfire.ffsaf.utils.Categorie; import fr.titionfire.ffsaf.utils.ResultPrivacy; public interface CombModel { Long getCombId(); String getName(); String getName(MembreModel model, ResultPrivacy privacy); + Categorie getCategorie(); } diff --git a/src/main/java/fr/titionfire/ffsaf/data/model/CompetitionGuestModel.java b/src/main/java/fr/titionfire/ffsaf/data/model/CompetitionGuestModel.java index fae3a91..2228f83 100644 --- a/src/main/java/fr/titionfire/ffsaf/data/model/CompetitionGuestModel.java +++ b/src/main/java/fr/titionfire/ffsaf/data/model/CompetitionGuestModel.java @@ -43,8 +43,8 @@ public class CompetitionGuestModel implements CombModel { String country = "fr"; - Integer weight = null; - Integer weightReal = null; + Float weight = null; + Float weightReal = null; @ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST) @JoinTable( @@ -109,7 +109,7 @@ public class CompetitionGuestModel implements CombModel { return Stream.concat(comb.stream(), guest.stream()).anyMatch(c -> Objects.equals(c, comb_)); } - public Integer getWeight2() { + public Float getWeight2() { return (this.weightReal != null) ? this.weightReal : this.weight; } } diff --git a/src/main/java/fr/titionfire/ffsaf/data/model/RegisterModel.java b/src/main/java/fr/titionfire/ffsaf/data/model/RegisterModel.java index 75c03bd..8a3680c 100644 --- a/src/main/java/fr/titionfire/ffsaf/data/model/RegisterModel.java +++ b/src/main/java/fr/titionfire/ffsaf/data/model/RegisterModel.java @@ -37,8 +37,8 @@ public class RegisterModel { @JoinColumn(name = "id_membre") MembreModel membre; - Integer weight; - Integer weightReal; + Float weight; + Float weightReal; int overCategory = 0; Categorie categorie; @@ -61,7 +61,7 @@ public class RegisterModel { ) List categoriesInscrites = new ArrayList<>(); - public RegisterModel(CompetitionModel competition, MembreModel membre, Integer weight, int overCategory, + public RegisterModel(CompetitionModel competition, MembreModel membre, Float weight, int overCategory, Categorie categorie, ClubModel club) { this.id = new RegisterId(competition.getId(), membre.getId()); this.competition = competition; @@ -91,7 +91,7 @@ public class RegisterModel { return Categorie.values()[Math.min(tmp.ordinal() + this.overCategory, Categorie.values().length - 1)]; } - public Integer getWeight2() { + public Float getWeight2() { if (weightReal != null) return weightReal; return weight; diff --git a/src/main/java/fr/titionfire/ffsaf/domain/entity/CombEntity.java b/src/main/java/fr/titionfire/ffsaf/domain/entity/CombEntity.java index b34bc13..408ca4d 100644 --- a/src/main/java/fr/titionfire/ffsaf/domain/entity/CombEntity.java +++ b/src/main/java/fr/titionfire/ffsaf/domain/entity/CombEntity.java @@ -27,7 +27,7 @@ public class CombEntity { Genre genre; String country; int overCategory; - Integer weight; + Float weight; List teamMembers; List categoriesInscrites; @@ -47,7 +47,8 @@ public class CombEntity { return null; return new CombEntity(model.getId() * -1, model.getLname(), model.getFname(), model.getCategorie(), null, - model.getClub(), model.getGenre(), model.getCountry(), 0, model.getWeight(), + model.getClub(), model.getGenre(), model.getCountry(), 0, + model.getWeight2() != null ? model.getWeight2() : model.getWeight(), Stream.concat(model.getComb().stream().map(CombEntity::fromModel), model.getGuest().stream().map(CombEntity::fromModel)).toList(), new ArrayList<>()); @@ -68,7 +69,8 @@ public class CombEntity { return new CombEntity(model.getId(), model.getLname(), model.getFname(), registerModel.getCategorie(), registerModel.getClub2() == null ? null : registerModel.getClub2().getClubId(), registerModel.getClub2() == null ? "Sans club" : registerModel.getClub2().getName(), model.getGenre(), - model.getCountry(), registerModel.getOverCategory(), registerModel.getWeight(), new ArrayList<>(), - new ArrayList<>()); + model.getCountry(), registerModel.getOverCategory(), + registerModel.getWeight2() != null ? registerModel.getWeight2() : registerModel.getWeight(), + new ArrayList<>(), new ArrayList<>()); } } diff --git a/src/main/java/fr/titionfire/ffsaf/domain/service/CompetitionService.java b/src/main/java/fr/titionfire/ffsaf/domain/service/CompetitionService.java index 110482e..1612bd5 100644 --- a/src/main/java/fr/titionfire/ffsaf/domain/service/CompetitionService.java +++ b/src/main/java/fr/titionfire/ffsaf/domain/service/CompetitionService.java @@ -368,6 +368,9 @@ public class CompetitionService { return Panache.withTransaction(() -> repository.persist(c)); }) .chain(combModel -> updateRegister(data, c, combModel, true, false))) + .call(r -> r.getCompetition().getSystem() == CompetitionSystem.INTERNAL ? + sRegister.sendRegisterNoFetch(r.getCompetition().getUuid(), r) : Uni.createFrom() + .voidItem()) .map(r -> SimpleRegisterComb.fromModel(r, r.getMembre().getLicences()) .setCategorieInscrite(r.getCategoriesInscrites())); } else { @@ -406,8 +409,8 @@ public class CompetitionService { })) .chain(model -> Panache.withTransaction(() -> competitionGuestRepository.persist(model)) .call(r -> model.getCompetition().getSystem() == CompetitionSystem.INTERNAL ? - sRegister.sendRegister(model.getCompetition().getUuid(), - r) : Uni.createFrom().voidItem())) + sRegister.sendRegisterNoFetch(model.getCompetition().getUuid(), r) + : Uni.createFrom().voidItem())) .map(g -> SimpleRegisterComb.fromModel(g).setCategorieInscrite(g.getCategoriesInscrites())); } if ("club".equals(source)) @@ -428,6 +431,9 @@ public class CompetitionService { throw new DForbiddenException(trad.t("insc.err1")); })) .chain(combModel -> updateRegister(data, c, combModel, false, false))) + .call(r -> r.getCompetition().getSystem() == CompetitionSystem.INTERNAL ? + sRegister.sendRegisterNoFetch(r.getCompetition().getUuid(), r) : Uni.createFrom() + .voidItem()) .map(r -> SimpleRegisterComb.fromModel(r, r.getMembre().getLicences()) .setCategorieInscrite(r.getCategoriesInscrites())); @@ -444,6 +450,8 @@ public class CompetitionService { throw new DForbiddenException(trad.t("insc.err2")); })) .chain(combModel -> updateRegister(data, c, combModel, false, false))) + .call(r -> r.getCompetition().getSystem() == CompetitionSystem.INTERNAL ? + sRegister.sendRegisterNoFetch(r.getCompetition().getUuid(), r) : Uni.createFrom().voidItem()) .map(r -> SimpleRegisterComb.fromModel(r, List.of()).setCategorieInscrite(r.getCategoriesInscrites())); } @@ -453,33 +461,33 @@ public class CompetitionService { 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(); + return permService.hasEditPerm(securityCtx, id) + .chain(cm -> Multi.createFrom().iterable(datas).onItem().transformToUni(data -> + makeImportUpdate(cm, 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 makeImportUpdate(SecurityCtx securityCtx, Long id, RegisterRequestData data) { + @WithSession + public Uni makeImportUpdate(CompetitionModel c, 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))) + return 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)) + return findGuestOrInit(data.getFname(), data.getLname(), c) .invoke(Unchecked.consumer(model -> { if (data.getCategorie() == null) throw new DBadRequestException(trad.t("categorie.requise")); @@ -503,13 +511,13 @@ public class CompetitionService { } else model.setCountry(data.getCountry()); - if (model.getCompetition().getRequiredWeight().contains(model.getCategorie())) { + if (c.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(), + .call(g -> catPresetRepository.list("competition = ?1 AND id IN ?2", c, data.getCategoriesInscrites()) .invoke(cats -> { g.getCategoriesInscrites().clear(); @@ -518,9 +526,8 @@ public class CompetitionService { .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())) + .call(r -> c.getSystem() == CompetitionSystem.INTERNAL ? + sRegister.sendRegister(c.getUuid(), r) : Uni.createFrom().voidItem())) .map(g -> SimpleRegisterComb.fromModel(g).setCategorieInscrite(g.getCategoriesInscrites())); } } diff --git a/src/main/java/fr/titionfire/ffsaf/domain/service/ResultService.java b/src/main/java/fr/titionfire/ffsaf/domain/service/ResultService.java index c3d4fbd..a9cc7bd 100644 --- a/src/main/java/fr/titionfire/ffsaf/domain/service/ResultService.java +++ b/src/main/java/fr/titionfire/ffsaf/domain/service/ResultService.java @@ -188,6 +188,7 @@ public class ResultService { comb.getName(membreModel, ResultPrivacy.REGISTERED_ONLY_NO_DETAILS), stat.score, stat.w, stat.pointMake, stat.pointTake, stat.getPointRate()); }) + .filter(r -> r.getPointMake() > 0 || r.getPointTake() > 0) .sorted(Comparator .comparing(ResultCategoryData.RankArray::getScore) .thenComparing(ResultCategoryData.RankArray::getWin) @@ -212,7 +213,7 @@ public class ResultService { }); } - private void getClassementArray(CategoryModel categoryModel, MembreModel membreModel, List cards, + public void getClassementArray(CategoryModel categoryModel, MembreModel membreModel, List cards, ResultCategoryData out) { if ((categoryModel.getType() & 2) != 0) { AtomicInteger rank = new AtomicInteger(0); @@ -258,7 +259,7 @@ public class ResultService { .add(new ResultCategoryData.ClassementData(rank.incrementAndGet(), m.getC1(), m.getC1Name(membreModel, ResultPrivacy.REGISTERED_ONLY_NO_DETAILS))); out.getClassement() - .add(new ResultCategoryData.ClassementData(rank.getAndIncrement(), m.getC2(), + .add(new ResultCategoryData.ClassementData(rank.get(), m.getC2(), m.getC2Name(membreModel, ResultPrivacy.REGISTERED_ONLY_NO_DETAILS))); } } else { diff --git a/src/main/java/fr/titionfire/ffsaf/rest/data/CompetitionData.java b/src/main/java/fr/titionfire/ffsaf/rest/data/CompetitionData.java index df37ffe..82f08e2 100644 --- a/src/main/java/fr/titionfire/ffsaf/rest/data/CompetitionData.java +++ b/src/main/java/fr/titionfire/ffsaf/rest/data/CompetitionData.java @@ -107,7 +107,7 @@ public class CompetitionData { public static class SimpleRegister { long id; int overCategory; - Integer weight; + Float weight; Categorie categorie; Long club; String club_str; diff --git a/src/main/java/fr/titionfire/ffsaf/rest/data/RegisterRequestData.java b/src/main/java/fr/titionfire/ffsaf/rest/data/RegisterRequestData.java index 93d20e5..0cf467a 100644 --- a/src/main/java/fr/titionfire/ffsaf/rest/data/RegisterRequestData.java +++ b/src/main/java/fr/titionfire/ffsaf/rest/data/RegisterRequestData.java @@ -18,8 +18,8 @@ public class RegisterRequestData { private String fname; private String lname; - private Integer weight; - private Integer weightReal; + private Float weight; + private Float weightReal; private Integer overCategory; private boolean lockEdit = false; private List categoriesInscrites; diff --git a/src/main/java/fr/titionfire/ffsaf/rest/data/SimpleRegisterComb.java b/src/main/java/fr/titionfire/ffsaf/rest/data/SimpleRegisterComb.java index 0dd5799..7f145ee 100644 --- a/src/main/java/fr/titionfire/ffsaf/rest/data/SimpleRegisterComb.java +++ b/src/main/java/fr/titionfire/ffsaf/rest/data/SimpleRegisterComb.java @@ -26,8 +26,8 @@ public class SimpleRegisterComb { private Categorie categorie; private SimpleClubModel club; private Integer licence; - private Integer weight; - private Integer weightReal; + private Float weight; + private Float weightReal; private int overCategory; private boolean hasLicenceActive; private boolean lockEdit; diff --git a/src/main/java/fr/titionfire/ffsaf/ws/CompetitionWS.java b/src/main/java/fr/titionfire/ffsaf/ws/CompetitionWS.java index 56d8f42..7edb0ad 100644 --- a/src/main/java/fr/titionfire/ffsaf/ws/CompetitionWS.java +++ b/src/main/java/fr/titionfire/ffsaf/ws/CompetitionWS.java @@ -54,6 +54,9 @@ public class CompetitionWS { @Inject RState rState; + @Inject + RPDF rpdf; + @Inject SecurityCtx securityCtx; @@ -99,6 +102,7 @@ public class CompetitionWS { getWSReceiverMethods(RCard.class, rCard); getWSReceiverMethods(RTeam.class, rTeam); getWSReceiverMethods(RState.class, rState); + getWSReceiverMethods(RPDF.class, rpdf); executor = notifyExecutor; } diff --git a/src/main/java/fr/titionfire/ffsaf/ws/recv/RCategorie.java b/src/main/java/fr/titionfire/ffsaf/ws/recv/RCategorie.java index 2f7dcf2..c118ece 100644 --- a/src/main/java/fr/titionfire/ffsaf/ws/recv/RCategorie.java +++ b/src/main/java/fr/titionfire/ffsaf/ws/recv/RCategorie.java @@ -29,6 +29,7 @@ import lombok.Data; import org.hibernate.reactive.mutiny.Mutiny; import java.util.*; +import java.util.stream.Collectors; import java.util.stream.Stream; @WithSession @@ -117,16 +118,52 @@ public class RCategorie { categoryModel.setTree(new ArrayList<>()); categoryModel.setType(categorie.type); categoryModel.setLiceName(categorie.liceName); + categoryModel.setTreeAreClassement(categorie.treeAreClassement); + categoryModel.setFullClassement(categorie.fullClassement); + if (categorie.preset() != null) + return catPresetRepository.findById(categorie.preset().getId()) + .invoke(categoryModel::setPreset) + .chain(__ -> categoryRepository.create(categoryModel)); return categoryRepository.create(categoryModel); }) .invoke(cat -> SSCategorie.sendAddCategory(connection, cat)) .map(CategoryModel::getId); } + @WSReceiver(code = "createOrReplaceCategory", permission = PermLevel.ADMIN) + public Uni createOrReplaceCategory(WebSocketConnection connection, JustCategorie categorie) { + return matchRepository.list("category.compet.uuid = ?1 AND category.name = ?2", connection.pathParam("uuid"), + categorie.name) + .chain(existing -> { + if (existing.isEmpty()) + return createCategory(connection, categorie); + + Map> matchesByCategory = existing.stream() + .filter(m -> m.getCategory() != null) + .collect(Collectors.groupingBy(m -> m.getCategory().getId())); + + for (Map.Entry> entry : matchesByCategory.entrySet()) { + Long categoryId = entry.getKey(); + List matches = entry.getValue(); + + if (matches.stream().noneMatch(m -> !m.getScores().isEmpty() || m.isEnd())) + return Panache.withTransaction(() -> updateCategory(connection, categorie, categoryId) + .call(__ -> treeRepository.delete("category = ?1", categoryId)) + .call(__ -> matchRepository.delete("category.id = ?1", categoryId))) + .replaceWith(categoryId); + } + return createCategory(connection, categorie); + }); + } + @WSReceiver(code = "updateCategory", permission = PermLevel.ADMIN) public Uni updateCategory(WebSocketConnection connection, JustCategorie categorie) { - return getById(categorie.id, connection) + return updateCategory(connection, categorie, categorie.id); + } + + private Uni updateCategory(WebSocketConnection connection, JustCategorie categorie, Long id) { + return getById(id, connection) .call(cat -> { if (categorie.preset() == null) { cat.setPreset(null); diff --git a/src/main/java/fr/titionfire/ffsaf/ws/recv/RPDF.java b/src/main/java/fr/titionfire/ffsaf/ws/recv/RPDF.java new file mode 100644 index 0000000..7aa1a17 --- /dev/null +++ b/src/main/java/fr/titionfire/ffsaf/ws/recv/RPDF.java @@ -0,0 +1,107 @@ +package fr.titionfire.ffsaf.ws.recv; + +import fr.titionfire.ffsaf.data.model.CardModel; +import fr.titionfire.ffsaf.data.model.CategoryModel; +import fr.titionfire.ffsaf.data.model.MatchModel; +import fr.titionfire.ffsaf.data.repository.CardRepository; +import fr.titionfire.ffsaf.data.repository.CategoryRepository; +import fr.titionfire.ffsaf.data.repository.CompetitionRepository; +import fr.titionfire.ffsaf.data.repository.MatchRepository; +import fr.titionfire.ffsaf.domain.entity.MatchModelExtend; +import fr.titionfire.ffsaf.domain.service.ResultService; +import fr.titionfire.ffsaf.domain.service.TradService; +import fr.titionfire.ffsaf.rest.data.ResultCategoryData; +import fr.titionfire.ffsaf.utils.Categorie; +import fr.titionfire.ffsaf.ws.PermLevel; +import io.quarkus.hibernate.reactive.panache.common.WithSession; +import io.quarkus.runtime.annotations.RegisterForReflection; +import io.quarkus.websockets.next.WebSocketConnection; +import io.smallrye.mutiny.Multi; +import io.smallrye.mutiny.Uni; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.transaction.Transactional; +import org.hibernate.reactive.mutiny.Mutiny; + +import java.util.HashMap; +import java.util.List; +import java.util.stream.Stream; + +@WithSession +@ApplicationScoped +@RegisterForReflection +public class RPDF { + + @Inject + CompetitionRepository competitionRepository; + + @Inject + MatchRepository matchRepository; + + @Inject + CardRepository cardRepository; + + @Inject + CategoryRepository categoryRepository; + + @Inject + ResultService resultService; + + @Inject + TradService trad; + + @Transactional + @WSReceiver(code = "getPodium", permission = PermLevel.VIEW) + public Uni> getPodium(WebSocketConnection connection, Object o) { + List cards = new java.util.ArrayList<>(); + + return cardRepository.list("competition.uuid = ?1", connection.pathParam("uuid")) + .invoke(cards::addAll) + .chain(__ -> matchRepository.list("category.compet.uuid = ?1", connection.pathParam("uuid"))) + .chain(matchs -> { + HashMap> map = new HashMap<>(); + for (MatchModel match : matchs) { + if (!map.containsKey(match.getCategory())) + map.put(match.getCategory(), new java.util.ArrayList<>()); + map.get(match.getCategory()).add(match); + } + + return Multi.createFrom().iterable(map.entrySet()) + .onItem().call(entry -> Mutiny.fetch(entry.getKey().getTree())) + .map(entry -> { + ResultCategoryData tmp = new ResultCategoryData(); + + double cmoy = entry.getValue().stream().flatMap(m -> Stream.of(m.getC1(), m.getC2())) + .filter(c -> c != null && c.getCategorie() != null) + .mapToInt(c -> c.getCategorie().ordinal()) + .average().orElse(0); + Categorie categorie_moy = Categorie.values()[(int) Math.ceil(cmoy)]; + + resultService.getArray2( + entry.getValue().stream().map(m -> new MatchModelExtend(m, cards)).toList(), + null, tmp); + resultService.getClassementArray(entry.getKey(), null, cards, tmp); + + String source = ""; + if ((entry.getKey().getType() & 2) != 0) { + if (entry.getKey().isTreeAreClassement()) + source = trad.t("podium.source.classement", connection); + else + source = trad.t("podium.source.tree", connection); + } else if ((entry.getKey().getType() & 1) != 0) + source = trad.t("podium.source.poule", connection); + + + return new PodiumEntity(entry.getKey().getName(), source, categorie_moy, + tmp.getClassement()); + }) + .collect().asList(); + }); + } + + @RegisterForReflection + public static record PodiumEntity(String poule_name, String source, Categorie categorie, + List podium) { + + } +} diff --git a/src/main/java/fr/titionfire/ffsaf/ws/recv/RTeam.java b/src/main/java/fr/titionfire/ffsaf/ws/recv/RTeam.java index 54015fd..97d6ee5 100644 --- a/src/main/java/fr/titionfire/ffsaf/ws/recv/RTeam.java +++ b/src/main/java/fr/titionfire/ffsaf/ws/recv/RTeam.java @@ -86,7 +86,7 @@ public class RTeam { .max(Integer::compareTo) .map(i -> Categorie.values()[i]).orElse(Categorie.SENIOR1)); - List s = Stream.concat( + List s = Stream.concat( pair.getKey().stream().map(RegisterModel::getWeight), pair.getValue().stream().map(CompetitionGuestModel::getWeight)) .filter(Objects::nonNull).toList(); @@ -95,7 +95,7 @@ public class RTeam { } else if (s.size() == 1) { team.setWeight(s.get(0)); } else { - team.setWeight((int) s.stream().mapToInt(Integer::intValue) + team.setWeight((float) s.stream().mapToDouble(Float::doubleValue) .average() .orElse(0)); } diff --git a/src/main/java/fr/titionfire/ffsaf/ws/send/SRegister.java b/src/main/java/fr/titionfire/ffsaf/ws/send/SRegister.java index 9dd8384..853e3f2 100644 --- a/src/main/java/fr/titionfire/ffsaf/ws/send/SRegister.java +++ b/src/main/java/fr/titionfire/ffsaf/ws/send/SRegister.java @@ -40,6 +40,16 @@ public class SRegister { .chain(cardModels -> send(uuid, "sendCards", cardModels)))); } + public Uni sendRegisterNoFetch(String uuid, RegisterModel registerModel) { + return send(uuid, "sendRegister", + CombEntity.fromModel(registerModel).addCategoriesInscrites(registerModel.getCategoriesInscrites())) + .call(__ -> registerModel.getClub2() == null ? Uni.createFrom().voidItem() : + cardService.addTeamCartToNewComb(registerModel.getMembre().getId(), + registerModel.getClub2().getClubId(), registerModel.getClub2().getName(), + registerModel.getCompetition()) + .chain(cardModels -> send(uuid, "sendCards", cardModels))); + } + public Uni sendRegister(String uuid, CompetitionGuestModel model) { return Mutiny.fetch(model.getCategoriesInscrites()).chain(o -> send(uuid, "sendRegister", CombEntity.fromModel(model).addCategoriesInscrites(o)) @@ -48,6 +58,14 @@ public class SRegister { .chain(cardModels -> send(uuid, "sendCards", cardModels)))); } + public Uni sendRegisterNoFetch(String uuid, CompetitionGuestModel model) { + return send(uuid, "sendRegister", + CombEntity.fromModel(model).addCategoriesInscrites(model.getCategoriesInscrites())) + .call(__ -> cardService.addTeamCartToNewComb(model.getId() * -1, + null, model.getClub(), model.getCompetition()) + .chain(cardModels -> send(uuid, "sendCards", cardModels))); + } + public Uni sendRegisterRemove(String uuid, Long combId) { return send(uuid, "sendRegisterRemove", combId) .call(__ -> cardService.rmTeamCardFromComb(combId, uuid)); diff --git a/src/main/resources/lang/messages_en.properties b/src/main/resources/lang/messages_en.properties index 14ee869..4d9bfc6 100644 --- a/src/main/resources/lang/messages_en.properties +++ b/src/main/resources/lang/messages_en.properties @@ -89,3 +89,6 @@ carton.non.trouver=Card not found card.cannot.be.added=Unable to add the card configuration.non.supportee=Unsupported configuration err.match.termine=Error, a placement match has already been played +podium.source.classement=Ranking +podium.source.tree=Tournaments +podium.source.poule=Pool diff --git a/src/main/resources/lang/messages_fr.properties b/src/main/resources/lang/messages_fr.properties index ac73a7f..e869324 100644 --- a/src/main/resources/lang/messages_fr.properties +++ b/src/main/resources/lang/messages_fr.properties @@ -84,4 +84,7 @@ demande.d.affiliation.non.trouve=Demande d'affiliation introuvable carton.non.trouver=Carton introuvable card.cannot.be.added=Impossible d'ajouter le carton configuration.non.supportee=Configuration non supportée -err.match.termine=Erreur, un match de classement a déjŕ été joué \ No newline at end of file +err.match.termine=Erreur, un match de classement a déjŕ été joué +podium.source.classement=Classement +podium.source.tree=Tournois +podium.source.poule=Poule \ No newline at end of file diff --git a/src/main/webapp/package-lock.json b/src/main/webapp/package-lock.json index dcab915..e9606cb 100644 --- a/src/main/webapp/package-lock.json +++ b/src/main/webapp/package-lock.json @@ -21,6 +21,8 @@ "i18next": "^25.8.0", "i18next-browser-languagedetector": "^8.2.0", "i18next-http-backend": "^3.0.2", + "jspdf": "^4.1.0", + "jspdf-autotable": "^5.0.7", "jszip": "^3.10.1", "leaflet": "^1.9.4", "obs-websocket-js": "^5.0.7", @@ -1605,6 +1607,19 @@ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, + "node_modules/@types/pako": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/pako/-/pako-2.0.4.tgz", + "integrity": "sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw==", + "license": "MIT" + }, + "node_modules/@types/raf": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz", + "integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==", + "license": "MIT", + "optional": true + }, "node_modules/@types/react": { "version": "19.2.9", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.9.tgz", @@ -1631,6 +1646,13 @@ "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.7.tgz", "integrity": "sha512-VgDNokpBoKF+wrdvhAAfS55OMQpL6QRglwTwNC3kIgBrzZxA4WsFj+2eLfEA/uMUDzBcEhYmjSbwQakn/i3ajA==" }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", + "optional": true + }, "node_modules/@types/use-sync-external-store": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", @@ -1884,6 +1906,16 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/baseline-browser-mapping": { "version": "2.9.15", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.15.tgz", @@ -2028,6 +2060,26 @@ } ] }, + "node_modules/canvg": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.11.tgz", + "integrity": "sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==", + "license": "MIT", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.12.5", + "@types/raf": "^3.4.0", + "core-js": "^3.8.3", + "raf": "^3.4.1", + "regenerator-runtime": "^0.13.7", + "rgbcolor": "^1.0.1", + "stackblur-canvas": "^2.0.0", + "svg-pathdata": "^6.0.3" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/cfb": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz", @@ -2088,6 +2140,18 @@ "url": "https://opencollective.com/express" } }, + "node_modules/core-js": { + "version": "3.48.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.48.0.tgz", + "integrity": "sha512-zpEHTy1fjTMZCKLHUZoVeylt9XrzaIN2rbPXEt0k+q7JE5CkCZdo6bNq55bn24a69CH7ErAVLKijxJja4fw+UQ==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", @@ -2139,6 +2203,16 @@ "node": ">=4" } }, + "node_modules/css-line-break": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", + "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", + "license": "MIT", + "optional": true, + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/css-to-react-native": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", @@ -2384,6 +2458,16 @@ "node": ">=0.4.0" } }, + "node_modules/dompurify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.1.tgz", + "integrity": "sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optional": true, + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -2952,6 +3036,23 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "node_modules/fast-png": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/fast-png/-/fast-png-6.4.0.tgz", + "integrity": "sha512-kAqZq1TlgBjZcLr5mcN6NP5Rv4V2f22z00c3g8vRrwkcqjerx7BEhPbOnWCPqaHUl2XWQBJQvOT/FQhdMT7X/Q==", + "license": "MIT", + "dependencies": { + "@types/pako": "^2.0.3", + "iobuffer": "^5.3.2", + "pako": "^2.1.0" + } + }, + "node_modules/fast-png/node_modules/pako": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", + "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==", + "license": "(MIT AND Zlib)" + }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -3348,6 +3449,20 @@ "void-elements": "3.1.0" } }, + "node_modules/html2canvas": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", + "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", + "license": "MIT", + "optional": true, + "dependencies": { + "css-line-break": "^2.1.0", + "text-segmentation": "^1.0.3" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/i18next": { "version": "25.8.0", "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.8.0.tgz", @@ -3471,6 +3586,12 @@ "node": ">=12" } }, + "node_modules/iobuffer": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/iobuffer/-/iobuffer-5.4.0.tgz", + "integrity": "sha512-DRebOWuqDvxunfkNJAlc3IzWIPD5xVxwUNbHr7xKB8E6aLJxIPfNX3CoMJghcFjpv6RWQsrcJbghtEwSPoJqMA==", + "license": "MIT" + }, "node_modules/is-array-buffer": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", @@ -3923,6 +4044,38 @@ "node": ">=6" } }, + "node_modules/jspdf": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-4.1.0.tgz", + "integrity": "sha512-xd1d/XRkwqnsq6FP3zH1Q+Ejqn2ULIJeDZ+FTKpaabVpZREjsJKRJwuokTNgdqOU+fl55KgbvgZ1pRTSWCP2kQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "fast-png": "^6.2.0", + "fflate": "^0.8.1" + }, + "optionalDependencies": { + "canvg": "^3.0.11", + "core-js": "^3.6.0", + "dompurify": "^3.3.1", + "html2canvas": "^1.0.0-rc.5" + } + }, + "node_modules/jspdf-autotable": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/jspdf-autotable/-/jspdf-autotable-5.0.7.tgz", + "integrity": "sha512-2wr7H6liNDBYNwt25hMQwXkEWFOEopgKIvR1Eukuw6Zmprm/ZcnmLTQEjW7Xx3FCbD3v7pflLcnMAv/h1jFDQw==", + "license": "MIT", + "peerDependencies": { + "jspdf": "^2 || ^3 || ^4" + } + }, + "node_modules/jspdf/node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "license": "MIT" + }, "node_modules/jsx-ast-utils": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", @@ -4358,6 +4511,13 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "license": "MIT", + "optional": true + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -4485,6 +4645,16 @@ "node": ">=6" } }, + "node_modules/raf": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", + "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", + "license": "MIT", + "optional": true, + "dependencies": { + "performance-now": "^2.1.0" + } + }, "node_modules/react": { "version": "19.2.3", "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", @@ -4732,6 +4902,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "license": "MIT", + "optional": true + }, "node_modules/regexp.prototype.flags": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", @@ -4783,6 +4960,16 @@ "node": ">=4" } }, + "node_modules/rgbcolor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz", + "integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==", + "license": "MIT OR SEE LICENSE IN FEEL-FREE.md", + "optional": true, + "engines": { + "node": ">= 0.8.15" + } + }, "node_modules/rollup": { "version": "4.55.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.55.1.tgz", @@ -5071,6 +5258,16 @@ "node": ">=0.8" } }, + "node_modules/stackblur-canvas": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz", + "integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.14" + } + }, "node_modules/stop-iteration-iterator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", @@ -5246,6 +5443,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/svg-pathdata": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz", + "integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/text-segmentation": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", + "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", + "license": "MIT", + "optional": true, + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/tiny-invariant": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", @@ -5438,6 +5655,16 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, + "node_modules/utrie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", + "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", + "license": "MIT", + "optional": true, + "dependencies": { + "base64-arraybuffer": "^1.0.2" + } + }, "node_modules/uzip": { "version": "0.20201231.0", "resolved": "https://registry.npmjs.org/uzip/-/uzip-0.20201231.0.tgz", diff --git a/src/main/webapp/package.json b/src/main/webapp/package.json index 6575d02..fd64a9f 100644 --- a/src/main/webapp/package.json +++ b/src/main/webapp/package.json @@ -23,6 +23,8 @@ "i18next": "^25.8.0", "i18next-browser-languagedetector": "^8.2.0", "i18next-http-backend": "^3.0.2", + "jspdf": "^4.1.0", + "jspdf-autotable": "^5.0.7", "jszip": "^3.10.1", "leaflet": "^1.9.4", "obs-websocket-js": "^5.0.7", diff --git a/src/main/webapp/public/locales/en/cm.json b/src/main/webapp/public/locales/en/cm.json index 5ccf2bc..db892dc 100644 --- a/src/main/webapp/public/locales/en/cm.json +++ b/src/main/webapp/public/locales/en/cm.json @@ -5,6 +5,7 @@ "actuel": "Current", "administration": "Administration", "adresseDuServeur": "Server address", + "afficher": "Show", "ajoutAutomatique": "Automatic addition", "ajouter": "Add", "ajouterDesCombattants": "Add fighters", @@ -23,7 +24,11 @@ "cartonNoir": "Black card", "cartonRouge": "Red card", "catĂ©gorie": "Category", + "catĂ©gorieDâgeMoyenne": "Middle-aged category", + "catĂ©gorieSĂ©lectionnĂ©e": "Selected category", + "catĂ©goriesVontĂŞtreCréées": "weight categories will be created", "ceCartonEstIssuDunCartonDĂ©quipe": "This card comes from a team card, do you really want to delete it?", + "certainsCombattantsNontPasDePoidsRenseignĂ©": "Some fighters do not have a weight listed; they will NOT be included in the categories.", "chrono.+/-...S": "+/- ... s", "chrono.+10S": "+10 s", "chrono.+1S": "+1 s", @@ -58,11 +63,15 @@ "conserverUniquementLesMatchsTerminĂ©s": "Keep only finished matches", "contre": "vs", "couleur": "Color", + "crĂ©ationDeLaLesCatĂ©gories": "Creating the category(ies)", + "crĂ©erLaPhaseFinaleSilYADesPoules": "Create the final phase if there are groups.", "crĂ©erLesMatchesDeClassement": "Create the ranking matches", "crĂ©erLesMatchesDeClassement.msg": "Ranking matches have already been set up/played; recreating these matches will delete them all (you will therefore lose any results). Please note down any information you wish to keep.", "crĂ©erLesMatchs": "Create matches", + "crĂ©erToutesLesCatĂ©gories": "Create all categories", "date": "Date", "demi-finalesEtFinales": "Semi-finals and finals", + "depuisUneCatĂ©goriePrĂ©dĂ©finie": "From a predefined category", "durĂ©ePause": "Pause duration", "durĂ©eRound": "Round duration", "editionDeLaCatĂ©gorie": "Edit category", @@ -78,21 +87,26 @@ "etatDesTablesDeMarque": "State of marque tables", "exporter": "Export", "fermer": "Close", + "feuilleVierge": "Blank sheet", "finalesUniquement": "Finals only", "genre": "Gender", "genre.f": "F", "genre.h": "M", "genre.na": "NA", + "imprimer": "Print", "individuelle": "Individual", "informationCatĂ©gorie": "Category information", "inscrit": "Registered", + "jusquauRang": "Up to the rank", "leTournoiServiraDePhaseFinaleAuxPoules": "The tournament will serve as the final phase for the group stage.", "lesCombattantsEnDehors": "Fighters not participating in the tournament will have a ranking match.", + "lesCombattantsEnDehors2": "Fighters outside the ranking tournament will have a ranking match", "listeDesCartons": "List of cards", "manche": "Round", "matchPourLesPerdantsDuTournoi": "Match for tournament losers:", "matchTerminĂ©": "Match over", "matches": "Matches", + "modeDeCrĂ©ation": "Creation method", "modifier": "Edit", "msg1": "There are already matches in this pool; what do you want to do with them?", "neRienConserver": "Keep nothing", @@ -105,10 +119,12 @@ "nouvelle...": "New...", "obs.prĂ©fixDesSources": "Source prefix", "pays": "Country", + "personnaliser": "Personalize", "poids": "Weight", "poule": "Pool", "poulePour": "Pool for: ", "prĂ©paration...": "Preparing...", + "quoiImprimer?": "What print?", "remplacer": "Replace", "rouge": "Red", "rĂ©initialiser": "Reset", @@ -128,6 +144,7 @@ "select.sĂ©lectionnerDesCombatants": "Select fighters", "select.Ă ": "to", "serveur": "Server", + "source": "Source", "suivant": "Next", "supprimer": "Delete", "supprimerUn": "Delete one", @@ -152,6 +169,9 @@ "toast.matchs.create.error": "Error while creating matches.", "toast.matchs.create.pending": "Creating matches in progress...", "toast.matchs.create.success": "Matches created successfully.", + "toast.print.error": "Error while preparing print", + "toast.print.pending": "Preparing print...", + "toast.print.success": "Print ready!", "toast.team.update.error": "Error while updating team", "toast.team.update.pending": "Updating team...", "toast.team.update.success": "Team updated!", @@ -171,6 +191,8 @@ "tournois": "Tournaments", "tousLesMatchs": "All matches", "toutConserver": "Keep all", + "touteLaCatĂ©gorie": "The entire category", + "toutesLesCatĂ©gories": "All categories", "ttm.admin.obs": "Short click: Download resources. Long click: Create OBS configuration", "ttm.admin.scripte": "Copy integration script", "ttm.table.inverserLaPosition": "Reverse fighter positions on this screen", diff --git a/src/main/webapp/public/locales/fr/cm.json b/src/main/webapp/public/locales/fr/cm.json index 384acfa..86a559f 100644 --- a/src/main/webapp/public/locales/fr/cm.json +++ b/src/main/webapp/public/locales/fr/cm.json @@ -5,6 +5,7 @@ "actuel": "Actuel", "administration": "Administration", "adresseDuServeur": "Adresse du serveur", + "afficher": "Afficher", "ajoutAutomatique": "Ajout automatique", "ajouter": "Ajouter", "ajouterDesCombattants": "Ajouter des combattants", @@ -23,7 +24,11 @@ "cartonNoir": "Carton noir", "cartonRouge": "Carton rouge", "catĂ©gorie": "CatĂ©gorie", + "catĂ©gorieDâgeMoyenne": "CatĂ©gorie d'âge moyenne", + "catĂ©gorieSĂ©lectionnĂ©e": "CatĂ©gorie sĂ©lectionnĂ©e", + "catĂ©goriesVontĂŞtreCréées": "catĂ©gories de poids vont ĂŞtre créées", "ceCartonEstIssuDunCartonDĂ©quipe": "Ce carton est issu d'un carton d'Ă©quipe, voulez-vous vraiment le supprimer ?", + "certainsCombattantsNontPasDePoidsRenseignĂ©": "Certains combattants n'ont pas de poids renseignĂ©, ils ne seront PAS insert dans les catĂ©gories", "chrono.+/-...S": "+/- ... s", "chrono.+10S": "+10 s", "chrono.+1S": "+1 s", @@ -38,7 +43,7 @@ "chronomètre": "Chronomètre", "classement": "Classement", "club": "Club", - "combattantsCorrespondentAuxSĂ©lectionnĂ©s": "combattant(s) correspondent aux sĂ©lectionnĂ©s ci-dessus.", + "combattantsCorrespondentAuxSĂ©lectionnĂ©s": "combattant(s) correspondent aux sĂ©lections ci-dessus.", "compĂ©tition": "CompĂ©tition", "compĂ©titionManager": "CompĂ©tition manager", "config.obs.dossierDesResources": "Dossier des resources", @@ -58,11 +63,15 @@ "conserverUniquementLesMatchsTerminĂ©s": "Conserver uniquement les matchs terminĂ©s", "contre": "contre", "couleur": "Couleur", + "crĂ©ationDeLaLesCatĂ©gories": "CrĂ©ation de la/les catĂ©gories", + "crĂ©erLaPhaseFinaleSilYADesPoules": "CrĂ©er la phase finale s'il y a des poules", "crĂ©erLesMatchesDeClassement": "CrĂ©er les matches de classement", "crĂ©erLesMatchesDeClassement.msg": "Des matches de classement ont dĂ©jĂ  Ă©tĂ© configurer/jouer, la recrĂ©ation de ces matches vont tous les supprimer (vous perdre donc les rĂ©sultats s'il y en a). Mercie de noter de votre cĂ´tĂ© les informations que vous voulez conserver.", "crĂ©erLesMatchs": "CrĂ©er les matchs", + "crĂ©erToutesLesCatĂ©gories": "CrĂ©er toutes les catĂ©gories", "date": "Date", "demi-finalesEtFinales": "Demi-finales et finales", + "depuisUneCatĂ©goriePrĂ©dĂ©finie": "Depuis une catĂ©gorie prĂ©dĂ©finie", "durĂ©ePause": "DurĂ©e pause", "durĂ©eRound": "DurĂ©e round", "editionDeLaCatĂ©gorie": "Edition de la catĂ©gorie", @@ -78,21 +87,26 @@ "etatDesTablesDeMarque": "Etat des tables de marque", "exporter": "Exporter", "fermer": "Fermer", + "feuilleVierge": "Feuille vierge", "finalesUniquement": "Finales uniquement", "genre": "Genre", "genre.f": "F", "genre.h": "H", "genre.na": "NA", + "imprimer": "Imprimer", "individuelle": "Individuelle", "informationCatĂ©gorie": "Information catĂ©gorie", "inscrit": "Inscrit", + "jusquauRang": "Jusqu'au rang", "leTournoiServiraDePhaseFinaleAuxPoules": "Le tournoi servira de phase finale aux poules", "lesCombattantsEnDehors": "Les combattants en dehors du tournoi auront un match de classement", + "lesCombattantsEnDehors2": "Les combattants en dehors du tournoi de classement auront un match de classement", "listeDesCartons": "Liste des cartons", "manche": "Manche", "matchPourLesPerdantsDuTournoi": "Match pour les perdants du tournoi:", "matchTerminĂ©": "Match terminĂ©", "matches": "Matches", + "modeDeCrĂ©ation": "Mode de crĂ©ation", "modifier": "Modifier", "msg1": "Il y a dĂ©jĂ  des matchs dans cette poule, que voulez-vous faire avec ?", "neRienConserver": "Ne rien conserver", @@ -105,10 +119,12 @@ "nouvelle...": "Nouvelle...", "obs.prĂ©fixDesSources": "PrĂ©fix des sources", "pays": "Pays", + "personnaliser": "Personnaliser", "poids": "Poids", "poule": "Poule", "poulePour": "Poule pour: ", "prĂ©paration...": "PrĂ©paration...", + "quoiImprimer?": "Quoi imprimer ?", "remplacer": "Remplacer", "rouge": "Rouge", "rĂ©initialiser": "RĂ©initialiser", @@ -128,6 +144,7 @@ "select.sĂ©lectionnerDesCombatants": "SĂ©lectionner des combatants", "select.Ă ": "Ă ", "serveur": "Serveur", + "source": "Source", "suivant": "Suivant", "supprimer": "Supprimer", "supprimerUn": "Supprimer un", @@ -152,6 +169,9 @@ "toast.matchs.create.error": "Erreur lors de la crĂ©ation des matchs.", "toast.matchs.create.pending": "CrĂ©ation des matchs en cours...", "toast.matchs.create.success": "Matchs créés avec succès.", + "toast.print.error": "Erreur lors de la gĂ©nĂ©ration du PDF", + "toast.print.pending": "GĂ©nĂ©ration du PDF en cours...", + "toast.print.success": "PDF gĂ©nĂ©rĂ© !", "toast.team.update.error": "Erreur lors de la mise Ă  jour de l'Ă©quipe", "toast.team.update.pending": "Mise Ă  jour de l'Ă©quipe...", "toast.team.update.success": "Équipe mise Ă  jour !", @@ -171,6 +191,8 @@ "tournois": "Tournois", "tousLesMatchs": "Tous les matchs", "toutConserver": "Tout conserver", + "touteLaCatĂ©gorie": "Toute la catĂ©gorie", + "toutesLesCatĂ©gories": "Toutes les catĂ©gories", "ttm.admin.obs": "Clique court : TĂ©lĂ©charger les ressources. Clique long : CrĂ©er la configuration obs", "ttm.admin.scripte": "Copier le scripte d'intĂ©gration", "ttm.table.inverserLaPosition": "Inverser la position des combattants sur cette Ă©cran", diff --git a/src/main/webapp/src/components/ProtectionSelector.jsx b/src/main/webapp/src/components/ProtectionSelector.jsx index 367c5af..cc1893d 100644 --- a/src/main/webapp/src/components/ProtectionSelector.jsx +++ b/src/main/webapp/src/components/ProtectionSelector.jsx @@ -164,3 +164,27 @@ const ProtectionSelector = ({ } export default ProtectionSelector; + + +export function getMandatoryProtectionsList(mandatoryProtection, shield, t) { + const protections = []; + const isOn = (bit) => (mandatoryProtection & (1 << (bit - 1))) !== 0; + + if (isOn(1)) protections.push(t('casque', {ns: "common"})); + if (isOn(2)) protections.push(t('gorgerin', {ns: "common"})); + if (isOn(3)) protections.push(t('coquilleProtectionPelvienne', {ns: "common"})); + if (isOn(4) && !shield) protections.push(t('gants', {ns: "common"})); + if (isOn(4) && shield) protections.push(t('gantMainsArmĂ©es', {ns: "common"})); + if (isOn(5) && shield) protections.push(t('gantMainBouclier', {ns: "common"})); + if (isOn(6)) protections.push(t('plastron', {ns: "common"})); + if (isOn(7) && !shield) protections.push(t('protectionDeBras', {ns: "common"})); + if (isOn(7) && shield) protections.push(t('protectionDeBrasArmĂ©', {ns: "common"})); + if (isOn(8) && shield) protections.push(t('protectionDeBrasDeBouclier', {ns: "common"})); + if (isOn(9)) protections.push(t('protectionDeJambes', {ns: "common"})); + if (isOn(10)) protections.push(t('protectionDeGenoux', {ns: "common"})); + if (isOn(11)) protections.push(t('protectionDeCoudes', {ns: "common"})); + if (isOn(12)) protections.push(t('protectionDorsale', {ns: "common"})); + if (isOn(13)) protections.push(t('protectionDePieds', {ns: "common"})); + + return protections; +} diff --git a/src/main/webapp/src/components/cm/AutoCatModalContent.jsx b/src/main/webapp/src/components/cm/AutoCatModalContent.jsx index bd9e1ee..2b513e0 100644 --- a/src/main/webapp/src/components/cm/AutoCatModalContent.jsx +++ b/src/main/webapp/src/components/cm/AutoCatModalContent.jsx @@ -1,8 +1,14 @@ -import React, {useEffect, useState} from "react"; -import {useTranslation} from "react-i18next"; +import React, {useEffect, useId, useState} from "react"; +import {Trans, useTranslation} from "react-i18next"; import {useCountries} from "../../hooks/useCountries.jsx"; import {ListPresetSelect} from "./ListPresetSelect.jsx"; import {CatList, getCatName} from "../../utils/Tools.js"; +import {useCombs} from "../../hooks/useComb.jsx"; +import {toast} from "react-toastify"; +import {build_tree} from "../../utils/TreeUtils.js"; +import {createMatch} from "../../utils/CompetitionTools.js"; +import {useRequestWS, useWS} from "../../hooks/useWS.jsx"; +import {AxiosError} from "../AxiosError.jsx"; export function AutoCatModalContent({data, groups, setGroups, defaultPreset = -1}) { const country = useCountries('fr') @@ -52,64 +58,6 @@ export function AutoCatModalContent({data, groups, setGroups, defaultPreset = -1 if (data != null) applyFilter(data, dispoFiltered); - const makePoule = (combIn, groups) => { - combIn = combIn.sort(() => Math.random() - 0.5); - const maxInPoule = Math.ceil(combIn.length / 2); - const out = [] - - const pa = []; - const pb = []; - - let nameA; - let nameB; - groups.forEach(g => { - const existsInCombIn = combIn.some(c => c.id === g.id); - if (existsInCombIn) { - if ((pa.length === 0 || g.poule === nameA) && pa.length < maxInPoule) { - nameA = g.poule || "1"; - pa.push(g.id); - } else if ((pb.length === 0 || g.poule === nameB) && pb.length < maxInPoule) { - if (!(nameA === (g.poule || (nameA === "1" ? "2" : "1")))) { - nameB = g.poule || (nameA === "1" ? "2" : "1"); - pb.push(g.id); - } - } - } - }); - nameA = nameA || (nameB === "1" ? "2" : "1"); - nameB = nameB || (nameA === "1" ? "2" : "1"); - - if (combIn.length <= 5) { - combIn.forEach(c => { - if (!pa.includes(c.id)) - pa.push(c.id) - }); - } else { - for (const c of combIn) { - if (pa.includes(c.id) || pb.includes(c.id)) - continue; - - const club = c.club_str || (c.teamMembers && c.teamMembers[0].club_str) || ""; - - const countInPa = pa.filter(p => (p.club_str || (p.teamMembers && p.teamMembers[0].club_str) || "") === club).length; - const countInPb = pb.filter(p => (p.club_str || (p.teamMembers && p.teamMembers[0].club_str) || "") === club).length; - - if (pa.length < maxInPoule && (countInPa <= countInPb || pb.length >= maxInPoule)) { - pa.push(c.id); - } else if (pb.length < maxInPoule) { - pb.push(c.id); - } else { - pa.push(c.id); - } - } - } - - pa.forEach(id => out.push({id: id, poule: nameA})); - pb.forEach(id => out.push({id: id, poule: nameB})); - - return out - } - const handleSubmit = (e) => { e.preventDefault(); @@ -230,3 +178,625 @@ export function AutoCatModalContent({data, groups, setGroups, defaultPreset = -1 } + + +function makePoule(combIn, groups) { + combIn = combIn.sort(() => Math.random() - 0.5); + const maxInPoule = Math.ceil(combIn.length / 2); + const out = [] + + const pa = []; + const pb = []; + + let nameA; + let nameB; + groups.forEach(g => { + const existsInCombIn = combIn.some(c => c.id === g.id); + if (existsInCombIn) { + if ((pa.length === 0 || g.poule === nameA) && pa.length < maxInPoule) { + nameA = g.poule || "1"; + pa.push(g.id); + } else if ((pb.length === 0 || g.poule === nameB) && pb.length < maxInPoule) { + if (!(nameA === (g.poule || (nameA === "1" ? "2" : "1")))) { + nameB = g.poule || (nameA === "1" ? "2" : "1"); + pb.push(g.id); + } + } + } + }); + nameA = nameA || (nameB === "1" ? "2" : "1"); + nameB = nameB || (nameA === "1" ? "2" : "1"); + + if (combIn.length <= 5) { + combIn.forEach(c => { + if (!pa.includes(c.id)) + pa.push(c.id) + }); + } else { + for (const c of combIn) { + if (pa.includes(c.id) || pb.includes(c.id)) + continue; + + const club = c.club_str || (c.teamMembers && c.teamMembers[0].club_str) || ""; + + const countInPa = pa.filter(p => (p.club_str || (p.teamMembers && p.teamMembers[0].club_str) || "") === club).length; + const countInPb = pb.filter(p => (p.club_str || (p.teamMembers && p.teamMembers[0].club_str) || "") === club).length; + + if (pa.length < maxInPoule && (countInPa <= countInPb || pb.length >= maxInPoule)) { + pa.push(c.id); + } else if (pb.length < maxInPoule) { + pb.push(c.id); + } else { + pa.push(c.id); + } + } + } + + pa.forEach(id => out.push({id: id, poule: nameA})); + pb.forEach(id => out.push({id: id, poule: nameB})); + + return out; +} + +function makeWeightCategories(combs) { + combs = combs.filter(c => c.weight != null).sort((a, b) => a.weight - b.weight); // Add random for same weight ? + const catCount = Math.ceil(combs.length / 10); + const catSize = combs.length / catCount; + const catMaxSize = Math.min(Math.ceil(catSize), 10); + const catMinSize = Math.max(Math.floor(catSize), 3); // Add marge ? + + const categories = Array.from({length: catCount}, () => []); + for (let i = 0; i < combs.length; i++) { + categories[Math.floor(i / catSize)].push(combs[i]); + } + + let change = false; + let maxIterations = 500; + do { + change = false; + + // ------ move in upper direction if better and possible ------ + + let needFree = -1; + let dIfFree = 0; + for (let i = 0; i < catCount - 1; i++) { + const weightDiff = categories.at(i).at(-1).weight - categories.at(i).at(-2).weight; + const nextWeightDiff = categories.at(i + 1).at(0).weight - categories.at(i).at(-1).weight; + + if (weightDiff > nextWeightDiff && categories.at(i).length > catMinSize) { + if (categories.at(i + 1).length < catMaxSize) { + const movedComb = categories.at(i).pop(); + categories.at(i + 1).unshift(movedComb); + change = true; + } else if (weightDiff - nextWeightDiff > dIfFree) { + needFree = i; + dIfFree = weightDiff - nextWeightDiff; + } + } + } + if (needFree !== -1) { + let haveSpace = -1; + let maxDiff = 0; + for (let i = needFree + 1; i < catCount; i++) { + if (categories.at(i).length < catMaxSize) { + haveSpace = i; + break; + } + } + if (haveSpace !== -1) { + for (let i = needFree + 1; i < haveSpace; i++) { + const weightDiff = categories.at(i).at(-1).weight - categories.at(i).at(-2).weight; + const nextWeightDiff = categories.at(i + 1).at(0).weight - categories.at(i).at(-1).weight; + const diffIfFree = weightDiff - nextWeightDiff; + if (diffIfFree > maxDiff) { + maxDiff = diffIfFree; + } + } + + if (maxDiff < dIfFree) { + for (let i = needFree; i < haveSpace; i++) { + const movedComb = categories.at(i).pop(); + categories.at(i + 1).unshift(movedComb); + change = true; + } + } + } + } + + // ------ move in lower direction if better and possible ------ + + needFree = -1; + dIfFree = 0; + for (let i = 1; i < catCount; i++) { + const currentFirst = categories[i][0]; + const currentSecondFirst = categories[i][1]; + const prevLast = categories[i - 1][categories[i - 1].length - 1]; + + const weightDiff = currentSecondFirst.weight - currentFirst.weight; + const prevWeightDiff = currentFirst.weight - prevLast.weight; + + if (weightDiff > prevWeightDiff && categories.at(i).length > catMinSize) { + if (categories.at(i - 1).length < catMaxSize) { + const movedComb = categories.at(i).shift(); + categories.at(i - 1).push(movedComb); + change = true; + } else if (weightDiff - prevWeightDiff > dIfFree) { + needFree = i; + dIfFree = weightDiff - prevWeightDiff; + } + } + } + if (needFree !== -1) { + let haveSpace = -1; + let maxDiff = 0; + for (let i = needFree - 1; i >= 0; i--) { + if (categories.at(i).length < catMaxSize) { + haveSpace = i; + break; + } + } + if (haveSpace !== -1) { + for (let i = needFree - 1; i > haveSpace; i--) { + const currentFirst = categories[i][0]; + const currentSecondFirst = categories[i][1]; + const prevLast = categories[i - 1][categories[i - 1].length - 1]; + + const weightDiff = currentSecondFirst.weight - currentFirst.weight; + const prevWeightDiff = currentFirst.weight - prevLast.weight; + + const diffIfFree = weightDiff - prevWeightDiff; + if (diffIfFree > maxDiff) { + maxDiff = diffIfFree; + } + } + + if (maxDiff < dIfFree) { + for (let i = needFree; i > haveSpace; i--) { + const movedComb = categories.at(i).shift(); + categories.at(i - 1).push(movedComb); + change = true; + } + } + } + } + } while (change && maxIterations-- > 0); + + return categories; +} + +const getCatNameList = (count) => { + const catNameList = []; + if (count >= 10) catNameList.push("Paille"); + if (count >= 9) catNameList.push("Mouche"); + if (count >= 8) catNameList.push("Coq"); + if (count >= 7) catNameList.push("Plume"); + if (count >= 2) catNameList.push("LĂ©ger"); + if (count >= 5) catNameList.push("Mi-moyen"); + if (count >= 3) catNameList.push("Moyen"); + if (count >= 6) catNameList.push("Mi-lourd"); + if (count >= 1) catNameList.push("Lourd"); + if (count >= 4) catNameList.push("Super-lourd"); + + return catNameList; +} + +function makeCategory(combs) { + const out = Array.from(CatList, (v, i) => ({ + h: combs.filter(c => c.categorie === v && c.genre !== "F"), + f: combs.filter(c => c.categorie === v && c.genre === "F"), + m: [], + canMakeGenreFusion: i <= CatList.indexOf("BENJAMIN"), + c: v, + c_index: i, + min_c_index: i, + done: false + })) + + for (let i = 0; i < out.length; i++) + out[i].done = out[i].h.length === 0 && out[i].f.length === 0; + + for (let i = 0; i < out.length - 1; i++) { + const p = i === 0 ? undefined : out[i - 1]; + const c = out[i]; + const n = out[i + 1]; + + if (c.done) + continue; + if (c.canMakeGenreFusion) { + if (c.f.length < 6 || c.h.length < 5) { + if (c.f.length + c.h.length >= 3) { + c.m = c.h.concat(c.f); + c.h = []; + c.f = []; + c.done = true; + } else { + n.h = n.h.concat(c.h); + n.f = n.f.concat(c.f); + n.min_c_index = c.min_c_index + c.h = []; + c.f = []; + c.done = true; + } + } else { + c.done = true; + } + } else { + if (c.h.length < 3 && c.h.length > 0) { + if (p) { + if (p.h.length > 0 && p.h.length + c.h.length <= c.h.length + n.h.length && p.min_c_index - p.c_index < 1) { + p.h = p.h.concat(c.h); + c.h = []; + } else { + n.h = n.h.concat(c.h); + c.h = []; + } + } else { + n.h = n.h.concat(c.h); + c.h = []; + } + } + if (c.f.length < 3 && c.f.length > 0) { + if (p) { + if (p.f.length > 0 && p.f.length + c.f.length <= c.f.length + n.f.length && p.min_c_index - p.c_index < 1) { + p.f = p.f.concat(c.f); + c.f = []; + } else { + n.f = n.f.concat(c.f); + c.f = []; + } + } else { + n.f = n.f.concat(c.f); + c.f = []; + } + } + c.done = (c.h.length >= 3 || c.h.length === 0) && (c.f.length >= 3 || c.f.length === 0); + } + } + + // Down fusion if not done + for (let i = out.length - 1; i > 0; i--) { + const p = out[i - 1]; + const c = out[i]; + + if (c.done) + continue; + if (c.h.length > 0 && c.h.length < 3) { + p.h = p.h.concat(c.h); + c.h = []; + } + if (c.f.length > 0 && c.f.length < 3) { + p.f = p.f.concat(c.f); + c.f = []; + } + c.done = (c.h.length >= 3 || c.h.length === 0) && (c.f.length >= 3 || c.f.length === 0); + p.done = (p.h.length >= 3 || p.h.length === 0) && (p.f.length >= 3 || p.f.length === 0); + } + + return out.map(c => [c.h, c.f, c.m]).flat().filter(l => l.length > 0); +} + +function sendCatList(toastId, t, catList, sendRequest) { + toastId.current = toast(t('crĂ©ationDeLaLesCatĂ©gories'), {progress: 0}); + + new Promise(async (resolve) => { + for (let i = 0; i < catList.length; i++) { + const progress = (i + 1) / catList.length; + toast.update(toastId.current, {progress}); + + const g = [] + if (catList[i].combs.some(c => c.genre === "H")) g.push('H'); + if (catList[i].combs.some(c => c.genre === "F")) g.push('F'); + + const cat = [] + catList[i].combs.forEach(c => { + if (!cat.includes(c.categorie)) + cat.push(c.categorie); + }) + + const type = catList[i].combs.length > 5 && catList[i].classement ? 3 : 1; + const newCat = { + name: catList[i].preset.name + " - " + cat.map(c => getCatName(c)).join(", ") + + (g.length === 2 ? "" : " - " + g.join("/")) + (catList[i].size === 1 ? "" : " - " + getCatNameList(catList[i].size)[catList[i].index]), + liceName: catList[i].lice, + type: type, + treeAreClassement: catList[i].classement, + fullClassement: catList[i].fullClassement, + preset: {id: catList[i].preset.id} + } + console.log(newCat) + + await sendRequest('createOrReplaceCategory', newCat).then(id => { + newCat["id"] = id; + const groups = makePoule(catList[i].combs, []); + const {newMatch, matchOrderToUpdate, matchPouleToUpdate} = createMatch(newCat, [], groups); + + const p = []; + p.push(sendRequest("recalculateMatch", { + categorie: newCat.id, + newMatch, + matchOrderToUpdate: Object.fromEntries(matchOrderToUpdate), + matchPouleToUpdate: Object.fromEntries(matchPouleToUpdate), + matchesToRemove: [] + }).then(() => { + console.log("Finished creating matches for category", newCat.name); + }).catch(err => { + console.error("Error creating matches for category", newCat.name, err); + })) + + if (type === 3) { + const trees = build_tree(4, 1) + console.log("Creating trees for new category:", trees); + + p.push(sendRequest('updateTrees', { + categoryId: id, + trees: trees + }).then(() => { + console.log("Finished creating trees for category", newCat.name); + }).catch(err => { + console.error("Error creating trees for category", newCat.name, err); + })) + } + + return Promise.allSettled(p) + }).catch(err => { + console.error("Error creating category", newCat.name, err); + }) + console.log("Finished category", i + 1, "/", catList.length); + } + resolve(); + }).finally(() => { + toast.done(toastId.current); + }) +} + +export function AutoNewCatModalContent() { + const {t} = useTranslation("cm"); + const {combs} = useCombs(); + const {sendRequest} = useWS(); + const toastId = React.useRef(null); + + const [gender, setGender] = useState({H: false, F: false, NA: false}) + const [cat, setCat] = useState([]) + const [preset, setPreset] = useState(undefined) + const [lice, setLice] = useState("1") + const [classement, setClassement] = useState(true) + const [fullClassement, setFullClassement] = useState(false) + + const setCat_ = (e, index) => { + if (e.target.checked) { + if (!cat.includes(index)) { + setCat([...cat, index]) + } + } else { + setCat(cat.filter(c => c !== index)) + } + } + + function applyFilter(dataIn, dataOut) { + dataIn.forEach(comb => { + if (comb == null) + return; + if ((gender.H && comb.genre === 'H' || gender.F && comb.genre === 'F' || gender.NA && comb.genre === 'NA') + && (cat.includes(Math.min(CatList.length, CatList.indexOf(comb.categorie) + comb.overCategory))) + && (preset === undefined || comb.categoriesInscrites?.includes(preset.id))) { + dataOut.push(comb) + } + } + ) + } + + const dispoFiltered = []; + if (combs != null) + applyFilter(Object.values(combs), dispoFiltered); + + const handleSubmit = (e) => { + e.preventDefault(); + + let catList + if (dispoFiltered.length > 10) { + catList = makeWeightCategories(dispoFiltered); + } else { + catList = [[...dispoFiltered]]; + } + console.log(catList.map(c => c.map(c => ({id: c.id, weight: c.weight, fname: c.fname, lname: c.lname})))) + + sendCatList(toastId, t, catList + .map((combs, index, a) => ({combs, classement, preset, lice, fullClassement, index, size: a.length})), sendRequest); + } + + return <> +
+

{t('depuisUneCatégoriePrédéfinie')}

+ +
+
+
+ + +
+ +
+
+ setGender((prev) => { + return {...prev, H: e.target.checked} + })}/> + +
+
+ setGender((prev) => { + return {...prev, F: e.target.checked} + })}/> + +
+
+ setGender((prev) => { + return {...prev, NA: e.target.checked} + })}/> + +
+
+
+
+ {preset !== undefined && <> +
+
+ + {preset.categories.map(c => [c.categorie, CatList.indexOf(c.categorie)]).sort((a, b) => a[1] - b[1]) + .map(([cat_, index]) => { + return
+
+ setCat_(e, index)}/> + +
+
+ })} +
+
+ } + +
+ + setLice(e.target.value)}/> +
+ +
+ setClassement(e.target.checked)}/> + +
+
+ setFullClassement(e.target.checked)}/> + +
+ + {dispoFiltered.length} {t('combattantsCorrespondentAuxSélectionnés')}
+ {Math.ceil(dispoFiltered.length / 10)} {t('catégoriesVontêtreCréées')}
+ {dispoFiltered.length > 10 && dispoFiltered.some(c => !c.weight) && + {t('certainsCombattantsNontPasDePoidsRenseigné')}} + +
+
+ + +
+ +} + + +export function AutoNewCatSModalContent() { + const {t} = useTranslation("cm"); + const {combs} = useCombs(); + const {sendRequest} = useWS(); + const toastId = React.useRef(null); + const {data, error} = useRequestWS("listPreset", {}, null); + + const id = useId() + const [categories, setCategories] = useState([]) + const [lice, setLice] = useState("1") + const [classement, setClassement] = useState(true) + const [fullClassement, setFullClassement] = useState(false) + + const setCategories_ = (e, catId) => { + if (e.target.checked) { + if (!categories.includes(catId)) { + setCategories([...categories, catId]) + } + } else { + setCategories(categories.filter(c => c !== catId)) + } + } + + const handleSubmit = (e) => { + e.preventDefault(); + + let catList2 = [] + for (const catId of categories) { + const preset = data.find(p => p.id === catId); + const dispoFiltered = Object.values(combs).filter(comb => comb.categoriesInscrites?.includes(catId)).sort(() => Math.random() - 0.5) + .map(comb => ({...comb, categorie: CatList[Math.min(CatList.length, CatList.indexOf(comb.categorie) + comb.overCategory)]})); + console.log("Creating category for preset", preset.name, "and", dispoFiltered.length, "combattants"); + + const catList = makeCategory(dispoFiltered); + console.log(catList) + + for (const list of catList) { + if (list.length > 10) { + catList2.push(...makeWeightCategories(list) + .map((combs, index, a) => ({combs, classement, preset, lice, fullClassement, index, size: a.length}))); + } else { + catList2.push(({combs: [...list], classement, preset, lice, fullClassement, index: 1, size: 1})); + } + } + } + sendCatList(toastId, t, catList2, sendRequest); + } + + return <> +
+

{t('créerToutesLesCatégories')}

+ +
+
+
+
+ + {error ? : <> + {data && data.length === 0 &&
{t('aucuneCatégorieDisponible')}
} + {data && data.map((cat, index) => +
+
+ setCategories_(e, cat.id)}/> + +
+
)} + } +
+
+ +
+ + setLice(e.target.value)}/> +
+ +
+ setClassement(e.target.checked)}/> + +
+
+ setFullClassement(e.target.checked)}/> + +
+
+
+ + +
+ +} diff --git a/src/main/webapp/src/components/cm/ListPresetSelect.jsx b/src/main/webapp/src/components/cm/ListPresetSelect.jsx index eb7b371..7877f0d 100644 --- a/src/main/webapp/src/components/cm/ListPresetSelect.jsx +++ b/src/main/webapp/src/components/cm/ListPresetSelect.jsx @@ -3,7 +3,7 @@ import {AxiosError} from "../AxiosError.jsx"; import {useTranslation} from "react-i18next"; import React, {useId} from "react"; -export function ListPresetSelect({disabled, value, onChange}) { +export function ListPresetSelect({disabled, value, onChange, returnId = true}) { const id = useId() const {data, error} = useRequestWS("listPreset", {}, null); const {t} = useTranslation(); @@ -12,9 +12,16 @@ export function ListPresetSelect({disabled, value, onChange}) { ?
: error diff --git a/src/main/webapp/src/pages/competition/CompetitionRegisterAdmin.jsx b/src/main/webapp/src/pages/competition/CompetitionRegisterAdmin.jsx index d018190..e8f6b9d 100644 --- a/src/main/webapp/src/pages/competition/CompetitionRegisterAdmin.jsx +++ b/src/main/webapp/src/pages/competition/CompetitionRegisterAdmin.jsx @@ -554,11 +554,11 @@ function Modal_({data2, data3, error2, sendRegister, modalState, setModalState,
{t('comp.modal.poids')} {source === "admin" && {t('comp.modal.annoncé')}} - setWeight(e.target.value)}/> {source === "admin" && <>{t('comp.modal.pesé')} - setWeightReal(e.target.value)}/>}
diff --git a/src/main/webapp/src/pages/competition/CompetitionView.jsx b/src/main/webapp/src/pages/competition/CompetitionView.jsx index d055e5b..0caceb9 100644 --- a/src/main/webapp/src/pages/competition/CompetitionView.jsx +++ b/src/main/webapp/src/pages/competition/CompetitionView.jsx @@ -156,11 +156,11 @@ function SelfRegister({data2}) {

{t('comp.monInscription')}