Compare commits

...

9 Commits

10 changed files with 189 additions and 60 deletions

View File

@ -270,14 +270,16 @@ public class MembreService {
if (model.getEmail() != null && !model.getEmail().isBlank()) { if (model.getEmail() != null && !model.getEmail().isBlank()) {
if (model.getLicence() != null && !model.getLicence().equals(dataIn.getLicence())) { if (model.getLicence() != null && !model.getLicence().equals(dataIn.getLicence())) {
LOGGER.info("Similar membres found: " + model); LOGGER.info("Similar membres found: " + model);
throw new DBadRequestException("Email '" + model.getEmail() + "' déja utilisé"); throw new DBadRequestException(
"Email '" + model.getEmail() + "' déja utilisé par " + model.getFname() + " " + model.getFname());
} }
if (StringSimilarity.similarity(model.getLname().toUpperCase(), if (StringSimilarity.similarity(model.getLname().toUpperCase(),
dataIn.getNom().toUpperCase()) > 3 || StringSimilarity.similarity( dataIn.getNom().toUpperCase()) > 3 || StringSimilarity.similarity(
model.getFname().toUpperCase(), dataIn.getPrenom().toUpperCase()) > 3) { model.getFname().toUpperCase(), dataIn.getPrenom().toUpperCase()) > 3) {
LOGGER.info("Similar membres found: " + model); LOGGER.info("Similar membres found: " + model);
throw new DBadRequestException("Email '" + model.getEmail() + "' déja utilisé"); throw new DBadRequestException(
"Email '" + model.getEmail() + "' déja utilisé par " + model.getFname() + " " + model.getFname());
} }
} }
@ -288,7 +290,8 @@ public class MembreService {
model.getFname().toUpperCase(), dataIn.getPrenom().toUpperCase()) > 3)) { model.getFname().toUpperCase(), dataIn.getPrenom().toUpperCase()) > 3)) {
LOGGER.info("Similar membres found: " + model); LOGGER.info("Similar membres found: " + model);
throw new DBadRequestException( throw new DBadRequestException(
"Pour enregistrer un nouveau membre, veuillez laisser le champ licence vide."); "Pour enregistrer un nouveau membre, veuillez laisser le champ licence vide. (tentative de changement non-autotiser de nom sur la licence "
+ model.getLicence() + " pour " + model.getFname() + " " + model.getFname() + ")");
} }
ls.logChange("Nom", model.getLname(), dataIn.getNom().toUpperCase(), model); ls.logChange("Nom", model.getLname(), dataIn.getNom().toUpperCase(), model);
@ -296,8 +299,7 @@ public class MembreService {
dataIn.getPrenom().toUpperCase().charAt(0) + dataIn.getPrenom().substring(1), model); dataIn.getPrenom().toUpperCase().charAt(0) + dataIn.getPrenom().substring(1), model);
model.setLname(dataIn.getNom().toUpperCase()); model.setLname(dataIn.getNom().toUpperCase());
model.setFname(dataIn.getPrenom().toUpperCase().charAt(0) + dataIn.getPrenom().substring(1)); model.setFname(Utils.formatPrenom(dataIn.getPrenom()));
if (dataIn.getEmail() != null && !dataIn.getEmail().isBlank()) { if (dataIn.getEmail() != null && !dataIn.getEmail().isBlank()) {
ls.logChange("Email", model.getEmail(), dataIn.getEmail(), model); ls.logChange("Email", model.getEmail(), dataIn.getEmail(), model);
@ -417,7 +419,7 @@ public class MembreService {
private Uni<String> update(Uni<MembreModel> uni, FullMemberForm membre, boolean admin) { private Uni<String> update(Uni<MembreModel> uni, FullMemberForm membre, boolean admin) {
return uni.chain(target -> { return uni.chain(target -> {
ls.logChange("Prénom", target.getFname(), membre.getFname(), target); ls.logChange("Prénom", target.getFname(), membre.getFname(), target);
target.setFname(membre.getFname()); target.setFname(Utils.formatPrenom(membre.getFname()));
ls.logChange("Nom", target.getLname(), membre.getLname(), target); ls.logChange("Nom", target.getLname(), membre.getLname(), target);
target.setLname(membre.getLname().toUpperCase()); target.setLname(membre.getLname().toUpperCase());
ls.logChange("Pays", target.getCountry(), membre.getCountry(), target); ls.logChange("Pays", target.getCountry(), membre.getCountry(), target);
@ -560,8 +562,8 @@ public class MembreService {
private static MembreModel getMembreModel(FullMemberForm input, ClubModel clubModel) { private static MembreModel getMembreModel(FullMemberForm input, ClubModel clubModel) {
MembreModel model = new MembreModel(); MembreModel model = new MembreModel();
model.setFname(input.getFname()); model.setFname(Utils.formatPrenom(input.getFname()));
model.setLname(input.getLname()); model.setLname(input.getLname().toUpperCase());
model.setEmail(input.getEmail()); model.setEmail(input.getEmail());
model.setLicence(null); model.setLicence(null);
model.setGenre(input.getGenre()); model.setGenre(input.getGenre());

View File

@ -335,4 +335,34 @@ public class Utils {
return (int) ((calendar.getTimeInMillis() - now.getTimeInMillis()) / (1000 * 60 * 60 * 24)); return (int) ((calendar.getTimeInMillis() - now.getTimeInMillis()) / (1000 * 60 * 60 * 24));
} }
public static String formatPrenom(String input) {
if (input == null || input.isEmpty()) {
return input;
}
StringBuilder result = new StringBuilder();
String[] mots = input.split(" ");
for (String mot : mots) {
if (!mot.isEmpty()) {
String[] parties = mot.split("[-']");
StringBuilder motFormate = new StringBuilder();
for (int i = 0; i < parties.length; i++) {
if (!parties[i].isEmpty()) {
String premiereLettre = parties[i].substring(0, 1).toUpperCase();
String reste = parties[i].substring(1).toLowerCase();
motFormate.append(premiereLettre).append(reste);
}
if (i < parties.length - 1) {
motFormate.append(mot.charAt(mot.indexOf(parties[i]) + parties[i].length()));
}
}
result.append(motFormate).append(" ");
}
}
return result.toString().trim();
}
} }

View File

@ -21,6 +21,7 @@ import org.jboss.logging.Logger;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.time.Duration;
import java.util.*; import java.util.*;
import static fr.titionfire.ffsaf.net2.Client_Thread.MAPPER; import static fr.titionfire.ffsaf.net2.Client_Thread.MAPPER;
@ -166,6 +167,7 @@ public class CompetitionWS {
return Uni.createFrom().item(makeError(message, "Permission denied")).toMulti(); return Uni.createFrom().item(makeError(message, "Permission denied")).toMulti();
return ((Uni<?>) method.invoke(entry.getValue(), connection, return ((Uni<?>) method.invoke(entry.getValue(), connection,
MAPPER.treeToValue(message.data(), method.getParameterTypes()[1]))) MAPPER.treeToValue(message.data(), method.getParameterTypes()[1])))
.ifNoItem().after(Duration.ofSeconds(5)).fail()
.map(o -> makeReply(message, o)) .map(o -> makeReply(message, o))
.onFailure() .onFailure()
.recoverWithItem(t -> { .recoverWithItem(t -> {

View File

@ -118,7 +118,8 @@ public class RCategorie {
uni = uni.chain(__ -> treeRepository.delete("category = ?1", cat.getId())) uni = uni.chain(__ -> treeRepository.delete("category = ?1", cat.getId()))
.chain(__ -> matchRepository.delete("category = ?1 AND category_ord = -42", cat)); .chain(__ -> matchRepository.delete("category = ?1 AND category_ord = -42", cat));
} }
return uni; Uni<Long> finalUni = uni;
return Panache.withTransaction(() -> finalUni);
}) })
.call(cat -> SSCategorie.sendCategory(connection, cat)) .call(cat -> SSCategorie.sendCategory(connection, cat))
.replaceWithVoid(); .replaceWithVoid();
@ -205,6 +206,16 @@ public class RCategorie {
.replaceWithVoid(); .replaceWithVoid();
} }
@WSReceiver(code = "deleteCategory", permission = PermLevel.ADMIN)
public Uni<Void> deleteCategory(WebSocketConnection connection, Long id) {
return getById(id, connection)
.call(cat -> Panache.withTransaction(() -> treeRepository.delete("category = ?1", cat.getId())
.call(__ -> matchRepository.delete("category = ?1", cat))))
.chain(cat -> Panache.withTransaction(() -> categoryRepository.delete(cat)))
.call(__ -> SSCategorie.sendDelCategory(connection, id))
.replaceWithVoid();
}
@RegisterForReflection @RegisterForReflection
public record JustCategorie(long id, String name, int type, String liceName) { public record JustCategorie(long id, String name, int type, String liceName) {
public static JustCategorie from(CategoryModel m) { public static JustCategorie from(CategoryModel m) {

View File

@ -30,4 +30,8 @@ public class SSCategorie {
public static Uni<Void> sendTreeCategory(WebSocketConnection connection, List<TreeEntity> treeEntities) { public static Uni<Void> sendTreeCategory(WebSocketConnection connection, List<TreeEntity> treeEntities) {
return CompetitionWS.sendNotifyToOtherEditor(connection, "sendTreeCategory", treeEntities); return CompetitionWS.sendNotifyToOtherEditor(connection, "sendTreeCategory", treeEntities);
} }
public static Uni<?> sendDelCategory(WebSocketConnection connection, Long id) {
return CompetitionWS.sendNotifyToOtherEditor(connection, "sendDelCategory", id);
}
} }

View File

@ -15,16 +15,16 @@ export function CMAdmin() {
const categoryListener = ({data}) => { const categoryListener = ({data}) => {
if (!cat || data.id !== cat.id) if (!cat || data.id !== cat.id)
return return
setCat({ setCat(cat_ => ({
...cat, ...cat_,
name: data.name, name: data.name,
liceName: data.liceName, liceName: data.liceName,
type: data.type type: data.type
}) }))
} }
dispatch({type: 'addListener', payload: {callback: categoryListener, code: 'sendCategory'}}) dispatch({type: 'addListener', payload: {callback: categoryListener, code: 'sendCategory'}})
return () => dispatch({type: 'removeListener', payload: categoryListener}) return () => dispatch({type: 'removeListener', payload: categoryListener})
}, []); }, [cat]);
return <> return <>
<div className="card"> <div className="card">
@ -60,18 +60,34 @@ function CategoryHeader({cat, setCatId}) {
data data
]) ])
} }
const sendAddCategory = ({data}) => {
setCats([...cats, data])
}
const sendDelCategory = ({data}) => {
setCatId(catId => {
if (catId === data) return null;
return catId;
})
setCats([...cats.filter(c => c.id !== data)])
}
dispatch({type: 'addListener', payload: {callback: categoryListener, code: 'sendCategory'}}) dispatch({type: 'addListener', payload: {callback: categoryListener, code: 'sendCategory'}})
return () => dispatch({type: 'removeListener', payload: categoryListener}) dispatch({type: 'addListener', payload: {callback: sendAddCategory, code: 'sendAddCategory'}})
dispatch({type: 'addListener', payload: {callback: sendDelCategory, code: 'sendDelCategory'}})
return () => {
dispatch({type: 'removeListener', payload: categoryListener})
dispatch({type: 'removeListener', payload: sendAddCategory})
dispatch({type: 'removeListener', payload: sendDelCategory})
}
}, [cats]); }, [cats]);
useEffect(() => { useEffect(() => {
if (cats && cats.length > 0 && !cat) { if (cats && cats.length > 0 && !cat || (cats && !cats.find(c => c.id === cat.id))) {
setCatId(cats.sort((a, b) => a.name.localeCompare(b.name))[0].id); setCatId(cats.sort((a, b) => a.name.localeCompare(b.name))[0].id);
} else if (cats && cats.length === 0) { } else if (cats && cats.length === 0) {
setModal({}); setModal({});
bthRef.current.click(); bthRef.current.click();
} }
}, [cats]); }, [cats, cat]);
const handleCatChange = (e) => { const handleCatChange = (e) => {
const selectedCatId = e.target.value; const selectedCatId = e.target.value;
@ -88,7 +104,7 @@ function CategoryHeader({cat, setCatId}) {
<div className="col-auto"> <div className="col-auto">
<div className="input-group"> <div className="input-group">
<h5 style={{margin: "auto 0.5em auto 0"}}>Edition de la catégorie</h5> <h5 style={{margin: "auto 0.5em auto 0"}}>Edition de la catégorie</h5>
<select className="form-select" onChange={handleCatChange}> <select className="form-select" onChange={handleCatChange} value={cat?.id || ""}>
{cats && cats.sort((a, b) => a.name.localeCompare(b.name)).map(c => ( {cats && cats.sort((a, b) => a.name.localeCompare(b.name)).map(c => (
<option key={c.id} value={c.id}>{c.name}</option>))} <option key={c.id} value={c.id}>{c.name}</option>))}
{cats && <option value={-1}>Nouvelle...</option>} {cats && <option value={-1}>Nouvelle...</option>}
@ -98,7 +114,7 @@ function CategoryHeader({cat, setCatId}) {
<div className="col" style={{margin: "auto 0", textAlign: "center"}}> <div className="col" style={{margin: "auto 0", textAlign: "center"}}>
{cat && {cat &&
<div>Type: {(cat.type & 1) !== 0 ? "Poule" : ""}{cat.type === 3 ? " & " : ""}{(cat.type & 2) !== 0 ? "Tournois" : ""} | <div>Type: {(cat.type & 1) !== 0 ? "Poule" : ""}{cat.type === 3 ? " & " : ""}{(cat.type & 2) !== 0 ? "Tournois" : ""} |
Lice: {cat.liceName}</div>} Zone: {cat.liceName}</div>}
</div> </div>
<div className="col-auto"> <div className="col-auto">
<button className="btn btn-primary float-end" onClick={() => { <button className="btn btn-primary float-end" onClick={() => {
@ -128,7 +144,7 @@ function CategoryHeader({cat, setCatId}) {
function ModalContent({state, setCatId, setConfirm, confirmRef}) { function ModalContent({state, setCatId, setConfirm, confirmRef}) {
const [name, setName] = useState("") const [name, setName] = useState("")
const [lice, setLice] = useState("1") const [lice, setLice] = useState("A")
const [poule, setPoule] = useState(true) const [poule, setPoule] = useState(true)
const [tournoi, setTournoi] = useState(false) const [tournoi, setTournoi] = useState(false)
const [size, setSize] = useState(4) const [size, setSize] = useState(4)
@ -138,7 +154,7 @@ function ModalContent({state, setCatId, setConfirm, confirmRef}) {
useEffect(() => { useEffect(() => {
setName(state.name || ""); setName(state.name || "");
setLice(state.liceName || "1"); setLice(state.liceName || "A");
setPoule(((state.type || 1) & 1) !== 0); setPoule(((state.type || 1) & 1) !== 0);
setTournoi((state.type & 2) !== 0); setTournoi((state.type & 2) !== 0);
@ -164,7 +180,7 @@ function ModalContent({state, setCatId, setConfirm, confirmRef}) {
const regex = /^([^;]+;)*[^;]+$/; const regex = /^([^;]+;)*[^;]+$/;
if (regex.test(lice.trim()) === false) { if (regex.test(lice.trim()) === false) {
toast.error("Le format du nom des lices est invalide. Veuillez séparer les noms par des ';'."); toast.error("Le format du nom des zones de combat est invalide. Veuillez séparer les noms par des ';'.");
return; return;
} }
@ -270,8 +286,6 @@ function ModalContent({state, setCatId, setConfirm, confirmRef}) {
error: 'Erreur lors de la création de la catégorie' error: 'Erreur lors de la création de la catégorie'
} }
).then(id => { ).then(id => {
setCatId(id);
if (tournoi) { if (tournoi) {
const trees = build_tree(size, loserMatch) const trees = build_tree(size, loserMatch)
console.log("Creating trees for new category:", trees); console.log("Creating trees for new category:", trees);
@ -282,7 +296,9 @@ function ModalContent({state, setCatId, setConfirm, confirmRef}) {
success: 'Arbres créés !', success: 'Arbres créés !',
error: 'Erreur lors de la création des arbres' error: 'Erreur lors de la création des arbres'
} }
) ).finally(() => setCatId(id))
} else {
setCatId(id);
} }
}) })
} }
@ -299,7 +315,7 @@ function ModalContent({state, setCatId, setConfirm, confirmRef}) {
return <form onSubmit={handleSubmit}> return <form onSubmit={handleSubmit}>
<div className="modal-header"> <div className="modal-header">
<h1 className="modal-title fs-5" id="CategorieModalLabel">Ajouter une catégorie</h1> <h1 className="modal-title fs-5" id="CategorieModalLabel">{state.id === undefined ? "Ajouter" : "Modifier"} une catégorie</h1>
<button type="button" className="btn-close" data-bs-dismiss="modal" <button type="button" className="btn-close" data-bs-dismiss="modal"
aria-label="Close"></button> aria-label="Close"></button>
</div> </div>
@ -311,8 +327,8 @@ function ModalContent({state, setCatId, setConfirm, confirmRef}) {
</div> </div>
<div className="mb-3"> <div className="mb-3">
<label htmlFor="liceInput1" className="form-label">Nom des lices <small>(séparée par des ';')</small></label> <label htmlFor="liceInput1" className="form-label">Nom des zones de combat <small>(séparée par des ';')</small></label>
<input type="text" className="form-control" id="liceInput1" placeholder="1;2" name="lice" value={lice} <input type="text" className="form-control" id="liceInput1" placeholder="A;B" name="zone de combat" value={lice}
onChange={e => setLice(e.target.value)}/> onChange={e => setLice(e.target.value)}/>
</div> </div>
@ -373,6 +389,22 @@ function ModalContent({state, setCatId, setConfirm, confirmRef}) {
<div className="modal-footer"> <div className="modal-footer">
<button type="button" className="btn btn-secondary" data-bs-dismiss="modal">Fermer</button> <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> <button type="submit" className="btn btn-primary" data-bs-dismiss="modal">Enregistrer</button>
{state.id !== undefined && <button type="button" className="btn btn-danger" data-bs-dismiss="modal" onClick={() => {
setConfirm({
title: "Suppression de la catégorie",
message: `Voulez-vous vraiment supprimer la catégorie ${state.name}. Cela va supprimer tous les matchs associés !`,
confirm: () => {
toast.promise(sendRequest('deleteCategory', state.id),
{
pending: 'Suppression de la catégorie...',
success: 'Catégorie supprimée !',
error: 'Erreur lors de la suppression de la catégorie'
}
).then(() => setCatId(null));
}
})
confirmRef.current.click();
}}>Supprimer</button>}
</div> </div>
</form> </form>
} }

View File

@ -27,8 +27,22 @@ export function CategorieSelect({catId, setCatId, menuActions}) {
const categoryListener = ({data}) => { const categoryListener = ({data}) => {
setCats([...cats.filter(c => c.id !== data.id), data]) setCats([...cats.filter(c => c.id !== data.id), data])
} }
const sendAddCategory = ({data}) => {
setCats([...cats, data])
}
const sendDelCategory = ({data}) => {
if (catId === data)
setCatId(-1);
setCats([...cats.filter(c => c.id !== data)])
}
dispatch({type: 'addListener', payload: {callback: categoryListener, code: 'sendCategory'}}) dispatch({type: 'addListener', payload: {callback: categoryListener, code: 'sendCategory'}})
return () => dispatch({type: 'removeListener', payload: categoryListener}) dispatch({type: 'addListener', payload: {callback: sendAddCategory, code: 'sendAddCategory'}})
dispatch({type: 'addListener', payload: {callback: sendDelCategory, code: 'sendDelCategory'}})
return () => {
dispatch({type: 'removeListener', payload: categoryListener})
dispatch({type: 'removeListener', payload: sendAddCategory})
dispatch({type: 'removeListener', payload: sendDelCategory})
}
}, [cats]); }, [cats]);
const cat = cats?.find(c => c.id === catId); const cat = cats?.find(c => c.id === catId);
@ -131,10 +145,15 @@ function ListMatch({cat, matches, trees, menuActions}) {
const [type, setType] = useState(1); const [type, setType] = useState(1);
useEffect(() => { useEffect(() => {
if (!cat)
return;
if ((cat.type & type) === 0) if ((cat.type & type) === 0)
setType(cat.type); setType(cat.type);
}, [cat]); }, [cat]);
if (!cat)
return <></>;
return <div style={{marginTop: "1em"}}> return <div style={{marginTop: "1em"}}>
{cat && cat.type === 3 && <> {cat && cat.type === 3 && <>
<ul className="nav nav-tabs"> <ul className="nav nav-tabs">
@ -173,6 +192,10 @@ function MatchList({matches, cat, menuActions}) {
.map(m => ({...m, win: win(m.scores)})) .map(m => ({...m, win: win(m.scores)}))
const firstIndex = marches2.findLastIndex(m => m.poule === '-') + 1; const firstIndex = marches2.findLastIndex(m => m.poule === '-') + 1;
const isActiveMatch = (index) => {
return liceName.length === 1 || (liceName[(index - firstIndex) % liceName.length] === lice)
}
const match = matches.find(m => m.id === activeMatch) const match = matches.find(m => m.id === activeMatch)
useEffect(() => { useEffect(() => {
if (!match) { if (!match) {
@ -183,7 +206,7 @@ function MatchList({matches, cat, menuActions}) {
payload: { payload: {
c1: match.c1, c1: match.c1,
c2: match.c2, c2: match.c2,
next: marches2.filter((m, index) => !m.end && liceName[(index - firstIndex) % liceName.length] === lice && m.id !== activeMatch).map(m => ({ next: marches2.filter((m, index) => !m.end && isActiveMatch(index) && m.id !== activeMatch).map(m => ({
c1: m.c1, c1: m.c1,
c2: m.c2 c2: m.c2
})) }))
@ -198,7 +221,7 @@ function MatchList({matches, cat, menuActions}) {
useEffect(() => { useEffect(() => {
if (match && match.poule !== lice) if (match && match.poule !== lice)
setActiveMatch(marches2.find((m, index) => !m.end && liceName[(index - firstIndex) % liceName.length] === lice)?.id) setActiveMatch(marches2.find((m, index) => !m.end && isActiveMatch(index))?.id)
}, [lice]); }, [lice]);
useEffect(() => { useEffect(() => {
@ -207,12 +230,12 @@ function MatchList({matches, cat, menuActions}) {
if (marches2.some(m => m.id === activeMatch)) if (marches2.some(m => m.id === activeMatch))
return; return;
setActiveMatch(marches2.find((m, index) => !m.end && liceName[(index - firstIndex) % liceName.length] === lice)?.id); setActiveMatch(marches2.find((m, index) => !m.end && isActiveMatch(index))?.id);
}, [matches]) }, [matches])
return <> return <>
{liceName.length > 1 && {liceName.length > 1 &&
<div className="input-group" style={{maxWidth: "10em", marginTop: "0.5em"}}> <div className="input-group" style={{maxWidth: "15em", marginTop: "0.5em"}}>
<label className="input-group-text" htmlFor="selectLice">Lice</label> <label className="input-group-text" htmlFor="selectLice">Zone de combat</label>
<select className="form-select" id="selectLice" value={lice} onChange={e => { <select className="form-select" id="selectLice" value={lice} onChange={e => {
setLice(e.target.value); setLice(e.target.value);
localStorage.setItem("cm_lice", e.target.value); localStorage.setItem("cm_lice", e.target.value);
@ -224,11 +247,11 @@ function MatchList({matches, cat, menuActions}) {
</div> </div>
} }
<div className="table-responsive-xxl"> <div className="table-responsive-xxl overflow-y-auto" style={{maxHeight: "50vh"}}>
<table className="table table-striped table-hover"> <table className="table table-striped table-hover">
<thead> <thead>
<tr> <tr>
<th style={{textAlign: "center", paddingLeft: "0.2em", paddingRight: "0.2em"}} scope="col">L</th> <th style={{textAlign: "center", paddingLeft: "0.2em", paddingRight: "0.2em"}} scope="col">Z</th>
<th style={{textAlign: "center", paddingLeft: "0.2em", paddingRight: "0.2em"}} scope="col">P</th> <th style={{textAlign: "center", paddingLeft: "0.2em", paddingRight: "0.2em"}} scope="col">P</th>
<th style={{textAlign: "center", paddingLeft: "0.2em", paddingRight: "0.2em"}} scope="col">N°</th> <th style={{textAlign: "center", paddingLeft: "0.2em", paddingRight: "0.2em"}} scope="col">N°</th>
<th style={{textAlign: "center"}} scope="col"></th> <th style={{textAlign: "center"}} scope="col"></th>
@ -240,7 +263,7 @@ function MatchList({matches, cat, menuActions}) {
<tbody className="table-group-divider"> <tbody className="table-group-divider">
{marches2.map((m, index) => ( {marches2.map((m, index) => (
<tr key={m.id} <tr key={m.id}
className={m.id === activeMatch ? "table-info" : (liceName[(index - firstIndex) % liceName.length] === lice ? "" : "table-warning")} className={m.id === activeMatch ? "table-info" : (isActiveMatch(index) ? "" : "table-warning")}
onClick={() => setActiveMatch(m.id)}> onClick={() => setActiveMatch(m.id)}>
<td style={{textAlign: "center", paddingLeft: "0.2em", paddingRight: "0.2em"}}> <td style={{textAlign: "center", paddingLeft: "0.2em", paddingRight: "0.2em"}}>
{liceName[(index - firstIndex) % liceName.length]}</td> {liceName[(index - firstIndex) % liceName.length]}</td>
@ -259,7 +282,7 @@ function MatchList({matches, cat, menuActions}) {
</table> </table>
</div> </div>
{activeMatch && <LoadingProvider><ScorePanel matchId={activeMatch} match={match} menuActions={menuActions}/></LoadingProvider>} {activeMatch && <LoadingProvider><ScorePanel matchId={activeMatch} matchs={matches} match={match} menuActions={menuActions}/></LoadingProvider>}
</> </>
} }
@ -325,27 +348,29 @@ function BuildTree({treeData, matches, menuActions}) {
return <div> return <div>
<div className="overflow-y-auto" style={{maxHeight: "50vh"}}>
<div ref={scrollRef} className="overflow-x-auto" style={{position: "relative"}}> <div ref={scrollRef} className="overflow-x-auto" style={{position: "relative"}}>
<DrawGraph root={trees} scrollRef={scrollRef} onMatchClick={onMatchClick} onClickVoid={onClickVoid} <DrawGraph root={trees} scrollRef={scrollRef} onMatchClick={onMatchClick} onClickVoid={onClickVoid}
matchSelect={currentMatch?.matchSelect} matchNext={currentMatch?.matchNext} size={23}/> matchSelect={currentMatch?.matchSelect} matchNext={currentMatch?.matchNext} size={23}/>
</div> </div>
</div>
{currentMatch?.matchSelect && {currentMatch?.matchSelect &&
<LoadingProvider><ScorePanel matchId={currentMatch?.matchSelect} match={match} menuActions={menuActions}/></LoadingProvider>} <LoadingProvider><ScorePanel matchId={currentMatch?.matchSelect} matchs={matches} match={match} menuActions={menuActions}/></LoadingProvider>}
</div> </div>
} }
function ScorePanel({matchId, match, menuActions}) { function ScorePanel({matchId, matchs, match, menuActions}) {
const onClickVoid = useRef(() => { const onClickVoid = useRef(() => {
}); });
return <div className="row" onClick={onClickVoid.current}> return <div className="row" onClick={onClickVoid.current}>
<ScorePanel_ matchId={matchId} match={match} menuActions={menuActions} onClickVoid_={onClickVoid}/> <ScorePanel_ matchId={matchId} matchs={matchs} match={match} menuActions={menuActions} onClickVoid_={onClickVoid}/>
<CardPanel matchId={matchId} match={match}/> <CardPanel matchId={matchId} match={match}/>
</div> </div>
} }
function ScorePanel_({matchId, match, menuActions, onClickVoid_}) { function ScorePanel_({matchId, matchs, match, menuActions, onClickVoid_}) {
const {sendRequest} = useWS() const {sendRequest} = useWS()
const setLoading = useLoadingSwitcher() const setLoading = useLoadingSwitcher()
@ -355,6 +380,11 @@ function ScorePanel_({matchId, match, menuActions, onClickVoid_}) {
const tableRef = useRef(null) const tableRef = useRef(null)
const scoreRef = useRef([]) const scoreRef = useRef([])
const lastScoreClick = useRef(null) const lastScoreClick = useRef(null)
const scoreInRef = useRef(null)
useEffect(() => {
scoreInRef.current = scoreIn;
}, [scoreIn]);
useEffect(() => { useEffect(() => {
menuActions.current.saveScore = (scoreRed, scoreBlue) => { menuActions.current.saveScore = (scoreRed, scoreBlue) => {
@ -403,9 +433,12 @@ function ScorePanel_({matchId, match, menuActions, onClickVoid_}) {
const {matchId, round, comb} = lastScoreClick.current; const {matchId, round, comb} = lastScoreClick.current;
lastScoreClick.current = null; lastScoreClick.current = null;
const scoreIn_ = String(scoreIn).trim() === "" ? -1000 : Number(scoreIn); const scoreIn_ = String(scoreInRef.current).trim() === "" ? -1000 : Number(scoreInRef.current);
const score = matchs.find(m => m.id === matchId).scores.find(s => s.n_round === round);
console.log("Updating score", matchId, round, comb, scoreIn_, score);
const score = match.scores.find(s => s.n_round === round);
let newScore; let newScore;
if (score) { if (score) {
if (comb === 1) if (comb === 1)
@ -474,6 +507,18 @@ function ScorePanel_({matchId, match, menuActions, onClickVoid_}) {
setEnd(match.end); setEnd(match.end);
}, [match]); }, [match]);
useEffect(() => {
const handleClickOutside = (event) => {
if (inputRef.current && !inputRef.current.contains(event.target)) {
onClickVoid();
}
};
document.addEventListener("mousedown", handleClickOutside);
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, []);
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]') const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]')
const o = [...tooltipTriggerList] const o = [...tooltipTriggerList]
o.map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl)) o.map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl))

View File

@ -49,9 +49,6 @@ export function CMTable() {
<CategorieSelect catId={catId} setCatId={setCatId} menuActions={menuActions}/> <CategorieSelect catId={catId} setCatId={setCatId} menuActions={menuActions}/>
</div> </div>
</div> </div>
<div style={{backgroundColor: "#c70000"}}>
D
</div>
</div> </div>
</div> </div>
<Menu menuActions={menuActions}/> <Menu menuActions={menuActions}/>

View File

@ -47,10 +47,10 @@ export function CategoryContent({cat, catId, setCat}) {
const treeListener = ({data}) => { const treeListener = ({data}) => {
if (!cat || data.length < 1 || data[0].categorie !== cat.id) if (!cat || data.length < 1 || data[0].categorie !== cat.id)
return return
setCat({ setCat(cat_ => ({
...cat, ...cat_,
trees: data.map(d => from_sendTree(d, true)) trees: data.sort((a, b) => a.level - b.level).map(d => from_sendTree(d, true))
}) }))
let matches2 = []; let matches2 = [];
let combsToAdd = []; let combsToAdd = [];
@ -64,10 +64,16 @@ export function CategoryContent({cat, catId, setCat}) {
reducer({type: 'UPDATE_OR_ADD', payload: {...data, c1: data.c1?.id, c2: data.c2?.id}}); 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)}}); combDispatch({type: 'SET_ALL', payload: {source: "match", data: [data.c1, data.c2].filter(d => d != null)}});
if (data.c1 !== null && !groupsRef.current.some(g => g.id === data.c1?.id)) setGroups(prev => {
setGroups(prev => [...prev, {id: data.c1?.id, poule: data.poule}]); if (data.c1 !== null && !prev.some(g => g.id === data.c1?.id))
if (data.c2 !== null && !groupsRef.current.some(g => g.id === data.c2?.id)) return [...prev, {id: data.c1?.id, poule: data.poule}];
setGroups(prev => [...prev, {id: data.c2?.id, poule: data.poule}]); return prev;
})
setGroups(prev => {
if (data.c2 !== null && !prev.some(g => g.id === data.c2?.id))
return [...prev, {id: data.c2?.id, poule: data.poule}];
return prev;
})
} }
} }
@ -103,7 +109,7 @@ export function CategoryContent({cat, catId, setCat}) {
name: data.name, name: data.name,
liceName: data.liceName, liceName: data.liceName,
type: data.type, type: data.type,
trees: data.trees.map(d => from_sendTree(d, true)) trees: data.trees.sort((a, b) => a.level - b.level).map(d => from_sendTree(d, true))
}) })
let matches2 = []; let matches2 = [];
@ -554,7 +560,7 @@ function MatchList({matches, cat, groups, reducer}) {
<tr> <tr>
<th style={{textAlign: "center", paddingLeft: "0.2em", paddingRight: "0.2em"}} scope="col">N°</th> <th style={{textAlign: "center", paddingLeft: "0.2em", paddingRight: "0.2em"}} scope="col">N°</th>
<th style={{textAlign: "center", paddingLeft: "0.2em", paddingRight: "0.2em"}} scope="col">Poule</th> <th style={{textAlign: "center", paddingLeft: "0.2em", paddingRight: "0.2em"}} scope="col">Poule</th>
<th style={{textAlign: "center", paddingLeft: "0.2em", paddingRight: "0.2em"}} scope="col">Lice</th> <th style={{textAlign: "center", paddingLeft: "0.2em", paddingRight: "0.2em"}} scope="col">Zone</th>
<th style={{textAlign: "center"}} scope="col"></th> <th style={{textAlign: "center"}} scope="col"></th>
<th style={{textAlign: "center"}} scope="col">Rouge</th> <th style={{textAlign: "center"}} scope="col">Rouge</th>
<th style={{textAlign: "center"}} scope="col">Blue</th> <th style={{textAlign: "center"}} scope="col">Blue</th>

View File

@ -95,7 +95,7 @@ function WSStatus({setPerm}) {
return () => dispatch({type: 'removeListener', payload: welcomeListener}) return () => dispatch({type: 'removeListener', payload: welcomeListener})
}, []) }, [])
return <div className="row"> return <div className="row" style={{marginRight: "inherit"}}>
<h2 className="col">{name}</h2> <h2 className="col">{name}</h2>
<div className="col-auto" style={{margin: "auto 0", padding: 0}}>Serveur: <ColoredCircle <div className="col-auto" style={{margin: "auto 0", padding: 0}}>Serveur: <ColoredCircle
color={isReady ? (inWait ? "#ffad32" : "#00c700") : "#e50000"}/> color={isReady ? (inWait ? "#ffad32" : "#00c700") : "#e50000"}/>