Compare commits
56 Commits
dev-comp
...
b7a1e58436
| Author | SHA1 | Date | |
|---|---|---|---|
| b7a1e58436 | |||
| a73a3562c3 | |||
| 0dace76fc3 | |||
| f76ca43ed6 | |||
| c6b24a1b20 | |||
| c47c04c29e | |||
| e06df24002 | |||
| 352c34064f | |||
| 2ccd2a9178 | |||
| 847674d62c | |||
| 68b4f8e476 | |||
| 81faafe2de | |||
| 4856326b29 | |||
| b94f4b43ce | |||
| 795d044c0b | |||
| dc5ab9c3c4 | |||
| 7e1356b621 | |||
| b689521e56 | |||
| 095a5fcede | |||
| 254fd7d73f | |||
| 12a75d003c | |||
| 7f957b98f2 | |||
| 50b49c40e3 | |||
| 47d3921e05 | |||
| 7c4addd525 | |||
| fefc5d651b | |||
| 53509820c6 | |||
| f46e268d39 | |||
| 6e7eec0587 | |||
| 8c83668364 | |||
| 771c06ccd8 | |||
| b479b992cf | |||
| be2f01c070 | |||
| 9ab50238b9 | |||
| cf5d93630f | |||
| 7410569ced | |||
| 8e2d68ebd5 | |||
| d02fd63834 | |||
| b143cc759f | |||
| cc5534ef00 | |||
| 26f56006f6 | |||
| ed1f30f2b6 | |||
| 6cec8ff31d | |||
| d95c173fa8 | |||
| 8a0e4423f2 | |||
| b956236934 | |||
| 7767c98304 | |||
| 7e380ccb69 | |||
| 94d1148eb1 | |||
| 79dbbdaaec | |||
| dec98f9508 | |||
| b89ed62795 | |||
| 5e48bc4623 | |||
| c6659f8d85 | |||
| e86fe42b3d | |||
| ef528aa524 |
@@ -355,19 +355,25 @@ public class AffiliationService {
|
||||
.map(c -> club));
|
||||
})
|
||||
.call(club -> reactiveMailer.send(
|
||||
Mail.withText(form.getM1_email(),
|
||||
"FFSAF - Acceptation de votre demande d'affiliation",
|
||||
String.format(
|
||||
"""
|
||||
Bonjour,
|
||||
|
||||
Votre demande d'affiliation pour le club %s a été acceptée.
|
||||
Le numéro d'affiliation de votre club est le %d.
|
||||
|
||||
Cordialement,
|
||||
L'équipe de la FFSAF
|
||||
""", club.getName(), club.getNo_affiliation())
|
||||
).setFrom("FFSAF <no-reply@ffsaf.fr>").setReplyTo("contact@ffsaf.fr")
|
||||
Mail.withHtml(form.getM1_email(), "FFSAF - Creation de votre compte sur l'intranet",
|
||||
String.format(Utils.HTML_HEADER, "FFSAF - Creation de votre compte sur l'intranet") +
|
||||
String.format("""
|
||||
<p>Votre demande d'affiliation pour le club <span class="highlight">%s</span> a été acceptée.</p>
|
||||
<p>LLe numéro d'affiliation de votre club est le <span class="highlight">%d</span>.</p>
|
||||
""",
|
||||
club.getName(), club.getNo_affiliation()) +
|
||||
Utils.HTML_FOOTER
|
||||
)
|
||||
.setText(String.format("""
|
||||
Bonjour,
|
||||
|
||||
Votre demande d'affiliation pour le club %s a été acceptée.
|
||||
Le numéro d'affiliation de votre club est le %d.
|
||||
|
||||
Cordialement,
|
||||
L'équipe de la FFSAF
|
||||
""", club.getName(), club.getNo_affiliation()))
|
||||
.setFrom("FFSAF <no-reply@ffsaf.fr>").setReplyTo("contact@ffsaf.fr")
|
||||
.addTo(form.getM2_email(), form.getM3_email())
|
||||
));
|
||||
}
|
||||
@@ -458,21 +464,27 @@ public class AffiliationService {
|
||||
public Uni<?> deleteReqAffiliation(long id, String reason, boolean federationAdmin) {
|
||||
return repositoryRequest.findById(id)
|
||||
.call(aff -> federationAdmin ? reactiveMailer.send(
|
||||
Mail.withText(aff.getM1_email(),
|
||||
"FFSAF - Votre demande d'affiliation a été rejetée.",
|
||||
String.format(
|
||||
"""
|
||||
Bonjour,
|
||||
|
||||
Votre demande d'affiliation pour le club %s a été rejetée pour la/les raison(s) suivante(s):
|
||||
%s
|
||||
|
||||
Si vous rencontrez un problème ou si vous avez des questions, n'hésitez pas à nous contacter à l'adresse contact@ffsaf.fr.
|
||||
|
||||
Cordialement,
|
||||
L'équipe de la FFSAF
|
||||
""", aff.getName(), reason)
|
||||
).setFrom("FFSAF <no-reply@ffsaf.fr>").setReplyTo("contact@ffsaf.fr")
|
||||
Mail.withHtml(aff.getM1_email(), "FFSAF - Votre demande d'affiliation a été rejetée.",
|
||||
String.format(Utils.HTML_HEADER, "FFSAF - Votre demande d'affiliation a été rejetée.") +
|
||||
String.format("""
|
||||
<p>Votre demande d'affiliation pour le club %s a été rejetée pour la/les raison(s) suivante(s):<br/>%s</p>
|
||||
<p>Si vous rencontrez un problème ou si vous avez des questions, n'hésitez pas à nous contacter à l'adresse <a href="mailto:contact@ffsaf.fr">contact@ffsaf.fr</a>.</p>
|
||||
""",
|
||||
aff.getName(), reason) +
|
||||
Utils.HTML_FOOTER
|
||||
)
|
||||
.setText(String.format("""
|
||||
Bonjour,
|
||||
|
||||
Votre demande d'affiliation pour le club %s a été rejetée pour la/les raison(s) suivante(s):
|
||||
%s
|
||||
|
||||
Si vous rencontrez un problème ou si vous avez des questions, n'hésitez pas à nous contacter à l'adresse contact@ffsaf.fr.
|
||||
|
||||
Cordialement,
|
||||
L'équipe de la FFSAF
|
||||
""", aff.getName(), reason))
|
||||
.setFrom("FFSAF <no-reply@ffsaf.fr>").setReplyTo("contact@ffsaf.fr")
|
||||
.addTo(aff.getM2_email(), aff.getM3_email())
|
||||
) : Uni.createFrom().nullItem())
|
||||
.chain(aff -> Panache.withTransaction(() -> repositoryRequest.delete(aff)))
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import fr.titionfire.ffsaf.data.model.AffiliationModel;
|
||||
import fr.titionfire.ffsaf.data.model.ClubModel;
|
||||
import fr.titionfire.ffsaf.data.model.MembreModel;
|
||||
import fr.titionfire.ffsaf.data.repository.AffiliationRepository;
|
||||
import fr.titionfire.ffsaf.data.repository.ClubRepository;
|
||||
import fr.titionfire.ffsaf.data.repository.CombRepository;
|
||||
import fr.titionfire.ffsaf.net2.ServerCustom;
|
||||
@@ -44,6 +45,9 @@ public class ClubService {
|
||||
@Inject
|
||||
ClubRepository repository;
|
||||
|
||||
@Inject
|
||||
AffiliationRepository affiliationRepository;
|
||||
|
||||
@Inject
|
||||
ServerCustom serverCustom;
|
||||
|
||||
@@ -234,7 +238,8 @@ public class ClubService {
|
||||
}
|
||||
return Panache.withTransaction(() -> repository.persist(m)).call(() -> ls.append());
|
||||
}))
|
||||
.call(clubModel -> nameChange.get() ? keycloakService.updateGroupFromClub(clubModel) // update group in keycloak
|
||||
.call(clubModel -> nameChange.get() ? keycloakService.updateGroupFromClub(
|
||||
clubModel) // update group in keycloak
|
||||
: Uni.createFrom().nullItem())
|
||||
.invoke(membreModel -> SReqClub.sendIfNeed(serverCustom.clients,
|
||||
SimpleClubModel.fromModel(membreModel)))
|
||||
@@ -282,7 +287,8 @@ public class ClubService {
|
||||
combModel.setClub(null);
|
||||
combModel.setRole(RoleAsso.MEMBRE);
|
||||
}).toList())
|
||||
.call(list -> (list.isEmpty()) ? Uni.createFrom().voidItem() :
|
||||
.call(list -> list.stream().noneMatch(m -> m.getUserId() != null) ?
|
||||
Uni.createFrom().voidItem() :
|
||||
Uni.join().all(list.stream().filter(m -> m.getUserId() != null)
|
||||
.map(m -> keycloakService.clearUser(m.getUserId())).toList())
|
||||
.andCollectFailures())
|
||||
@@ -323,9 +329,11 @@ public class ClubService {
|
||||
}
|
||||
|
||||
public Uni<List<ClubMapData>> getMapData() {
|
||||
return repository.list("international", false).toMulti().flatMap(list -> Multi.createFrom().iterable(list))
|
||||
.call(clubModel -> Mutiny.fetch(clubModel.getContact()))
|
||||
.map(clubModel -> {
|
||||
return affiliationRepository.list("saison >= ?1 AND club.international = ?2", Utils.getSaison() - 1, false)
|
||||
.toMulti().flatMap(list -> Multi.createFrom().iterable(list))
|
||||
.call(affiliationModel -> Mutiny.fetch(affiliationModel.getClub().getContact()))
|
||||
.map(affiliationModel -> {
|
||||
ClubModel clubModel = affiliationModel.getClub();
|
||||
ClubMapData data = new ClubMapData();
|
||||
|
||||
data.setName(clubModel.getName());
|
||||
|
||||
@@ -281,7 +281,7 @@ public class CompetitionService {
|
||||
public Uni<SimpleRegisterComb> addRegisterComb(SecurityCtx securityCtx, Long id, RegisterRequestData data,
|
||||
String source) {
|
||||
if ("admin".equals(source))
|
||||
if (data.getLicence() != -1) { // not a guest
|
||||
if (data.getLicence() == null || data.getLicence() != -1) { // not a guest
|
||||
return permService.hasEditPerm(securityCtx, id)
|
||||
.chain(c -> findComb(data.getLicence(), data.getFname(), data.getLname())
|
||||
.call(combModel -> Mutiny.fetch(combModel.getLicences()))
|
||||
|
||||
@@ -156,24 +156,32 @@ public class KeycloakService {
|
||||
return oldEmail;
|
||||
}).call(oldEmail -> oldEmail == null || !enabled_email ? Uni.createFrom().item("") :
|
||||
reactiveMailer.send(
|
||||
Mail.withText(oldEmail,
|
||||
"FFSAF - Changement de votre adresse email",
|
||||
String.format(
|
||||
"""
|
||||
Bonjour,
|
||||
|
||||
Suite à la modification de votre adresse email fournie lors de votre (ré)inscription à la FFSAF,
|
||||
vous allez recevoir dans les prochaines minutes un email de vérification de votre nouvelle adresse sur celle-ci.
|
||||
|
||||
Ancienne adresse email : %s
|
||||
Nouvelle adresse email : %s
|
||||
|
||||
Si vous n'avez pas demandé cette modification, veuillez contacter le support à l'adresse support@ffsaf.fr.
|
||||
|
||||
Cordialement,
|
||||
L'équipe de la FFSAF
|
||||
""", oldEmail, email)
|
||||
).setFrom("FFSAF <no-reply@ffsaf.fr>").setReplyTo("support@ffsaf.fr")
|
||||
Mail.withHtml(oldEmail, "FFSAF - Changement de votre adresse email",
|
||||
String.format(Utils.HTML_HEADER, "FFSAF - Changement de votre adresse email") +
|
||||
String.format("""
|
||||
<p>Suite à la modification de votre adresse email fournie lors de votre (ré)inscription à la FFSAF,<br/>
|
||||
vous allez recevoir dans les prochaines minutes un email de vérification de votre nouvelle adresse sur celle-ci.</p>
|
||||
<p>Ancienne adresse email : <span class="highlight">%s</span><br/>Nouvelle adresse email : <span class="highlight">%s</span></p>
|
||||
<p>Si vous n'avez pas demandé cette modification, veuillez contacter le support à l'adresse <a href="mailto:support@ffsaf.fr">support@ffsaf.fr</a>.</p>
|
||||
""",
|
||||
oldEmail, email) +
|
||||
Utils.HTML_FOOTER
|
||||
)
|
||||
.setText(String.format("""
|
||||
Bonjour,
|
||||
|
||||
Suite à la modification de votre adresse email fournie lors de votre (ré)inscription à la FFSAF,
|
||||
vous allez recevoir dans les prochaines minutes un email de vérification de votre nouvelle adresse sur celle-ci.
|
||||
|
||||
Ancienne adresse email : %s
|
||||
Nouvelle adresse email : %s
|
||||
|
||||
Si vous n'avez pas demandé cette modification, veuillez contacter le support à l'adresse support@ffsaf.fr.
|
||||
|
||||
Cordialement,
|
||||
L'équipe de la FFSAF
|
||||
""", oldEmail, email))
|
||||
.setFrom("FFSAF <no-reply@ffsaf.fr>").setReplyTo("support@ffsaf.fr")
|
||||
).onFailure().invoke(e -> LOGGER.error("Fail to send email", e)));
|
||||
}
|
||||
|
||||
@@ -270,13 +278,33 @@ public class KeycloakService {
|
||||
.invoke(user -> membreModel.setUserId(user.getId()))
|
||||
.call(user -> updateRole(user.getId(), List.of("safca_user"), List.of()))
|
||||
.call(user -> enabled_email ? reactiveMailer.send(
|
||||
Mail.withText(user.getEmail(),
|
||||
"FFSAF - Creation de votre compte sur l'intranet",
|
||||
String.format(
|
||||
Mail.withHtml(user.getEmail(), "FFSAF - Creation de votre compte sur l'intranet",
|
||||
String.format(Utils.HTML_HEADER, "Création de votre compte intranet FFSAF") +
|
||||
String.format(
|
||||
"""
|
||||
<p>Suite à votre première inscription <span class="highlight">%s</span> à la <strong>Fédération France Soft Armored Fighting (FFSAF)</strong>, votre compte intranet a été créé.</p>
|
||||
<p>Ce compte vous permettra de :</p>
|
||||
<ul>
|
||||
<li>Consulter vos informations personnelles,</li>
|
||||
<li>Vous inscrire aux compétitions (bientôt disponible),</li>
|
||||
<li>Consulter les résultats des compétitions.</li>
|
||||
</ul>
|
||||
<p>L’intranet est accessible à l’adresse suivante :</p>
|
||||
<p style="text-align: center;"><a href="https://intra.ffsaf.fr" class="button">Accéder à l'intranet</a></p>
|
||||
<p>Votre nom d’utilisateur est : <span class="highlight">%s</span></p>
|
||||
<p>Pour définir votre mot de passe, rendez-vous sur l’intranet : <strong>Connexion > Mot de passe oublié ?</strong></p>
|
||||
<p>Si vous n’avez pas demandé cette inscription, veuillez contacter le support à l’adresse : <a href="mailto:support@ffsaf.fr">support@ffsaf.fr</a>.</p>
|
||||
<p class="footer">(Pas de panique, nous ne vous enverrons pas de message autre que ceux concernant votre compte.)</p>
|
||||
""",
|
||||
membreModel.getRole() == RoleAsso.MEMBRE ? "par votre club (" + membreModel.getClub()
|
||||
.getName() + ") " : "", user.getUsername()) +
|
||||
Utils.HTML_FOOTER
|
||||
)
|
||||
.setText(String.format(
|
||||
"""
|
||||
Bonjour,
|
||||
|
||||
Suite à votre première inscription %sà la Fédération Française de Soft Armored Fighting (FFSAF), votre compte intranet a été créé.
|
||||
Suite à votre première inscription %sà la Fédération France Soft Armored Fighting (FFSAF), votre compte intranet a été créé.
|
||||
Ce compte vous permettra de consulter vos informations et, dans un futur proche, de vous inscrire aux compétitions ainsi que d'en consulter les résultats.
|
||||
|
||||
L'intranet est accessible à l'adresse suivante : https://intra.ffsaf.fr
|
||||
@@ -291,8 +319,8 @@ public class KeycloakService {
|
||||
L'équipe de la FFSAF
|
||||
""",
|
||||
membreModel.getRole() == RoleAsso.MEMBRE ? "par votre club (" + membreModel.getClub()
|
||||
.getName() + ") " : "", user.getUsername())
|
||||
).setFrom("FFSAF <no-reply@ffsaf.fr>").setReplyTo("support@ffsaf.fr")
|
||||
.getName() + ") " : "", user.getUsername()))
|
||||
.setFrom("FFSAF <no-reply@ffsaf.fr>").setReplyTo("support@ffsaf.fr")
|
||||
) : Uni.createFrom().nullItem())
|
||||
.call(user -> membreService.setUserId(membreModel.getId(), user.getId()))
|
||||
.call(user -> setClubGroupMembre(membreModel, membreModel.getClub()));
|
||||
|
||||
@@ -84,6 +84,26 @@ public class LicenceService {
|
||||
));
|
||||
}
|
||||
|
||||
public Uni<?> validePaymentLicences(List<Long> ids) {
|
||||
Uni<String> uni = Uni.createFrom().nullItem();
|
||||
|
||||
for (Long id : ids) {
|
||||
uni = uni.chain(__ -> repository.find("membre.id = ?1 AND saison = ?2", id, Utils.getSaison()).firstResult()
|
||||
.chain(model -> {
|
||||
if (!model.isPay())
|
||||
ls.logUpdate("payment (admin) de la licence", model);
|
||||
return validatePayLicences(model);
|
||||
}))
|
||||
.map(__ -> "OK");
|
||||
}
|
||||
return uni.call(__ -> ls.append());
|
||||
}
|
||||
|
||||
protected Uni<LicenceModel> validatePayLicences(LicenceModel model) {
|
||||
model.setPay(true);
|
||||
return Panache.withTransaction(() -> repository.persist(model));
|
||||
}
|
||||
|
||||
public Uni<LicenceModel> setLicence(long id, LicenceForm form) {
|
||||
if (form.getId() == -1) {
|
||||
return combRepository.findById(id).chain(membreModel -> {
|
||||
@@ -121,7 +141,7 @@ public class LicenceService {
|
||||
}
|
||||
|
||||
private Function<MembreModel, Uni<?>> genLicenceNumberAndAccountIfNeed() {
|
||||
return membreModel -> ((membreModel.getLicence() <= 0) ?
|
||||
return membreModel -> ((membreModel.getLicence() == null || membreModel.getLicence() <= 0) ?
|
||||
sequenceRepository.getNextValueInTransaction(SequenceType.Licence)
|
||||
.invoke(i -> membreModel.setLicence(Math.toIntExact(i)))
|
||||
.chain(() -> combRepository.persist(membreModel))
|
||||
@@ -157,7 +177,7 @@ public class LicenceService {
|
||||
"Impossible de supprimer une licence pour laquelle un paiement est en cours");
|
||||
})))
|
||||
.call(model -> ls.logADelete(model))
|
||||
.chain(model -> repository.delete(model));
|
||||
.chain(model -> Panache.withTransaction(() -> repository.delete(model)));
|
||||
}
|
||||
|
||||
public Uni<LicenceModel> askLicence(long id, LicenceForm form, Consumer<MembreModel> checkPerm) {
|
||||
|
||||
@@ -125,8 +125,21 @@ public class MembreService {
|
||||
return sort;
|
||||
}
|
||||
|
||||
public Uni<PageResult<SimpleMembre>> searchAdmin(int limit, int page, String search, String club,
|
||||
int licenceRequest, int payState, String order, String categorie) {
|
||||
public Uni<PageResult<SimpleMembre>> search(int limit, int page, String search, int licenceRequest, int payState,
|
||||
String order, String categorie, Boolean archive, String subject) {
|
||||
return repository.find("userId = ?1", subject).firstResult()
|
||||
.invoke(Unchecked.consumer(membreModel -> {
|
||||
if (membreModel == null || membreModel.getClub() == null)
|
||||
throw new DForbiddenException();
|
||||
}))
|
||||
.chain(membreModel ->
|
||||
searchAdmin(limit, page, search, membreModel.getClub(), licenceRequest, payState, order,
|
||||
categorie, archive));
|
||||
}
|
||||
|
||||
public Uni<PageResult<SimpleMembre>> searchAdmin(int limit, int page, String search, Object club,
|
||||
int licenceRequest, int payState, String order, String categorie,
|
||||
Boolean archive) {
|
||||
if (search == null)
|
||||
search = "";
|
||||
search = "%" + search.replaceAll(" ", "% %") + "%";
|
||||
@@ -137,77 +150,47 @@ public class MembreService {
|
||||
else
|
||||
categorieFilter = "categorie = " + Categorie.valueOf(categorie).ordinal();
|
||||
|
||||
String finalSearch = search;
|
||||
Uni<List<LicenceModel>> baseUni = getLicenceListe(licenceRequest, payState);
|
||||
String archiveFilter;
|
||||
if (archive == null || archive)
|
||||
archiveFilter = "True";
|
||||
else
|
||||
archiveFilter = String.format(
|
||||
"EXISTS (SELECT 1 FROM LicenceModel l WHERE l.membre.id = m.id AND l.saison >= %s)",
|
||||
Utils.getSaison() - 1);
|
||||
|
||||
String clubFilter = "?3 = ?3";
|
||||
if (club != null) {
|
||||
if (club instanceof String club_) {
|
||||
if (!club_.isBlank()) {
|
||||
if (club_.equals("null"))
|
||||
clubFilter = "club IS NULL AND ?3 = ?3";
|
||||
else
|
||||
clubFilter = "LOWER(club.name) LIKE LOWER(?3)";
|
||||
}
|
||||
} else if (club instanceof ClubModel)
|
||||
clubFilter = "club = ?3";
|
||||
}
|
||||
|
||||
String queryStr = String.format(
|
||||
"SELECT m FROM MembreModel m WHERE id %s ?2 AND %s AND (%s) AND %s AND %s",
|
||||
(licenceRequest == 0 || licenceRequest == 4) ? "NOT IN" : "IN", clubFilter, FIND_NAME_REQUEST,
|
||||
categorieFilter,
|
||||
archiveFilter);
|
||||
|
||||
Sort sort = getSort(order);
|
||||
if (sort == null)
|
||||
return Uni.createFrom().failure(new DInternalError("Erreur lors calcul du trie"));
|
||||
|
||||
return baseUni
|
||||
String finalSearch = search;
|
||||
return getLicenceListe(licenceRequest, payState)
|
||||
.map(l -> l.stream().map(l2 -> l2.getMembre().getId()).toList())
|
||||
.chain(ids -> {
|
||||
PanacheQuery<MembreModel> query;
|
||||
|
||||
String idf = ((licenceRequest == 0 || licenceRequest == 4) ? "NOT IN" : "IN");
|
||||
|
||||
if (club == null || club.isBlank()) {
|
||||
query = repository.find(
|
||||
"id " + idf + " ?2 AND (" + FIND_NAME_REQUEST + ") AND " + categorieFilter,
|
||||
sort, finalSearch, ids)
|
||||
.page(Page.ofSize(limit));
|
||||
} else {
|
||||
if (club.equals("null")) {
|
||||
query = repository.find(
|
||||
"id " + idf + " ?2 AND club IS NULL AND (" + FIND_NAME_REQUEST + ") AND " + categorieFilter,
|
||||
sort, finalSearch, ids).page(Page.ofSize(limit));
|
||||
} else {
|
||||
query = repository.find(
|
||||
"id " + idf + " ?3 AND LOWER(club.name) LIKE LOWER(?2) AND (" + FIND_NAME_REQUEST + ") AND " + categorieFilter,
|
||||
sort, finalSearch, club, ids)
|
||||
.page(Page.ofSize(limit));
|
||||
}
|
||||
}
|
||||
query = repository.find(queryStr, sort, finalSearch, ids, club).page(Page.ofSize(limit));
|
||||
return getPageResult(query, limit, page);
|
||||
});
|
||||
}
|
||||
|
||||
public Uni<PageResult<SimpleMembre>> search(int limit, int page, String search, int licenceRequest, int payState,
|
||||
String order, String categorie, String subject) {
|
||||
if (search == null)
|
||||
search = "";
|
||||
search = "%" + search.replaceAll(" ", "% %") + "%";
|
||||
|
||||
String finalSearch = search;
|
||||
|
||||
Uni<List<LicenceModel>> baseUni = getLicenceListe(licenceRequest, payState);
|
||||
|
||||
String categorieFilter;
|
||||
if (categorie == null || categorie.isBlank())
|
||||
categorieFilter = " True";
|
||||
else
|
||||
categorieFilter = "categorie = " + Categorie.valueOf(categorie).ordinal();
|
||||
|
||||
Sort sort = getSort(order);
|
||||
if (sort == null)
|
||||
return Uni.createFrom().failure(new DInternalError("Erreur lors calcul du trie"));
|
||||
|
||||
return baseUni
|
||||
.map(l -> l.stream().map(l2 -> l2.getMembre().getId()).toList())
|
||||
.chain(ids -> {
|
||||
String idf = ((licenceRequest == 0 || licenceRequest == 4) ? "NOT IN" : "IN");
|
||||
|
||||
return repository.find("userId = ?1", subject).firstResult()
|
||||
.chain(membreModel -> {
|
||||
PanacheQuery<MembreModel> query = repository.find(
|
||||
"id " + idf + " ?3 AND club = ?2 AND (" + FIND_NAME_REQUEST + ") AND " + categorieFilter,
|
||||
sort, finalSearch, membreModel.getClub(), ids)
|
||||
.page(Page.ofSize(limit));
|
||||
return getPageResult(query, limit, page);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private Uni<PageResult<SimpleMembre>> getPageResult(PanacheQuery<MembreModel> query, int limit, int page) {
|
||||
return Uni.createFrom().item(new PageResult<SimpleMembre>())
|
||||
.invoke(result -> result.setPage(page))
|
||||
@@ -287,14 +270,16 @@ public class MembreService {
|
||||
if (model.getEmail() != null && !model.getEmail().isBlank()) {
|
||||
if (model.getLicence() != null && !model.getLicence().equals(dataIn.getLicence())) {
|
||||
LOGGER.info("Similar membres found: " + model);
|
||||
throw new DBadRequestException("Email '" + model.getEmail() + "' déja utiliser");
|
||||
throw new DBadRequestException(
|
||||
"Email '" + model.getEmail() + "' déja utilisé par " + model.getFname() + " " + model.getFname());
|
||||
}
|
||||
|
||||
if (StringSimilarity.similarity(model.getLname().toUpperCase(),
|
||||
dataIn.getNom().toUpperCase()) > 3 || StringSimilarity.similarity(
|
||||
model.getFname().toUpperCase(), dataIn.getPrenom().toUpperCase()) > 3) {
|
||||
LOGGER.info("Similar membres found: " + model);
|
||||
throw new DBadRequestException("Email '" + model.getEmail() + "' déja utiliser");
|
||||
throw new DBadRequestException(
|
||||
"Email '" + model.getEmail() + "' déja utilisé par " + model.getFname() + " " + model.getFname());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -305,7 +290,8 @@ public class MembreService {
|
||||
model.getFname().toUpperCase(), dataIn.getPrenom().toUpperCase()) > 3)) {
|
||||
LOGGER.info("Similar membres found: " + model);
|
||||
throw new DBadRequestException(
|
||||
"Pour enregistrer un nouveau membre, veuillez laisser le champ licence vide.");
|
||||
"Pour enregistrer un nouveau membre, veuillez laisser le champ licence vide. (tentative de changement non-autotiser de nom sur la licence "
|
||||
+ model.getLicence() + " pour " + model.getFname() + " " + model.getFname() + ")");
|
||||
}
|
||||
|
||||
ls.logChange("Nom", model.getLname(), dataIn.getNom().toUpperCase(), model);
|
||||
@@ -313,8 +299,7 @@ public class MembreService {
|
||||
dataIn.getPrenom().toUpperCase().charAt(0) + dataIn.getPrenom().substring(1), model);
|
||||
|
||||
model.setLname(dataIn.getNom().toUpperCase());
|
||||
model.setFname(dataIn.getPrenom().toUpperCase().charAt(0) + dataIn.getPrenom().substring(1));
|
||||
|
||||
model.setFname(Utils.formatPrenom(dataIn.getPrenom()));
|
||||
|
||||
if (dataIn.getEmail() != null && !dataIn.getEmail().isBlank()) {
|
||||
ls.logChange("Email", model.getEmail(), dataIn.getEmail(), model);
|
||||
@@ -380,7 +365,7 @@ public class MembreService {
|
||||
.call(__ -> repository.count("email LIKE ?1 AND id != ?2", membre.getEmail(), id)
|
||||
.invoke(Unchecked.consumer(c -> {
|
||||
if (c > 0 && !membre.getEmail().isBlank())
|
||||
throw new DBadRequestException("Email déjà utiliser");
|
||||
throw new DBadRequestException("Email déjà utilisé");
|
||||
})))
|
||||
.chain(membreModel -> clubRepository.findById(membre.getClub())
|
||||
.map(club -> new Pair<>(membreModel, club)))
|
||||
@@ -402,7 +387,7 @@ public class MembreService {
|
||||
.call(__ -> repository.count("email LIKE ?1 AND id != ?2", membre.getEmail(), id)
|
||||
.invoke(Unchecked.consumer(c -> {
|
||||
if (c > 0 && !membre.getEmail().isBlank())
|
||||
throw new DBadRequestException("Email déjà utiliser");
|
||||
throw new DBadRequestException("Email déjà utilisé");
|
||||
})))
|
||||
.invoke(Unchecked.consumer(membreModel -> {
|
||||
if (!securityCtx.isInClubGroup(membreModel.getClub().getId()))
|
||||
@@ -434,7 +419,7 @@ public class MembreService {
|
||||
private Uni<String> update(Uni<MembreModel> uni, FullMemberForm membre, boolean admin) {
|
||||
return uni.chain(target -> {
|
||||
ls.logChange("Prénom", target.getFname(), membre.getFname(), target);
|
||||
target.setFname(membre.getFname());
|
||||
target.setFname(Utils.formatPrenom(membre.getFname()));
|
||||
ls.logChange("Nom", target.getLname(), membre.getLname(), target);
|
||||
target.setLname(membre.getLname().toUpperCase());
|
||||
ls.logChange("Pays", target.getCountry(), membre.getCountry(), target);
|
||||
@@ -492,7 +477,7 @@ public class MembreService {
|
||||
return clubRepository.findById(input.getClub())
|
||||
.call(__ -> repository.count("email LIKE ?1", input.getEmail())
|
||||
.invoke(Unchecked.consumer(c -> {
|
||||
if (c > 0) throw new DBadRequestException("Email déjà utiliser");
|
||||
if (c > 0) throw new DBadRequestException("Email déjà utilisé");
|
||||
})))
|
||||
.chain(clubModel -> {
|
||||
MembreModel model = getMembreModel(input, clubModel);
|
||||
@@ -508,7 +493,7 @@ public class MembreService {
|
||||
return repository.find("userId = ?1", subject).firstResult()
|
||||
.call(__ -> repository.count("email LIKE ?1", input.getEmail())
|
||||
.invoke(Unchecked.consumer(c -> {
|
||||
if (c > 0) throw new DBadRequestException("Email déjà utiliser");
|
||||
if (c > 0) throw new DBadRequestException("Email déjà utilisé");
|
||||
})))
|
||||
.call(membreModel ->
|
||||
repository.count(
|
||||
@@ -577,8 +562,8 @@ public class MembreService {
|
||||
|
||||
private static MembreModel getMembreModel(FullMemberForm input, ClubModel clubModel) {
|
||||
MembreModel model = new MembreModel();
|
||||
model.setFname(input.getFname());
|
||||
model.setLname(input.getLname());
|
||||
model.setFname(Utils.formatPrenom(input.getFname()));
|
||||
model.setLname(input.getLname().toUpperCase());
|
||||
model.setEmail(input.getEmail());
|
||||
model.setLicence(null);
|
||||
model.setGenre(input.getGenre());
|
||||
|
||||
@@ -49,7 +49,7 @@ public class AffiliationEndpoints {
|
||||
|
||||
@GET
|
||||
@Path("{id}")
|
||||
@RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_respo_intra"})
|
||||
@RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_tresorier", "club_respo_intra"})
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Operation(summary = "Renvoie les affiliations pour un club", description = "Cette méthode renvoie les affiliations pour un club donné. Seuls les administrateurs de la fédération et les présidents, secrétaires et responsables intranet du club peuvent accéder à cette méthode.")
|
||||
@APIResponses(value = {
|
||||
|
||||
@@ -73,7 +73,7 @@ public class AffiliationRequestEndpoints {
|
||||
|
||||
@GET
|
||||
@Path("/{id}")
|
||||
@RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_respo_intra"})
|
||||
@RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_tresorier", "club_respo_intra"})
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Operation(summary = "Renvoie une demande d'affiliation", description = "Cette méthode renvoie une demande d'affiliation " +
|
||||
"pour l'identifiant spécifié.")
|
||||
@@ -92,7 +92,7 @@ public class AffiliationRequestEndpoints {
|
||||
|
||||
@DELETE
|
||||
@Path("/{id}")
|
||||
@RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_respo_intra"})
|
||||
@RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_tresorier", "club_respo_intra"})
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Operation(summary = "Supprime une demande d'affiliation", description = "Cette méthode supprime une demande " +
|
||||
"d'affiliation pour l'identifiant spécifié.")
|
||||
@@ -128,7 +128,7 @@ public class AffiliationRequestEndpoints {
|
||||
|
||||
@PUT
|
||||
@Path("/edit")
|
||||
@RolesAllowed({"club_president", "club_secretaire", "club_respo_intra"})
|
||||
@RolesAllowed({"club_president", "club_secretaire", "club_tresorier", "club_respo_intra"})
|
||||
@Produces(MediaType.TEXT_PLAIN)
|
||||
@Consumes(MediaType.MULTIPART_FORM_DATA)
|
||||
@Operation(summary = "Modifie une demande d'affiliation", description = "Cette méthode modifie une demande " +
|
||||
|
||||
@@ -109,7 +109,7 @@ public class ClubEndpoints {
|
||||
|
||||
@GET
|
||||
@Path("{id}")
|
||||
@RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_respo_intra"})
|
||||
@RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_tresorier", "club_respo_intra"})
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Operation(summary = "Renvoie les détails d'un club en fonction de son identifiant", description = "Renvoie les " +
|
||||
"détails d'un club en fonction de son identifiant, y compris les informations sur les membres et les affiliations")
|
||||
@@ -230,7 +230,7 @@ public class ClubEndpoints {
|
||||
|
||||
@GET
|
||||
@Path("/me")
|
||||
@RolesAllowed({"club_president", "club_secretaire", "club_respo_intra"})
|
||||
@RolesAllowed({"club_president", "club_secretaire", "club_tresorier", "club_respo_intra"})
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Operation(summary = "Renvoie les informations du club de l'utilisateur connecté", description = "Renvoie les " +
|
||||
"informations du club de l'utilisateur connecté")
|
||||
@@ -247,7 +247,7 @@ public class ClubEndpoints {
|
||||
|
||||
@PUT
|
||||
@Path("/me")
|
||||
@RolesAllowed({"club_president", "club_secretaire", "club_respo_intra"})
|
||||
@RolesAllowed({"club_president", "club_secretaire", "club_tresorier", "club_respo_intra"})
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.MULTIPART_FORM_DATA)
|
||||
@Operation(summary = "Met à jour les informations du club de l'utilisateur connecté", description = "Met à jour les" +
|
||||
@@ -264,7 +264,7 @@ public class ClubEndpoints {
|
||||
|
||||
@GET
|
||||
@Path("/me/affiliation")
|
||||
@RolesAllowed({"club_president", "club_secretaire", "club_respo_intra"})
|
||||
@RolesAllowed({"club_president", "club_secretaire", "club_tresorier", "club_respo_intra"})
|
||||
@Operation(summary = "Renvoie l'attestation d'affiliation du club de l'utilisateur connecté", description =
|
||||
"Renvoie l'attestation d'affiliation du club de l'utilisateur connecté")
|
||||
@APIResponses(value = {
|
||||
@@ -296,7 +296,7 @@ public class ClubEndpoints {
|
||||
|
||||
@GET
|
||||
@Path("/renew/{id}")
|
||||
@RolesAllowed({"club_president", "club_secretaire", "club_respo_intra"})
|
||||
@RolesAllowed({"club_president", "club_secretaire", "club_tresorier", "club_respo_intra"})
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Operation(hidden = true)
|
||||
public Uni<RenewAffData> getRenew(@PathParam("id") long id, @QueryParam("m1") long m1_id,
|
||||
@@ -307,7 +307,7 @@ public class ClubEndpoints {
|
||||
|
||||
@GET
|
||||
@Path("/desk/{id}")
|
||||
@RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_respo_intra"})
|
||||
@RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_tresorier", "club_respo_intra"})
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Operation(summary = "Renvoie la liste des membres du bureau du club", description = "Renvoie la liste des membres " +
|
||||
"du bureau du club spécifié")
|
||||
@@ -345,7 +345,7 @@ public class ClubEndpoints {
|
||||
|
||||
@GET
|
||||
@Path("{id}/status")
|
||||
@RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_respo_intra"})
|
||||
@RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_tresorier", "club_respo_intra"})
|
||||
@Operation(summary = "Renvoie le statut du club", description = "Renvoie le statut du club spécifié")
|
||||
@APIResponses(value = {
|
||||
@APIResponse(responseCode = "200", description = "Le statut du club"),
|
||||
|
||||
@@ -37,7 +37,7 @@ public class CompteEndpoints {
|
||||
|
||||
@GET
|
||||
@Path("{id}")
|
||||
@RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_respo_intra"})
|
||||
@RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_tresorier", "club_respo_intra"})
|
||||
@Operation(summary = "Renvoie les informations d'un compte utilisateur", description = "Renvoie les informations d'un" +
|
||||
" compte utilisateur en fonction de son identifiant long (UUID)")
|
||||
@APIResponses(value = {
|
||||
|
||||
@@ -36,7 +36,7 @@ public class LicenceEndpoints {
|
||||
|
||||
@GET
|
||||
@Path("{id}")
|
||||
@RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_respo_intra"})
|
||||
@RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_tresorier", "club_respo_intra"})
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Operation(summary = "Renvoie les licences d'un membre", description = "Renvoie les licences d'un membre en fonction " +
|
||||
"de son identifiant")
|
||||
@@ -68,7 +68,7 @@ public class LicenceEndpoints {
|
||||
|
||||
@GET
|
||||
@Path("current/club")
|
||||
@RolesAllowed({"club_president", "club_secretaire", "club_respo_intra"})
|
||||
@RolesAllowed({"club_president", "club_secretaire", "club_tresorier", "club_respo_intra"})
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Operation(summary = "Renvoie les licences de la saison en cours (pour les clubs)", description = "Renvoie les " +
|
||||
"licences de la saison en cours (pour les clubs)")
|
||||
@@ -83,7 +83,7 @@ public class LicenceEndpoints {
|
||||
|
||||
@POST
|
||||
@Path("pay")
|
||||
@RolesAllowed({"club_president", "club_secretaire", "club_respo_intra"})
|
||||
@RolesAllowed({"club_president", "club_secretaire", "club_tresorier", "club_respo_intra"})
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Operation(summary = "Paiement des licence", description = "Retourne le lien de paiement pour les licence des membre fournie")
|
||||
@@ -118,7 +118,7 @@ public class LicenceEndpoints {
|
||||
@RolesAllowed("federation_admin")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Operation(summary = "Validation licence", description = "Valide en masse les licence de l'année en cours (pour les administrateurs)")
|
||||
@Operation(summary = "Validation licence", description = "Valide en masse les licences de l'année en cours (pour les administrateurs)")
|
||||
@APIResponses(value = {
|
||||
@APIResponse(responseCode = "200", description = "Les licences ont été mise à jour avec succès"),
|
||||
@APIResponse(responseCode = "403", description = "Accès refusé"),
|
||||
@@ -128,6 +128,21 @@ public class LicenceEndpoints {
|
||||
return licenceService.valideLicences(ids);
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("validate-pay")
|
||||
@RolesAllowed("federation_admin")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Operation(summary = "Validation du payment des licences", description = "Valide en masse le payment des licences de l'année en cours (pour les administrateurs)")
|
||||
@APIResponses(value = {
|
||||
@APIResponse(responseCode = "200", description = "Les licences ont été mise à jour avec succès"),
|
||||
@APIResponse(responseCode = "403", description = "Accès refusé"),
|
||||
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
|
||||
})
|
||||
public Uni<?> validePaymentLicences(@Parameter(description = "Id des membres a valider") List<Long> ids) {
|
||||
return licenceService.validePaymentLicences(ids);
|
||||
}
|
||||
|
||||
@DELETE
|
||||
@Path("{id}")
|
||||
@RolesAllowed("federation_admin")
|
||||
@@ -146,7 +161,7 @@ public class LicenceEndpoints {
|
||||
|
||||
@POST
|
||||
@Path("club/{id}")
|
||||
@RolesAllowed({"club_president", "club_secretaire", "club_respo_intra"})
|
||||
@RolesAllowed({"club_president", "club_secretaire", "club_tresorier", "club_respo_intra"})
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.MULTIPART_FORM_DATA)
|
||||
@Operation(summary = "Demande une nouvelle licence", description = "Demande une nouvelle licence en fonction de" +
|
||||
@@ -163,7 +178,7 @@ public class LicenceEndpoints {
|
||||
|
||||
@DELETE
|
||||
@Path("club/{id}")
|
||||
@RolesAllowed({"club_president", "club_secretaire", "club_respo_intra"})
|
||||
@RolesAllowed({"club_president", "club_secretaire", "club_tresorier", "club_respo_intra"})
|
||||
@Produces(MediaType.TEXT_PLAIN)
|
||||
@Operation(summary = "Supprime une demande de licence", description = "Supprime une demande de licence en fonction " +
|
||||
"de son identifiant (pour les clubs)")
|
||||
|
||||
@@ -59,6 +59,7 @@ public class MembreAdminEndpoints {
|
||||
@Parameter(description = "Text à rechercher") @QueryParam("search") String search,
|
||||
@Parameter(description = "Club à filter") @QueryParam("club") String club,
|
||||
@Parameter(description = "Catégorie à filter") @QueryParam("categorie") String categorie,
|
||||
@Parameter(description = "Inclure les archive") @QueryParam("archive") Boolean archive,
|
||||
@Parameter(description = "État de la demande de licence: 0 -> sans demande, 1 -> avec demande ou validée, 2 -> toute les demande non validée, 3 -> validée, 4 -> tout, 5 -> demande complete, 6 -> demande incomplete") @QueryParam("licenceRequest") int licenceRequest,
|
||||
@Parameter(description = "État du payment: 0 -> non payer, 1 -> payer, 2 -> tout") @QueryParam("payment") int payment,
|
||||
@Parameter(description = "Ordre") @QueryParam("order") String order) {
|
||||
@@ -66,7 +67,8 @@ public class MembreAdminEndpoints {
|
||||
limit = 50;
|
||||
if (page == null || page < 1)
|
||||
page = 1;
|
||||
return membreService.searchAdmin(limit, page - 1, search, club, licenceRequest, payment, order, categorie);
|
||||
return membreService.searchAdmin(limit, page - 1, search, club, licenceRequest, payment, order,
|
||||
categorie, archive);
|
||||
}
|
||||
|
||||
@GET
|
||||
|
||||
@@ -24,7 +24,7 @@ import org.eclipse.microprofile.openapi.annotations.tags.Tag;
|
||||
import java.util.List;
|
||||
|
||||
@Tag(name = "Membre club", description = "Gestion des membres (pour les clubs)")
|
||||
@RolesAllowed({"club_president", "club_secretaire", "club_respo_intra"})
|
||||
@RolesAllowed({"club_president", "club_secretaire", "club_tresorier", "club_respo_intra"})
|
||||
@Path("api/member")
|
||||
public class MembreClubEndpoints {
|
||||
|
||||
@@ -51,6 +51,7 @@ public class MembreClubEndpoints {
|
||||
@Parameter(description = "Page à consulter") @QueryParam("page") Integer page,
|
||||
@Parameter(description = "Text à rechercher") @QueryParam("search") String search,
|
||||
@Parameter(description = "Catégorie à filter") @QueryParam("categorie") String categorie,
|
||||
@Parameter(description = "Inclure les archive") @QueryParam("archive") Boolean archive,
|
||||
@Parameter(description = "Etat de la demande de licence: 0 -> sans demande, 1 -> avec demande ou validée, 2 -> toute les demande non validée, 3 -> validée, 4 -> tout, 5 -> demande complete, 6 -> demande incomplete") @QueryParam("licenceRequest") int licenceRequest,
|
||||
@Parameter(description = "Etat du payment: 0 -> non payer, 1 -> payer, 2 -> tout") @QueryParam("payment") int payment,
|
||||
@Parameter(description = "Ordre") @QueryParam("order") String order) {
|
||||
@@ -58,7 +59,7 @@ public class MembreClubEndpoints {
|
||||
limit = 50;
|
||||
if (page == null || page < 1)
|
||||
page = 1;
|
||||
return membreService.search(limit, page - 1, search, licenceRequest, payment, order, categorie, securityCtx.getSubject());
|
||||
return membreService.search(limit, page - 1, search, licenceRequest, payment, order, categorie, archive, securityCtx.getSubject());
|
||||
}
|
||||
|
||||
@GET
|
||||
|
||||
@@ -51,7 +51,7 @@ public class MembreEndpoints {
|
||||
|
||||
@GET
|
||||
@Path("{id}")
|
||||
@RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_respo_intra"})
|
||||
@RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_tresorier", "club_respo_intra"})
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Operation(summary = "Renvoie les détails d'un membre en fonction de son identifiant", description = "Renvoie les " +
|
||||
"détails d'un membre en fonction de son identifiant")
|
||||
@@ -68,7 +68,7 @@ public class MembreEndpoints {
|
||||
|
||||
@GET
|
||||
@Path("/find/licence")
|
||||
@RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_respo_intra"})
|
||||
@RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_tresorier", "club_respo_intra"})
|
||||
@Operation(summary = "Renvoie les détails d'un membre en fonction de son numéro de licence", description = "Renvoie " +
|
||||
"les détails d'un membre en fonction de son numéro de licence")
|
||||
@APIResponses(value = {
|
||||
@@ -130,7 +130,7 @@ public class MembreEndpoints {
|
||||
|
||||
@GET
|
||||
@Path("{id}/photo")
|
||||
@RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_respo_intra"})
|
||||
@RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_tresorier", "club_respo_intra"})
|
||||
@Operation(summary = "Renvoie la photo d'un membre", description = "Renvoie la photo d'un membre en fonction de son identifiant")
|
||||
@APIResponses(value = {
|
||||
@APIResponse(responseCode = "200", description = "La photo du membre"),
|
||||
@@ -146,7 +146,7 @@ public class MembreEndpoints {
|
||||
|
||||
@GET
|
||||
@Path("{id}/licence")
|
||||
@RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_respo_intra"})
|
||||
@RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_tresorier", "club_respo_intra"})
|
||||
@Operation(summary = "Renvoie le pdf de la licence d'un membre", description = "Renvoie le pdf de la licence d'un membre en fonction de son identifiant")
|
||||
@APIResponses(value = {
|
||||
@APIResponse(responseCode = "200", description = "Le pdf de la licence"),
|
||||
|
||||
@@ -46,7 +46,7 @@ public class MeData {
|
||||
this.id = membreModel.getId();
|
||||
this.lname = membreModel.getLname();
|
||||
this.fname = membreModel.getFname();
|
||||
this.categorie = membreModel.getCategorie().getName();
|
||||
this.categorie = membreModel.getCategorie() == null ? "catégorie inconnue" : membreModel.getCategorie().getName();
|
||||
this.club = membreModel.getClub() == null ? "Sans club" : membreModel.getClub().getName();
|
||||
this.genre = membreModel.getGenre().str;
|
||||
this.licence = membreModel.getLicence();
|
||||
|
||||
@@ -21,6 +21,79 @@ import java.util.concurrent.Future;
|
||||
public class Utils {
|
||||
private static final Logger LOGGER = Logger.getLogger(Utils.class);
|
||||
|
||||
public static String HTML_HEADER = """
|
||||
<!DOCTYPE html>
|
||||
<html data-lt-installed="true">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>%s</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.header {
|
||||
background-color: #003366;
|
||||
color: white;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
border-radius: 5px 5px 0 0;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 20px;
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 0 0 5px 5px;
|
||||
border: 1px solid #ddd;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.button {
|
||||
display: inline-block;
|
||||
padding: 10px 20px;
|
||||
background-color: #003366;
|
||||
color: white !important;
|
||||
text-decoration: none;
|
||||
border-radius: 5px;
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
.footer {
|
||||
margin-top: 20px;
|
||||
font-size: 0.9em;
|
||||
color: #666;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.highlight {
|
||||
font-weight: bold;
|
||||
color: #003366;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body data-gramm="false" data-lt-tmp-id="lt-957854">
|
||||
<div class="header">
|
||||
<div><img src="https://intra.ffsaf.fr/Logo-FFSAF-2023.png" alt="ffsaf" height="128">
|
||||
<h1>Fédération France Soft Armored Fighting</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<p>Bonjour,</p>
|
||||
""";
|
||||
public static String HTML_FOOTER = """
|
||||
<p>Cordialement,<br>L’équipe de la FFSAF</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
""";
|
||||
|
||||
public static int getSaison() {
|
||||
return getSaison(new Date());
|
||||
}
|
||||
@@ -262,4 +335,34 @@ public class Utils {
|
||||
|
||||
return (int) ((calendar.getTimeInMillis() - now.getTimeInMillis()) / (1000 * 60 * 60 * 24));
|
||||
}
|
||||
|
||||
public static String formatPrenom(String input) {
|
||||
if (input == null || input.isEmpty()) {
|
||||
return input;
|
||||
}
|
||||
|
||||
StringBuilder result = new StringBuilder();
|
||||
String[] mots = input.split(" ");
|
||||
|
||||
for (String mot : mots) {
|
||||
if (!mot.isEmpty()) {
|
||||
String[] parties = mot.split("[-']");
|
||||
StringBuilder motFormate = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < parties.length; i++) {
|
||||
if (!parties[i].isEmpty()) {
|
||||
String premiereLettre = parties[i].substring(0, 1).toUpperCase();
|
||||
String reste = parties[i].substring(1).toLowerCase();
|
||||
motFormate.append(premiereLettre).append(reste);
|
||||
}
|
||||
|
||||
if (i < parties.length - 1) {
|
||||
motFormate.append(mot.charAt(mot.indexOf(parties[i]) + parties[i].length()));
|
||||
}
|
||||
}
|
||||
result.append(motFormate).append(" ");
|
||||
}
|
||||
}
|
||||
return result.toString().trim();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import org.jboss.logging.Logger;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.time.Duration;
|
||||
import java.util.*;
|
||||
|
||||
import static fr.titionfire.ffsaf.net2.Client_Thread.MAPPER;
|
||||
@@ -166,6 +167,7 @@ public class CompetitionWS {
|
||||
return Uni.createFrom().item(makeError(message, "Permission denied")).toMulti();
|
||||
return ((Uni<?>) method.invoke(entry.getValue(), connection,
|
||||
MAPPER.treeToValue(message.data(), method.getParameterTypes()[1])))
|
||||
.ifNoItem().after(Duration.ofSeconds(5)).fail()
|
||||
.map(o -> makeReply(message, o))
|
||||
.onFailure()
|
||||
.recoverWithItem(t -> {
|
||||
|
||||
@@ -118,7 +118,8 @@ public class RCategorie {
|
||||
uni = uni.chain(__ -> treeRepository.delete("category = ?1", cat.getId()))
|
||||
.chain(__ -> matchRepository.delete("category = ?1 AND category_ord = -42", cat));
|
||||
}
|
||||
return uni;
|
||||
Uni<Long> finalUni = uni;
|
||||
return Panache.withTransaction(() -> finalUni);
|
||||
})
|
||||
.call(cat -> SSCategorie.sendCategory(connection, cat))
|
||||
.replaceWithVoid();
|
||||
@@ -205,6 +206,16 @@ public class RCategorie {
|
||||
.replaceWithVoid();
|
||||
}
|
||||
|
||||
@WSReceiver(code = "deleteCategory", permission = PermLevel.ADMIN)
|
||||
public Uni<Void> deleteCategory(WebSocketConnection connection, Long id) {
|
||||
return getById(id, connection)
|
||||
.call(cat -> Panache.withTransaction(() -> treeRepository.delete("category = ?1", cat.getId())
|
||||
.call(__ -> matchRepository.delete("category = ?1", cat))))
|
||||
.chain(cat -> Panache.withTransaction(() -> categoryRepository.delete(cat)))
|
||||
.call(__ -> SSCategorie.sendDelCategory(connection, id))
|
||||
.replaceWithVoid();
|
||||
}
|
||||
|
||||
@RegisterForReflection
|
||||
public record JustCategorie(long id, String name, int type, String liceName) {
|
||||
public static JustCategorie from(CategoryModel m) {
|
||||
|
||||
@@ -292,9 +292,10 @@ public class RMatch {
|
||||
if (!o.getCompet().getUuid().equals(connection.pathParam("uuid")))
|
||||
throw new DForbiddenException("Permission denied");
|
||||
}))
|
||||
.call(cm -> matchRepository.delete("id IN ?1 AND category = ?2", data.matchesToRemove, cm)
|
||||
.call(cm -> Panache.withTransaction(
|
||||
() -> matchRepository.delete("id IN ?1 AND category = ?2", data.matchesToRemove, cm))
|
||||
.call(__ -> SSMatch.sendDeleteMatch(connection, data.matchesToRemove)))
|
||||
.call(cm -> matchRepository.list("id IN ?1 AND category = ?2",
|
||||
.call(cm -> Panache.withSession(() -> matchRepository.list("id IN ?1 AND category = ?2",
|
||||
Stream.concat(data.matchOrderToUpdate.keySet().stream(),
|
||||
data.matchPouleToUpdate.keySet().stream())
|
||||
.distinct().toList(), cm)
|
||||
@@ -305,13 +306,14 @@ public class RMatch {
|
||||
model.setCategory_ord(data.matchOrderToUpdate.get(model.getId()));
|
||||
}))
|
||||
.call(mm -> Panache.withTransaction(() -> matchRepository.persist(mm)))
|
||||
.invoke(mm -> matches.addAll(mm.stream().map(MatchEntity::fromModel).toList()))
|
||||
.invoke(mm -> matches.addAll(mm.stream().map(MatchEntity::fromModel).toList())))
|
||||
)
|
||||
.chain(categoryModel -> {
|
||||
Uni<List<MatchModel>> uni = Uni.createFrom().item(new ArrayList<>());
|
||||
for (AddMatch match : data.newMatch)
|
||||
uni = uni.call(l -> creatMatch(categoryModel, match).invoke(l::add));
|
||||
return uni;
|
||||
Uni<List<MatchModel>> finalUni = uni;
|
||||
return Panache.withSession(() -> finalUni);
|
||||
}
|
||||
)
|
||||
.chain(mm -> Panache.withTransaction(() -> matchRepository.create(mm))
|
||||
|
||||
@@ -30,4 +30,8 @@ public class SSCategorie {
|
||||
public static Uni<Void> sendTreeCategory(WebSocketConnection connection, List<TreeEntity> treeEntities) {
|
||||
return CompetitionWS.sendNotifyToOtherEditor(connection, "sendTreeCategory", treeEntities);
|
||||
}
|
||||
|
||||
public static Uni<?> sendDelCategory(WebSocketConnection connection, Long id) {
|
||||
return CompetitionWS.sendNotifyToOtherEditor(connection, "sendDelCategory", id);
|
||||
}
|
||||
}
|
||||
|
||||
BIN
src/main/webapp/public/Logo-FFSAF-2023.png
Normal file
BIN
src/main/webapp/public/Logo-FFSAF-2023.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 155 KiB |
@@ -14,6 +14,7 @@ import {ClubRoot, getClubChildren} from "./pages/club/ClubRoot.jsx";
|
||||
import {DemandeAff, DemandeAffOk} from "./pages/DemandeAff.jsx";
|
||||
import {MePage} from "./pages/MePage.jsx";
|
||||
import {CompetitionRoot, getCompetitionChildren} from "./pages/competition/CompetitionRoot.jsx";
|
||||
import {FallingLines} from "react-loader-spinner";
|
||||
import {getResultChildren, ResultRoot} from "./pages/result/ResultRoot.jsx";
|
||||
|
||||
const router = createBrowserRouter([
|
||||
|
||||
@@ -119,13 +119,13 @@ export function CheckField({name, text, value, row = false}) {
|
||||
</>
|
||||
}
|
||||
|
||||
export const Checkbox = ({label, value, onChange}) => {
|
||||
export const Checkbox = ({label, value, onChange, name = "checkbox1"}) => {
|
||||
const handleChange = () => {
|
||||
onChange(!value);
|
||||
};
|
||||
|
||||
return <div className="form-check">
|
||||
<input className="form-check-input" type="checkbox" id="checkbox1" checked={value} onChange={handleChange}/>
|
||||
<label className="form-check-label" htmlFor="checkbox1">{label}</label>
|
||||
<input className="form-check-input" type="checkbox" id={name} checked={value} onChange={handleChange}/>
|
||||
<label className="form-check-label" htmlFor={name}>{label}</label>
|
||||
</div>
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@ import './Nav.css'
|
||||
import {NavLink} from "react-router-dom";
|
||||
import {useAuth} from "../hooks/useAuth.jsx";
|
||||
import {login, logout} from "../utils/auth.js";
|
||||
import {isClubAdmin} from "../utils/Tools.js";
|
||||
|
||||
export function Nav() {
|
||||
|
||||
@@ -55,7 +56,8 @@ function CompMenu() {
|
||||
<ul className="dropdown-menu">
|
||||
<li className="nav-item"><NavLink className="nav-link" to="/competition">Inscription</NavLink></li>
|
||||
<li className="nav-item"><NavLink className="nav-link" to="/result">Mes résultats</NavLink></li>
|
||||
<li className="nav-item"><NavLink className="nav-link" to="/competition-manager">Compétitions Manager</NavLink></li>
|
||||
<li className="nav-item"><NavLink className="nav-link" to="/competition-manager">Compétitions
|
||||
Manager</NavLink></li>
|
||||
</ul>
|
||||
</li>
|
||||
}
|
||||
@@ -63,8 +65,7 @@ function CompMenu() {
|
||||
function ClubMenu() {
|
||||
const {is_authenticated, userinfo} = useAuth()
|
||||
|
||||
if (!is_authenticated || !(userinfo?.roles?.includes("club_president")
|
||||
|| userinfo?.roles?.includes("club_secretaire") || userinfo?.roles?.includes("club_respo_intra")))
|
||||
if (!is_authenticated || !(isClubAdmin(userinfo)))
|
||||
return <></>
|
||||
|
||||
return <li className="nav-item dropdown">
|
||||
|
||||
@@ -299,7 +299,7 @@ function AssoInfo({initData, needFile}) {
|
||||
|
||||
<div className="mb-3">
|
||||
<div className="input-group">
|
||||
<label className="input-group-text" htmlFor="status">Statue{needFile && "*"}</label>
|
||||
<label className="input-group-text" htmlFor="status">Statuts{needFile && "*"}</label>
|
||||
<input type="file" className="form-control" id="status" name="status" accept=".pdf,.txt" required={needFile}/>
|
||||
</div>
|
||||
{!needFile && <div className="form-text" id="status">Laissez vide pour ne rien changer. (Si un statu a déjà été envoyé lors de cette
|
||||
@@ -313,7 +313,7 @@ function MembreInfo({role, initData}) {
|
||||
|
||||
return <>
|
||||
<div className="input-group mb-3">
|
||||
<label className="input-group-text" htmlFor="inputGroupSelect01">Rôles</label>
|
||||
<label className="input-group-text" htmlFor="inputGroupSelect01">Rôle</label>
|
||||
<select className="form-select" id="inputGroupSelect01" defaultValue={initData.role ? initData.role : (role === "m1" ? "PRESIDENT" : 0)}
|
||||
disabled={initData.role ? initData.role === "PRESIDENT" : role === "m1"} name={role + "_role"} required>
|
||||
<option value="0">Sélectionner...</option>
|
||||
|
||||
@@ -35,6 +35,7 @@ export function MemberList({source}) {
|
||||
payment: 2,
|
||||
order: "",
|
||||
categorie: "",
|
||||
archived: false,
|
||||
...JSON.parse(decodeURI(hash.substring(1)) || "{}"),
|
||||
}
|
||||
|
||||
@@ -43,10 +44,10 @@ export function MemberList({source}) {
|
||||
data,
|
||||
error,
|
||||
refresh
|
||||
} = useFetch(`/member/find/${source}?page=${filter.page}&search=${filter.search}&club=${filter.club}&licenceRequest=${filter.licenceRequest}&payment=${filter.payment}&order=${filter.order}&categorie=${filter.categorie}`, setLoading, 1)
|
||||
} = useFetch(`/member/find/${source}?page=${filter.page}&search=${filter.search}&club=${filter.club}&licenceRequest=${filter.licenceRequest}&payment=${filter.payment}&order=${filter.order}&categorie=${filter.categorie}&archive=${filter.archived}`, setLoading, 1)
|
||||
|
||||
useEffect(() => {
|
||||
const tmp = `/member/find/${source}?page=${filter.page}&search=${filter.search}&club=${filter.club}&licenceRequest=${filter.licenceRequest}&payment=${filter.payment}&order=${filter.order}&categorie=${filter.categorie}`;
|
||||
const tmp = `/member/find/${source}?page=${filter.page}&search=${filter.search}&club=${filter.club}&licenceRequest=${filter.licenceRequest}&payment=${filter.payment}&order=${filter.order}&categorie=${filter.categorie}&archive=${filter.archived}`;
|
||||
if (tmp === lastRefresh)
|
||||
return;
|
||||
lastRefresh = tmp
|
||||
@@ -137,6 +138,8 @@ export function MemberList({source}) {
|
||||
<div className="card-body">
|
||||
<FiltreBar showLicenceState={showLicenceState}
|
||||
setShowLicenceState={setShowLicenceState}
|
||||
showArchived={filter.archived}
|
||||
setShowArchived={e => setFilter({...filter, archived: e})}
|
||||
clubFilter={filter.club}
|
||||
setClubFilter={e => setFilter({...filter, club: e})}
|
||||
source={source}
|
||||
@@ -523,6 +526,8 @@ function OrderBar({onOrderChange, defaultValues = "", source}) {
|
||||
function FiltreBar({
|
||||
showLicenceState,
|
||||
setShowLicenceState,
|
||||
showArchived,
|
||||
setShowArchived,
|
||||
clubFilter,
|
||||
setClubFilter,
|
||||
source,
|
||||
@@ -538,6 +543,9 @@ function FiltreBar({
|
||||
<div className="mb-3">
|
||||
<Checkbox value={showLicenceState} onChange={setShowLicenceState} label="Afficher l'état des licences"/>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<Checkbox value={showArchived} onChange={setShowArchived} name="checkbox2" label="Afficher les combattants inactifs"/>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<select className="form-select" value={catFilter} onChange={event => setCatFilter(event.target.value)}>
|
||||
<option value="">--- toute les catégories ---</option>
|
||||
|
||||
@@ -4,13 +4,14 @@ import {AxiosError} from "../components/AxiosError.jsx";
|
||||
import {ThreeDots} from "react-loader-spinner";
|
||||
import {useEffect, useRef, useState} from "react";
|
||||
import {useLocation, useNavigate} from "react-router-dom";
|
||||
import {apiAxios, errFormater} from "../utils/Tools.js";
|
||||
import {apiAxios, errFormater, getCatName} from "../utils/Tools.js";
|
||||
import {toast} from "react-toastify";
|
||||
import {SearchBar} from "../components/SearchBar.jsx";
|
||||
import {ConfirmDialog} from "../components/ConfirmDialog.jsx";
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
import {faCircleInfo, faEuroSign} from "@fortawesome/free-solid-svg-icons";
|
||||
import "./PayAndValidateList.css";
|
||||
import * as Tools from "../utils/Tools.js";
|
||||
|
||||
export function PayAndValidateList({source}) {
|
||||
|
||||
@@ -22,6 +23,7 @@ export function PayAndValidateList({source}) {
|
||||
const [memberData, setMemberData] = useState([]);
|
||||
const [licenceData, setLicenceData] = useState([]);
|
||||
const [clubFilter, setClubFilter] = useState("");
|
||||
const [catFilter, setCatFilter] = useState("");
|
||||
const [stateFilter, setStateFilter] = useState((source === "club") ? 1 : 2)
|
||||
const [lastSearch, setLastSearch] = useState("");
|
||||
const [paymentFilter, setPaymentFilter] = useState((source === "club") ? 0 : 2);
|
||||
@@ -34,15 +36,15 @@ export function PayAndValidateList({source}) {
|
||||
data,
|
||||
error,
|
||||
refresh
|
||||
} = useFetch(`/member/find/${source}?page=${page}&licenceRequest=${stateFilter}&payment=${paymentFilter}`, setLoading, 1)
|
||||
} = useFetch(`/member/find/${source}?page=${page}&licenceRequest=${stateFilter}&payment=${paymentFilter}&categorie=${catFilter}`, setLoading, 1)
|
||||
|
||||
useEffect(() => {
|
||||
sessionStorage.setItem("selectedMembers", JSON.stringify(selectedMembers));
|
||||
}, [selectedMembers]);
|
||||
|
||||
useEffect(() => {
|
||||
refresh(`/member/find/${source}?page=${page}&search=${lastSearch}&club=${clubFilter}&licenceRequest=${stateFilter}&payment=${paymentFilter}`);
|
||||
}, [hash, clubFilter, stateFilter, lastSearch, paymentFilter]);
|
||||
refresh(`/member/find/${source}?page=${page}&search=${lastSearch}&club=${clubFilter}&licenceRequest=${stateFilter}&payment=${paymentFilter}&categorie=${catFilter}`);
|
||||
}, [hash, clubFilter, stateFilter, lastSearch, paymentFilter, catFilter]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!data)
|
||||
@@ -62,7 +64,7 @@ export function PayAndValidateList({source}) {
|
||||
setMemberData(data2);
|
||||
}, [data, licenceData]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchLicenceData = () => {
|
||||
toast.promise(
|
||||
apiAxios.get(`/licence/current/${source}`),
|
||||
{
|
||||
@@ -77,6 +79,10 @@ export function PayAndValidateList({source}) {
|
||||
.then(data => {
|
||||
setLicenceData(data.data);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchLicenceData();
|
||||
}, []);
|
||||
|
||||
const search = (search) => {
|
||||
@@ -104,7 +110,31 @@ export function PayAndValidateList({source}) {
|
||||
}
|
||||
).then(() => {
|
||||
setSelectedMembers([]);
|
||||
refresh(`/member/find/${source}?page=${page}&search=${lastSearch}&club=${clubFilter}&licenceRequest=${stateFilter}&payment=${paymentFilter}`);
|
||||
refresh(`/member/find/${source}?page=${page}&search=${lastSearch}&club=${clubFilter}&licenceRequest=${stateFilter}&payment=${paymentFilter}&categorie=${catFilter}`);
|
||||
});
|
||||
}
|
||||
|
||||
const handlePayment = () => {
|
||||
if (selectedMembers.length === 0) {
|
||||
toast.error("Aucun membre sélectionné");
|
||||
return;
|
||||
}
|
||||
|
||||
toast.promise(
|
||||
apiAxios.post(`/licence/validate-pay`, selectedMembers),
|
||||
{
|
||||
pending: "Validation des licences en cours...",
|
||||
success: "Licences validées avec succès 🎉",
|
||||
error: {
|
||||
render({data}) {
|
||||
return errFormater(data, "Échec de la validation des licences")
|
||||
}
|
||||
}
|
||||
}
|
||||
).then(() => {
|
||||
setSelectedMembers([]);
|
||||
fetchLicenceData();
|
||||
refresh(`/member/find/${source}?page=${page}&search=${lastSearch}&club=${clubFilter}&licenceRequest=${stateFilter}&payment=${paymentFilter}&categorie=${catFilter}`);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -153,13 +183,20 @@ export function PayAndValidateList({source}) {
|
||||
<div className="card-body">
|
||||
<FiltreBar data={data} clubFilter={clubFilter} setClubFilter={setClubFilter} source={source}
|
||||
stateFilter={stateFilter} setStateFilter={setStateFilter} paymentFilter={paymentFilter}
|
||||
setPaymentFilter={setPaymentFilter}/>
|
||||
setPaymentFilter={setPaymentFilter} setCatFilter={setCatFilter} catFilter={catFilter}/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mb-4">
|
||||
{source === "admin" && <>
|
||||
<button className="btn btn-primary" data-bs-toggle="modal" data-bs-target="#confirm-validation">Valider
|
||||
<button className="btn btn-primary" data-bs-toggle="modal" data-bs-target="#confirm-pay">Valider le payement
|
||||
des {selectedMembers.length} licences sélectionnée
|
||||
</button>
|
||||
<ConfirmDialog title="Payment des licences"
|
||||
message={"Êtes-vous sûr de vouloir marquer comme payées les " + selectedMembers.length + " licences ?"}
|
||||
onConfirm={handlePayment} id="confirm-pay"/>
|
||||
|
||||
<button className="btn btn-primary" data-bs-toggle="modal" data-bs-target="#confirm-validation" style={{marginTop: "0.5em"}}>Valider
|
||||
les {selectedMembers.length} licences sélectionnée
|
||||
</button>
|
||||
<ConfirmDialog title="Validation des licences"
|
||||
@@ -385,7 +422,7 @@ function MakeRow({member, source, isChecked, onCheckboxClick, onRowClick}) {
|
||||
|
||||
let allClub = []
|
||||
|
||||
function FiltreBar({data, clubFilter, setClubFilter, source, stateFilter, setStateFilter, paymentFilter, setPaymentFilter}) {
|
||||
function FiltreBar({data, clubFilter, setClubFilter, source, stateFilter, setStateFilter, paymentFilter, setPaymentFilter, setCatFilter, catFilter}) {
|
||||
useEffect(() => {
|
||||
if (!data)
|
||||
return;
|
||||
@@ -395,6 +432,14 @@ function FiltreBar({data, clubFilter, setClubFilter, source, stateFilter, setSta
|
||||
|
||||
return <div>
|
||||
{source !== "club" && <ClubSelectFilter clubFilter={clubFilter} setClubFilter={setClubFilter}/>}
|
||||
<div className="mb-3">
|
||||
<select className="form-select" value={catFilter} onChange={event => setCatFilter(event.target.value)}>
|
||||
<option value="">--- toute les catégories ---</option>
|
||||
{Tools.CatList.map(cat => (
|
||||
<option key={cat} value={cat}>{getCatName(cat)}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<select className="form-select" value={stateFilter} onChange={event => setStateFilter(Number(event.target.value))}>
|
||||
<option value={1}>Avec demande ou licence validée</option>
|
||||
|
||||
@@ -67,10 +67,10 @@ export function InformationForm({data}) {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
}
|
||||
}).then(_ => {
|
||||
toast.success('Profile mis à jours avec succès 🎉');
|
||||
toast.success('Profil mis à jours avec succès 🎉');
|
||||
}).catch(e => {
|
||||
console.log(e.response)
|
||||
toast.error(errFormater(e,'Échec de la mise à jours du profile 😕'));
|
||||
toast.error(errFormater(e,'Échec de la mise à jours du profil 😕'));
|
||||
}).finally(() => {
|
||||
if (setLoading)
|
||||
setLoading(0)
|
||||
@@ -114,4 +114,4 @@ export function InformationForm({data}) {
|
||||
</div>
|
||||
</div>
|
||||
</form>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,8 +155,8 @@ function ModalContent2({clubData, data}) {
|
||||
aria-label="Close"></button>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
<p>Veuillez sélectionner 3 membres du bureau pour remplir la pré-demande. (Si un membre non-bureau va le devenir l'an prochain,
|
||||
vous pourrez toujours remplacer un des membres sélectionné à la prochaine étape)</p>
|
||||
<p>Veuillez sélectionner 0 à 3 membres du bureau pour remplir la pré-demande. (Si un membre non-bureau va le devenir l'an prochain,
|
||||
vous pourrez les renseigner à la prochaine étape)</p>
|
||||
{data && data.map((d, index) => {
|
||||
return <div key={index} className="input-group mb-1">
|
||||
<div className="input-group-text">
|
||||
|
||||
@@ -147,15 +147,13 @@ function SearchMember({sendRegister}) {
|
||||
|
||||
const handleAdd = (name) => {
|
||||
const member = data.find(m => `${m.fname} ${m.lname}`.trim() === name);
|
||||
console.log("Quick add licence:", member)
|
||||
|
||||
if (!member) {
|
||||
toast.error("Combattant non trouvé");
|
||||
return;
|
||||
}
|
||||
|
||||
sendRegister({
|
||||
licence: member.licence, fname: member.fname, lname: member.lname, weight: "", overCategory: 0, lockEdit: false, id: null
|
||||
licence: member.licence.trim(), fname: member.fname.trim(), lname: member.lname.trim(), weight: "", overCategory: 0, lockEdit: false, id: null
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -15,16 +15,16 @@ export function CMAdmin() {
|
||||
const categoryListener = ({data}) => {
|
||||
if (!cat || data.id !== cat.id)
|
||||
return
|
||||
setCat({
|
||||
...cat,
|
||||
setCat(cat_ => ({
|
||||
...cat_,
|
||||
name: data.name,
|
||||
liceName: data.liceName,
|
||||
type: data.type
|
||||
})
|
||||
}))
|
||||
}
|
||||
dispatch({type: 'addListener', payload: {callback: categoryListener, code: 'sendCategory'}})
|
||||
return () => dispatch({type: 'removeListener', payload: categoryListener})
|
||||
}, []);
|
||||
}, [cat]);
|
||||
|
||||
return <>
|
||||
<div className="card">
|
||||
@@ -60,15 +60,34 @@ function CategoryHeader({cat, setCatId}) {
|
||||
data
|
||||
])
|
||||
}
|
||||
const sendAddCategory = ({data}) => {
|
||||
setCats([...cats, data])
|
||||
}
|
||||
const sendDelCategory = ({data}) => {
|
||||
setCatId(catId => {
|
||||
if (catId === data) return null;
|
||||
return catId;
|
||||
})
|
||||
setCats([...cats.filter(c => c.id !== data)])
|
||||
}
|
||||
dispatch({type: 'addListener', payload: {callback: categoryListener, code: 'sendCategory'}})
|
||||
return () => dispatch({type: 'removeListener', payload: categoryListener})
|
||||
dispatch({type: 'addListener', payload: {callback: sendAddCategory, code: 'sendAddCategory'}})
|
||||
dispatch({type: 'addListener', payload: {callback: sendDelCategory, code: 'sendDelCategory'}})
|
||||
return () => {
|
||||
dispatch({type: 'removeListener', payload: categoryListener})
|
||||
dispatch({type: 'removeListener', payload: sendAddCategory})
|
||||
dispatch({type: 'removeListener', payload: sendDelCategory})
|
||||
}
|
||||
}, [cats]);
|
||||
|
||||
useEffect(() => {
|
||||
if (cats && cats.length > 0 && !cat) {
|
||||
if (cats && cats.length > 0 && !cat || (cats && !cats.find(c => c.id === cat.id))) {
|
||||
setCatId(cats.sort((a, b) => a.name.localeCompare(b.name))[0].id);
|
||||
} else if (cats && cats.length === 0) {
|
||||
setModal({});
|
||||
bthRef.current.click();
|
||||
}
|
||||
}, [cats]);
|
||||
}, [cats, cat]);
|
||||
|
||||
const handleCatChange = (e) => {
|
||||
const selectedCatId = e.target.value;
|
||||
@@ -77,7 +96,6 @@ function CategoryHeader({cat, setCatId}) {
|
||||
} else { // New category
|
||||
setModal({});
|
||||
bthRef.current.click();
|
||||
console.log(cat);
|
||||
e.target.value = cat?.id;
|
||||
}
|
||||
}
|
||||
@@ -86,7 +104,7 @@ function CategoryHeader({cat, setCatId}) {
|
||||
<div className="col-auto">
|
||||
<div className="input-group">
|
||||
<h5 style={{margin: "auto 0.5em auto 0"}}>Edition de la catégorie</h5>
|
||||
<select className="form-select" onChange={handleCatChange}>
|
||||
<select className="form-select" onChange={handleCatChange} value={cat?.id || ""}>
|
||||
{cats && cats.sort((a, b) => a.name.localeCompare(b.name)).map(c => (
|
||||
<option key={c.id} value={c.id}>{c.name}</option>))}
|
||||
{cats && <option value={-1}>Nouvelle...</option>}
|
||||
@@ -96,7 +114,7 @@ function CategoryHeader({cat, setCatId}) {
|
||||
<div className="col" style={{margin: "auto 0", textAlign: "center"}}>
|
||||
{cat &&
|
||||
<div>Type: {(cat.type & 1) !== 0 ? "Poule" : ""}{cat.type === 3 ? " & " : ""}{(cat.type & 2) !== 0 ? "Tournois" : ""} |
|
||||
Lice: {cat.liceName}</div>}
|
||||
Zone: {cat.liceName}</div>}
|
||||
</div>
|
||||
<div className="col-auto">
|
||||
<button className="btn btn-primary float-end" onClick={() => {
|
||||
@@ -126,7 +144,7 @@ function CategoryHeader({cat, setCatId}) {
|
||||
|
||||
function ModalContent({state, setCatId, setConfirm, confirmRef}) {
|
||||
const [name, setName] = useState("")
|
||||
const [lice, setLice] = useState("1")
|
||||
const [lice, setLice] = useState("A")
|
||||
const [poule, setPoule] = useState(true)
|
||||
const [tournoi, setTournoi] = useState(false)
|
||||
const [size, setSize] = useState(4)
|
||||
@@ -136,7 +154,7 @@ function ModalContent({state, setCatId, setConfirm, confirmRef}) {
|
||||
|
||||
useEffect(() => {
|
||||
setName(state.name || "");
|
||||
setLice(state.liceName || "1");
|
||||
setLice(state.liceName || "A");
|
||||
setPoule(((state.type || 1) & 1) !== 0);
|
||||
setTournoi((state.type & 2) !== 0);
|
||||
|
||||
@@ -162,7 +180,7 @@ function ModalContent({state, setCatId, setConfirm, confirmRef}) {
|
||||
|
||||
const regex = /^([^;]+;)*[^;]+$/;
|
||||
if (regex.test(lice.trim()) === false) {
|
||||
toast.error("Le format du nom des lices est invalide. Veuillez séparer les noms par des ';'.");
|
||||
toast.error("Le format du nom des zones de combat est invalide. Veuillez séparer les noms par des ';'.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -268,8 +286,6 @@ function ModalContent({state, setCatId, setConfirm, confirmRef}) {
|
||||
error: 'Erreur lors de la création de la catégorie'
|
||||
}
|
||||
).then(id => {
|
||||
setCatId(id);
|
||||
|
||||
if (tournoi) {
|
||||
const trees = build_tree(size, loserMatch)
|
||||
console.log("Creating trees for new category:", trees);
|
||||
@@ -280,7 +296,9 @@ function ModalContent({state, setCatId, setConfirm, confirmRef}) {
|
||||
success: 'Arbres créés !',
|
||||
error: 'Erreur lors de la création des arbres'
|
||||
}
|
||||
)
|
||||
).finally(() => setCatId(id))
|
||||
} else {
|
||||
setCatId(id);
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -297,7 +315,7 @@ function ModalContent({state, setCatId, setConfirm, confirmRef}) {
|
||||
|
||||
return <form onSubmit={handleSubmit}>
|
||||
<div className="modal-header">
|
||||
<h1 className="modal-title fs-5" id="CategorieModalLabel">Ajouter une catégorie</h1>
|
||||
<h1 className="modal-title fs-5" id="CategorieModalLabel">{state.id === undefined ? "Ajouter" : "Modifier"} une catégorie</h1>
|
||||
<button type="button" className="btn-close" data-bs-dismiss="modal"
|
||||
aria-label="Close"></button>
|
||||
</div>
|
||||
@@ -309,8 +327,8 @@ function ModalContent({state, setCatId, setConfirm, confirmRef}) {
|
||||
</div>
|
||||
|
||||
<div className="mb-3">
|
||||
<label htmlFor="liceInput1" className="form-label">Nom des lices <small>(séparée par des ';')</small></label>
|
||||
<input type="text" className="form-control" id="liceInput1" placeholder="1;2" name="lice" value={lice}
|
||||
<label htmlFor="liceInput1" className="form-label">Nom des zones de combat <small>(séparée par des ';')</small></label>
|
||||
<input type="text" className="form-control" id="liceInput1" placeholder="A;B" name="zone de combat" value={lice}
|
||||
onChange={e => setLice(e.target.value)}/>
|
||||
</div>
|
||||
|
||||
@@ -371,6 +389,22 @@ function ModalContent({state, setCatId, setConfirm, confirmRef}) {
|
||||
<div className="modal-footer">
|
||||
<button type="button" className="btn btn-secondary" data-bs-dismiss="modal">Fermer</button>
|
||||
<button type="submit" className="btn btn-primary" data-bs-dismiss="modal">Enregistrer</button>
|
||||
{state.id !== undefined && <button type="button" className="btn btn-danger" data-bs-dismiss="modal" onClick={() => {
|
||||
setConfirm({
|
||||
title: "Suppression de la catégorie",
|
||||
message: `Voulez-vous vraiment supprimer la catégorie ${state.name}. Cela va supprimer tous les matchs associés !`,
|
||||
confirm: () => {
|
||||
toast.promise(sendRequest('deleteCategory', state.id),
|
||||
{
|
||||
pending: 'Suppression de la catégorie...',
|
||||
success: 'Catégorie supprimée !',
|
||||
error: 'Erreur lors de la suppression de la catégorie'
|
||||
}
|
||||
).then(() => setCatId(null));
|
||||
}
|
||||
})
|
||||
confirmRef.current.click();
|
||||
}}>Supprimer</button>}
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
|
||||
@@ -27,8 +27,22 @@ export function CategorieSelect({catId, setCatId, menuActions}) {
|
||||
const categoryListener = ({data}) => {
|
||||
setCats([...cats.filter(c => c.id !== data.id), data])
|
||||
}
|
||||
const sendAddCategory = ({data}) => {
|
||||
setCats([...cats, data])
|
||||
}
|
||||
const sendDelCategory = ({data}) => {
|
||||
if (catId === data)
|
||||
setCatId(-1);
|
||||
setCats([...cats.filter(c => c.id !== data)])
|
||||
}
|
||||
dispatch({type: 'addListener', payload: {callback: categoryListener, code: 'sendCategory'}})
|
||||
return () => dispatch({type: 'removeListener', payload: categoryListener})
|
||||
dispatch({type: 'addListener', payload: {callback: sendAddCategory, code: 'sendAddCategory'}})
|
||||
dispatch({type: 'addListener', payload: {callback: sendDelCategory, code: 'sendDelCategory'}})
|
||||
return () => {
|
||||
dispatch({type: 'removeListener', payload: categoryListener})
|
||||
dispatch({type: 'removeListener', payload: sendAddCategory})
|
||||
dispatch({type: 'removeListener', payload: sendDelCategory})
|
||||
}
|
||||
}, [cats]);
|
||||
|
||||
const cat = cats?.find(c => c.id === catId);
|
||||
@@ -131,10 +145,15 @@ function ListMatch({cat, matches, trees, menuActions}) {
|
||||
const [type, setType] = useState(1);
|
||||
|
||||
useEffect(() => {
|
||||
if (!cat)
|
||||
return;
|
||||
if ((cat.type & type) === 0)
|
||||
setType(cat.type);
|
||||
}, [cat]);
|
||||
|
||||
if (!cat)
|
||||
return <></>;
|
||||
|
||||
return <div style={{marginTop: "1em"}}>
|
||||
{cat && cat.type === 3 && <>
|
||||
<ul className="nav nav-tabs">
|
||||
@@ -173,6 +192,10 @@ function MatchList({matches, cat, menuActions}) {
|
||||
.map(m => ({...m, win: win(m.scores)}))
|
||||
const firstIndex = marches2.findLastIndex(m => m.poule === '-') + 1;
|
||||
|
||||
const isActiveMatch = (index) => {
|
||||
return liceName.length === 1 || (liceName[(index - firstIndex) % liceName.length] === lice)
|
||||
}
|
||||
|
||||
const match = matches.find(m => m.id === activeMatch)
|
||||
useEffect(() => {
|
||||
if (!match) {
|
||||
@@ -183,7 +206,7 @@ function MatchList({matches, cat, menuActions}) {
|
||||
payload: {
|
||||
c1: match.c1,
|
||||
c2: match.c2,
|
||||
next: marches2.filter((m, index) => !m.end && liceName[(index - firstIndex) % liceName.length] === lice && m.id !== activeMatch).map(m => ({
|
||||
next: marches2.filter((m, index) => !m.end && isActiveMatch(index) && m.id !== activeMatch).map(m => ({
|
||||
c1: m.c1,
|
||||
c2: m.c2
|
||||
}))
|
||||
@@ -198,7 +221,7 @@ function MatchList({matches, cat, menuActions}) {
|
||||
|
||||
useEffect(() => {
|
||||
if (match && match.poule !== lice)
|
||||
setActiveMatch(marches2.find((m, index) => !m.end && liceName[(index - firstIndex) % liceName.length] === lice)?.id)
|
||||
setActiveMatch(marches2.find((m, index) => !m.end && isActiveMatch(index))?.id)
|
||||
}, [lice]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -207,12 +230,12 @@ function MatchList({matches, cat, menuActions}) {
|
||||
if (marches2.some(m => m.id === activeMatch))
|
||||
return;
|
||||
|
||||
setActiveMatch(marches2.find((m, index) => !m.end && liceName[(index - firstIndex) % liceName.length] === lice)?.id);
|
||||
setActiveMatch(marches2.find((m, index) => !m.end && isActiveMatch(index))?.id);
|
||||
}, [matches])
|
||||
return <>
|
||||
{liceName.length > 1 &&
|
||||
<div className="input-group" style={{maxWidth: "10em", marginTop: "0.5em"}}>
|
||||
<label className="input-group-text" htmlFor="selectLice">Lice</label>
|
||||
<div className="input-group" style={{maxWidth: "15em", marginTop: "0.5em"}}>
|
||||
<label className="input-group-text" htmlFor="selectLice">Zone de combat</label>
|
||||
<select className="form-select" id="selectLice" value={lice} onChange={e => {
|
||||
setLice(e.target.value);
|
||||
localStorage.setItem("cm_lice", e.target.value);
|
||||
@@ -224,11 +247,11 @@ function MatchList({matches, cat, menuActions}) {
|
||||
</div>
|
||||
}
|
||||
|
||||
<div className="table-responsive-xxl">
|
||||
<div className="table-responsive-xxl overflow-y-auto" style={{maxHeight: "50vh"}}>
|
||||
<table className="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style={{textAlign: "center", paddingLeft: "0.2em", paddingRight: "0.2em"}} scope="col">L</th>
|
||||
<th style={{textAlign: "center", paddingLeft: "0.2em", paddingRight: "0.2em"}} scope="col">Z</th>
|
||||
<th style={{textAlign: "center", paddingLeft: "0.2em", paddingRight: "0.2em"}} scope="col">P</th>
|
||||
<th style={{textAlign: "center", paddingLeft: "0.2em", paddingRight: "0.2em"}} scope="col">N°</th>
|
||||
<th style={{textAlign: "center"}} scope="col"></th>
|
||||
@@ -240,7 +263,7 @@ function MatchList({matches, cat, menuActions}) {
|
||||
<tbody className="table-group-divider">
|
||||
{marches2.map((m, index) => (
|
||||
<tr key={m.id}
|
||||
className={m.id === activeMatch ? "table-info" : (liceName[(index - firstIndex) % liceName.length] === lice ? "" : "table-warning")}
|
||||
className={m.id === activeMatch ? "table-info" : (isActiveMatch(index) ? "" : "table-warning")}
|
||||
onClick={() => setActiveMatch(m.id)}>
|
||||
<td style={{textAlign: "center", paddingLeft: "0.2em", paddingRight: "0.2em"}}>
|
||||
{liceName[(index - firstIndex) % liceName.length]}</td>
|
||||
@@ -259,7 +282,7 @@ function MatchList({matches, cat, menuActions}) {
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{activeMatch && <LoadingProvider><ScorePanel matchId={activeMatch} match={match} menuActions={menuActions}/></LoadingProvider>}
|
||||
{activeMatch && <LoadingProvider><ScorePanel matchId={activeMatch} matchs={matches} match={match} menuActions={menuActions}/></LoadingProvider>}
|
||||
</>
|
||||
}
|
||||
|
||||
@@ -325,27 +348,29 @@ function BuildTree({treeData, matches, menuActions}) {
|
||||
|
||||
|
||||
return <div>
|
||||
<div ref={scrollRef} className="overflow-x-auto" style={{position: "relative"}}>
|
||||
<DrawGraph root={trees} scrollRef={scrollRef} onMatchClick={onMatchClick} onClickVoid={onClickVoid}
|
||||
matchSelect={currentMatch?.matchSelect} matchNext={currentMatch?.matchNext} size={23}/>
|
||||
<div className="overflow-y-auto" style={{maxHeight: "50vh"}}>
|
||||
<div ref={scrollRef} className="overflow-x-auto" style={{position: "relative"}}>
|
||||
<DrawGraph root={trees} scrollRef={scrollRef} onMatchClick={onMatchClick} onClickVoid={onClickVoid}
|
||||
matchSelect={currentMatch?.matchSelect} matchNext={currentMatch?.matchNext} size={23}/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{currentMatch?.matchSelect &&
|
||||
<LoadingProvider><ScorePanel matchId={currentMatch?.matchSelect} match={match} menuActions={menuActions}/></LoadingProvider>}
|
||||
<LoadingProvider><ScorePanel matchId={currentMatch?.matchSelect} matchs={matches} match={match} menuActions={menuActions}/></LoadingProvider>}
|
||||
</div>
|
||||
}
|
||||
|
||||
function ScorePanel({matchId, match, menuActions}) {
|
||||
function ScorePanel({matchId, matchs, match, menuActions}) {
|
||||
const onClickVoid = useRef(() => {
|
||||
});
|
||||
|
||||
return <div className="row" onClick={onClickVoid.current}>
|
||||
<ScorePanel_ matchId={matchId} match={match} menuActions={menuActions} onClickVoid_={onClickVoid}/>
|
||||
<ScorePanel_ matchId={matchId} matchs={matchs} match={match} menuActions={menuActions} onClickVoid_={onClickVoid}/>
|
||||
<CardPanel matchId={matchId} match={match}/>
|
||||
</div>
|
||||
}
|
||||
|
||||
function ScorePanel_({matchId, match, menuActions, onClickVoid_}) {
|
||||
function ScorePanel_({matchId, matchs, match, menuActions, onClickVoid_}) {
|
||||
const {sendRequest} = useWS()
|
||||
const setLoading = useLoadingSwitcher()
|
||||
|
||||
@@ -355,6 +380,11 @@ function ScorePanel_({matchId, match, menuActions, onClickVoid_}) {
|
||||
const tableRef = useRef(null)
|
||||
const scoreRef = useRef([])
|
||||
const lastScoreClick = useRef(null)
|
||||
const scoreInRef = useRef(null)
|
||||
|
||||
useEffect(() => {
|
||||
scoreInRef.current = scoreIn;
|
||||
}, [scoreIn]);
|
||||
|
||||
useEffect(() => {
|
||||
menuActions.current.saveScore = (scoreRed, scoreBlue) => {
|
||||
@@ -403,9 +433,12 @@ function ScorePanel_({matchId, match, menuActions, onClickVoid_}) {
|
||||
const {matchId, round, comb} = lastScoreClick.current;
|
||||
lastScoreClick.current = null;
|
||||
|
||||
const scoreIn_ = String(scoreIn).trim() === "" ? -1000 : Number(scoreIn);
|
||||
const scoreIn_ = String(scoreInRef.current).trim() === "" ? -1000 : Number(scoreInRef.current);
|
||||
|
||||
const score = matchs.find(m => m.id === matchId).scores.find(s => s.n_round === round);
|
||||
|
||||
console.log("Updating score", matchId, round, comb, scoreIn_, score);
|
||||
|
||||
const score = match.scores.find(s => s.n_round === round);
|
||||
let newScore;
|
||||
if (score) {
|
||||
if (comb === 1)
|
||||
@@ -474,6 +507,18 @@ function ScorePanel_({matchId, match, menuActions, onClickVoid_}) {
|
||||
setEnd(match.end);
|
||||
}, [match]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event) => {
|
||||
if (inputRef.current && !inputRef.current.contains(event.target)) {
|
||||
onClickVoid();
|
||||
}
|
||||
};
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener("mousedown", handleClickOutside);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]')
|
||||
const o = [...tooltipTriggerList]
|
||||
o.map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl))
|
||||
|
||||
@@ -49,9 +49,6 @@ export function CMTable() {
|
||||
<CategorieSelect catId={catId} setCatId={setCatId} menuActions={menuActions}/>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{backgroundColor: "#c70000"}}>
|
||||
D
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Menu menuActions={menuActions}/>
|
||||
|
||||
@@ -47,10 +47,10 @@ export function CategoryContent({cat, catId, setCat}) {
|
||||
const treeListener = ({data}) => {
|
||||
if (!cat || data.length < 1 || data[0].categorie !== cat.id)
|
||||
return
|
||||
setCat({
|
||||
...cat,
|
||||
trees: data.map(d => from_sendTree(d, true))
|
||||
})
|
||||
setCat(cat_ => ({
|
||||
...cat_,
|
||||
trees: data.sort((a, b) => a.level - b.level).map(d => from_sendTree(d, true))
|
||||
}))
|
||||
|
||||
let matches2 = [];
|
||||
let combsToAdd = [];
|
||||
@@ -64,10 +64,16 @@ export function CategoryContent({cat, catId, setCat}) {
|
||||
reducer({type: 'UPDATE_OR_ADD', payload: {...data, c1: data.c1?.id, c2: data.c2?.id}});
|
||||
combDispatch({type: 'SET_ALL', payload: {source: "match", data: [data.c1, data.c2].filter(d => d != null)}});
|
||||
|
||||
if (data.c1 !== null && !groupsRef.current.some(g => g.id === data.c1?.id))
|
||||
setGroups(prev => [...prev, {id: data.c1?.id, poule: data.poule}]);
|
||||
if (data.c2 !== null && !groupsRef.current.some(g => g.id === data.c2?.id))
|
||||
setGroups(prev => [...prev, {id: data.c2?.id, poule: data.poule}]);
|
||||
setGroups(prev => {
|
||||
if (data.c1 !== null && !prev.some(g => g.id === data.c1?.id))
|
||||
return [...prev, {id: data.c1?.id, poule: data.poule}];
|
||||
return prev;
|
||||
})
|
||||
setGroups(prev => {
|
||||
if (data.c2 !== null && !prev.some(g => g.id === data.c2?.id))
|
||||
return [...prev, {id: data.c2?.id, poule: data.poule}];
|
||||
return prev;
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,7 +109,7 @@ export function CategoryContent({cat, catId, setCat}) {
|
||||
name: data.name,
|
||||
liceName: data.liceName,
|
||||
type: data.type,
|
||||
trees: data.trees.map(d => from_sendTree(d, true))
|
||||
trees: data.trees.sort((a, b) => a.level - b.level).map(d => from_sendTree(d, true))
|
||||
})
|
||||
|
||||
let matches2 = [];
|
||||
@@ -116,7 +122,7 @@ export function CategoryContent({cat, catId, setCat}) {
|
||||
|
||||
const activeMatches = matches2.filter(m => m.poule !== '-')
|
||||
|
||||
const combsIDs = activeMatches.flatMap(d => [d.c1, d.c2]).filter((v, i, a) => v != null && a.indexOf(v) === i)
|
||||
const combsIDs = matches2.flatMap(d => [d.c1, d.c2]).filter((v, i, a) => v != null && a.indexOf(v) === i)
|
||||
.map(d => {
|
||||
let poule = activeMatches.find(m => (m.c1 === d || m.c2 === d) && m.categorie_ord !== -42)?.poule
|
||||
if (!poule)
|
||||
@@ -554,7 +560,7 @@ function MatchList({matches, cat, groups, reducer}) {
|
||||
<tr>
|
||||
<th style={{textAlign: "center", paddingLeft: "0.2em", paddingRight: "0.2em"}} scope="col">N°</th>
|
||||
<th style={{textAlign: "center", paddingLeft: "0.2em", paddingRight: "0.2em"}} scope="col">Poule</th>
|
||||
<th style={{textAlign: "center", paddingLeft: "0.2em", paddingRight: "0.2em"}} scope="col">Lice</th>
|
||||
<th style={{textAlign: "center", paddingLeft: "0.2em", paddingRight: "0.2em"}} scope="col">Zone</th>
|
||||
<th style={{textAlign: "center"}} scope="col"></th>
|
||||
<th style={{textAlign: "center"}} scope="col">Rouge</th>
|
||||
<th style={{textAlign: "center"}} scope="col">Blue</th>
|
||||
|
||||
@@ -95,7 +95,7 @@ function WSStatus({setPerm}) {
|
||||
return () => dispatch({type: 'removeListener', payload: welcomeListener})
|
||||
}, [])
|
||||
|
||||
return <div className="row">
|
||||
return <div className="row" style={{marginRight: "inherit"}}>
|
||||
<h2 className="col">{name}</h2>
|
||||
<div className="col-auto" style={{margin: "auto 0", padding: 0}}>Serveur: <ColoredCircle
|
||||
color={isReady ? (inWait ? "#ffad32" : "#00c700") : "#e50000"}/>
|
||||
@@ -111,11 +111,11 @@ function Home2({perm}) {
|
||||
<h4 className="col-auto" style={{margin: "auto 0"}}>Sélectionne les modes d'affichage</h4>
|
||||
<div className="col">
|
||||
{perm === "ADMIN" && <>
|
||||
<button className="btn btn-primary" onClick={() => nav("table")}>Table de marque</button>
|
||||
<button className="btn btn-primary" onClick={() => nav("table")}>Secrétariats de lice</button>
|
||||
<button className="btn btn-primary ms-3" onClick={() => nav("admin")}>Administration</button>
|
||||
</>}
|
||||
{perm === "TABLE" && <>
|
||||
<button className="btn btn-primary" onClick={() => nav("table")}>Table de marque</button>
|
||||
<button className="btn btn-primary" onClick={() => nav("table")}>Secrétariats de lice</button>
|
||||
</>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user