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 = ""; String name = "";
List<Categorie> categories; @ElementCollection(fetch = FetchType.EAGER)
@CollectionTable(name = "category_preset_catconfig", joinColumns = @JoinColumn(name = "id_preset"))
long roundDuration; List<CategorieEmbeddable> categories;
long pauseDuration;
SwordType swordType = SwordType.NONE; SwordType swordType = SwordType.NONE;
ShieldType shieldType = ShieldType.NONE; ShieldType shieldType = ShieldType.NONE;
/* Bitmask protections: /*
* 1 - 1 - Head * 1 - 1 - Casque
* 2 - 2 - Throat * 2 - 2 - Gorgerin
* 3 - 4 - Torso * 3 - 4 - Coquille et Protection pelvienne
* 4 - 8 - Arms * 4 - 8 - Gant main(s) armée(s)
* 5 - 16 - Hands * 5 - 16 - Gant main bouclier
* 6 - 32 - Groin * 6 - 32 - Plastron
* 7 - 64 - Legs * 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) @ManyToMany(mappedBy = "categoriesInscrites", fetch = FetchType.LAZY)
private List<RegisterModel> registers = new ArrayList<>(); private List<RegisterModel> registers = new ArrayList<>();
@ -68,4 +74,16 @@ public class CatPresetModel {
BUCKLER 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.setName(preset.getName());
presetModel.setSwordType(preset.getSword()); presetModel.setSwordType(preset.getSword());
presetModel.setShieldType(preset.getShield()); presetModel.setShieldType(preset.getShield());
presetModel.setRoundDuration(preset.getRoundDuration());
presetModel.setPauseDuration(preset.getPauseDuration());
presetModel.setCategories(preset.getCategories()); presetModel.setCategories(preset.getCategories());
presetModel.setMandatoryProtection(preset.getMandatoryProtection()); presetModel.setMandatoryProtection1(preset.getMandatoryProtection1());
presetModel.setMandatoryProtection2(preset.getMandatoryProtection2());
} }
// Remove deleted presets // Remove deleted presets
@ -399,7 +398,8 @@ public class CompetitionService {
g.getCategoriesInscrites().clear(); g.getCategoriesInscrites().clear();
g.getCategoriesInscrites().addAll(cats); g.getCategoriesInscrites().addAll(cats);
g.getCategoriesInscrites() 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)) .chain(model -> Panache.withTransaction(() -> competitionGuestRepository.persist(model))
.call(r -> model.getCompetition().getSystem() == CompetitionSystem.INTERNAL ? .call(r -> model.getCompetition().getSystem() == CompetitionSystem.INTERNAL ?
@ -495,7 +495,8 @@ public class CompetitionService {
} }
r.getCategoriesInscrites().addAll(cats); r.getCategoriesInscrites().addAll(cats);
r.getCategoriesInscrites() 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))) .chain(r -> Panache.withTransaction(() -> registerRepository.persist(r)))
.call(r -> c.getSystem() == CompetitionSystem.INTERNAL ? .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) { } else if ((matchModel.isC1(comb) && win > 0) || matchModel.isC2(comb) && win < 0) {
stat.w++; stat.w++;
stat.win_ids.add(matchModel.getId()); stat.win_ids.add(matchModel.getId());
stat.score += 3; stat.score += 2;
} else { } else {
stat.l++; stat.l++;
} }

View File

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

View File

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

View File

@ -83,6 +83,7 @@
"ajouterUnClub": "Ajouter un club", "ajouterUnClub": "Ajouter un club",
"ajouterUnMembre": "Ajouter un membre", "ajouterUnMembre": "Ajouter un membre",
"all_season": "--- tout les saisons ---", "all_season": "--- tout les saisons ---",
"ans": "ans",
"arme": "Arme", "arme": "Arme",
"au": "au", "au": "au",
"aucun": "Aucun", "aucun": "Aucun",
@ -105,6 +106,7 @@
"button.seDésinscrire": "Se désinscrire", "button.seDésinscrire": "Se désinscrire",
"button.suivant": "Suivant", "button.suivant": "Suivant",
"button.supprimer": "Supprimer", "button.supprimer": "Supprimer",
"casque": "Casque",
"cat.benjamin": "Benjamin", "cat.benjamin": "Benjamin",
"cat.cadet": "Cadet", "cat.cadet": "Cadet",
"cat.catégorieInconnue": "Catégorie inconnue", "cat.catégorieInconnue": "Catégorie inconnue",
@ -260,6 +262,7 @@
"contactInterne": "Contact interne", "contactInterne": "Contact interne",
"contact_one": "Contact", "contact_one": "Contact",
"contact_other": "Contacts", "contact_other": "Contacts",
"coquilleProtectionPelvienne": "Coquille / Protection pelvienne",
"date": "Date", "date": "Date",
"dateDeNaissance": "Date de naissance", "dateDeNaissance": "Date de naissance",
"days": [ "days": [
@ -299,8 +302,12 @@
"faitPar": "Fait par", "faitPar": "Fait par",
"femme": "Femme", "femme": "Femme",
"filtre": "Filtre", "filtre": "Filtre",
"gantMainBouclier": "Gant main de bouclier",
"gantMainsArmées": "Gant main(s) armée(s)",
"gants": "Gants",
"genre": "Genre", "genre": "Genre",
"gestionGroupée": "Gestion groupée", "gestionGroupée": "Gestion groupée",
"gorgerin": "Gorgerin",
"gradeDarbitrage": "Grade d'arbitrage", "gradeDarbitrage": "Grade d'arbitrage",
"h": "H", "h": "H",
"home": { "home": {
@ -480,8 +487,18 @@
"perm.créerDesCompétion": "Créer des compétion", "perm.créerDesCompétion": "Créer des compétion",
"perm.ffsafIntra": "FFSAF intra", "perm.ffsafIntra": "FFSAF intra",
"permission": "Permission", "permission": "Permission",
"peutSinscrire": "Peut s'inscrire?",
"photos": "Photos", "photos": "Photos",
"plastron": "Plastron",
"prenom": "Prénom", "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", "protectionObligatoire": "Protection obligatoire",
"prénomEtNom": "Prénom et nom", "prénomEtNom": "Prénom et nom",
"rechercher": "Rechercher", "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,98 +1,165 @@
import {useTranslation} from "react-i18next";
const ProtectionSelector = ({mandatoryProtection = 0, setMandatoryProtection = () => {} }) => { const ProtectionSelector = ({
shield = true,
mandatoryProtection = 0, setMandatoryProtection = () => {
}
}) => {
const {t} = useTranslation();
const toggle = (bit) => { const toggle = (bit) => {
bit = 1 << (bit - 1);
setMandatoryProtection(v => (v & bit ? v & ~bit : v | bit)); 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"); 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 ( return (
<div> <svg width="200" height="300" viewBox="0 0 160 320">
<svg width="200" height="420" viewBox="0 0 160 320"> <rect
<rect width="160" height="320"
width="160" height="320" fill="#f9fafb00"
fill="#f9fafb00" stroke="#d1d5db"
stroke="#d1d5db" strokeWidth="2"
strokeWidth="2" rx="10"
rx="10" />
/>
{/* Head */} {/* Casque */}
<ellipse <ellipse {...props}
cx="80" cy="35" rx="20" ry="22" cx="80" cy="35" rx="20" ry="22"
fill={color(1)} fill={color(1)}
onClick={() => toggle(1)} onClick={() => toggle(1)}
/> ><title>{t('casque')}</title></ellipse>
{/* Throat / Neck */} {/* Gorgerin */}
<ellipse <ellipse {...props}
cx="80" cy="65" rx="12" ry="8" cx="80" cy="65" rx="12" ry="8"
fill={color(2)} fill={color(2)}
onClick={() => toggle(2)} onClick={() => toggle(2)}
/> ><title>{t('gorgerin')}</title></ellipse>
{/* Torso */} {/* Plastron */}
<ellipse <ellipse {...props}
cx="80" cy="118" rx="30" ry="45" cx="80" cy="118" rx="30" ry="45"
fill={color(4)} fill={color(6)}
onClick={() => toggle(4)} onClick={() => toggle(6)}
/> ><title>{t('plastron')}</title></ellipse>
{/* Arms */} {/* Protection dorsale */}
<ellipse <ellipse {...propsDash}
cx="38" cy="118" rx="12" ry="40" cx="80" cy="118" rx="10" ry="35"
fill={color(8)} fill={color(12)}
onClick={() => toggle(8)} onClick={() => toggle(12)}
/> ><title>{t('protectionDorsale')}</title></ellipse>
<ellipse
cx="122" cy="118" rx="12" ry="40"
fill={color(8)}
onClick={() => toggle(8)}
/>
{/* Hands */} {/* Protection de bras armé(s) */}
<ellipse <ellipse {...props}
cx="38" cy="170" rx="10" ry="12" cx="38" cy="118" rx="12" ry="40"
fill={color(16)} fill={color(7)}
onClick={() => toggle(16)} onClick={() => toggle(7)}
/> ><title>{shield ? t('protectionDeBrasArmé') : t('protectionDeBras')}</title></ellipse>
<ellipse {/* Protection de bras de bouclier */}
cx="122" cy="170" rx="10" ry="12" <ellipse {...props}
fill={color(16)} cx="122" cy="118" rx="12" ry="40"
onClick={() => toggle(16)} fill={color(shield ? 8 : 7)}
/> onClick={() => toggle(shield ? 8 : 7)}
><title>{shield ? t('protectionDeBrasDeBouclier') : t('protectionDeBras')}</title></ellipse>
{/* Legs */} {/* Protection de coudes */}
<ellipse <ellipse {...propsDash}
cx="65" cy="230" rx="14" ry="55" cx="38" cy="118" rx="12" ry="12"
fill={color(64)} fill={color(11)}
onClick={() => toggle(64)} onClick={() => toggle(11)}
/> ><title>{t('protectionDeCoudes')}</title></ellipse>
<ellipse <ellipse {...propsDash}
cx="95" cy="230" rx="14" ry="55" cx="122" cy="118" rx="12" ry="12"
fill={color(64)} fill={color(11)}
onClick={() => toggle(64)} onClick={() => toggle(11)}
/> ><title>{t('protectionDeCoudes')}</title></ellipse>
{/* Groin */} {/* Gant main(s) armée(s) */}
<ellipse <ellipse {...props}
cx="80" cy="170" rx="20" ry="10" cx="38" cy="170" rx="10" ry="12"
fill={color(32)} fill={color(4)}
onClick={() => toggle(32)} onClick={() => toggle(4)}
/> ><title>{shield ? t('gantMainsArmées') : t('gants')}</title></ellipse>
{/* Gant main bouclier */}
<ellipse {...props}
cx="122" cy="170" rx="10" ry="12"
fill={color(shield ? 5 : 4)}
onClick={() => toggle(shield ? 5 : 4)}
><title>{shield ? t('gantMainBouclier') : t('gants')} </title></ellipse>
{/* Feet */} {/* Protection de jambes */}
<ellipse <ellipse {...props}
cx="65" cy="295" rx="16" ry="8" cx="65" cy="230" rx="14" ry="55"
fill="#f0f0f0" fill={color(9)}
/> onClick={() => toggle(9)}
<ellipse ><title>{t('protectionDeJambes')}</title></ellipse>
cx="95" cy="295" rx="16" ry="8" <ellipse {...props}
fill="#f0f0f0" cx="95" cy="230" rx="14" ry="55"
/> fill={color(9)}
</svg> onClick={() => toggle(9)}
</div> ><title>{t('protectionDeJambes')}</title></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(3)}
onClick={() => toggle(3)}
><title>{t('coquilleProtectionPelvienne')}</title></ellipse>
{/* Protection de pieds */}
<ellipse {...props}
cx="65" cy="295" rx="16" ry="8"
fill={color(13)}
onClick={() => toggle(13)}
><title>{t('protectionDePieds')}</title></ellipse>
<ellipse {...props}
cx="95" cy="295" rx="16" ry="8"
fill={color(13)}
onClick={() => toggle(13)}
><title>{t('protectionDePieds')}</title></ellipse>
</svg>
); );
} }

