dev #105
@ -20,6 +20,11 @@ services:
|
||||
- default
|
||||
- intra
|
||||
- nginx
|
||||
healthcheck:
|
||||
test: [ "CMD", "curl", "-f", "https://intra.ffsaf.fr/api" ]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
|
||||
ffsaf-db:
|
||||
image: public.ecr.aws/docker/library/postgres:17.2
|
||||
|
||||
@ -0,0 +1,89 @@
|
||||
package fr.titionfire.ffsaf.data.model;
|
||||
|
||||
import fr.titionfire.ffsaf.utils.Categorie;
|
||||
import io.quarkus.runtime.annotations.RegisterForReflection;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@RegisterForReflection
|
||||
|
||||
@Entity
|
||||
@Table(name = "category_preset")
|
||||
public class CatPresetModel {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
Long id;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "competition", referencedColumnName = "id")
|
||||
CompetitionModel competition;
|
||||
|
||||
String name = "";
|
||||
|
||||
@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;
|
||||
|
||||
/*
|
||||
* 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 mandatoryProtection1 = 0;
|
||||
int mandatoryProtection2 = 0;
|
||||
|
||||
@ManyToMany(mappedBy = "categoriesInscrites", fetch = FetchType.LAZY)
|
||||
private List<RegisterModel> registers = new ArrayList<>();
|
||||
|
||||
@ManyToMany(mappedBy = "categoriesInscrites", fetch = FetchType.LAZY)
|
||||
private List<CompetitionGuestModel> guest = new ArrayList<>();
|
||||
|
||||
public enum SwordType {
|
||||
NONE,
|
||||
ONE_HAND,
|
||||
TWO_HAND,
|
||||
SABER
|
||||
}
|
||||
|
||||
public enum ShieldType {
|
||||
NONE,
|
||||
STANDARD,
|
||||
ROUND,
|
||||
TEARDROP,
|
||||
BUCKLER
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@RegisterForReflection
|
||||
|
||||
@Embeddable
|
||||
public static class CategorieEmbeddable {
|
||||
Categorie categorie;
|
||||
long roundDuration;
|
||||
long pauseDuration;
|
||||
}
|
||||
}
|
||||
@ -44,4 +44,8 @@ public class CategoryModel {
|
||||
Integer type;
|
||||
|
||||
String liceName = "1";
|
||||
|
||||
@ManyToOne(fetch = FetchType.EAGER)
|
||||
@JoinColumn(name = "id_preset", referencedColumnName = "id")
|
||||
CatPresetModel preset;
|
||||
}
|
||||
|
||||
@ -44,6 +44,7 @@ public class CompetitionGuestModel implements CombModel {
|
||||
String country = "fr";
|
||||
|
||||
Integer weight = null;
|
||||
Integer weightReal = null;
|
||||
|
||||
@ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST)
|
||||
@JoinTable(
|
||||
@ -61,6 +62,14 @@ public class CompetitionGuestModel implements CombModel {
|
||||
)
|
||||
List<CompetitionGuestModel> guest = new ArrayList<>();
|
||||
|
||||
@ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
|
||||
@JoinTable(
|
||||
name = "categories_insc_guest",
|
||||
joinColumns = @JoinColumn(name = "guest_id"),
|
||||
inverseJoinColumns = @JoinColumn(name = "category_id")
|
||||
)
|
||||
List<CatPresetModel> categoriesInscrites = new ArrayList<>();
|
||||
|
||||
public CompetitionGuestModel(String s) {
|
||||
this.fname = s.substring(0, s.indexOf(" "));
|
||||
this.lname = s.substring(s.indexOf(" ") + 1);
|
||||
@ -99,4 +108,8 @@ public class CompetitionGuestModel implements CombModel {
|
||||
}
|
||||
return Stream.concat(comb.stream(), guest.stream()).anyMatch(c -> Objects.equals(c, comb_));
|
||||
}
|
||||
|
||||
public Integer getWeight2() {
|
||||
return (this.weightReal != null) ? this.weightReal : this.weight;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package fr.titionfire.ffsaf.data.model;
|
||||
|
||||
import fr.titionfire.ffsaf.utils.Categorie;
|
||||
import fr.titionfire.ffsaf.utils.CompetitionSystem;
|
||||
import fr.titionfire.ffsaf.utils.RegisterMode;
|
||||
import io.quarkus.runtime.annotations.RegisterForReflection;
|
||||
@ -58,6 +59,10 @@ public class CompetitionModel {
|
||||
@OneToMany(mappedBy = "competition", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
|
||||
List<CompetitionGuestModel> guests = new ArrayList<>();
|
||||
|
||||
@OneToMany(mappedBy = "competition", fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
|
||||
List<CatPresetModel> catPreset = new ArrayList<>();
|
||||
|
||||
List<Categorie> requiredWeight = new ArrayList<>();
|
||||
|
||||
List<Long> banMembre = new ArrayList<>();
|
||||
|
||||
|
||||
@ -11,6 +11,9 @@ import lombok.Setter;
|
||||
import org.hibernate.annotations.OnDelete;
|
||||
import org.hibernate.annotations.OnDeleteAction;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
@ -35,6 +38,7 @@ public class RegisterModel {
|
||||
MembreModel membre;
|
||||
|
||||
Integer weight;
|
||||
Integer weightReal;
|
||||
int overCategory = 0;
|
||||
Categorie categorie;
|
||||
|
||||
@ -46,6 +50,17 @@ public class RegisterModel {
|
||||
@Column(nullable = false, columnDefinition = "boolean default false")
|
||||
boolean lockEdit = false;
|
||||
|
||||
@ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
|
||||
@JoinTable(
|
||||
name = "categories_insc_comb",
|
||||
joinColumns = {
|
||||
@JoinColumn(name = "id_competition", referencedColumnName = "id_competition"),
|
||||
@JoinColumn(name = "id_membre", referencedColumnName = "id_membre")
|
||||
},
|
||||
inverseJoinColumns = @JoinColumn(name = "category_id")
|
||||
)
|
||||
List<CatPresetModel> categoriesInscrites = new ArrayList<>();
|
||||
|
||||
public RegisterModel(CompetitionModel competition, MembreModel membre, Integer weight, int overCategory,
|
||||
Categorie categorie, ClubModel club) {
|
||||
this.id = new RegisterId(competition.getId(), membre.getId());
|
||||
@ -75,4 +90,10 @@ public class RegisterModel {
|
||||
return null;
|
||||
return Categorie.values()[Math.min(tmp.ordinal() + this.overCategory, Categorie.values().length - 1)];
|
||||
}
|
||||
|
||||
public Integer getWeight2() {
|
||||
if (weightReal != null)
|
||||
return weightReal;
|
||||
return weight;
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
package fr.titionfire.ffsaf.data.repository;
|
||||
|
||||
import fr.titionfire.ffsaf.data.model.CatPresetModel;
|
||||
import io.quarkus.hibernate.reactive.panache.PanacheRepositoryBase;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
|
||||
@ApplicationScoped
|
||||
public class CatPresetRepository implements PanacheRepositoryBase<CatPresetModel, Long> {
|
||||
}
|
||||
@ -98,12 +98,14 @@ public class CompetPermService {
|
||||
map.putIfAbsent(model.getId(), "owner");
|
||||
else if (securityCtx.roleHas("federation_admin"))
|
||||
map.putIfAbsent(model.getId(), "admin");
|
||||
else if (securityCtx.isInClubGroup(model.getClub().getId()) && (securityCtx.roleHas(
|
||||
"club_president")
|
||||
|| securityCtx.roleHas("club_respo_intra") || securityCtx.roleHas(
|
||||
"club_secretaire")
|
||||
|| securityCtx.roleHas("club_tresorier")))
|
||||
else if (securityCtx.isInClubGroup(
|
||||
model.getClub().getId()) && (securityCtx.isClubAdmin()))
|
||||
map.putIfAbsent(model.getId(), "admin");
|
||||
else if (model.getAdmin().contains(securityCtx.getSubject()))
|
||||
map.putIfAbsent(model.getId(), "admin");
|
||||
else if (model.getTable().contains(securityCtx.getSubject()))
|
||||
map.putIfAbsent(model.getId(), "table");
|
||||
|
||||
}
|
||||
return map;
|
||||
}));
|
||||
@ -182,12 +184,14 @@ public class CompetPermService {
|
||||
if (o.getSystem() == CompetitionSystem.SAFCA)
|
||||
return hasSafcaViewPerm(securityCtx, o.getId());
|
||||
|
||||
if (o.getAdmin().contains(securityCtx.getSubject()))
|
||||
return Uni.createFrom().nullItem();
|
||||
|
||||
if (!securityCtx.isInClubGroup(o.getClub().getId())) // Only membre club pass here
|
||||
throw new DForbiddenException();
|
||||
|
||||
if (o.getSystem() == CompetitionSystem.INTERNAL)
|
||||
if (securityCtx.roleHas("club_president") || securityCtx.roleHas("club_respo_intra")
|
||||
|| securityCtx.roleHas("club_secretaire") || securityCtx.roleHas("club_tresorier"))
|
||||
if (securityCtx.isClubAdmin())
|
||||
return Uni.createFrom().nullItem();
|
||||
|
||||
throw new DForbiddenException();
|
||||
|
||||
@ -7,10 +7,7 @@ import fr.titionfire.ffsaf.net2.data.SimpleCompet;
|
||||
import fr.titionfire.ffsaf.net2.request.SReqCompet;
|
||||
import fr.titionfire.ffsaf.net2.request.SReqRegister;
|
||||
import fr.titionfire.ffsaf.rest.client.dto.NotificationData;
|
||||
import fr.titionfire.ffsaf.rest.data.CompetitionData;
|
||||
import fr.titionfire.ffsaf.rest.data.RegisterRequestData;
|
||||
import fr.titionfire.ffsaf.rest.data.SimpleCompetData;
|
||||
import fr.titionfire.ffsaf.rest.data.SimpleRegisterComb;
|
||||
import fr.titionfire.ffsaf.rest.data.*;
|
||||
import fr.titionfire.ffsaf.rest.exception.DBadRequestException;
|
||||
import fr.titionfire.ffsaf.rest.exception.DForbiddenException;
|
||||
import fr.titionfire.ffsaf.rest.exception.DNotFoundException;
|
||||
@ -63,6 +60,9 @@ public class CompetitionService {
|
||||
@Inject
|
||||
CompetitionGuestRepository competitionGuestRepository;
|
||||
|
||||
@Inject
|
||||
CatPresetRepository catPresetRepository;
|
||||
|
||||
@Inject
|
||||
ServerCustom serverCustom;
|
||||
|
||||
@ -112,16 +112,14 @@ public class CompetitionService {
|
||||
|
||||
public Uni<CompetitionData> getByIdAdmin(SecurityCtx securityCtx, Long id) {
|
||||
if (id == 0) {
|
||||
return Uni.createFrom()
|
||||
.item(new CompetitionData(null, "", "", "", "", new Date(), new Date(),
|
||||
CompetitionSystem.INTERNAL, RegisterMode.FREE, new Date(), new Date(), true,
|
||||
null, "", "", null, true, true,
|
||||
"", "", "", "", "{}"));
|
||||
return Uni.createFrom().item(new CompetitionData());
|
||||
}
|
||||
return permService.hasAdminViewPerm(securityCtx, id)
|
||||
.call(competitionModel -> Mutiny.fetch(competitionModel.getCatPreset()))
|
||||
.chain(competitionModel -> Mutiny.fetch(competitionModel.getInsc())
|
||||
.chain(insc -> Mutiny.fetch(competitionModel.getGuests())
|
||||
.map(guest -> CompetitionData.fromModel(competitionModel).addInsc(insc, guest))))
|
||||
.map(guest -> CompetitionData.fromModel(competitionModel).addInsc(insc, guest)
|
||||
.addPresets(competitionModel.getCatPreset()))))
|
||||
.chain(data ->
|
||||
vertx.getOrCreateContext().executeBlocking(() -> {
|
||||
keycloakService.getUser(UUID.fromString(data.getOwner()))
|
||||
@ -208,17 +206,21 @@ public class CompetitionService {
|
||||
model.setGuests(new ArrayList<>());
|
||||
model.setUuid(UUID.randomUUID().toString());
|
||||
model.setOwner(securityCtx.getSubject());
|
||||
model.setCatPreset(new ArrayList<>());
|
||||
|
||||
copyData(data, model);
|
||||
|
||||
return Panache.withTransaction(() -> repository.persist(model));
|
||||
}).map(CompetitionData::fromModel)
|
||||
})
|
||||
.call(model -> syncPreset(data, model))
|
||||
.map(CompetitionData::fromModel)
|
||||
.call(c -> (c.getSystem() == CompetitionSystem.SAFCA) ? cacheAccess.invalidate(
|
||||
securityCtx.getSubject()) : Uni.createFrom().nullItem())
|
||||
.call(c -> (c.getSystem() == CompetitionSystem.INTERNAL) ? cacheNoneAccess.invalidate(
|
||||
securityCtx.getSubject()) : Uni.createFrom().nullItem());
|
||||
} else {
|
||||
return permService.hasEditPerm(securityCtx, data.getId())
|
||||
.call(model -> Mutiny.fetch(model.getCatPreset()))
|
||||
.chain(model -> {
|
||||
copyData(data, model);
|
||||
|
||||
@ -237,7 +239,9 @@ public class CompetitionService {
|
||||
}
|
||||
}))
|
||||
.chain(__ -> Panache.withTransaction(() -> repository.persist(model)));
|
||||
}).map(CompetitionData::fromModel)
|
||||
})
|
||||
.call(model -> syncPreset(data, model))
|
||||
.map(model -> CompetitionData.fromModel(model).addPresets(model.getCatPreset()))
|
||||
.call(c -> (c.getSystem() == CompetitionSystem.SAFCA) ? cacheAccess.invalidate(
|
||||
securityCtx.getSubject()) : Uni.createFrom().nullItem())
|
||||
.call(c -> (c.getSystem() == CompetitionSystem.INTERNAL) ? cacheNoneAccess.invalidate(
|
||||
@ -245,6 +249,45 @@ public class CompetitionService {
|
||||
}
|
||||
}
|
||||
|
||||
private Uni<?> syncPreset(CompetitionData data, CompetitionModel model) {
|
||||
List<Long> toRemoveId = model.getCatPreset().stream()
|
||||
.map(CatPresetModel::getId)
|
||||
.filter(id -> data.getPresets().stream().noneMatch(preset -> Objects.equals(preset.getId(), id)))
|
||||
.toList();
|
||||
|
||||
for (PresetData preset : data.getPresets()) {
|
||||
CatPresetModel presetModel;
|
||||
if (preset.getId() != null && preset.getId() > 0) {
|
||||
presetModel = model.getCatPreset().stream()
|
||||
.filter(p -> p.getId().equals(preset.getId()))
|
||||
.findFirst()
|
||||
.orElse(new CatPresetModel());
|
||||
} else {
|
||||
presetModel = new CatPresetModel();
|
||||
model.getCatPreset().add(presetModel);
|
||||
}
|
||||
|
||||
presetModel.setCompetition(model);
|
||||
presetModel.setName(preset.getName());
|
||||
presetModel.setSwordType(preset.getSword());
|
||||
presetModel.setShieldType(preset.getShield());
|
||||
presetModel.setCategories(preset.getCategories());
|
||||
presetModel.setMandatoryProtection1(preset.getMandatoryProtection1());
|
||||
presetModel.setMandatoryProtection2(preset.getMandatoryProtection2());
|
||||
}
|
||||
|
||||
// Remove deleted presets
|
||||
model.getCatPreset().removeIf(presetModel -> toRemoveId.contains(presetModel.getId()));
|
||||
|
||||
return Panache.withTransaction(() -> repository.persist(model)
|
||||
.call(__ -> {
|
||||
if (!toRemoveId.isEmpty()) {
|
||||
return catPresetRepository.delete("id IN ?1", toRemoveId);
|
||||
}
|
||||
return Uni.createFrom().nullItem();
|
||||
}));
|
||||
}
|
||||
|
||||
private void copyData(CompetitionData data, CompetitionModel model) {
|
||||
if (model.getBanMembre() == null)
|
||||
model.setBanMembre(new ArrayList<>());
|
||||
@ -258,6 +301,7 @@ public class CompetitionService {
|
||||
model.setStartRegister(data.getStartRegister());
|
||||
model.setEndRegister(data.getEndRegister());
|
||||
model.setRegisterMode(data.getRegisterMode());
|
||||
model.setRequiredWeight(data.getRequiredWeight());
|
||||
model.setData1(data.getData1());
|
||||
model.setData2(data.getData2());
|
||||
model.setData3(data.getData3());
|
||||
@ -271,11 +315,18 @@ public class CompetitionService {
|
||||
Uni<List<SimpleRegisterComb>> uni = Mutiny.fetch(c.getInsc())
|
||||
.onItem().transformToMulti(Multi.createFrom()::iterable)
|
||||
.onItem().call(combModel -> Mutiny.fetch(combModel.getMembre().getLicences()))
|
||||
.map(cm -> SimpleRegisterComb.fromModel(cm, cm.getMembre().getLicences()))
|
||||
.onItem().call(combModel -> Mutiny.fetch(combModel.getCategoriesInscrites()))
|
||||
.map(cm -> SimpleRegisterComb.fromModel(cm, cm.getMembre().getLicences())
|
||||
.setCategorieInscrite(cm.getCategoriesInscrites()))
|
||||
.collect().asList();
|
||||
return uni
|
||||
.call(l -> Mutiny.fetch(c.getGuests())
|
||||
.map(guest -> guest.stream().map(SimpleRegisterComb::fromModel).toList())
|
||||
.onItem().transformToMulti(Multi.createFrom()::iterable)
|
||||
.filter(g -> !g.isTeam())
|
||||
.onItem().call(guest -> Mutiny.fetch(guest.getCategoriesInscrites()))
|
||||
.map(guest -> SimpleRegisterComb.fromModel(guest)
|
||||
.setCategorieInscrite(guest.getCategoriesInscrites()))
|
||||
.collect().asList()
|
||||
.invoke(l::addAll));
|
||||
});
|
||||
|
||||
@ -290,12 +341,17 @@ public class CompetitionService {
|
||||
model.getClub()))
|
||||
.onItem().transformToMulti(Multi.createFrom()::iterable)
|
||||
.onItem().call(combModel -> Mutiny.fetch(combModel.getMembre().getLicences()))
|
||||
.map(combModel -> SimpleRegisterComb.fromModel(combModel, combModel.getMembre().getLicences()))
|
||||
.onItem().call(combModel -> Mutiny.fetch(combModel.getCategoriesInscrites()))
|
||||
.map(combModel -> SimpleRegisterComb.fromModel(combModel, combModel.getMembre().getLicences())
|
||||
.setCategorieInscrite(combModel.getCategoriesInscrites()))
|
||||
.collect().asList();
|
||||
|
||||
return membreService.getByAccountId(securityCtx.getSubject())
|
||||
.chain(model -> registerRepository.find("competition.id = ?1 AND membre = ?2", id, model).firstResult()
|
||||
.map(rm -> rm == null ? List.of() : List.of(SimpleRegisterComb.fromModel(rm, List.of()))));
|
||||
.call(rm -> rm == null ? Uni.createFrom().voidItem() :
|
||||
Mutiny.fetch(rm.getCategoriesInscrites()))
|
||||
.map(rm -> rm == null ? List.of() : List.of(SimpleRegisterComb.fromModel(rm, List.of())
|
||||
.setCategorieInscrite(rm.getCategoriesInscrites()))));
|
||||
}
|
||||
|
||||
public Uni<SimpleRegisterComb> addRegisterComb(SecurityCtx securityCtx, Long id, RegisterRequestData data,
|
||||
@ -311,8 +367,9 @@ public class CompetitionService {
|
||||
c.getBanMembre().remove(combModel.getId());
|
||||
return Panache.withTransaction(() -> repository.persist(c));
|
||||
})
|
||||
.chain(combModel -> updateRegister(data, c, combModel, true)))
|
||||
.map(r -> SimpleRegisterComb.fromModel(r, r.getMembre().getLicences()));
|
||||
.chain(combModel -> updateRegister(data, c, combModel, true, false)))
|
||||
.map(r -> SimpleRegisterComb.fromModel(r, r.getMembre().getLicences())
|
||||
.setCategorieInscrite(r.getCategoriesInscrites()));
|
||||
} else {
|
||||
return permService.hasEditPerm(securityCtx, id)
|
||||
.chain(c -> competitionGuestRepository.findById(data.getId() * -1)
|
||||
@ -323,7 +380,7 @@ public class CompetitionService {
|
||||
model.setCompetition(c);
|
||||
return model;
|
||||
}))
|
||||
.chain(model -> {
|
||||
.invoke(model -> {
|
||||
model.setFname(data.getFname());
|
||||
if (data.getLname().equals("__team"))
|
||||
model.setLname("_team");
|
||||
@ -332,15 +389,26 @@ public class CompetitionService {
|
||||
model.setGenre(data.getGenre());
|
||||
model.setClub(data.getClub());
|
||||
model.setCountry(data.getCountry());
|
||||
model.setWeight(data.getWeight());
|
||||
model.setWeightReal(data.getWeightReal());
|
||||
model.setCategorie(data.getCategorie());
|
||||
|
||||
return Panache.withTransaction(() -> competitionGuestRepository.persist(model))
|
||||
.call(r -> model.getCompetition().getSystem() == CompetitionSystem.INTERNAL ?
|
||||
sRegister.sendRegister(model.getCompetition().getUuid(),
|
||||
r) : Uni.createFrom().voidItem());
|
||||
if (model.getCompetition().getRequiredWeight().contains(model.getCategorie()))
|
||||
model.setWeight(data.getWeight());
|
||||
})
|
||||
.map(SimpleRegisterComb::fromModel);
|
||||
.call(g -> Mutiny.fetch(g.getCategoriesInscrites()))
|
||||
.call(g -> catPresetRepository.list("competition = ?1 AND id IN ?2", g.getCompetition(),
|
||||
data.getCategoriesInscrites())
|
||||
.invoke(cats -> {
|
||||
g.getCategoriesInscrites().clear();
|
||||
g.getCategoriesInscrites().addAll(cats);
|
||||
g.getCategoriesInscrites()
|
||||
.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 ?
|
||||
sRegister.sendRegister(model.getCompetition().getUuid(),
|
||||
r) : Uni.createFrom().voidItem()))
|
||||
.map(g -> SimpleRegisterComb.fromModel(g).setCategorieInscrite(g.getCategoriesInscrites()));
|
||||
}
|
||||
if ("club".equals(source))
|
||||
return repository.findById(id)
|
||||
@ -359,8 +427,9 @@ public class CompetitionService {
|
||||
if (c.getBanMembre().contains(model.getId()))
|
||||
throw new DForbiddenException(trad.t("insc.err1"));
|
||||
}))
|
||||
.chain(combModel -> updateRegister(data, c, combModel, false)))
|
||||
.map(r -> SimpleRegisterComb.fromModel(r, r.getMembre().getLicences()));
|
||||
.chain(combModel -> updateRegister(data, c, combModel, false, false)))
|
||||
.map(r -> SimpleRegisterComb.fromModel(r, r.getMembre().getLicences())
|
||||
.setCategorieInscrite(r.getCategoriesInscrites()));
|
||||
|
||||
return repository.findById(id)
|
||||
.invoke(Unchecked.consumer(cm -> {
|
||||
@ -374,20 +443,101 @@ public class CompetitionService {
|
||||
if (c.getBanMembre().contains(model.getId()))
|
||||
throw new DForbiddenException(trad.t("insc.err2"));
|
||||
}))
|
||||
.chain(combModel -> updateRegister(data, c, combModel, false)))
|
||||
.map(r -> SimpleRegisterComb.fromModel(r, List.of()));
|
||||
.chain(combModel -> updateRegister(data, c, combModel, false, false)))
|
||||
.map(r -> SimpleRegisterComb.fromModel(r, List.of()).setCategorieInscrite(r.getCategoriesInscrites()));
|
||||
}
|
||||
|
||||
public Uni<List<SimpleRegisterComb>> addRegistersComb(SecurityCtx securityCtx, Long id,
|
||||
List<RegisterRequestData> datas,
|
||||
String source) {
|
||||
if (!"admin".equals(source))
|
||||
return Uni.createFrom().failure(new DForbiddenException());
|
||||
|
||||
return Multi.createFrom().iterable(datas).onItem().transformToUni(data ->
|
||||
makeImportUpdate(securityCtx, id, data).onFailure().recoverWithItem(t -> {
|
||||
SimpleRegisterComb errorComb = new SimpleRegisterComb();
|
||||
errorComb.setLicence(-42);
|
||||
errorComb.setFname("ERROR");
|
||||
errorComb.setLname(t.getMessage());
|
||||
return errorComb;
|
||||
})).concatenate().collect().asList();
|
||||
}
|
||||
|
||||
private Uni<SimpleRegisterComb> makeImportUpdate(SecurityCtx securityCtx, Long id, RegisterRequestData data) {
|
||||
if (data.getLicence() == null || data.getLicence() != -1) { // not a guest
|
||||
return permService.hasEditPerm(securityCtx, id)
|
||||
.chain(c -> findComb(data.getLicence(), data.getFname(), data.getLname())
|
||||
.call(combModel -> Mutiny.fetch(combModel.getLicences()))
|
||||
.call(combModel -> {
|
||||
if (c.getBanMembre() == null)
|
||||
c.setBanMembre(new ArrayList<>());
|
||||
c.getBanMembre().remove(combModel.getId());
|
||||
return Panache.withTransaction(() -> repository.persist(c));
|
||||
})
|
||||
.chain(combModel -> updateRegister(data, c, combModel, true, true)))
|
||||
.map(r -> SimpleRegisterComb.fromModel(r, r.getMembre().getLicences())
|
||||
.setCategorieInscrite(r.getCategoriesInscrites()));
|
||||
} else {
|
||||
return permService.hasEditPerm(securityCtx, id)
|
||||
.chain(c -> findGuestOrInit(data.getFname(), data.getLname(), c))
|
||||
.invoke(Unchecked.consumer(model -> {
|
||||
if (data.getCategorie() == null)
|
||||
throw new DBadRequestException(trad.t("categorie.requise"));
|
||||
model.setCategorie(data.getCategorie());
|
||||
|
||||
if (data.getGenre() == null) {
|
||||
if (model.getGenre() == null)
|
||||
data.setGenre(Genre.NA);
|
||||
} else
|
||||
model.setGenre(data.getGenre());
|
||||
|
||||
if (data.getClub() == null) {
|
||||
if (model.getClub() == null)
|
||||
data.setClub("");
|
||||
} else
|
||||
model.setClub(data.getClub());
|
||||
|
||||
if (data.getCountry() == null) {
|
||||
if (model.getCountry() == null)
|
||||
data.setCountry("FR");
|
||||
} else
|
||||
model.setCountry(data.getCountry());
|
||||
|
||||
if (model.getCompetition().getRequiredWeight().contains(model.getCategorie())) {
|
||||
if (data.getCountry() != null)
|
||||
model.setWeight(data.getWeight());
|
||||
}
|
||||
}))
|
||||
.call(g -> Mutiny.fetch(g.getCategoriesInscrites()))
|
||||
.call(g -> catPresetRepository.list("competition = ?1 AND id IN ?2", g.getCompetition(),
|
||||
data.getCategoriesInscrites())
|
||||
.invoke(cats -> {
|
||||
g.getCategoriesInscrites().clear();
|
||||
g.getCategoriesInscrites().addAll(cats);
|
||||
g.getCategoriesInscrites().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 ?
|
||||
sRegister.sendRegister(model.getCompetition().getUuid(),
|
||||
r) : Uni.createFrom().voidItem()))
|
||||
.map(g -> SimpleRegisterComb.fromModel(g).setCategorieInscrite(g.getCategoriesInscrites()));
|
||||
}
|
||||
}
|
||||
|
||||
private Uni<RegisterModel> updateRegister(RegisterRequestData data, CompetitionModel c,
|
||||
MembreModel combModel, boolean admin) {
|
||||
MembreModel combModel, boolean admin, boolean append) {
|
||||
return registerRepository.find("competition = ?1 AND membre = ?2", c, combModel).firstResult()
|
||||
.onFailure().recoverWithNull()
|
||||
.map(Unchecked.function(r -> {
|
||||
if (r != null) {
|
||||
if (!admin && r.isLockEdit())
|
||||
throw new DForbiddenException(trad.t("insc.err3"));
|
||||
r.setWeight(data.getWeight());
|
||||
r.setOverCategory(data.getOverCategory());
|
||||
if (data.getOverCategory() != null || !append)
|
||||
if (data.getOverCategory() == null)
|
||||
r.setOverCategory(0);
|
||||
else
|
||||
r.setOverCategory(data.getOverCategory());
|
||||
r.setCategorie(
|
||||
(combModel.getBirth_date() == null) ? combModel.getCategorie() :
|
||||
Utils.getCategoryFormBirthDate(combModel.getBirth_date(),
|
||||
@ -395,34 +545,75 @@ public class CompetitionService {
|
||||
int days = Utils.getDaysBeforeCompetition(c.getDate());
|
||||
if (days > -7)
|
||||
r.setClub(combModel.getClub());
|
||||
if (admin)
|
||||
if (c.getRequiredWeight().contains(r.getCategorie2()))
|
||||
if (data.getCountry() != null || !append)
|
||||
r.setWeight(data.getWeight());
|
||||
if (admin) {
|
||||
r.setWeightReal(data.getWeightReal());
|
||||
r.setLockEdit(data.isLockEdit());
|
||||
}
|
||||
} else {
|
||||
r = new RegisterModel(c, combModel, data.getWeight(), data.getOverCategory(),
|
||||
(combModel.getBirth_date() == null) ? combModel.getCategorie() :
|
||||
Utils.getCategoryFormBirthDate(combModel.getBirth_date(),
|
||||
c.getDate()),
|
||||
(combModel.getClub() == null) ? null : combModel.getClub());
|
||||
if (admin)
|
||||
if (admin) {
|
||||
r.setWeightReal(data.getWeightReal());
|
||||
r.setLockEdit(data.isLockEdit());
|
||||
else
|
||||
} else
|
||||
r.setLockEdit(false);
|
||||
}
|
||||
|
||||
if (c.getSystem() == CompetitionSystem.SAFCA) {
|
||||
SReqRegister.sendIfNeed(serverCustom.clients,
|
||||
new CompetitionData.SimpleRegister(r.getMembre().getId(),
|
||||
r.getOverCategory(), r.getWeight(), r.getCategorie(),
|
||||
r.getOverCategory(), r.getWeight2(), r.getCategorie(),
|
||||
(r.getClub() == null) ? null : r.getClub().getId(),
|
||||
(r.getClub() == null) ? null : r.getClub().getName()), c.getId());
|
||||
}
|
||||
return r;
|
||||
}))
|
||||
.call(r -> Mutiny.fetch(r.getCategoriesInscrites()).chain(__ ->
|
||||
catPresetRepository.list("competition = ?1 AND id IN ?2", c, data.getCategoriesInscrites())
|
||||
.invoke(cats -> {
|
||||
if (data.isQuick()) {
|
||||
cats.removeIf(cat -> r.getCategoriesInscrites().stream()
|
||||
.anyMatch(cp -> cp.equals(cat)));
|
||||
} else {
|
||||
r.getCategoriesInscrites().clear();
|
||||
}
|
||||
r.getCategoriesInscrites().addAll(cats);
|
||||
r.getCategoriesInscrites()
|
||||
.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 ?
|
||||
sRegister.sendRegister(c.getUuid(), r) : Uni.createFrom().voidItem());
|
||||
}
|
||||
|
||||
private Uni<CompetitionGuestModel> findGuestOrInit(String fname, String lname, CompetitionModel competition) {
|
||||
if (fname == null || lname == null)
|
||||
return Uni.createFrom().failure(new DBadRequestException(trad.t("nom.et.prenom.requis")));
|
||||
return competitionGuestRepository.find(
|
||||
"unaccent(lname) ILIKE unaccent(?1) AND unaccent(fname) ILIKE unaccent(?2) AND competition = ?3",
|
||||
lname, fname, competition).firstResult()
|
||||
.map(guestModel -> {
|
||||
if (guestModel == null) {
|
||||
CompetitionGuestModel model = new CompetitionGuestModel();
|
||||
model.setFname(fname);
|
||||
if (lname.equals("__team"))
|
||||
model.setLname("_team");
|
||||
else
|
||||
model.setLname(lname);
|
||||
model.setCompetition(competition);
|
||||
return model;
|
||||
}
|
||||
return guestModel;
|
||||
});
|
||||
}
|
||||
|
||||
private Uni<MembreModel> findComb(Long licence, String fname, String lname) {
|
||||
if (licence != null && licence > 0) {
|
||||
return combRepository.find("licence = ?1", licence).firstResult()
|
||||
@ -660,6 +851,12 @@ public class CompetitionService {
|
||||
.call(__ -> cache.invalidate(data.getId()));
|
||||
}
|
||||
|
||||
public Uni<List<PresetData>> getPresetsForCompetition(SecurityCtx securityCtx, Long id) {
|
||||
return permService.hasViewPerm(securityCtx, id)
|
||||
.chain(cm -> Mutiny.fetch(cm.getCatPreset()))
|
||||
.map(p -> p.stream().map(PresetData::fromModel).toList());
|
||||
}
|
||||
|
||||
public Uni<Response> unregisterHelloAsso(NotificationData data) {
|
||||
if (!data.getState().equals("Refunded"))
|
||||
return Uni.createFrom().item(Response.ok().build());
|
||||
@ -693,8 +890,8 @@ public class CompetitionService {
|
||||
public Uni<Response> registerHelloAsso(NotificationData data) {
|
||||
String organizationSlug = data.getOrganizationSlug();
|
||||
String formSlug = data.getFormSlug();
|
||||
RegisterRequestData req = new RegisterRequestData(null, "", "", null, 0, false, null, Categorie.CADET, Genre.NA,
|
||||
null, "fr");
|
||||
RegisterRequestData req = new RegisterRequestData(null, "", "", null, null, 0, false, new ArrayList<>(), null,
|
||||
Categorie.CADET, Genre.NA, null, "fr", false);
|
||||
|
||||
return repository.find("data1 = ?1 AND data2 = ?2", organizationSlug, formSlug).firstResult()
|
||||
.onFailure().recoverWithNull()
|
||||
@ -728,7 +925,7 @@ public class CompetitionService {
|
||||
.call(m -> Panache.withTransaction(() ->
|
||||
helloAssoRepository.persist(
|
||||
new HelloAssoRegisterModel(cm, m, data.getId()))))
|
||||
.chain(m -> updateRegister(req, cm, m, true)))
|
||||
.chain(m -> updateRegister(req, cm, m, true, true)))
|
||||
.onFailure().recoverWithItem(throwable -> {
|
||||
fail.add("%s %s - licence n°%d".formatted(item.getUser().getLastName(),
|
||||
item.getUser().getFirstName(), optional.get()));
|
||||
|
||||
@ -381,11 +381,15 @@ public class KeycloakService {
|
||||
}
|
||||
|
||||
public Optional<UserRepresentation> getUserById(String userId) {
|
||||
UserResource user = keycloak.realm(realm).users().get(userId);
|
||||
if (user == null)
|
||||
try {
|
||||
UserResource user = keycloak.realm(realm).users().get(userId);
|
||||
if (user == null)
|
||||
return Optional.empty();
|
||||
else
|
||||
return Optional.of(user.toRepresentation());
|
||||
} catch (Exception e) {
|
||||
return Optional.empty();
|
||||
else
|
||||
return Optional.of(user.toRepresentation());
|
||||
}
|
||||
}
|
||||
|
||||
private String makeLogin(MembreModel model) {
|
||||
|
||||
@ -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++;
|
||||
}
|
||||
|
||||
@ -1,10 +1,7 @@
|
||||
package fr.titionfire.ffsaf.rest;
|
||||
|
||||
import fr.titionfire.ffsaf.domain.service.CompetitionService;
|
||||
import fr.titionfire.ffsaf.rest.data.CompetitionData;
|
||||
import fr.titionfire.ffsaf.rest.data.RegisterRequestData;
|
||||
import fr.titionfire.ffsaf.rest.data.SimpleCompetData;
|
||||
import fr.titionfire.ffsaf.rest.data.SimpleRegisterComb;
|
||||
import fr.titionfire.ffsaf.rest.data.*;
|
||||
import fr.titionfire.ffsaf.utils.SecurityCtx;
|
||||
import io.quarkus.security.Authenticated;
|
||||
import io.smallrye.mutiny.Uni;
|
||||
@ -53,6 +50,16 @@ public class CompetitionEndpoints {
|
||||
return service.addRegisterComb(securityCtx, id, data, source);
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("{id}/registers/{source}")
|
||||
@Authenticated
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Operation(hidden = true)
|
||||
public Uni<List<SimpleRegisterComb>> addRegistersComb(@PathParam("id") Long id, @PathParam("source") String source,
|
||||
List<RegisterRequestData> data) {
|
||||
return service.addRegistersComb(securityCtx, id, data, source);
|
||||
}
|
||||
|
||||
@DELETE
|
||||
@Path("{id}/register/{comb_id}/{source}")
|
||||
@Authenticated
|
||||
@ -79,6 +86,13 @@ public class CompetitionEndpoints {
|
||||
return service.getInternalData(securityCtx, id);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("{id}/categories")
|
||||
@Authenticated
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Uni<List<PresetData>> getPresetsForCompetition(@PathParam("id") Long id) {
|
||||
return service.getPresetsForCompetition(securityCtx, id);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("all")
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package fr.titionfire.ffsaf.rest.data;
|
||||
|
||||
import fr.titionfire.ffsaf.data.model.CatPresetModel;
|
||||
import fr.titionfire.ffsaf.data.model.CompetitionGuestModel;
|
||||
import fr.titionfire.ffsaf.data.model.CompetitionModel;
|
||||
import fr.titionfire.ffsaf.data.model.RegisterModel;
|
||||
@ -10,6 +11,7 @@ import io.quarkus.runtime.annotations.RegisterForReflection;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
@ -33,7 +35,7 @@ public class CompetitionData {
|
||||
private Long club;
|
||||
private String clubName;
|
||||
private String owner;
|
||||
private List<SimpleRegister> registers;
|
||||
private List<SimpleRegister> registers; // for SAFCA
|
||||
private boolean canEdit;
|
||||
private boolean canEditRegisters;
|
||||
private String data1;
|
||||
@ -41,6 +43,15 @@ public class CompetitionData {
|
||||
private String data3;
|
||||
private String data4;
|
||||
private String config;
|
||||
private List<PresetData> presets;
|
||||
private List<Categorie> requiredWeight;
|
||||
|
||||
public CompetitionData() {
|
||||
this(null, "", "", "", "", new Date(), new Date(),
|
||||
CompetitionSystem.INTERNAL, RegisterMode.FREE, new Date(), new Date(), true,
|
||||
null, "", "", null, true, true,
|
||||
"", "", "", "", "{}", new ArrayList<>(), new ArrayList<>());
|
||||
}
|
||||
|
||||
public static CompetitionData fromModel(CompetitionModel model) {
|
||||
if (model == null)
|
||||
@ -50,7 +61,8 @@ public class CompetitionData {
|
||||
model.getUuid(), model.getDate(), model.getTodate(), model.getSystem(),
|
||||
model.getRegisterMode(), model.getStartRegister(), model.getEndRegister(), model.isPublicVisible(),
|
||||
model.getClub().getId(), model.getClub().getName(), model.getOwner(), null, false, false,
|
||||
model.getData1(), model.getData2(), model.getData3(), model.getData4(), model.getConfig());
|
||||
model.getData1(), model.getData2(), model.getData3(), model.getData4(), model.getConfig(),
|
||||
new ArrayList<>(), model.getRequiredWeight());
|
||||
}
|
||||
|
||||
public static CompetitionData fromModelLight(CompetitionModel model) {
|
||||
@ -61,7 +73,7 @@ public class CompetitionData {
|
||||
model.getAdresse(), "", model.getDate(), model.getTodate(), null,
|
||||
model.getRegisterMode(), model.getStartRegister(), model.getEndRegister(), model.isPublicVisible(),
|
||||
null, model.getClub().getName(), "", null, false, false,
|
||||
"", "", "", "", "{}");
|
||||
"", "", "", "", "{}", new ArrayList<>(), model.getRequiredWeight());
|
||||
|
||||
if (model.getRegisterMode() == RegisterMode.HELLOASSO) {
|
||||
out.setData1(model.getData1());
|
||||
@ -75,15 +87,20 @@ public class CompetitionData {
|
||||
public CompetitionData addInsc(List<RegisterModel> insc, List<CompetitionGuestModel> guests) {
|
||||
this.registers = Stream.concat(
|
||||
insc.stream()
|
||||
.map(i -> new SimpleRegister(i.getMembre().getId(), i.getOverCategory(), i.getWeight(),
|
||||
.map(i -> new SimpleRegister(i.getMembre().getId(), i.getOverCategory(), i.getWeight2(),
|
||||
i.getCategorie(), (i.getClub() == null) ? null : i.getClub().getId(),
|
||||
(i.getClub() == null) ? null : i.getClub().getName())),
|
||||
guests.stream()
|
||||
.map(i -> new SimpleRegister(i.getId() * -1, 0, i.getWeight(),
|
||||
.map(i -> new SimpleRegister(i.getId() * -1, 0, i.getWeight2(),
|
||||
i.getCategorie(), null, i.getClub()))).toList();
|
||||
return this;
|
||||
}
|
||||
|
||||
public CompetitionData addPresets(List<CatPresetModel> presets) {
|
||||
this.presets = presets.stream().map(PresetData::fromModel).toList();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@RegisterForReflection
|
||||
|
||||
29
src/main/java/fr/titionfire/ffsaf/rest/data/PresetData.java
Normal file
29
src/main/java/fr/titionfire/ffsaf/rest/data/PresetData.java
Normal file
@ -0,0 +1,29 @@
|
||||
package fr.titionfire.ffsaf.rest.data;
|
||||
|
||||
import fr.titionfire.ffsaf.data.model.CatPresetModel;
|
||||
import io.quarkus.runtime.annotations.RegisterForReflection;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@RegisterForReflection
|
||||
public class PresetData {
|
||||
private Long id;
|
||||
private String name;
|
||||
private CatPresetModel.SwordType sword;
|
||||
private CatPresetModel.ShieldType shield;
|
||||
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.getMandatoryProtection1(), model.getMandatoryProtection2());
|
||||
}
|
||||
}
|
||||
@ -7,6 +7,8 @@ import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@ -17,8 +19,10 @@ public class RegisterRequestData {
|
||||
private String lname;
|
||||
|
||||
private Integer weight;
|
||||
private int overCategory;
|
||||
private Integer weightReal;
|
||||
private Integer overCategory;
|
||||
private boolean lockEdit = false;
|
||||
private List<Long> categoriesInscrites;
|
||||
|
||||
// for guest registration only
|
||||
private Long id = null;
|
||||
@ -26,4 +30,6 @@ public class RegisterRequestData {
|
||||
private Genre genre = Genre.NA;
|
||||
private String club = null;
|
||||
private String country = null;
|
||||
|
||||
private boolean quick = false;
|
||||
}
|
||||
|
||||
@ -1,9 +1,6 @@
|
||||
package fr.titionfire.ffsaf.rest.data;
|
||||
|
||||
import fr.titionfire.ffsaf.data.model.CompetitionGuestModel;
|
||||
import fr.titionfire.ffsaf.data.model.LicenceModel;
|
||||
import fr.titionfire.ffsaf.data.model.MembreModel;
|
||||
import fr.titionfire.ffsaf.data.model.RegisterModel;
|
||||
import fr.titionfire.ffsaf.data.model.*;
|
||||
import fr.titionfire.ffsaf.net2.data.SimpleClubModel;
|
||||
import fr.titionfire.ffsaf.utils.Categorie;
|
||||
import fr.titionfire.ffsaf.utils.Genre;
|
||||
@ -11,11 +8,14 @@ import fr.titionfire.ffsaf.utils.Utils;
|
||||
import io.quarkus.runtime.annotations.RegisterForReflection;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@RegisterForReflection
|
||||
public class SimpleRegisterComb {
|
||||
private long id;
|
||||
@ -27,9 +27,11 @@ public class SimpleRegisterComb {
|
||||
private SimpleClubModel club;
|
||||
private Integer licence;
|
||||
private Integer weight;
|
||||
private Integer weightReal;
|
||||
private int overCategory;
|
||||
private boolean hasLicenceActive;
|
||||
private boolean lockEdit;
|
||||
private List<Long> categoriesInscrites;
|
||||
|
||||
public static SimpleRegisterComb fromModel(RegisterModel register, List<LicenceModel> licences) {
|
||||
MembreModel membreModel = register.getMembre();
|
||||
@ -37,15 +39,21 @@ public class SimpleRegisterComb {
|
||||
membreModel.getGenre(), membreModel.getCountry(),
|
||||
(register.getCategorie() == null) ? null : register.getCategorie(),
|
||||
SimpleClubModel.fromModel(register.getClub()), membreModel.getLicence(), register.getWeight(),
|
||||
register.getOverCategory(),
|
||||
register.getWeightReal(), register.getOverCategory(),
|
||||
licences.stream().anyMatch(l -> l.isValidate() && l.getSaison() == Utils.getSaison()),
|
||||
register.isLockEdit());
|
||||
register.isLockEdit(), new ArrayList<>());
|
||||
}
|
||||
|
||||
public static SimpleRegisterComb fromModel(CompetitionGuestModel guest) {
|
||||
return new SimpleRegisterComb(guest.getId() * -1, guest.getFname(), guest.getLname(),
|
||||
guest.getGenre(), guest.getCountry(), guest.getCategorie(),
|
||||
new SimpleClubModel(null, guest.getClub(), "fr", null),
|
||||
null, guest.getWeight(), 0, false, false);
|
||||
null, guest.getWeight(), guest.getWeightReal(), 0, false, false,
|
||||
new ArrayList<>());
|
||||
}
|
||||
|
||||
public SimpleRegisterComb setCategorieInscrite(List<CatPresetModel> presets) {
|
||||
this.categoriesInscrites = presets.stream().map(CatPresetModel::getId).toList();
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,8 +44,8 @@ service.momentanement.indisponible=Service momentan
|
||||
asso.introuvable=Association introuvable
|
||||
erreur.lors.calcul.du.trie=Erreur lors du calcul du tri
|
||||
page.out.of.range=Page out of range
|
||||
le.membre.appartient.pas.a.votre.club=Le membre n°%d n?appartient pas à votre club
|
||||
email.deja.utilise.par=L?adresse e-mail '%s' est déjà utilisée par %s %s
|
||||
le.membre.appartient.pas.a.votre.club=Le membre n°%d n'appartient pas à votre club
|
||||
email.deja.utilise.par=L'adresse e-mail '%s' est déjà utilisée par %s %s
|
||||
try.edit.licence=Pour enregistrer un nouveau membre, veuillez laisser le champ licence vide. (Tentative de modification non autorisée du nom sur la licence %d pour %s %s)
|
||||
email.deja.utilise=Adresse e-mail déjà utilisée
|
||||
regiter.new.membre=Pour enregistrer un nouveau membre, veuillez utiliser le bouton prévu à cet effet.
|
||||
@ -61,23 +61,23 @@ licence.rm.err1=Impossible de supprimer une licence pour laquelle un paiement es
|
||||
licence.deja.demandee=Licence déjà demandée
|
||||
impossible.de.supprimer.une.licence.deja.validee=Impossible de supprimer une licence déjà validée
|
||||
impossible.de.supprimer.une.licence.deja.payee=Impossible de supprimer une licence déjà payée
|
||||
vous.ne.pouvez.pas.creer.de.competition=Vous n?êtes pas autorisé à créer une compétition
|
||||
vous.ne.pouvez.pas.creer.de.competition=Vous n'êtes pas autorisé à créer une compétition
|
||||
user.not.found=Utilisateur %s introuvable
|
||||
inscription.fermee=Inscription fermée
|
||||
insc.err1=Vous n?êtes pas autorisé à inscrire ce membre (décision de l?administrateur de la compétition)
|
||||
insc.err2=Vous n?êtes pas autorisé à vous inscrire (décision de l?administrateur de la compétition)
|
||||
insc.err3=Modification bloquée par l?administrateur de la compétition
|
||||
insc.err1=Vous n'êtes pas autorisé à inscrire ce membre (décision de l'administrateur de la compétition)
|
||||
insc.err2=Vous n'êtes pas autorisé à vous inscrire (décision de l'administrateur de la compétition)
|
||||
insc.err3=Modification bloquée par l'administrateur de la compétition
|
||||
licence.non.trouve=Licence %s introuvable
|
||||
nom.et.prenom.requis=Nom et prénom obligatoires
|
||||
combattant.non.trouve=Combattant %s %s introuvable
|
||||
le.membre.n.existe.pas=Le membre n°%d n?existe pas
|
||||
le.membre.n.existe.pas=Le membre n°%d n'existe pas
|
||||
competition.is.not.internal=Competition is not INTERNAL
|
||||
erreur.de.format.des.contacts=Format des contacts invalide
|
||||
competition.not.found=Compétition introuvable
|
||||
saison.non.valid=Saison invalide
|
||||
demande.d.affiliation.deja.existante=Une demande d?affiliation existe déjà
|
||||
demande.d.affiliation.deja.existante=Une demande d'affiliation existe déjà
|
||||
affiliation.deja.existante=Affiliation déjà existante
|
||||
licence.membre.n.1.inconnue=Licence du membre n°1 inconnue
|
||||
licence.membre.n.2.inconnue=Licence du membre n°2 inconnue
|
||||
licence.membre.n.3.inconnue=Licence du membre n°3 inconnue
|
||||
demande.d.affiliation.non.trouve=Demande d?affiliation introuvable
|
||||
demande.d.affiliation.non.trouve=Demande d'affiliation introuvable
|
||||
55
src/main/webapp/package-lock.json
generated
55
src/main/webapp/package-lock.json
generated
@ -18,7 +18,7 @@
|
||||
"@fortawesome/react-fontawesome": "^3.1.1",
|
||||
"axios": "^1.13.2",
|
||||
"browser-image-compression": "^2.0.2",
|
||||
"i18next": "^25.7.4",
|
||||
"i18next": "^25.8.0",
|
||||
"i18next-browser-languagedetector": "^8.2.0",
|
||||
"i18next-http-backend": "^3.0.2",
|
||||
"jszip": "^3.10.1",
|
||||
@ -33,12 +33,12 @@
|
||||
"react-loader-spinner": "^8.0.2",
|
||||
"react-router-dom": "^7.12.0",
|
||||
"react-toastify": "^11.0.5",
|
||||
"recharts": "^3.6.0",
|
||||
"xlsx": "^0.18.5",
|
||||
"recharts": "^3.7.0",
|
||||
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz",
|
||||
"xlsx-js-style": "^1.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^19.2.8",
|
||||
"@types/react": "^19.2.9",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitejs/plugin-react": "^5.1.2",
|
||||
"eslint": "^9.39.2",
|
||||
@ -1606,9 +1606,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "19.2.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.8.tgz",
|
||||
"integrity": "sha512-3MbSL37jEchWZz2p2mjntRZtPt837ij10ApxKfgmXCTuHWagYg7iA5bqPw6C8BMPfwidlvfPI/fxOc42HLhcyg==",
|
||||
"version": "19.2.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.9.tgz",
|
||||
"integrity": "sha512-Lpo8kgb/igvMIPeNV2rsYKTgaORYdO1XGVZ4Qz3akwOj0ySGYMPlQWa8BaLn0G63D1aSaAQ5ldR06wCpChQCjA==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
@ -2048,14 +2048,6 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/codepage": {
|
||||
"version": "1.15.0",
|
||||
"resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz",
|
||||
"integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==",
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
@ -3357,9 +3349,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/i18next": {
|
||||
"version": "25.7.4",
|
||||
"resolved": "https://registry.npmjs.org/i18next/-/i18next-25.7.4.tgz",
|
||||
"integrity": "sha512-hRkpEblXXcXSNbw8mBNq9042OEetgyB/ahc/X17uV/khPwzV+uB8RHceHh3qavyrkPJvmXFKXME2Sy1E0KjAfw==",
|
||||
"version": "25.8.0",
|
||||
"resolved": "https://registry.npmjs.org/i18next/-/i18next-25.8.0.tgz",
|
||||
"integrity": "sha512-urrg4HMFFMQZ2bbKRK7IZ8/CTE7D8H4JRlAwqA2ZwDRFfdd0K/4cdbNNLgfn9mo+I/h9wJu61qJzH7jCFAhUZQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
@ -3374,6 +3366,7 @@
|
||||
"url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.28.4"
|
||||
@ -4674,9 +4667,13 @@
|
||||
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
|
||||
},
|
||||
"node_modules/recharts": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/recharts/-/recharts-3.6.0.tgz",
|
||||
"integrity": "sha512-L5bjxvQRAe26RlToBAziKUB7whaGKEwD3znoM6fz3DrTowCIC/FnJYnuq1GEzB8Zv2kdTfaxQfi5GoH0tBinyg==",
|
||||
"version": "3.7.0",
|
||||
"resolved": "https://registry.npmjs.org/recharts/-/recharts-3.7.0.tgz",
|
||||
"integrity": "sha512-l2VCsy3XXeraxIID9fx23eCb6iCBsxUQDnE8tWm6DFdszVAO7WVY/ChAD9wVit01y6B2PMupYiMmQwhgPHc9Ew==",
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
"www"
|
||||
],
|
||||
"dependencies": {
|
||||
"@reduxjs/toolkit": "1.x.x || 2.x.x",
|
||||
"clsx": "^2.1.1",
|
||||
@ -5735,18 +5732,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/xlsx": {
|
||||
"version": "0.18.5",
|
||||
"resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz",
|
||||
"integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==",
|
||||
"dependencies": {
|
||||
"adler-32": "~1.3.0",
|
||||
"cfb": "~1.2.1",
|
||||
"codepage": "~1.15.0",
|
||||
"crc-32": "~1.2.1",
|
||||
"ssf": "~0.11.2",
|
||||
"wmf": "~1.0.1",
|
||||
"word": "~0.3.0"
|
||||
},
|
||||
"version": "0.20.3",
|
||||
"resolved": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz",
|
||||
"integrity": "sha512-oLDq3jw7AcLqKWH2AhCpVTZl8mf6X2YReP+Neh0SJUzV/BdZYjth94tG5toiMB1PPrYtxOCfaoUCkvtuH+3AJA==",
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"xlsx": "bin/xlsx.njs"
|
||||
},
|
||||
|
||||
@ -20,7 +20,7 @@
|
||||
"@fortawesome/react-fontawesome": "^3.1.1",
|
||||
"axios": "^1.13.2",
|
||||
"browser-image-compression": "^2.0.2",
|
||||
"i18next": "^25.7.4",
|
||||
"i18next": "^25.8.0",
|
||||
"i18next-browser-languagedetector": "^8.2.0",
|
||||
"i18next-http-backend": "^3.0.2",
|
||||
"jszip": "^3.10.1",
|
||||
@ -35,12 +35,12 @@
|
||||
"react-loader-spinner": "^8.0.2",
|
||||
"react-router-dom": "^7.12.0",
|
||||
"react-toastify": "^11.0.5",
|
||||
"recharts": "^3.6.0",
|
||||
"xlsx": "^0.18.5",
|
||||
"recharts": "^3.7.0",
|
||||
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz",
|
||||
"xlsx-js-style": "^1.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^19.2.8",
|
||||
"@types/react": "^19.2.9",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitejs/plugin-react": "^5.1.2",
|
||||
"eslint": "^9.39.2",
|
||||
|
||||
@ -102,6 +102,7 @@
|
||||
"supprimer": "Delete",
|
||||
"sélectionneLesModesDaffichage": "Select display modes",
|
||||
"sélectionner": "Select",
|
||||
"team": "Team",
|
||||
"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",
|
||||
@ -113,6 +114,9 @@
|
||||
"toast.matchs.create.error": "Error while creating matches.",
|
||||
"toast.matchs.create.pending": "Creating matches in progress...",
|
||||
"toast.matchs.create.success": "Matches created successfully.",
|
||||
"toast.team.update.error": "Error while updating team",
|
||||
"toast.team.update.pending": "Updating team...",
|
||||
"toast.team.update.success": "Team updated!",
|
||||
"toast.updateCategory.error": "Error while updating the category",
|
||||
"toast.updateCategory.pending": "Updating category...",
|
||||
"toast.updateCategory.success": "Category updated!",
|
||||
@ -125,12 +129,8 @@
|
||||
"toast.updateTrees.init.success": "Trees created!",
|
||||
"toast.updateTrees.pending": "Updating tournament trees...",
|
||||
"toast.updateTrees.success": "Trees updated!",
|
||||
"toast.team.update.error": "Error while updating team",
|
||||
"toast.team.update.pending": "Updating team...",
|
||||
"toast.team.update.success": "Team updated!",
|
||||
"tournoi": "Tournament",
|
||||
"tournois": "Tournaments",
|
||||
"team": "Team",
|
||||
"tousLesMatchs": "All matches",
|
||||
"toutConserver": "Keep all",
|
||||
"ttm.admin.obs": "Short click: Download resources. Long click: Create OBS configuration",
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
{
|
||||
"(optionnelle)": "(optional)",
|
||||
"---SansClub---": "--- no club ---",
|
||||
"---TousLesAges---": "--- all ages ---",
|
||||
"---ToutLesClubs---": "--- all clubs ---",
|
||||
"---ToutLesPays---": "--- all countries ---",
|
||||
"---TouteLesCatégories---": "--- all categories ---",
|
||||
@ -8,6 +9,7 @@
|
||||
"--SélectionnerCatégorie--": "-- Select category --",
|
||||
"1Catégorie": "+1 category",
|
||||
"2Catégorie": "+2 categories",
|
||||
"LesModificationsNontEnregistrer": "/!\\ The changes have not yet been saved, click save /!\\",
|
||||
"activer": "Activate",
|
||||
"admin": "Administration",
|
||||
"administrateur": "Administrator",
|
||||
@ -74,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}}",
|
||||
@ -81,11 +84,15 @@
|
||||
"ajouterUnClub": "Add a club",
|
||||
"ajouterUnMembre": "Add a member",
|
||||
"all_season": "--- all seasons ---",
|
||||
"ans": "years",
|
||||
"arme": "Weapon",
|
||||
"au": "to",
|
||||
"aucun": "None",
|
||||
"aucunMembreSélectionné": "No member selected",
|
||||
"aucuneCatégorieDisponible": "No categories available at this time.",
|
||||
"back": "« back",
|
||||
"blason": "Coat of arms",
|
||||
"bouclier": "Shield",
|
||||
"bureau": "Board",
|
||||
"button.accepter": "Accept",
|
||||
"button.ajouter": "Add",
|
||||
@ -93,7 +100,6 @@
|
||||
"button.appliquer": "Apply",
|
||||
"button.confirmer": "Confirm",
|
||||
"button.créer": "Create",
|
||||
"button.enregister": "Save",
|
||||
"button.enregistrer": "Save",
|
||||
"button.fermer": "Close",
|
||||
"button.modifier": "Edit",
|
||||
@ -101,6 +107,7 @@
|
||||
"button.seDésinscrire": "Unsubscribe",
|
||||
"button.suivant": "Next",
|
||||
"button.supprimer": "Delete",
|
||||
"casque": "Helmet",
|
||||
"cat.benjamin": "Benjamin",
|
||||
"cat.cadet": "Cadet",
|
||||
"cat.catégorieInconnue": "Unknown category",
|
||||
@ -115,7 +122,9 @@
|
||||
"cat.vétéran2": "Veteran 2",
|
||||
"categorie": "category",
|
||||
"catégorie": "Category",
|
||||
"catégorieàAjouter": "Category to add",
|
||||
"certificatMédical": "Medical certificate",
|
||||
"champAttendu": "Expected field",
|
||||
"chargement...": "Loading...",
|
||||
"chargerLexcel": "Load Excel",
|
||||
"chargerLexcel.msg": "Please use the file above as a template; do not rename the columns or modify the license numbers.",
|
||||
@ -146,6 +155,7 @@
|
||||
"club_one": "Club",
|
||||
"club_other": "Clubs",
|
||||
"club_zero": "No club",
|
||||
"colonneDansLeFichier": "Column in the file",
|
||||
"combattant": "fighter",
|
||||
"comp.aff.blason": "Display the club's coat of arms on screens",
|
||||
"comp.aff.flag": "Display the fighter's country on screens",
|
||||
@ -186,7 +196,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",
|
||||
@ -221,6 +233,8 @@
|
||||
"comp.toast.register.add.error": "Fighter not found",
|
||||
"comp.toast.register.add.pending": "Search in progress",
|
||||
"comp.toast.register.add.success": "Fighter found and added/updated",
|
||||
"comp.toast.register.addMultiple.success_one": "Successful import for 1 fighter",
|
||||
"comp.toast.register.addMultiple.success_other": "Successful import for {{count}} fighters",
|
||||
"comp.toast.register.ban.error": "Error",
|
||||
"comp.toast.register.ban.pending": "Unregistration in progress",
|
||||
"comp.toast.register.ban.success": "Fighter unregistered and banned",
|
||||
@ -249,11 +263,13 @@
|
||||
"compte": "Account",
|
||||
"compétition": "Competition",
|
||||
"configuration": "Configuration",
|
||||
"configurationDeLaCatégorie": "Category configuration",
|
||||
"conserverLancienEmail": "Keep the old email",
|
||||
"contactAdministratif": "Administrative contact",
|
||||
"contactInterne": "Internal contact",
|
||||
"contact_one": "Contact",
|
||||
"contact_other": "Contacts",
|
||||
"coquilleProtectionPelvienne": "Shell / Pelvic protection",
|
||||
"date": "Date",
|
||||
"dateDeNaissance": "Date of birth",
|
||||
"days": [
|
||||
@ -274,6 +290,8 @@
|
||||
"donnéesAdministratives": "Administrative data",
|
||||
"du": "From",
|
||||
"dun": "of a",
|
||||
"duréePause": "Pause duration",
|
||||
"duréeRound": "Round duration",
|
||||
"définirLidDuCompte": "Define account ID",
|
||||
"editionDeL'affiliation": "Editing affiliation",
|
||||
"editionDeLaDemande": "Editing request",
|
||||
@ -286,13 +304,18 @@
|
||||
"erreurDePaiement": "Payment error😕",
|
||||
"erreurDePaiement.detail": "Error message:",
|
||||
"erreurDePaiement.msg": "An error occurred while processing your payment. Please try again later.",
|
||||
"erreurPourLinscription": "Registration error",
|
||||
"espaceAdministration": "Administration space",
|
||||
"f": "F",
|
||||
"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": {
|
||||
@ -304,6 +327,9 @@
|
||||
},
|
||||
"homme": "Male",
|
||||
"horairesD'entraînements": "Training schedules",
|
||||
"importationDuFichier": "Importing the file",
|
||||
"importerDesCombattants": "Import fighters",
|
||||
"importerDesInvités": "Import guests",
|
||||
"information": "Information",
|
||||
"invité": "guest",
|
||||
"keepEmpty": "Leave blank to make no changes.",
|
||||
@ -312,6 +338,8 @@
|
||||
"licenceNo": "License no. {{no}}",
|
||||
"lieu": "Place",
|
||||
"lieuxDentraînements": "Training locations",
|
||||
"ligneIgnorée1": "Line ignored: missing name, first name or category.",
|
||||
"ligneIgnorée2": "Line ignored: missing first name or license.",
|
||||
"loading": "Loading...",
|
||||
"me": {
|
||||
"result": {
|
||||
@ -440,6 +468,8 @@
|
||||
"nouveauClub": "New club",
|
||||
"nouveauMembre": "New member",
|
||||
"nouvelEmail": "New email",
|
||||
"numéroDeLaLigneDentête": "Header line number",
|
||||
"numéroDeLigne": "Line number",
|
||||
"ou": "or",
|
||||
"oui": "Yes",
|
||||
"outdated_session": {
|
||||
@ -472,8 +502,21 @@
|
||||
"perm.créerDesCompétion": "Create competitions",
|
||||
"perm.ffsafIntra": "FFSAF intra",
|
||||
"permission": "Permission",
|
||||
"peutSinscrire": "Can register?",
|
||||
"photos": "Photos",
|
||||
"plastron": "Breastplate",
|
||||
"poids": "Weight",
|
||||
"poidsDemandéPour": "Weight required for",
|
||||
"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",
|
||||
"rechercher...": "Search...",
|
||||
@ -494,8 +537,15 @@
|
||||
"role.vise-secrétaire": "Vice-Secretary",
|
||||
"role.vise-trésorier": "Vice-Treasurer",
|
||||
"saison": "Season",
|
||||
"sans": "Without",
|
||||
"secrétariatsDeLice": "Ring secretariats",
|
||||
"selectionner...": "Select...",
|
||||
"shield.buckler": "Buckler",
|
||||
"shield.none": "$t(sans) / $t(nonDéfinie)",
|
||||
"shield.round": "Round",
|
||||
"shield.standard": "Standard",
|
||||
"shield.teardrop": "Teardrop",
|
||||
"siDisponiblePourLaCatégorieDages": "If available for the age category",
|
||||
"siretOuRna": "SIRET or RNA",
|
||||
"stats": "Statistics",
|
||||
"statue": "Statue",
|
||||
@ -505,6 +555,10 @@
|
||||
"supprimerLeClub.msg": "Are you sure you want to delete this club?",
|
||||
"supprimerLeCompte": "Delete account",
|
||||
"supprimerLeCompte.msg": "Are you sure you want to delete this account?",
|
||||
"sword.none": "$t(sans) / $t(nonDéfinie)",
|
||||
"sword.oneHand": "One hand",
|
||||
"sword.saber": "Saber",
|
||||
"sword.twoHand": "Two hands",
|
||||
"sélectionEnéquipeDeFrance": "Selection in the French team",
|
||||
"sélectionner...": "Select...",
|
||||
"toast.edit.error": "Failed to save changes",
|
||||
@ -536,8 +590,75 @@
|
||||
"validerLicence_other": "Validate the {{count}} selected licenses",
|
||||
"validerLicence_zero": "$t(validerLicence_other)",
|
||||
"validée": "Validated",
|
||||
"veuillezAssocierChaqueChampàUneColonneDuFichier": "Please associate each field with a column in the file",
|
||||
"veuillezIndiqueràQuelle": "Please indicate on which line the headers are located in the file",
|
||||
"veuillezMapperLesColonnesSuivantes": "Please map the following columns",
|
||||
"voir/modifierLesParticipants": "View/Edit participants",
|
||||
"voirLesStatues": "View statues",
|
||||
"vousNêtesPasEncoreInscrit": "You are not yet registered or your registration has not yet been entered on the intranet",
|
||||
"à": "at",
|
||||
"étatDeLaDemande": "Request status"
|
||||
"étatDeLaDemande": "Request status",
|
||||
"fileImport.variants": {
|
||||
"licence": [
|
||||
"license",
|
||||
"licence",
|
||||
"license number",
|
||||
"license ID",
|
||||
"ID license",
|
||||
"licence no"
|
||||
],
|
||||
"pays": [
|
||||
"country",
|
||||
"pays",
|
||||
"country of residence",
|
||||
"origin country"
|
||||
],
|
||||
"nom": [
|
||||
"last name",
|
||||
"nom",
|
||||
"family name",
|
||||
"surname",
|
||||
"lastname"
|
||||
],
|
||||
"prenom": [
|
||||
"first name",
|
||||
"prénom",
|
||||
"given name",
|
||||
"first given name"
|
||||
],
|
||||
"genre": [
|
||||
"gender",
|
||||
"genre",
|
||||
"sex",
|
||||
"civility"
|
||||
],
|
||||
"weight": [
|
||||
"weight",
|
||||
"poids",
|
||||
"weight (kg)",
|
||||
"actual weight",
|
||||
"mass"
|
||||
],
|
||||
"categorie": [
|
||||
"category",
|
||||
"catégorie",
|
||||
"weight category",
|
||||
"age category"
|
||||
],
|
||||
"overCategory": [
|
||||
"over category",
|
||||
"surclassement",
|
||||
"category override",
|
||||
"over classification"
|
||||
],
|
||||
"club": [
|
||||
"club",
|
||||
"club name",
|
||||
"association",
|
||||
"association name"
|
||||
]
|
||||
},
|
||||
"comp.toast.registers.addMultiple.error": "Import failed",
|
||||
"comp.toast.registers.addMultiple.pending": "Import in progress",
|
||||
"comp.toast.registers.addMultiple.success": "Import completed successfully 🎉"
|
||||
}
|
||||
|
||||
@ -102,6 +102,7 @@
|
||||
"supprimer": "Supprimer",
|
||||
"sélectionneLesModesDaffichage": "Sélectionne les modes d'affichage",
|
||||
"sélectionner": "Sélectionner",
|
||||
"team": "Équipe",
|
||||
"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",
|
||||
@ -113,6 +114,9 @@
|
||||
"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.team.update.error": "Erreur lors de la mise à jour de l'équipe",
|
||||
"toast.team.update.pending": "Mise à jour de l'équipe...",
|
||||
"toast.team.update.success": "Équipe mise à jour !",
|
||||
"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 !",
|
||||
@ -125,12 +129,8 @@
|
||||
"toast.updateTrees.init.success": "Arbres créés !",
|
||||
"toast.updateTrees.pending": "Mise à jour des arbres du tournoi...",
|
||||
"toast.updateTrees.success": "Arbres mis à jour !",
|
||||
"toast.team.update.error": "Erreur lors de la mise à jour de l'équipe",
|
||||
"toast.team.update.pending": "Mise à jour de l'équipe...",
|
||||
"toast.team.update.success": "Équipe mise à jour !",
|
||||
"tournoi": "Tournoi",
|
||||
"tournois": "Tournois",
|
||||
"team": "Équipe",
|
||||
"tousLesMatchs": "Tous les matchs",
|
||||
"toutConserver": "Tout conserver",
|
||||
"ttm.admin.obs": "Clique court : Télécharger les ressources. Clique long : Créer la configuration obs",
|
||||
|
||||
@ -1,13 +1,15 @@
|
||||
{
|
||||
"(optionnelle)": "(optionnelle)",
|
||||
"---SansClub---": "--- sans club ---",
|
||||
"---ToutLesClubs---": "--- tout les clubs ---",
|
||||
"---TousLesAges---": "--- tous les ages ---",
|
||||
"---ToutLesClubs---": "--- tous les clubs ---",
|
||||
"---ToutLesPays---": "--- tout les pays ---",
|
||||
"---TouteLesCatégories---": "--- toute les catégories ---",
|
||||
"--NonLicencier--": "-- Non licencier --",
|
||||
"--SélectionnerCatégorie--": "-- Sélectionner catégorie --",
|
||||
"1Catégorie": "+1 catégorie",
|
||||
"2Catégorie": "+2 catégorie",
|
||||
"LesModificationsNontEnregistrer": "/!\\ Les modifications n'ont pas encore été enregistré, cliqué sur enregistrer /!\\",
|
||||
"activer": "Activer",
|
||||
"admin": "Administration",
|
||||
"administrateur": "Administrateur",
|
||||
@ -74,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}}",
|
||||
@ -81,11 +84,15 @@
|
||||
"ajouterUnClub": "Ajouter un club",
|
||||
"ajouterUnMembre": "Ajouter un membre",
|
||||
"all_season": "--- tout les saisons ---",
|
||||
"ans": "ans",
|
||||
"arme": "Arme",
|
||||
"au": "au",
|
||||
"aucun": "Aucun",
|
||||
"aucunMembreSélectionné": "Aucun membre sélectionné",
|
||||
"aucuneCatégorieDisponible": "Aucune catégorie disponible pour le moment.",
|
||||
"back": "« retour",
|
||||
"blason": "Blason",
|
||||
"bouclier": "Bouclier",
|
||||
"bureau": "Bureau",
|
||||
"button.accepter": "Accepter",
|
||||
"button.ajouter": "Ajouter",
|
||||
@ -93,7 +100,6 @@
|
||||
"button.appliquer": "Appliquer",
|
||||
"button.confirmer": "Confirmer",
|
||||
"button.créer": "Créer",
|
||||
"button.enregister": "Enregister",
|
||||
"button.enregistrer": "Enregistrer",
|
||||
"button.fermer": "Fermer",
|
||||
"button.modifier": "Modifier",
|
||||
@ -101,6 +107,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",
|
||||
@ -115,7 +122,9 @@
|
||||
"cat.vétéran2": "Vétéran 2",
|
||||
"categorie": "categorie",
|
||||
"catégorie": "Catégorie",
|
||||
"catégorieàAjouter": "Catégorie à ajouter",
|
||||
"certificatMédical": "Certificat médical",
|
||||
"champAttendu": "Champ attendu",
|
||||
"chargement...": "Chargement...",
|
||||
"chargerLexcel": "Charger l'Excel",
|
||||
"chargerLexcel.msg": "Merci d'utiliser le fichier ci-dessus comme base, ne pas renommer les colonnes ni modifier les n° de licences.",
|
||||
@ -146,6 +155,7 @@
|
||||
"club_one": "Club",
|
||||
"club_other": "Clubs",
|
||||
"club_zero": "Sans club",
|
||||
"colonneDansLeFichier": "Colonne dans le fichier",
|
||||
"combattant": "combattant",
|
||||
"comp.aff.blason": "Afficher le blason du club sur les écrans",
|
||||
"comp.aff.flag": "Afficher le pays du combattant sur les écrans",
|
||||
@ -165,7 +175,7 @@
|
||||
"comp.error1": "La date de fin doit être postérieure à la date de début.",
|
||||
"comp.error2": "Veuillez renseigner les dates de début et de fin d'inscription.",
|
||||
"comp.error3": "La date de fin d'inscription doit être postérieure à la date de début d'inscription.",
|
||||
"comp.exporterLesInscription": "Exporter les inscription",
|
||||
"comp.exporterLesInscription": "Exporter les inscriptions",
|
||||
"comp.ha.emailDeRéceptionDesInscriptionséchoué": "Email de réception des inscriptions échoué",
|
||||
"comp.ha.error1": "Veuillez renseigner l'URL de la billetterie HelloAsso et les tarifs associés.",
|
||||
"comp.ha.error2": "L'URL de la billetterie HelloAsso n'est pas valide. Veuillez vérifier le format de l'URL.",
|
||||
@ -186,7 +196,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",
|
||||
@ -221,6 +233,8 @@
|
||||
"comp.toast.register.add.error": "Combattant non trouvé",
|
||||
"comp.toast.register.add.pending": "Recherche en cours",
|
||||
"comp.toast.register.add.success": "Combattant trouvé et ajouté/mis à jour",
|
||||
"comp.toast.register.addMultiple.success_one": "Importation réussie pour 1 combattant",
|
||||
"comp.toast.register.addMultiple.success_other": "Importation réussie pour {{count}} combattants",
|
||||
"comp.toast.register.ban.error": "Erreur",
|
||||
"comp.toast.register.ban.pending": "Désinscription en cours",
|
||||
"comp.toast.register.ban.success": "Combattant désinscrit et bannie",
|
||||
@ -249,11 +263,13 @@
|
||||
"compte": "Compte",
|
||||
"compétition": "Compétition",
|
||||
"configuration": "Configuration",
|
||||
"configurationDeLaCatégorie": "Configuration de la catégorie",
|
||||
"conserverLancienEmail": "Conserver l'ancien email",
|
||||
"contactAdministratif": "Contact administratif",
|
||||
"contactInterne": "Contact interne",
|
||||
"contact_one": "Contact",
|
||||
"contact_other": "Contacts",
|
||||
"coquilleProtectionPelvienne": "Coquille / Protection pelvienne",
|
||||
"date": "Date",
|
||||
"dateDeNaissance": "Date de naissance",
|
||||
"days": [
|
||||
@ -274,6 +290,8 @@
|
||||
"donnéesAdministratives": "Données administratives",
|
||||
"du": "Du",
|
||||
"dun": "d'un",
|
||||
"duréePause": "Durée pause",
|
||||
"duréeRound": "Durée round",
|
||||
"définirLidDuCompte": "Définir l'id du compte",
|
||||
"editionDeL'affiliation": "Edition de l'affiliation",
|
||||
"editionDeLaDemande": "Edition de la demande ",
|
||||
@ -286,13 +304,18 @@
|
||||
"erreurDePaiement": "Erreur de paiement😕",
|
||||
"erreurDePaiement.detail": "Message d'erreur :",
|
||||
"erreurDePaiement.msg": "Une erreur est survenue lors du traitement de votre paiement. Veuillez réessayer plus tard.",
|
||||
"erreurPourLinscription": "Erreur pour l'inscription",
|
||||
"espaceAdministration": "Espace administration",
|
||||
"f": "F",
|
||||
"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": {
|
||||
@ -304,6 +327,9 @@
|
||||
},
|
||||
"homme": "Homme",
|
||||
"horairesD'entraînements": "Horaires d'entraînements",
|
||||
"importationDuFichier": "Importation du fichier",
|
||||
"importerDesCombattants": "Importer des combattants",
|
||||
"importerDesInvités": "Importer des invités",
|
||||
"information": "Information",
|
||||
"invité": "invité",
|
||||
"keepEmpty": "Laissez vide pour ne rien changer.",
|
||||
@ -312,6 +338,8 @@
|
||||
"licenceNo": "Licence n°{{no}}",
|
||||
"lieu": "Lieu",
|
||||
"lieuxDentraînements": "Lieux d'entraînements",
|
||||
"ligneIgnorée1": "Ligne ignorée : nom, prénom ou catégorie manquante.",
|
||||
"ligneIgnorée2": "Ligne ignorée : nom prénom ou licence manquante.",
|
||||
"loading": "Chargement...",
|
||||
"me": {
|
||||
"result": {
|
||||
@ -440,6 +468,8 @@
|
||||
"nouveauClub": "Nouveau club",
|
||||
"nouveauMembre": "Nouveau membre",
|
||||
"nouvelEmail": "Nouvel email",
|
||||
"numéroDeLaLigneDentête": "Numéro de la ligne d'en-tête",
|
||||
"numéroDeLigne": "Numéro de ligne",
|
||||
"ou": "Ou",
|
||||
"oui": "Oui",
|
||||
"outdated_session": {
|
||||
@ -472,8 +502,21 @@
|
||||
"perm.créerDesCompétion": "Créer des compétion",
|
||||
"perm.ffsafIntra": "FFSAF intra",
|
||||
"permission": "Permission",
|
||||
"peutSinscrire": "Peut s'inscrire?",
|
||||
"photos": "Photos",
|
||||
"plastron": "Plastron",
|
||||
"poids": "Poids",
|
||||
"poidsDemandéPour": "Poids demandé pour",
|
||||
"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",
|
||||
"rechercher...": "Rechercher...",
|
||||
@ -494,8 +537,15 @@
|
||||
"role.vise-secrétaire": "Vise-Secrétaire",
|
||||
"role.vise-trésorier": "Vise-Trésorier",
|
||||
"saison": "Saison",
|
||||
"sans": "Sans",
|
||||
"secrétariatsDeLice": "Secrétariats de lice",
|
||||
"selectionner...": "Sélectionner...",
|
||||
"shield.buckler": "Bocle",
|
||||
"shield.none": "$t(sans) / $t(nonDéfinie)",
|
||||
"shield.round": "Rond",
|
||||
"shield.standard": "Standard",
|
||||
"shield.teardrop": "Larme",
|
||||
"siDisponiblePourLaCatégorieDages": "Si disponible pour la catégorie d'ages",
|
||||
"siretOuRna": "SIRET ou RNA",
|
||||
"stats": "Statistiques",
|
||||
"statue": "Statue",
|
||||
@ -505,6 +555,10 @@
|
||||
"supprimerLeClub.msg": "Êtes-vous sûr de vouloir supprimer ce club ?",
|
||||
"supprimerLeCompte": "Supprimer le compte",
|
||||
"supprimerLeCompte.msg": "Êtes-vous sûr de vouloir supprimer ce compte ?",
|
||||
"sword.none": "$t(sans) / $t(nonDéfinie)",
|
||||
"sword.oneHand": "Une main",
|
||||
"sword.saber": "Sabre",
|
||||
"sword.twoHand": "Deux mains",
|
||||
"sélectionEnéquipeDeFrance": "Sélection en équipe de France",
|
||||
"sélectionner...": "Sélectionner...",
|
||||
"toast.edit.error": "Échec de l'enregistrement des modifications",
|
||||
@ -536,8 +590,79 @@
|
||||
"validerLicence_other": "Valider les {{count}} licences sélectionnées",
|
||||
"validerLicence_zero": "$t(validerLicence_other)",
|
||||
"validée": "Validée",
|
||||
"veuillezAssocierChaqueChampàUneColonneDuFichier": "Veuillez associer chaque champ à une colonne du fichier",
|
||||
"veuillezIndiqueràQuelle": "Veuillez indiquer à quelle ligne se trouvent les en-têtes dans le fichier",
|
||||
"veuillezMapperLesColonnesSuivantes": "Veuillez mapper les colonnes suivantes",
|
||||
"voir/modifierLesParticipants": "Voir/Modifier les participants",
|
||||
"voirLesStatues": "Voir les statues",
|
||||
"vousNêtesPasEncoreInscrit": "Vous n'êtes pas encore inscrit ou votre inscription n'a pas encore été rentrée sur l'intranet",
|
||||
"à": "à",
|
||||
"étatDeLaDemande": "État de la demande"
|
||||
"étatDeLaDemande": "État de la demande",
|
||||
"fileImport.variants": {
|
||||
"licence": [
|
||||
"licence",
|
||||
"n° licence",
|
||||
"num licence",
|
||||
"id licence",
|
||||
"license",
|
||||
"licence id"
|
||||
],
|
||||
"pays": [
|
||||
"pays",
|
||||
"country",
|
||||
"pays de résidence",
|
||||
"pays d'origine"
|
||||
],
|
||||
"nom": [
|
||||
"nom",
|
||||
"nom de famille",
|
||||
"lastname",
|
||||
"family name",
|
||||
"nom complet"
|
||||
],
|
||||
"prenom": [
|
||||
"prénom",
|
||||
"prenom",
|
||||
"first name",
|
||||
"given name",
|
||||
"prénom usuel"
|
||||
],
|
||||
"genre": [
|
||||
"genre",
|
||||
"sexe",
|
||||
"gender",
|
||||
"sex",
|
||||
"civilité"
|
||||
],
|
||||
"weight": [
|
||||
"poids",
|
||||
"weight",
|
||||
"poids (kg)",
|
||||
"poids réel",
|
||||
"masse"
|
||||
],
|
||||
"categorie": [
|
||||
"catégorie",
|
||||
"category",
|
||||
"catégorie de poids",
|
||||
"weight category",
|
||||
"catégorie d'âge"
|
||||
],
|
||||
"overCategory": [
|
||||
"surclassement",
|
||||
"over category",
|
||||
"surcatégorie",
|
||||
"surclassement de catégorie"
|
||||
],
|
||||
"club": [
|
||||
"club",
|
||||
"nom du club",
|
||||
"club name",
|
||||
"association",
|
||||
"nom de l'association"
|
||||
]
|
||||
},
|
||||
"comp.toast.registers.addMultiple.error": "Erreur lors de l'importation des combattants",
|
||||
"comp.toast.registers.addMultiple.pending": "Importation des combattants en cours...",
|
||||
"comp.toast.registers.addMultiple.success": "Importation des combattants réussie 🎉"
|
||||
}
|
||||
|
||||
@ -1,14 +0,0 @@
|
||||
import Keycloak from "keycloak-js";
|
||||
|
||||
const vite_url = import.meta.env.VITE_URL;
|
||||
const client_id = import.meta.env.VITE_CLIENT_ID;
|
||||
|
||||
const keycloak = new Keycloak({
|
||||
url: `${vite_url}/auth-api`,
|
||||
realm: "safca",
|
||||
clientId: client_id,
|
||||
});
|
||||
|
||||
|
||||
|
||||
export default keycloak;
|
||||
115
src/main/webapp/src/assets/CategoryPreset.js
Normal file
115
src/main/webapp/src/assets/CategoryPreset.js
Normal 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;
|
||||
241
src/main/webapp/src/components/FileImport.jsx
Normal file
241
src/main/webapp/src/components/FileImport.jsx
Normal file
@ -0,0 +1,241 @@
|
||||
import React, {useId, useRef, useState} from "react";
|
||||
import {toast} from "react-toastify";
|
||||
import * as XLSX from "xlsx";
|
||||
import {useTranslation} from "react-i18next";
|
||||
|
||||
const parseValue = (value, type) => {
|
||||
if (value === undefined || value === null)
|
||||
return null;
|
||||
|
||||
switch (type) {
|
||||
case 'Integer':
|
||||
if (value === '')
|
||||
return null;
|
||||
const parsedInt = parseInt(value, 10);
|
||||
return isNaN(parsedInt) ? null : parsedInt;
|
||||
case 'Boolean':
|
||||
if (typeof value === 'boolean')
|
||||
return value;
|
||||
if (typeof value === 'string') {
|
||||
const lowerValue = value.toLowerCase().trim();
|
||||
if (lowerValue === 'oui' || lowerValue === 'true' || lowerValue === '1' || lowerValue === 'x') {
|
||||
return true;
|
||||
} else if (lowerValue === 'non' || lowerValue === 'false' || lowerValue === '0' || lowerValue === '') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
case 'Date':
|
||||
if (value === '')
|
||||
return null;
|
||||
if (typeof value === 'string') {
|
||||
const date = new Date(value);
|
||||
return isNaN(date.getTime()) ? null : date;
|
||||
}
|
||||
return null;
|
||||
case 'String':
|
||||
default:
|
||||
return String(value);
|
||||
}
|
||||
};
|
||||
|
||||
export function FileImport({onDataMapped, expectedFields, textButton}) {
|
||||
const id = useId();
|
||||
const [headerLineNumber, setHeaderLineNumber] = useState(1);
|
||||
const [fileData, setFileData] = useState([]);
|
||||
const [fileHeaders, setFileHeaders] = useState([]);
|
||||
const [fileName, setFileName] = useState('');
|
||||
const [selectedFile, setSelectedFile] = useState(null);
|
||||
const [columnMappings, setColumnMappings] = useState({});
|
||||
const fileChooser = useRef(null);
|
||||
const openMappingModal = useRef(null);
|
||||
const closeMappingModal = useRef(null);
|
||||
const openHeaderLineModal = useRef(null);
|
||||
const {t} = useTranslation();
|
||||
|
||||
|
||||
// Fonction pour trouver la meilleure correspondance
|
||||
const findBestMatch = (fileHeaders, expectedField) => {
|
||||
const fieldLabel = expectedField.label.toLowerCase();
|
||||
const fieldKey = expectedField.key.toLowerCase();
|
||||
|
||||
// Variantes possibles pour chaque champ (ex: "Nom" peut être "nom", "Nom de famille", etc.)
|
||||
const variants = {
|
||||
licence: t('fileImport.variants.licence', {returnObjects: true}),
|
||||
pays: t('fileImport.variants.pays', {returnObjects: true}),
|
||||
nom: t('fileImport.variants.nom', {returnObjects: true}),
|
||||
prenom: t('fileImport.variants.prenom', {returnObjects: true}),
|
||||
genre: t('fileImport.variants.genre', {returnObjects: true}),
|
||||
weight: t('fileImport.variants.weight', {returnObjects: true}),
|
||||
categorie: t('fileImport.variants.categorie', {returnObjects: true}),
|
||||
overCategory: t('fileImport.variants.overCategory', {returnObjects: true}),
|
||||
club: t('fileImport.variants.club', {returnObjects: true}),
|
||||
};
|
||||
|
||||
// Recherche de la meilleure correspondance
|
||||
for (const header of fileHeaders) {
|
||||
const lowerHeader = header.toLowerCase();
|
||||
if (lowerHeader === fieldLabel || lowerHeader === fieldKey || (variants[fieldKey] && variants[fieldKey].includes(lowerHeader))) {
|
||||
return header;
|
||||
}
|
||||
}
|
||||
|
||||
// Aucune correspondance trouvée
|
||||
return null;
|
||||
};
|
||||
|
||||
// Gestion du fichier sélectionné
|
||||
const handleFileChange = (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (!file) return;
|
||||
|
||||
setSelectedFile(file);
|
||||
setFileName(file.name);
|
||||
openHeaderLineModal.current.click();
|
||||
};
|
||||
|
||||
// Valider le numéro de la ligne d'en-tête et lire le fichier
|
||||
const handleHeaderLineSubmit = () => {
|
||||
if (!selectedFile) return;
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = (event) => {
|
||||
const data = event.target.result;
|
||||
const workbook = XLSX.read(data, {type: 'binary'});
|
||||
const sheetName = workbook.SheetNames[0];
|
||||
const sheet = workbook.Sheets[sheetName];
|
||||
const jsonData = XLSX.utils.sheet_to_json(sheet, {header: 1});
|
||||
|
||||
// Extraire les en-têtes et les données en fonction du numéro de ligne
|
||||
const headers = jsonData[headerLineNumber - 1];
|
||||
const rows = jsonData.slice(headerLineNumber);
|
||||
setFileHeaders(headers);
|
||||
setFileData(rows);
|
||||
|
||||
// Initialiser le mapping avec pré-remplissage intelligent
|
||||
const initialMappings = {};
|
||||
expectedFields.forEach(field => {
|
||||
const bestMatch = findBestMatch(headers, field);
|
||||
initialMappings[field.key] = bestMatch || '';
|
||||
});
|
||||
setColumnMappings(initialMappings);
|
||||
|
||||
openMappingModal.current.click();
|
||||
fileChooser.current.value = '';
|
||||
};
|
||||
reader.readAsBinaryString(selectedFile);
|
||||
};
|
||||
|
||||
// Mettre à jour le mapping d'une colonne
|
||||
const handleMappingChange = (fieldKey, header) => {
|
||||
setColumnMappings({
|
||||
...columnMappings,
|
||||
[fieldKey]: header,
|
||||
});
|
||||
};
|
||||
|
||||
// Valider le mapping et envoyer les données
|
||||
const handleSubmit = () => {
|
||||
// Vérifier que tous les champs requis sont mappés
|
||||
const missingMappings = expectedFields
|
||||
.filter(field => field.mandatory && !columnMappings[field.key])
|
||||
.map(field => field.label);
|
||||
|
||||
if (missingMappings.length > 0) {
|
||||
toast.error(`${t('veuillezMapperLesColonnesSuivantes')} : ${missingMappings.join(', ')}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Préparer les données mappées et parsées
|
||||
const mappedData = fileData.map(row => {
|
||||
const mappedRow = {};
|
||||
expectedFields.forEach(field => {
|
||||
const headerIndex = fileHeaders.indexOf(columnMappings[field.key]);
|
||||
const rawValue = headerIndex !== -1 ? row[headerIndex] : '';
|
||||
mappedRow[field.key] = parseValue(rawValue, field.type);
|
||||
});
|
||||
return mappedRow;
|
||||
});
|
||||
|
||||
// Envoyer les données au parent ou au backend
|
||||
onDataMapped(mappedData);
|
||||
closeMappingModal.current.click();
|
||||
};
|
||||
|
||||
const handleFileChooser = () => {
|
||||
fileChooser.current.click();
|
||||
}
|
||||
|
||||
return <div>
|
||||
<button type="button" className="btn btn-primary" onClick={handleFileChooser}>{textButton}</button>
|
||||
|
||||
<input ref={fileChooser} type="file" accept=".xlsx, .xls, .csv" onChange={handleFileChange} hidden={true}/>
|
||||
<button ref={openMappingModal} type="button" className="btn btn-primary" data-bs-toggle="modal" data-bs-target={"#mappingModal" + id}
|
||||
hidden={true}>A
|
||||
</button>
|
||||
<button ref={openHeaderLineModal} type="button" className="btn btn-primary" data-bs-toggle="modal"
|
||||
data-bs-target={"#headerLineModal" + id} hidden={true}>B
|
||||
</button>
|
||||
|
||||
<div className="modal fade" id={"mappingModal" + id} tabIndex="-1" aria-labelledby="mappingModalLabel" aria-hidden="true">
|
||||
<div className="modal-dialog modal-dialog-scrollable modal-lg modal-fullscreen-lg-down">
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<h1 className="modal-title fs-5" id="mappingModalLabel">{t('importationDuFichier')} {fileName}</h1>
|
||||
<button ref={closeMappingModal} type="button" className="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
<p>{t('veuillezAssocierChaqueChampàUneColonneDuFichier')} :</p>
|
||||
<table className="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">{t('champAttendu')}</th>
|
||||
<th scope="col">{t('colonneDansLeFichier')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{expectedFields.map(field => <tr key={field.key}>
|
||||
<td>{field.label}</td>
|
||||
<td>
|
||||
<select className="form-select" value={columnMappings[field.key]}
|
||||
onChange={(e) => handleMappingChange(field.key, e.target.value)}>
|
||||
<option value="">{t('sélectionner...')}</option>
|
||||
{fileHeaders.map(header => (<option key={header} value={header}>{header}</option>))}
|
||||
</select>
|
||||
</td>
|
||||
</tr>)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div className="modal-footer">
|
||||
<button type="button" className="btn btn-secondary" data-bs-dismiss="modal">{t('button.annuler')}</button>
|
||||
<button type="button" className="btn btn-primary" onClick={handleSubmit}>{t('button.confirmer')}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="modal fade" id={"headerLineModal" + id} tabIndex="-1" aria-labelledby="headerLineModalLabel" aria-hidden="true">
|
||||
<div className="modal-dialog modal-dialog-scrollable">
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<h1 className="modal-title fs-5" id="headerLineModalLabel">{t('numéroDeLaLigneDentête')}</h1>
|
||||
<button type="button" className="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
<p>{t('veuillezIndiqueràQuelle')} :</p>
|
||||
<div className="input-group mb-3">
|
||||
<span className="input-group-text" id={id + "basic-addon1"}>{t('numéroDeLigne')}</span>
|
||||
<input type="number" className="form-control" aria-describedby={id + "basic-addon1"} min="1" value={headerLineNumber}
|
||||
onChange={(e) => setHeaderLineNumber(parseInt(e.target.value) || 1)}/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="modal-footer">
|
||||
<button type="button" className="btn btn-secondary" data-bs-dismiss="modal">{t('button.annuler')}</button>
|
||||
<button type="button" className="btn btn-primary" onClick={handleHeaderLineSubmit}>{t('button.confirmer')}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
166
src/main/webapp/src/components/ProtectionSelector.jsx
Normal file
166
src/main/webapp/src/components/ProtectionSelector.jsx
Normal file
@ -0,0 +1,166 @@
|
||||
import {useTranslation} from "react-i18next";
|
||||
|
||||
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 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 (
|
||||
<svg width="200" height="300" viewBox="0 0 160 320">
|
||||
<rect
|
||||
width="160" height="320"
|
||||
fill="#f9fafb00"
|
||||
stroke="#d1d5db"
|
||||
strokeWidth="2"
|
||||
rx="10"
|
||||
/>
|
||||
|
||||
{/* Casque */}
|
||||
<ellipse {...props}
|
||||
cx="80" cy="35" rx="20" ry="22"
|
||||
fill={color(1)}
|
||||
onClick={() => toggle(1)}
|
||||
><title>{t('casque')}</title></ellipse>
|
||||
|
||||
{/* Gorgerin */}
|
||||
<ellipse {...props}
|
||||
cx="80" cy="65" rx="12" ry="8"
|
||||
fill={color(2)}
|
||||
onClick={() => toggle(2)}
|
||||
><title>{t('gorgerin')}</title></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)}
|
||||
><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>
|
||||
|
||||
{/* Protection de jambes */}
|
||||
<ellipse {...props}
|
||||
cx="65" cy="230" rx="14" ry="55"
|
||||
fill={color(9)}
|
||||
onClick={() => toggle(9)}
|
||||
><title>{t('protectionDeJambes')}</title></ellipse>
|
||||
<ellipse {...props}
|
||||
cx="95" cy="230" rx="14" ry="55"
|
||||
fill={color(9)}
|
||||
onClick={() => toggle(9)}
|
||||
><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>
|
||||
);
|
||||
}
|
||||
|
||||
export default ProtectionSelector;
|
||||
@ -6,12 +6,24 @@ import {CheckField, OptionField, TextField} from "../../components/MemberCustomF
|
||||
import {ClubSelect} from "../../components/ClubSelect.jsx";
|
||||
import {ConfirmDialog} from "../../components/ConfirmDialog.jsx";
|
||||
import {toast} from "react-toastify";
|
||||
import {apiAxios, getToastMessage} from "../../utils/Tools.js";
|
||||
import {useEffect, useReducer, useState} from "react";
|
||||
import {
|
||||
apiAxios,
|
||||
CatList,
|
||||
getCatName, getShieldTypeName,
|
||||
getSwordTypeName,
|
||||
getToastMessage,
|
||||
ShieldList,
|
||||
sortCategories,
|
||||
SwordList,
|
||||
timePrint
|
||||
} from "../../utils/Tools.js";
|
||||
import React, {useEffect, useReducer, useState} from "react";
|
||||
import {SimpleReducer} from "../../utils/SimpleReducer.jsx";
|
||||
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()
|
||||
@ -44,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") &&
|
||||
@ -190,10 +202,10 @@ function ContentSAFCAAndInternal({data2, type = "SAFCA"}) {
|
||||
}}><FontAwesomeIcon icon={faAdd}/></button>
|
||||
</div>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="row mb-3">
|
||||
<div className="d-grid gap-2 d-md-flex justify-content-md-end">
|
||||
<button type="submit" className="btn btn-primary">{t('button.enregistrer')}</button>
|
||||
<div className="row" style={{marginTop: "1em"}}>
|
||||
<div className="d-grid gap-2 d-md-flex justify-content-md-end">
|
||||
<button type="submit" className="btn btn-primary">{t('button.enregistrer')}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -205,8 +217,23 @@ function ContentSAFCAAndInternal({data2, type = "SAFCA"}) {
|
||||
function Content({data}) {
|
||||
const navigate = useNavigate();
|
||||
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();
|
||||
|
||||
@ -227,6 +254,8 @@ function Content({data}) {
|
||||
out['startRegister'] = event.target.startRegister?.value
|
||||
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
|
||||
@ -277,14 +306,17 @@ function Content({data}) {
|
||||
toast.promise(
|
||||
apiAxios.post(`/competition`, out), getToastMessage("comp.toast.save")
|
||||
).then(data => {
|
||||
setPresetChange(false)
|
||||
console.log(data.data)
|
||||
if (data.data.id !== undefined)
|
||||
navigate("/competition/" + data.data.id)
|
||||
if (data.data.presets !== undefined)
|
||||
setPresets(data.data.presets)
|
||||
})
|
||||
}
|
||||
|
||||
return <form onSubmit={handleSubmit}>
|
||||
<div className="card mb-4">
|
||||
return <>
|
||||
<form onSubmit={handleSubmit} className="card mb-4">
|
||||
<input name="id" value={data.id || ""} readOnly hidden/>
|
||||
<div className="card-header">{data.id ? t('comp.editionCompétition') : t('comp.créationCompétition')}</div>
|
||||
<div className="card-body text-center">
|
||||
@ -340,6 +372,56 @@ function Content({data}) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="accordion-item">
|
||||
<h2 className="accordion-header">
|
||||
<button className="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseFour"
|
||||
aria-expanded="false" aria-controls="collapseFour">
|
||||
Catégories proposées
|
||||
</button>
|
||||
</h2>
|
||||
<div id="collapseFour" className="accordion-collapse collapse" data-bs-parent="#accordionExample">
|
||||
<div className="accordion-body" style={{textAlign: "left"}}>
|
||||
<div className="list-group">
|
||||
{presets.sort((a, b) => a.name.localeCompare(b.name)).map((preset) =>
|
||||
<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.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>)}
|
||||
</div>
|
||||
<div className="row" style={{marginTop: "1em"}}>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="accordion-item">
|
||||
<h2 className="accordion-header">
|
||||
<button className="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseThree"
|
||||
@ -375,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>
|
||||
@ -404,14 +496,192 @@ function Content({data}) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="row" style={{marginTop: "1em"}}>
|
||||
<div className="d-grid gap-2 d-md-flex justify-content-md-end">
|
||||
<button type="submit" className="btn btn-primary">{t('button.enregistrer')}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div className="row mb-3">
|
||||
<div className="d-grid gap-2 d-md-flex justify-content-md-end">
|
||||
<button type="submit" className="btn btn-primary">{t('button.enregistrer')}</button>
|
||||
<div className="modal fade" id="catModal" tabIndex="-1" aria-labelledby="catModalLabel" aria-hidden="true">
|
||||
<div className="modal-dialog modal-dialog-scrollable modal-lg modal-fullscreen-lg-down">
|
||||
<div className="modal-content">
|
||||
<CatModalContent setPresets={setPresets} setPresetChange={setPresetChange} state={modaleState}/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</>
|
||||
}
|
||||
|
||||
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 [cats, setCats] = useState(state.categories || [])
|
||||
const [mandatoryProtection1, setMandatoryProtection1] = useState(state.mandatoryProtection1 || 5)
|
||||
const [mandatoryProtection2, setMandatoryProtection2] = useState(state.mandatoryProtection2 || 5)
|
||||
|
||||
const {t} = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
setName(state.name || "")
|
||||
setSword(state.sword || "NONE")
|
||||
setShield(state.shield || "NONE")
|
||||
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, {categorie: cat, roundDuration: "", pauseDuration: ""}])
|
||||
}
|
||||
} else {
|
||||
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.some(cat_ => cat_.categorie === cat)
|
||||
|
||||
const parseTime = (str) => {
|
||||
const parts = str.split(":").map(part => parseInt(part, 10));
|
||||
if (parts.length === 1) {
|
||||
return parts[0] * 1000;
|
||||
} else if (parts.length === 2) {
|
||||
return (parts[0] * 60 + parts[1]) * 1000;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
const handleSave = () => {
|
||||
const out = {
|
||||
id: state.id,
|
||||
name: name,
|
||||
sword: sword,
|
||||
shield: shield,
|
||||
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)
|
||||
}
|
||||
|
||||
const handleRm = () => {
|
||||
setPresets(presets => presets.filter(p => p.id !== state.id))
|
||||
setPresetChange(true)
|
||||
}
|
||||
|
||||
return <>
|
||||
<div className="modal-header">
|
||||
<h1 className="modal-title fs-5" id="CategorieModalLabel">{t('configurationDeLaCatégorie')}</h1>
|
||||
<button type="button" className="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
<div className="row">
|
||||
<div className="col-12 col-md-7 mb-3">
|
||||
<div className="input-group mb-3">
|
||||
<span className="input-group-text" id="categorie">{t("nom")}</span>
|
||||
<input type="text" className="form-control" placeholder={t("nom")} name="name"
|
||||
value={name} onChange={e => setName(e.target.value)}/>
|
||||
</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}
|
||||
onChange={e => setSword(e.target.value)}>
|
||||
{SwordList.map(sword =>
|
||||
<option key={sword} value={sword}>{getSwordTypeName(sword)}</option>
|
||||
)}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="input-group mb-3">
|
||||
<span className="input-group-text" id="shield">{t('bouclier')}</span>
|
||||
<select className="form-select" aria-label={t('bouclier')} name="shield" value={shield}
|
||||
onChange={e => setShield(e.target.value)}>
|
||||
{ShieldList.map(shield =>
|
||||
<option key={shield} value={shield}>{getShieldTypeName(shield)}</option>
|
||||
)}
|
||||
</select>
|
||||
</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>
|
||||
{cats.some(cat_ => CatList.indexOf(cat_.categorie) <= CatList.indexOf("JUNIOR")) && <>
|
||||
<div>< 18 {t('ans')}</div>
|
||||
<ProtectionSelector shield={shield !== "NONE"} mandatoryProtection={mandatoryProtection1}
|
||||
setMandatoryProtection={setMandatoryProtection1}/>
|
||||
</>}
|
||||
{cats.some(cat_ => CatList.indexOf(cat_.categorie) > CatList.indexOf("JUNIOR")) && <>
|
||||
<div>≥ 18 {t('ans')}</div>
|
||||
<ProtectionSelector shield={shield !== "NONE"} mandatoryProtection={mandatoryProtection2}
|
||||
setMandatoryProtection={setMandatoryProtection2}/>
|
||||
</>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="modal-footer">
|
||||
<button type="button" className="btn btn-danger" data-bs-dismiss="modal" onClick={handleRm}>{t('button.supprimer')}</button>
|
||||
<button type="button" className="btn btn-secondary" data-bs-dismiss="modal">{t('button.fermer')}</button>
|
||||
<button type="button" className="btn btn-primary" data-bs-dismiss="modal" onClick={handleSave}>{t('button.appliquer')}</button>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
|
||||
@ -1,30 +1,36 @@
|
||||
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";
|
||||
import {ThreeDots} from "react-loader-spinner";
|
||||
import {useEffect, useReducer, useRef, useState} from "react";
|
||||
import {apiAxios, CatList, getCatName, getToastMessage} from "../../utils/Tools.js";
|
||||
import React, {useEffect, useId, useReducer, useRef, useState} from "react";
|
||||
import {apiAxios, applyOverCategory, CatList, getCatFromName, getCatName, getToastMessage} from "../../utils/Tools.js";
|
||||
import {toast} from "react-toastify";
|
||||
import {SimpleReducer} from "../../utils/SimpleReducer.jsx";
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
import {faAdd, faGavel, faTrashCan} from "@fortawesome/free-solid-svg-icons";
|
||||
import {faAdd, faGavel, faLock, faTrashCan} from "@fortawesome/free-solid-svg-icons";
|
||||
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";
|
||||
import {FileImport} from "../../components/FileImport.jsx";
|
||||
|
||||
export function CompetitionRegisterAdmin({source}) {
|
||||
const {id} = useParams()
|
||||
const navigate = useNavigate()
|
||||
const [state, dispatch] = useReducer(SimpleReducer, [])
|
||||
const [clubFilter, setClubFilter] = useState("")
|
||||
const [catFilter, setCatFilter] = 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);
|
||||
@ -43,11 +49,31 @@ export function CompetitionRegisterAdmin({source}) {
|
||||
return toast.promise(apiAxios.post(`/competition/${id}/register/${source}`, new_state), getToastMessage("comp.toast.register.add")
|
||||
).then((response) => {
|
||||
if (response.data.error) {
|
||||
return
|
||||
return null;
|
||||
}
|
||||
dispatch({type: 'UPDATE_OR_ADD', payload: {id: response.data.id, data: response.data}})
|
||||
dispatch({type: 'SORT', payload: sortName})
|
||||
document.getElementById("closeModal").click();
|
||||
return response.data
|
||||
})
|
||||
}
|
||||
const sendRegisters = (new_state) => {
|
||||
toast.promise(apiAxios.post(`/competition/${id}/registers/${source}`, new_state), getToastMessage("comp.toast.registers.addMultiple")
|
||||
).then((response) => {
|
||||
if (response.data.error)
|
||||
return;
|
||||
|
||||
let i = 0;
|
||||
response.data.forEach((d) => {
|
||||
if (d.licence === -42) {
|
||||
toast.warn(t('erreurPourLinscription') + " :" + d.lname, {autoClose: false});
|
||||
} else {
|
||||
dispatch({type: 'UPDATE_OR_ADD', payload: {id: d.id, data: d}})
|
||||
i++;
|
||||
}
|
||||
})
|
||||
if (i > 0)
|
||||
toast.success(t('comp.toast.register.addMultiple.success', {count: i}))
|
||||
dispatch({type: 'SORT', payload: sortName})
|
||||
})
|
||||
}
|
||||
|
||||
@ -62,8 +88,15 @@ export function CompetitionRegisterAdmin({source}) {
|
||||
<div className="col-lg-9">
|
||||
{data ? <div className="">
|
||||
<MakeCentralPanel
|
||||
data={state.filter(s => (clubFilter.length === 0 || s.data.club.name === clubFilter) && (catFilter.length === 0 || s.data.categorie === catFilter))}
|
||||
dispatch={dispatch} id={id} setModalState={setModalState} source={source}/>
|
||||
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))
|
||||
&& (!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">
|
||||
@ -77,36 +110,65 @@ export function CompetitionRegisterAdmin({source}) {
|
||||
onClick={() => setModalState({id: -793548328091516928})}>{t('comp.ajouterUnInvité')}
|
||||
</button>
|
||||
</div>}
|
||||
<QuickAdd sendRegister={sendRegister} source={source}/>
|
||||
<QuickAdd sendRegister={sendRegister} source={source} data2={data2} error2={error2}/>
|
||||
<div className="card mb-4">
|
||||
<div className="card-header">{t('filtre')}</div>
|
||||
<div className="card-body">
|
||||
<FiltreBar data={data} clubFilter={clubFilter} setClubFilter={setClubFilter} catFilter={catFilter}
|
||||
setCatFilter={setCatFilter} source={source}/>
|
||||
<FiltreBar data={data} data2={data2} clubFilter={clubFilter} setClubFilter={setClubFilter} catFilter={catFilter}
|
||||
setCatFilter={setCatFilter} catAgeFilter={catAgeFilter} setCatAgeFilter={setCatAgeFilter}
|
||||
filterNotWeight={filterNotWeight} setFilterNotWeight={setFilterNotWeight} source={source}/>
|
||||
</div>
|
||||
</div>
|
||||
{source === "admin" && <FileOutput data={data}/>}
|
||||
{source === "admin" && <div className="mb-2"><FileOutput data={data} data2={data2}/></div>}
|
||||
{source === "admin" && <div className="mb-2"><FileImportComb data2={data2} sendRegisters={sendRegisters}/></div>}
|
||||
{source === "admin" && <div className="mb-2"><FileImportGuest data2={data2} sendRegisters={sendRegisters}/></div>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Modal sendRegister={sendRegister} modalState={modalState} setModalState={setModalState} source={source}/>
|
||||
<Modal_ data2={data2} error2={error2} data3={data3} sendRegister={sendRegister} modalState={modalState} setModalState={setModalState}
|
||||
source={source}/>
|
||||
</div>
|
||||
}
|
||||
|
||||
function QuickAdd({sendRegister, source}) {
|
||||
function QuickAdd({sendRegister, source, data2, error2}) {
|
||||
const {t} = useTranslation();
|
||||
const [categories, setCategories] = useState([])
|
||||
|
||||
const handleAdd = (licence) => {
|
||||
console.log("Quick add licence: " + licence)
|
||||
|
||||
sendRegister({
|
||||
licence: licence, fname: "", lname: "", weight: "", overCategory: 0, lockEdit: false, id: null
|
||||
licence: licence,
|
||||
fname: "",
|
||||
lname: "",
|
||||
weight: "",
|
||||
overCategory: 0,
|
||||
lockEdit: false,
|
||||
id: null,
|
||||
quick: true,
|
||||
categoriesInscrites: categories
|
||||
})
|
||||
}
|
||||
|
||||
const setCategories_ = (e, catId) => {
|
||||
if (e.target.checked) {
|
||||
if (!categories.includes(catId)) {
|
||||
setCategories([...categories, catId])
|
||||
}
|
||||
} else {
|
||||
setCategories(categories.filter(c => c !== catId))
|
||||
}
|
||||
}
|
||||
|
||||
return <div className="card mb-4">
|
||||
<div className="card-header">{t('comp.ajoutRapide')}</div>
|
||||
<div className="card-body">
|
||||
<div className="d-flex flex-wrap">
|
||||
<label htmlFor="inputState2" className="form-label align-self-center" style={{margin: "0 0.5em 0 0"}}>
|
||||
{t('catégorieàAjouter')}<br/> <small>({t('siDisponiblePourLaCatégorieDages')})</small>
|
||||
</label>
|
||||
<CategoriesList error2={error2} availableCats={data2?.sort((a, b) => a.name.localeCompare(b.name))} categories={categories}
|
||||
setCategories={setCategories_}/>
|
||||
</div>
|
||||
|
||||
<div className="row">
|
||||
<span>{t('comp.noDeLicence')}</span>
|
||||
</div>
|
||||
@ -131,14 +193,14 @@ function QuickAdd({sendRegister, source}) {
|
||||
</button>
|
||||
|
||||
{source === "club" && <LoadingProvider>
|
||||
<SearchMember sendRegister={sendRegister}/>
|
||||
<SearchMember sendRegister={sendRegister} categories={categories}/>
|
||||
</LoadingProvider>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
function SearchMember({sendRegister}) {
|
||||
function SearchMember({sendRegister, categories}) {
|
||||
const setLoading = useLoadingSwitcher()
|
||||
const {data, error} = useFetch(`/club/members`, setLoading, 1)
|
||||
const [suggestions, setSuggestions] = useState([])
|
||||
@ -158,7 +220,9 @@ function SearchMember({sendRegister}) {
|
||||
weight: "",
|
||||
overCategory: 0,
|
||||
lockEdit: false,
|
||||
id: null
|
||||
id: null,
|
||||
quick: true,
|
||||
categoriesInscrites: categories
|
||||
})
|
||||
}
|
||||
|
||||
@ -284,14 +348,41 @@ const AutoCompleteInput = ({suggestions = [], handleAdd}) => {
|
||||
</div>);
|
||||
};
|
||||
|
||||
function Modal({sendRegister, modalState, setModalState, source}) {
|
||||
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>}
|
||||
{availableCats && availableCats.map((cat, index) =>
|
||||
<div key={cat.id} className="input-group"
|
||||
style={{display: "contents"}}>
|
||||
<div className="input-group-text">
|
||||
<input ref={index === 0 ? fistCatInput : undefined} className="form-check-input mt-0" type="checkbox"
|
||||
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={id + "categoriesInput" + index}>{cat.name}</label>
|
||||
</div>
|
||||
</div>)}
|
||||
</>}
|
||||
</>
|
||||
}
|
||||
|
||||
function Modal_({data2, data3, error2, sendRegister, modalState, setModalState, source}) {
|
||||
const country = useCountries('fr')
|
||||
const {t} = useTranslation();
|
||||
const closeBtn = useRef(null);
|
||||
const licenceInput = useRef(null);
|
||||
const nameInput = useRef(null);
|
||||
const fistCatInput = useRef(null);
|
||||
const submitBtn = useRef(null);
|
||||
|
||||
const [licence, setLicence] = useState("")
|
||||
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("")
|
||||
@ -299,97 +390,127 @@ function Modal({sendRegister, modalState, setModalState, source}) {
|
||||
const [genre, setGenre] = useState("NA")
|
||||
const [editMode, setEditMode] = useState(false)
|
||||
const [lockEdit, setLockEdit] = useState(false)
|
||||
const [categories, setCategories] = useState([])
|
||||
|
||||
useEffect(() => {
|
||||
console.log(modalState)
|
||||
if (!modalState) {
|
||||
setLicence("")
|
||||
setFname("")
|
||||
setLname("")
|
||||
setWeight("")
|
||||
setCat(0)
|
||||
setEditMode(false)
|
||||
setLockEdit(false)
|
||||
setClub("")
|
||||
setGCat("")
|
||||
setCountry_("FR")
|
||||
setGenre("NA")
|
||||
} else {
|
||||
setLicence(modalState.licence ? modalState.licence : "")
|
||||
setFname(modalState.fname ? modalState.fname : "")
|
||||
setLname(modalState.lname ? modalState.lname : "")
|
||||
setWeight(modalState.weight ? modalState.weight : "")
|
||||
setCat(modalState.overCategory ? modalState.overCategory : 0)
|
||||
setEditMode(modalState.licence || (modalState.fname && modalState.lname))
|
||||
setLockEdit(modalState.lockEdit)
|
||||
setClub(modalState.club ? modalState.club.name : "")
|
||||
setGCat(modalState.categorie ? modalState.categorie : "")
|
||||
setCountry_(modalState.country ? modalState.country : "FR")
|
||||
setGenre(modalState.genre ? modalState.genre : "NA")
|
||||
}
|
||||
setLicence(modalState?.licence ? modalState.licence : "")
|
||||
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)
|
||||
setClub(modalState?.club ? modalState.club.name : "")
|
||||
setGCat(modalState?.categorie ? modalState.categorie : "")
|
||||
setCountry_(modalState?.country ? modalState.country : "FR")
|
||||
setGenre(modalState?.genre ? modalState.genre : "NA")
|
||||
setCategories(modalState?.categoriesInscrites ? modalState.categoriesInscrites : [])
|
||||
|
||||
setTimeout(() => {
|
||||
if (modalState?.id === 0) {
|
||||
licenceInput.current?.focus()
|
||||
} else if (modalState?.id < 0) {
|
||||
nameInput.current?.focus()
|
||||
}
|
||||
}, 450)
|
||||
}, [modalState]);
|
||||
|
||||
return <div className="modal fade" id="registerModal" tabIndex="-1" aria-labelledby="registerLabel"
|
||||
aria-hidden="true">
|
||||
const setCategories_ = (e, catId) => {
|
||||
if (e.target.checked) {
|
||||
if (!categories.includes(catId)) {
|
||||
setCategories([...categories, catId])
|
||||
}
|
||||
} else {
|
||||
setCategories(categories.filter(c => c !== catId))
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault()
|
||||
const new_state = {
|
||||
licence: Number.isInteger(licence) ? licence : licence.trim(),
|
||||
fname: fname.trim(),
|
||||
lname: lname.trim(),
|
||||
weight: weight,
|
||||
weightReal: weightReal,
|
||||
overCategory: cat,
|
||||
lockEdit: lockEdit,
|
||||
categoriesInscrites: categories,
|
||||
id: modalState.id !== 0 ? modalState.id : null
|
||||
}
|
||||
if (modalState.id < 0) {
|
||||
new_state.licence = -1
|
||||
new_state.categorie = gcat
|
||||
new_state.club = club
|
||||
new_state.country = country_
|
||||
new_state.genre = genre
|
||||
}
|
||||
sendRegister(new_state)
|
||||
.then(data => {
|
||||
if (!data) return;
|
||||
setModalState(data)
|
||||
if (editMode || data.id < 0) {
|
||||
closeBtn.current.click()
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
if (fistCatInput.current) {
|
||||
fistCatInput.current.focus()
|
||||
} else {
|
||||
submitBtn.current.focus()
|
||||
}
|
||||
}, 100)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const currenCat = gcat !== "" ? applyOverCategory(gcat, cat) : "";
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
return <div className="modal fade" id="registerModal" tabIndex="-1" aria-labelledby="registerLabel" aria-hidden="true">
|
||||
<div className="modal-dialog">
|
||||
<div className="modal-content">
|
||||
<form onSubmit={e => {
|
||||
e.preventDefault()
|
||||
const new_state = {
|
||||
licence: Number.isInteger(licence) ? licence : licence.trim(),
|
||||
fname: fname.trim(),
|
||||
lname: lname.trim(),
|
||||
weight: weight,
|
||||
overCategory: cat,
|
||||
lockEdit: lockEdit,
|
||||
id: modalState.id !== 0 ? modalState.id : null
|
||||
}
|
||||
if (modalState.id < 0) {
|
||||
new_state.licence = -1
|
||||
new_state.categorie = gcat
|
||||
new_state.club = club
|
||||
new_state.country = country_
|
||||
new_state.genre = genre
|
||||
}
|
||||
sendRegister(new_state)
|
||||
.then(() => {
|
||||
setModalState(new_state)
|
||||
})
|
||||
}}>
|
||||
<div className="modal-header">
|
||||
<h1 className="modal-title fs-5"
|
||||
id="registerLabel">{editMode ? t('modification') : t('ajout')} {t('dun')} {modalState.id >= 0 ? t('combattant') : t('invité')}</h1>
|
||||
<button type="button" className="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
{modalState.id < 0 &&
|
||||
<div className="mb-2">{t('comp.modal.text1')}</div>}
|
||||
<div className="card" style={{marginBottom: "1em"}}>
|
||||
<div className="card-header">{modalState.id >= 0 ? t('comp.modal.recherche') : t('comp.modal.information')}</div>
|
||||
<div className="card-body">
|
||||
<div className="row" hidden={modalState.id < 0}>
|
||||
<div className="col">
|
||||
<input type="number" min={0} step={1} className="form-control" placeholder={t("comp.noDeLicence")}
|
||||
name="licence"
|
||||
value={licence} onChange={e => setLicence(e.target.value)} disabled={editMode}/>
|
||||
</div>
|
||||
<div className="modal-header">
|
||||
<h1 className="modal-title fs-5"
|
||||
id="registerLabel">{editMode ? t('modification') : t('ajout')} {t('dun')} {modalState.id >= 0 ? t('combattant') : t('invité')}</h1>
|
||||
<button ref={closeBtn} type="button" className="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
{modalState.id < 0 &&
|
||||
<div className="mb-2">{t('comp.modal.text1')}</div>}
|
||||
<div className="card" style={{marginBottom: "1em"}}>
|
||||
<div className="card-header">{modalState.id >= 0 ? t('comp.modal.recherche') : t('comp.modal.information')}</div>
|
||||
<div className="card-body">
|
||||
<div className="row" hidden={modalState.id < 0}>
|
||||
<div className="col">
|
||||
<input ref={licenceInput} type="number" min={0} step={1} className="form-control"
|
||||
placeholder={t("comp.noDeLicence")} name="licence" value={licence}
|
||||
onChange={e => setLicence(e.target.value)} disabled={editMode}
|
||||
onKeyUp={e => e.key === "Enter" ? handleSubmit(e) : undefined}/>
|
||||
</div>
|
||||
<h5 style={{textAlign: "center", marginTop: "0.25em"}} hidden={modalState.id < 0}>{t('ou')}</h5>
|
||||
<div className="row">
|
||||
<div className="col">
|
||||
<input type="text" className="form-control" placeholder={t('prenom')} name="fname"
|
||||
disabled={editMode && modalState.id >= 0}
|
||||
value={fname} onChange={e => setFname(e.target.value)}/>
|
||||
</div>
|
||||
<div className="col">
|
||||
<input type="text" className="form-control" placeholder={t('nom')} name="lname"
|
||||
disabled={editMode && modalState.id >= 0}
|
||||
value={lname} onChange={e => setLname(e.target.value)}/>
|
||||
</div>
|
||||
</div>
|
||||
<h5 style={{textAlign: "center", marginTop: "0.25em"}} hidden={modalState.id < 0}>{t('ou')}</h5>
|
||||
<div className="row">
|
||||
<div className="col">
|
||||
<input ref={nameInput} type="text" className="form-control" placeholder={t('prenom')} name="fname"
|
||||
disabled={editMode && modalState.id >= 0}
|
||||
value={fname} onChange={e => setFname(e.target.value)}/>
|
||||
</div>
|
||||
<div className="col">
|
||||
<input type="text" className="form-control" placeholder={t('nom')} name="lname"
|
||||
disabled={editMode && modalState.id >= 0} value={lname} onChange={e => setLname(e.target.value)}
|
||||
onKeyUp={e => e.key === "Enter" && modalState.id >= 0 ? handleSubmit(e) : undefined}/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{(editMode || modalState.id < 0) && <>
|
||||
<div className="input-group mb-3" hidden={modalState.id >= 0}>
|
||||
<span className="input-group-text" id="categorie">{t("club", {count: 1})}</span>
|
||||
<input type="text" className="form-control" placeholder={t("club", {count: 1})} name="club"
|
||||
@ -432,8 +553,14 @@ function Modal({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}>
|
||||
@ -451,31 +578,53 @@ function Modal({sendRegister, modalState, setModalState, source}) {
|
||||
onChange={e => setLockEdit(e.target.checked)}/>
|
||||
<label className="form-check-label" htmlFor="switchCheckReverse">{t('comp.modal.text2')}</label>
|
||||
</div>}
|
||||
</div>
|
||||
<div className="modal-footer">
|
||||
<button type="submit" className="btn btn-primary">{editMode ? t('button.modifier') : t('button.ajouter')}</button>
|
||||
<button type="reset" className="btn btn-secondary" data-bs-dismiss="modal" id="closeModal">{t('button.annuler')}</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div className="d-flex flex-wrap">
|
||||
<label htmlFor="inputState2" className="form-label align-self-center" style={{margin: "0 0.5em 0 0"}}>
|
||||
{t('catégorie')} :
|
||||
</label>
|
||||
<CategoriesList error2={error2} availableCats={availableCats} fistCatInput={fistCatInput} categories={categories}
|
||||
setCategories={setCategories_}/>
|
||||
</div>
|
||||
</>}
|
||||
</div>
|
||||
<div className="modal-footer">
|
||||
<button type="button" className="btn btn-secondary" data-bs-dismiss="modal" id="closeModal">{t('button.annuler')}</button>
|
||||
<button ref={submitBtn} type="button" className="btn btn-primary"
|
||||
onClick={handleSubmit}>{editMode ? t('button.modifier') : t('button.ajouter')}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
let allClub = []
|
||||
let allCat = []
|
||||
|
||||
function FiltreBar({data, clubFilter, setClubFilter, catFilter, setCatFilter, source}) {
|
||||
function FiltreBar({
|
||||
data,
|
||||
data2,
|
||||
clubFilter,
|
||||
setClubFilter,
|
||||
catFilter,
|
||||
setCatFilter,
|
||||
catAgeFilter,
|
||||
setCatAgeFilter,
|
||||
filterNotWeight,
|
||||
setFilterNotWeight,
|
||||
source
|
||||
}) {
|
||||
const {t} = useTranslation();
|
||||
useEffect(() => {
|
||||
if (!data) return;
|
||||
allClub.push(...data.map((e) => e.club?.name))
|
||||
allClub = allClub.filter((value, index, self) => self.indexOf(value) === index).filter(value => value != null).sort()
|
||||
allCat.push(...data.map((e) => e.categorie))
|
||||
allCat = allCat.filter((value, index, self) => self.indexOf(value) === index).filter(value => value != null).sort()
|
||||
}, [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>
|
||||
@ -485,19 +634,24 @@ function FiltreBar({data, clubFilter, setClubFilter, catFilter, setCatFilter, so
|
||||
</select>
|
||||
</div>}
|
||||
<div className="mb-3">
|
||||
<select className="form-select" value={catFilter} onChange={event => setCatFilter(event.target.value)}>
|
||||
<option value="">{t('---TouteLesCatégories---')}</option>
|
||||
{allCat && allCat.map((value, index) => {
|
||||
return <option key={index} value={value}>{value}</option>
|
||||
<select className="form-select" value={catAgeFilter} onChange={event => setCatAgeFilter(event.target.value)}>
|
||||
<option value="">{t('---TousLesAges---')}</option>
|
||||
{CatList && CatList.map((value, index) => {
|
||||
return <option key={index} value={value}>{getCatName(value)}</option>
|
||||
})}
|
||||
</select>
|
||||
</div>
|
||||
<select className="form-select" value={catFilter} onChange={event => setCatFilter(Number(event.target.value))}>
|
||||
<option value={-1}>{t('---TouteLesCatégories---')}</option>
|
||||
{data2 && data2.map((cat) => {
|
||||
return <option key={cat.id} value={cat.id}>{cat.name}</option>
|
||||
})}
|
||||
</select>
|
||||
</div>
|
||||
}
|
||||
|
||||
function MakeCentralPanel({data, 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 <>
|
||||
@ -515,14 +669,29 @@ function MakeCentralPanel({data, dispatch, id, setModalState, source}) {
|
||||
<div className="row">
|
||||
<span className="col-auto">{req.data.licence ? String(req.data.licence).padStart(5, '0') : "-------"}</span>
|
||||
<div className="ms-2 col-auto">
|
||||
<div><strong>{req.data.fname} {req.data.lname}</strong> <small>{req.data.genre}</small></div>
|
||||
<div><strong>{req.data.fname} {req.data.lname}</strong> <small>{req.data.lockEdit &&
|
||||
<FontAwesomeIcon icon={faLock} style={{color: "#e40101",}}/>}{req.data.genre}</small></div>
|
||||
<small>{req.data.club?.name || t("club", {count: 0})}</small>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col-auto" style={{textAlign: "right"}}>
|
||||
<small>{t("comp.surclassement", {count: req.data.overCategory, cat: getCatName(req.data.categorie)})}<br/>
|
||||
{req.data.weight ? req.data.weight : "---"} kg
|
||||
<small>{t("comp.surclassement", {
|
||||
count: req.data.overCategory,
|
||||
cat: getCatName(req.data.categorie)
|
||||
})} {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)
|
||||
.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>)}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
@ -575,29 +744,78 @@ function MakeCentralPanel({data, dispatch, id, setModalState, source}) {
|
||||
</>
|
||||
}
|
||||
|
||||
function FileOutput({data}) {
|
||||
function FileOutput({data, data2}) {
|
||||
const {t} = useTranslation();
|
||||
|
||||
const handleFileDownload = () => {
|
||||
const catColumns = {}
|
||||
for (const cat of data2) {
|
||||
catColumns[cat.id] = ""
|
||||
}
|
||||
|
||||
const columnOrder = [
|
||||
"licence", "pays", "nom", "prenom", "genre", "weight",
|
||||
"categorie", "overCategory", "categorie2", "club",
|
||||
...Object.keys(catColumns)
|
||||
];
|
||||
|
||||
const dataOut = []
|
||||
for (const e of data) {
|
||||
const tmp = {
|
||||
licence: e.licence,
|
||||
licence: e.id <= 0 ? -1 : e.licence,
|
||||
pays: e.country,
|
||||
nom: e.lname,
|
||||
prenom: e.fname,
|
||||
genre: e.genre,
|
||||
weight: e.weight,
|
||||
categorie: e.categorie,
|
||||
weight: e.weightReal ? e.weightReal : e.weight,
|
||||
categorie: getCatName(e.categorie),
|
||||
overCategory: e.overCategory,
|
||||
categorie2: getCatName(applyOverCategory(e.categorie, e.overCategory)),
|
||||
club: e.club ? e.club.name : '',
|
||||
...catColumns
|
||||
}
|
||||
for (const c of e.categoriesInscrites) {
|
||||
tmp[c] = "X"
|
||||
}
|
||||
dataOut.push(tmp)
|
||||
}
|
||||
dataOut.sort((a, b) => a.prenom.localeCompare(b.prenom) || a.nom.localeCompare(b.nom));
|
||||
|
||||
const secondHeaders = [
|
||||
"Licence", "Pays", "Nom", "Prénom", "Genre", "Poids",
|
||||
"Catégorie normalizer", "Surclassement", "Catégorie d'inscription", "Club",
|
||||
...Object.keys(catColumns).map(id => data2.find(p => p.id === Number(id))?.name)
|
||||
];
|
||||
const headers = [
|
||||
"", "", "", "", "", "", "", "", "", "", "Catégories",
|
||||
...Object.keys(catColumns).map(() => "")
|
||||
];
|
||||
|
||||
const orderedData = dataOut.map(row => columnOrder.map(col => row[col]));
|
||||
|
||||
const wb = XLSX.utils.book_new();
|
||||
const ws = XLSX.utils.json_to_sheet(dataOut);
|
||||
XLSX.utils.sheet_add_aoa(ws, [["Licence", "Nom", "Prénom", "Genre", "Poids", "Catégorie normalizer", "Surclassement", "Club"]], {origin: 'A1'});
|
||||
const ws = XLSX.utils.json_to_sheet([], {skipHeader: true});
|
||||
|
||||
ws["!cols"] = [{wch: 7}, {wch: 16}, {wch: 16}, {wch: 6}, {wch: 6}, {wch: 10}, {wch: 10}, {wch: 60}]
|
||||
XLSX.utils.sheet_add_aoa(ws, [headers, secondHeaders, ...orderedData], {origin: "A1"});
|
||||
|
||||
// Fusionner les cellules pour le titre "Catégories"
|
||||
const mergeStart = XLSX.utils.encode_cell({r: 0, c: 10}); // Ligne 1, colonne K (index 10)
|
||||
const mergeEnd = XLSX.utils.encode_cell({r: 0, c: 10 + Object.keys(catColumns).length - 1});
|
||||
ws["!merges"] = [{s: mergeStart, e: mergeEnd}];
|
||||
|
||||
// 10. Appliquer une rotation de 45° aux en-têtes
|
||||
const headerRow = ws["!rows"] || (ws["!rows"] = {});
|
||||
headerRow[1] = {hpt: 70}; // Hauteur de la première ligne
|
||||
for (let i = 0; i < headers.length; i++) {
|
||||
const cellRef = XLSX.utils.encode_cell({r: 1, c: i});
|
||||
if (!ws[cellRef]) ws[cellRef] = {};
|
||||
ws[cellRef].s = {
|
||||
alignment: {textRotation: 45, vertical: "bottom", wrapText: true}
|
||||
};
|
||||
}
|
||||
|
||||
ws["!cols"] = [{wch: 5}, {wch: 4}, {wch: 16}, {wch: 16}, {wch: 4}, {wch: 4}, {wch: 10}, {wch: 4}, {wch: 10}, {wch: 60},
|
||||
...Object.keys(catColumns).map(() => ({wch: 2}))]
|
||||
|
||||
XLSX.utils.book_append_sheet(wb, ws, "Feuille 1");
|
||||
XLSX.writeFile(wb, "output.xlsx");
|
||||
@ -610,6 +828,113 @@ function FileOutput({data}) {
|
||||
);
|
||||
}
|
||||
|
||||
function FileImportGuest({data2, sendRegisters}) {
|
||||
const {t} = useTranslation();
|
||||
|
||||
const expectedFields = [
|
||||
{key: 'nom', label: t('nom'), mandatory: true, type: 'String'},
|
||||
{key: 'prenom', label: t('prenom'), mandatory: true, type: 'String'},
|
||||
{key: 'pays', label: t('pays'), mandatory: false, type: 'String'},
|
||||
{key: 'genre', label: t('genre'), mandatory: false, type: 'String'},
|
||||
{key: 'weight', label: t('poids'), mandatory: false, type: 'Integer'},
|
||||
{key: 'categorie', label: t('catégorie'), mandatory: true, type: 'String'},
|
||||
{key: 'club', label: t('club', {count: 1}), mandatory: false, type: 'String'},
|
||||
];
|
||||
|
||||
if (data2)
|
||||
data2.forEach(row => {
|
||||
expectedFields.push({key: "__" + row.id, label: row.name, mandatory: false, type: 'Boolean'})
|
||||
})
|
||||
|
||||
|
||||
const onDataMapped = (mappedData) => {
|
||||
const out = []
|
||||
mappedData.forEach(row => {
|
||||
if (!row.nom || !row.prenom || !row.categorie) {
|
||||
toast.warn(t('ligneIgnorée1'))
|
||||
return;
|
||||
}
|
||||
|
||||
const categoriesInscrites = []
|
||||
data2.forEach(cat => {
|
||||
if (row["__" + cat.id]) {
|
||||
categoriesInscrites.push(cat.id)
|
||||
}
|
||||
delete row["__" + cat.id]
|
||||
})
|
||||
out.push({
|
||||
id: 0,
|
||||
licence: -1,
|
||||
fname: row.prenom.trim(),
|
||||
lname: row.nom.trim(),
|
||||
country: row.pays ? row.pays.trim() : "FR",
|
||||
genre: row.genre ? row.genre.trim() : "NA",
|
||||
categorie: getCatFromName(row.categorie.trim()),
|
||||
club: row.club ? row.club.trim() : "",
|
||||
weight: row.weight,
|
||||
overCategory: 0,
|
||||
lockEdit: false,
|
||||
categoriesInscrites: categoriesInscrites
|
||||
})
|
||||
})
|
||||
|
||||
sendRegisters(out)
|
||||
}
|
||||
|
||||
return <FileImport onDataMapped={onDataMapped} expectedFields={expectedFields} textButton={t('importerDesInvités')}/>
|
||||
}
|
||||
|
||||
function FileImportComb({data2, sendRegisters}) {
|
||||
const {t} = useTranslation();
|
||||
|
||||
const expectedFields = [
|
||||
{key: 'licence', label: t('licence'), mandatory: true, type: 'Integer'},
|
||||
{key: 'nom', label: t('nom'), mandatory: true, type: 'String'},
|
||||
{key: 'prenom', label: t('prenom'), mandatory: true, type: 'String'},
|
||||
{key: 'weight', label: t('poids'), mandatory: false, type: 'Integer'},
|
||||
{key: 'overCategory', label: t('comp.modal.surclassement'), mandatory: false, type: 'Integer'},
|
||||
];
|
||||
|
||||
if (data2)
|
||||
data2.forEach(row => {
|
||||
expectedFields.push({key: "__" + row.id, label: row.name, mandatory: false, type: 'Boolean'})
|
||||
})
|
||||
|
||||
const onDataMapped = (mappedData) => {
|
||||
const out = []
|
||||
mappedData.forEach(row => {
|
||||
if (row.licence && row.licence <= 0)
|
||||
return;
|
||||
if (!(row.licence || (row.nom && row.prenom))) {
|
||||
toast.warn(t('ligneIgnorée2'))
|
||||
return;
|
||||
}
|
||||
|
||||
const categoriesInscrites = []
|
||||
data2.forEach(cat => {
|
||||
if (row["__" + cat.id]) {
|
||||
categoriesInscrites.push(cat.id)
|
||||
}
|
||||
delete row["__" + cat.id]
|
||||
})
|
||||
out.push({
|
||||
id: 0,
|
||||
licence: row.licence,
|
||||
fname: row.prenom.trim(),
|
||||
lname: row.nom.trim(),
|
||||
weight: row.weight,
|
||||
overCategory: row.overCategory,
|
||||
lockEdit: false,
|
||||
categoriesInscrites: categoriesInscrites
|
||||
})
|
||||
})
|
||||
|
||||
sendRegisters(out)
|
||||
}
|
||||
|
||||
return <FileImport onDataMapped={onDataMapped} expectedFields={expectedFields} textButton={t('importerDesCombattants')}/>
|
||||
}
|
||||
|
||||
function Def() {
|
||||
return <div className="list-group">
|
||||
<li className="list-group-item"><ThreeDots/></li>
|
||||
|
||||
@ -3,9 +3,9 @@ import {useLoadingSwitcher} from "../../hooks/useLoading.jsx";
|
||||
import {useFetch} from "../../hooks/useFetch.js";
|
||||
import {AxiosError} from "../../components/AxiosError.jsx";
|
||||
import {useAuth} from "../../hooks/useAuth.jsx";
|
||||
import {apiAxios, getToastMessage, isClubAdmin} from "../../utils/Tools.js";
|
||||
import {apiAxios, applyOverCategory, getCatName, getToastMessage, isClubAdmin} from "../../utils/Tools.js";
|
||||
import {ThreeDots} from "react-loader-spinner";
|
||||
import {useEffect, useState} from "react";
|
||||
import React, {useEffect, useState} from "react";
|
||||
import {toast} from "react-toastify";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import i18n from "i18next";
|
||||
@ -74,8 +74,8 @@ 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 data2={data}/>
|
||||
{data.registerMode === "FREE" && !isClubAdmin(userinfo) && <SelfRegister data2={data}/>
|
||||
|| <ShowRegister data2={data}/>
|
||||
}
|
||||
{data.registerMode === "HELLOASSO" &&
|
||||
<p><strong>{t('comp.billetterie')} :</strong> <a
|
||||
@ -97,15 +97,18 @@ function SelfRegister({data2}) {
|
||||
const {id} = useParams()
|
||||
const setLoading = useLoadingSwitcher()
|
||||
const {data, refresh, error} = useFetch(`/competition/${id}/register/user`, setLoading, 1)
|
||||
const {data: data3, error: error2} = useFetch(`/competition/${id}/categories`, setLoading, 1)
|
||||
const {t} = useTranslation();
|
||||
|
||||
const [weight, setWeight] = useState("")
|
||||
const [cat, setCat] = useState(0)
|
||||
const [categories, setCategories] = useState([])
|
||||
|
||||
useEffect(() => {
|
||||
if (data && data.length > 0) {
|
||||
setWeight(data[0].weight || "")
|
||||
setCat(data[0].overCategory || 0)
|
||||
setCategories(data[0].categoriesInscrites || [])
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
@ -129,22 +132,41 @@ function SelfRegister({data2}) {
|
||||
|
||||
const handleSubmit = () => {
|
||||
sendSubmit({
|
||||
licence: 0, fname: "", lname: "", weight: weight, overCategory: cat, lockEdit: false, id: null
|
||||
licence: 0, fname: "", lname: "", weight: weight, overCategory: cat, lockEdit: false, id: null, categoriesInscrites: categories
|
||||
})
|
||||
}
|
||||
|
||||
const setCategories_ = (e, catId) => {
|
||||
if (e.target.checked) {
|
||||
if (!categories.includes(catId)) {
|
||||
setCategories([...categories, catId])
|
||||
}
|
||||
} else {
|
||||
setCategories(categories.filter(c => c !== catId))
|
||||
}
|
||||
}
|
||||
|
||||
const currenCat = data?.length > 0 && data[0]?.categorie !== "" ? applyOverCategory(data[0]?.categorie, cat) : "";
|
||||
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
|
||||
? data.length > 0
|
||||
? <div style={{textAlign: "right", maxWidth: "20em"}}>
|
||||
? <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')}: {data[0].categorie}</div>
|
||||
<div style={{textAlign: "left"}}>{t('comp.catégorieNormalisée')}: {getCatName(data[0].categorie)}</div>
|
||||
<div className="input-group mb-3">
|
||||
<span className="input-group-text" id="categorie">{t("comp.modal.surclassement")}</span>
|
||||
<select className="form-select" aria-label="categorie" name="categorie" value={cat} disabled={disabled}
|
||||
@ -155,12 +177,31 @@ function SelfRegister({data2}) {
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="d-flex flex-wrap mb-3">
|
||||
<label htmlFor="inputState2" className="form-label align-self-center" style={{margin: "0 0.5em 0 0"}}>
|
||||
{t('catégorie')} :
|
||||
</label>
|
||||
{error2 ? <AxiosError error={error2}/> : <>
|
||||
{availableCats && availableCats.length === 0 && <div>{t('aucuneCatégorieDisponible')}</div>}
|
||||
{availableCats && availableCats.map((cat, index) =>
|
||||
<div key={cat.id} className="input-group"
|
||||
style={{display: "contents"}}>
|
||||
<div className="input-group-text">
|
||||
<input className="form-check-input mt-0" type="checkbox"
|
||||
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>
|
||||
</div>
|
||||
</div>)}
|
||||
</>}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button type="button" className="btn btn-danger" disabled={disabled} style={{marginRight: "0.5em"}}
|
||||
onClick={handleUnregister}>{t('button.seDésinscrire')}
|
||||
</button>
|
||||
<button type="button" className="btn btn-primary" disabled={disabled}
|
||||
onClick={handleSubmit}>{t('button.enregister')}
|
||||
onClick={handleSubmit}>{t('button.enregistrer')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -172,6 +213,44 @@ function SelfRegister({data2}) {
|
||||
</>
|
||||
}
|
||||
|
||||
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
|
||||
? <div style={{textAlign: "right", maxWidth: "30em"}}>
|
||||
<h4 style={{textAlign: "left"}}>{t('comp.monInscription')}</h4>
|
||||
<div style={{textAlign: "left"}}>{t('comp.catégorieNormalisée')} : {getCatName(data[0].categorie)}</div>
|
||||
<div style={{textAlign: "left"}}>{t('comp.modal.surclassement')} :
|
||||
{data[0].overCategory === 0 && ` ${t('aucun')}`}
|
||||
{data[0].overCategory === 1 && ` ${t('1Catégorie')}`}
|
||||
{data[0].overCategory === 2 && ` ${t('2Catégorie')}`}
|
||||
</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>}
|
||||
{data3 && data3.filter(c => data[0].categoriesInscrites.includes(c.id)).sort((a, b) => a.name.localeCompare(b.name)).map(cat =>
|
||||
<span key={cat.id} className="badge text-bg-secondary" style={{margin: "0 0.25em"}}>{cat.name}</span>)}
|
||||
</>}
|
||||
</div>
|
||||
</div> : <span>{t('vousNêtesPasEncoreInscrit')}</span>
|
||||
: error
|
||||
? <AxiosError error={error}/>
|
||||
: <Def/>
|
||||
}
|
||||
</>
|
||||
}
|
||||
|
||||
function Def() {
|
||||
return <div className="list-group">
|
||||
<li className="list-group-item"><ThreeDots/></li>
|
||||
|
||||
@ -42,6 +42,32 @@ export const CatList = [
|
||||
"VETERAN2"
|
||||
];
|
||||
|
||||
export function sortCategories(catA, catB) {
|
||||
const indexA = CatList.indexOf(catA);
|
||||
const indexB = CatList.indexOf(catB);
|
||||
|
||||
if (indexA === -1 && indexB === -1) {
|
||||
return 0; // Both categories are unknown, maintain their order
|
||||
} else if (indexA === -1) {
|
||||
return 1; // catA is unknown, place it after catB
|
||||
} else if (indexB === -1) {
|
||||
return -1; // catB is unknown, place it after catA
|
||||
} else {
|
||||
return indexA - indexB; // Both categories are known, sort by their indices
|
||||
}
|
||||
}
|
||||
|
||||
export function applyOverCategory(cat, overCat) {
|
||||
const catIndex = CatList.indexOf(cat) + overCat;
|
||||
if (catIndex < 0) {
|
||||
return CatList[0];
|
||||
} else if (catIndex >= CatList.length) {
|
||||
return CatList[CatList.length - 1];
|
||||
} else {
|
||||
return CatList[catIndex];
|
||||
}
|
||||
}
|
||||
|
||||
export function getCategoryFormBirthDate(birth_date, currentDate = new Date()) {
|
||||
const currentSaison = getSaison(currentDate)
|
||||
const birthYear = birth_date.getFullYear()
|
||||
@ -111,6 +137,82 @@ export function getCatName(cat) {
|
||||
}
|
||||
}
|
||||
|
||||
export function getCatFromName(name) {
|
||||
switch (name.toLowerCase()) {
|
||||
case i18n.t('cat.superMini').toLowerCase():
|
||||
return "SUPER_MINI";
|
||||
case i18n.t('cat.miniPoussin').toLowerCase():
|
||||
return "MINI_POUSSIN";
|
||||
case i18n.t('cat.poussin').toLowerCase():
|
||||
return "POUSSIN";
|
||||
case i18n.t('cat.benjamin').toLowerCase():
|
||||
return "BENJAMIN";
|
||||
case i18n.t('cat.minime').toLowerCase():
|
||||
return "MINIME";
|
||||
case i18n.t('cat.cadet').toLowerCase():
|
||||
return "CADET";
|
||||
case i18n.t('cat.junior').toLowerCase():
|
||||
return "JUNIOR";
|
||||
case i18n.t('cat.senior1').toLowerCase():
|
||||
return "SENIOR1";
|
||||
case i18n.t('cat.senior2').toLowerCase():
|
||||
return "SENIOR2";
|
||||
case i18n.t('cat.vétéran1').toLowerCase():
|
||||
return "VETERAN1";
|
||||
case i18n.t('cat.vétéran2').toLowerCase():
|
||||
return "VETERAN2";
|
||||
default:
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
export const SwordList = [
|
||||
"NONE",
|
||||
"ONE_HAND",
|
||||
"TWO_HAND",
|
||||
"SABER"
|
||||
]
|
||||
|
||||
export function getSwordTypeName(type) {
|
||||
switch (type) {
|
||||
case "NONE":
|
||||
return i18n.t('sword.none');
|
||||
case "ONE_HAND":
|
||||
return i18n.t('sword.oneHand');
|
||||
case "TWO_HAND":
|
||||
return i18n.t('sword.twoHand');
|
||||
case "SABER":
|
||||
return i18n.t('sword.saber');
|
||||
default:
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
export const ShieldList = [
|
||||
"NONE",
|
||||
"STANDARD",
|
||||
"ROUND",
|
||||
"TEARDROP",
|
||||
"BUCKLER"
|
||||
]
|
||||
|
||||
export function getShieldTypeName(type) {
|
||||
switch (type) {
|
||||
case "NONE":
|
||||
return i18n.t('shield.none');
|
||||
case "STANDARD":
|
||||
return i18n.t('shield.standard');
|
||||
case "ROUND":
|
||||
return i18n.t('shield.round');
|
||||
case "TEARDROP":
|
||||
return i18n.t('shield.teardrop');
|
||||
case "BUCKLER":
|
||||
return i18n.t('shield.buckler');
|
||||
default:
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
export function getToastMessage(msgKey, ns = 'common') {
|
||||
return {
|
||||
pending: i18n.t(msgKey + '.pending', {ns}),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user