dev #105

Merged
Thibaut merged 8 commits from dev into master 2026-01-23 15:49:25 +00:00
11 changed files with 440 additions and 158 deletions
Showing only changes of commit d2a7e6cbac - Show all commits

View File

@ -28,24 +28,30 @@ public class CatPresetModel {
String name = "";
List<Categorie> categories;
long roundDuration;
long pauseDuration;
@ElementCollection(fetch = FetchType.EAGER)
@CollectionTable(name = "category_preset_catconfig", joinColumns = @JoinColumn(name = "id_preset"))
List<CategorieEmbeddable> categories;
SwordType swordType = SwordType.NONE;
ShieldType shieldType = ShieldType.NONE;
/* Bitmask protections:
* 1 - 1 - Head
* 2 - 2 - Throat
* 3 - 4 - Torso
* 4 - 8 - Arms
* 5 - 16 - Hands
* 6 - 32 - Groin
* 7 - 64 - Legs
/*
* 1 - 1 - Casque
* 2 - 2 - Gorgerin
* 3 - 4 - Coquille et Protection pelvienne
* 4 - 8 - Gant main(s) armée(s)
* 5 - 16 - Gant main bouclier
* 6 - 32 - Plastron
* 7 - 64 - Protection de bras armé(s)
* 8 - 128 - Protection de bras de bouclier
* 9 - 256 - Protection de jambes
* 10 - 512 - Protection de genoux
* 11 - 1024 - Protection de coudes
* 12 - 2048 - Protection dorsale
* 13 - 4096 - Protection de pieds
*/
int mandatoryProtection = 0;
int mandatoryProtection1 = 0;
int mandatoryProtection2 = 0;
@ManyToMany(mappedBy = "categoriesInscrites", fetch = FetchType.LAZY)
private List<RegisterModel> registers = new ArrayList<>();
@ -68,4 +74,16 @@ public class CatPresetModel {
BUCKLER
}
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@RegisterForReflection
@Embeddable
public static class CategorieEmbeddable {
Categorie categorie;
long roundDuration;
long pauseDuration;
}
}

View File

@ -271,10 +271,9 @@ public class CompetitionService {
presetModel.setName(preset.getName());
presetModel.setSwordType(preset.getSword());
presetModel.setShieldType(preset.getShield());
presetModel.setRoundDuration(preset.getRoundDuration());
presetModel.setPauseDuration(preset.getPauseDuration());
presetModel.setCategories(preset.getCategories());
presetModel.setMandatoryProtection(preset.getMandatoryProtection());
presetModel.setMandatoryProtection1(preset.getMandatoryProtection1());
presetModel.setMandatoryProtection2(preset.getMandatoryProtection2());
}
// Remove deleted presets
@ -399,7 +398,8 @@ public class CompetitionService {
g.getCategoriesInscrites().clear();
g.getCategoriesInscrites().addAll(cats);
g.getCategoriesInscrites()
.removeIf(cat -> !cat.getCategories().contains(g.getCategorie()));
.removeIf(cat -> cat.getCategories().stream()
.noneMatch(e -> e.getCategorie().equals(g.getCategorie())));
}))
.chain(model -> Panache.withTransaction(() -> competitionGuestRepository.persist(model))
.call(r -> model.getCompetition().getSystem() == CompetitionSystem.INTERNAL ?
@ -495,7 +495,8 @@ public class CompetitionService {
}
r.getCategoriesInscrites().addAll(cats);
r.getCategoriesInscrites()
.removeIf(cat -> !cat.getCategories().contains(r.getCategorie2()));
.removeIf(cat -> cat.getCategories().stream()
.noneMatch(e -> e.getCategorie().equals(r.getCategorie2())));
})))
.chain(r -> Panache.withTransaction(() -> registerRepository.persist(r)))
.call(r -> c.getSystem() == CompetitionSystem.INTERNAL ?

View File

@ -610,7 +610,7 @@ public class ResultService {
} else if ((matchModel.isC1(comb) && win > 0) || matchModel.isC2(comb) && win < 0) {
stat.w++;
stat.win_ids.add(matchModel.getId());
stat.score += 3;
stat.score += 2;
} else {
stat.l++;
}

View File

@ -1,7 +1,6 @@
package fr.titionfire.ffsaf.rest.data;
import fr.titionfire.ffsaf.data.model.CatPresetModel;
import fr.titionfire.ffsaf.utils.Categorie;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.AllArgsConstructor;
import lombok.Data;
@ -16,17 +15,15 @@ public class PresetData {
private String name;
private CatPresetModel.SwordType sword;
private CatPresetModel.ShieldType shield;
private List<Categorie> categories;
long roundDuration;
long pauseDuration;
int mandatoryProtection;
private List<CatPresetModel.CategorieEmbeddable> categories;
private int mandatoryProtection1;
private int mandatoryProtection2;
public static PresetData fromModel(CatPresetModel model) {
if (model == null)
return null;
return new PresetData(model.getId(), model.getName(), model.getSwordType(), model.getShieldType(),
model.getCategories(), model.getRoundDuration(), model.getPauseDuration(),
model.getMandatoryProtection());
model.getCategories(), model.getMandatoryProtection1(), model.getMandatoryProtection2());
}
}

