commit
b9d752ac55
@ -41,7 +41,7 @@ public class LogModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public enum ObjectType {
|
public enum ObjectType {
|
||||||
Membre, Affiliation, Licence, Club, Competition, Register
|
Membre, Affiliation, Licence, Club, Competition, Register, Selection
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -72,6 +72,10 @@ public class MembreModel implements LoggableModel, CombModel {
|
|||||||
@Schema(description = "Les licences du membre. (optionnel)")
|
@Schema(description = "Les licences du membre. (optionnel)")
|
||||||
List<LicenceModel> licences;
|
List<LicenceModel> licences;
|
||||||
|
|
||||||
|
@OneToMany(mappedBy = "membre", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
|
||||||
|
@Schema(description = "Les séléctions du membre. (optionnel)")
|
||||||
|
List<SelectionModel> selections;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getObjectName() {
|
public String getObjectName() {
|
||||||
return fname + " " + lname;
|
return fname + " " + lname;
|
||||||
|
|||||||
@ -0,0 +1,44 @@
|
|||||||
|
package fr.titionfire.ffsaf.data.model;
|
||||||
|
|
||||||
|
import fr.titionfire.ffsaf.utils.Categorie;
|
||||||
|
import io.quarkus.runtime.annotations.RegisterForReflection;
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.*;
|
||||||
|
import org.eclipse.microprofile.openapi.annotations.media.Schema;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@Builder
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@RegisterForReflection
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "selection")
|
||||||
|
public class SelectionModel implements LoggableModel {
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
@Schema(description = "L'identifiant de la séléction.")
|
||||||
|
Long id;
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "membre", referencedColumnName = "id")
|
||||||
|
@Schema(description = "Le membre de la séléction. (optionnel)")
|
||||||
|
MembreModel membre;
|
||||||
|
|
||||||
|
@Schema(description = "La saison de la séléction.", examples = "2025")
|
||||||
|
int saison;
|
||||||
|
|
||||||
|
@Schema(description = "Catégorie de la séléction.")
|
||||||
|
Categorie categorie;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getObjectName() {
|
||||||
|
return "selection " + id.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LogModel.ObjectType getObjectType() {
|
||||||
|
return LogModel.ObjectType.Selection;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
package fr.titionfire.ffsaf.data.repository;
|
||||||
|
|
||||||
|
import fr.titionfire.ffsaf.data.model.SelectionModel;
|
||||||
|
import io.quarkus.hibernate.reactive.panache.PanacheRepositoryBase;
|
||||||
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
|
|
||||||
|
@ApplicationScoped
|
||||||
|
public class SelectionRepository implements PanacheRepositoryBase<SelectionModel, Long> {
|
||||||
|
}
|
||||||
@ -7,10 +7,7 @@ import fr.titionfire.ffsaf.data.repository.*;
|
|||||||
import fr.titionfire.ffsaf.net2.ServerCustom;
|
import fr.titionfire.ffsaf.net2.ServerCustom;
|
||||||
import fr.titionfire.ffsaf.net2.data.SimpleCombModel;
|
import fr.titionfire.ffsaf.net2.data.SimpleCombModel;
|
||||||
import fr.titionfire.ffsaf.net2.request.SReqComb;
|
import fr.titionfire.ffsaf.net2.request.SReqComb;
|
||||||
import fr.titionfire.ffsaf.rest.data.MeData;
|
import fr.titionfire.ffsaf.rest.data.*;
|
||||||
import fr.titionfire.ffsaf.rest.data.SimpleLicence;
|
|
||||||
import fr.titionfire.ffsaf.rest.data.SimpleMembre;
|
|
||||||
import fr.titionfire.ffsaf.rest.data.SimpleMembreInOutData;
|
|
||||||
import fr.titionfire.ffsaf.rest.exception.DBadRequestException;
|
import fr.titionfire.ffsaf.rest.exception.DBadRequestException;
|
||||||
import fr.titionfire.ffsaf.rest.exception.DForbiddenException;
|
import fr.titionfire.ffsaf.rest.exception.DForbiddenException;
|
||||||
import fr.titionfire.ffsaf.rest.exception.DInternalError;
|
import fr.titionfire.ffsaf.rest.exception.DInternalError;
|
||||||
@ -479,7 +476,8 @@ public class MembreService {
|
|||||||
return clubRepository.findById(input.getClub())
|
return clubRepository.findById(input.getClub())
|
||||||
.call(__ -> repository.count("email LIKE ?1", input.getEmail())
|
.call(__ -> repository.count("email LIKE ?1", input.getEmail())
|
||||||
.invoke(Unchecked.consumer(c -> {
|
.invoke(Unchecked.consumer(c -> {
|
||||||
if (c > 0) throw new DBadRequestException("Email déjà utilisé");
|
if (c > 0 && input.getEmail() != null && !input.getEmail().isBlank())
|
||||||
|
throw new DBadRequestException("Email déjà utilisé");
|
||||||
})))
|
})))
|
||||||
.chain(clubModel -> {
|
.chain(clubModel -> {
|
||||||
MembreModel model = getMembreModel(input, clubModel);
|
MembreModel model = getMembreModel(input, clubModel);
|
||||||
@ -589,9 +587,12 @@ public class MembreService {
|
|||||||
MeData meData = new MeData();
|
MeData meData = new MeData();
|
||||||
return repository.find("userId = ?1", subject).firstResult()
|
return repository.find("userId = ?1", subject).firstResult()
|
||||||
.invoke(meData::setMembre)
|
.invoke(meData::setMembre)
|
||||||
.chain(membreModel -> Mutiny.fetch(membreModel.getLicences()))
|
.call(membreModel -> Mutiny.fetch(membreModel.getLicences())
|
||||||
.map(licences -> licences.stream().map(SimpleLicence::fromModel).toList())
|
.map(licences -> licences.stream().map(SimpleLicence::fromModel).toList())
|
||||||
.invoke(meData::setLicences)
|
.invoke(meData::setLicences))
|
||||||
|
.call(membreModel -> Mutiny.fetch(membreModel.getSelections())
|
||||||
|
.map(licences -> licences.stream().map(SimpleSelection::fromModel).toList())
|
||||||
|
.invoke(meData::setSelections))
|
||||||
.map(__ -> meData);
|
.map(__ -> meData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -93,12 +93,21 @@ public class ResultService {
|
|||||||
|
|
||||||
public Uni<List<Object[]>> getList(SecurityCtx securityCtx) {
|
public Uni<List<Object[]>> getList(SecurityCtx securityCtx) {
|
||||||
return membreService.getByAccountId(securityCtx.getSubject())
|
return membreService.getByAccountId(securityCtx.getSubject())
|
||||||
.chain(m -> registerRepository.list("membre = ?1", m))
|
.chain(m -> registerRepository.list(
|
||||||
|
"membre = ?1 OR (TRUE = ?2 AND membre.club = ?3)",
|
||||||
|
m, securityCtx.isClubAdmin(), m.getClub()))
|
||||||
.onItem().transformToMulti(Multi.createFrom()::iterable)
|
.onItem().transformToMulti(Multi.createFrom()::iterable)
|
||||||
.onItem().call(r -> Mutiny.fetch(r.getCompetition()))
|
.onItem().call(r -> Mutiny.fetch(r.getCompetition()))
|
||||||
.onItem().transform(r -> new Object[]{r.getCompetition().getUuid(), r.getCompetition().getName(),
|
.onItem().transform(RegisterModel::getCompetition)
|
||||||
r.getCompetition().getDate()})
|
.collect().asList()
|
||||||
.collect().asList();
|
.chain(l -> compRepository.list("owner = ?1 OR ?1 IN admin", securityCtx.getSubject())
|
||||||
|
.map(l2 -> Stream.concat(l.stream(), l2.stream()).distinct()
|
||||||
|
.map(c -> new Object[]{c.getUuid(), c.getName(), c.getDate()}).toList())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Uni<HashMap<String, Long>> getCategoryList(String uuid, SecurityCtx securityCtx) {
|
||||||
|
return hasAccess(uuid, securityCtx).chain(__ -> getCategoryList(uuid));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Uni<HashMap<String, Long>> getCategoryList(String uuid) {
|
public Uni<HashMap<String, Long>> getCategoryList(String uuid) {
|
||||||
@ -113,11 +122,11 @@ public class ResultService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Uni<ResultCategoryData> getCategory(String uuid, long poule, SecurityCtx securityCtx) {
|
public Uni<ResultCategoryData> getCategory(String uuid, long poule, SecurityCtx securityCtx) {
|
||||||
return hasAccess(uuid, securityCtx).chain(r ->
|
return hasAccess(uuid, securityCtx).chain(membreModel ->
|
||||||
matchRepository.list("category.compet.uuid = ?1 AND category.id = ?2", uuid, poule)
|
matchRepository.list("category.compet.uuid = ?1 AND category.id = ?2", uuid, poule)
|
||||||
.call(list -> list.isEmpty() ? Uni.createFrom().voidItem() :
|
.call(list -> list.isEmpty() ? Uni.createFrom().voidItem() :
|
||||||
Mutiny.fetch(list.get(0).getCategory().getTree()))
|
Mutiny.fetch(list.get(0).getCategory().getTree()))
|
||||||
.map(list -> getData(list, r.getMembre())));
|
.map(list -> getData(list, membreModel)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Uni<ResultCategoryData> getCategory(String uuid, long poule) {
|
public Uni<ResultCategoryData> getCategory(String uuid, long poule) {
|
||||||
@ -242,7 +251,7 @@ public class ResultService {
|
|||||||
|
|
||||||
public Uni<CombsArrayData> getAllCombArray(String uuid, SecurityCtx securityCtx) {
|
public Uni<CombsArrayData> getAllCombArray(String uuid, SecurityCtx securityCtx) {
|
||||||
return hasAccess(uuid, securityCtx)
|
return hasAccess(uuid, securityCtx)
|
||||||
.chain(r -> getAllCombArray_(uuid, r.getMembre()));
|
.chain(membreModel -> getAllCombArray_(uuid, membreModel));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Uni<CombsArrayData> getAllCombArrayPublic(String uuid) {
|
public Uni<CombsArrayData> getAllCombArrayPublic(String uuid) {
|
||||||
@ -315,7 +324,16 @@ public class ResultService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public Uni<HashMap<String, String>> getCombList(String uuid, ResultPrivacy privacy) {
|
public Uni<HashMap<String, String>> getCombList(String uuid, SecurityCtx securityCtx) {
|
||||||
|
return hasAccess(uuid, securityCtx)
|
||||||
|
.chain(membreModel -> getCombList(uuid, ResultPrivacy.REGISTERED_ONLY));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Uni<HashMap<String, String>> getCombList(String uuid) {
|
||||||
|
return getCombList(uuid, ResultPrivacy.PUBLIC);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Uni<HashMap<String, String>> getCombList(String uuid, ResultPrivacy privacy) {
|
||||||
return registerRepository.list("competition.uuid = ?1 AND membre.resultPrivacy <= ?2", uuid, privacy)
|
return registerRepository.list("competition.uuid = ?1 AND membre.resultPrivacy <= ?2", uuid, privacy)
|
||||||
.map(models -> {
|
.map(models -> {
|
||||||
HashMap<String, String> map = new HashMap<>();
|
HashMap<String, String> map = new HashMap<>();
|
||||||
@ -332,7 +350,16 @@ public class ResultService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Uni<?> getCombArrayPublic(String uuid, String combTempId, ResultPrivacy privacy) {
|
public Uni<?> getCombArrayPublic(String uuid, String combTempId, SecurityCtx securityCtx) {
|
||||||
|
return hasAccess(uuid, securityCtx)
|
||||||
|
.chain(membreModel -> getCombArrayPublic(uuid, combTempId, ResultPrivacy.REGISTERED_ONLY));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Uni<?> getCombArrayPublic(String uuid, String combTempId) {
|
||||||
|
return getCombArrayPublic(uuid, combTempId, ResultPrivacy.PUBLIC);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Uni<?> getCombArrayPublic(String uuid, String combTempId, ResultPrivacy privacy) {
|
||||||
CombArrayData.CombArrayDataBuilder builder = CombArrayData.builder();
|
CombArrayData.CombArrayDataBuilder builder = CombArrayData.builder();
|
||||||
|
|
||||||
Long id = getCombTempId(combTempId);
|
Long id = getCombTempId(combTempId);
|
||||||
@ -471,6 +498,10 @@ public class ResultService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Uni<HashMap<String, Long>> getClubList(String uuid, SecurityCtx securityCtx) {
|
||||||
|
return hasAccess(uuid, securityCtx).chain(__ -> getClubList(uuid));
|
||||||
|
}
|
||||||
|
|
||||||
public Uni<HashMap<String, Long>> getClubList(String uuid) {
|
public Uni<HashMap<String, Long>> getClubList(String uuid) {
|
||||||
return registerRepository.list("competition.uuid = ?1", uuid)
|
return registerRepository.list("competition.uuid = ?1", uuid)
|
||||||
.map(registers -> {
|
.map(registers -> {
|
||||||
@ -491,7 +522,7 @@ public class ResultService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Uni<ClubArrayData> getClubArray(String uuid, Long id, SecurityCtx securityCtx) {
|
public Uni<ClubArrayData> getClubArray(String uuid, Long id, SecurityCtx securityCtx) {
|
||||||
return hasAccess(uuid, securityCtx).chain(cm_register -> getClubArray2(uuid, id, cm_register.getMembre()));
|
return hasAccess(uuid, securityCtx).chain(membreModel -> getClubArray2(uuid, id, membreModel));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Uni<ClubArrayData> getClubArray2(String uuid, Long id, MembreModel membreModel) {
|
public Uni<ClubArrayData> getClubArray2(String uuid, Long id, MembreModel membreModel) {
|
||||||
@ -620,21 +651,35 @@ public class ResultService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Uni<RegisterModel> hasAccess(String uuid, SecurityCtx securityCtx) {
|
private Uni<MembreModel> hasAccess(String uuid, SecurityCtx securityCtx) {
|
||||||
return registerRepository.find("membre.userId = ?1 AND competition.uuid = ?2", securityCtx.getSubject(), uuid)
|
return registerRepository.find("membre.userId = ?1 AND competition.uuid = ?2", securityCtx.getSubject(), uuid)
|
||||||
.firstResult()
|
.firstResult()
|
||||||
.invoke(Unchecked.consumer(o -> {
|
.chain(Unchecked.function(o -> {
|
||||||
if (o == null)
|
if (o != null)
|
||||||
throw new DForbiddenException("Access denied");
|
return Uni.createFrom().item(o.getMembre());
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
private Uni<RegisterModel> hasAccess(Long compId, SecurityCtx securityCtx) {
|
return membreService.getByAccountId(securityCtx.getSubject()).chain(m -> {
|
||||||
return registerRepository.find("membre.userId = ?1 AND competition.id = ?2", securityCtx.getSubject(), compId)
|
if (securityCtx.isClubAdmin()) {
|
||||||
.firstResult()
|
return registerRepository.count("membre.club = ?2 AND competition.uuid = ?1",
|
||||||
.invoke(Unchecked.consumer(o -> {
|
uuid, m.getClub()).chain(c -> {
|
||||||
if (o == null)
|
if (c > 0) return Uni.createFrom().item(m);
|
||||||
throw new DForbiddenException("Access denied");
|
|
||||||
|
return compRepository.count("uuid = ?1 AND (owner = ?2 OR ?2 IN admin)",
|
||||||
|
uuid, securityCtx.getSubject())
|
||||||
|
.chain(c2 -> {
|
||||||
|
if (c2 > 0) return Uni.createFrom().item(m);
|
||||||
|
return Uni.createFrom().failure(new DForbiddenException("Access denied"));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return compRepository.count("uuid = ?1 AND (owner = ?2 OR ?2 IN admin)", uuid,
|
||||||
|
securityCtx.getSubject())
|
||||||
|
.chain(c2 -> {
|
||||||
|
if (c2 > 0) return Uni.createFrom().item(m);
|
||||||
|
return Uni.createFrom().failure(new DForbiddenException("Access denied"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,64 @@
|
|||||||
|
package fr.titionfire.ffsaf.domain.service;
|
||||||
|
|
||||||
|
import fr.titionfire.ffsaf.data.model.LogModel;
|
||||||
|
import fr.titionfire.ffsaf.data.model.MembreModel;
|
||||||
|
import fr.titionfire.ffsaf.data.model.SelectionModel;
|
||||||
|
import fr.titionfire.ffsaf.data.repository.CombRepository;
|
||||||
|
import fr.titionfire.ffsaf.data.repository.SelectionRepository;
|
||||||
|
import fr.titionfire.ffsaf.rest.data.SimpleSelection;
|
||||||
|
import io.quarkus.hibernate.reactive.panache.Panache;
|
||||||
|
import io.quarkus.hibernate.reactive.panache.common.WithSession;
|
||||||
|
import io.smallrye.mutiny.Uni;
|
||||||
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
|
import jakarta.inject.Inject;
|
||||||
|
import org.hibernate.reactive.mutiny.Mutiny;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
@WithSession
|
||||||
|
@ApplicationScoped
|
||||||
|
public class SelectionService {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
CombRepository combRepository;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
SelectionRepository repository;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
LoggerService ls;
|
||||||
|
|
||||||
|
public Uni<List<SelectionModel>> getSelection(long id, Consumer<MembreModel> checkPerm) {
|
||||||
|
return combRepository.findById(id).invoke(checkPerm)
|
||||||
|
.chain(combRepository -> Mutiny.fetch(combRepository.getSelections()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Uni<SelectionModel> setSelection(long id, SimpleSelection data) {
|
||||||
|
if (data.getId() == -1) {
|
||||||
|
return combRepository.findById(id).chain(membreModel -> {
|
||||||
|
SelectionModel model = new SelectionModel();
|
||||||
|
|
||||||
|
model.setMembre(membreModel);
|
||||||
|
model.setSaison(data.getSaison());
|
||||||
|
model.setCategorie(data.getCategorie());
|
||||||
|
return Panache.withTransaction(() -> repository.persist(model))
|
||||||
|
.call(licenceModel -> ls.logA(LogModel.ActionType.ADD, membreModel.getObjectName(),
|
||||||
|
licenceModel));
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return repository.findById(data.getId()).chain(model -> {
|
||||||
|
ls.logChange("Catégorie", model.getCategorie(), data.getCategorie(), model);
|
||||||
|
model.setCategorie(data.getCategorie());
|
||||||
|
return Panache.withTransaction(() -> repository.persist(model))
|
||||||
|
.call(__ -> ls.append());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Uni<Void> deleteSelection(long id) {
|
||||||
|
return repository.findById(id)
|
||||||
|
.call(model -> ls.logADelete(model))
|
||||||
|
.chain(model -> Panache.withTransaction(() -> repository.delete(model)));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,7 +2,6 @@ package fr.titionfire.ffsaf.rest;
|
|||||||
|
|
||||||
import fr.titionfire.ffsaf.domain.service.ResultService;
|
import fr.titionfire.ffsaf.domain.service.ResultService;
|
||||||
import fr.titionfire.ffsaf.domain.service.UpdateService;
|
import fr.titionfire.ffsaf.domain.service.UpdateService;
|
||||||
import fr.titionfire.ffsaf.utils.ResultPrivacy;
|
|
||||||
import io.smallrye.mutiny.Uni;
|
import io.smallrye.mutiny.Uni;
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
import jakarta.ws.rs.*;
|
import jakarta.ws.rs.*;
|
||||||
@ -47,7 +46,7 @@ public class ExternalResultEndpoints {
|
|||||||
@Path("/comb/list")
|
@Path("/comb/list")
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
public Uni<HashMap<String, String>> combList() {
|
public Uni<HashMap<String, String>> combList() {
|
||||||
return resultService.getCombList(id, ResultPrivacy.PUBLIC);
|
return resultService.getCombList(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@ -56,7 +55,7 @@ public class ExternalResultEndpoints {
|
|||||||
public Uni<?> getArray(@QueryParam("comb") String comb) {
|
public Uni<?> getArray(@QueryParam("comb") String comb) {
|
||||||
if (comb.equals("0"))
|
if (comb.equals("0"))
|
||||||
return Uni.createFrom().item("");
|
return Uni.createFrom().item("");
|
||||||
return resultService.getCombArrayPublic(id, comb, ResultPrivacy.PUBLIC);
|
return resultService.getCombArrayPublic(id, comb);
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
|
|||||||
@ -101,7 +101,7 @@ public class LicenceEndpoints {
|
|||||||
@RolesAllowed("federation_admin")
|
@RolesAllowed("federation_admin")
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
@Consumes(MediaType.MULTIPART_FORM_DATA)
|
@Consumes(MediaType.MULTIPART_FORM_DATA)
|
||||||
@Operation(summary = "Créer une licence", description = "Créer unr licence en fonction de son identifiant et des " +
|
@Operation(summary = "Créer une licence", description = "Créer une licence en fonction de son identifiant et des " +
|
||||||
"informations fournies dans le formulaire (pour les administrateurs)")
|
"informations fournies dans le formulaire (pour les administrateurs)")
|
||||||
@APIResponses(value = {
|
@APIResponses(value = {
|
||||||
@APIResponse(responseCode = "200", description = "La licence a été mise à jour avec succès"),
|
@APIResponse(responseCode = "200", description = "La licence a été mise à jour avec succès"),
|
||||||
|
|||||||
@ -2,7 +2,6 @@ package fr.titionfire.ffsaf.rest;
|
|||||||
|
|
||||||
import fr.titionfire.ffsaf.domain.service.ResultService;
|
import fr.titionfire.ffsaf.domain.service.ResultService;
|
||||||
import fr.titionfire.ffsaf.rest.data.ResultCategoryData;
|
import fr.titionfire.ffsaf.rest.data.ResultCategoryData;
|
||||||
import fr.titionfire.ffsaf.utils.ResultPrivacy;
|
|
||||||
import fr.titionfire.ffsaf.utils.SecurityCtx;
|
import fr.titionfire.ffsaf.utils.SecurityCtx;
|
||||||
import io.quarkus.security.Authenticated;
|
import io.quarkus.security.Authenticated;
|
||||||
import io.smallrye.mutiny.Uni;
|
import io.smallrye.mutiny.Uni;
|
||||||
@ -33,7 +32,7 @@ public class ResultEndpoints {
|
|||||||
@GET
|
@GET
|
||||||
@Path("{uuid}/category/list")
|
@Path("{uuid}/category/list")
|
||||||
public Uni<HashMap<String, Long>> getCategoryList(@PathParam("uuid") String uuid) {
|
public Uni<HashMap<String, Long>> getCategoryList(@PathParam("uuid") String uuid) {
|
||||||
return resultService.getCategoryList(uuid);
|
return resultService.getCategoryList(uuid, securityCtx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@ -45,7 +44,7 @@ public class ResultEndpoints {
|
|||||||
@GET
|
@GET
|
||||||
@Path("{uuid}/club/list")
|
@Path("{uuid}/club/list")
|
||||||
public Uni<HashMap<String, Long>> getClubList(@PathParam("uuid") String uuid) {
|
public Uni<HashMap<String, Long>> getClubList(@PathParam("uuid") String uuid) {
|
||||||
return resultService.getClubList(uuid);
|
return resultService.getClubList(uuid, securityCtx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@ -57,13 +56,13 @@ public class ResultEndpoints {
|
|||||||
@GET
|
@GET
|
||||||
@Path("{uuid}/comb/list")
|
@Path("{uuid}/comb/list")
|
||||||
public Uni<HashMap<String, String>> getCombList(@PathParam("uuid") String uuid) {
|
public Uni<HashMap<String, String>> getCombList(@PathParam("uuid") String uuid) {
|
||||||
return resultService.getCombList(uuid, ResultPrivacy.REGISTERED_ONLY);
|
return resultService.getCombList(uuid, securityCtx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("{uuid}/comb/{id}")
|
@Path("{uuid}/comb/{id}")
|
||||||
public Uni<?> getCombList(@PathParam("uuid") String uuid, @PathParam("id") String id) {
|
public Uni<?> getCombList(@PathParam("uuid") String uuid, @PathParam("id") String id) {
|
||||||
return resultService.getCombArrayPublic(uuid, id, ResultPrivacy.REGISTERED_ONLY);
|
return resultService.getCombArrayPublic(uuid, id, securityCtx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
|
|||||||
@ -0,0 +1,85 @@
|
|||||||
|
package fr.titionfire.ffsaf.rest;
|
||||||
|
|
||||||
|
import fr.titionfire.ffsaf.data.model.MembreModel;
|
||||||
|
import fr.titionfire.ffsaf.domain.service.SelectionService;
|
||||||
|
import fr.titionfire.ffsaf.rest.data.SimpleSelection;
|
||||||
|
import fr.titionfire.ffsaf.rest.exception.DForbiddenException;
|
||||||
|
import fr.titionfire.ffsaf.utils.SecurityCtx;
|
||||||
|
import io.smallrye.mutiny.Uni;
|
||||||
|
import io.smallrye.mutiny.unchecked.Unchecked;
|
||||||
|
import jakarta.annotation.security.RolesAllowed;
|
||||||
|
import jakarta.inject.Inject;
|
||||||
|
import jakarta.ws.rs.*;
|
||||||
|
import jakarta.ws.rs.core.MediaType;
|
||||||
|
import org.eclipse.microprofile.openapi.annotations.Operation;
|
||||||
|
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
|
||||||
|
import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
@Path("api/selection")
|
||||||
|
public class SelectionEndpoints {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
SelectionService selectionService;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
SecurityCtx securityCtx;
|
||||||
|
|
||||||
|
Consumer<MembreModel> checkPerm = Unchecked.consumer(membreModel -> {
|
||||||
|
if (!securityCtx.roleHas("federation_admin") && !securityCtx.isInClubGroup(membreModel.getClub().getId()))
|
||||||
|
throw new DForbiddenException();
|
||||||
|
});
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("{id}")
|
||||||
|
@RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_tresorier", "club_respo_intra",
|
||||||
|
"ffsaf_selectionneur"})
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
@Operation(summary = "Renvoie les séléctions d'un membre", description = "Renvoie les séléctions d'un membre en fonction " +
|
||||||
|
"de son identifiant")
|
||||||
|
@APIResponses(value = {
|
||||||
|
@APIResponse(responseCode = "200", description = "La liste des séléctions du membre"),
|
||||||
|
@APIResponse(responseCode = "403", description = "Accès refusé"),
|
||||||
|
@APIResponse(responseCode = "404", description = "Le membre n'existe pas"),
|
||||||
|
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
|
||||||
|
})
|
||||||
|
public Uni<List<SimpleSelection>> getSelection(@PathParam("id") long id) {
|
||||||
|
return selectionService.getSelection(id, checkPerm)
|
||||||
|
.map(selectionModels -> selectionModels.stream().map(SimpleSelection::fromModel).toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("{id}")
|
||||||
|
@RolesAllowed({"federation_admin", "ffsaf_selectionneur"})
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
|
@Operation(summary = "Créer une séléction", description = "Créer une séléction en fonction de son identifiant et des " +
|
||||||
|
"informations fournies dans le formulaire (pour les administrateurs)")
|
||||||
|
@APIResponses(value = {
|
||||||
|
@APIResponse(responseCode = "200", description = "La séléction a été mise à jour avec succès"),
|
||||||
|
@APIResponse(responseCode = "403", description = "Accès refusé"),
|
||||||
|
@APIResponse(responseCode = "404", description = "La séléction n'existe pas"),
|
||||||
|
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
|
||||||
|
})
|
||||||
|
public Uni<SimpleSelection> setSelection(@PathParam("id") long id, SimpleSelection data) {
|
||||||
|
return selectionService.setSelection(id, data).map(SimpleSelection::fromModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DELETE
|
||||||
|
@Path("{id}")
|
||||||
|
@RolesAllowed({"federation_admin", "ffsaf_selectionneur"})
|
||||||
|
@Produces(MediaType.TEXT_PLAIN)
|
||||||
|
@Operation(summary = "Supprime une séléction", description = "Supprime une séléction en fonction de son identifiant " +
|
||||||
|
"(pour les administrateurs)")
|
||||||
|
@APIResponses(value = {
|
||||||
|
@APIResponse(responseCode = "204", description = "La séléction a été supprimée avec succès"),
|
||||||
|
@APIResponse(responseCode = "403", description = "Accès refusé"),
|
||||||
|
@APIResponse(responseCode = "404", description = "La séléction n'existe pas"),
|
||||||
|
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
|
||||||
|
})
|
||||||
|
public Uni<?> deleteSelection(@PathParam("id") long id) {
|
||||||
|
return selectionService.deleteSelection(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -44,6 +44,8 @@ public class MeData {
|
|||||||
private ResultPrivacy resultPrivacy;
|
private ResultPrivacy resultPrivacy;
|
||||||
@Schema(description = "La liste des licences du membre.")
|
@Schema(description = "La liste des licences du membre.")
|
||||||
private List<SimpleLicence> licences;
|
private List<SimpleLicence> licences;
|
||||||
|
@Schema(description = "La liste des séléctions du membre.")
|
||||||
|
private List<SimpleSelection> selections;
|
||||||
|
|
||||||
public void setMembre(MembreModel membreModel) {
|
public void setMembre(MembreModel membreModel) {
|
||||||
this.id = membreModel.getId();
|
this.id = membreModel.getId();
|
||||||
|
|||||||
@ -0,0 +1,36 @@
|
|||||||
|
package fr.titionfire.ffsaf.rest.data;
|
||||||
|
|
||||||
|
import fr.titionfire.ffsaf.data.model.SelectionModel;
|
||||||
|
import fr.titionfire.ffsaf.utils.Categorie;
|
||||||
|
import io.quarkus.runtime.annotations.RegisterForReflection;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import org.eclipse.microprofile.openapi.annotations.media.Schema;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@AllArgsConstructor
|
||||||
|
@RegisterForReflection
|
||||||
|
public class SimpleSelection {
|
||||||
|
@Schema(description = "ID de la séléction", examples = "1")
|
||||||
|
Long id;
|
||||||
|
@Schema(description = "ID du membre", examples = "1")
|
||||||
|
Long membre;
|
||||||
|
@Schema(description = "Saison de la séléction", examples = "2024")
|
||||||
|
int saison;
|
||||||
|
@Schema(description = "Catégorie de la séléction", examples = "JUNIOR")
|
||||||
|
Categorie categorie;
|
||||||
|
|
||||||
|
public static SimpleSelection fromModel(SelectionModel model) {
|
||||||
|
if (model == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return new SimpleSelection.SimpleSelectionBuilder()
|
||||||
|
.id(model.getId())
|
||||||
|
.membre(model.getMembre().getId())
|
||||||
|
.saison(model.getSaison())
|
||||||
|
.categorie(model.getCategorie())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4,6 +4,7 @@ import com.fasterxml.jackson.core.JsonProcessingException;
|
|||||||
import fr.titionfire.ffsaf.data.repository.CompetitionRepository;
|
import fr.titionfire.ffsaf.data.repository.CompetitionRepository;
|
||||||
import fr.titionfire.ffsaf.domain.service.CompetPermService;
|
import fr.titionfire.ffsaf.domain.service.CompetPermService;
|
||||||
import fr.titionfire.ffsaf.net2.MessageType;
|
import fr.titionfire.ffsaf.net2.MessageType;
|
||||||
|
import fr.titionfire.ffsaf.rest.data.SimpleCompetData;
|
||||||
import fr.titionfire.ffsaf.utils.SecurityCtx;
|
import fr.titionfire.ffsaf.utils.SecurityCtx;
|
||||||
import fr.titionfire.ffsaf.ws.data.WelcomeInfo;
|
import fr.titionfire.ffsaf.ws.data.WelcomeInfo;
|
||||||
import fr.titionfire.ffsaf.ws.recv.*;
|
import fr.titionfire.ffsaf.ws.recv.*;
|
||||||
@ -118,10 +119,13 @@ public class CompetitionWS {
|
|||||||
waitingResponse.put(connection, new HashMap<>());
|
waitingResponse.put(connection, new HashMap<>());
|
||||||
})
|
})
|
||||||
.map(cm -> {
|
.map(cm -> {
|
||||||
|
SimpleCompetData data = SimpleCompetData.fromModel(cm);
|
||||||
WelcomeInfo welcomeInfo = new WelcomeInfo();
|
WelcomeInfo welcomeInfo = new WelcomeInfo();
|
||||||
|
|
||||||
welcomeInfo.setName(cm.getName());
|
welcomeInfo.setName(cm.getName());
|
||||||
welcomeInfo.setPerm(connection.userData().get(UserData.TypedKey.forString("prem")));
|
welcomeInfo.setPerm(connection.userData().get(UserData.TypedKey.forString("prem")));
|
||||||
|
welcomeInfo.setShow_blason(data.isShow_blason());
|
||||||
|
welcomeInfo.setShow_flag(data.isShow_flag());
|
||||||
|
|
||||||
return new MessageOut(UUID.randomUUID(), "welcomeInfo", MessageType.NOTIFY, welcomeInfo);
|
return new MessageOut(UUID.randomUUID(), "welcomeInfo", MessageType.NOTIFY, welcomeInfo);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -8,4 +8,6 @@ import lombok.Data;
|
|||||||
public class WelcomeInfo {
|
public class WelcomeInfo {
|
||||||
private String name;
|
private String name;
|
||||||
private String perm;
|
private String perm;
|
||||||
|
private boolean show_blason;
|
||||||
|
private boolean show_flag;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,7 +25,7 @@ function reducer(state, action) {
|
|||||||
country: action.payload.data.country,
|
country: action.payload.data.country,
|
||||||
})
|
})
|
||||||
if (state[comb.id] === undefined || !compareCombs(comb, state[comb.id])) {
|
if (state[comb.id] === undefined || !compareCombs(comb, state[comb.id])) {
|
||||||
console.debug("Updating comb", comb);
|
//console.debug("Updating comb", comb);
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
[comb.id]: comb
|
[comb.id]: comb
|
||||||
@ -49,7 +49,7 @@ function reducer(state, action) {
|
|||||||
for (const o of combs) {
|
for (const o of combs) {
|
||||||
newCombs[o.id] = o;
|
newCombs[o.id] = o;
|
||||||
}
|
}
|
||||||
console.debug("Updating combs", newCombs);
|
//console.debug("Updating combs", newCombs);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
|||||||
@ -44,6 +44,7 @@ export function WSProvider({url, onmessage, children}) {
|
|||||||
const {is_authenticated} = useAuth()
|
const {is_authenticated} = useAuth()
|
||||||
const [isReady, setIsReady] = useState(false)
|
const [isReady, setIsReady] = useState(false)
|
||||||
const [doReconnect, setDoReconnect] = useState(false)
|
const [doReconnect, setDoReconnect] = useState(false)
|
||||||
|
const [welcomeData, setWelcomeData] = useState({name: "", perm: "", show_blason: true, show_flag: false})
|
||||||
const [state, dispatch] = useReducer(reducer, {listener: []})
|
const [state, dispatch] = useReducer(reducer, {listener: []})
|
||||||
const ws = useRef(null)
|
const ws = useRef(null)
|
||||||
const listenersRef = useRef([])
|
const listenersRef = useRef([])
|
||||||
@ -58,6 +59,15 @@ export function WSProvider({url, onmessage, children}) {
|
|||||||
listenersRef.current = state.listener
|
listenersRef.current = state.listener
|
||||||
}, [state.listener])
|
}, [state.listener])
|
||||||
|
|
||||||
|
const welcomeListener = ({data}) => {
|
||||||
|
setWelcomeData({
|
||||||
|
name: data.name,
|
||||||
|
perm: data.perm,
|
||||||
|
show_blason: data.show_blason,
|
||||||
|
show_flag: data.show_flag
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!doReconnect && !is_authenticated && isReady)
|
if (!doReconnect && !is_authenticated && isReady)
|
||||||
return;
|
return;
|
||||||
@ -73,7 +83,7 @@ export function WSProvider({url, onmessage, children}) {
|
|||||||
newSocket.onclose = ws.current.onclose
|
newSocket.onclose = ws.current.onclose
|
||||||
newSocket.onmessage = ws.current.onmessage
|
newSocket.onmessage = ws.current.onmessage
|
||||||
ws.current = newSocket
|
ws.current = newSocket
|
||||||
}catch (e) {
|
} catch (e) {
|
||||||
|
|
||||||
}
|
}
|
||||||
}, 5000);
|
}, 5000);
|
||||||
@ -87,7 +97,7 @@ export function WSProvider({url, onmessage, children}) {
|
|||||||
setDoReconnect(true)
|
setDoReconnect(true)
|
||||||
console.log(`WSProvider ${id} mounted ${mountCounter[id]} time(s)`);
|
console.log(`WSProvider ${id} mounted ${mountCounter[id]} time(s)`);
|
||||||
|
|
||||||
if (mountCounter[id] === 1 && (ws.current === null || ws.current.readyState >= WebSocket.CLOSING)){
|
if (mountCounter[id] === 1 && (ws.current === null || ws.current.readyState >= WebSocket.CLOSING)) {
|
||||||
console.log("WSProvider: connecting to", url);
|
console.log("WSProvider: connecting to", url);
|
||||||
const socket = new WebSocket(url)
|
const socket = new WebSocket(url)
|
||||||
|
|
||||||
@ -122,6 +132,12 @@ export function WSProvider({url, onmessage, children}) {
|
|||||||
console.error("Listener callback error:", err)
|
console.error("Listener callback error:", err)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (msg.code === 'welcomeInfo') {
|
||||||
|
welcomeListener({...msg})
|
||||||
|
isHandled = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (!isHandled && onmessage)
|
if (!isHandled && onmessage)
|
||||||
onmessage(JSON.parse(event.data))
|
onmessage(JSON.parse(event.data))
|
||||||
}
|
}
|
||||||
@ -170,7 +186,7 @@ export function WSProvider({url, onmessage, children}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("WSProvider: sending message", {uuid, code, type, data});
|
//console.log("WSProvider: sending message", {uuid, code, type, data});
|
||||||
ws.current?.send(JSON.stringify({
|
ws.current?.send(JSON.stringify({
|
||||||
uuid: uuid,
|
uuid: uuid,
|
||||||
code: code,
|
code: code,
|
||||||
@ -183,7 +199,7 @@ export function WSProvider({url, onmessage, children}) {
|
|||||||
}) => {
|
}) => {
|
||||||
if (isReadyRef.current) {
|
if (isReadyRef.current) {
|
||||||
send2(uuid, code, type, data, resolve, reject);
|
send2(uuid, code, type, data, resolve, reject);
|
||||||
}else {
|
} else {
|
||||||
let counter = 0;
|
let counter = 0;
|
||||||
const waitInterval = setInterval(() => {
|
const waitInterval = setInterval(() => {
|
||||||
if (isReadyRef.current) {
|
if (isReadyRef.current) {
|
||||||
@ -200,18 +216,19 @@ export function WSProvider({url, onmessage, children}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const ret = {isReady, dispatch, send, wait_length: callbackRef}
|
const ret = {isReady, dispatch, send, wait_length: callbackRef, welcomeData}
|
||||||
return <WebsocketContext.Provider value={ret}>
|
return <WebsocketContext.Provider value={ret}>
|
||||||
{children}
|
{children}
|
||||||
</WebsocketContext.Provider>
|
</WebsocketContext.Provider>
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useWS() {
|
export function useWS() {
|
||||||
const {isReady, dispatch, send, wait_length} = useContext(WebsocketContext)
|
const {isReady, dispatch, send, wait_length, welcomeData} = useContext(WebsocketContext)
|
||||||
return {
|
return {
|
||||||
dispatch,
|
dispatch,
|
||||||
isReady,
|
isReady,
|
||||||
wait_length,
|
wait_length,
|
||||||
|
welcomeData,
|
||||||
sendRequest: (code, data) => {
|
sendRequest: (code, data) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
send(uuidv4(), code, "REQUEST", data, resolve, reject);
|
send(uuidv4(), code, "REQUEST", data, resolve, reject);
|
||||||
@ -255,7 +272,7 @@ export function useRequestWS(code, payload, setLoading = null, loadingLevel = 1)
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isReady)
|
if (isReady)
|
||||||
refresh(code, payload)
|
refresh(code, payload)
|
||||||
else{
|
else {
|
||||||
if (setLoading)
|
if (setLoading)
|
||||||
setLoading(loadingLevel)
|
setLoading(loadingLevel)
|
||||||
setTimeout(() => refresh(code, payload), 1000)
|
setTimeout(() => refresh(code, payload), 1000)
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import {
|
|||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import {CheckField} from "../components/MemberCustomFiels.jsx";
|
import {CheckField} from "../components/MemberCustomFiels.jsx";
|
||||||
import {toast} from "react-toastify";
|
import {toast} from "react-toastify";
|
||||||
import {apiAxios} from "../utils/Tools.js";
|
import {apiAxios, getCatName} from "../utils/Tools.js";
|
||||||
import {useEffect, useState} from "react";
|
import {useEffect, useState} from "react";
|
||||||
|
|
||||||
const vite_url = import.meta.env.VITE_URL;
|
const vite_url = import.meta.env.VITE_URL;
|
||||||
@ -40,7 +40,7 @@ export function MePage() {
|
|||||||
<LoadingProvider><LicenceCard userData={data}/></LoadingProvider>
|
<LoadingProvider><LicenceCard userData={data}/></LoadingProvider>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-md-6">
|
<div className="col-md-6">
|
||||||
<LoadingProvider><SelectCard/></LoadingProvider>
|
<LoadingProvider><SelectCard userData={data}/></LoadingProvider>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -93,10 +93,17 @@ function PhotoCard({data}) {
|
|||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function SelectCard() {
|
function SelectCard({userData}) {
|
||||||
return <div className="card mb-4 mb-md-0">
|
return <div className="card mb-4 mb-md-0">
|
||||||
<div className="card-header">Sélection en équipe de France</div>
|
<div className="card-header">Sélection en équipe de France</div>
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
|
<ul className="list-group">
|
||||||
|
{userData?.selections && userData.selections.sort((a, b) => b.saison - a.saison).map((selection, index) => {
|
||||||
|
return <div key={index} className="list-group-item d-flex justify-content-between align-items-start">
|
||||||
|
<div className="me-auto">{selection?.saison}-{selection?.saison + 1} en {getCatName(selection?.categorie)}</div>
|
||||||
|
</div>
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -100,7 +100,7 @@ function InformationForm({data}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit} autoComplete="off">
|
||||||
<div className="card mb-4">
|
<div className="card mb-4">
|
||||||
<input name="id" value={data.id} readOnly hidden/>
|
<input name="id" value={data.id} readOnly hidden/>
|
||||||
<input name="clubId" value={data.clubId} readOnly hidden/>
|
<input name="clubId" value={data.clubId} readOnly hidden/>
|
||||||
|
|||||||
@ -60,7 +60,7 @@ function InformationForm() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit} autoComplete="off">
|
||||||
<div className="card mb-4">
|
<div className="card mb-4">
|
||||||
<div className="card-header">Nouveau club</div>
|
<div className="card-header">Nouveau club</div>
|
||||||
<div className="card-body text-center">
|
<div className="card-body text-center">
|
||||||
|
|||||||
@ -79,7 +79,7 @@ export function InformationForm({data}) {
|
|||||||
addPhoto(event, formData, send);
|
addPhoto(event, formData, send);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <form onSubmit={handleSubmit}>
|
return <form onSubmit={handleSubmit} autoComplete="off">
|
||||||
<div className="card mb-4">
|
<div className="card mb-4">
|
||||||
<div className="card-header">Information</div>
|
<div className="card-header">Information</div>
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import {apiAxios, errFormater} from "../../../utils/Tools.js";
|
|||||||
import {ConfirmDialog} from "../../../components/ConfirmDialog.jsx";
|
import {ConfirmDialog} from "../../../components/ConfirmDialog.jsx";
|
||||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||||
import {faFilePdf} from "@fortawesome/free-solid-svg-icons";
|
import {faFilePdf} from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import {SelectCard} from "./SelectCard.jsx";
|
||||||
|
|
||||||
const vite_url = import.meta.env.VITE_URL;
|
const vite_url = import.meta.env.VITE_URL;
|
||||||
|
|
||||||
@ -58,7 +59,7 @@ export function MemberPage() {
|
|||||||
<LoadingProvider><LicenceCard userData={data}/></LoadingProvider>
|
<LoadingProvider><LicenceCard userData={data}/></LoadingProvider>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-md-6">
|
<div className="col-md-6">
|
||||||
<LoadingProvider><SelectCard/></LoadingProvider>
|
<LoadingProvider><SelectCard userData={data}/></LoadingProvider>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="col" style={{textAlign: 'right', marginTop: '1em'}}>
|
<div className="col" style={{textAlign: 'right', marginTop: '1em'}}>
|
||||||
@ -94,14 +95,3 @@ function PhotoCard({data}) {
|
|||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function SelectCard() {
|
|
||||||
return <></>
|
|
||||||
/*return <div className="card mb-4 mb-md-0">
|
|
||||||
<div className="card-header">Sélection en équipe de France</div>
|
|
||||||
<div className="card-body">
|
|
||||||
<p className="mb-1">Web Design</p>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>;*/
|
|
||||||
}
|
|
||||||
|
|||||||
208
src/main/webapp/src/pages/admin/member/SelectCard.jsx
Normal file
208
src/main/webapp/src/pages/admin/member/SelectCard.jsx
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
import {useLoadingSwitcher} from "../../../hooks/useLoading.jsx";
|
||||||
|
import {useFetch} from "../../../hooks/useFetch.js";
|
||||||
|
import {useEffect, useReducer, useState} from "react";
|
||||||
|
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||||
|
import {faEuroSign, faPen} from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import {AxiosError} from "../../../components/AxiosError.jsx";
|
||||||
|
import {apiAxios, CatList, errFormater, getCatName, getSaison} from "../../../utils/Tools.js";
|
||||||
|
import {toast} from "react-toastify";
|
||||||
|
|
||||||
|
function selectionReducer(selections, action) {
|
||||||
|
switch (action.type) {
|
||||||
|
case 'ADD':
|
||||||
|
return [
|
||||||
|
...selections,
|
||||||
|
action.payload
|
||||||
|
]
|
||||||
|
case 'REMOVE':
|
||||||
|
return selections.filter(selection => selection.id !== action.payload)
|
||||||
|
case 'UPDATE_OR_ADD':
|
||||||
|
const index = selections.findIndex(selection => selection.id === action.payload.id)
|
||||||
|
if (index === -1) {
|
||||||
|
return [
|
||||||
|
...selections,
|
||||||
|
action.payload
|
||||||
|
]
|
||||||
|
} else {
|
||||||
|
selections[index] = action.payload
|
||||||
|
return [...selections]
|
||||||
|
}
|
||||||
|
case 'SORT':
|
||||||
|
return selections.sort((a, b) => b.saison - a.saison)
|
||||||
|
default:
|
||||||
|
throw new Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SelectCard({userData}) {
|
||||||
|
const setLoading = useLoadingSwitcher()
|
||||||
|
const {data, error} = useFetch(`/selection/${userData.id}`, setLoading, 1)
|
||||||
|
|
||||||
|
const [modalSelection, setModal] = useState({id: -1, membre: userData.id})
|
||||||
|
const [selections, dispatch] = useReducer(selectionReducer, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!data) return
|
||||||
|
for (const dataKey of data) {
|
||||||
|
dispatch({type: 'UPDATE_OR_ADD', payload: dataKey})
|
||||||
|
}
|
||||||
|
dispatch({type: 'SORT'})
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
|
return <div className="card mb-4 mb-md-0">
|
||||||
|
<div className="card-header container-fluid">
|
||||||
|
<div className="row">
|
||||||
|
<div className="col">Sélection en équipe de France</div>
|
||||||
|
<div className="col" style={{textAlign: 'right'}}>
|
||||||
|
<button className="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#SelectionModal"
|
||||||
|
onClick={() => setModal({id: -1, membre: userData.id, categorie: userData.categorie})}>Ajouter
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="card-body">
|
||||||
|
<ul className="list-group">
|
||||||
|
{selections.map((selection, index) => {
|
||||||
|
return <div key={index} className="list-group-item d-flex justify-content-between align-items-start">
|
||||||
|
<div className="me-auto">{selection?.saison}-{selection?.saison + 1} en {getCatName(selection?.categorie)}</div>
|
||||||
|
<button className="badge btn btn-primary rounded-pill" data-bs-toggle="modal"
|
||||||
|
data-bs-target="#SelectionModal" onClick={_ => setModal(selection)}>
|
||||||
|
<FontAwesomeIcon icon={faPen}/></button>
|
||||||
|
</div>
|
||||||
|
})}
|
||||||
|
{error && <AxiosError error={error}/>}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="modal fade" id="SelectionModal" tabIndex="-1" aria-labelledby="SelectionModalLabel"
|
||||||
|
aria-hidden="true">
|
||||||
|
<div className="modal-dialog">
|
||||||
|
<div className="modal-content">
|
||||||
|
<ModalContent selection={modalSelection} dispatch={dispatch}/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendSelection(event, dispatch) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const formData = new FormData(event.target);
|
||||||
|
formData.set('selection', event.target.selection?.value?.length > 0 ? event.target.selection?.value : null)
|
||||||
|
formData.set('categorie', event.target.categorie?.value)
|
||||||
|
|
||||||
|
toast.promise(
|
||||||
|
apiAxios.post(`/selection/${event.target.membre.value}`, {
|
||||||
|
id: event.target.id.value,
|
||||||
|
membre: event.target.membre.value,
|
||||||
|
saison: event.target.saison.value,
|
||||||
|
categorie: event.target.categorie.value,
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
pending: "Enregistrement de la séléction en cours",
|
||||||
|
success: "Séléction enregistrée avec succès 🎉",
|
||||||
|
error: {
|
||||||
|
render({data}) {
|
||||||
|
return errFormater(data, "Échec de l'enregistrement de la séléction")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
).then(data => {
|
||||||
|
dispatch({type: 'UPDATE_OR_ADD', payload: data.data})
|
||||||
|
dispatch({type: 'SORT'})
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeSelection(id, dispatch) {
|
||||||
|
toast.promise(
|
||||||
|
apiAxios.delete(`/selection/${id}`),
|
||||||
|
{
|
||||||
|
pending: "Suppression de la séléction en cours",
|
||||||
|
success: "Séléction supprimée avec succès 🎉",
|
||||||
|
error: {
|
||||||
|
render({data}) {
|
||||||
|
return errFormater(data, "Échec de la suppression de la séléction")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
).then(_ => {
|
||||||
|
dispatch({type: 'REMOVE', payload: id})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function ModalContent({selection, dispatch}) {
|
||||||
|
const [saison, setSaison] = useState(0)
|
||||||
|
const [cat, setCat] = useState("")
|
||||||
|
const [isNew, setNew] = useState(true)
|
||||||
|
|
||||||
|
const setSeason = (event) => {
|
||||||
|
setSaison(Number(event.target.value))
|
||||||
|
}
|
||||||
|
const handleCatChange = (event) => {
|
||||||
|
setCat(event.target.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (selection.id !== -1) {
|
||||||
|
setNew(false)
|
||||||
|
setSaison(selection.saison)
|
||||||
|
} else {
|
||||||
|
setNew(true)
|
||||||
|
setSaison(getSaison())
|
||||||
|
}
|
||||||
|
setCat(selection.categorie)
|
||||||
|
}, [selection]);
|
||||||
|
|
||||||
|
return <form onSubmit={e => sendSelection(e, dispatch)}>
|
||||||
|
<input name="id" value={selection.id} readOnly hidden/>
|
||||||
|
<input name="membre" value={selection.membre} readOnly hidden/>
|
||||||
|
<div className="modal-header">
|
||||||
|
<h1 className="modal-title fs-5" id="SelectionModalLabel">Edition de la séléction</h1>
|
||||||
|
<button type="button" className="btn-close" data-bs-dismiss="modal"
|
||||||
|
aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div className="modal-body">
|
||||||
|
<div className="input-group mb-3 justify-content-md-center">
|
||||||
|
{isNew
|
||||||
|
? <input type="number" className="form-control" placeholder="Saison" name="saison"
|
||||||
|
aria-label="Saison" aria-describedby="basic-addon2" value={saison} onChange={setSeason}/>
|
||||||
|
: <><span className="input-group-text" id="basic-addon2">{saison}</span>
|
||||||
|
<input name="saison" value={saison} readOnly hidden/></>}
|
||||||
|
<span className="input-group-text" id="basic-addon2">-</span>
|
||||||
|
<span className="input-group-text" id="basic-addon2">{saison + 1}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="input-group mb-3">
|
||||||
|
<label className="input-group-text" htmlFor="inputGroupSelect01">Catégorie</label>
|
||||||
|
<select className="form-select" id="inputGroupSelect01" value={cat} onChange={handleCatChange} name="categorie" required>
|
||||||
|
<option>Choisir...</option>
|
||||||
|
{CatList.map((cat) => {
|
||||||
|
return (<option key={cat} value={cat}>{getCatName(cat)}</option>)
|
||||||
|
})}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div className="modal-footer">
|
||||||
|
<button type="submit" className="btn btn-primary" data-bs-dismiss="modal">Enregistrer</button>
|
||||||
|
<button type="reset" className="btn btn-secondary" data-bs-dismiss="modal">Annuler</button>
|
||||||
|
{isNew || <button type="button" className="btn btn-danger" data-bs-dismiss="modal"
|
||||||
|
onClick={() => removeSelection(selection.id, dispatch)}>Supprimer</button>}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
}
|
||||||
|
|
||||||
|
function RadioGroupeOnOff({value, onChange, name, text}) {
|
||||||
|
return <div className="btn-group input-group mb-3 justify-content-md-center" role="group"
|
||||||
|
aria-label="Basic radio toggle button group">
|
||||||
|
<span className="input-group-text">{text}</span>
|
||||||
|
<input type="radio" className="btn-check" id={"btnradio1" + name} autoComplete="off"
|
||||||
|
value="false" checked={value === false} onChange={onChange}/>
|
||||||
|
<label className="btn btn-outline-primary" htmlFor={"btnradio1" + name}>Non</label>
|
||||||
|
<input type="radio" className="btn-check" name={name} id={"btnradio2" + name} autoComplete="off"
|
||||||
|
value="true" checked={value === true} onChange={onChange}/>
|
||||||
|
<label className="btn btn-outline-primary" htmlFor={"btnradio2" + name}>Oui</label>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
@ -67,7 +67,7 @@ function InformationForm({data}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit} autoComplete="off">
|
||||||
<div className="card mb-4">
|
<div className="card mb-4">
|
||||||
<input name="id" value={data.id} readOnly hidden/>
|
<input name="id" value={data.id} readOnly hidden/>
|
||||||
<div className="card-header">Affiliation n°{data.no_affiliation}</div>
|
<div className="card-header">Affiliation n°{data.no_affiliation}</div>
|
||||||
|
|||||||
@ -42,7 +42,7 @@ export function InformationForm({data}) {
|
|||||||
addPhoto(event, formData, send);
|
addPhoto(event, formData, send);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <form onSubmit={handleSubmit}>
|
return <form onSubmit={handleSubmit} autoComplete="off">
|
||||||
<div className="card mb-4">
|
<div className="card mb-4">
|
||||||
<div className="card-header">Information</div>
|
<div className="card-header">Information</div>
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import {apiAxios, errFormater} from "../../../utils/Tools.js";
|
|||||||
import {toast} from "react-toastify";
|
import {toast} from "react-toastify";
|
||||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||||
import {faFilePdf} from "@fortawesome/free-solid-svg-icons";
|
import {faFilePdf} from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import {SelectCard} from "./SelectCard.jsx";
|
||||||
|
|
||||||
const vite_url = import.meta.env.VITE_URL;
|
const vite_url = import.meta.env.VITE_URL;
|
||||||
|
|
||||||
@ -56,7 +57,7 @@ export function MemberPage() {
|
|||||||
<LoadingProvider><LicenceCard userData={data}/></LoadingProvider>
|
<LoadingProvider><LicenceCard userData={data}/></LoadingProvider>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-md-6">
|
<div className="col-md-6">
|
||||||
<LoadingProvider><SelectCard/></LoadingProvider>
|
<LoadingProvider><SelectCard userData={data}/></LoadingProvider>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{data.licence == null &&
|
{data.licence == null &&
|
||||||
@ -93,14 +94,3 @@ function PhotoCard({data}) {
|
|||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function SelectCard() {
|
|
||||||
return <></>
|
|
||||||
/*return <div className="card mb-4 mb-md-0">
|
|
||||||
<div className="card-header">Sélection en équipe de France</div>
|
|
||||||
<div className="card-body">
|
|
||||||
<p className="mb-1">Soon</p>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>;*/
|
|
||||||
}
|
|
||||||
|
|||||||
27
src/main/webapp/src/pages/club/member/SelectCard.jsx
Normal file
27
src/main/webapp/src/pages/club/member/SelectCard.jsx
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import {useLoadingSwitcher} from "../../../hooks/useLoading.jsx";
|
||||||
|
import {useFetch} from "../../../hooks/useFetch.js";
|
||||||
|
import {getCatName} from "../../../utils/Tools.js";
|
||||||
|
import {AxiosError} from "../../../components/AxiosError.jsx";
|
||||||
|
|
||||||
|
export function SelectCard({userData}) {
|
||||||
|
const setLoading = useLoadingSwitcher()
|
||||||
|
const {data, error} = useFetch(`/selection/${userData.id}`, setLoading, 1)
|
||||||
|
|
||||||
|
return <div className="card mb-4 mb-md-0">
|
||||||
|
<div className="card-header container-fluid">
|
||||||
|
<div className="row">
|
||||||
|
<div className="col">Sélection en équipe de France</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="card-body">
|
||||||
|
<ul className="list-group">
|
||||||
|
{data && data.sort((a, b) => b.saison - a.saison).map((selection, index) => {
|
||||||
|
return <div key={index} className="list-group-item d-flex justify-content-between align-items-start">
|
||||||
|
<div className="me-auto">{selection?.saison}-{selection?.saison + 1} en {getCatName(selection?.categorie)}</div>
|
||||||
|
</div>
|
||||||
|
})}
|
||||||
|
{error && <AxiosError error={error}/>}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import React, {useEffect, useRef, useState} from "react";
|
import React, {useEffect, useRef, useState} from "react";
|
||||||
import {useRequestWS} from "../../../hooks/useWS.jsx";
|
import {useRequestWS, useWS} from "../../../hooks/useWS.jsx";
|
||||||
import {useCombs, useCombsDispatch} from "../../../hooks/useComb.jsx";
|
import {useCombs, useCombsDispatch} from "../../../hooks/useComb.jsx";
|
||||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||||
import {createPortal} from "react-dom";
|
import {createPortal} from "react-dom";
|
||||||
@ -13,7 +13,6 @@ import {CategorieSelect} from "./CMTMatchPanel.jsx";
|
|||||||
import {PointPanel} from "./CMTPoint.jsx";
|
import {PointPanel} from "./CMTPoint.jsx";
|
||||||
import {importOBSConfiguration, OBSProvider, useOBS} from "../../../hooks/useOBS.jsx";
|
import {importOBSConfiguration, OBSProvider, useOBS} from "../../../hooks/useOBS.jsx";
|
||||||
import {SimpleIconsOBS} from "../../../assets/SimpleIconsOBS.ts";
|
import {SimpleIconsOBS} from "../../../assets/SimpleIconsOBS.ts";
|
||||||
import {timePrint} from "../../../utils/Tools.js";
|
|
||||||
import {toast} from "react-toastify";
|
import {toast} from "react-toastify";
|
||||||
|
|
||||||
export function CMTable() {
|
export function CMTable() {
|
||||||
@ -235,6 +234,7 @@ function ObsAutoSyncWhitPubAff() {
|
|||||||
const {connected, setText, setTextAndColor, setDiapo} = useOBS();
|
const {connected, setText, setTextAndColor, setDiapo} = useOBS();
|
||||||
const oldState = useRef({timeColor: "#000000", timeStr: "--:--", c1: null, c2: null, showScore: true, scoreRouge: 0, scoreBleu: 0});
|
const oldState = useRef({timeColor: "#000000", timeStr: "--:--", c1: null, c2: null, showScore: true, scoreRouge: 0, scoreBleu: 0});
|
||||||
const state = usePubAffState();
|
const state = usePubAffState();
|
||||||
|
const {welcomeData} = useWS();
|
||||||
const {getComb} = useCombs();
|
const {getComb} = useCombs();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -242,8 +242,8 @@ function ObsAutoSyncWhitPubAff() {
|
|||||||
const comb = getComb(state.c1);
|
const comb = getComb(state.c1);
|
||||||
setText("comb.rouge", comb ? (comb?.fname + " " + comb?.lname) : "");
|
setText("comb.rouge", comb ? (comb?.fname + " " + comb?.lname) : "");
|
||||||
const files = []
|
const files = []
|
||||||
if (comb?.club_uuid) files.push(`club_${comb.club_uuid}.png`)
|
if (comb?.club_uuid && welcomeData.show_blason) files.push(`club_${comb.club_uuid}.png`)
|
||||||
if (comb?.country) files.push(`flag_${comb.country.toLowerCase()}.png`)
|
if (comb?.country && welcomeData.show_flag) files.push(`flag_${comb.country.toLowerCase()}.png`)
|
||||||
setDiapo("img.rouge", files);
|
setDiapo("img.rouge", files);
|
||||||
oldState.current.c1 = state.c1;
|
oldState.current.c1 = state.c1;
|
||||||
}
|
}
|
||||||
@ -252,8 +252,8 @@ function ObsAutoSyncWhitPubAff() {
|
|||||||
const comb = getComb(state.c2);
|
const comb = getComb(state.c2);
|
||||||
setText("comb.blue", comb ? (comb?.fname + " " + comb?.lname) : "");
|
setText("comb.blue", comb ? (comb?.fname + " " + comb?.lname) : "");
|
||||||
const files = []
|
const files = []
|
||||||
if (comb?.club_uuid) files.push(`club_${comb.club_uuid}.png`)
|
if (comb?.club_uuid && welcomeData.show_blason) files.push(`club_${comb.club_uuid}.png`)
|
||||||
if (comb?.country) files.push(`flag_${comb.country.toLowerCase()}.png`)
|
if (comb?.country && welcomeData.show_flag) files.push(`flag_${comb.country.toLowerCase()}.png`)
|
||||||
setDiapo("img.blue", files);
|
setDiapo("img.blue", files);
|
||||||
oldState.current.c2 = state.c2;
|
oldState.current.c2 = state.c2;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -38,7 +38,13 @@ export function CategoryContent({cat, catId, setCat, menuActions}) {
|
|||||||
}, [groups]);
|
}, [groups]);
|
||||||
|
|
||||||
function readAndConvertMatch(matches, data, combsToAdd) {
|
function readAndConvertMatch(matches, data, combsToAdd) {
|
||||||
matches.push({...data, c1: data.c1?.id, c2: data.c2?.id})
|
matches.push({
|
||||||
|
...data,
|
||||||
|
c1: data.c1?.id,
|
||||||
|
c2: data.c2?.id,
|
||||||
|
c1_cacheName: data.c1?.fname + " " + data.c1?.lname,
|
||||||
|
c2_cacheName: data.c2?.fname + " " + data.c2?.lname
|
||||||
|
})
|
||||||
if (data.c1)
|
if (data.c1)
|
||||||
combsToAdd.push(data.c1)
|
combsToAdd.push(data.c1)
|
||||||
if (data.c2)
|
if (data.c2)
|
||||||
|
|||||||
@ -75,9 +75,8 @@ function HomeComp() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function WSStatus({setPerm}) {
|
function WSStatus({setPerm}) {
|
||||||
const [name, setName] = useState("")
|
|
||||||
const [inWait, setInWait] = useState(false)
|
const [inWait, setInWait] = useState(false)
|
||||||
const {isReady, wait_length, dispatch} = useWS();
|
const {isReady, wait_length, welcomeData} = useWS();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const timer = setInterval(() => {
|
const timer = setInterval(() => {
|
||||||
@ -87,16 +86,11 @@ function WSStatus({setPerm}) {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const welcomeListener = ({data}) => {
|
setPerm(welcomeData.perm)
|
||||||
setName(data.name)
|
}, [welcomeData])
|
||||||
setPerm(data.perm)
|
|
||||||
}
|
|
||||||
dispatch({type: 'addListener', payload: {callback: welcomeListener, code: 'welcomeInfo'}})
|
|
||||||
return () => dispatch({type: 'removeListener', payload: welcomeListener})
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return <div className="row" style={{marginRight: "inherit"}}>
|
return <div className="row" style={{marginRight: "inherit"}}>
|
||||||
<h2 className="col">{name}</h2>
|
<h2 className="col">{welcomeData.name}</h2>
|
||||||
<div className="col-auto" style={{margin: "auto 0", padding: 0}}>Serveur: <ColoredCircle
|
<div className="col-auto" style={{margin: "auto 0", padding: 0}}>Serveur: <ColoredCircle
|
||||||
color={isReady ? (inWait ? "#ffad32" : "#00c700") : "#e50000"}/>
|
color={isReady ? (inWait ? "#ffad32" : "#00c700") : "#e50000"}/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import {useCombs} from "../../../hooks/useComb.jsx";
|
|||||||
import {usePubAffState} from "../../../hooks/useExternalWindow.jsx";
|
import {usePubAffState} from "../../../hooks/useExternalWindow.jsx";
|
||||||
import {SmartLogoBackgroundMemo} from "../../../components/SmartLogoBackground.jsx";
|
import {SmartLogoBackgroundMemo} from "../../../components/SmartLogoBackground.jsx";
|
||||||
import {useMemo, useRef} from 'react';
|
import {useMemo, useRef} from 'react';
|
||||||
|
import {useWS} from "../../../hooks/useWS.jsx";
|
||||||
|
|
||||||
const vite_url = import.meta.env.VITE_URL;
|
const vite_url = import.meta.env.VITE_URL;
|
||||||
|
|
||||||
@ -124,6 +125,7 @@ const logoStyle = {width: "6vw", height: "min(11vh, 6vw)", objectFit: "contain",
|
|||||||
function CombDisplay({combId, background, children}) {
|
function CombDisplay({combId, background, children}) {
|
||||||
const {getComb} = useCombs();
|
const {getComb} = useCombs();
|
||||||
const comb = getComb(combId, "");
|
const comb = getComb(combId, "");
|
||||||
|
const {welcomeData} = useWS();
|
||||||
|
|
||||||
const logoAlt = useMemo(() => {
|
const logoAlt = useMemo(() => {
|
||||||
return comb?.club_str
|
return comb?.club_str
|
||||||
@ -142,10 +144,11 @@ function CombDisplay({combId, background, children}) {
|
|||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
}}>
|
}}>
|
||||||
{comb !== "" && <>
|
{comb !== "" && <>
|
||||||
<SmartLogoBackgroundMemo src={logoSrc} alt={logoAlt} style={logoStyle}/>
|
{welcomeData.show_blason && <SmartLogoBackgroundMemo src={logoSrc} alt={logoAlt} style={logoStyle}/>}
|
||||||
<div style={{fontSize: "min(3.5vw, 6.5vh)"}}>{comb.fname} {comb.lname}</div>
|
<div style={{fontSize: "min(3.5vw, 6.5vh)"}}>{comb.fname} {comb.lname}</div>
|
||||||
<img src={`/flags/svg/${comb.country.toLowerCase()}.svg`} alt={comb.country}
|
{welcomeData.show_flag ? <img src={`/flags/svg/${comb.country.toLowerCase()}.svg`} alt={comb.country}
|
||||||
style={{width: "4vw", height: "8vh", objectFit: "contain", margin: "0 1.25vw"}}/>
|
style={{width: "4vw", height: "8vh", objectFit: "contain", margin: "0 1.25vw"}}/>
|
||||||
|
: <div style={{width: "4vw", height: "8vh", objectFit: "contain", margin: "0 1.25vw"}}></div>}
|
||||||
</>}
|
</>}
|
||||||
<div className="position-absolute top-0 start-0 w-100" style={{...noMP, height: "0.4vh", backgroundColor: "#646464AA"}}/>
|
<div className="position-absolute top-0 start-0 w-100" style={{...noMP, height: "0.4vh", backgroundColor: "#646464AA"}}/>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@ -243,7 +243,7 @@ export function SelectCombModalContent({data, setGroups}) {
|
|||||||
<div style={{textAlign: "center"}}>Inscrit</div>
|
<div style={{textAlign: "center"}}>Inscrit</div>
|
||||||
<div className="list-group overflow-y-auto" style={{maxHeight: "50vh"}}>
|
<div className="list-group overflow-y-auto" style={{maxHeight: "50vh"}}>
|
||||||
{dispoFiltered && Object.keys(dispoFiltered).length === 0 && <div>Aucun combattant disponible</div>}
|
{dispoFiltered && Object.keys(dispoFiltered).length === 0 && <div>Aucun combattant disponible</div>}
|
||||||
{Object.keys(dispoFiltered).map((id) => (
|
{Object.keys(dispoFiltered).sort((a, b) => nameCompare(data, a, b)).map((id) => (
|
||||||
<button key={id} type="button" className={"list-group-item list-group-item-action " + (dispoFiltered[id] ? "active" : "")}
|
<button key={id} type="button" className={"list-group-item list-group-item-action " + (dispoFiltered[id] ? "active" : "")}
|
||||||
onClick={() => dispoReducer({type: 'TOGGLE_ID', payload: id})}>
|
onClick={() => dispoReducer({type: 'TOGGLE_ID', payload: id})}>
|
||||||
<CombName combId={id}/>
|
<CombName combId={id}/>
|
||||||
@ -262,7 +262,7 @@ export function SelectCombModalContent({data, setGroups}) {
|
|||||||
<div style={{textAlign: "center"}}>Sélectionner</div>
|
<div style={{textAlign: "center"}}>Sélectionner</div>
|
||||||
<div className="list-group overflow-y-auto" style={{maxHeight: "50vh"}}>
|
<div className="list-group overflow-y-auto" style={{maxHeight: "50vh"}}>
|
||||||
{selectFiltered && Object.keys(selectFiltered).length === 0 && <div>Aucun combattant sélectionné</div>}
|
{selectFiltered && Object.keys(selectFiltered).length === 0 && <div>Aucun combattant sélectionné</div>}
|
||||||
{Object.keys(selectFiltered).map((id) => (
|
{Object.keys(selectFiltered).sort((a, b) => nameCompare(data, a, b)).map((id) => (
|
||||||
<button key={id} type="button"
|
<button key={id} type="button"
|
||||||
className={"list-group-item list-group-item-action " + (selectFiltered[id] ? "active" : "")}
|
className={"list-group-item list-group-item-action " + (selectFiltered[id] ? "active" : "")}
|
||||||
onClick={() => selectReducer({type: 'TOGGLE_ID', payload: id})}>
|
onClick={() => selectReducer({type: 'TOGGLE_ID', payload: id})}>
|
||||||
@ -285,3 +285,9 @@ export function SelectCombModalContent({data, setGroups}) {
|
|||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function nameCompare(data, a, b) {
|
||||||
|
const combA = data.find(d => d.id === Number(a));
|
||||||
|
const combB = data.find(d => d.id === Number(b));
|
||||||
|
return (combA.fname + " " + combA.lname).toLowerCase().localeCompare((combB.fname + " " + combB.lname).toLowerCase());
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user