feat: categorie on CM
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
import {createContext, useContext, useEffect, useId, useReducer, useRef, useState} from "react";
|
||||
import {apiAxios} from "../utils/Tools.js";
|
||||
import {toast} from "react-toastify";
|
||||
|
||||
function uuidv4() {
|
||||
return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, c =>
|
||||
@@ -45,6 +47,11 @@ export function WSProvider({url, onmessage, children}) {
|
||||
const ws = useRef(null)
|
||||
const listenersRef = useRef([])
|
||||
const callbackRef = useRef({})
|
||||
const isReadyRef = useRef(isReady)
|
||||
|
||||
useEffect(() => {
|
||||
isReadyRef.current = isReady
|
||||
}, [isReady])
|
||||
|
||||
useEffect(() => {
|
||||
listenersRef.current = state.listener
|
||||
@@ -129,7 +136,7 @@ export function WSProvider({url, onmessage, children}) {
|
||||
const send = (uuid, code, type, data, resolve = () => {
|
||||
}, reject = () => {
|
||||
}) => {
|
||||
if (!isReady) {
|
||||
if (!isReadyRef.current) {
|
||||
reject("WebSocket is not connected");
|
||||
return;
|
||||
}
|
||||
@@ -150,6 +157,8 @@ export function WSProvider({url, onmessage, children}) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
console.log("WSProvider: sending message", {uuid, code, type, data});
|
||||
ws.current?.send(JSON.stringify({
|
||||
uuid: uuid,
|
||||
code: code,
|
||||
@@ -191,3 +200,37 @@ export function useWS() {
|
||||
send,
|
||||
}
|
||||
}
|
||||
|
||||
export function useRequestWS(code, payload, setLoading = null, loadingLevel = 1) {
|
||||
const [data, setData] = useState(null)
|
||||
const [error, setErrors] = useState(null)
|
||||
const {isReady, sendRequest} = useWS()
|
||||
|
||||
const refresh = (code, payload) => {
|
||||
if (setLoading)
|
||||
setLoading(loadingLevel)
|
||||
|
||||
sendRequest(code, payload).then((data) => {
|
||||
setData(data);
|
||||
}).catch((err) => {
|
||||
setErrors(err);
|
||||
}).finally(() => {
|
||||
if (setLoading)
|
||||
setLoading(0)
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (isReady)
|
||||
refresh(code, payload)
|
||||
else{
|
||||
if (setLoading)
|
||||
setLoading(loadingLevel)
|
||||
setTimeout(() => refresh(code, payload), 1000)
|
||||
}
|
||||
}, []);
|
||||
|
||||
return {
|
||||
data, error, refresh, setData
|
||||
}
|
||||
}
|
||||
|
||||
445
src/main/webapp/src/pages/competition/editor/CMAdmin.jsx
Normal file
445
src/main/webapp/src/pages/competition/editor/CMAdmin.jsx
Normal file
@@ -0,0 +1,445 @@
|
||||
import {useEffect, useReducer, useRef, useState} from "react";
|
||||
import {useRequestWS, useWS} from "../../../hooks/useWS.jsx";
|
||||
import {LoadingProvider, useLoadingSwitcher} from "../../../hooks/useLoading.jsx";
|
||||
import {CheckField, TextField} from "../../../components/MemberCustomFiels.jsx";
|
||||
import {toast} from "react-toastify";
|
||||
import {build_tree, from_sendTree, resize_tree, TreeNode} from "../../../utils/TreeUtils.js"
|
||||
import {ConfirmDialog} from "../../../components/ConfirmDialog.jsx";
|
||||
import {SimpleReducer} from "../../../utils/SimpleReducer.jsx";
|
||||
import {MarchReducer} from "../../../utils/MatchReducer.jsx";
|
||||
|
||||
export function CMAdmin() {
|
||||
const [catId, setCatId] = useState(null);
|
||||
const [cat, setCat] = useState(null);
|
||||
const [combs, setCombs] = useState([])
|
||||
// const [cats, setCats] = useState([])
|
||||
|
||||
const {dispatch} = useWS();
|
||||
|
||||
useEffect(() => {
|
||||
const categoryListener = ({data}) => {
|
||||
if (!cat || data.id !== cat.id)
|
||||
return
|
||||
setCat({
|
||||
...cat,
|
||||
name: data.name,
|
||||
liceName: data.liceName,
|
||||
type: data.type
|
||||
})
|
||||
}
|
||||
dispatch({type: 'addListener', payload: {callback: categoryListener, code: 'sendCategory'}})
|
||||
return () => dispatch({type: 'removeListener', payload: categoryListener})
|
||||
}, []);
|
||||
|
||||
/*useEffect(() => {
|
||||
toast.promise(sendRequest("getAllCategory", {}),
|
||||
{
|
||||
pending: 'Chargement des catégories...',
|
||||
success: 'Catégories chargées !',
|
||||
error: 'Erreur lors du chargement des catégories'
|
||||
}
|
||||
).then((data) => {
|
||||
setCats(data);
|
||||
})
|
||||
}, []);*/
|
||||
|
||||
return <>
|
||||
<div className="card">
|
||||
<div className='card-header'>
|
||||
<CategoryHeader cat={cat} setCatId={setCatId}/>
|
||||
</div>
|
||||
|
||||
<div className="card-body">
|
||||
<LoadingProvider>
|
||||
<div className="row">
|
||||
<CategoryContent cat={cat} catId={catId} setCat={setCat}/>
|
||||
</div>
|
||||
</LoadingProvider>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
|
||||
function CategoryHeader({cat, setCatId}) {
|
||||
const setLoading = useLoadingSwitcher()
|
||||
const bthRef = useRef();
|
||||
const confirmRef = useRef();
|
||||
const [modal, setModal] = useState({})
|
||||
const [confirm, setConfirm] = useState({})
|
||||
|
||||
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]);
|
||||
|
||||
useEffect(() => {
|
||||
if (cats && cats.length > 0 && !cat) {
|
||||
setCatId(cats[0].id);
|
||||
}
|
||||
}, [cats]);
|
||||
|
||||
const handleCatChange = (e) => {
|
||||
const selectedCatId = e.target.value;
|
||||
if (selectedCatId !== "-1") {
|
||||
setCatId(selectedCatId);
|
||||
} else { // New category
|
||||
setModal({});
|
||||
bthRef.current.click();
|
||||
console.log(cat);
|
||||
e.target.value = cat?.id;
|
||||
}
|
||||
}
|
||||
|
||||
return <div className="row">
|
||||
<div className="col-auto">
|
||||
<div className="input-group">
|
||||
<h5 style={{margin: "auto 0.5em auto 0"}}>Edition de la catégorie</h5>
|
||||
<select className="form-select" onChange={handleCatChange}>
|
||||
{cats && cats.map(c => (
|
||||
<option key={c.id} value={c.id}>{c.name}</option>))}
|
||||
{cats && <option value={-1}>Nouvelle...</option>}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col">
|
||||
</div>
|
||||
<div className="col">
|
||||
<button className="btn btn-primary float-end" onClick={() => {
|
||||
setModal(cat);
|
||||
bthRef.current.click();
|
||||
}} disabled={cat === null}>Modifier
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
<button ref={bthRef} data-bs-toggle="modal" data-bs-target="#CategorieModal" style={{display: "none"}}>open</button>
|
||||
<div className="modal fade" id="CategorieModal" tabIndex="-1" aria-labelledby="CategorieModalLabel"
|
||||
aria-hidden="true">
|
||||
<div className="modal-dialog">
|
||||
<div className="modal-content">
|
||||
<ModalContent state={modal} setCatId={setCatId} setConfirm={setConfirm} confirmRef={confirmRef}/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button ref={confirmRef} data-bs-toggle="modal" data-bs-target="#confirm-dialog" style={{display: "none"}}>open</button>
|
||||
<ConfirmDialog id="confirm-dialog" onConfirm={confirm.confirm ? confirm.confirm : () => {
|
||||
}} onCancel={confirm.cancel ? confirm.cancel : () => {
|
||||
}} title={confirm ? confirm.title : ""} message={confirm ? confirm.message : ""}/>
|
||||
</div>
|
||||
}
|
||||
|
||||
function ModalContent({state, setCatId, setConfirm, confirmRef}) {
|
||||
const [name, setName] = useState("")
|
||||
const [lice, setLice] = useState("1")
|
||||
const [poule, setPoule] = useState(true)
|
||||
const [tournoi, setTournoi] = useState(false)
|
||||
const [size, setSize] = useState(4)
|
||||
const [loserMatch, setLoserMatch] = useState(1)
|
||||
|
||||
const {sendRequest} = useWS();
|
||||
|
||||
useEffect(() => {
|
||||
setName(state.name || "");
|
||||
setLice(state.liceName || "1");
|
||||
setPoule(((state.type || 1) & 1) !== 0);
|
||||
setTournoi((state.type & 2) !== 0);
|
||||
|
||||
if (state?.trees && state.trees.length >= 1) {
|
||||
const tree = state.trees[0];
|
||||
setSize(tree.getMaxChildrenAtDepth(tree.death() - 1) * 2);
|
||||
|
||||
if (state.trees.length === 1) {
|
||||
setLoserMatch(0);
|
||||
} else if (state.trees.length === 2) {
|
||||
setLoserMatch(1);
|
||||
} else {
|
||||
setLoserMatch(-1);
|
||||
}
|
||||
} else {
|
||||
setSize(4);
|
||||
setLoserMatch(1);
|
||||
}
|
||||
}, [state])
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const regex = /^([^;]+;)*[^;]+$/;
|
||||
if (regex.test(lice.trim()) === false) {
|
||||
toast.error("Le format du nom des lices est invalide. Veuillez séparer les noms par des ';'.");
|
||||
return;
|
||||
}
|
||||
|
||||
const nType = (poule ? 1 : 0) + (tournoi ? 2 : 0);
|
||||
if (nType === 0) {
|
||||
toast.error("Au moins un type (poule ou tournoi) doit être sélectionné.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (state?.id) {
|
||||
const applyChanges = () => {
|
||||
const newData = {
|
||||
id: state.id,
|
||||
name: name.trim(),
|
||||
liceName: lice.trim(),
|
||||
type: nType
|
||||
}
|
||||
|
||||
let nbMatch = -1;
|
||||
let oldSubTree = -1;
|
||||
const oldTrees = state?.trees || [];
|
||||
if (oldTrees.length >= 1) {
|
||||
const tree = state.trees[0];
|
||||
nbMatch = tree.getMaxChildrenAtDepth(tree.death() - 1);
|
||||
if (state.trees.length === 1)
|
||||
oldSubTree = 0
|
||||
else if (state.trees.length === 2)
|
||||
oldSubTree = 1
|
||||
}
|
||||
|
||||
console.log(tournoi, size, nbMatch, loserMatch, oldSubTree);
|
||||
if (tournoi && (size !== nbMatch * 2 || loserMatch !== oldSubTree)) {
|
||||
setConfirm({
|
||||
title: "Changement de l'arbre du tournoi",
|
||||
message: `Voulez-vous vraiment changer la taille de l'arbre du tournoi ou les matchs pour les perdants ? Cela va modifier les matchs existants (incluant des possibles suppressions)!`,
|
||||
confirm: () => {
|
||||
const trees2 = build_tree(size, loserMatch)
|
||||
const newTrees = []
|
||||
|
||||
let i = 0;
|
||||
for (; i < oldTrees.length && i < trees2.length; i++) {
|
||||
newTrees.push(oldTrees[i]);
|
||||
resize_tree(newTrees.at(i), trees2.at(i));
|
||||
}
|
||||
|
||||
for (; i < trees2.length; i++) {
|
||||
newTrees.push(trees2.at(i));
|
||||
}
|
||||
|
||||
toast.promise(sendRequest('updateTrees', {categoryId: state.id, trees: newTrees}),
|
||||
{
|
||||
pending: 'Mise à jour des arbres du tournoi...',
|
||||
success: 'Arbres mis à jour !',
|
||||
error: 'Erreur lors de la mise à jour des arbres'
|
||||
}
|
||||
).then(__ => {
|
||||
toast.promise(sendRequest('updateCategory', newData),
|
||||
{
|
||||
pending: 'Mise à jour de la catégorie...',
|
||||
success: 'Catégorie mise à jour !',
|
||||
error: 'Erreur lors de la mise à jour de la catégorie'
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
})
|
||||
confirmRef.current.click();
|
||||
} else {
|
||||
toast.promise(sendRequest('updateCategory', newData),
|
||||
{
|
||||
pending: 'Mise à jour de la catégorie...',
|
||||
success: 'Catégorie mise à jour !',
|
||||
error: 'Erreur lors de la mise à jour de la catégorie'
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (nType !== state.type) {
|
||||
let typeStr = "";
|
||||
if ((state.type & 1) !== 0 && (nType & 1) === 0)
|
||||
typeStr += "poule ";
|
||||
if ((state.type & 2) !== 0 && (nType & 2) === 0)
|
||||
typeStr += "tournoi ";
|
||||
|
||||
setConfirm({
|
||||
title: "Changement de type de catégorie",
|
||||
message: `Voulez-vous vraiment enlever la partie ${typeStr} de la catégorie. Cela va supprimer les matchs contenus dans cette partie !`,
|
||||
confirm: () => {
|
||||
applyChanges();
|
||||
}
|
||||
})
|
||||
confirmRef.current.click();
|
||||
} else {
|
||||
applyChanges();
|
||||
}
|
||||
} else {
|
||||
toast.promise(sendRequest('createCategory', {name: name.trim(), liceName: lice.trim(), type: nType}),
|
||||
{
|
||||
pending: 'Création de la catégorie...',
|
||||
success: 'Catégorie créée !',
|
||||
error: 'Erreur lors de la création de la catégorie'
|
||||
}
|
||||
).then(id => {
|
||||
setCatId(id);
|
||||
|
||||
if (tournoi) {
|
||||
const trees = build_tree(size, loserMatch)
|
||||
console.log("Creating trees for new category:", trees);
|
||||
|
||||
toast.promise(sendRequest('updateTrees', {categoryId: id, trees: trees}),
|
||||
{
|
||||
pending: 'Création des arbres du tournoi...',
|
||||
success: 'Arbres créés !',
|
||||
error: 'Erreur lors de la création des arbres'
|
||||
}
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const data = {
|
||||
name: name.trim(),
|
||||
liceName: lice.trim(),
|
||||
type: poule + (tournoi << 1),
|
||||
size: size,
|
||||
loserMatch: loserMatch
|
||||
}
|
||||
console.log("Submitting category data:", data);
|
||||
}
|
||||
|
||||
return <form onSubmit={handleSubmit}>
|
||||
<div className="modal-header">
|
||||
<h1 className="modal-title fs-5" id="CategorieModalLabel">Ajouter une catégorie</h1>
|
||||
<button type="button" className="btn-close" data-bs-dismiss="modal"
|
||||
aria-label="Close"></button>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
<div className="mb-3">
|
||||
<label htmlFor="nameInput1" className="form-label">Nom</label>
|
||||
<input type="text" className="form-control" id="nameInput1" placeholder="Epée bouclier" name="name" value={name}
|
||||
onChange={e => setName(e.target.value)}/>
|
||||
</div>
|
||||
|
||||
<div className="mb-3">
|
||||
<label htmlFor="liceInput1" className="form-label">Nom des lices <small>(séparée par des ';')</small></label>
|
||||
<input type="text" className="form-control" id="liceInput1" placeholder="1;2" name="lice" value={lice}
|
||||
onChange={e => setLice(e.target.value)}/>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="mb-3">
|
||||
<label className="form-label">Type</label>
|
||||
<div className="form-check form-switch">
|
||||
<input className="form-check-input" type="checkbox" role="switch" id="switchCheckDefault" name="poule" checked={poule}
|
||||
onChange={e => setPoule(e.target.checked)}/>
|
||||
<label className="form-check-label" htmlFor="switchCheckDefault">Poule</label>
|
||||
</div>
|
||||
|
||||
<div className="form-check form-switch">
|
||||
<input className="form-check-input" type="checkbox" role="switch" id="switchCheckDefault2" name="trournoi" checked={tournoi}
|
||||
onChange={e => setTournoi(e.target.checked)}/>
|
||||
<label className="form-check-label" htmlFor="switchCheckDefault2">Tournoi</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mb-3">
|
||||
<label htmlFor="sizeInput1" className="form-label">Nombre de combattants</label>
|
||||
<input type="number" className="form-control" id="sizeInput1" placeholder="4" name="size" disabled={!tournoi} value={size}
|
||||
onChange={e => setSize(Number.parseInt(e.target.value))}/>
|
||||
</div>
|
||||
|
||||
<div className="mb-3">
|
||||
<span>Match pour les perdants du tournoi:</span>
|
||||
<div className="form-check">
|
||||
<input className="form-check-input" type="radio" name="radioDefault" id="radioDefault1" disabled={!tournoi}
|
||||
checked={loserMatch === -1} onChange={e => {
|
||||
if (e.target.checked) setLoserMatch(-1)
|
||||
}}/>
|
||||
<label className="form-check-label" htmlFor="radioDefault1">
|
||||
Tous les matchs
|
||||
</label>
|
||||
</div>
|
||||
<div className="form-check">
|
||||
<input className="form-check-input" type="radio" name="radioDefault" id="radioDefault2" disabled={!tournoi}
|
||||
checked={loserMatch === 1} onChange={e => {
|
||||
if (e.target.checked) setLoserMatch(1)
|
||||
}}/>
|
||||
<label className="form-check-label" htmlFor="radioDefault2">
|
||||
Demi-finales et finales
|
||||
</label>
|
||||
</div>
|
||||
<div className="form-check">
|
||||
<input className="form-check-input" type="radio" name="radioDefault" id="radioDefault3" disabled={!tournoi}
|
||||
checked={loserMatch === 0} onChange={e => {
|
||||
if (e.target.checked) setLoserMatch(0)
|
||||
}}/>
|
||||
<label className="form-check-label" htmlFor="radioDefault3">
|
||||
Finales uniquement
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div className="modal-footer">
|
||||
<button type="button" className="btn btn-secondary" data-bs-dismiss="modal">Fermer</button>
|
||||
<button type="submit" className="btn btn-primary" data-bs-dismiss="modal">Enregistrer</button>
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
|
||||
function CategoryContent({cat, catId, setCat}) {
|
||||
const setLoading = useLoadingSwitcher()
|
||||
const {sendRequest, dispatch} = useWS();
|
||||
const [matches, reducer] = useReducer(MarchReducer, []);
|
||||
|
||||
useEffect(() => {
|
||||
const treeListener = ({data}) => {
|
||||
if (!cat || data.length < 1 || data[0].categorie !== cat.id)
|
||||
return
|
||||
setCat({
|
||||
...cat,
|
||||
trees: data.map(d => from_sendTree(d, true))
|
||||
})
|
||||
let matches2 = [];
|
||||
data.flatMap(d => from_sendTree(d, false).flat()).forEach((data_) => matches2.push({...data_}));
|
||||
reducer({type: 'REPLACE_TREE', payload: matches2});
|
||||
}
|
||||
dispatch({type: 'addListener', payload: {callback: treeListener, code: 'sendTreeCategory'}})
|
||||
return () => dispatch({type: 'removeListener', payload: treeListener})
|
||||
}, [cat]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!catId)
|
||||
return;
|
||||
setLoading(1);
|
||||
sendRequest('getFullCategory', catId)
|
||||
.then((data) => {
|
||||
setCat({
|
||||
id: data.id,
|
||||
name: data.name,
|
||||
liceName: data.liceName,
|
||||
type: data.type,
|
||||
trees: data.trees.map(d => from_sendTree(d, true))
|
||||
})
|
||||
|
||||
let matches2 = [];
|
||||
data.trees.flatMap(d => from_sendTree(d, false).flat()).forEach((data_) => matches2.push({...data_}));
|
||||
data.matches.forEach((data_) => matches2.push({...data_}));
|
||||
|
||||
reducer({type: 'REPLACE_ALL', payload: matches2});
|
||||
}).finally(() => setLoading(0))
|
||||
}, [catId]);
|
||||
|
||||
console.log("Matches in category content:", matches);
|
||||
|
||||
return <>
|
||||
<div className="col">
|
||||
|
||||
|
||||
<div className="vr"></div>
|
||||
</div>
|
||||
<div className="col">
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import {LoadingProvider} from "../../../hooks/useLoading.jsx";
|
||||
import {useEffect, useState} from "react";
|
||||
import {useWS, WSProvider} from "../../../hooks/useWS.jsx";
|
||||
import {ColoredCircle} from "../../../components/ColoredCircle.jsx";
|
||||
import {CMAdmin} from "./CMAdmin.jsx";
|
||||
|
||||
const vite_url = import.meta.env.VITE_URL;
|
||||
|
||||
@@ -36,10 +37,13 @@ function HomeComp() {
|
||||
|
||||
return <WSProvider url={`${vite_url.replace('http', 'ws')}/api/ws/competition/${compUuid}`} onmessage={messageHandler}>
|
||||
<WSStatus setPerm={setPerm}/>
|
||||
<Routes>
|
||||
<Route path="/" element={<Home2 perm={perm}/>}/>
|
||||
<Route path="/test" element={<Test2/>}/>
|
||||
</Routes>
|
||||
<LoadingProvider>
|
||||
<Routes>
|
||||
<Route path="/" element={<Home2 perm={perm}/>}/>
|
||||
<Route path="/admin" element={<CMAdmin/>}/>
|
||||
<Route path="/test" element={<Test2/>}/>
|
||||
</Routes>
|
||||
</LoadingProvider>
|
||||
</WSProvider>
|
||||
}
|
||||
|
||||
@@ -79,8 +83,8 @@ function Home2({perm}) {
|
||||
<h4 className="col-auto" style={{margin: "auto 0"}}>Sélectionne les modes d'affichage</h4>
|
||||
<div className="col">
|
||||
{perm === "admin" && <>
|
||||
<button className="btn btn-primary" onClick={() => nav("admin")}>Administration</button>
|
||||
<button className="btn btn-primary ms-3" onClick={() => nav("table")}>Table de marque</button>
|
||||
<button className="btn btn-primary" onClick={() => nav("table")}>Table de marque</button>
|
||||
<button className="btn btn-primary ms-3" onClick={() => nav("admin")}>Administration</button>
|
||||
</>}
|
||||
{perm === "table" && <>
|
||||
<button className="btn btn-primary" onClick={() => nav("table")}>Table de marque</button>
|
||||
|
||||
44
src/main/webapp/src/utils/MatchReducer.jsx
Normal file
44
src/main/webapp/src/utils/MatchReducer.jsx
Normal file
@@ -0,0 +1,44 @@
|
||||
export function MarchReducer(datas, action) {
|
||||
switch (action.type) {
|
||||
case 'ADD':
|
||||
return [
|
||||
...datas,
|
||||
action.payload
|
||||
]
|
||||
case 'ADD_ALL':
|
||||
return [
|
||||
...datas,
|
||||
...action.payload
|
||||
]
|
||||
case 'REPLACE_ALL':
|
||||
return [
|
||||
...action.payload
|
||||
]
|
||||
case 'REPLACE_TREE':
|
||||
return [
|
||||
...datas.filter(data => data.categorie_ord !== -42),
|
||||
...action.payload
|
||||
]
|
||||
case 'CLEAR':
|
||||
return []
|
||||
case 'REMOVE':
|
||||
return datas.filter(data => data.id !== action.payload)
|
||||
case 'REMOVE_TREE':
|
||||
return datas.filter(data => data.categorie_ord !== -42)
|
||||
case 'UPDATE_OR_ADD':
|
||||
const index = datas.findIndex(data => data.id === action.payload.id)
|
||||
if (index === -1) {
|
||||
return [
|
||||
...datas,
|
||||
action.payload
|
||||
]
|
||||
} else {
|
||||
datas[index] = action.payload
|
||||
return [...datas]
|
||||
}
|
||||
case 'SORT':
|
||||
return datas.sort(action.payload)
|
||||
default:
|
||||
throw new Error()
|
||||
}
|
||||
}
|
||||
129
src/main/webapp/src/utils/TreeUtils.js
Normal file
129
src/main/webapp/src/utils/TreeUtils.js
Normal file
@@ -0,0 +1,129 @@
|
||||
export function TreeNode(data, left = null, right = null) {
|
||||
this.data = data;
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
TreeNode.prototype = {
|
||||
getMaxChildrenAtDepth: function (death, current = 0) {
|
||||
if (current === death)
|
||||
return 1;
|
||||
|
||||
let tmp = 0;
|
||||
if (this.right != null)
|
||||
tmp += this.right.getMaxChildrenAtDepth(death, current + 1);
|
||||
|
||||
if (this.left != null)
|
||||
tmp += this.left.getMaxChildrenAtDepth(death, current + 1);
|
||||
|
||||
return tmp;
|
||||
},
|
||||
death: function () {
|
||||
let dg = 0;
|
||||
let dd = 0;
|
||||
|
||||
if (this.right != null)
|
||||
dg = this.right.death();
|
||||
|
||||
if (this.left != null)
|
||||
dg = this.left.death();
|
||||
|
||||
return 1 + Math.max(dg, dd);
|
||||
},
|
||||
isEnd: function (data) {
|
||||
if (this.data === data) {
|
||||
return this.right == null && this.left == null;
|
||||
} else {
|
||||
return (this.right != null && this.right.isEnd(data)) || (this.left != null && this.left.isEnd(data));
|
||||
}
|
||||
},
|
||||
flat: function () {
|
||||
let out = []
|
||||
this.__flat(out)
|
||||
return out;
|
||||
},
|
||||
__flat: function (out) {
|
||||
out.push(this.data)
|
||||
if (this.left != null)
|
||||
this.left.__flat(out)
|
||||
if (this.right != null)
|
||||
this.right.__flat(out)
|
||||
}
|
||||
}
|
||||
|
||||
export function build_tree(nb_comb, sub_tree_level = 0) {
|
||||
const nb_match = Math.ceil(nb_comb / 2)
|
||||
const level = Math.ceil(Math.log(nb_match) / Math.log(2))
|
||||
|
||||
const treeNodes = []
|
||||
|
||||
for (let i = level; i >= 0; i--) {
|
||||
const treeNode = new TreeNode(null)
|
||||
build_tree_(treeNode, 0, i, 0, nb_match)
|
||||
treeNodes.push(treeNode)
|
||||
|
||||
if (sub_tree_level !== -1) {
|
||||
if (sub_tree_level === 0)
|
||||
break
|
||||
if (i > sub_tree_level)
|
||||
i = sub_tree_level
|
||||
}
|
||||
}
|
||||
|
||||
return treeNodes
|
||||
}
|
||||
|
||||
export function build_tree_(treeNode, death, death_target, leave, leave_target) {
|
||||
if (death === death_target) {
|
||||
return (leave < leave_target) ? 1 : 0;
|
||||
}
|
||||
|
||||
if (leave + 1 === leave_target) {
|
||||
return 1;
|
||||
} else {
|
||||
let to_add = 0;
|
||||
if (leave < leave_target) {
|
||||
treeNode.left = new TreeNode(null)
|
||||
to_add += build_tree_(treeNode.left, death + 1, death_target, leave, leave_target)
|
||||
}
|
||||
|
||||
if (leave + to_add < leave_target) {
|
||||
treeNode.right = new TreeNode(null)
|
||||
to_add += build_tree_(treeNode.right, death + 1, death_target, leave + to_add, leave_target)
|
||||
}
|
||||
|
||||
return to_add;
|
||||
}
|
||||
}
|
||||
|
||||
export function resize_tree(src, to) {
|
||||
if (src === null || to === null)
|
||||
return null;
|
||||
|
||||
if (src.left === null && to.left != null) {
|
||||
src.left = to.left;
|
||||
} else if (src.left != null && to.left === null) {
|
||||
src.left = null;
|
||||
} else {
|
||||
resize_tree(src.left, to.left);
|
||||
}
|
||||
|
||||
if (src.right === null && to.right != null) {
|
||||
src.right = to.right;
|
||||
} else if (src.right != null && to.right === null) {
|
||||
src.right = null;
|
||||
} else {
|
||||
resize_tree(src.right, to.right);
|
||||
}
|
||||
}
|
||||
|
||||
export function from_sendTree(tree, matchId = true) {
|
||||
if (tree == null)
|
||||
return null;
|
||||
|
||||
const node = new TreeNode(matchId ? tree.match?.id : tree.match);
|
||||
node.left = from_sendTree(tree.left, matchId);
|
||||
node.right = from_sendTree(tree.right, matchId);
|
||||
|
||||
return node;
|
||||
}
|
||||
Reference in New Issue
Block a user