View File

@ -83,6 +83,7 @@
"ajouterUnClub": "Add a club",
"ajouterUnMembre": "Add a member",
"all_season": "--- all seasons ---",
"ans": "years",
"arme": "Weapon",
"au": "to",
"aucun": "None",
@ -105,6 +106,7 @@
"button.seDésinscrire": "Unsubscribe",
"button.suivant": "Next",
"button.supprimer": "Delete",
"casque": "Helmet",
"cat.benjamin": "Benjamin",
"cat.cadet": "Cadet",
"cat.catégorieInconnue": "Unknown category",
@ -260,6 +262,7 @@
"contactInterne": "Internal contact",
"contact_one": "Contact",
"contact_other": "Contacts",
"coquilleProtectionPelvienne": "Shell / Pelvic protection",
"date": "Date",
"dateDeNaissance": "Date of birth",
"days": [
@ -299,8 +302,12 @@
"faitPar": "Done by",
"femme": "Female",
"filtre": "Filter",
"gantMainBouclier": "Shield hand glove",
"gantMainsArmées": "Armed hand(s) glove(s)",
"gants": "Gloves",
"genre": "Gender",
"gestionGroupée": "Group management",
"gorgerin": "Gorgerin",
"gradeDarbitrage": "Refereeing grade",
"h": "M",
"home": {
@ -480,8 +487,18 @@
"perm.créerDesCompétion": "Create competitions",
"perm.ffsafIntra": "FFSAF intra",
"permission": "Permission",
"peutSinscrire": "Can register?",
"photos": "Photos",
"plastron": "Breastplate",
"prenom": "First name",
"protectionDeBras": "Arm protection",
"protectionDeBrasArmé": "Protection of armed arm(s)",
"protectionDeBrasDeBouclier": "Shield arm protection",
"protectionDeCoudes": "Elbow protection",
"protectionDeGenoux": "Knee protection",
"protectionDeJambes": "Leg protection",
"protectionDePieds": "Foot protection",
"protectionDorsale": "Back protector",
"protectionObligatoire": "Mandatory protection",
"prénomEtNom": "First and last name",
"rechercher": "Search",

View File

@ -83,6 +83,7 @@
"ajouterUnClub": "Ajouter un club",
"ajouterUnMembre": "Ajouter un membre",
"all_season": "--- tout les saisons ---",
"ans": "ans",
"arme": "Arme",
"au": "au",
"aucun": "Aucun",
@ -105,6 +106,7 @@
"button.seDésinscrire": "Se désinscrire",
"button.suivant": "Suivant",
"button.supprimer": "Supprimer",
"casque": "Casque",
"cat.benjamin": "Benjamin",
"cat.cadet": "Cadet",
"cat.catégorieInconnue": "Catégorie inconnue",
@ -260,6 +262,7 @@
"contactInterne": "Contact interne",
"contact_one": "Contact",
"contact_other": "Contacts",
"coquilleProtectionPelvienne": "Coquille / Protection pelvienne",
"date": "Date",
"dateDeNaissance": "Date de naissance",
"days": [
@ -299,8 +302,12 @@
"faitPar": "Fait par",
"femme": "Femme",
"filtre": "Filtre",
"gantMainBouclier": "Gant main de bouclier",
"gantMainsArmées": "Gant main(s) armée(s)",
"gants": "Gants",
"genre": "Genre",
"gestionGroupée": "Gestion groupée",
"gorgerin": "Gorgerin",
"gradeDarbitrage": "Grade d'arbitrage",
"h": "H",
"home": {
@ -480,8 +487,18 @@
"perm.créerDesCompétion": "Créer des compétion",
"perm.ffsafIntra": "FFSAF intra",
"permission": "Permission",
"peutSinscrire": "Peut s'inscrire?",
"photos": "Photos",
"plastron": "Plastron",
"prenom": "Prénom",
"protectionDeBras": "Protection de bras",
"protectionDeBrasArmé": "Protection de bras armé(s)",
"protectionDeBrasDeBouclier": "Protection de bras de bouclier",
"protectionDeCoudes": "Protection de coudes",
"protectionDeGenoux": "Protection de genoux",
"protectionDeJambes": "Protection de jambes",
"protectionDePieds": "Protection de pieds",
"protectionDorsale": "Protection dorsale",
"protectionObligatoire": "Protection obligatoire",
"prénomEtNom": "Prénom et nom",
"rechercher": "Rechercher",

View File

@ -0,0 +1,115 @@
const CategoryPreset = [
{
name: "Épée",
sword: "ONE_HAND",
shield: "NONE",
categories: [
{categorie: "MINI_POUSSIN", roundDuration: 30000, pauseDuration: 60000},
{categorie: "POUSSIN", roundDuration: 30000, pauseDuration: 60000},
{categorie: "BENJAMIN", roundDuration: 45000, pauseDuration: 60000},
{categorie: "MINIME", roundDuration: 45000, pauseDuration: 60000},
{categorie: "CADET", roundDuration: 60000, pauseDuration: 60000},
],
mandatoryProtection1: 45,
mandatoryProtection2: 13
},
{
name: "Épée Bouclier",
sword: "ONE_HAND",
shield: "STANDARD",
categories: [
{categorie: "SUPER_MINI", roundDuration: 30000, pauseDuration: 60000},
{categorie: "MINI_POUSSIN", roundDuration: 30000, pauseDuration: 60000},
{categorie: "POUSSIN", roundDuration: 45000, pauseDuration: 60000},
{categorie: "BENJAMIN", roundDuration: 45000, pauseDuration: 60000},
{categorie: "MINIME", roundDuration: 60000, pauseDuration: 60000},
{categorie: "CADET", roundDuration: 60000, pauseDuration: 60000},
{categorie: "JUNIOR", roundDuration: 60000, pauseDuration: 60000},
{categorie: "SENIOR1", roundDuration: 60000, pauseDuration: 60000},
{categorie: "SENIOR2", roundDuration: 60000, pauseDuration: 60000},
{categorie: "VETERAN1", roundDuration: 60000, pauseDuration: 60000},
{categorie: "VETERAN2", roundDuration: 60000, pauseDuration: 60000},
],
mandatoryProtection1: 45,
mandatoryProtection2: 13
},
{
name: "Épée Bocle",
sword: "ONE_HAND",
shield: "BUCKLER",
categories: [
{categorie: "POUSSIN", roundDuration: 45000, pauseDuration: 60000},
{categorie: "BENJAMIN", roundDuration: 45000, pauseDuration: 60000},
{categorie: "MINIME", roundDuration: 60000, pauseDuration: 60000},
{categorie: "CADET", roundDuration: 60000, pauseDuration: 60000},
{categorie: "JUNIOR", roundDuration: 60000, pauseDuration: 60000},
{categorie: "SENIOR1", roundDuration: 60000, pauseDuration: 60000},
{categorie: "SENIOR2", roundDuration: 60000, pauseDuration: 60000},
{categorie: "VETERAN1", roundDuration: 60000, pauseDuration: 60000},
{categorie: "VETERAN2", roundDuration: 60000, pauseDuration: 60000},
],
mandatoryProtection1: 45,
mandatoryProtection2: 13
},
{
name: "Épée Longue",
sword: "TWO_HAND",
shield: "NONE",
categories: [
{categorie: "JUNIOR", roundDuration: 60000, pauseDuration: 60000},
{categorie: "SENIOR1", roundDuration: 60000, pauseDuration: 60000},
{categorie: "SENIOR2", roundDuration: 60000, pauseDuration: 60000},
{categorie: "VETERAN1", roundDuration: 60000, pauseDuration: 60000},
{categorie: "VETERAN2", roundDuration: 60000, pauseDuration: 60000},
],
mandatoryProtection1: 61,
mandatoryProtection2: 29
},
{
name: "Sabre",
sword: "SABER",
shield: "NONE",
categories: [
{categorie: "MINIME", roundDuration: 60000, pauseDuration: 60000},
{categorie: "CADET", roundDuration: 60000, pauseDuration: 60000},
{categorie: "JUNIOR", roundDuration: 60000, pauseDuration: 60000},
{categorie: "SENIOR1", roundDuration: 60000, pauseDuration: 60000},
{categorie: "SENIOR2", roundDuration: 60000, pauseDuration: 60000},
{categorie: "VETERAN1", roundDuration: 60000, pauseDuration: 60000},
{categorie: "VETERAN2", roundDuration: 60000, pauseDuration: 60000},
],
mandatoryProtection1: 47,
mandatoryProtection2: 47
},
{
name: "Sabre Bocle",
sword: "SABER",
shield: "BUCKLER",
categories: [
{categorie: "MINIME", roundDuration: 60000, pauseDuration: 60000},
{categorie: "CADET", roundDuration: 60000, pauseDuration: 60000},
{categorie: "JUNIOR", roundDuration: 60000, pauseDuration: 60000},
{categorie: "SENIOR1", roundDuration: 60000, pauseDuration: 60000},
{categorie: "SENIOR2", roundDuration: 60000, pauseDuration: 60000},
{categorie: "VETERAN1", roundDuration: 60000, pauseDuration: 60000},
{categorie: "VETERAN2", roundDuration: 60000, pauseDuration: 60000},
],
mandatoryProtection1: 47,
mandatoryProtection2: 47
},
{
name: "Profight Léger",
sword: "ONE_HAND",
shield: "TEARDROP",
categories: [
{categorie: "SENIOR1", roundDuration: 120000, pauseDuration: 60000},
{categorie: "SENIOR2", roundDuration: 120000, pauseDuration: 60000},
{categorie: "VETERAN1", roundDuration: 120000, pauseDuration: 60000},
{categorie: "VETERAN2", roundDuration: 120000, pauseDuration: 60000},
],
mandatoryProtection1: 3647,
mandatoryProtection2: 3647
},
]
export default CategoryPreset;

View File

@ -1,15 +1,48 @@
import {useTranslation} from "react-i18next";
const ProtectionSelector = ({mandatoryProtection = 0, setMandatoryProtection = () => {} }) => {
const ProtectionSelector = ({
shield = true,
mandatoryProtection = 0, setMandatoryProtection = () => {
}
}) => {
const {t} = useTranslation();
const toggle = (bit) => {
bit = 1 << (bit - 1);
setMandatoryProtection(v => (v & bit ? v & ~bit : v | bit));
};
const isOn = (bit) => (mandatoryProtection & bit) !== 0;
const props = {
style: ({cursor: "pointer"}),
};
const propsDash = {
style: ({cursor: "pointer"}),
opacity: "0.8",
stroke: "#7b8285",
strokeDasharray: "4 3",
strokeWidth: "1"
};
const isOn = (bit) => (mandatoryProtection & (1 << (bit - 1))) !== 0;
const color = (bit) => (isOn(bit) ? "#4ade80" : "#e5e7eb");
/*
1 - 1 - Casque
2 - 2 - Gorgerin
3 - 4 - Coquille et Protection pelvienne
4 - 8 - Gant main(s) armée(s)
5 - 16 - Gant main bouclier
6 - 32 - Plastron
7 - 64 - Protection de bras armé(s)
8 - 128 - Protection de bras de bouclier
9 - 256 - Protection de jambes
10 - 512 - Protection de genoux
11 - 1024 - Protection de coudes
12 - 2048 - Protection dorsale
13 - 4096 - Protection de pieds
*/
return (
<div>
<svg width="200" height="420" viewBox="0 0 160 320">
<svg width="200" height="300" viewBox="0 0 160 320">
<rect
width="160" height="320"
fill="#f9fafb00"
@ -18,81 +51,115 @@ const ProtectionSelector = ({mandatoryProtection = 0, setMandatoryProtection = (
rx="10"
/>
{/* Head */}
<ellipse
{/* Casque */}
<ellipse {...props}
cx="80" cy="35" rx="20" ry="22"
fill={color(1)}
onClick={() => toggle(1)}
/>
><title>{t('casque')}</title></ellipse>
{/* Throat / Neck */}
<ellipse
{/* Gorgerin */}
<ellipse {...props}
cx="80" cy="65" rx="12" ry="8"
fill={color(2)}
onClick={() => toggle(2)}
/>
><title>{t('gorgerin')}</title></ellipse>
{/* Torso */}
<ellipse
{/* Plastron */}
<ellipse {...props}
cx="80" cy="118" rx="30" ry="45"
fill={color(6)}
onClick={() => toggle(6)}
><title>{t('plastron')}</title></ellipse>
{/* Protection dorsale */}
<ellipse {...propsDash}
cx="80" cy="118" rx="10" ry="35"
fill={color(12)}
onClick={() => toggle(12)}
><title>{t('protectionDorsale')}</title></ellipse>
{/* Protection de bras armé(s) */}
<ellipse {...props}
cx="38" cy="118" rx="12" ry="40"
fill={color(7)}
onClick={() => toggle(7)}
><title>{shield ? t('protectionDeBrasArmé') : t('protectionDeBras')}</title></ellipse>
{/* Protection de bras de bouclier */}
<ellipse {...props}
cx="122" cy="118" rx="12" ry="40"
fill={color(shield ? 8 : 7)}
onClick={() => toggle(shield ? 8 : 7)}
><title>{shield ? t('protectionDeBrasDeBouclier') : t('protectionDeBras')}</title></ellipse>
{/* Protection de coudes */}
<ellipse {...propsDash}
cx="38" cy="118" rx="12" ry="12"
fill={color(11)}
onClick={() => toggle(11)}
><title>{t('protectionDeCoudes')}</title></ellipse>
<ellipse {...propsDash}
cx="122" cy="118" rx="12" ry="12"
fill={color(11)}
onClick={() => toggle(11)}
><title>{t('protectionDeCoudes')}</title></ellipse>
{/* Gant main(s) armée(s) */}
<ellipse {...props}
cx="38" cy="170" rx="10" ry="12"
fill={color(4)}
onClick={() => toggle(4)}
/>
{/* Arms */}
<ellipse
cx="38" cy="118" rx="12" ry="40"
fill={color(8)}
onClick={() => toggle(8)}
/>
<ellipse
cx="122" cy="118" rx="12" ry="40"
fill={color(8)}
onClick={() => toggle(8)}
/>
{/* Hands */}
<ellipse
cx="38" cy="170" rx="10" ry="12"
fill={color(16)}
onClick={() => toggle(16)}
/>
<ellipse
><title>{shield ? t('gantMainsArmées') : t('gants')}</title></ellipse>
{/* Gant main bouclier */}
<ellipse {...props}
cx="122" cy="170" rx="10" ry="12"
fill={color(16)}
onClick={() => toggle(16)}
/>
fill={color(shield ? 5 : 4)}
onClick={() => toggle(shield ? 5 : 4)}
><title>{shield ? t('gantMainBouclier') : t('gants')} </title></ellipse>
{/* Legs */}
<ellipse
{/* Protection de jambes */}
<ellipse {...props}
cx="65" cy="230" rx="14" ry="55"
fill={color(64)}
onClick={() => toggle(64)}
/>
<ellipse
fill={color(9)}
onClick={() => toggle(9)}
><title>{t('protectionDeJambes')}</title></ellipse>
<ellipse {...props}
cx="95" cy="230" rx="14" ry="55"
fill={color(64)}
onClick={() => toggle(64)}
/>
fill={color(9)}
onClick={() => toggle(9)}
><title>{t('protectionDeJambes')}</title></ellipse>
{/* Groin */}
<ellipse
{/* Protection de genoux */}
<ellipse {...propsDash}
cx="65" cy="230" rx="14" ry="14"
fill={color(10)}
onClick={() => toggle(10)}
><title>{t('protectionDeGenoux')}</title></ellipse>
<ellipse {...propsDash}
cx="95" cy="230" rx="14" ry="14"
fill={color(10)}
onClick={() => toggle(10)}
><title>{t('protectionDeGenoux')}</title></ellipse>
{/* Coquille et Protection pelvienne */}
<ellipse {...props}
cx="80" cy="170" rx="20" ry="10"
fill={color(32)}
onClick={() => toggle(32)}
/>
fill={color(3)}
onClick={() => toggle(3)}
><title>{t('coquilleProtectionPelvienne')}</title></ellipse>
{/* Feet */}
<ellipse
{/* Protection de pieds */}
<ellipse {...props}
cx="65" cy="295" rx="16" ry="8"
fill="#f0f0f0"
/>
<ellipse
fill={color(13)}
onClick={() => toggle(13)}
><title>{t('protectionDePieds')}</title></ellipse>
<ellipse {...props}
cx="95" cy="295" rx="16" ry="8"
fill="#f0f0f0"
/>
fill={color(13)}
onClick={() => toggle(13)}
><title>{t('protectionDePieds')}</title></ellipse>
</svg>
</div>
);
}

View File

@ -23,6 +23,7 @@ import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import {faAdd, faTrashCan} from "@fortawesome/free-solid-svg-icons";
import {Trans, useTranslation} from "react-i18next";
import ProtectionSelector from "../../components/ProtectionSelector.jsx";
import CategoryPreset from "../../assets/CategoryPreset.js";
export function CompetitionEdit() {
const {id} = useParams()
@ -372,7 +373,7 @@ function Content({data}) {
<a key={preset.id} className="list-group-item list-group-item-action" data-bs-toggle="modal"
data-bs-target="#catModal" onClick={() => setModaleState(preset)}>
<span style={{margin: "0 0.25em 0 0"}}>{preset.name}</span>
{preset.categories.sort(sortCategories).map((cat, index) =>
{preset.categories.map(e => e.categorie).sort(sortCategories).map((cat, index) =>
<span key={index} className="badge text-bg-secondary"
style={{margin: "0 0.25em"}}>{getCatName(cat)}</span>)}
</a>)}
@ -381,11 +382,27 @@ function Content({data}) {
<div className="col-auto"
style={{color: "red"}}>{presetChange && t('LesModificationsNontEnregistrer')}</div>
<div className="col" style={{textAlign: "right"}}>
<div className="btn-group">
<button type="button" className="btn btn-success" data-bs-toggle="modal"
data-bs-target="#catModal"
onClick={() => setModaleState({id: Math.min(...presets.map(p => p.id), 0) - 1})}>
<FontAwesomeIcon icon={faAdd}/>
</button>
<button type="button" className="btn btn-success dropdown-toggle dropdown-toggle-split"
data-bs-toggle="dropdown" aria-expanded="false">
<span className="visually-hidden">Toggle Dropdown</span>
</button>
<ul className="dropdown-menu">
{CategoryPreset.map((preset, index) =>
<li key={index}>
<button className="dropdown-item" type="button" data-bs-toggle="modal"
data-bs-target="#catModal"
onClick={() => setModaleState({id: Math.min(...presets.map(p => p.id), 0) - 1, ...preset})}>
{preset.name}
</button>
</li>)}
</ul>
</div>
</div>
</div>
</div>
@ -479,10 +496,9 @@ function CatModalContent({setPresets, setPresetChange, state}) {
const [name, setName] = useState(state.name || "")
const [sword, setSword] = useState(state.sword || "NONE")
const [shield, setShield] = useState(state.shield || "NONE")
const [time, setTime] = useState(timePrint(state.roundDuration || 90000))
const [pause, setPause] = useState(timePrint(state.pauseDuration || 60000))
const [cats, setCats] = useState(state.categories || [])
const [mandatoryProtection, setMandatoryProtection] = useState(state.mandatoryProtection || 33)
const [mandatoryProtection1, setMandatoryProtection1] = useState(state.mandatoryProtection1 || 5)
const [mandatoryProtection2, setMandatoryProtection2] = useState(state.mandatoryProtection2 || 5)
const {t} = useTranslation();
@ -490,23 +506,42 @@ function CatModalContent({setPresets, setPresetChange, state}) {
setName(state.name || "")
setSword(state.sword || "NONE")
setShield(state.shield || "NONE")
setTime(timePrint(state.roundDuration || 90000))
setPause(timePrint(state.pauseDuration || 60000))
setCats(state.categories || [])
setMandatoryProtection(state.mandatoryProtection || 33)
setCats(state.categories?.map(c => ({
categorie: c.categorie,
roundDuration: timePrint(c.roundDuration),
pauseDuration: timePrint(c.pauseDuration)
})) || [])
setMandatoryProtection1(state.mandatoryProtection1 || 5)
setMandatoryProtection2(state.mandatoryProtection2 || 5)
}, [state]);
const setCat = (e, cat) => {
if (e.target.checked) {
if (!cats.includes(cat)) {
setCats([...cats, cat])
setCats([...cats, {categorie: cat, roundDuration: "", pauseDuration: ""}])
}
} else {
setCats(cats.filter(c => c !== cat))
setCats(cats.filter(c => c.categorie !== cat))
}
}
const setTime = (e, cat) => {
const value = e.target.value;
setCats(cats.map(c => {
if (c.categorie === cat)
return {...c, roundDuration: value}
return c
}))
}
const setPause = (e, cat) => {
const value = e.target.value;
setCats(cats.map(c => {
if (c.categorie === cat)
return {...c, pauseDuration: value}
return c
}))
}
const isCatSelected = (cat) => cats.includes(cat)
const isCatSelected = (cat) => cats.some(cat_ => cat_.categorie === cat)
const parseTime = (str) => {
const parts = str.split(":").map(part => parseInt(part, 10));
@ -525,10 +560,13 @@ function CatModalContent({setPresets, setPresetChange, state}) {
name: name,
sword: sword,
shield: shield,
roundDuration: parseTime(time),
pauseDuration: parseTime(pause),
categories: cats,
mandatoryProtection: mandatoryProtection
categories: cats.map(c => ({
categorie: c.categorie,
roundDuration: parseTime(c.roundDuration),
pauseDuration: parseTime(c.pauseDuration)
})),
mandatoryProtection1: mandatoryProtection1,
mandatoryProtection2: mandatoryProtection2
}
setPresets(presets => [...presets.filter(p => p.id !== out.id), out])
setPresetChange(true)
@ -553,19 +591,6 @@ function CatModalContent({setPresets, setPresetChange, state}) {
value={name} onChange={e => setName(e.target.value)}/>
</div>
<div className="input-group mb-3">
<span className="input-group-text">{t('duréeRound')}</span>
<input type="text" className="form-control" placeholder="0" aria-label="Min" value={time}
onChange={e => setTime(e.target.value)}/>
<span className="input-group-text">(mm:ss)</span>
</div>
<div className="input-group mb-3">
<span className="input-group-text">{t('duréePause')}</span>
<input type="text" className="form-control" placeholder="0" aria-label="Min" value={pause}
onChange={e => setPause(e.target.value)}/>
<span className="input-group-text">(mm:ss)</span>
</div>
<div className="input-group mb-3">
<span className="input-group-text" id="sword">{t('arme')}</span>
<select className="form-select" aria-label={t('arme')} name="sword" value={sword}
@ -586,24 +611,46 @@ function CatModalContent({setPresets, setPresetChange, state}) {
</select>
</div>
<label htmlFor="inputState2" className="form-label">
{t('catégorie')} :
</label>
<div className="d-flex flex-wrap">
{CatList.map((cat, index) => <div key={index} className="input-group" style={{display: "contents"}}>
<div 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>
<table className="table" style={{textAlign: "center"}}>
<thead>
<tr>
<th scope="col">{t('catégorie')}</th>
<th scope="col">{t('peutSinscrire')}</th>
<th scope="col">{t('duréeRound')}</th>
<th scope="col">{t('duréePause')}</th>
</tr>
</thead>
<tbody>
{CatList.map((cat, index) => <tr key={index} style={{verticalAlign: "middle"}}>
<th scope="row" style={{width: "7em"}}><label htmlFor={"catInput" + index}>{getCatName(cat)}</label></th>
<td><input className="form-check-input" type="checkbox" id={"catInput" + index} checked={isCatSelected(cat)}
aria-label={getCatName(cat)} onChange={e => setCat(e, cat)}/></td>
<td style={{padding: "0"}}><input type="text" className="form-control form-control-sm" placeholder="mm:ss"
value={cats.find(c => c.categorie === cat)?.roundDuration || ""}
onChange={e => setTime(e, cat)}
aria-label="mm:ss" hidden={!isCatSelected(cat)} style={{width: "4.5em"}}/></td>
<td style={{padding: "0"}}><input type="text" className="form-control form-control-sm" placeholder="mm:ss"
value={cats.find(c => c.categorie === cat)?.pauseDuration || ""}
onChange={e => setPause(e, cat)}
aria-label="mm:ss" hidden={!isCatSelected(cat)} style={{width: "4.5em"}}/></td>
</tr>)}
</tbody>
</table>
</div>
<div className="col-12 col-md-5">
<div style={{textAlign: "center"}}>
<h6>{t('protectionObligatoire')} :</h6>
<ProtectionSelector mandatoryProtection={mandatoryProtection} setMandatoryProtection={setMandatoryProtection}/>
{cats.some(cat_ => CatList.indexOf(cat_.categorie) <= CatList.indexOf("JUNIOR")) && <>
<div>&lt; 18 {t('ans')}</div>
<ProtectionSelector shield={shield !== "NONE"} mandatoryProtection={mandatoryProtection1}
setMandatoryProtection={setMandatoryProtection1}/>
</>}
{cats.some(cat_ => CatList.indexOf(cat_.categorie) > CatList.indexOf("JUNIOR")) && <>
<div>&ge; 18 {t('ans')}</div>
<ProtectionSelector shield={shield !== "NONE"} mandatoryProtection={mandatoryProtection2}
setMandatoryProtection={setMandatoryProtection2}/>
</>}
</div>
</div>
</div>

View File

@ -3,7 +3,7 @@ import {LoadingProvider, useLoadingSwitcher} from "../../hooks/useLoading.jsx";
import {useFetch} from "../../hooks/useFetch.js";
import {AxiosError} from "../../components/AxiosError.jsx";
import {ThreeDots} from "react-loader-spinner";
import React, {useEffect, useReducer, useRef, useState} from "react";
import React, {useEffect, useId, useReducer, useRef, useState} from "react";
import {apiAxios, applyOverCategory, CatList, getCatName, getToastMessage} from "../../utils/Tools.js";
import {toast} from "react-toastify";
import {SimpleReducer} from "../../utils/SimpleReducer.jsx";
@ -317,6 +317,8 @@ const AutoCompleteInput = ({suggestions = [], handleAdd}) => {
function CategoriesList({error2, availableCats, fistCatInput, categories, setCategories}) {
const {t} = useTranslation();
const id = useId();
return <>
{error2 ? <AxiosError error={error2}/> : <>
{availableCats && availableCats.length === 0 && <div>{t('aucuneCatégorieDisponible')}</div>}
@ -325,9 +327,9 @@ function CategoriesList({error2, availableCats, fistCatInput, categories, setCat
style={{display: "contents"}}>
<div className="input-group-text">
<input ref={index === 0 ? fistCatInput : undefined} className="form-check-input mt-0" type="checkbox"
id={"categoriesInput" + index} checked={categories.includes(cat.id)} aria-label={cat.name}
id={id + "categoriesInput" + index} checked={categories.includes(cat.id)} aria-label={cat.name}
onChange={e => setCategories(e, cat.id)}/>
<label style={{marginLeft: "0.5em"}} htmlFor={"categoriesInput" + index}>{cat.name}</label>
<label style={{marginLeft: "0.5em"}} htmlFor={id + "categoriesInput" + index}>{cat.name}</label>
</div>
</div>)}
</>}
@ -427,7 +429,7 @@ function Modal({data2, error2, sendRegister, modalState, setModalState, source})
}
const currenCat = gcat !== "" ? applyOverCategory(gcat, cat) : "";
const availableCats = data2 ? (currenCat !== "" ? data2.filter(c => c.categories.includes(currenCat)) : data2).sort((a, b) => a.name.localeCompare(b.name)) : []
const availableCats = data2 ? (currenCat !== "" ? data2.filter(c => c.categories.some(c2 => c2.categorie === currenCat)) : data2).sort((a, b) => a.name.localeCompare(b.name)) : []
if (availableCats.length === 0) {
if (fistCatInput.current) {
fistCatInput.current = null
@ -625,7 +627,8 @@ function MakeCentralPanel({data, data2, dispatch, id, setModalState, source}) {
<small>
{req.data.categoriesInscrites.map(catId => data2?.find(c => c.id === catId)).filter(o => o !== undefined)
.sort((a, b) => a.name.localeCompare(b.name)).map(cat =>
<span key={cat.id} className="badge text-bg-secondary" style={{margin: "0 0.125em"}}>{cat.name}</span>)}
<span key={cat.id} className="badge text-bg-secondary"
style={{margin: "0 0.125em"}}>{cat.name}</span>)}
</small>
</div>
</div>

View File

@ -74,7 +74,7 @@ function MakeContent({data}) {
disabled={new Date() < new Date(data.startRegister.split('+')[0]) || new Date() > new Date(data.endRegister.split('+')[0])}
onClick={_ => navigate("/competition/" + data.id + "/club/register")}>{t('comp.inscription')}</button>
}
{data.registerMode === "FREE" && !isClubAdmin(userinfo) && <SelfRegister/>
{data.registerMode === "FREE" && !isClubAdmin(userinfo) && <SelfRegister data2={data}/>
|| <ShowRegister/>
}
{data.registerMode === "HELLOASSO" &&
@ -147,7 +147,7 @@ function SelfRegister({data2}) {
}
const currenCat = data?.length > 0 && data[0]?.categorie !== "" ? applyOverCategory(data[0]?.categorie, cat) : "";
const availableCats = data3 ? (currenCat !== "" ? data3.filter(c => c.categories.includes(currenCat)) : data3).sort((a, b) => a.name.localeCompare(b.name)) : []
const availableCats = data3 ? (currenCat !== "" ? data3.filter(c => c.categories.some(c2 => c2.categorie === currenCat)) : data3).sort((a, b) => a.name.localeCompare(b.name)) : []
return <>
{data