feat: comp register optional weight config

This commit is contained in:
2026-01-22 15:13:48 +01:00
parent d2a7e6cbac
commit 4a07eb4ed9
12 changed files with 142 additions and 35 deletions

View File

@@ -76,6 +76,7 @@
"aff_req.toast.undo.error": "Failed to cancel affiliation request",
"aff_req.toast.undo.pending": "Cancelling affiliation request in progress",
"aff_req.toast.undo.success": "Affiliation request cancelled successfully 🎉",
"afficherLesCombattantsNonPesés": "Show unweighed fighters",
"afficherLétatDesAffiliation": "Display affiliation status",
"affiliation": "Affiliation",
"affiliationNo": "Affiliation no. {{no}}",
@@ -193,7 +194,9 @@
"comp.inscriptionsParLesAdministrateursDeLaCompétition": "Registrations by competition administrators",
"comp.inscriptionsParLesResponsablesDeClub": "Registrations by club managers",
"comp.inscriptionsSurLaBilletterieHelloasso": "Registrations on the HelloAsso ticketing",
"comp.modal.annoncé": "Announced",
"comp.modal.information": "Information",
"comp.modal.pesé": "Weighed",
"comp.modal.poids": "Weight (in kg)",
"comp.modal.recherche": "Search*",
"comp.modal.surclassement": "Overclassification",
@@ -490,6 +493,7 @@
"peutSinscrire": "Can register?",
"photos": "Photos",
"plastron": "Breastplate",
"poidsDemandéPour": "Weight required for",
"prenom": "First name",
"protectionDeBras": "Arm protection",
"protectionDeBrasArmé": "Protection of armed arm(s)",

View File

@@ -76,6 +76,7 @@
"aff_req.toast.undo.error": "Échec de l'annulation de la demande d'affiliation",
"aff_req.toast.undo.pending": "Annulation de la demande d'affiliation en cours",
"aff_req.toast.undo.success": "Demande d'affiliation annulée avec succès 🎉",
"afficherLesCombattantsNonPesés": "Afficher les combattants non pesés",
"afficherLétatDesAffiliation": "Afficher l'état des affiliation",
"affiliation": "Affiliation",
"affiliationNo": "Affiliation n°{{no}}",
@@ -193,7 +194,9 @@
"comp.inscriptionsParLesAdministrateursDeLaCompétition": "Inscriptions par les administrateurs de la compétition",
"comp.inscriptionsParLesResponsablesDeClub": "Inscriptions par les responsables de club",
"comp.inscriptionsSurLaBilletterieHelloasso": "Inscriptions sur la billetterie HelloAsso",
"comp.modal.annoncé": "Annoncé",
"comp.modal.information": "Information",
"comp.modal.pesé": "Pesé",
"comp.modal.poids": "Poids (en kg)",
"comp.modal.recherche": "Recherche*",
"comp.modal.surclassement": "Surclassement",
@@ -490,6 +493,7 @@
"peutSinscrire": "Peut s'inscrire?",
"photos": "Photos",
"plastron": "Plastron",
"poidsDemandéPour": "Poids demandé pour",
"prenom": "Prénom",
"protectionDeBras": "Protection de bras",
"protectionDeBrasArmé": "Protection de bras armé(s)",

View File

