dev #107

Merged
Thibaut merged 8 commits from dev into master 2026-02-06 16:18:40 +00:00
10 changed files with 174 additions and 16 deletions
Showing only changes of commit 2e846bf801 - Show all commits

View File

@ -82,12 +82,14 @@
"genre.h": "M",
"genre.na": "NA",
"individuelle": "Individual",
"informationCatégorie": "Category information",
"inscrit": "Registered",
"leTournoiServiraDePhaseFinaleAuxPoules": "The tournament will serve as the final phase for the group stage.",
"lesCombattantsEnDehors": "Fighters not participating in the tournament will have a ranking match.",
"listeDesCartons": "List of cards",
"manche": "Round",
"matchPourLesPerdantsDuTournoi": "Match for tournament losers:",
"matchTerminé": "Match over",
"matches": "Matches",
"modifier": "Edit",
"msg1": "There are already matches in this pool; what do you want to do with them?",
@ -97,6 +99,7 @@
"nomDeLaZone": "Area name",
"nomDeLéquipe": "team name",
"nomDesZonesDeCombat": "Combat zone names <1>(separated by ';')</1>",
"nombreDeCombattants": "Number of fighters",
"nouvelle...": "New...",
"obs.préfixDesSources": "Source prefix",
"pays": "Country",
@ -127,6 +130,7 @@
"supprimerUn": "Delete one",
"sélectionneLesModesDaffichage": "Select display modes",
"sélectionner": "Select",
"taille": "Size",
"team": "Team",
"terminé": "Finished",
"texteCopiéDansLePresse": "Text copied to clipboard! Paste it into an HTML tag on your WordPress.",

View File

@ -619,9 +619,9 @@
"supprimerLeCompte": "Delete account",
"supprimerLeCompte.msg": "Are you sure you want to delete this account?",
"sword.none": "$t(sans) / $t(nonDéfinie)",
"sword.oneHand": "One hand",
"sword.oneHand": "One hand sword",
"sword.saber": "Saber",
"sword.twoHand": "Two hands",
"sword.twoHand": "Two hands sword",
"sélectionEnéquipeDeFrance": "Selection in the French team",
"sélectionner...": "Select...",
"toast.edit.error": "Failed to save changes",

View File

@ -11,6 +11,7 @@
"catégorie": "Category",
"chargement": "Loading",
"classement": "Ranking",
"classementFinal": "Final standings",
"club": "Club",
"combattant": "Fighter",
"combattants": "Fighters",
@ -65,6 +66,5 @@
"tournois": "Tournaments",
"tousLesCombattants": "All fighters",
"victoire": "Win",
"victoires": "Wins",
"classementFinal": "Final standings"
"victoires": "Wins"
}

View File

@ -82,12 +82,14 @@
"genre.h": "H",
"genre.na": "NA",
"individuelle": "Individuelle",
"informationCatégorie": "Information catégorie",
"inscrit": "Inscrit",
"leTournoiServiraDePhaseFinaleAuxPoules": "Le tournoi servira de phase finale aux poules",
"lesCombattantsEnDehors": "Les combattants en dehors du tournoi auront un match de classement",
"listeDesCartons": "Liste des cartons",
"manche": "Manche",
"matchPourLesPerdantsDuTournoi": "Match pour les perdants du tournoi:",
"matchTerminé": "Match terminé",
"matches": "Matches",
"modifier": "Modifier",
"msg1": "Il y a déjà des matchs dans cette poule, que voulez-vous faire avec ?",
@ -97,6 +99,7 @@
"nomDeLaZone": "Nom de la zone",
"nomDeLéquipe": "Nom de l'équipe",
"nomDesZonesDeCombat": "Nom des zones de combat <1>(séparée par des ';')</1>",
"nombreDeCombattants": "Nombre de combattants",
"nouvelle...": "Nouvelle...",
"obs.préfixDesSources": "Préfix des sources",
"pays": "Pays",
@ -127,6 +130,7 @@
"supprimerUn": "Supprimer un",
"sélectionneLesModesDaffichage": "Sélectionne les modes d'affichage",
"sélectionner": "Sélectionner",
"taille": "Taille",
"team": "Équipe",
"terminé": "Terminé",
"texteCopiéDansLePresse": "Texte copié dans le presse-papier ! Collez-le dans une balise HTML sur votre WordPress.",

View File

@ -623,9 +623,9 @@
"supprimerLeCompte": "Supprimer le compte",
"supprimerLeCompte.msg": "Êtes-vous sûr de vouloir supprimer ce compte ?",
"sword.none": "$t(sans) / $t(nonDéfinie)",
"sword.oneHand": "Une main",
"sword.oneHand": "Épée une main",
"sword.saber": "Sabre",
"sword.twoHand": "Deux mains",
"sword.twoHand": "Épée deux mains",
"sélectionEnéquipeDeFrance": "Sélection en équipe de France",
"sélectionner...": "Sélectionner...",
"toast.edit.error": "Échec de l'enregistrement des modifications",

View File

