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 24ca75c..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 @@ -130,9 +131,39 @@ public class RCategorie { .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/webapp/src/components/cm/AutoCatModalContent.jsx b/src/main/webapp/src/components/cm/AutoCatModalContent.jsx index 8584740..0bd0303 100644 --- a/src/main/webapp/src/components/cm/AutoCatModalContent.jsx +++ b/src/main/webapp/src/components/cm/AutoCatModalContent.jsx @@ -1,4 +1,4 @@ -import React, {useEffect, useState} from "react"; +import React, {useEffect, useId, useState} from "react"; import {Trans, useTranslation} from "react-i18next"; import {useCountries} from "../../hooks/useCountries.jsx"; import {ListPresetSelect} from "./ListPresetSelect.jsx"; @@ -7,7 +7,8 @@ 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"; +import {useRequestWS, useWS} from "../../hooks/useWS.jsx"; +import {AxiosError} from "../AxiosError.jsx"; export function AutoCatModalContent({data, groups, setGroups, defaultPreset = -1}) { const country = useCountries('fr') @@ -369,16 +370,185 @@ const getCatNameList = (count) => { 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 >= 2) catNameList.push("Léger"); if (count >= 5) catNameList.push("Mi-moyen"); - if (count >= 1) catNameList.push("Moyen"); + if (count >= 3) catNameList.push("Moyen"); if (count >= 6) catNameList.push("Mi-lourd"); - if (count >= 2) catNameList.push("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(); @@ -408,7 +578,7 @@ export function AutoNewCatModalContent() { 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))) { + && (preset === undefined || comb.categoriesInscrites?.includes(preset.id))) { dataOut.push(comb) } } @@ -430,71 +600,8 @@ export function AutoNewCatModalContent() { } 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); - }) + sendCatList(toastId, t, catList + .map((combs, index, a) => ({combs, classement, preset, lice, fullClassement, index, size: a.length})), sendRequest); } return <> @@ -589,3 +696,107 @@ export function AutoNewCatModalContent() { } + + +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/pages/competition/editor/CMAdmin.jsx b/src/main/webapp/src/pages/competition/editor/CMAdmin.jsx index cfd9c5d..876ebda 100644 --- a/src/main/webapp/src/pages/competition/editor/CMAdmin.jsx +++ b/src/main/webapp/src/pages/competition/editor/CMAdmin.jsx @@ -20,7 +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"; +import {AutoNewCatModalContent, AutoNewCatSModalContent} from "../../../components/cm/AutoCatModalContent.jsx"; const vite_url = import.meta.env.VITE_URL; @@ -597,8 +597,7 @@ function CategoryHeader({ - - diff --git a/src/main/webapp/src/pages/competition/editor/CategoryAdminContent.jsx b/src/main/webapp/src/pages/competition/editor/CategoryAdminContent.jsx index 96ca086..fa0feaa 100644 --- a/src/main/webapp/src/pages/competition/editor/CategoryAdminContent.jsx +++ b/src/main/webapp/src/pages/competition/editor/CategoryAdminContent.jsx @@ -30,6 +30,7 @@ function CupImg() { style={{width: "16px"}} src="/img/171891.png" alt=""/> } + function CupImg2() { return d != null)}}); + if (data.categorie !== cat.id) + continue; + setGroups(prev => { if (data.c1 !== null && !prev.some(g => g.id === data.c1?.id)) return [...prev, {id: data.c1?.id, poule: data.poule}]; @@ -717,7 +721,8 @@ function MatchList({matches, cat, groups, reducer, classement = false}) { {index + 1} {!classement && {m.poule}} {!classement && {liceName[index % liceName.length]}} - {m.end && ((m.win > 0 && ) || (m.win === 0 && ))} + {m.end && ((m.win > 0 && + ) || (m.win === 0 && ))} handleCombClick(e, m.id, m.c1)}> @@ -726,7 +731,8 @@ function MatchList({matches, cat, groups, reducer, classement = false}) { onClick={e => handleCombClick(e, m.id, m.c2)}> - {m.end && ((m.win < 0 && ) || (m.win === 0 && ))} + {m.end && ((m.win < 0 && + ) || (m.win === 0 && ))} {scoreToString2(m, cards_v)} handleEditMatch(m.id)}> diff --git a/src/main/webapp/src/pages/competition/editor/SelectCombModalContent.jsx b/src/main/webapp/src/pages/competition/editor/SelectCombModalContent.jsx index 49cc6ef..a3fe039 100644 --- a/src/main/webapp/src/pages/competition/editor/SelectCombModalContent.jsx +++ b/src/main/webapp/src/pages/competition/editor/SelectCombModalContent.jsx @@ -156,7 +156,7 @@ export function SelectCombModalContent({data, groups, setGroups, teamMode = fals && (weightMax === 0 || comb.weight !== null && comb.weight <= weightMax) && (teamMode && (comb.teamMembers == null || comb.teamMembers.length === 0) || !teamMode && ((comb.teamMembers == null || comb.teamMembers.length === 0) !== team)) - && (preset === -1 || comb.categoriesInscrites.includes(preset))) { + && (preset === -1 || comb.categoriesInscrites?.includes(preset))) { dataOut[id] = dataIn[id]; } }