From c5f7b81ac3189aa9a5765f5577ac130653a26391 Mon Sep 17 00:00:00 2001 From: Thibaut Valentin Date: Wed, 17 Dec 2025 20:53:32 +0100 Subject: [PATCH] feat: move to MatchPanel --- .../competition/editor/CMTMatchPanel.jsx | 510 +++++++++++++++++ .../src/pages/competition/editor/CMTable.jsx | 513 +----------------- 2 files changed, 514 insertions(+), 509 deletions(-) create mode 100644 src/main/webapp/src/pages/competition/editor/CMTMatchPanel.jsx diff --git a/src/main/webapp/src/pages/competition/editor/CMTMatchPanel.jsx b/src/main/webapp/src/pages/competition/editor/CMTMatchPanel.jsx new file mode 100644 index 0000000..ab7f99b --- /dev/null +++ b/src/main/webapp/src/pages/competition/editor/CMTMatchPanel.jsx @@ -0,0 +1,510 @@ +import React, {useEffect, useRef, useState, useReducer} from "react"; +import {CombName, useCombs, useCombsDispatch} from "../../../hooks/useComb.jsx"; +import {usePubAffDispatch} from "../../../hooks/useExternalWindow.jsx"; +import {from_sendTree, TreeNode} from "../../../utils/TreeUtils.js"; +import {DrawGraph} from "../../result/DrawGraph.jsx"; +import {LoadingProvider, useLoadingSwitcher} from "../../../hooks/useLoading.jsx"; +import {useRequestWS, useWS} from "../../../hooks/useWS.jsx"; +import {MarchReducer} from "../../../utils/MatchReducer.jsx"; +import {scorePrint, win} from "../../../utils/Tools.js"; +import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; +import {faCircleQuestion} from "@fortawesome/free-regular-svg-icons"; +import {toast} from "react-toastify"; + +function CupImg() { + return +} + +export function CategorieSelect({catId, setCatId}) { + const setLoading = useLoadingSwitcher() + const {data: cats, setData: setCats} = useRequestWS('getAllCategory', {}, setLoading); + const {dispatch} = useWS(); + + useEffect(() => { + const categoryListener = ({data}) => { + setCats([...cats.filter(c => c.id !== data.id), data]) + } + dispatch({type: 'addListener', payload: {callback: categoryListener, code: 'sendCategory'}}) + return () => dispatch({type: 'removeListener', payload: categoryListener}) + }, [cats]); + + const cat = cats?.find(c => c.id === catId); + + return <> +
+
Catégorie
+ +
+ {catId !== -1 && } + +} + +function CMTMatchPanel({catId, cat}) { + const setLoading = useLoadingSwitcher() + const {sendRequest, dispatch} = useWS(); + const [trees, setTrees] = useState([]); + const [matches, reducer] = useReducer(MarchReducer, []); + const combDispatch = useCombsDispatch(); + + function readAndConvertMatch(matches, data, combsToAdd) { + matches.push({...data, c1: data.c1?.id, c2: data.c2?.id}) + if (data.c1) + combsToAdd.push(data.c1) + if (data.c2) + combsToAdd.push(data.c2) + } + + useEffect(() => { + if (!catId) + return; + setLoading(1); + sendRequest('getFullCategory', catId) + .then((data) => { + setTrees(data.trees.map(d => from_sendTree(d, true))) + + let matches2 = []; + let combsToAdd = []; + data.trees.flatMap(d => from_sendTree(d, false).flat()).forEach((data_) => readAndConvertMatch(matches2, data_, combsToAdd)); + data.matches.forEach((data_) => readAndConvertMatch(matches2, data_, combsToAdd)); + + reducer({type: 'REPLACE_ALL', payload: matches2}); + combDispatch({type: 'SET_ALL', payload: {source: "match", data: combsToAdd}}); + }).finally(() => setLoading(0)) + + const treeListener = ({data}) => { + if (data.length < 1 || data[0].categorie !== catId) + return + setTrees(data.map(d => from_sendTree(d, true))) + + let matches2 = []; + let combsToAdd = []; + data.flatMap(d => from_sendTree(d, false).flat()).forEach((data_) => readAndConvertMatch(matches2, data_, combsToAdd)); + reducer({type: 'REPLACE_TREE', payload: matches2}); + combDispatch({type: 'SET_ALL', payload: {source: "match", data: combsToAdd}}); + } + + const matchListener = ({data: datas}) => { + for (const data of datas) { + reducer({type: 'UPDATE_OR_ADD', payload: {...data, c1: data.c1?.id, c2: data.c2?.id}}) + combDispatch({type: 'SET_ALL', payload: {source: "match", data: [data.c1, data.c2].filter(d => d != null)}}) + } + } + + const matchOrder = ({data}) => { + reducer({type: 'REORDER', payload: data}) + } + + const deleteMatch = ({data: datas}) => { + for (const data of datas) + reducer({type: 'REMOVE', payload: data}) + } + + dispatch({type: 'addListener', payload: {callback: treeListener, code: 'sendTreeCategory'}}) + dispatch({type: 'addListener', payload: {callback: matchListener, code: 'sendMatch'}}) + dispatch({type: 'addListener', payload: {callback: matchOrder, code: 'sendMatchOrder'}}) + dispatch({type: 'addListener', payload: {callback: deleteMatch, code: 'sendDeleteMatch'}}) + return () => { + dispatch({type: 'removeListener', payload: treeListener}) + dispatch({type: 'removeListener', payload: matchListener}) + dispatch({type: 'removeListener', payload: matchOrder}) + dispatch({type: 'removeListener', payload: deleteMatch}) + } + }, [catId]); + + return +} + +function ListMatch({cat, matches, trees}) { + const [type, setType] = useState(1); + + useEffect(() => { + if ((cat.type & type) === 0) + setType(cat.type); + }, [cat]); + + return
+ {cat && cat.type === 3 && <> +
    +
  • +
    setType(1)}>Poule +
    +
  • +
  • +
    setType(2)}>Tournois +
    +
  • +