@@ -56,7 +56,7 @@ export function CompetitionEdit() {
<Content data={data} refresh={refresh}/>
{data.id !== null && <button style={{marginBottom: "1.5em", width: "100%"}} className="btn btn-primary"
onClick={_ => navigate(`/competition/${data.id}/register?type=${data.registerMode}`)}>
onClick={_ => navigate(`/competition/${data.id}/register`)}>
{t('comp.modifierLesParticipants')}</button>}
{data.id !== null && (data.system === "SAFCA" || data.system === "INTERNAL") &&
@@ -219,9 +219,21 @@ function Content({data}) {
const [registerMode, setRegisterMode] = useState(data.registerMode || "FREE");
const [modaleState, setModaleState] = useState({})
const [presets, setPresets] = useState(data.presets || []);
const [cats, setCats] = useState(data.requiredWeight || [])
const [presetChange, setPresetChange] = useState(false)
const {t} = useTranslation();
const setCat = (e, cat) => {
if (e.target.checked) {
if (!cats.includes(cat)) {
setCats([...cats, cat])
}
} else {
setCats(cats.filter(c => c !== cat))
}
}
const isCatSelected = (cat) => cats.includes(cat)
const handleSubmit = (event) => {
event.preventDefault();
@@ -243,6 +255,7 @@ function Content({data}) {
out['endRegister'] = event.target.endRegister?.value
out['registerMode'] = registerMode
out['presets'] = presets
out['requiredWeight'] = cats
if (out['registerMode'] === "HELLOASSO") {
out['data3'] = event.target.data3?.value
@@ -444,6 +457,16 @@ function Content({data}) {
defaultValue={data.endRegister ? data.endRegister.substring(0, 16) : ''}/>
</div>
<div className="input-group mb-3"
style={{display: registerMode === "FREE" || registerMode === "CLUB_ADMIN" ? "flex" : "none"}}>
<span className="input-group-text" id="startRegister">{t('poidsDemandéPour')}</span>
{CatList.map((cat, index) => <div key={index} className="input-group-text">
<input className="form-check-input mt-0" type="checkbox" id={"catInput" + index} checked={isCatSelected(cat)}
aria-label={getCatName(cat)} onChange={e => setCat(e, cat)}/>
<label style={{marginLeft: "0.5em"}} htmlFor={"catInput" + index}>{getCatName(cat)}</label>
</div>)}
</div>
<div style={{display: registerMode === "HELLOASSO" ? "initial" : "none"}}>
<span style={{textAlign: "left"}}>
<div>{t('comp.ha.text1')}</div>

View File

@@ -1,4 +1,4 @@
import {useNavigate, useParams, useSearchParams} from "react-router-dom";
import {useNavigate, useParams} from "react-router-dom";
import {LoadingProvider, useLoadingSwitcher} from "../../hooks/useLoading.jsx";
import {useFetch} from "../../hooks/useFetch.js";
import {AxiosError} from "../../components/AxiosError.jsx";
@@ -13,6 +13,7 @@ import "./CompetitionRegisterAdmin.css"
import * as XLSX from "xlsx-js-style";
import {useCountries} from "../../hooks/useCountries.jsx";
import {Trans, useTranslation} from "react-i18next";
import {Checkbox} from "../../components/MemberCustomFiels.jsx";
export function CompetitionRegisterAdmin({source}) {
const {id} = useParams()
@@ -21,12 +22,14 @@ export function CompetitionRegisterAdmin({source}) {
const [clubFilter, setClubFilter] = useState("")
const [catAgeFilter, setCatAgeFilter] = useState("")
const [catFilter, setCatFilter] = useState(-1)
const [filterNotWeight, setFilterNotWeight] = useState(false)
const [modalState, setModalState] = useState({})
const {t} = useTranslation();
const setLoading = useLoadingSwitcher()
const {data, error} = useFetch(`/competition/${id}/register/${source}`, setLoading, 1)
const {data: data2, error: error2} = useFetch(`/competition/${id}/categories`, setLoading, 1)
const {data: data3} = useFetch(`/competition/${id}?light=true`, setLoading, 1)
const sortName = (a, b) => {
if (a.data.fname === b.data.fname) return a.data.lname.localeCompare(b.data.lname);
@@ -66,8 +69,13 @@ export function CompetitionRegisterAdmin({source}) {
<MakeCentralPanel
data={state.filter(s => (clubFilter.length === 0 || s.data.club.name === clubFilter)
&& (catAgeFilter.length === 0 || s.data.categorie === catAgeFilter)
&& (catFilter === -1 || s.data.categoriesInscrites.includes(catFilter)))}
data2={data2} dispatch={dispatch} id={id} setModalState={setModalState} source={source}/>
&& (catFilter === -1 || s.data.categoriesInscrites.includes(catFilter))
&& (!filterNotWeight || (data3?.requiredWeight.includes(s.data.categorie) && (
(source === "admin" && (s.data.weightReal === "" || s.data.weightReal === null)) ||
(source !== "admin" && (s.data.weight === "" || s.data.weight === null))
)))
)}
data2={data2} data3={data3} dispatch={dispatch} id={id} setModalState={setModalState} source={source}/>
</div> : error ? <AxiosError error={error}/> : <Def/>}
</div>
<div className="col-lg-3">
@@ -86,14 +94,16 @@ export function CompetitionRegisterAdmin({source}) {
<div className="card-header">{t('filtre')}</div>
<div className="card-body">
<FiltreBar data={data} data2={data2} clubFilter={clubFilter} setClubFilter={setClubFilter} catFilter={catFilter}
setCatFilter={setCatFilter} catAgeFilter={catAgeFilter} setCatAgeFilter={setCatAgeFilter} source={source}/>
setCatFilter={setCatFilter} catAgeFilter={catAgeFilter} setCatAgeFilter={setCatAgeFilter}
filterNotWeight={filterNotWeight} setFilterNotWeight={setFilterNotWeight} source={source}/>
</div>
</div>
{source === "admin" && <FileOutput data={data}/>}
</div>
</div>
<Modal data2={data2} error2={error2} sendRegister={sendRegister} modalState={modalState} setModalState={setModalState} source={source}/>
<Modal data2={data2} error2={error2} data3={data3} sendRegister={sendRegister} modalState={modalState} setModalState={setModalState}
source={source}/>
</div>
}
@@ -336,7 +346,7 @@ function CategoriesList({error2, availableCats, fistCatInput, categories, setCat
</>
}
function Modal({data2, error2, sendRegister, modalState, setModalState, source}) {
function Modal({data2, data3, error2, sendRegister, modalState, setModalState, source}) {
const country = useCountries('fr')
const {t} = useTranslation();
const closeBtn = useRef(null);
@@ -349,6 +359,7 @@ function Modal({data2, error2, sendRegister, modalState, setModalState, source})
const [fname, setFname] = useState("")
const [lname, setLname] = useState("")
const [weight, setWeight] = useState("")
const [weightReal, setWeightReal] = useState("")
const [cat, setCat] = useState(0)
const [gcat, setGCat] = useState("")
const [club, setClub] = useState("")
@@ -363,6 +374,7 @@ function Modal({data2, error2, sendRegister, modalState, setModalState, source})
setFname(modalState?.fname ? modalState.fname : "")
setLname(modalState?.lname ? modalState.lname : "")
setWeight(modalState?.weight ? modalState.weight : "")
setWeightReal(modalState?.weightReal ? modalState.weightReal : "")
setCat(modalState?.overCategory ? modalState.overCategory : 0)
setEditMode(modalState?.licence || (modalState.fname && modalState.lname))
setLockEdit(modalState?.lockEdit === undefined ? false : modalState.lockEdit)
@@ -398,6 +410,7 @@ function Modal({data2, error2, sendRegister, modalState, setModalState, source})
fname: fname.trim(),
lname: lname.trim(),
weight: weight,
weightReal: weightReal,
overCategory: cat,
lockEdit: lockEdit,
categoriesInscrites: categories,
@@ -517,8 +530,14 @@ function Modal({data2, error2, sendRegister, modalState, setModalState, source})
<div className="input-group mb-3">
<span className="input-group-text" id="weight">{t('comp.modal.poids')}</span>
<input type="number" min={1} step={1} className="form-control" placeholder="42" aria-label="weight"
name="weight" aria-describedby="weight" value={weight} onChange={e => setWeight(e.target.value)}/>
{source === "admin" && <span className="input-group-text" id="weightReal">{t('comp.modal.annoncé')}</span>}
<input type="number" min={1} step={1} className="form-control" placeholder="--" aria-label="weight"
name="weight" aria-describedby="weight" disabled={!(data3.requiredWeight.includes(currenCat))} value={weight}
onChange={e => setWeight(e.target.value)}/>
{source === "admin" && <><span className="input-group-text" id="weightReal">{t('comp.modal.pesé')}</span>
<input type="number" min={1} step={1} className="form-control" placeholder="--" aria-label="weightReal"
name="weightReal" aria-describedby="weightReal" value={weightReal}
onChange={e => setWeightReal(e.target.value)}/></>}
</div>
<div className="input-group mb-3" hidden={modalState.id < 0}>
@@ -558,7 +577,19 @@ function Modal({data2, error2, sendRegister, modalState, setModalState, source})
let allClub = []
function FiltreBar({data, data2, clubFilter, setClubFilter, catFilter, setCatFilter, catAgeFilter, setCatAgeFilter, source}) {
function FiltreBar({
data,
data2,
clubFilter,
setClubFilter,
catFilter,
setCatFilter,
catAgeFilter,
setCatAgeFilter,
filterNotWeight,
setFilterNotWeight,
source
}) {
const {t} = useTranslation();
useEffect(() => {
if (!data) return;
@@ -567,6 +598,10 @@ function FiltreBar({data, data2, clubFilter, setClubFilter, catFilter, setCatFil
}, [data]);
return <div>
<div className="mb-3">
<Checkbox value={filterNotWeight} onChange={setFilterNotWeight} name="checkbox2" label={t('afficherLesCombattantsNonPesés')}/>
</div>
{source === "admin" && <div className="mb-3">
<select className="form-select" value={clubFilter} onChange={event => setClubFilter(event.target.value)}>
<option value="">{t('---ToutLesClubs---')}</option>
@@ -592,9 +627,8 @@ function FiltreBar({data, data2, clubFilter, setClubFilter, catFilter, setCatFil
</div>
}
function MakeCentralPanel({data, data2, dispatch, id, setModalState, source}) {
const [searchParams] = useSearchParams();
const registerType = searchParams.get("type") || "FREE";
function MakeCentralPanel({data, data2, data3, dispatch, id, setModalState, source}) {
const registerType = data3?.registerMode || "FREE";
const {t} = useTranslation();
return <>
@@ -622,7 +656,13 @@ function MakeCentralPanel({data, data2, dispatch, id, setModalState, source}) {
<small>{t("comp.surclassement", {
count: req.data.overCategory,
cat: getCatName(req.data.categorie)
})} | {req.data.weight ? req.data.weight : "---"} kg</small>
})} {source !== "admin" && data3?.requiredWeight.includes(applyOverCategory(req.data.categorie, req.data.weight)) && <>
| {req.data.weight ? req.data.weight : "---"} kg
</>}
{source === "admin" && (data3?.requiredWeight.includes(applyOverCategory(req.data.categorie, req.data.overCategory)) || req.data.weightReal) && <>
| {req.data.weightReal ? <span style={{color: "#3cbc02"}}>{req.data.weightReal} kg</span> :
(req.data.weight ? <span style={{color: "#e40101"}}>{req.data.weight} kg</span> : "--- kg")}
</>}</small>
<br/>
<small>
{req.data.categoriesInscrites.map(catId => data2?.find(c => c.id === catId)).filter(o => o !== undefined)

View File

@@ -75,7 +75,7 @@ function MakeContent({data}) {
onClick={_ => navigate("/competition/" + data.id + "/club/register")}>{t('comp.inscription')}</button>
}
{data.registerMode === "FREE" && !isClubAdmin(userinfo) && <SelfRegister data2={data}/>
|| <ShowRegister/>
|| <ShowRegister data2={data}/>
}
{data.registerMode === "HELLOASSO" &&
<p><strong>{t('comp.billetterie')} :</strong> <a
@@ -154,10 +154,16 @@ function SelfRegister({data2}) {
? data.length > 0
? <div style={{textAlign: "right", maxWidth: "30em"}}>
<h4 style={{textAlign: "left"}}>{t('comp.monInscription')}</h4>
<div className="input-group mb-3">
<div className="input-group mb-3" hidden={!(data2?.requiredWeight.includes(currenCat))}>
<span className="input-group-text" id="weight">{t("comp.modal.poids")}</span>
<input type="number" min={1} step={1} className="form-control" placeholder="42" aria-label="weight" disabled={disabled}
<input type="number" min={1} step={1} className="form-control" placeholder="--" aria-label="weight" disabled={disabled}
name="weight" aria-describedby="weight" value={weight} onChange={e => setWeight(e.target.value)}/>
{data[0]?.weightReal && <>
<span className="input-group-text" id="weight">{t("comp.modal.pesé")}</span>
<input type="number" min={1} step={1} className="form-control" placeholder="--" aria-label="weight" disabled={true}
name="weight" aria-describedby="weight" value={data[0]?.weightReal} onChange={() => {
}}/>
</>}
</div>
<div style={{textAlign: "left"}}>{t('comp.catégorieNormalisée')}: {getCatName(data[0].categorie)}</div>
@@ -207,13 +213,14 @@ function SelfRegister({data2}) {
</>
}
function ShowRegister() {
function ShowRegister({data2}) {
const {id} = useParams()
const setLoading = useLoadingSwitcher()
const {data, error} = useFetch(`/competition/${id}/register/user`, setLoading, 1)
const {data: data3, error: error2} = useFetch(`/competition/${id}/categories`, setLoading, 1)
const {t} = useTranslation();
const currenCat = data?.length > 0 && data[0]?.categorie !== "" ? applyOverCategory(data[0]?.categorie, data[0].overCategory) : "";
return <>
{data ? data.length > 0
@@ -225,7 +232,10 @@ function ShowRegister() {
{data[0].overCategory === 1 && ` ${t('1Catégorie')}`}
{data[0].overCategory === 2 && ` ${t('2Catégorie')}`}
</div>
<div style={{textAlign: "left"}}>{t("comp.modal.poids")} : {data[0].weight}</div>
{data2?.requiredWeight.includes(currenCat) &&
<div style={{textAlign: "left"}}>{t("comp.modal.poids")} : {data[0].weight} {data[0]?.weightReal && <>
({t("comp.modal.pesé")} : {data[0]?.weightReal})</>}</div>}
<div style={{textAlign: "left"}}>{t('catégorie')} :
{error2 ? <AxiosError error={error2}/> : <>
{data3 && data3.length === 0 && <div>{t('aucuneCatégorieDisponible')}</div>}