View File

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

View File

@ -3,7 +3,7 @@ import {LoadingProvider, useLoadingSwitcher} from "../../hooks/useLoading.jsx";
import {useFetch} from "../../hooks/useFetch.js"; import {useFetch} from "../../hooks/useFetch.js";
import {AxiosError} from "../../components/AxiosError.jsx"; import {AxiosError} from "../../components/AxiosError.jsx";
import {ThreeDots} from "react-loader-spinner"; 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 {apiAxios, applyOverCategory, CatList, getCatName, getToastMessage} from "../../utils/Tools.js";
import {toast} from "react-toastify"; import {toast} from "react-toastify";
import {SimpleReducer} from "../../utils/SimpleReducer.jsx"; import {SimpleReducer} from "../../utils/SimpleReducer.jsx";
@ -317,6 +317,8 @@ const AutoCompleteInput = ({suggestions = [], handleAdd}) => {
function CategoriesList({error2, availableCats, fistCatInput, categories, setCategories}) { function CategoriesList({error2, availableCats, fistCatInput, categories, setCategories}) {
const {t} = useTranslation(); const {t} = useTranslation();
const id = useId();
return <> return <>
{error2 ? <AxiosError error={error2}/> : <> {error2 ? <AxiosError error={error2}/> : <>
{availableCats && availableCats.length === 0 && <div>{t('aucuneCatégorieDisponible')}</div>} {availableCats && availableCats.length === 0 && <div>{t('aucuneCatégorieDisponible')}</div>}
@ -325,9 +327,9 @@ function CategoriesList({error2, availableCats, fistCatInput, categories, setCat
style={{display: "contents"}}> style={{display: "contents"}}>
<div className="input-group-text"> <div className="input-group-text">
<input ref={index === 0 ? fistCatInput : undefined} className="form-check-input mt-0" type="checkbox" <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)}/> 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>
</div>)} </div>)}
</>} </>}
@ -427,7 +429,7 @@ function Modal({data2, error2, sendRegister, modalState, setModalState, source})
} }
const currenCat = gcat !== "" ? applyOverCategory(gcat, cat) : ""; 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 (availableCats.length === 0) {
if (fistCatInput.current) { if (fistCatInput.current) {
fistCatInput.current = null fistCatInput.current = null
@ -625,7 +627,8 @@ function MakeCentralPanel({data, data2, dispatch, id, setModalState, source}) {
<small> <small>
{req.data.categoriesInscrites.map(catId => data2?.find(c => c.id === catId)).filter(o => o !== undefined) {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 => .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> </small>
</div> </div>
</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])} 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> 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/> || <ShowRegister/>
} }
{data.registerMode === "HELLOASSO" && {data.registerMode === "HELLOASSO" &&
@ -147,7 +147,7 @@ function SelfRegister({data2}) {
} }
const currenCat = data?.length > 0 && data[0]?.categorie !== "" ? applyOverCategory(data[0]?.categorie, cat) : ""; 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 <> return <>
{data {data