diff --git a/src/main/java/fr/titionfire/ffsaf/data/model/CatPresetModel.java b/src/main/java/fr/titionfire/ffsaf/data/model/CatPresetModel.java new file mode 100644 index 0000000..950ef4d --- /dev/null +++ b/src/main/java/fr/titionfire/ffsaf/data/model/CatPresetModel.java @@ -0,0 +1,71 @@ +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 = ""; + + List categories; + + long roundDuration; + long pauseDuration; + + SwordType swordType = SwordType.NONE; + ShieldType shieldType = ShieldType.NONE; + + /* Bitmask protections: + * 1 - 1 - Head + * 2 - 2 - Throat + * 3 - 4 - Torso + * 4 - 8 - Arms + * 5 - 16 - Hands + * 6 - 32 - Groin + * 7 - 64 - Legs + */ + int mandatoryProtection = 0; + + @ManyToMany(mappedBy = "categoriesInscrites", fetch = FetchType.LAZY) + private List registers = new ArrayList<>(); + + @ManyToMany(mappedBy = "categoriesInscrites", fetch = FetchType.LAZY) + private List guest = new ArrayList<>(); + + public enum SwordType { + NONE, + ONE_HAND, + TWO_HAND, + SABER + } + + public enum ShieldType { + NONE, + STANDARD, + ROUND, + TEARDROP, + BUCKLER + } + +} diff --git a/src/main/java/fr/titionfire/ffsaf/data/model/CategoryModel.java b/src/main/java/fr/titionfire/ffsaf/data/model/CategoryModel.java index 5f381cf..de7720b 100644 --- a/src/main/java/fr/titionfire/ffsaf/data/model/CategoryModel.java +++ b/src/main/java/fr/titionfire/ffsaf/data/model/CategoryModel.java @@ -44,4 +44,8 @@ public class CategoryModel { Integer type; String liceName = "1"; + + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "id_preset", referencedColumnName = "id") + CatPresetModel preset; } diff --git a/src/main/java/fr/titionfire/ffsaf/data/model/CompetitionGuestModel.java b/src/main/java/fr/titionfire/ffsaf/data/model/CompetitionGuestModel.java index 42f2a4a..031d525 100644 --- a/src/main/java/fr/titionfire/ffsaf/data/model/CompetitionGuestModel.java +++ b/src/main/java/fr/titionfire/ffsaf/data/model/CompetitionGuestModel.java @@ -61,6 +61,14 @@ public class CompetitionGuestModel implements CombModel { ) List guest = new ArrayList<>(); + @ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST) + @JoinTable( + name = "categories_insc_guest", + joinColumns = @JoinColumn(name = "categories_id"), + inverseJoinColumns = @JoinColumn(name = "guest_id") + ) + List categoriesInscrites = new ArrayList<>(); + public CompetitionGuestModel(String s) { this.fname = s.substring(0, s.indexOf(" ")); this.lname = s.substring(s.indexOf(" ") + 1); diff --git a/src/main/java/fr/titionfire/ffsaf/data/model/CompetitionModel.java b/src/main/java/fr/titionfire/ffsaf/data/model/CompetitionModel.java index c3e2d7c..ceac29e 100644 --- a/src/main/java/fr/titionfire/ffsaf/data/model/CompetitionModel.java +++ b/src/main/java/fr/titionfire/ffsaf/data/model/CompetitionModel.java @@ -58,6 +58,8 @@ public class CompetitionModel { @OneToMany(mappedBy = "competition", fetch = FetchType.LAZY, cascade = CascadeType.ALL) List guests = new ArrayList<>(); + @OneToMany(mappedBy = "competition", fetch = FetchType.LAZY, cascade = CascadeType.PERSIST) + List catPreset = new ArrayList<>(); List banMembre = new ArrayList<>(); diff --git a/src/main/java/fr/titionfire/ffsaf/data/model/RegisterModel.java b/src/main/java/fr/titionfire/ffsaf/data/model/RegisterModel.java index 16691d3..dce503c 100644 --- a/src/main/java/fr/titionfire/ffsaf/data/model/RegisterModel.java +++ b/src/main/java/fr/titionfire/ffsaf/data/model/RegisterModel.java @@ -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 @@ -46,6 +49,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 = "comb_id") + ) + List 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()); diff --git a/src/main/java/fr/titionfire/ffsaf/data/repository/CatPresetRepository.java b/src/main/java/fr/titionfire/ffsaf/data/repository/CatPresetRepository.java new file mode 100644 index 0000000..bd62519 --- /dev/null +++ b/src/main/java/fr/titionfire/ffsaf/data/repository/CatPresetRepository.java @@ -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 { +} diff --git a/src/main/java/fr/titionfire/ffsaf/domain/service/CompetPermService.java b/src/main/java/fr/titionfire/ffsaf/domain/service/CompetPermService.java index 448b915..6babba7 100644 --- a/src/main/java/fr/titionfire/ffsaf/domain/service/CompetPermService.java +++ b/src/main/java/fr/titionfire/ffsaf/domain/service/CompetPermService.java @@ -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(); diff --git a/src/main/java/fr/titionfire/ffsaf/domain/service/CompetitionService.java b/src/main/java/fr/titionfire/ffsaf/domain/service/CompetitionService.java index 03faff3..d220b14 100644 --- a/src/main/java/fr/titionfire/ffsaf/domain/service/CompetitionService.java +++ b/src/main/java/fr/titionfire/ffsaf/domain/service/CompetitionService.java @@ -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 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,46 @@ public class CompetitionService { } } + private Uni syncPreset(CompetitionData data, CompetitionModel model) { + List 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.setRoundDuration(preset.getRoundDuration()); + presetModel.setPauseDuration(preset.getPauseDuration()); + presetModel.setCategories(preset.getCategories()); + presetModel.setMandatoryProtection(preset.getMandatoryProtection()); + } + + // 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<>()); @@ -271,11 +315,17 @@ public class CompetitionService { Uni> 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) + .onItem().call(guest -> Mutiny.fetch(guest.getCategoriesInscrites())) + .map(guest -> SimpleRegisterComb.fromModel(guest) + .setCategorieInscrite(guest.getCategoriesInscrites())) + .collect().asList() .invoke(l::addAll)); }); @@ -290,11 +340,15 @@ 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() + .call(rm -> rm == null ? Uni.createFrom().voidItem() : + Mutiny.fetch(rm.getCategoriesInscrites())) .map(rm -> rm == null ? List.of() : List.of(SimpleRegisterComb.fromModel(rm, List.of())))); } @@ -312,7 +366,8 @@ public class CompetitionService { return Panache.withTransaction(() -> repository.persist(c)); }) .chain(combModel -> updateRegister(data, c, combModel, true))) - .map(r -> SimpleRegisterComb.fromModel(r, r.getMembre().getLicences())); + .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 +378,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"); @@ -334,13 +389,21 @@ public class CompetitionService { model.setCountry(data.getCountry()); model.setWeight(data.getWeight()); 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()); }) - .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().contains(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) @@ -418,6 +481,19 @@ public class CompetitionService { } 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().contains(r.getCategorie2())); + }))) .chain(r -> Panache.withTransaction(() -> registerRepository.persist(r))) .call(r -> c.getSystem() == CompetitionSystem.INTERNAL ? sRegister.sendRegister(c.getUuid(), r) : Uni.createFrom().voidItem()); @@ -660,6 +736,12 @@ public class CompetitionService { .call(__ -> cache.invalidate(data.getId())); } + public Uni> 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 unregisterHelloAsso(NotificationData data) { if (!data.getState().equals("Refunded")) return Uni.createFrom().item(Response.ok().build()); @@ -693,8 +775,8 @@ public class CompetitionService { public Uni 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, 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() diff --git a/src/main/java/fr/titionfire/ffsaf/rest/CompetitionEndpoints.java b/src/main/java/fr/titionfire/ffsaf/rest/CompetitionEndpoints.java index 77cabc5..c2785af 100644 --- a/src/main/java/fr/titionfire/ffsaf/rest/CompetitionEndpoints.java +++ b/src/main/java/fr/titionfire/ffsaf/rest/CompetitionEndpoints.java @@ -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; @@ -79,6 +76,13 @@ public class CompetitionEndpoints { return service.getInternalData(securityCtx, id); } + @GET + @Path("{id}/categories") + @Authenticated + @Produces(MediaType.APPLICATION_JSON) + public Uni> getPresetsForCompetition(@PathParam("id") Long id) { + return service.getPresetsForCompetition(securityCtx, id); + } @GET @Path("all") diff --git a/src/main/java/fr/titionfire/ffsaf/rest/data/CompetitionData.java b/src/main/java/fr/titionfire/ffsaf/rest/data/CompetitionData.java index 15cf538..8b48765 100644 --- a/src/main/java/fr/titionfire/ffsaf/rest/data/CompetitionData.java +++ b/src/main/java/fr/titionfire/ffsaf/rest/data/CompetitionData.java @@ -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; @@ -41,6 +43,14 @@ public class CompetitionData { private String data3; private String data4; private String config; + private List presets; + + public CompetitionData () { + this(null, "", "", "", "", new Date(), new Date(), + CompetitionSystem.INTERNAL, RegisterMode.FREE, new Date(), new Date(), true, + null, "", "", null, true, true, + "", "", "", "", "{}", new ArrayList<>()); + } public static CompetitionData fromModel(CompetitionModel model) { if (model == null) @@ -50,7 +60,7 @@ 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<>()); } public static CompetitionData fromModelLight(CompetitionModel model) { @@ -61,7 +71,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<>()); if (model.getRegisterMode() == RegisterMode.HELLOASSO) { out.setData1(model.getData1()); @@ -84,6 +94,11 @@ public class CompetitionData { return this; } + public CompetitionData addPresets(List presets) { + this.presets = presets.stream().map(PresetData::fromModel).toList(); + return this; + } + @Data @AllArgsConstructor @RegisterForReflection diff --git a/src/main/java/fr/titionfire/ffsaf/rest/data/PresetData.java b/src/main/java/fr/titionfire/ffsaf/rest/data/PresetData.java new file mode 100644 index 0000000..1f5dba7 --- /dev/null +++ b/src/main/java/fr/titionfire/ffsaf/rest/data/PresetData.java @@ -0,0 +1,32 @@ +package fr.titionfire.ffsaf.rest.data; + +import fr.titionfire.ffsaf.data.model.CatPresetModel; +import fr.titionfire.ffsaf.utils.Categorie; +import io.quarkus.runtime.annotations.RegisterForReflection; +import lombok.AllArgsConstructor; +import lombok.Data; + +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 categories; + long roundDuration; + long pauseDuration; + int mandatoryProtection; + + public static PresetData fromModel(CatPresetModel model) { + if (model == null) + return null; + + return new PresetData(model.getId(), model.getName(), model.getSwordType(), model.getShieldType(), + model.getCategories(), model.getRoundDuration(), model.getPauseDuration(), + model.getMandatoryProtection()); + } +} diff --git a/src/main/java/fr/titionfire/ffsaf/rest/data/RegisterRequestData.java b/src/main/java/fr/titionfire/ffsaf/rest/data/RegisterRequestData.java index 3b482f1..6d72eb0 100644 --- a/src/main/java/fr/titionfire/ffsaf/rest/data/RegisterRequestData.java +++ b/src/main/java/fr/titionfire/ffsaf/rest/data/RegisterRequestData.java @@ -7,6 +7,8 @@ import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; +import java.util.List; + @Data @AllArgsConstructor @NoArgsConstructor @@ -19,6 +21,7 @@ public class RegisterRequestData { private Integer weight; private int overCategory; private boolean lockEdit = false; + private List categoriesInscrites; // for guest registration only private Long id = null; @@ -26,4 +29,6 @@ public class RegisterRequestData { private Genre genre = Genre.NA; private String club = null; private String country = null; + + private boolean quick = false; } diff --git a/src/main/java/fr/titionfire/ffsaf/rest/data/SimpleRegisterComb.java b/src/main/java/fr/titionfire/ffsaf/rest/data/SimpleRegisterComb.java index 148c950..c981763 100644 --- a/src/main/java/fr/titionfire/ffsaf/rest/data/SimpleRegisterComb.java +++ b/src/main/java/fr/titionfire/ffsaf/rest/data/SimpleRegisterComb.java @@ -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; @@ -12,6 +9,7 @@ import io.quarkus.runtime.annotations.RegisterForReflection; import lombok.AllArgsConstructor; import lombok.Data; +import java.util.ArrayList; import java.util.List; @Data @@ -30,6 +28,7 @@ public class SimpleRegisterComb { private int overCategory; private boolean hasLicenceActive; private boolean lockEdit; + private List categoriesInscrites; public static SimpleRegisterComb fromModel(RegisterModel register, List licences) { MembreModel membreModel = register.getMembre(); @@ -39,13 +38,19 @@ public class SimpleRegisterComb { SimpleClubModel.fromModel(register.getClub()), membreModel.getLicence(), register.getWeight(), 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(), 0, false, false, + new ArrayList<>()); + } + + public SimpleRegisterComb setCategorieInscrite(List presets) { + this.categoriesInscrites = presets.stream().map(CatPresetModel::getId).toList(); + return this; } } diff --git a/src/main/webapp/public/locales/en/cm.json b/src/main/webapp/public/locales/en/cm.json index 5e0df67..c993b44 100644 --- a/src/main/webapp/public/locales/en/cm.json +++ b/src/main/webapp/public/locales/en/cm.json @@ -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", diff --git a/src/main/webapp/public/locales/en/common.json b/src/main/webapp/public/locales/en/common.json index 50fe38a..d3dfedf 100644 --- a/src/main/webapp/public/locales/en/common.json +++ b/src/main/webapp/public/locales/en/common.json @@ -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", @@ -81,11 +83,14 @@ "ajouterUnClub": "Add a club", "ajouterUnMembre": "Add a member", "all_season": "--- all seasons ---", + "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 +98,6 @@ "button.appliquer": "Apply", "button.confirmer": "Confirm", "button.créer": "Create", - "button.enregister": "Save", "button.enregistrer": "Save", "button.fermer": "Close", "button.modifier": "Edit", @@ -115,6 +119,7 @@ "cat.vétéran2": "Veteran 2", "categorie": "category", "catégorie": "Category", + "catégorieàAjouter": "Category to add", "certificatMédical": "Medical certificate", "chargement...": "Loading...", "chargerLexcel": "Load Excel", @@ -249,6 +254,7 @@ "compte": "Account", "compétition": "Competition", "configuration": "Configuration", + "configurationDeLaCatégorie": "Category configuration", "conserverLancienEmail": "Keep the old email", "contactAdministratif": "Administrative contact", "contactInterne": "Internal contact", @@ -274,6 +280,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", @@ -474,6 +482,7 @@ "permission": "Permission", "photos": "Photos", "prenom": "First name", + "protectionObligatoire": "Mandatory protection", "prénomEtNom": "First and last name", "rechercher": "Search", "rechercher...": "Search...", @@ -494,8 +503,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 +521,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", diff --git a/src/main/webapp/public/locales/fr/cm.json b/src/main/webapp/public/locales/fr/cm.json index 4cc89af..94153d3 100644 --- a/src/main/webapp/public/locales/fr/cm.json +++ b/src/main/webapp/public/locales/fr/cm.json @@ -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", diff --git a/src/main/webapp/public/locales/fr/common.json b/src/main/webapp/public/locales/fr/common.json index d8b246d..38c5a56 100644 --- a/src/main/webapp/public/locales/fr/common.json +++ b/src/main/webapp/public/locales/fr/common.json @@ -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", @@ -81,11 +83,14 @@ "ajouterUnClub": "Ajouter un club", "ajouterUnMembre": "Ajouter un membre", "all_season": "--- tout les saisons ---", + "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 +98,6 @@ "button.appliquer": "Appliquer", "button.confirmer": "Confirmer", "button.créer": "Créer", - "button.enregister": "Enregister", "button.enregistrer": "Enregistrer", "button.fermer": "Fermer", "button.modifier": "Modifier", @@ -115,6 +119,7 @@ "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", "chargement...": "Chargement...", "chargerLexcel": "Charger l'Excel", @@ -249,6 +254,7 @@ "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", @@ -274,6 +280,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 ", @@ -474,6 +482,7 @@ "permission": "Permission", "photos": "Photos", "prenom": "Prénom", + "protectionObligatoire": "Protection obligatoire", "prénomEtNom": "Prénom et nom", "rechercher": "Rechercher", "rechercher...": "Rechercher...", @@ -494,8 +503,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 +521,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", diff --git a/src/main/webapp/src/components/ProtectionSelector.jsx b/src/main/webapp/src/components/ProtectionSelector.jsx new file mode 100644 index 0000000..5ee6063 --- /dev/null +++ b/src/main/webapp/src/components/ProtectionSelector.jsx @@ -0,0 +1,99 @@ + +const ProtectionSelector = ({mandatoryProtection = 0, setMandatoryProtection = () => {} }) => { + const toggle = (bit) => { + setMandatoryProtection(v => (v & bit ? v & ~bit : v | bit)); + }; + + const isOn = (bit) => (mandatoryProtection & bit) !== 0; + const color = (bit) => (isOn(bit) ? "#4ade80" : "#e5e7eb"); + + return ( +
+ + + + {/* Head */} + toggle(1)} + /> + + {/* Throat / Neck */} + toggle(2)} + /> + + {/* Torso */} + toggle(4)} + /> + + {/* Arms */} + toggle(8)} + /> + toggle(8)} + /> + + {/* Hands */} + toggle(16)} + /> + toggle(16)} + /> + + {/* Legs */} + toggle(64)} + /> + toggle(64)} + /> + + {/* Groin */} + toggle(32)} + /> + + {/* Feet */} + + + +
+ ); +} + +export default ProtectionSelector; diff --git a/src/main/webapp/src/pages/competition/CompetitionEdit.jsx b/src/main/webapp/src/pages/competition/CompetitionEdit.jsx index a938abd..fbadfa5 100644 --- a/src/main/webapp/src/pages/competition/CompetitionEdit.jsx +++ b/src/main/webapp/src/pages/competition/CompetitionEdit.jsx @@ -6,12 +6,23 @@ 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"; export function CompetitionEdit() { const {id} = useParams() @@ -190,10 +201,10 @@ function ContentSAFCAAndInternal({data2, type = "SAFCA"}) { }}> - -
-
- +
+
+ +
@@ -205,6 +216,9 @@ 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 [presetChange, setPresetChange] = useState(false) const {t} = useTranslation(); const handleSubmit = (event) => { @@ -227,6 +241,7 @@ function Content({data}) { out['startRegister'] = event.target.startRegister?.value out['endRegister'] = event.target.endRegister?.value out['registerMode'] = registerMode + out['presets'] = presets if (out['registerMode'] === "HELLOASSO") { out['data3'] = event.target.data3?.value @@ -277,14 +292,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
-
+ return <> +
{data.id ? t('comp.editionCompétition') : t('comp.créationCompétition')}
@@ -340,6 +358,40 @@ function Content({data}) {
+
+

+ +

+
+
+ +
+
{presetChange && t('LesModificationsNontEnregistrer')}
+
+ +
+
+
+
+
+

+
+
+ +
+
+
-
-
- + - + +} + +function CatModalContent({setPresets, setPresetChange, state}) { + const [name, setName] = useState(state.name || "") + const [sword, setSword] = useState(state.sword || "NONE") + const [shield, setShield] = useState(state.shield || "NONE") + const [time, setTime] = useState(timePrint(state.roundDuration || 90000)) + const [pause, setPause] = useState(timePrint(state.pauseDuration || 60000)) + const [cats, setCats] = useState(state.categories || []) + const [mandatoryProtection, setMandatoryProtection] = useState(state.mandatoryProtection || 33) + + const {t} = useTranslation(); + + useEffect(() => { + setName(state.name || "") + setSword(state.sword || "NONE") + setShield(state.shield || "NONE") + setTime(timePrint(state.roundDuration || 90000)) + setPause(timePrint(state.pauseDuration || 60000)) + setCats(state.categories || []) + setMandatoryProtection(state.mandatoryProtection || 33) + }, [state]); + + 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 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, + roundDuration: parseTime(time), + pauseDuration: parseTime(pause), + categories: cats, + mandatoryProtection: mandatoryProtection + } + 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 <> +
+

{t('configurationDeLaCatégorie')}

+ +
+
+
+
+
+ {t("nom")} + setName(e.target.value)}/> +
+ +
+ {t('duréeRound')} + setTime(e.target.value)}/> + (mm:ss) +
+
+ {t('duréePause')} + setPause(e.target.value)}/> + (mm:ss) +
+ +
+ {t('arme')} + +
+ +
+ {t('bouclier')} + +
+ + +
+ {CatList.map((cat, index) =>
+
+ setCat(e, cat)}/> + +
+
)} +
+
+ +
+
+
{t('protectionObligatoire')} :
+ +
+
+
+
+
+ + + +
+ } diff --git a/src/main/webapp/src/pages/competition/CompetitionRegisterAdmin.jsx b/src/main/webapp/src/pages/competition/CompetitionRegisterAdmin.jsx index 11f17c3..48269b4 100644 --- a/src/main/webapp/src/pages/competition/CompetitionRegisterAdmin.jsx +++ b/src/main/webapp/src/pages/competition/CompetitionRegisterAdmin.jsx @@ -3,8 +3,8 @@ 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, useReducer, useRef, useState} from "react"; +import {apiAxios, applyOverCategory, CatList, getCatName, getToastMessage} from "../../utils/Tools.js"; import {toast} from "react-toastify"; import {SimpleReducer} from "../../utils/SimpleReducer.jsx"; import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; @@ -19,12 +19,14 @@ export function CompetitionRegisterAdmin({source}) { 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 [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 sortName = (a, b) => { if (a.data.fname === b.data.fname) return a.data.lname.localeCompare(b.data.lname); @@ -43,11 +45,11 @@ 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 }) } @@ -62,7 +64,9 @@ export function CompetitionRegisterAdmin({source}) {
{data ?
(clubFilter.length === 0 || s.data.club.name === clubFilter) && (catFilter.length === 0 || s.data.categorie === catFilter))} + 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)))} dispatch={dispatch} id={id} setModalState={setModalState} source={source}/>
: error ? : }
@@ -77,36 +81,60 @@ export function CompetitionRegisterAdmin({source}) { onClick={() => setModalState({id: -793548328091516928})}>{t('comp.ajouterUnInvité')}
} - +
{t('filtre')}
- +
{source === "admin" && }
- + } -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
{t('comp.ajoutRapide')}
+
+ + +
+
{t('comp.noDeLicence')}
@@ -131,14 +159,14 @@ function QuickAdd({sendRegister, source}) { {source === "club" && - + }
} -function SearchMember({sendRegister}) { +function SearchMember({sendRegister, categories}) { const setLoading = useLoadingSwitcher() const {data, error} = useFetch(`/club/members`, setLoading, 1) const [suggestions, setSuggestions] = useState([]) @@ -158,7 +186,9 @@ function SearchMember({sendRegister}) { weight: "", overCategory: 0, lockEdit: false, - id: null + id: null, + quick: true, + categoriesInscrites: categories }) } @@ -284,9 +314,33 @@ const AutoCompleteInput = ({suggestions = [], handleAdd}) => { ); }; -function Modal({sendRegister, modalState, setModalState, source}) { +function CategoriesList({error2, availableCats, fistCatInput, categories, setCategories}) { + const {t} = useTranslation(); + return <> + {error2 ? : <> + {availableCats && availableCats.length === 0 &&
{t('aucuneCatégorieDisponible')}
} + {availableCats && availableCats.map((cat, index) => +
+
+ setCategories(e, cat.id)}/> + +
+
)} + } + +} + +function Modal({data2, 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("") @@ -299,97 +353,125 @@ 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 : "") + 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