+ + } + + {type === 1 && <> + + } + + {type === 2 && <> + + } +
+} + +function MatchList({matches, cat}) { + const [activeMatch, setActiveMatch] = useState(null) + const [lice, setLice] = useState(localStorage.getItem("cm_lice") || "A") + const publicAffDispatch = usePubAffDispatch(); + + const liceName = (cat.liceName || "N/A").split(";"); + const marches2 = matches.filter(m => m.categorie_ord !== -42) + .sort((a, b) => a.categorie_ord - b.categorie_ord) + .map(m => ({...m, win: win(m.scores)})) + + const match = matches.find(m => m.id === activeMatch) + useEffect(() => { + if (!match) { + publicAffDispatch({type: 'SET_DATA', payload: {c1: undefined, c2: undefined, next: []}}); + } else { + publicAffDispatch({ + type: 'SET_DATA', + payload: { + c1: match.c1, + c2: match.c2, + next: marches2.filter(m => !m.end && m.poule === lice && m.id !== activeMatch).map(m => ({c1: m.c1, c2: m.c2})) + } + }); + } + }, [match]); + //useEffect(() => { + // if (activeMatch !== null) + // setActiveMatch(null); + //}, [cat]) + + useEffect(() => { + if (match && match.poule !== lice) + setActiveMatch(marches2.find(m => !m.end && m.poule === lice)?.id) + }, [lice]); + + useEffect(() => { + if (marches2.length === 0) + return; + if (marches2.some(m => m.id === activeMatch)) + return; + + setActiveMatch(marches2.find(m => !m.end && m.poule === lice)?.id); + }, [matches]) + + const firstIndex = marches2.findLastIndex(m => m.poule === '-') + 1; + return <> + {liceName.length > 1 && +
+ + +
+ } + +
+ + + + + + + + + + + + + + {marches2.map((m, index) => ( + setActiveMatch(m.id)}> + + + + + + + + + ))} + +
LPRougeBlue
+ {liceName[(index - firstIndex) % liceName.length]}{m.poule} + {index >= firstIndex ? index + 1 - firstIndex : ""}{m.end && m.win > 0 && } + + {m.end && m.win < 0 && }
+
+ + {activeMatch && } + +} + +function BuildTree({treeData, matches}) { + const scrollRef = useRef(null) + const [currentMatch, setCurrentMatch] = useState(null) + const {getComb} = useCombs() + const publicAffDispatch = usePubAffDispatch(); + + const match = matches.find(m => m.id === currentMatch?.matchSelect) + useEffect(() => { + if (!match) { + publicAffDispatch({type: 'SET_DATA', payload: {c1: undefined, c2: undefined}}); + } else { + publicAffDispatch({type: 'SET_DATA', payload: {c1: match.c1, c2: match.c2}}); + } + }, [match]); + const next_match = matches.find(m => m.id === currentMatch?.matchNext) + useEffect(() => { + if (!next_match) { + publicAffDispatch({type: 'SET_DATA', payload: {next: []}}); + } else { + publicAffDispatch({type: 'SET_DATA', payload: {next: [{c1: next_match.c1, c2: next_match.c2}]}}); + } + }, [next_match]); + + function parseTree(data_in) { + if (data_in?.data == null) + return null + + const matchData = matches.find(m => m.id === data_in.data) + const c1 = getComb(matchData?.c1) + const c2 = getComb(matchData?.c2) + + + let node = new TreeNode({ + ...matchData, + c1FullName: c1 !== null ? c1.fname + " " + c1.lname : null, + c2FullName: c2 !== null ? c2.fname + " " + c2.lname : null + }) + node.left = parseTree(data_in?.left) + node.right = parseTree(data_in?.right) + + return node + } + + function initTree(data_in) { + let out = [] + for (const din of data_in) { + out.push(parseTree(din)) + } + return out + } + + const trees = initTree(treeData); + + const onMatchClick = (rect, matchId, __) => { + setCurrentMatch({matchSelect: matchId, matchNext: new TreeNode(matchId).nextMatchTree(trees.reverse())}); + } + + const onClickVoid = () => { + } + + + return
+
+ +
+ + {currentMatch?.matchSelect && } +
+} + +function ScorePanel({matchId, match}) { + const {sendRequest} = useWS() + const setLoading = useLoadingSwitcher() + + const [end, setEnd] = useState(match?.end || false) + const [scoreIn, setScoreIn] = useState("") + const inputRef = useRef(null) + const tableRef = useRef(null) + const scoreRef = useRef([]) + const lastScoreClick = useRef(null) + + const handleScoreClick = (e, round, comb) => { + e.stopPropagation(); + const tableRect = tableRef.current.getBoundingClientRect(); + const rect = e.currentTarget.getBoundingClientRect(); + + updateScore(); + + const sel = inputRef.current; + sel.style.top = (rect.y - tableRect.y) + "px"; + sel.style.left = (rect.x - tableRect.x) + "px"; + sel.style.width = rect.width + "px"; + sel.style.height = rect.height + "px"; + sel.style.display = "block"; + + if (round === -1) { + const maxRound = (Math.max(...match.scores.map(s => s.n_round), -1) + 1) || 0; + setScoreIn(""); + console.log("Setting for new round", maxRound); + lastScoreClick.current = {matchId: matchId, round: maxRound, comb}; + } else { + const score = match.scores.find(s => s.n_round === round); + setScoreIn((comb === 1 ? score?.s1 : score?.s2) || ""); + lastScoreClick.current = {matchId: matchId, round, comb}; + setTimeout(() => inputRef.current.select(), 100); + } + } + + const updateScore = () => { + if (lastScoreClick.current !== null) { + const {matchId, round, comb} = lastScoreClick.current; + lastScoreClick.current = null; + + const scoreIn_ = String(scoreIn).trim() === "" ? -1000 : Number(scoreIn); + + const score = match.scores.find(s => s.n_round === round); + let newScore; + if (score) { + if (comb === 1) + newScore = {...score, s1: scoreIn_}; + else + newScore = {...score, s2: scoreIn_}; + + if (newScore.s1 === score?.s1 && newScore.s2 === score?.s2) + return + } else { + newScore = {n_round: round, s1: (comb === 1 ? scoreIn_ : -1000), s2: (comb === 2 ? scoreIn_ : -1000)}; + if (newScore.s1 === -1000 && newScore.s2 === -1000) + return + } + + console.log("Updating score", matchId, newScore); + + setLoading(1) + sendRequest('updateMatchScore', {matchId: matchId, ...newScore}) + .finally(() => { + setLoading(0) + }) + } + } + + const onClickVoid = () => { + updateScore(); + + const sel = inputRef.current; + sel.style.display = "none"; + lastScoreClick.current = null; + } + + useEffect(() => { + if (!match || match?.end === end) + return; + + if (end) { + if (win(match?.scores) === 0 && match.categorie_ord === -42) { + toast.error("Impossible de terminer un match nul en tournois."); + setEnd(false); + return; + } + } + + setLoading(1) + sendRequest('updateMatchEnd', {matchId: matchId, end}) + .finally(() => { + setLoading(0) + }) + }, [end]); + + useEffect(() => { + onClickVoid() + }, [matchId]); + + useEffect(() => { + if (match?.scores) + scoreRef.current = scoreRef.current.slice(0, match.scores.length); + }, [match?.scores]); + + useEffect(() => { + if (!match) + return; + setEnd(match.end); + }, [match]); + + const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]') + const o = [...tooltipTriggerList] + o.map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl)) + + const tt = "Score speciaux :
" + + "-997 : disqualifié
" + + "-998 : absent
" + + "-999 : forfait" + + const maxRound = (match?.scores) ? (Math.max(...match.scores.map(s => s.n_round), -1) + 1) : 0; + return
+
+
Scores
+ + + + + + + + + + {match?.scores && match.scores.sort((a, b) => a.n_round - b.n_round).map(score => ( + + + + + + ))} + + + + + + +
MancheRougeBleu
{score.n_round + 1} scoreRef.current[score.n_round * 2] = e} + onClick={e => handleScoreClick(e, score.n_round, 1)}>{scorePrint(score.s1)} scoreRef.current[score.n_round * 2 + 1] = e} + onClick={e => handleScoreClick(e, score.n_round, 2)}>{scorePrint(score.s2)}
scoreRef.current[maxRound * 2] = e} onClick={e => handleScoreClick(e, -1, 1)}>- scoreRef.current[maxRound * 2 + 1] = e} onClick={e => handleScoreClick(e, -1, 2)}>- +
+
+
+ setEnd(e.target.checked)}/> + +
+
+ setScoreIn(e.target.value)} + onClick={e => e.stopPropagation()} + onKeyDown={e => { + if (e.key === "Tab") { + if (lastScoreClick.current !== null) { + const {round, comb} = lastScoreClick.current; + const nextIndex = (round * 2 + (comb - 1)) + (e.shiftKey ? -1 : 1); + if (nextIndex >= 0 && nextIndex < scoreRef.current.length) { + e.preventDefault(); + scoreRef.current[nextIndex].click(); + } + } + } else if (e.key === "Enter") { + e.preventDefault(); + onClickVoid(); + } + }}/> +
+
+
+
+} diff --git a/src/main/webapp/src/pages/competition/editor/CMTable.jsx b/src/main/webapp/src/pages/competition/editor/CMTable.jsx index 9929bc2..c5e0fde 100644 --- a/src/main/webapp/src/pages/competition/editor/CMTable.jsx +++ b/src/main/webapp/src/pages/competition/editor/CMTable.jsx @@ -1,14 +1,7 @@ -import React, {useEffect, useReducer, useRef, useState} from "react"; -import {useRequestWS, useWS} from "../../../hooks/useWS.jsx"; -import {LoadingProvider, useLoadingSwitcher} from "../../../hooks/useLoading.jsx"; -import {from_sendTree, TreeNode} from "../../../utils/TreeUtils.js"; -import {MarchReducer} from "../../../utils/MatchReducer.jsx"; -import {CombName, useCombs, useCombsDispatch} from "../../../hooks/useComb.jsx"; +import React, {useEffect, useRef, useState} from "react"; +import {useRequestWS} from "../../../hooks/useWS.jsx"; +import {useCombsDispatch} from "../../../hooks/useComb.jsx"; import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; -import {faCircleQuestion} from "@fortawesome/free-regular-svg-icons"; -import {DrawGraph} from "../../result/DrawGraph.jsx"; -import {scorePrint, win} from "../../../utils/Tools.js"; -import {toast} from "react-toastify"; import {createPortal} from "react-dom"; import {copyStyles} from "../../../utils/copyStyles.js"; import {PubAffProvider, usePubAffDispatch} from "../../../hooks/useExternalWindow.jsx"; @@ -16,12 +9,7 @@ import {faDisplay} from "@fortawesome/free-solid-svg-icons"; import {PubAffWindow} from "./PubAffWindow.jsx"; import {SimpleIconsScore} from "../../../assets/SimpleIconsScore.ts"; import {ChronoPanel} from "./CMTChronoPanel.jsx"; - -function CupImg() { - return -} +import {CategorieSelect} from "./CMTMatchPanel.jsx"; export function CMTable() { const combDispatch = useCombsDispatch() @@ -128,496 +116,3 @@ function Menu() { {externalWindow.current && createPortal(, containerEl.current)} } - -function CategorieSelect({catId, setCatId}) { - const setLoading = useLoadingSwitcher() - const {data: cats, setData: setCats} = useRequestWS('getAllCategory', {}, setLoading); - const {dispatch} = useWS(); - - useEffect(() => { - const categoryListener = ({data}) => { - setCats([...cats.filter(c => c.id !== data.id), data]) - } - dispatch({type: 'addListener', payload: {callback: categoryListener, code: 'sendCategory'}}) - return () => dispatch({type: 'removeListener', payload: categoryListener}) - }, [cats]); - - const cat = cats?.find(c => c.id === catId); - - return <> -
-
Catégorie
- -
- {catId !== -1 && } - -} - -function MatchPanel({catId, cat}) { - const setLoading = useLoadingSwitcher() - const {sendRequest, dispatch} = useWS(); - const [trees, setTrees] = useState([]); - const [matches, reducer] = useReducer(MarchReducer, []); - const combDispatch = useCombsDispatch(); - - function readAndConvertMatch(matches, data, combsToAdd) { - matches.push({...data, c1: data.c1?.id, c2: data.c2?.id}) - if (data.c1) - combsToAdd.push(data.c1) - if (data.c2) - combsToAdd.push(data.c2) - } - - useEffect(() => { - if (!catId) - return; - setLoading(1); - sendRequest('getFullCategory', catId) - .then((data) => { - setTrees(data.trees.map(d => from_sendTree(d, true))) - - let matches2 = []; - let combsToAdd = []; - data.trees.flatMap(d => from_sendTree(d, false).flat()).forEach((data_) => readAndConvertMatch(matches2, data_, combsToAdd)); - data.matches.forEach((data_) => readAndConvertMatch(matches2, data_, combsToAdd)); - - reducer({type: 'REPLACE_ALL', payload: matches2}); - combDispatch({type: 'SET_ALL', payload: {source: "match", data: combsToAdd}}); - }).finally(() => setLoading(0)) - - const treeListener = ({data}) => { - if (data.length < 1 || data[0].categorie !== catId) - return - setTrees(data.map(d => from_sendTree(d, true))) - - let matches2 = []; - let combsToAdd = []; - data.flatMap(d => from_sendTree(d, false).flat()).forEach((data_) => readAndConvertMatch(matches2, data_, combsToAdd)); - reducer({type: 'REPLACE_TREE', payload: matches2}); - combDispatch({type: 'SET_ALL', payload: {source: "match", data: combsToAdd}}); - } - - const matchListener = ({data: datas}) => { - for (const data of datas) { - reducer({type: 'UPDATE_OR_ADD', payload: {...data, c1: data.c1?.id, c2: data.c2?.id}}) - combDispatch({type: 'SET_ALL', payload: {source: "match", data: [data.c1, data.c2].filter(d => d != null)}}) - } - } - - const matchOrder = ({data}) => { - reducer({type: 'REORDER', payload: data}) - } - - const deleteMatch = ({data: datas}) => { - for (const data of datas) - reducer({type: 'REMOVE', payload: data}) - } - - dispatch({type: 'addListener', payload: {callback: treeListener, code: 'sendTreeCategory'}}) - dispatch({type: 'addListener', payload: {callback: matchListener, code: 'sendMatch'}}) - dispatch({type: 'addListener', payload: {callback: matchOrder, code: 'sendMatchOrder'}}) - dispatch({type: 'addListener', payload: {callback: deleteMatch, code: 'sendDeleteMatch'}}) - return () => { - dispatch({type: 'removeListener', payload: treeListener}) - dispatch({type: 'removeListener', payload: matchListener}) - dispatch({type: 'removeListener', payload: matchOrder}) - dispatch({type: 'removeListener', payload: deleteMatch}) - } - }, [catId]); - - return -} - -function ListMatch({cat, matches, trees}) { - const [type, setType] = useState(1); - - useEffect(() => { - if ((cat.type & type) === 0) - setType(cat.type); - }, [cat]); - - return
- {cat && cat.type === 3 && <> -
    -
  • -
    setType(1)}>Poule -
    -
  • -
  • -
    setType(2)}>Tournois -
    -
  • -
- - } - - {type === 1 && <> - - } - - {type === 2 && <> - - } -
-} - -function MatchList({matches, cat}) { - const [activeMatch, setActiveMatch] = useState(null) - const [lice, setLice] = useState(localStorage.getItem("cm_lice") || "A") - const publicAffDispatch = usePubAffDispatch(); - - const liceName = (cat.liceName || "N/A").split(";"); - const marches2 = matches.filter(m => m.categorie_ord !== -42) - .sort((a, b) => a.categorie_ord - b.categorie_ord) - .map(m => ({...m, win: win(m.scores)})) - - const match = matches.find(m => m.id === activeMatch) - useEffect(() => { - if (!match) { - publicAffDispatch({type: 'SET_DATA', payload: {c1: undefined, c2: undefined, next: []}}); - } else { - publicAffDispatch({ - type: 'SET_DATA', - payload: { - c1: match.c1, - c2: match.c2, - next: marches2.filter(m => !m.end && m.poule === lice && m.id !== activeMatch).map(m => ({c1: m.c1, c2: m.c2})) - } - }); - } - }, [match]); - //useEffect(() => { - // if (activeMatch !== null) - // setActiveMatch(null); - //}, [cat]) - - useEffect(() => { - if (match && match.poule !== lice) - setActiveMatch(marches2.find(m => !m.end && m.poule === lice)?.id) - }, [lice]); - - useEffect(() => { - if (marches2.length === 0) - return; - if (marches2.some(m => m.id === activeMatch)) - return; - - setActiveMatch(marches2.find(m => !m.end && m.poule === lice)?.id); - }, [matches]) - - const firstIndex = marches2.findLastIndex(m => m.poule === '-') + 1; - return <> - {liceName.length > 1 && -
- - -
- } - -
- - - - - - - - - - - - - - {marches2.map((m, index) => ( - setActiveMatch(m.id)}> - - - - - - - - - ))} - -
LPRougeBlue
- {liceName[(index - firstIndex) % liceName.length]}{m.poule} - {index >= firstIndex ? index + 1 - firstIndex : ""}{m.end && m.win > 0 && } - - {m.end && m.win < 0 && }
-
- - {activeMatch && } - -} - -function BuildTree({treeData, matches}) { - const scrollRef = useRef(null) - const [currentMatch, setCurrentMatch] = useState(null) - const {getComb} = useCombs() - const publicAffDispatch = usePubAffDispatch(); - - const match = matches.find(m => m.id === currentMatch?.matchSelect) - useEffect(() => { - if (!match) { - publicAffDispatch({type: 'SET_DATA', payload: {c1: undefined, c2: undefined}}); - } else { - publicAffDispatch({type: 'SET_DATA', payload: {c1: match.c1, c2: match.c2}}); - } - }, [match]); - const next_match = matches.find(m => m.id === currentMatch?.matchNext) - useEffect(() => { - if (!next_match) { - publicAffDispatch({type: 'SET_DATA', payload: {next: []}}); - } else { - publicAffDispatch({type: 'SET_DATA', payload: {next: [{c1: next_match.c1, c2: next_match.c2}]}}); - } - }, [next_match]); - - function parseTree(data_in) { - if (data_in?.data == null) - return null - - const matchData = matches.find(m => m.id === data_in.data) - const c1 = getComb(matchData?.c1) - const c2 = getComb(matchData?.c2) - - - let node = new TreeNode({ - ...matchData, - c1FullName: c1 !== null ? c1.fname + " " + c1.lname : null, - c2FullName: c2 !== null ? c2.fname + " " + c2.lname : null - }) - node.left = parseTree(data_in?.left) - node.right = parseTree(data_in?.right) - - return node - } - - function initTree(data_in) { - let out = [] - for (const din of data_in) { - out.push(parseTree(din)) - } - return out - } - - const trees = initTree(treeData); - - const onMatchClick = (rect, matchId, __) => { - setCurrentMatch({matchSelect: matchId, matchNext: new TreeNode(matchId).nextMatchTree(trees.reverse())}); - } - - const onClickVoid = () => { - } - - - return
-
- -
- - {currentMatch?.matchSelect && } -
-} - - -function ScorePanel({matchId, match}) { - const {sendRequest} = useWS() - const setLoading = useLoadingSwitcher() - - const [end, setEnd] = useState(match?.end || false) - const [scoreIn, setScoreIn] = useState("") - const inputRef = useRef(null) - const tableRef = useRef(null) - const scoreRef = useRef([]) - const lastScoreClick = useRef(null) - - const handleScoreClick = (e, round, comb) => { - e.stopPropagation(); - const tableRect = tableRef.current.getBoundingClientRect(); - const rect = e.currentTarget.getBoundingClientRect(); - - updateScore(); - - const sel = inputRef.current; - sel.style.top = (rect.y - tableRect.y) + "px"; - sel.style.left = (rect.x - tableRect.x) + "px"; - sel.style.width = rect.width + "px"; - sel.style.height = rect.height + "px"; - sel.style.display = "block"; - - if (round === -1) { - const maxRound = (Math.max(...match.scores.map(s => s.n_round), -1) + 1) || 0; - setScoreIn(""); - console.log("Setting for new round", maxRound); - lastScoreClick.current = {matchId: matchId, round: maxRound, comb}; - } else { - const score = match.scores.find(s => s.n_round === round); - setScoreIn((comb === 1 ? score?.s1 : score?.s2) || ""); - lastScoreClick.current = {matchId: matchId, round, comb}; - setTimeout(() => inputRef.current.select(), 100); - } - } - - const updateScore = () => { - if (lastScoreClick.current !== null) { - const {matchId, round, comb} = lastScoreClick.current; - lastScoreClick.current = null; - - const scoreIn_ = String(scoreIn).trim() === "" ? -1000 : Number(scoreIn); - - const score = match.scores.find(s => s.n_round === round); - let newScore; - if (score) { - if (comb === 1) - newScore = {...score, s1: scoreIn_}; - else - newScore = {...score, s2: scoreIn_}; - - if (newScore.s1 === score?.s1 && newScore.s2 === score?.s2) - return - } else { - newScore = {n_round: round, s1: (comb === 1 ? scoreIn_ : -1000), s2: (comb === 2 ? scoreIn_ : -1000)}; - if (newScore.s1 === -1000 && newScore.s2 === -1000) - return - } - - console.log("Updating score", matchId, newScore); - - setLoading(1) - sendRequest('updateMatchScore', {matchId: matchId, ...newScore}) - .finally(() => { - setLoading(0) - }) - } - } - - const onClickVoid = () => { - updateScore(); - - const sel = inputRef.current; - sel.style.display = "none"; - lastScoreClick.current = null; - } - - useEffect(() => { - if (!match || match?.end === end) - return; - - if (end) { - if (win(match?.scores) === 0 && match.categorie_ord === -42) { - toast.error("Impossible de terminer un match nul en tournois."); - setEnd(false); - return; - } - } - - setLoading(1) - sendRequest('updateMatchEnd', {matchId: matchId, end}) - .finally(() => { - setLoading(0) - }) - }, [end]); - - useEffect(() => { - onClickVoid() - }, [matchId]); - - useEffect(() => { - if (match?.scores) - scoreRef.current = scoreRef.current.slice(0, match.scores.length); - }, [match?.scores]); - - useEffect(() => { - if (!match) - return; - setEnd(match.end); - }, [match]); - - const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]') - const o = [...tooltipTriggerList] - o.map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl)) - - const tt = "Score speciaux :
" + - "-997 : disqualifié
" + - "-998 : absent
" + - "-999 : forfait" - - const maxRound = (match?.scores) ? (Math.max(...match.scores.map(s => s.n_round), -1) + 1) : 0; - return
-
-
Scores
- - - - - - - - - - {match?.scores && match.scores.sort((a, b) => a.n_round - b.n_round).map(score => ( - - - - - - ))} - - - - - - -
MancheRougeBleu
{score.n_round + 1} scoreRef.current[score.n_round * 2] = e} - onClick={e => handleScoreClick(e, score.n_round, 1)}>{scorePrint(score.s1)} scoreRef.current[score.n_round * 2 + 1] = e} - onClick={e => handleScoreClick(e, score.n_round, 2)}>{scorePrint(score.s2)}
scoreRef.current[maxRound * 2] = e} onClick={e => handleScoreClick(e, -1, 1)}>- scoreRef.current[maxRound * 2 + 1] = e} onClick={e => handleScoreClick(e, -1, 2)}>- -
-
-
- setEnd(e.target.checked)}/> - -
-
- setScoreIn(e.target.value)} - onClick={e => e.stopPropagation()} - onKeyDown={e => { - if (e.key === "Tab") { - if (lastScoreClick.current !== null) { - const {round, comb} = lastScoreClick.current; - const nextIndex = (round * 2 + (comb - 1)) + (e.shiftKey ? -1 : 1); - if (nextIndex >= 0 && nextIndex < scoreRef.current.length) { - e.preventDefault(); - scoreRef.current[nextIndex].click(); - } - } - } else if (e.key === "Enter") { - e.preventDefault(); - onClickVoid(); - } - }}/> -
-
-
-
-}