@ -11,6 +11,7 @@
"catégorie": "Catégorie",
"chargement": "Chargement",
"classement": "Classement",
"classementFinal": "Classement final",
"club": "Club",
"combattant": "Combattant",
"combattants": "Combattants",
@ -65,6 +66,5 @@
"tournois": "Tournois",
"tousLesCombattants": "Tous les combattants",
"victoire": "Victoire",
"victoires": "Victoires",
"classementFinal": "Classement final"
"victoires": "Victoires"
}

View File

@ -4,7 +4,7 @@ import {timePrint} from "../../../utils/Tools.js";
import {useTranslation} from "react-i18next";
import {useWS} from "../../../hooks/useWS.jsx";
export function ChronoPanel() {
export function ChronoPanel({menuActions}) {
const [config, setConfig] = useState({
time: Number(sessionStorage.getItem("chronoTime") || "90999"),
pause: Number(sessionStorage.getItem("chronoPause") || "60999")
@ -25,6 +25,10 @@ export function ChronoPanel() {
return chrono.time + Date.now() - chrono.startTime
}
menuActions.current.setTimerConfig = (time, pause) => {
setConfig({time: time + 999, pause: pause + 999})
}
useEffect(() => {
publicAffDispatch({type: 'CALL_TIME', payload: {timeStr: state.current.lastTimeStr, timeColor: state.current.color}})
}, [])

View File

@ -6,13 +6,24 @@ 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 {getToastMessage, virtualScore, win_end} from "../../../utils/Tools.js";
import {
CatList,
getCatName, getShieldSize,
getShieldTypeName, getSwordSize,
getSwordTypeName,
getToastMessage, timePrint,
virtual_end,
virtualScore,
win_end
} from "../../../utils/Tools.js";
import "./CMTMatchPanel.css"
import {useOBS} from "../../../hooks/useOBS.jsx";
import {useTranslation} from "react-i18next";
import {hasEffectCard, useCards, useCardsDispatch} from "../../../hooks/useCard.jsx";
import {ScorePanel} from "./ScoreAndCardPanel.jsx";
import {toast} from "react-toastify";
import {createPortal} from "react-dom";
import ProtectionSelector from "../../../components/ProtectionSelector.jsx";
function CupImg() {
return <img decoding="async" loading="lazy" width={"16"} height={"16"} className="wp-image-1635"
@ -149,7 +160,97 @@ function CMTMatchPanel({catId, cat, menuActions}) {
}
}, [catId]);
return <ListMatch cat={cat} matches={matches} trees={trees} menuActions={menuActions}/>
return <>
<ListMatch cat={cat} matches={matches} trees={trees} menuActions={menuActions}/>
<SetTimeToChrono cat={cat} matches={matches} menuActions={menuActions}/>
</>
}
function SetTimeToChrono({cat, matches, menuActions}) {
const [catAverage, setCatAverage] = useState("---");
const [genreAverage, setGenreAverage] = useState("H");
const [nbComb, setNbComb] = useState(0);
const [preset, setPreset] = useState(undefined);
const [time, setTime] = useState({round: 0, pause: 0});
const {cards_v} = useCards();
const {t} = useTranslation("cm");
const {getComb} = useCombs();
useEffect(() => {
if (!cat || matches.filter(m => m.categorie === cat.id).length === 0) {
setCatAverage("---");
setNbComb(0);
return;
}
setPreset(cat.preset);
const genres = [];
const cats = [];
const combs = [];
for (const m of matches.filter(m => m.categorie === cat.id)) {
if (m.c1 && !combs.includes(m.c1))
combs.push(m.c1);
if (m.c2 && !combs.includes(m.c2))
combs.push(m.c2);
}
setNbComb(combs.length);
combs.map(cId => getComb(cId, null)).filter(c => c && c.categorie)
.forEach(c => {
cats.push(Math.min(CatList.length, CatList.indexOf(c.categorie) + c.overCategory))
genres.push(c.genre)
});
const catAvg = Math.round(cats.reduce((a, b) => a + b, 0) / cats.length);
setCatAverage(CatList.at(catAvg) || "---");
const genreAvg = Math.round(genres.reduce((a, b) => a + (b === "F" ? 1 : 0), 0) / genres.length);
setGenreAverage(genreAvg > 0.5 ? "F" : "H");
if (!cat.preset || !cat.preset.categories)
return;
const catAvailable = cat.preset.categories.map(c => CatList.indexOf(c.categorie));
let p;
if (catAvailable.includes(catAvg)) {
p = cat.preset.categories.find(c => CatList.indexOf(c.categorie) === catAvg);
} else {
const closest = catAvailable.reduce((a, b) => Math.abs(b - catAvg) < Math.abs(a - catAvg) ? b : a);
p = cat.preset.categories.find(c => CatList.indexOf(c.categorie) === closest);
}
menuActions.current.setTimerConfig(p.roundDuration, p.pauseDuration)
setTime({round: p.roundDuration, pause: p.pauseDuration})
}, [cat, matches]);
const marches2 = matches.filter(m => m.categorie === cat.id)
.map(m => ({...m, end: m.end || virtual_end(m, cards_v)}))
return createPortal(<div className="card mb-3">
<div className="card-header">{t('informationCatégorie')}</div>
<div className="card-body">
<div className="row">
<div className="col text-start">
<div>{t('catégorie')} : {getCatName(catAverage)}</div>
<div>{t('arme', {ns: 'common'})} : {getSwordTypeName(preset?.sword)} - {t('taille')} {getSwordSize(preset?.sword, catAverage, genreAverage)}</div>
<div>{t('bouclier', {ns: 'common'})} : {getShieldTypeName(preset?.shield)} - {t('taille')} {getShieldSize(preset?.shield, catAverage)}</div>
<div>{t('duréeRound')} : {timePrint(time.round)}</div>
<div>{t('duréePause')} : {timePrint(time.pause)}</div>
<div>{t('matchTerminé')}: {marches2.filter(m => m.end).length} sur {marches2.length}</div>
<div>{t('nombreDeCombattants')} : {nbComb}</div>
</div>
<div className="col text-center">
<h6>{t('protectionObligatoire', {ns: 'common'})} :</h6>
<ProtectionSelector shield={preset?.shield !== "NONE"}
mandatoryProtection={CatList.indexOf(catAverage) <= CatList.indexOf("JUNIOR") ?
preset?.mandatoryProtection1 : preset?.mandatoryProtection2} setMandatoryProtection={() => {
}}/>
</div>
</div>
</div>
</div>, document.getElementById("infoCategory"))
}
function ListMatch({cat, matches, trees, menuActions}) {

View File

@ -1,11 +1,11 @@
import React, {useEffect, useRef, useState} from "react";
import {useRequestWS, useWS} from "../../../hooks/useWS.jsx";
import {CombName, useCombs, useCombsDispatch} from "../../../hooks/useComb.jsx";
import {useCombs, useCombsDispatch} from "../../../hooks/useComb.jsx";
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import {createPortal} from "react-dom";
import {copyStyles} from "../../../utils/copyStyles.js";
import {PubAffProvider, usePubAffDispatch, usePubAffState} from "../../../hooks/useExternalWindow.jsx";
import {faArrowRightArrowLeft, faDisplay, faFile, faTrash} from "@fortawesome/free-solid-svg-icons";
import {faArrowRightArrowLeft, faDisplay, faFile} from "@fortawesome/free-solid-svg-icons";
import {PubAffWindow} from "./PubAffWindow.jsx";
import {SimpleIconsScore} from "../../../assets/SimpleIconsScore.ts";
import {ChronoPanel} from "./CMTChronoPanel.jsx";
@ -15,7 +15,6 @@ import {importOBSConfiguration, OBSProvider, useOBS} from "../../../hooks/useOBS
import {SimpleIconsOBS} from "../../../assets/SimpleIconsOBS.ts";
import {Flip, toast} from "react-toastify";
import {useTranslation} from "react-i18next";
import {useCards, useCardsDispatch} from "../../../hooks/useCard.jsx";
import {getToastMessage} from "../../../utils/Tools.js";
export function CMTable() {
@ -39,7 +38,7 @@ export function CMTable() {
<div className="card mb-3">
<div className="card-header">{t('chronomètre')}</div>
<div className="card-body">
<ChronoPanel/>
<ChronoPanel menuActions={menuActions}/>
</div>
</div>
@ -49,6 +48,8 @@ export function CMTable() {
<PointPanel menuActions={menuActions}/>
</div>
</div>
<div id="infoCategory"></div>
</div>
<div className="col-md-12 col-xl-6 col-xxl-5">
<div className="card mb-3">

View File

@ -199,6 +199,38 @@ export function getSwordTypeName(type) {
}
}
export function getSwordSize(type, cat, gender) {
const catIndex = CatList.indexOf(cat)
if (type === "ONE_HAND") {
if (catIndex <= 1) {
return "XS"
} else if (catIndex <= 3) {
return "S"
} else if (catIndex <= 6 || gender === "F") {
return "M"
} else {
return "L"
}
} else if (type === "TWO_HAND") {
if (catIndex <= 5) {
return "--"
} else if (catIndex <= 6 || gender === "F") {
return "XL"
} else {
return "XXL"
}
} else if (type === "SABER") {
if (catIndex <= 3) {
return "--"
} else if (catIndex <= 6) {
return "S"
} else {
return "L"
}
}
return "--"
}
export const ShieldList = [
"NONE",
"STANDARD",
@ -224,6 +256,18 @@ export function getShieldTypeName(type) {
}
}
export function getShieldSize(type, cat) {
if (type === "STANDARD") {
const catIndex = CatList.indexOf(cat)
if (catIndex <= 1) {
return "S"
} else {
return "L"
}
}
return "--"
}
export function getToastMessage(msgKey, ns = 'common') {
return {
pending: i18n.t(msgKey + '.pending', {ns}),