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..e0da444 100644 --- a/src/main/java/fr/titionfire/ffsaf/domain/entity/CombEntity.java +++ b/src/main/java/fr/titionfire/ffsaf/domain/entity/CombEntity.java @@ -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/ws/recv/RCategorie.java b/src/main/java/fr/titionfire/ffsaf/ws/recv/RCategorie.java index 2f7dcf2..24ca75c 100644 --- a/src/main/java/fr/titionfire/ffsaf/ws/recv/RCategorie.java +++ b/src/main/java/fr/titionfire/ffsaf/ws/recv/RCategorie.java @@ -117,7 +117,13 @@ 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)) 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/webapp/public/locales/en/cm.json b/src/main/webapp/public/locales/en/cm.json index 5ccf2bc..da85ab4 100644 --- a/src/main/webapp/public/locales/en/cm.json +++ b/src/main/webapp/public/locales/en/cm.json @@ -23,7 +23,9 @@ "cartonNoir": "Black card", "cartonRouge": "Red card", "catégorie": "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 +60,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", @@ -88,11 +94,13 @@ "inscrit": "Registered", "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,6 +113,7 @@ "nouvelle...": "New...", "obs.préfixDesSources": "Source prefix", "pays": "Country", + "personnaliser": "Personalize", "poids": "Weight", "poule": "Pool", "poulePour": "Pool for: ", diff --git a/src/main/webapp/public/locales/fr/cm.json b/src/main/webapp/public/locales/fr/cm.json index 384acfa..4f8f446 100644 --- a/src/main/webapp/public/locales/fr/cm.json +++ b/src/main/webapp/public/locales/fr/cm.json @@ -23,7 +23,9 @@ "cartonNoir": "Carton noir", "cartonRouge": "Carton rouge", "catégorie": "Catégorie", + "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 +40,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 +60,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", @@ -88,11 +94,13 @@ "inscrit": "Inscrit", "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,6 +113,7 @@ "nouvelle...": "Nouvelle...", "obs.préfixDesSources": "Préfix des sources", "pays": "Pays", + "personnaliser": "Personnaliser", "poids": "Poids", "poule": "Poule", "poulePour": "Poule pour: ", diff --git a/src/main/webapp/src/components/cm/AutoCatModalContent.jsx b/src/main/webapp/src/components/cm/AutoCatModalContent.jsx index bd9e1ee..8584740 100644 --- a/src/main/webapp/src/components/cm/AutoCatModalContent.jsx +++ b/src/main/webapp/src/components/cm/AutoCatModalContent.jsx @@ -1,8 +1,13 @@ import React, {useEffect, useState} from "react"; -import {useTranslation} from "react-i18next"; +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 {useWS} from "../../hooks/useWS.jsx"; export function AutoCatModalContent({data, groups, setGroups, defaultPreset = -1}) { const country = useCountries('fr') @@ -52,64 +57,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 +177,415 @@ 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 >= 3) catNameList.push("Léger"); + if (count >= 5) catNameList.push("Mi-moyen"); + if (count >= 1) catNameList.push("Moyen"); + if (count >= 6) catNameList.push("Mi-lourd"); + if (count >= 2) catNameList.push("Lourd"); + if (count >= 4) catNameList.push("Super-lourd"); + + return catNameList; +} + +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})))) + + 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 (gender.H) g.push('H'); + if (gender.F) g.push('F'); + + const type = catList[i].length > 5 && classement ? 3 : 1; + const newCat = { + name: preset.name + " - " + cat.map(pos => getCatName(CatList[pos])).join(", ") + + (gender.H && gender.F ? "" : " - " + g.join("/")) + (catList.length === 1 ? "" : " - " + getCatNameList(catList.length)[i]), + liceName: lice, + type: type, + treeAreClassement: classement, + fullClassement: fullClassement, + preset: {id: preset.id} + } + console.log(newCat) + + await sendRequest('createCategory', newCat).then(id => { + newCat["id"] = id; + const groups = makePoule(catList[i], []); + 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); + }) + } + + 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é')}} + +
+
+ + +
+ +} diff --git a/src/main/webapp/src/components/cm/ListPresetSelect.jsx b/src/main/webapp/src/components/cm/ListPresetSelect.jsx index eb7b371..6212e53 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/editor/CMAdmin.jsx b/src/main/webapp/src/pages/competition/editor/CMAdmin.jsx index 1cdcf1e..cfd9c5d 100644 --- a/src/main/webapp/src/pages/competition/editor/CMAdmin.jsx +++ b/src/main/webapp/src/pages/competition/editor/CMAdmin.jsx @@ -20,6 +20,7 @@ import {StateWindow} from "./StateWindow.jsx"; import {CombName, useCombs} from "../../../hooks/useComb.jsx"; import {useCards, useCardsDispatch} from "../../../hooks/useCard.jsx"; import {ListPresetSelect} from "../../../components/cm/ListPresetSelect.jsx"; +import {AutoNewCatModalContent} from "../../../components/cm/AutoCatModalContent.jsx"; const vite_url = import.meta.env.VITE_URL; @@ -504,8 +505,9 @@ function CategoryHeader({ cat, setCatId }) { const setLoading = useLoadingSwitcher() - const bthRef = useRef(); - const confirmRef = useRef(); + const bthRef = useRef(null); + const newBthRef = useRef(null); + const confirmRef = useRef(null); const [modal, setModal] = useState({}) const [confirm, setConfirm] = useState({}) const {t} = useTranslation("cm"); @@ -521,6 +523,7 @@ function CategoryHeader({ ]) } const sendAddCategory = ({data}) => { + console.log("add cat", data); setCats([...cats, data]) } const sendDelCategory = ({data}) => { @@ -554,8 +557,7 @@ function CategoryHeader({ if (selectedCatId !== "-1") { setCatId(selectedCatId); } else { // New category - setModal({}); - bthRef.current.click(); + newBthRef.current.click(); e.target.value = cat?.id; } } @@ -595,10 +597,58 @@ function CategoryHeader({ + + + + + { }} onCancel={confirm.cancel ? confirm.cancel : () => { }} title={confirm ? confirm.title : ""} message={confirm ? confirm.message : ""}/> + + + + + } @@ -617,6 +667,7 @@ function ModalContent({state, setCatId, setConfirm, confirmRef}) { const {sendRequest} = useWS(); useEffect(() => { + console.log(state); setName(state.name || ""); setLice(state.liceName || "1"); setPoule(((state.type || 1) & 1) !== 0); diff --git a/src/main/webapp/src/pages/competition/editor/CMTMatchPanel.jsx b/src/main/webapp/src/pages/competition/editor/CMTMatchPanel.jsx index 5fca965..feef8ab 100644 --- a/src/main/webapp/src/pages/competition/editor/CMTMatchPanel.jsx +++ b/src/main/webapp/src/pages/competition/editor/CMTMatchPanel.jsx @@ -317,9 +317,9 @@ function MatchList({matches, cat, menuActions, classement = false, currentMatch const liceName = (cat.liceName || "N/A").split(";"); const marches2 = classement - ? matches.filter(m => m.categorie_ord === -42) + ? matches.filter(m => m.categorie_ord === -42 && m.categorie === cat.id) .map(m => ({...m, ...win_end(m, cards_v)})) - : matches.filter(m => m.categorie_ord !== -42) + : matches.filter(m => m.categorie_ord !== -42 && m.categorie === cat.id) .sort((a, b) => a.categorie_ord - b.categorie_ord) .map(m => ({...m, ...win_end(m, cards_v)})) const firstIndex = marches2.findLastIndex(m => m.poule === '-') + 1; diff --git a/src/main/webapp/src/pages/competition/editor/CategoryAdminContent.jsx b/src/main/webapp/src/pages/competition/editor/CategoryAdminContent.jsx index ef17a2b..96ca086 100644 --- a/src/main/webapp/src/pages/competition/editor/CategoryAdminContent.jsx +++ b/src/main/webapp/src/pages/competition/editor/CategoryAdminContent.jsx @@ -433,7 +433,7 @@ function ListMatch({cat, matches, groups, reducer}) { matchOrderToUpdate: Object.fromEntries(matchOrderToUpdate), matchPouleToUpdate: Object.fromEntries(matchPouleToUpdate), matchesToRemove: matchesToRemove.map(m => m.id) - }), getToastMessage("toast.matchs.create", "ns")) + }), getToastMessage("toast.matchs.create", "cm")) .finally(() => { console.log("Finished creating matches"); }) @@ -521,9 +521,9 @@ function MatchList({matches, cat, groups, reducer, classement = false}) { const liceName = (cat.liceName || "N/A").split(";"); const marches2 = classement - ? matches.filter(m => m.categorie_ord === -42) + ? matches.filter(m => m.categorie_ord === -42 && m.categorie === cat.id) .map(m => ({...m, ...win_end(m, cards_v)})) - : matches.filter(m => m.categorie_ord !== -42) + : matches.filter(m => m.categorie_ord !== -42 && m.categorie === cat.id) .sort((a, b) => a.categorie_ord - b.categorie_ord) .map(m => ({...m, ...win_end(m, cards_v)}))