i18n #99
143
src/main/webapp/public/locales/en/cm.json
Normal file
143
src/main/webapp/public/locales/en/cm.json
Normal file
@ -0,0 +1,143 @@
|
||||
{
|
||||
"--SélectionnerUnCombattant--": "-- Select a fighter --",
|
||||
"--Tous--": "-- All --",
|
||||
"actuel": "Current",
|
||||
"administration": "Administration",
|
||||
"adresseDuServeur": "Server address",
|
||||
"ajouter": "Add",
|
||||
"ajouterDesCombattants": "Add fighters",
|
||||
"attention": "Warning",
|
||||
"aucuneConfigurationObs": "No OBS configuration found, please import one",
|
||||
"bleu": "Blue",
|
||||
"blue": "Blue",
|
||||
"catégorie": "Category",
|
||||
"chrono.+/-...S": "+/- ... s",
|
||||
"chrono.+10S": "+10 s",
|
||||
"chrono.+1S": "+1 s",
|
||||
"chrono.-10S": "-10 s",
|
||||
"chrono.-1S": "-1 s",
|
||||
"chrono.arrêter": "Stop",
|
||||
"chrono.définirLeTemps": "Set time",
|
||||
"chrono.démarrer": "Start",
|
||||
"chrono.editionTemps": "Time editing",
|
||||
"chrono.entrezLeTempsEnS": "Enter time in seconds",
|
||||
"chrono.recapTemps": "Time: {{temps}}, pause: {{pause}}",
|
||||
"chronomètre": "Stopwatch",
|
||||
"club": "Club",
|
||||
"compétition": "Competition",
|
||||
"compétitionManager": "Competition manager",
|
||||
"config.obs.dossierDesResources": "Resources folder",
|
||||
"config.obs.motDePasseDuServeur": "Server password",
|
||||
"config.obs.warn1": "/! The password will be stored in plain text; it is recommended to use it only on OBS WebSocket and to change it between each competition",
|
||||
"config.obs.ws": "ws://",
|
||||
"configurationObs": "OBS Configuration",
|
||||
"confirm1": "This match already has results; are you sure you want to delete it?",
|
||||
"confirm2.msg": "Do you really want to change the tournament tree size or the loser matches? This will modify existing matches (including possible deletions)!",
|
||||
"confirm2.title": "Tournament tree change",
|
||||
"confirm3.msg": "Do you really want to remove the {{typeStr}} part from the category? This will delete the matches contained in this part!",
|
||||
"confirm3.title": "Change category type",
|
||||
"confirm4.msg": "Do you really want to delete the category {{name}}. This will delete all associated matches!",
|
||||
"confirm4.title": "Delete category",
|
||||
"conserverUniquementLesMatchsTerminés": "Keep only finished matches",
|
||||
"contre": "vs",
|
||||
"créerLesMatchs": "Create matches",
|
||||
"demi-finalesEtFinales": "Semi-finals and finals",
|
||||
"duréePause": "Pause duration",
|
||||
"duréeRound": "Round duration",
|
||||
"editionDeLaCatégorie": "Edit category",
|
||||
"enregister": "Save",
|
||||
"enregistrer": "Save",
|
||||
"epéeBouclier": "Sword and shield",
|
||||
"err1": "A fighter cannot fight themselves!",
|
||||
"err2": "The combat zone name format is invalid. Please separate names with ';'.",
|
||||
"err3": "At least one type (pool or tournament) must be selected.",
|
||||
"erreurLorsDeLaCopieDansLePresse": "Error while copying to clipboard: ",
|
||||
"erreurLorsDeLaCréationDesMatchs": "Error while creating matches: ",
|
||||
"exporter": "Export",
|
||||
"fermer": "Close",
|
||||
"finalesUniquement": "Finals only",
|
||||
"genre": "Gender",
|
||||
"genre.f": "F",
|
||||
"genre.h": "M",
|
||||
"genre.na": "NA",
|
||||
"inscrit": "Registered",
|
||||
"manche": "Round",
|
||||
"matchPourLesPerdantsDuTournoi": "Match for tournament losers:",
|
||||
"matches": "Matches",
|
||||
"modifier": "Edit",
|
||||
"msg1": "There are already matches in this pool; what do you want to do with them?",
|
||||
"neRienConserver": "Keep nothing",
|
||||
"no": "No.",
|
||||
"nom": "Name",
|
||||
"nomDesZonesDeCombat": "Combat zone names <1>(separated by ';')</1>",
|
||||
"nouvelle...": "New...",
|
||||
"obs.préfixDesSources": "Source prefix",
|
||||
"pays": "Country",
|
||||
"poids": "Weight",
|
||||
"poule": "Pool",
|
||||
"poulePour": "Pool for: ",
|
||||
"préparation...": "Preparing...",
|
||||
"rouge": "Red",
|
||||
"réinitialiser": "Reset",
|
||||
"résultat": "Result",
|
||||
"sansPoule": "No pool",
|
||||
"sauvegarde": "Backup",
|
||||
"sauvegarder": "Save",
|
||||
"score": "Score",
|
||||
"score.err1": "Cannot end a tournament match in a draw.",
|
||||
"score.spe": "Special scores: <br/>-997: disqualified <br/>-998: absent <br/>-999: forfeit",
|
||||
"scores": "Scores",
|
||||
"secrétariatsDeLice": "Ring secretariats",
|
||||
"select.aucunCombattantDisponible": "No fighters available",
|
||||
"select.aucunCombattantSélectionné": "No fighters selected",
|
||||
"select.msg1": "(0 = disabled)",
|
||||
"select.recherche": "Search",
|
||||
"select.sélectionnerDesCombatants": "Select fighters",
|
||||
"select.à": "to",
|
||||
"serveur": "Server",
|
||||
"suivant": "Next",
|
||||
"supprimer": "Delete",
|
||||
"sélectionneLesModesDaffichage": "Select display modes",
|
||||
"sélectionner": "Select",
|
||||
"terminé": "Finished",
|
||||
"texteCopiéDansLePresse": "Text copied to clipboard! Paste it into an HTML tag on your WordPress.",
|
||||
"toast.createCategory.error": "Error while creating the category",
|
||||
"toast.createCategory.pending": "Creating category...",
|
||||
"toast.createCategory.success": "Category created!",
|
||||
"toast.deleteCategory.error": "Error while deleting the category",
|
||||
"toast.deleteCategory.pending": "Deleting category...",
|
||||
"toast.deleteCategory.success": "Category deleted!",
|
||||
"toast.matchs.create.error": "Error while creating matches.",
|
||||
"toast.matchs.create.pending": "Creating matches in progress...",
|
||||
"toast.matchs.create.success": "Matches created successfully.",
|
||||
"toast.updateCategory.error": "Error while updating the category",
|
||||
"toast.updateCategory.pending": "Updating category...",
|
||||
"toast.updateCategory.success": "Category updated!",
|
||||
"toast.updateMatchScore.error": "Error while updating match score",
|
||||
"toast.updateMatchScore.pending": "Updating match score...",
|
||||
"toast.updateMatchScore.success": "Match score updated!",
|
||||
"toast.updateTrees.error": "Error while updating trees",
|
||||
"toast.updateTrees.init.error": "Error while creating trees",
|
||||
"toast.updateTrees.init.pending": "Creating tournament trees...",
|
||||
"toast.updateTrees.init.success": "Trees created!",
|
||||
"toast.updateTrees.pending": "Updating tournament trees...",
|
||||
"toast.updateTrees.success": "Trees updated!",
|
||||
"tournoi": "Tournament",
|
||||
"tournois": "Tournaments",
|
||||
"tousLesMatchs": "All matches",
|
||||
"toutConserver": "Keep all",
|
||||
"ttm.admin.obs": "Short click: Download resources. Long click: Create OBS configuration",
|
||||
"ttm.admin.scripte": "Copy integration script",
|
||||
"ttm.table.inverserLaPosition": "Reverse fighter positions on this screen",
|
||||
"ttm.table.obs": "Short click: Load configuration and connect. Long click: Ring configuration",
|
||||
"ttm.table.pub_aff": "Open public display",
|
||||
"ttm.table.pub_score": "Show scores on public display",
|
||||
"type": "Type",
|
||||
"téléchargement": "Download",
|
||||
"téléchargementEnCours": "Downloading...",
|
||||
"téléchargementTerminé!": "Download completed!",
|
||||
"uneCatégorie": "a category",
|
||||
"valider": "Validate",
|
||||
"zone": "Zone",
|
||||
"zoneDeCombat": "Combat zone"
|
||||
}
|
||||
143
src/main/webapp/public/locales/fr/cm.json
Normal file
143
src/main/webapp/public/locales/fr/cm.json
Normal file
@ -0,0 +1,143 @@
|
||||
{
|
||||
"--SélectionnerUnCombattant--": "-- Sélectionner un combattant --",
|
||||
"--Tous--": "-- Tous --",
|
||||
"actuel": "Actuel",
|
||||
"administration": "Administration",
|
||||
"adresseDuServeur": "Adresse du serveur",
|
||||
"ajouter": "Ajouter",
|
||||
"ajouterDesCombattants": "Ajouter des combattants",
|
||||
"attention": "Attention",
|
||||
"aucuneConfigurationObs": "Aucune configuration OBS trouvée, veuillez en importer une",
|
||||
"bleu": "Bleu",
|
||||
"blue": "Blue",
|
||||
"catégorie": "Catégorie",
|
||||
"chrono.+/-...S": "+/- ... s",
|
||||
"chrono.+10S": "+10 s",
|
||||
"chrono.+1S": "+1 s",
|
||||
"chrono.-10S": "-10 s",
|
||||
"chrono.-1S": "-1 s",
|
||||
"chrono.arrêter": "Arrêter",
|
||||
"chrono.définirLeTemps": "Définir le temps",
|
||||
"chrono.démarrer": "Démarrer",
|
||||
"chrono.editionTemps": "Edition temps",
|
||||
"chrono.entrezLeTempsEnS": "Entrez le temps en s",
|
||||
"chrono.recapTemps": "Temps: {{temps}}, pause: {{pause}}",
|
||||
"chronomètre": "Chronomètre",
|
||||
"club": "Club",
|
||||
"compétition": "Compétition",
|
||||
"compétitionManager": "Compétition manager",
|
||||
"config.obs.dossierDesResources": "Dossier des resources",
|
||||
"config.obs.motDePasseDuServeur": "Mot de passe du serveur",
|
||||
"config.obs.warn1": "/! Le mot de passe va être stoker en claire, il est recommandé de ne l'utiliser que sur obs websocket et d'en changer entre chaque compétition",
|
||||
"config.obs.ws": "ws://",
|
||||
"configurationObs": "Configuration OBS",
|
||||
"confirm1": "Ce match a déjà des résultats, êtes-vous sûr de vouloir le supprimer ?",
|
||||
"confirm2.msg": "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)!",
|
||||
"confirm2.title": "Changement de l'arbre du tournoi",
|
||||
"confirm3.msg": "Voulez-vous vraiment enlever la partie {{typeStr}} de la catégorie. Cela va supprimer les matchs contenus dans cette partie !",
|
||||
"confirm3.title": "Changement de type de catégorie",
|
||||
"confirm4.msg": "Voulez-vous vraiment supprimer la catégorie {{name}}. Cela va supprimer tous les matchs associés !",
|
||||
"confirm4.title": "Suppression de la catégorie",
|
||||
"conserverUniquementLesMatchsTerminés": "Conserver uniquement les matchs terminés",
|
||||
"contre": "contre",
|
||||
"créerLesMatchs": "Créer les matchs",
|
||||
"demi-finalesEtFinales": "Demi-finales et finales",
|
||||
"duréePause": "Durée pause",
|
||||
"duréeRound": "Durée round",
|
||||
"editionDeLaCatégorie": "Edition de la catégorie",
|
||||
"enregister": "Enregister",
|
||||
"enregistrer": "Enregistrer",
|
||||
"epéeBouclier": "Epée bouclier",
|
||||
"err1": "Un combattant ne peut pas s'affronter lui-même !",
|
||||
"err2": "Le format du nom des zones de combat est invalide. Veuillez séparer les noms par des ';'.",
|
||||
"err3": "Au moins un type (poule ou tournoi) doit être sélectionné.",
|
||||
"erreurLorsDeLaCopieDansLePresse": "Erreur lors de la copie dans le presse-papier : ",
|
||||
"erreurLorsDeLaCréationDesMatchs": "Erreur lors de la création des matchs: ",
|
||||
"exporter": "Exporter",
|
||||
"fermer": "Fermer",
|
||||
"finalesUniquement": "Finales uniquement",
|
||||
"genre": "Genre",
|
||||
"genre.f": "F",
|
||||
"genre.h": "H",
|
||||
"genre.na": "NA",
|
||||
"inscrit": "Inscrit",
|
||||
"manche": "Manche",
|
||||
"matchPourLesPerdantsDuTournoi": "Match pour les perdants du tournoi:",
|
||||
"matches": "Matches",
|
||||
"modifier": "Modifier",
|
||||
"msg1": "Il y a déjà des matchs dans cette poule, que voulez-vous faire avec ?",
|
||||
"neRienConserver": "Ne rien conserver",
|
||||
"no": "N°",
|
||||
"nom": "Nom",
|
||||
"nomDesZonesDeCombat": "Nom des zones de combat <1>(séparée par des ';')</1>",
|
||||
"nouvelle...": "Nouvelle...",
|
||||
"obs.préfixDesSources": "Préfix des sources",
|
||||
"pays": "Pays",
|
||||
"poids": "Poids",
|
||||
"poule": "Poule",
|
||||
"poulePour": "Poule pour: ",
|
||||
"préparation...": "Préparation...",
|
||||
"rouge": "Rouge",
|
||||
"réinitialiser": "Réinitialiser",
|
||||
"résultat": "Résultat",
|
||||
"sansPoule": "Sans poule",
|
||||
"sauvegarde": "Sauvegarde",
|
||||
"sauvegarder": "Sauvegarder",
|
||||
"score": "Score",
|
||||
"score.err1": "Impossible de terminer un match nul en tournois.",
|
||||
"score.spe": "Score speciaux : <br/>-997 : disqualifié <br/>-998 : absent <br/>-999 : forfait",
|
||||
"scores": "Scores",
|
||||
"secrétariatsDeLice": "Secrétariats de lice",
|
||||
"select.aucunCombattantDisponible": "Aucun combattant disponible",
|
||||
"select.aucunCombattantSélectionné": "Aucun combattant sélectionné",
|
||||
"select.msg1": "(0 = désactivé)",
|
||||
"select.recherche": "Recherche",
|
||||
"select.sélectionnerDesCombatants": "Sélectionner des combatants",
|
||||
"select.à": "à",
|
||||
"serveur": "Serveur",
|
||||
"suivant": "Suivant",
|
||||
"supprimer": "Supprimer",
|
||||
"sélectionneLesModesDaffichage": "Sélectionne les modes d'affichage",
|
||||
"sélectionner": "Sélectionner",
|
||||
"terminé": "Terminé",
|
||||
"texteCopiéDansLePresse": "Texte copié dans le presse-papier ! Collez-le dans une balise HTML sur votre WordPress.",
|
||||
"toast.createCategory.error": "Erreur lors de la création de la catégorie",
|
||||
"toast.createCategory.pending": "Création de la catégorie...",
|
||||
"toast.createCategory.success": "Catégorie créée !",
|
||||
"toast.deleteCategory.error": "Erreur lors de la suppression de la catégorie",
|
||||
"toast.deleteCategory.pending": "Suppression de la catégorie...",
|
||||
"toast.deleteCategory.success": "Catégorie supprimée !",
|
||||
"toast.matchs.create.error": "Erreur lors de la création des matchs.",
|
||||
"toast.matchs.create.pending": "Création des matchs en cours...",
|
||||
"toast.matchs.create.success": "Matchs créés avec succès.",
|
||||
"toast.updateCategory.error": "Erreur lors de la mise à jour de la catégorie",
|
||||
"toast.updateCategory.pending": "Mise à jour de la catégorie...",
|
||||
"toast.updateCategory.success": "Catégorie mise à jour !",
|
||||
"toast.updateMatchScore.error": "Erreur lors de la mise à jour du score du match",
|
||||
"toast.updateMatchScore.pending": "Mise à jour du score du match...",
|
||||
"toast.updateMatchScore.success": "Score du match mis à jour !",
|
||||
"toast.updateTrees.error": "Erreur lors de la mise à jour des arbres",
|
||||
"toast.updateTrees.init.error": "Erreur lors de la création des arbres",
|
||||
"toast.updateTrees.init.pending": "Création des arbres du tournoi...",
|
||||
"toast.updateTrees.init.success": "Arbres créés !",
|
||||
"toast.updateTrees.pending": "Mise à jour des arbres du tournoi...",
|
||||
"toast.updateTrees.success": "Arbres mis à jour !",
|
||||
"tournoi": "Tournoi",
|
||||
"tournois": "Tournois",
|
||||
"tousLesMatchs": "Tous les matchs",
|
||||
"toutConserver": "Tout conserver",
|
||||
"ttm.admin.obs": "Clique court : Télécharger les ressources. Clique long : Créer la configuration obs",
|
||||
"ttm.admin.scripte": "Copier le scripte d'intégration",
|
||||
"ttm.table.inverserLaPosition": "Inverser la position des combattants sur cette écran",
|
||||
"ttm.table.obs": "Clique court : Charger la configuration et se connecter. Clique long : Configuration de la lice",
|
||||
"ttm.table.pub_aff": "Ouvrir l'affichage public",
|
||||
"ttm.table.pub_score": "Afficher les scores sur l'affichage public",
|
||||
"type": "Type",
|
||||
"téléchargement": "Téléchargement",
|
||||
"téléchargementEnCours": "Téléchargement en cours...",
|
||||
"téléchargementTerminé!": "Téléchargement terminé !",
|
||||
"uneCatégorie": "une catégorie",
|
||||
"valider": "Valider",
|
||||
"zone": "Zone",
|
||||
"zoneDeCombat": "Zone de combat"
|
||||
}
|
||||
@ -12,6 +12,9 @@ import {SimpleIconsOBS} from "../../../assets/SimpleIconsOBS.ts";
|
||||
import JSZip from "jszip";
|
||||
import {detectOptimalBackground} from "../../../components/SmartLogoBackground.jsx";
|
||||
import {faGlobe} from "@fortawesome/free-solid-svg-icons";
|
||||
import {Trans, useTranslation} from "react-i18next";
|
||||
import i18n from "i18next";
|
||||
import {getToastMessage} from "../../../utils/Tools.js";
|
||||
|
||||
const vite_url = import.meta.env.VITE_URL;
|
||||
|
||||
@ -139,7 +142,7 @@ async function downloadResourcesAsZip(resourceList) {
|
||||
completed++;
|
||||
const progress = Math.round((completed / resourceList.length) * 100);
|
||||
progressBar.style.width = `${progress}%`;
|
||||
progressText.textContent = `Téléchargement (${completed}/${resourceList.length}) : ${result.filename}`;
|
||||
progressText.textContent = `${i18n.t('téléchargement')} (${completed}/${resourceList.length}) : ${result.filename}`;
|
||||
return result;
|
||||
})
|
||||
);
|
||||
@ -155,13 +158,14 @@ async function downloadResourcesAsZip(resourceList) {
|
||||
|
||||
// Fermer la modale
|
||||
modal.hide();
|
||||
progressText.textContent = "Téléchargement terminé !";
|
||||
progressText.textContent = i18n.t('téléchargementTerminé!');
|
||||
}
|
||||
|
||||
function Menu({menuActions, compUuid}) {
|
||||
const e = document.getElementById("actionMenu")
|
||||
const longPress = useRef({time: null, timer: null, button: null});
|
||||
const obsModal = useRef(null);
|
||||
const {t} = useTranslation("cm");
|
||||
|
||||
for (const x of tto)
|
||||
x.dispose();
|
||||
@ -224,9 +228,9 @@ function Menu({menuActions, compUuid}) {
|
||||
initCompetitionApi("${vite_url}/api/public/result/${compUuid}")
|
||||
</script>`
|
||||
).then(() => {
|
||||
toast.success("Texte copié dans le presse-papier ! Collez-le dans une balise HTML sur votre WordPress.");
|
||||
toast.success(t('texteCopiéDansLePresse'));
|
||||
}).catch(err => {
|
||||
toast.error("Erreur lors de la copie dans le presse-papier : " + err);
|
||||
toast.error(t('erreurLorsDeLaCopieDansLePresse') + err);
|
||||
});
|
||||
}
|
||||
|
||||
@ -241,12 +245,12 @@ function Menu({menuActions, compUuid}) {
|
||||
onMouseDown={() => longPressDown("obs")}
|
||||
onMouseUp={() => longPressUp("obs")}
|
||||
data-bs-toggle="tooltip2" data-bs-placement="top"
|
||||
data-bs-title="Clique court : Télécharger les ressources. Clique long : Créer la configuration obs"/>
|
||||
data-bs-title={t('ttm.admin.obs')}/>
|
||||
<FontAwesomeIcon icon={faGlobe} size="xl"
|
||||
style={{color: "#6c757d", cursor: "pointer"}}
|
||||
onClick={() => copyScriptToClipboard()}
|
||||
data-bs-toggle="tooltip2" data-bs-placement="top"
|
||||
data-bs-title="Copier le scripte d'intégration"/>
|
||||
data-bs-title={t('ttm.admin.scripte')}/>
|
||||
</>, document.getElementById("actionMenu"))}
|
||||
|
||||
<button ref={obsModal} type="button" className="btn btn-link" data-bs-toggle="modal" data-bs-target="#OBSModal" style={{display: 'none'}}>
|
||||
@ -256,33 +260,32 @@ function Menu({menuActions, compUuid}) {
|
||||
<div className="modal-dialog">
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<h5 className="modal-title">Configuration OBS</h5>
|
||||
<h5 className="modal-title">{t('configurationObs')}</h5>
|
||||
<button type="button" className="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form onSubmit={handleOBSSubmit}>
|
||||
<div className="modal-body">
|
||||
<strong>/!\ Le mot de passe va être stoker en claire, il est recommandé de ne l'utiliser que sur obs websocket et d'en
|
||||
changer entre chaque compétition</strong>
|
||||
<strong>{t('config.obs.warn1')}</strong>
|
||||
<div className="input-group mb-3">
|
||||
<span className="input-group-text">Adresse du serveur</span>
|
||||
<span className="input-group-text">ws://</span>
|
||||
<span className="input-group-text">{t('adresseDuServeur')}</span>
|
||||
<span className="input-group-text">{t('config.obs.ws')}</span>
|
||||
<input type="text" className="form-control" placeholder="127.0.0.1:4455" aria-label=""
|
||||
defaultValue={"127.0.0.1:4455"}/>
|
||||
<span className="input-group-text">/</span>
|
||||
</div>
|
||||
<div className="input-group mb-3">
|
||||
<span className="input-group-text">Mot de passe du serveur</span>
|
||||
<span className="input-group-text">{t('config.obs.motDePasseDuServeur')}</span>
|
||||
<input type="password" className="form-control" placeholder="12345" aria-label=""
|
||||
defaultValue={""}/>
|
||||
</div>
|
||||
<div className="input-group mb-3">
|
||||
<span className="input-group-text">Dossier des resources</span>
|
||||
<span className="input-group-text">{t('config.obs.dossierDesResources')}</span>
|
||||
<input type="text" className="form-control" placeholder="" aria-label="" required/>
|
||||
</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">Exporter</button>
|
||||
<button type="button" className="btn btn-secondary" data-bs-dismiss="modal">{t('fermer')}</button>
|
||||
<button type="submit" className="btn btn-primary" data-bs-dismiss="modal">{t('exporter')}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@ -293,14 +296,14 @@ function Menu({menuActions, compUuid}) {
|
||||
<div className="modal-dialog">
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<h5 className="modal-title">Téléchargement en cours...</h5>
|
||||
<h5 className="modal-title">{t('téléchargementEnCours')}</h5>
|
||||
<button type="button" className="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
<div className="progress">
|
||||
<div id="progressBar" className="progress-bar" role="progressbar" style={{width: "0%"}}></div>
|
||||
</div>
|
||||
<div id="progressText" className="mt-2">Préparation...</div>
|
||||
<div id="progressText" className="mt-2">{t('préparation...')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -314,6 +317,7 @@ function CategoryHeader({cat, setCatId}) {
|
||||
const confirmRef = useRef();
|
||||
const [modal, setModal] = useState({})
|
||||
const [confirm, setConfirm] = useState({})
|
||||
const {t} = useTranslation("cm");
|
||||
|
||||
const {data: cats, setData: setCats} = useRequestWS('getAllCategory', {}, setLoading);
|
||||
const {dispatch} = useWS();
|
||||
@ -368,17 +372,17 @@ function CategoryHeader({cat, setCatId}) {
|
||||
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>
|
||||
<h5 style={{margin: "auto 0.5em auto 0"}}>{t('editionDeLaCatégorie')}</h5>
|
||||
<select className="form-select" onChange={handleCatChange} value={cat?.id || ""}>
|
||||
{cats && cats.sort((a, b) => a.name.localeCompare(b.name)).map(c => (
|
||||
<option key={c.id} value={c.id}>{c.name}</option>))}
|
||||
{cats && <option value={-1}>Nouvelle...</option>}
|
||||
{cats && <option value={-1}>{t('nouvelle...')}</option>}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col" style={{margin: "auto 0", textAlign: "center"}}>
|
||||
{cat &&
|
||||
<div>Type: {(cat.type & 1) !== 0 ? "Poule" : ""}{cat.type === 3 ? " & " : ""}{(cat.type & 2) !== 0 ? "Tournois" : ""} |
|
||||
<div>Type: {(cat.type & 1) !== 0 ? t('poule') : ""}{cat.type === 3 ? " & " : ""}{(cat.type & 2) !== 0 ? t('tournois') : ""} |
|
||||
Zone: {cat.liceName}</div>}
|
||||
</div>
|
||||
<div className="col-auto">
|
||||
@ -409,17 +413,18 @@ function CategoryHeader({cat, setCatId}) {
|
||||
|
||||
function ModalContent({state, setCatId, setConfirm, confirmRef}) {
|
||||
const [name, setName] = useState("")
|
||||
const [lice, setLice] = useState("A")
|
||||
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 {t} = useTranslation("cm");
|
||||
|
||||
const {sendRequest} = useWS();
|
||||
|
||||
useEffect(() => {
|
||||
setName(state.name || "");
|
||||
setLice(state.liceName || "A");
|
||||
setLice(state.liceName || "1");
|
||||
setPoule(((state.type || 1) & 1) !== 0);
|
||||
setTournoi((state.type & 2) !== 0);
|
||||
|
||||
@ -445,13 +450,13 @@ function ModalContent({state, setCatId, setConfirm, confirmRef}) {
|
||||
|
||||
const regex = /^([^;]+;)*[^;]+$/;
|
||||
if (regex.test(lice.trim()) === false) {
|
||||
toast.error("Le format du nom des zones de combat est invalide. Veuillez séparer les noms par des ';'.");
|
||||
toast.error(t('err2'));
|
||||
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é.");
|
||||
toast.error(t('err3'));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -479,8 +484,8 @@ function ModalContent({state, setCatId, setConfirm, confirmRef}) {
|
||||
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)!`,
|
||||
title: t('confirm2.title'),
|
||||
message: t('confirm2.msg'),
|
||||
confirm: () => {
|
||||
const trees2 = build_tree(size, loserMatch)
|
||||
const newTrees = []
|
||||
@ -495,48 +500,30 @@ function ModalContent({state, setCatId, setConfirm, confirmRef}) {
|
||||
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'
|
||||
}
|
||||
toast.promise(sendRequest('updateTrees', {categoryId: state.id, trees: newTrees}), getToastMessage("toast.updateTrees")
|
||||
).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'
|
||||
}
|
||||
)
|
||||
toast.promise(sendRequest('updateCategory', newData), getToastMessage("toast.updateCategory"))
|
||||
})
|
||||
}
|
||||
})
|
||||
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'
|
||||
}
|
||||
)
|
||||
toast.promise(sendRequest('updateCategory', newData), getToastMessage("toast.updateCategory"))
|
||||
}
|
||||
}
|
||||
|
||||
if (nType !== state.type) {
|
||||
let typeStr = "";
|
||||
if ((state.type & 1) !== 0 && (nType & 1) === 0)
|
||||
typeStr += "poule ";
|
||||
typeStr += `${t('poule').toLowerCase()} `;
|
||||
if ((state.type & 2) !== 0 && (nType & 2) === 0)
|
||||
typeStr += "tournoi ";
|
||||
typeStr += `${t('tournoi').toLowerCase()} `;
|
||||
|
||||
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 !`,
|
||||
title: t('confirm3.title'),
|
||||
message: t('confirm3.msg', {typeStr: typeStr.trim()}),
|
||||
confirm: () => {
|
||||
setTimeout(() =>
|
||||
applyChanges(), 500);
|
||||
setTimeout(() => applyChanges(), 500);
|
||||
}
|
||||
})
|
||||
confirmRef.current.click();
|
||||
@ -544,23 +531,17 @@ function ModalContent({state, setCatId, setConfirm, confirmRef}) {
|
||||
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'
|
||||
}
|
||||
toast.promise(sendRequest('createCategory', {
|
||||
name: name.trim(),
|
||||
liceName: lice.trim(),
|
||||
type: nType
|
||||
}), getToastMessage("toast.createCategory")
|
||||
).then(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'
|
||||
}
|
||||
toast.promise(sendRequest('updateTrees', {categoryId: id, trees: trees}), getToastMessage("toast.updateTrees.init")
|
||||
).finally(() => setCatId(id))
|
||||
} else {
|
||||
setCatId(id);
|
||||
@ -580,36 +561,36 @@ function ModalContent({state, setCatId, setConfirm, confirmRef}) {
|
||||
|
||||
return <form onSubmit={handleSubmit}>
|
||||
<div className="modal-header">
|
||||
<h1 className="modal-title fs-5" id="CategorieModalLabel">{state.id === undefined ? "Ajouter" : "Modifier"} une catégorie</h1>
|
||||
<h1 className="modal-title fs-5" id="CategorieModalLabel">{state.id === undefined ? t('ajouter') : t('modifier')} {t('uneCaté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}
|
||||
<label htmlFor="nameInput1" className="form-label">{t('nom')}</label>
|
||||
<input type="text" className="form-control" id="nameInput1" placeholder={t('epéeBouclier')} name="name" value={name}
|
||||
onChange={e => setName(e.target.value)}/>
|
||||
</div>
|
||||
|
||||
<div className="mb-3">
|
||||
<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="A;B" name="zone de combat" value={lice}
|
||||
<label htmlFor="liceInput1" className="form-label"><Trans i18nKey="nomDesZonesDeCombat" ns="cm">t <small>(séparée par des ';')</small></Trans></label>
|
||||
<input type="text" className="form-control" id="liceInput1" placeholder="1;2" name="zone de combat" value={lice}
|
||||
onChange={e => setLice(e.target.value)}/>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="mb-3">
|
||||
<label className="form-label">Type</label>
|
||||
<label className="form-label">{t('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>
|
||||
<label className="form-check-label" htmlFor="switchCheckDefault">{t('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>
|
||||
<label className="form-check-label" htmlFor="switchCheckDefault2">{t('tournoi')}</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -620,14 +601,14 @@ function ModalContent({state, setCatId, setConfirm, confirmRef}) {
|
||||
</div>
|
||||
|
||||
<div className="mb-3">
|
||||
<span>Match pour les perdants du tournoi:</span>
|
||||
<span>{t('matchPourLesPerdantsDuTournoi')}</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
|
||||
{t('tousLesMatchs')}
|
||||
</label>
|
||||
</div>
|
||||
<div className="form-check">
|
||||
@ -636,7 +617,7 @@ function ModalContent({state, setCatId, setConfirm, confirmRef}) {
|
||||
if (e.target.checked) setLoserMatch(1)
|
||||
}}/>
|
||||
<label className="form-check-label" htmlFor="radioDefault2">
|
||||
Demi-finales et finales
|
||||
{t('demi-finalesEtFinales')}
|
||||
</label>
|
||||
</div>
|
||||
<div className="form-check">
|
||||
@ -645,31 +626,26 @@ function ModalContent({state, setCatId, setConfirm, confirmRef}) {
|
||||
if (e.target.checked) setLoserMatch(0)
|
||||
}}/>
|
||||
<label className="form-check-label" htmlFor="radioDefault3">
|
||||
Finales uniquement
|
||||
{t('finalesUniquement')}
|
||||
</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>
|
||||
<button type="button" className="btn btn-secondary" data-bs-dismiss="modal">{t('fermer')}</button>
|
||||
<button type="submit" className="btn btn-primary" data-bs-dismiss="modal">{t('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 !`,
|
||||
title: t('confirm4.title'),
|
||||
message: t('confirm4.msg', {name: state.name}),
|
||||
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'
|
||||
}
|
||||
toast.promise(sendRequest('deleteCategory', state.id), getToastMessage("toast.deleteCategory")
|
||||
).then(() => setCatId(null));
|
||||
}
|
||||
})
|
||||
confirmRef.current.click();
|
||||
}}>Supprimer</button>}
|
||||
}}>{t('supprimer')}</button>}
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import React, {useEffect, useRef, useState} from "react";
|
||||
import {usePubAffDispatch} from "../../../hooks/useExternalWindow.jsx";
|
||||
import {timePrint} from "../../../utils/Tools.js";
|
||||
import {useTranslation} from "react-i18next";
|
||||
|
||||
export function ChronoPanel() {
|
||||
const [config, setConfig] = useState({
|
||||
@ -11,6 +12,7 @@ export function ChronoPanel() {
|
||||
const chronoText = useRef(null)
|
||||
const state = useRef({chronoState: 0, countBlink: 20, lastColor: "#000000", lastTimeStr: "00:00"})
|
||||
const publicAffDispatch = usePubAffDispatch();
|
||||
const {t} = useTranslation("cm");
|
||||
|
||||
const addTime = (time) => setChrono(prev => ({...prev, time: prev.time - time}))
|
||||
const isRunning = () => chrono.startTime !== 0
|
||||
@ -111,11 +113,11 @@ export function ChronoPanel() {
|
||||
onClick={__ => isRunning() ?
|
||||
setChrono(prev => ({...prev, time: prev.time + Date.now() - prev.startTime, startTime: 0})) :
|
||||
setChrono(prev => ({...prev, startTime: Date.now()}))}>
|
||||
{isRunning() ? "Arrêter" : "Démarrer"}</button>
|
||||
{isRunning() ? t('chrono.arrêter') : t('chrono.démarrer')}</button>
|
||||
<button className="btn btn-danger col" onClick={__ => {
|
||||
setChrono(prev => ({...prev, time: 0, startTime: 0}))
|
||||
state.current.chronoState = 0
|
||||
}}>Réinitialiser
|
||||
}}>{t('réinitialiser')}
|
||||
</button>
|
||||
</div>
|
||||
<div className="row" style={{marginTop: "0.5em"}}>
|
||||
@ -125,29 +127,29 @@ export function ChronoPanel() {
|
||||
</div>
|
||||
<div className="col" style={{margin: "auto 0"}}>
|
||||
<div className="row">
|
||||
<button className="btn btn-outline-secondary col-6" onClick={__ => addTime(-10000)}>-10 s</button>
|
||||
<button className="btn btn-outline-secondary col-6" onClick={__ => addTime(10000)}>+10 s</button>
|
||||
<button className="btn btn-outline-secondary col-6" onClick={__ => addTime(-10000)}>{t('chrono.-10S')}</button>
|
||||
<button className="btn btn-outline-secondary col-6" onClick={__ => addTime(10000)}>{t('chrono.+10S')}</button>
|
||||
</div>
|
||||
<div className="row" style={{marginTop: "0.5em"}}>
|
||||
<button className="btn btn-outline-secondary col-6" onClick={__ => addTime(-1000)}>-1 s</button>
|
||||
<button className="btn btn-outline-secondary col-6" onClick={__ => addTime(1000)}>+1 s</button>
|
||||
<button className="btn btn-outline-secondary col-6" onClick={__ => addTime(-1000)}>{t('chrono.-1S')}</button>
|
||||
<button className="btn btn-outline-secondary col-6" onClick={__ => addTime(1000)}>{t('chrono.+1S')}</button>
|
||||
</div>
|
||||
<div className="row" style={{marginTop: "0.5em"}}>
|
||||
<button className="btn btn-outline-secondary col-12" onClick={__ => {
|
||||
const timeStr = prompt("Entrez le temps en s", "0");
|
||||
const timeStr = prompt(t('chrono.entrezLeTempsEnS'), "0");
|
||||
if (timeStr === null)
|
||||
return;
|
||||
addTime(parseInt(timeStr, 10) * 1000);
|
||||
}}>+/- ... s
|
||||
}}>{t('chrono.+/-...S')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row" style={{marginTop: "0.5em"}}>
|
||||
<div className="col-12 col-sm-8" style={{margin: 'auto 0'}}>
|
||||
<div>Temps: {timePrint(config.time)}, pause: {timePrint(config.pause)}</div>
|
||||
<div>{t('chrono.recapTemps', {temps: timePrint(config.time), pause: timePrint(config.pause)})}</div>
|
||||
</div>
|
||||
<button className="btn btn-secondary col" data-bs-toggle="modal" data-bs-target="#timeModal">Définir le temps
|
||||
<button className="btn btn-secondary col" data-bs-toggle="modal" data-bs-target="#timeModal">{t('chrono.définirLeTemps')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@ -155,25 +157,25 @@ export function ChronoPanel() {
|
||||
<div className="modal-dialog">
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<h5 className="modal-title">Edition temps</h5>
|
||||
<h5 className="modal-title">{t('chrono.editionTemps')}</h5>
|
||||
<button type="button" className="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="modal-body">
|
||||
<div className="input-group mb-3">
|
||||
<span className="input-group-text">Durée round</span>
|
||||
<span className="input-group-text">{t('duréeRound')}</span>
|
||||
<input type="text" className="form-control" placeholder="0" aria-label="Min" defaultValue={timePrint(config.time)}/>
|
||||
<span className="input-group-text">(mm:ss)</span>
|
||||
</div>
|
||||
<div className="input-group mb-3">
|
||||
<span className="input-group-text">Durée pause</span>
|
||||
<span className="input-group-text">{t('duréePause')}</span>
|
||||
<input type="text" className="form-control" placeholder="0" aria-label="Min" defaultValue={timePrint(config.pause)}/>
|
||||
<span className="input-group-text">(mm:ss)</span>
|
||||
</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">Valider</button>
|
||||
<button type="button" className="btn btn-secondary" data-bs-dismiss="modal">{t('fermer')}</button>
|
||||
<button type="submit" className="btn btn-primary" data-bs-dismiss="modal">{t('valider')}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@ -6,12 +6,13 @@ 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 {getToastMessage, 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";
|
||||
import "./CMTMatchPanel.css"
|
||||
import {useOBS} from "../../../hooks/useOBS.jsx";
|
||||
import {useTranslation} from "react-i18next";
|
||||
|
||||
function CupImg() {
|
||||
return <img decoding="async" loading="lazy" width={"16"} height={"16"} className="wp-image-1635"
|
||||
@ -24,6 +25,7 @@ export function CategorieSelect({catId, setCatId, menuActions}) {
|
||||
const {data: cats, setData: setCats} = useRequestWS('getAllCategory', {}, setLoading);
|
||||
const {dispatch} = useWS();
|
||||
const {connected, setText} = useOBS();
|
||||
const {t} = useTranslation("cm");
|
||||
|
||||
useEffect(() => {
|
||||
const categoryListener = ({data}) => {
|
||||
@ -55,7 +57,7 @@ export function CategorieSelect({catId, setCatId, menuActions}) {
|
||||
|
||||
return <>
|
||||
<div className="input-group">
|
||||
<h6 style={{margin: "auto 0.5em auto 0"}}>Catégorie</h6>
|
||||
<h6 style={{margin: "auto 0.5em auto 0"}}>{t('catégorie')}</h6>
|
||||
<select className="form-select" onChange={e => setCatId(Number(e.target.value))} value={catId}>
|
||||
{cats && <option value={-1}></option>}
|
||||
{cats && cats.sort((a, b) => a.name.localeCompare(b.name)).map(c => (
|
||||
@ -149,6 +151,7 @@ function CMTMatchPanel({catId, cat, menuActions}) {
|
||||
|
||||
function ListMatch({cat, matches, trees, menuActions}) {
|
||||
const [type, setType] = useState(1);
|
||||
const {t} = useTranslation("cm");
|
||||
|
||||
useEffect(() => {
|
||||
if (!cat)
|
||||
@ -165,12 +168,12 @@ function ListMatch({cat, matches, trees, menuActions}) {
|
||||
<ul className="nav nav-tabs">
|
||||
<li className="nav-item">
|
||||
<div className={"nav-link" + (type === 1 ? " active" : "")} aria-current={(type === 1 ? " page" : "false")}
|
||||
onClick={_ => setType(1)}>Poule
|
||||
onClick={_ => setType(1)}>{t('poule')}
|
||||
</div>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<div className={"nav-link" + (type === 2 ? " active" : "")} aria-current={(type === 2 ? " page" : "false")}
|
||||
onClick={_ => setType(2)}>Tournois
|
||||
onClick={_ => setType(2)}>{t('tournois')}
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
@ -189,8 +192,9 @@ function ListMatch({cat, matches, trees, menuActions}) {
|
||||
|
||||
function MatchList({matches, cat, menuActions}) {
|
||||
const [activeMatch, setActiveMatch] = useState(null)
|
||||
const [lice, setLice] = useState(localStorage.getItem("cm_lice") || "A")
|
||||
const [lice, setLice] = useState(localStorage.getItem("cm_lice") || "1")
|
||||
const publicAffDispatch = usePubAffDispatch();
|
||||
const {t} = useTranslation("cm");
|
||||
|
||||
const liceName = (cat.liceName || "N/A").split(";");
|
||||
const marches2 = matches.filter(m => m.categorie_ord !== -42)
|
||||
@ -241,7 +245,7 @@ function MatchList({matches, cat, menuActions}) {
|
||||
return <>
|
||||
{liceName.length > 1 &&
|
||||
<div className="input-group" style={{maxWidth: "15em", marginTop: "0.5em"}}>
|
||||
<label className="input-group-text" htmlFor="selectLice">Zone de combat</label>
|
||||
<label className="input-group-text" htmlFor="selectLice">{t('zoneDeCombat')}</label>
|
||||
<select className="form-select" id="selectLice" value={lice} onChange={e => {
|
||||
setLice(e.target.value);
|
||||
localStorage.setItem("cm_lice", e.target.value);
|
||||
@ -259,10 +263,10 @@ function MatchList({matches, cat, menuActions}) {
|
||||
<tr>
|
||||
<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">N°</th>
|
||||
<th style={{textAlign: "center", paddingLeft: "0.2em", paddingRight: "0.2em"}} scope="col">{t('no')}</th>
|
||||
<th style={{textAlign: "center"}} scope="col"></th>
|
||||
<th style={{textAlign: "center"}} scope="col">Rouge</th>
|
||||
<th style={{textAlign: "center"}} scope="col">Blue</th>
|
||||
<th style={{textAlign: "center"}} scope="col">{t('rouge')}</th>
|
||||
<th style={{textAlign: "center"}} scope="col">{t('blue')}</th>
|
||||
<th style={{textAlign: "center"}} scope="col"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -288,7 +292,8 @@ function MatchList({matches, cat, menuActions}) {
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{activeMatch && <LoadingProvider><ScorePanel matchId={activeMatch} matchs={matches} match={match} menuActions={menuActions}/></LoadingProvider>}
|
||||
{activeMatch &&
|
||||
<LoadingProvider><ScorePanel matchId={activeMatch} matchs={matches} match={match} menuActions={menuActions}/></LoadingProvider>}
|
||||
</>
|
||||
}
|
||||
|
||||
@ -362,7 +367,8 @@ function BuildTree({treeData, matches, menuActions}) {
|
||||
</div>
|
||||
|
||||
{currentMatch?.matchSelect &&
|
||||
<LoadingProvider><ScorePanel matchId={currentMatch?.matchSelect} matchs={matches} match={match} menuActions={menuActions}/></LoadingProvider>}
|
||||
<LoadingProvider><ScorePanel matchId={currentMatch?.matchSelect} matchs={matches} match={match}
|
||||
menuActions={menuActions}/></LoadingProvider>}
|
||||
</div>
|
||||
}
|
||||
|
||||
@ -379,6 +385,7 @@ function ScorePanel({matchId, matchs, match, menuActions}) {
|
||||
function ScorePanel_({matchId, matchs, match, menuActions, onClickVoid_}) {
|
||||
const {sendRequest} = useWS()
|
||||
const setLoading = useLoadingSwitcher()
|
||||
const {t} = useTranslation("cm");
|
||||
|
||||
const [end, setEnd] = useState(match?.end || false)
|
||||
const [scoreIn, setScoreIn] = useState("")
|
||||
@ -396,13 +403,7 @@ function ScorePanel_({matchId, matchs, match, menuActions, onClickVoid_}) {
|
||||
menuActions.current.saveScore = (scoreRed, scoreBlue) => {
|
||||
const maxRound = (Math.max(...match.scores.map(s => s.n_round), -1) + 1) || 0;
|
||||
const newScore = {n_round: maxRound, s1: scoreRed, s2: scoreBlue};
|
||||
toast.promise(sendRequest('updateMatchScore', {matchId: matchId, ...newScore}),
|
||||
{
|
||||
pending: 'Sauvegarde du score...',
|
||||
success: 'Score sauvegardé !',
|
||||
error: 'Erreur lors de la sauvegarde du score'
|
||||
}
|
||||
);
|
||||
toast.promise(sendRequest('updateMatchScore', {matchId: matchId, ...newScore}), getToastMessage("toast.updateMatchScore"));
|
||||
}
|
||||
return () => menuActions.current.saveScore = undefined;
|
||||
}, [matchId])
|
||||
@ -443,8 +444,6 @@ function ScorePanel_({matchId, matchs, match, menuActions, onClickVoid_}) {
|
||||
|
||||
const score = matchs.find(m => m.id === matchId).scores.find(s => s.n_round === round);
|
||||
|
||||
console.log("Updating score", matchId, round, comb, scoreIn_, score);
|
||||
|
||||
let newScore;
|
||||
if (score) {
|
||||
if (comb === 1)
|
||||
@ -460,8 +459,6 @@ function ScorePanel_({matchId, matchs, match, menuActions, onClickVoid_}) {
|
||||
return
|
||||
}
|
||||
|
||||
console.log("Updating score", matchId, newScore);
|
||||
|
||||
setLoading(1)
|
||||
sendRequest('updateMatchScore', {matchId: matchId, ...newScore})
|
||||
.finally(() => {
|
||||
@ -485,7 +482,7 @@ function ScorePanel_({matchId, matchs, match, menuActions, onClickVoid_}) {
|
||||
|
||||
if (end) {
|
||||
if (win(match?.scores) === 0 && match.categorie_ord === -42) {
|
||||
toast.error("Impossible de terminer un match nul en tournois.");
|
||||
toast.error(t('score.err1'));
|
||||
setEnd(false);
|
||||
return;
|
||||
}
|
||||
@ -529,21 +526,18 @@ function ScorePanel_({matchId, matchs, match, menuActions, onClickVoid_}) {
|
||||
const o = [...tooltipTriggerList]
|
||||
o.map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl))
|
||||
|
||||
const tt = "Score speciaux : <br/>" +
|
||||
"-997 : disqualifié <br/>" +
|
||||
"-998 : absent <br/>" +
|
||||
"-999 : forfait"
|
||||
const tt = t('score.spe')
|
||||
|
||||
const maxRound = (match?.scores) ? (Math.max(...match.scores.map(s => s.n_round), -1) + 1) : 0;
|
||||
return <div ref={tableRef} className="col" style={{position: "relative"}}>
|
||||
<h6>Scores <FontAwesomeIcon icon={faCircleQuestion} role="button" data-bs-toggle="tooltip" data-bs-placement="right" data-bs-title={tt}
|
||||
<h6>{t('scores')} <FontAwesomeIcon icon={faCircleQuestion} role="button" data-bs-toggle="tooltip" data-bs-placement="right" data-bs-title={tt}
|
||||
data-bs-html="true"/></h6>
|
||||
<table className="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style={{textAlign: "center"}} scope="col">Manche</th>
|
||||
<th style={{textAlign: "center", minWidth: "4em"}} scope="col">Rouge</th>
|
||||
<th style={{textAlign: "center", minWidth: "4em"}} scope="col">Bleu</th>
|
||||
<th style={{textAlign: "center"}} scope="col">{t('manche')}</th>
|
||||
<th style={{textAlign: "center", minWidth: "4em"}} scope="col">{t('rouge')}</th>
|
||||
<th style={{textAlign: "center", minWidth: "4em"}} scope="col">{t('bleu')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="table-group-divider">
|
||||
@ -568,7 +562,7 @@ function ScorePanel_({matchId, matchs, match, menuActions, onClickVoid_}) {
|
||||
<div className="form-check" style={{display: "inline-block"}}>
|
||||
<input className="form-check-input" type="checkbox" id="checkboxEnd" name="checkboxEnd" checked={end}
|
||||
onChange={e => setEnd(e.target.checked)}/>
|
||||
<label className="form-check-label" htmlFor="checkboxEnd">Terminé</label>
|
||||
<label className="form-check-label" htmlFor="checkboxEnd">{t('terminé')}</label>
|
||||
</div>
|
||||
</div>
|
||||
<input ref={inputRef} type="number" className="form-control" style={{position: "absolute", top: 0, left: 0, display: "none"}} min="-999"
|
||||
|
||||
@ -2,12 +2,14 @@ import {useEffect, useState} from "react";
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
import {faChevronDown, faChevronUp} from "@fortawesome/free-solid-svg-icons";
|
||||
import {usePubAffDispatch} from "../../../hooks/useExternalWindow.jsx";
|
||||
import {useTranslation} from "react-i18next";
|
||||
|
||||
export function PointPanel({menuActions}) {
|
||||
const [revers, setRevers] = useState(false)
|
||||
const [scoreRouge, setScoreRouge] = useState(0)
|
||||
const [scoreBleu, setScoreBleu] = useState(0)
|
||||
const publicAffDispatch = usePubAffDispatch()
|
||||
const {t} = useTranslation("cm");
|
||||
|
||||
menuActions.current.switchSore = () => {
|
||||
setRevers(!revers)
|
||||
@ -44,8 +46,8 @@ export function PointPanel({menuActions}) {
|
||||
{revers && red}
|
||||
|
||||
<div className="col row align-items-center">
|
||||
<button className="btn btn-danger" onClick={handleReset}>Réinitialiser</button>
|
||||
<button className="btn btn-success" onClick={handleSave}>Sauvegarder</button>
|
||||
<button className="btn btn-danger" onClick={handleReset}>{t('réinitialiser')}</button>
|
||||
<button className="btn btn-success" onClick={handleSave}>{t('sauvegarder')}</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@ -14,12 +14,14 @@ import {PointPanel} from "./CMTPoint.jsx";
|
||||
import {importOBSConfiguration, OBSProvider, useOBS} from "../../../hooks/useOBS.jsx";
|
||||
import {SimpleIconsOBS} from "../../../assets/SimpleIconsOBS.ts";
|
||||
import {toast} from "react-toastify";
|
||||
import {useTranslation} from "react-i18next";
|
||||
|
||||
export function CMTable() {
|
||||
const combDispatch = useCombsDispatch()
|
||||
const [catId, setCatId] = useState(-1);
|
||||
const menuActions = useRef({});
|
||||
const {data} = useRequestWS("getRegister", null)
|
||||
const {t} = useTranslation("cm");
|
||||
|
||||
useEffect(() => {
|
||||
if (data === null)
|
||||
@ -33,14 +35,14 @@ export function CMTable() {
|
||||
<div className="row">
|
||||
<div className="col-md-12 col-lg">
|
||||
<div className="card mb-3">
|
||||
<div className="card-header">Chronomètre</div>
|
||||
<div className="card-header">{t('chronomètre')}</div>
|
||||
<div className="card-body">
|
||||
<ChronoPanel/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="card mb-3">
|
||||
<div className="card-header">Score</div>
|
||||
<div className="card-header">{t('score')}</div>
|
||||
<div className="card-body">
|
||||
<PointPanel menuActions={menuActions}/>
|
||||
</div>
|
||||
@ -48,7 +50,7 @@ export function CMTable() {
|
||||
</div>
|
||||
<div className="col-md-12 col-xl-6 col-xxl-5">
|
||||
<div className="card mb-3">
|
||||
<div className="card-header">Matches</div>
|
||||
<div className="card-header">{t('matches')}</div>
|
||||
<div className="card-body">
|
||||
<CategorieSelect catId={catId} setCatId={setCatId} menuActions={menuActions}/>
|
||||
</div>
|
||||
@ -74,6 +76,7 @@ function Menu({menuActions}) {
|
||||
const {connected, connect, disconnect} = useOBS();
|
||||
const longPress = useRef({time: null, timer: null, button: null});
|
||||
const obsModal = useRef(null);
|
||||
const {t} = useTranslation("cm");
|
||||
|
||||
const externalWindow = useRef(null)
|
||||
const containerEl = useRef(document.createElement("div"))
|
||||
@ -154,7 +157,7 @@ function Menu({menuActions}) {
|
||||
connect("ws://" + config.adresse + "/", config.password, config.assets_dir);
|
||||
})
|
||||
.catch(() => {
|
||||
toast.error("Aucune configuration OBS trouvée, veuillez en importer une");
|
||||
toast.error(t('aucuneConfigurationObs'));
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -182,21 +185,21 @@ function Menu({menuActions}) {
|
||||
<div className="vr" style={{margin: "0 0.5em", height: "100%"}}></div>
|
||||
<FontAwesomeIcon icon={faArrowRightArrowLeft} size="xl" style={{color: "#6c757d", cursor: "pointer", marginRight: "0.25em"}}
|
||||
onClick={handleSwitchScore} data-bs-toggle="tooltip2" data-bs-placement="top"
|
||||
data-bs-title="Inverser la position des combattants sur cette écran"/>
|
||||
data-bs-title={t('ttm.table.inverserLaPosition')}/>
|
||||
<FontAwesomeIcon icon={SimpleIconsOBS} size="xl"
|
||||
style={{color: connected ? "#00c700" : "#6c757d", cursor: "pointer"}}
|
||||
onMouseDown={() => longPressDown("obs")}
|
||||
onMouseUp={() => longPressUp("obs")}
|
||||
data-bs-toggle="tooltip2" data-bs-placement="top"
|
||||
data-bs-title="Clique court : Charger la configuration et se connecter. Clique long : Configuration de la lice"/>
|
||||
data-bs-title={t('ttm.table.obs')}/>
|
||||
<div className="vr" style={{margin: "0 0.5em", height: "100%"}}></div>
|
||||
<FontAwesomeIcon icon={faDisplay} size="xl"
|
||||
style={{color: showPubAff ? "#00c700" : "#6c757d", cursor: "pointer", marginRight: "0.25em"}}
|
||||
onClick={handlePubAff}
|
||||
data-bs-toggle="tooltip2" data-bs-placement="top" data-bs-title="Ouvrir l'affichage public"/>
|
||||
data-bs-toggle="tooltip2" data-bs-placement="top" data-bs-title={t('ttm.table.pub_aff')}/>
|
||||
<FontAwesomeIcon icon={SimpleIconsScore} size="xl" style={{color: showScore ? "#00c700" : "#6c757d", cursor: "pointer"}}
|
||||
onClick={handleScore}
|
||||
data-bs-toggle="tooltip2" data-bs-placement="top" data-bs-title="Afficher les scores sur l'affichage public"/>
|
||||
data-bs-toggle="tooltip2" data-bs-placement="top" data-bs-title={t('ttm.table.pub_score')}/>
|
||||
</>, document.getElementById("actionMenu"))}
|
||||
{externalWindow.current && createPortal(<PubAffWindow document={externalWindow.current.document}/>, containerEl.current)}
|
||||
|
||||
@ -213,15 +216,15 @@ function Menu({menuActions}) {
|
||||
<form onSubmit={handleOBSSubmit}>
|
||||
<div className="modal-body">
|
||||
<div className="input-group mb-3">
|
||||
<span className="input-group-text">Préfix des sources</span>
|
||||
<span className="input-group-text">{t('obs.préfixDesSources')}</span>
|
||||
<span className="input-group-text">sub</span>
|
||||
<input type="text" className="form-control" placeholder="1" aria-label="" size={1} minLength={1} maxLength={1}
|
||||
defaultValue={localStorage.getItem("obs_prefix") || "1"} required/>
|
||||
</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">Sauvegarde</button>
|
||||
<button type="button" className="btn btn-secondary" data-bs-dismiss="modal">{t('fermer')}</button>
|
||||
<button type="submit" className="btn btn-primary" data-bs-dismiss="modal">{t('sauvegarde')}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@ -15,7 +15,8 @@ import {CSS} from '@dnd-kit/utilities';
|
||||
import {toast} from "react-toastify";
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
import {faTrash} from "@fortawesome/free-solid-svg-icons";
|
||||
import {win} from "../../../utils/Tools.js";
|
||||
import {getToastMessage, win} from "../../../utils/Tools.js";
|
||||
import {useTranslation} from "react-i18next";
|
||||
|
||||
const vite_url = import.meta.env.VITE_URL;
|
||||
|
||||
@ -169,6 +170,7 @@ function AddComb({groups, setGroups, removeGroup, menuActions}) {
|
||||
const combDispatch = useCombsDispatch()
|
||||
const {dispatch} = useWS()
|
||||
const [modalId, setModalId] = useState(null)
|
||||
const {t} = useTranslation("cm");
|
||||
|
||||
useEffect(() => {
|
||||
const sendRegister = ({data}) => {
|
||||
@ -218,7 +220,7 @@ function AddComb({groups, setGroups, removeGroup, menuActions}) {
|
||||
return <>
|
||||
<GroupsList groups={groups} setModalId={setModalId}/>
|
||||
<button type="button" className="btn btn-primary mt-3 w-100" data-bs-toggle="modal" data-bs-target="#selectCombModal"
|
||||
disabled={data === null}>Ajouter des combattants
|
||||
disabled={data === null}>{t('ajouterDesCombattants')}
|
||||
</button>
|
||||
|
||||
<div className="modal fade" id="selectCombModal" tabIndex="-1" aria-labelledby="selectCombModalLabel" aria-hidden="true">
|
||||
@ -241,6 +243,7 @@ function AddComb({groups, setGroups, removeGroup, menuActions}) {
|
||||
|
||||
function GroupsList({groups, setModalId}) {
|
||||
const {getComb} = useCombs();
|
||||
const {t} = useTranslation("cm");
|
||||
|
||||
const groups2 = groups.map(g => {
|
||||
const comb = getComb(g.id);
|
||||
@ -261,7 +264,7 @@ function GroupsList({groups, setModalId}) {
|
||||
return <>
|
||||
{Object.keys(groups2).map((poule) => (
|
||||
<div key={poule} className="mb-3">
|
||||
<h5>{poule !== '-' ? "Poule: " + poule : "Sans poule"}</h5>
|
||||
<h5>{poule !== '-' ? (t('poule') +" : " + poule) : t('sansPoule')}</h5>
|
||||
<ol className="list-group list-group-numbered">
|
||||
{groups2[poule].map((comb) => (
|
||||
<li key={comb.id} className="list-group-item list-group-item-action d-flex justify-content-between align-items-start"
|
||||
@ -279,6 +282,7 @@ function GroupsList({groups, setModalId}) {
|
||||
function GroupModalContent({combId, groups, setGroups, removeGroup}) {
|
||||
const inputRef = useRef(null);
|
||||
const [pouleInput, setPouleInput] = useState(groups.find(g => g.id === combId)?.poule || "")
|
||||
const {t} = useTranslation("cm");
|
||||
|
||||
useEffect(() => {
|
||||
setPouleInput(groups.find(g => g.id === combId)?.poule || "")
|
||||
@ -308,7 +312,7 @@ function GroupModalContent({combId, groups, setGroups, removeGroup}) {
|
||||
|
||||
return <>
|
||||
<div className="modal-header">
|
||||
<h5 className="modal-title">Poule pour: <CombName combId={combId}/></h5>
|
||||
<h5 className="modal-title">{t('poulePour')}<CombName combId={combId}/></h5>
|
||||
<button type="button" className="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
@ -319,11 +323,11 @@ function GroupModalContent({combId, groups, setGroups, removeGroup}) {
|
||||
}}/>
|
||||
</div>
|
||||
<div className="modal-footer">
|
||||
<button type="button" className="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
<button type="button" className="btn btn-primary" onClick={handleClick}>Enregister</button>
|
||||
<button type="button" className="btn btn-secondary" data-bs-dismiss="modal">{t('fermer')}</button>
|
||||
<button type="button" className="btn btn-primary" onClick={handleClick}>{t('enregister')}</button>
|
||||
<button type="button" className="btn btn-danger" data-bs-dismiss="modal" onClick={() => {
|
||||
removeGroup(combId)
|
||||
}}>Supprimer
|
||||
}}>{t('supprimer')}
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
@ -334,6 +338,7 @@ function ListMatch({cat, matches, groups, reducer}) {
|
||||
const {sendRequest} = useWS();
|
||||
const [type, setType] = useState(1);
|
||||
const bthRef = useRef(null);
|
||||
const {t} = useTranslation("cm");
|
||||
|
||||
useEffect(() => {
|
||||
if ((cat.type & type) === 0)
|
||||
@ -369,18 +374,13 @@ function ListMatch({cat, matches, groups, reducer}) {
|
||||
matchOrderToUpdate: Object.fromEntries(matchOrderToUpdate),
|
||||
matchPouleToUpdate: Object.fromEntries(matchPouleToUpdate),
|
||||
matchesToRemove: matchesToRemove.map(m => m.id)
|
||||
}),
|
||||
{
|
||||
pending: 'Création des matchs en cours...',
|
||||
success: 'Matchs créés avec succès !',
|
||||
error: 'Erreur lors de la création des matchs',
|
||||
})
|
||||
}), getToastMessage("toast.matchs.create"))
|
||||
.finally(() => {
|
||||
console.log("Finished creating matches");
|
||||
})
|
||||
|
||||
} catch (e) {
|
||||
toast.error("Erreur lors de la création des matchs: " + e.message);
|
||||
toast.error(t('erreurLorsDeLaCréationDesMatchs') + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
@ -389,12 +389,12 @@ function ListMatch({cat, matches, groups, reducer}) {
|
||||
<ul className="nav nav-tabs">
|
||||
<li className="nav-item">
|
||||
<div className={"nav-link" + (type === 1 ? " active" : "")} aria-current={(type === 1 ? " page" : "false")}
|
||||
onClick={_ => setType(1)}>Poule
|
||||
onClick={_ => setType(1)}>{t('poule')}
|
||||
</div>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<div className={"nav-link" + (type === 2 ? " active" : "")} aria-current={(type === 2 ? " page" : "false")}
|
||||
onClick={_ => setType(2)}>Tournois
|
||||
onClick={_ => setType(2)}>{t('tournois')}
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
@ -402,7 +402,7 @@ function ListMatch({cat, matches, groups, reducer}) {
|
||||
|
||||
{type === 1 && <>
|
||||
<MatchList matches={matches} groups={groups} cat={cat} reducer={reducer}/>
|
||||
<button className="btn btn-primary float-end" onClick={handleCreatMatch}>Créer les matchs</button>
|
||||
<button className="btn btn-primary float-end" onClick={handleCreatMatch}>{t('créerLesMatchs')}</button>
|
||||
</>}
|
||||
|
||||
{type === 2 && <>
|
||||
@ -414,21 +414,21 @@ function ListMatch({cat, matches, groups, reducer}) {
|
||||
<div className="modal-dialog">
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<h4 className="modal-title" id="makeMatchModeLabel">Attention</h4>
|
||||
<h4 className="modal-title" id="makeMatchModeLabel">{t('attention')}</h4>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
Il y a déjà des matchs dans cette poule, que voulez-vous faire avec ?
|
||||
{t('msg1')}
|
||||
</div>
|
||||
<div className="modal-footer">
|
||||
<div className="btn-group" role="group" aria-label="Basic mixed styles example">
|
||||
<button className="btn btn-primary" data-dismiss="modal" data-bs-dismiss="modal" onClick={() => recalculateMatch(2)}>
|
||||
Tout conserver
|
||||
{t('toutConserver')}
|
||||
</button>
|
||||
<button className="btn btn-success" data-bs-dismiss="modal" onClick={() => recalculateMatch(1)}>
|
||||
Conserver uniquement les matchs terminés
|
||||
{t('conserverUniquementLesMatchsTerminés')}
|
||||
</button>
|
||||
<button className="btn btn-danger" data-bs-dismiss="modal" onClick={() => recalculateMatch(0)}>
|
||||
Ne rien conserver
|
||||
{t('neRienConserver')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -447,6 +447,7 @@ function MatchList({matches, cat, groups, reducer}) {
|
||||
const [combSelect, setCombSelect] = useState(0)
|
||||
const [combC1nm, setCombC1nm] = useState(null)
|
||||
const [combC2nm, setCombC2nm] = useState(null)
|
||||
const {t} = useTranslation("cm");
|
||||
|
||||
const liceName = (cat.liceName || "N/A").split(";");
|
||||
const marches2 = matches.filter(m => m.categorie_ord !== -42)
|
||||
@ -474,7 +475,7 @@ function MatchList({matches, cat, groups, reducer}) {
|
||||
if (!combC1nm || !combC2nm)
|
||||
return;
|
||||
if (combC1nm === combC2nm) {
|
||||
toast.error("Un combattant ne peut pas s'affronter lui-même !");
|
||||
toast.error(t('err1'));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -525,7 +526,7 @@ function MatchList({matches, cat, groups, reducer}) {
|
||||
}
|
||||
|
||||
if (data.c1 === data.c2) {
|
||||
toast.error("Un combattant ne peut pas s'affronter lui-même !");
|
||||
toast.error(t('err1'));
|
||||
onClickVoid()
|
||||
return;
|
||||
}
|
||||
@ -543,7 +544,7 @@ function MatchList({matches, cat, groups, reducer}) {
|
||||
if (!match)
|
||||
return;
|
||||
|
||||
if (!confirm("Ce match a déjà des résultats, êtes-vous sûr de vouloir le supprimer ?"))
|
||||
if (!confirm(t('confirm1')))
|
||||
return;
|
||||
|
||||
setLoading(1)
|
||||
@ -581,14 +582,14 @@ function MatchList({matches, cat, groups, reducer}) {
|
||||
<table className="table table-striped">
|
||||
<thead>
|
||||
<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">Poule</th>
|
||||
<th style={{textAlign: "center", paddingLeft: "0.2em", paddingRight: "0.2em"}} scope="col">Zone</th>
|
||||
<th style={{textAlign: "center", paddingLeft: "0.2em", paddingRight: "0.2em"}} scope="col">{t('no')}</th>
|
||||
<th style={{textAlign: "center", paddingLeft: "0.2em", paddingRight: "0.2em"}} scope="col">{t('poule')}</th>
|
||||
<th style={{textAlign: "center", paddingLeft: "0.2em", paddingRight: "0.2em"}} scope="col">{t('zone')}</th>
|
||||
<th style={{textAlign: "center"}} scope="col"></th>
|
||||
<th style={{textAlign: "center"}} scope="col">Rouge</th>
|
||||
<th style={{textAlign: "center"}} scope="col">Blue</th>
|
||||
<th style={{textAlign: "center"}} scope="col">{t('rouge')}</th>
|
||||
<th style={{textAlign: "center"}} scope="col">{t('blue')}</th>
|
||||
<th style={{textAlign: "center"}} scope="col"></th>
|
||||
<th style={{textAlign: "center"}} scope="col">Résultat</th>
|
||||
<th style={{textAlign: "center"}} scope="col">{t('résultat')}</th>
|
||||
<th style={{textAlign: "center"}} scope="col"></th>
|
||||
<th style={{textAlign: "center"}} scope="col"></th>
|
||||
</tr>
|
||||
@ -637,7 +638,7 @@ function MatchList({matches, cat, groups, reducer}) {
|
||||
|
||||
<select ref={selectRef} className="form-select" style={{position: "absolute", top: 0, left: 0, display: "none"}}
|
||||
value={combSelect} onChange={e => setCombSelect(Number(e.target.value))}>
|
||||
<option value={0}>-- Sélectionner un combattant --</option>
|
||||
<option value={0}>{t('--SélectionnerUnCombattant--')}</option>
|
||||
{combsIDs.map((combId) => (
|
||||
<option key={combId} value={combId}><CombName combId={combId}/></option>
|
||||
))}
|
||||
@ -683,6 +684,7 @@ function BuildTree({treeData, matches, groups}) {
|
||||
const {getComb} = useCombs()
|
||||
const {sendRequest} = useWS();
|
||||
const setLoading = useLoadingSwitcher()
|
||||
const {t} = useTranslation("cm");
|
||||
|
||||
function parseTree(data_in) {
|
||||
if (data_in?.data == null)
|
||||
@ -774,7 +776,7 @@ function BuildTree({treeData, matches, groups}) {
|
||||
<DrawGraph root={initTree(treeData)} scrollRef={scrollRef} onMatchClick={onMatchClick} onClickVoid={onClickVoid}/>
|
||||
<select ref={selectRef} className="form-select" style={{position: "absolute", top: 0, left: 0, display: "none"}}
|
||||
value={combSelect} onChange={e => setCombSelect(Number(e.target.value))}>
|
||||
<option value={0}>-- Sélectionner un combattant --</option>
|
||||
<option value={0}>{t('--SélectionnerUnCombattant--')}</option>
|
||||
{combsIDs.map((combId) => (
|
||||
<option key={combId} value={combId}><CombName combId={combId}/></option>
|
||||
))}
|
||||
|
||||
@ -9,12 +9,14 @@ import {CMTable} from "./CMTable.jsx";
|
||||
import {ThreeDots} from "react-loader-spinner";
|
||||
import {AxiosError} from "../../../components/AxiosError.jsx";
|
||||
import {useFetch} from "../../../hooks/useFetch.js";
|
||||
import {useTranslation} from "react-i18next";
|
||||
|
||||
const vite_url = import.meta.env.VITE_URL;
|
||||
|
||||
export default function CompetitionManagerRoot() {
|
||||
const {t} = useTranslation("cm");
|
||||
return <>
|
||||
<h1>Compétition manager</h1>
|
||||
<h1>{t('compétitionManager')}</h1>
|
||||
<LoadingProvider>
|
||||
<Routes>
|
||||
<Route path="/" element={<Home/>}/>
|
||||
@ -40,9 +42,10 @@ function Home() {
|
||||
}
|
||||
|
||||
function MakeCentralPanel({data, navigate}) {
|
||||
const {t} = useTranslation("cm");
|
||||
return <>
|
||||
<div className="mb-4">
|
||||
<h4>Compétition:</h4>
|
||||
<h4>{t('compétition')}:</h4>
|
||||
<div className="list-group">
|
||||
{data.sort((a, b) => new Date(b.date.split('T')[0]) - new Date(a.date.split('T')[0])).map((o) => (
|
||||
<li className="list-group-item list-group-item-action" key={o.id}
|
||||
@ -77,6 +80,7 @@ function HomeComp() {
|
||||
function WSStatus({setPerm}) {
|
||||
const [inWait, setInWait] = useState(false)
|
||||
const {isReady, wait_length, welcomeData} = useWS();
|
||||
const {t} = useTranslation("cm");
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setInterval(() => {
|
||||
@ -91,7 +95,7 @@ function WSStatus({setPerm}) {
|
||||
|
||||
return <div className="row" style={{marginRight: "inherit"}}>
|
||||
<h2 className="col">{welcomeData.name}</h2>
|
||||
<div className="col-auto" style={{margin: "auto 0", padding: 0}}>Serveur: <ColoredCircle
|
||||
<div className="col-auto" style={{margin: "auto 0", padding: 0}}>{t('serveur')}: <ColoredCircle
|
||||
color={isReady ? (inWait ? "#ffad32" : "#00c700") : "#e50000"}/>
|
||||
</div>
|
||||
<div className="col-auto " id="actionMenu" style={{verticalAlign: "center", textAlign: "center", margin: "auto 0", padding: 0}}></div>
|
||||
@ -100,16 +104,17 @@ function WSStatus({setPerm}) {
|
||||
|
||||
function Home2({perm}) {
|
||||
const nav = useNavigate();
|
||||
const {t} = useTranslation("cm");
|
||||
|
||||
return <div className="row">
|
||||
<h4 className="col-auto" style={{margin: "auto 0"}}>Sélectionne les modes d'affichage</h4>
|
||||
<h4 className="col-auto" style={{margin: "auto 0"}}>{t('sélectionneLesModesDaffichage')}</h4>
|
||||
<div className="col">
|
||||
{perm === "ADMIN" && <>
|
||||
<button className="btn btn-primary" onClick={() => nav("table")}>Secrétariats de lice</button>
|
||||
<button className="btn btn-primary ms-3" onClick={() => nav("admin")}>Administration</button>
|
||||
<button className="btn btn-primary" onClick={() => nav("table")}>{t('secrétariatsDeLice')}</button>
|
||||
<button className="btn btn-primary ms-3" onClick={() => nav("admin")}>{t('administration')}</button>
|
||||
</>}
|
||||
{perm === "TABLE" && <>
|
||||
<button className="btn btn-primary" onClick={() => nav("table")}>Secrétariats de lice</button>
|
||||
<button className="btn btn-primary" onClick={() => nav("table")}>{t('secrétariatsDeLice')}</button>
|
||||
</>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -3,6 +3,7 @@ import {usePubAffState} from "../../../hooks/useExternalWindow.jsx";
|
||||
import {SmartLogoBackgroundMemo} from "../../../components/SmartLogoBackground.jsx";
|
||||
import {useMemo, useRef} from 'react';
|
||||
import {useWS} from "../../../hooks/useWS.jsx";
|
||||
import {useTranslation} from "react-i18next";
|
||||
|
||||
const vite_url = import.meta.env.VITE_URL;
|
||||
|
||||
@ -17,6 +18,7 @@ export function PubAffWindow({document}) {
|
||||
const chronoText = useRef(null)
|
||||
const state2 = useRef({lastColor: "#ffffff", lastTimeStr: "--:--"})
|
||||
const state = usePubAffState();
|
||||
const {t} = useTranslation("cm");
|
||||
|
||||
document.title = "Affichage Public";
|
||||
document.body.className = "bg-dark text-white overflow-hidden";
|
||||
@ -55,24 +57,24 @@ export function PubAffWindow({document}) {
|
||||
<div className="row" style={noMP}>
|
||||
<div className="col" style={noMP}>
|
||||
<CombDisplay combId={state?.c1} background={"red"}>
|
||||
<span className="position-absolute top-0 start-0 translate-middle-y" style={text1Style}>Actuel</span>
|
||||
<span className="position-absolute top-0 start-0 translate-middle-y" style={text1Style}>{t('actuel')}</span>
|
||||
</CombDisplay>
|
||||
</div>
|
||||
<div className="col" style={noMP}>
|
||||
<CombDisplay combId={state?.c2} background={"blue"}>
|
||||
<span className="position-absolute bottom-0 start-0 translate-middle-x" style={text2Style}>contre</span>
|
||||
<span className="position-absolute bottom-0 start-0 translate-middle-x" style={text2Style}>{t('contre')}</span>
|
||||
</CombDisplay>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row" style={noMP}>
|
||||
<div className="col" style={noMP}>
|
||||
<CombDisplay combId={state?.next?.[0]?.c1} background={"red"}>
|
||||
<span className="position-absolute top-0 start-0 translate-middle-y" style={text1Style}>Suivant</span>
|
||||
<span className="position-absolute top-0 start-0 translate-middle-y" style={text1Style}>{t('suivant')}</span>
|
||||
</CombDisplay>
|
||||
</div>
|
||||
<div className="col" style={noMP}>
|
||||
<CombDisplay combId={state?.next?.[0]?.c2} background={"blue"}>
|
||||
<span className="position-absolute bottom-0 start-0 translate-middle-x" style={text2Style}>contre</span>
|
||||
<span className="position-absolute bottom-0 start-0 translate-middle-x" style={text2Style}>{t('contre')}</span>
|
||||
</CombDisplay>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -3,6 +3,7 @@ import {useEffect, useReducer, useState} from "react";
|
||||
import {CatList, getCatName} from "../../../utils/Tools.js";
|
||||
import {CombName} from "../../../hooks/useComb.jsx";
|
||||
import {useWS} from "../../../hooks/useWS.jsx";
|
||||
import {useTranslation} from "react-i18next";
|
||||
|
||||
function SelectReducer(state, action) {
|
||||
switch (action.type) {
|
||||
@ -54,6 +55,7 @@ function SelectReducer(state, action) {
|
||||
|
||||
export function SelectCombModalContent({data, setGroups}) {
|
||||
const country = useCountries('fr')
|
||||
const {t} = useTranslation("cm");
|
||||
const {dispatch} = useWS()
|
||||
const [dispo, dispoReducer] = useReducer(SelectReducer, {})
|
||||
const [select, selectReducer] = useReducer(SelectReducer, {})
|
||||
@ -153,20 +155,20 @@ export function SelectCombModalContent({data, setGroups}) {
|
||||
|
||||
return <>
|
||||
<div className="modal-header">
|
||||
<h1 className="modal-title fs-5" id="CategorieModalLabel">Sélectionner des combatants</h1>
|
||||
<h1 className="modal-title fs-5" id="CategorieModalLabel">{t('select.sélectionnerDesCombatants')}</h1>
|
||||
<button type="button" className="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
<div className="d-flex flex-wrap justify-content-around mb-1">
|
||||
<div className="col-md-5">
|
||||
<label htmlFor="input4" className="form-label">Recherche</label>
|
||||
<label htmlFor="input4" className="form-label">{t('select.recherche')}</label>
|
||||
<input type="text" className="form-control" id="input4" value={search} onChange={(e) => setSearch(e.target.value)}/>
|
||||
</div>
|
||||
|
||||
<div style={{width: "12em"}}>
|
||||
<label htmlFor="inputState0" className="form-label">Pays</label>
|
||||
<label htmlFor="inputState0" className="form-label">{t('pays')}</label>
|
||||
<select id="inputState0" className="form-select" value={country_} onChange={(e) => setCountry_(e.target.value)}>
|
||||
<option value={""}>-- Tous --</option>
|
||||
<option value={""}>{t('--Tous--')}</option>
|
||||
{country && Object.keys(country).sort((a, b) => {
|
||||
if (a < b) return -1
|
||||
if (a > b) return 1
|
||||
@ -178,9 +180,9 @@ export function SelectCombModalContent({data, setGroups}) {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="inputState1" className="form-label">Club</label>
|
||||
<label htmlFor="inputState1" className="form-label">{t('club')}</label>
|
||||
<select id="inputState1" className="form-select" value={club} onChange={(e) => setClub(e.target.value)}>
|
||||
<option value={""}>-- Tous --</option>
|
||||
<option value={""}>{t('--Tous--')}</option>
|
||||
{clubList.sort((a, b) => a.localeCompare(b)).map((club) => (
|
||||
<option key={club} value={club}>{club}</option>))}
|
||||
</select>
|
||||
@ -189,50 +191,50 @@ export function SelectCombModalContent({data, setGroups}) {
|
||||
|
||||
<div className="d-flex flex-wrap justify-content-around mb-1">
|
||||
<div>
|
||||
<label className="form-label">Genre</label>
|
||||
<label className="form-label">{t('genre')}</label>
|
||||
<div className="d-flex align-items-center">
|
||||
<div className="form-check" style={{marginRight: '10px'}}>
|
||||
<input className="form-check-input" type="checkbox" id="gridCheck" checked={gender.H}
|
||||
onChange={e => setGender((prev) => {
|
||||
return {...prev, H: e.target.checked}
|
||||
})}/>
|
||||
<label className="form-check-label" htmlFor="gridCheck">H</label>
|
||||
<label className="form-check-label" htmlFor="gridCheck">{t('genre.h')}</label>
|
||||
</div>
|
||||
<div className="form-check" style={{marginRight: '10px'}}>
|
||||
<input className="form-check-input" type="checkbox" id="gridCheck2" checked={gender.F}
|
||||
onChange={e => setGender((prev) => {
|
||||
return {...prev, F: e.target.checked}
|
||||
})}/>
|
||||
<label className="form-check-label" htmlFor="gridCheck2">F</label>
|
||||
<label className="form-check-label" htmlFor="gridCheck2">{t('genre.f')}</label>
|
||||
</div>
|
||||
<div className="form-check">
|
||||
<input className="form-check-input" type="checkbox" id="gridCheck3" checked={gender.NA}
|
||||
onChange={e => setGender((prev) => {
|
||||
return {...prev, NA: e.target.checked}
|
||||
})}/>
|
||||
<label className="form-check-label" htmlFor="gridCheck3">NA</label>
|
||||
<label className="form-check-label" htmlFor="gridCheck3">{t('genre.na')}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="inputState2" className="form-label">Catégorie</label>
|
||||
<label htmlFor="inputState2" className="form-label">{t('catégorie')}</label>
|
||||
<select id="inputState2" className="form-select" value={cat} onChange={(e) => setCat(Number(e.target.value))}>
|
||||
<option value={-1}>-- Tous --</option>
|
||||
<option value={-1}>{t('--Tous--')}</option>
|
||||
{CatList.map((cat, index) => {
|
||||
return (<option key={index} value={index}>{getCatName(cat)}</option>)
|
||||
})}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="input5" className="form-label">Poids</label>
|
||||
<label htmlFor="input5" className="form-label">{t('poids')}</label>
|
||||
<div className="row-cols-sm-auto d-flex align-items-center">
|
||||
<div style={{width: "4.25em"}}><input type="number" className="form-control" id="input5" value={weightMin} min="0"
|
||||
name="999"
|
||||
onChange={e => setWeightMin(Number(e.target.value))}/></div>
|
||||
<div><span>à</span></div>
|
||||
<div><span>{t('select.à')}</span></div>
|
||||
<div style={{width: "4.25em"}}><input type="number" className="form-control" value={weightMax} min="0" name="999"
|
||||
onChange={e => setWeightMax(Number(e.target.value))}/></div>
|
||||
<div><small>(0 = désactivé)</small></div>
|
||||
<div><small>{t('select.msg1')}</small></div>
|
||||
</div>
|
||||
</div>
|
||||
<button style={{display: "none"}} onClick={event => event.preventDefault()}></button>
|
||||
@ -240,9 +242,9 @@ export function SelectCombModalContent({data, setGroups}) {
|
||||
<hr/>
|
||||
<div className="row g-3">
|
||||
<div className="col-md">
|
||||
<div style={{textAlign: "center"}}>Inscrit</div>
|
||||
<div style={{textAlign: "center"}}>{t('inscrit')}</div>
|
||||
<div className="list-group overflow-y-auto" style={{maxHeight: "50vh"}}>
|
||||
{dispoFiltered && Object.keys(dispoFiltered).length === 0 && <div>Aucun combattant disponible</div>}
|
||||
{dispoFiltered && Object.keys(dispoFiltered).length === 0 && <div>{t('select.aucunCombattantDisponible')}</div>}
|
||||
{Object.keys(dispoFiltered).sort((a, b) => nameCompare(data, a, b)).map((id) => (
|
||||
<button key={id} type="button" className={"list-group-item list-group-item-action " + (dispoFiltered[id] ? "active" : "")}
|
||||
onClick={() => dispoReducer({type: 'TOGGLE_ID', payload: id})}>
|
||||
@ -259,9 +261,9 @@ export function SelectCombModalContent({data, setGroups}) {
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-md">
|
||||
<div style={{textAlign: "center"}}>Sélectionner</div>
|
||||
<div style={{textAlign: "center"}}>{t('sélectionner')}</div>
|
||||
<div className="list-group overflow-y-auto" style={{maxHeight: "50vh"}}>
|
||||
{selectFiltered && Object.keys(selectFiltered).length === 0 && <div>Aucun combattant sélectionné</div>}
|
||||
{selectFiltered && Object.keys(selectFiltered).length === 0 && <div>{t('select.aucunCombattantSélectionné')}</div>}
|
||||
{Object.keys(selectFiltered).sort((a, b) => nameCompare(data, a, b)).map((id) => (
|
||||
<button key={id} type="button"
|
||||
className={"list-group-item list-group-item-action " + (selectFiltered[id] ? "active" : "")}
|
||||
@ -273,15 +275,15 @@ export function SelectCombModalContent({data, setGroups}) {
|
||||
</div>
|
||||
</div>
|
||||
<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">{t('fermer')}</button>
|
||||
<div className="vr"></div>
|
||||
<label htmlFor="input6" className="form-label">Poule</label>
|
||||
<label htmlFor="input6" className="form-label">{t('poule')}</label>
|
||||
<input type="text" className="form-control" id="input6" style={{width: "3em"}} maxLength={1} value={targetGroupe}
|
||||
onChange={(e) => {
|
||||
if (/^[a-zA-Z0-9]$/.test(e.target.value))
|
||||
setTargetGroupe(e.target.value)
|
||||
}}/>
|
||||
<button type="submit" className="btn btn-primary" data-bs-dismiss="modal" onClick={handleSubmit}>Ajouter</button>
|
||||
<button type="submit" className="btn btn-primary" data-bs-dismiss="modal" onClick={handleSubmit}>{t('ajouter')}</button>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user