Compare commits
63 Commits
89d9e04a6f
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| dcaa79245c | |||
| 2772e44456 | |||
| 8f0e3c0738 | |||
| 81f6169d41 | |||
| 931c5076b7 | |||
| a328e71035 | |||
| 72f8a27272 | |||
| d3805ea0e7 | |||
| a1e5006e95 | |||
| 2d883ca1da | |||
| d357328e48 | |||
| 2501b998fa | |||
| f9e4b83ad3 | |||
| a7c89dbd73 | |||
| ae087084e9 | |||
| b2ee06d3b9 | |||
| 5afef2bcab | |||
| 3ed44bc044 | |||
| 55d39e02d2 | |||
| d091503f19 | |||
| d5c1273e18 | |||
| ea17989a82 | |||
| eda96d5ed2 | |||
| e8555f7992 | |||
| 3a4e2f160f | |||
| fdd42c6a63 | |||
| 2b84854ef2 | |||
| ddf530ce4c | |||
| fc3bea7f76 | |||
| 286911cf89 | |||
| e67e01640a | |||
| 4bfa6e845d | |||
| e870ffdda0 | |||
| 806ead5b98 | |||
| 378a17ba75 | |||
| ee0a7d87e9 | |||
| 812d873d5d | |||
| d6d9f86254 | |||
| a22225ee7a | |||
| 3cb12826d0 | |||
| 2f390b03e2 | |||
| ed5d73c25f | |||
| 0a368454c4 | |||
| 752f03cba5 | |||
| d857fce71f | |||
| 2fd09af0ea | |||
| d43cdc1a4e | |||
| 8663aa61cf | |||
| 758e02dc5b | |||
| 72b248d09d | |||
| 31315c951a | |||
| ab4d57985c | |||
| 2e846bf801 | |||
| 9954dd002c | |||
| 7dab5b8880 | |||
| b37902466c | |||
| 34a6911fa0 | |||
| 59ba31ae2d | |||
| 4b56aa0209 | |||
| eefa77369a | |||
| a942798a6c | |||
| 876545630a | |||
| cad6d14ba8 |
@@ -44,7 +44,9 @@ public class FrontendForwardingFilter implements ContainerResponseFilter {
|
|||||||
final String path = info.getPath();
|
final String path = info.getPath();
|
||||||
final String address = request.remoteAddress().toString();
|
final String address = request.remoteAddress().toString();
|
||||||
|
|
||||||
|
if (!path.equals("/api")) {
|
||||||
LOG.infof("Request %s %s from IP %s", method, path, address);
|
LOG.infof("Request %s %s from IP %s", method, path, address);
|
||||||
|
}
|
||||||
|
|
||||||
int status = responseContext.getStatus();
|
int status = responseContext.getStatus();
|
||||||
if (status != 404 && !(status == 405 && "GET".equals(requestContext.getMethod()))) {
|
if (status != 404 && !(status == 405 && "GET".equals(requestContext.getMethod()))) {
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
package fr.titionfire.ffsaf.data.model;
|
package fr.titionfire.ffsaf.data.model;
|
||||||
|
|
||||||
|
import fr.titionfire.ffsaf.utils.Categorie;
|
||||||
import fr.titionfire.ffsaf.utils.ResultPrivacy;
|
import fr.titionfire.ffsaf.utils.ResultPrivacy;
|
||||||
|
|
||||||
public interface CombModel {
|
public interface CombModel {
|
||||||
Long getCombId();
|
Long getCombId();
|
||||||
String getName();
|
String getName();
|
||||||
String getName(MembreModel model, ResultPrivacy privacy);
|
String getName(MembreModel model, ResultPrivacy privacy);
|
||||||
|
Categorie getCategorie();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,8 +43,8 @@ public class CompetitionGuestModel implements CombModel {
|
|||||||
|
|
||||||
String country = "fr";
|
String country = "fr";
|
||||||
|
|
||||||
Integer weight = null;
|
Float weight = null;
|
||||||
Integer weightReal = null;
|
Float weightReal = null;
|
||||||
|
|
||||||
@ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST)
|
@ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST)
|
||||||
@JoinTable(
|
@JoinTable(
|
||||||
@@ -109,7 +109,7 @@ public class CompetitionGuestModel implements CombModel {
|
|||||||
return Stream.concat(comb.stream(), guest.stream()).anyMatch(c -> Objects.equals(c, comb_));
|
return Stream.concat(comb.stream(), guest.stream()).anyMatch(c -> Objects.equals(c, comb_));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Integer getWeight2() {
|
public Float getWeight2() {
|
||||||
return (this.weightReal != null) ? this.weightReal : this.weight;
|
return (this.weightReal != null) ? this.weightReal : this.weight;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,8 +37,8 @@ public class RegisterModel {
|
|||||||
@JoinColumn(name = "id_membre")
|
@JoinColumn(name = "id_membre")
|
||||||
MembreModel membre;
|
MembreModel membre;
|
||||||
|
|
||||||
Integer weight;
|
Float weight;
|
||||||
Integer weightReal;
|
Float weightReal;
|
||||||
int overCategory = 0;
|
int overCategory = 0;
|
||||||
Categorie categorie;
|
Categorie categorie;
|
||||||
|
|
||||||
@@ -61,7 +61,7 @@ public class RegisterModel {
|
|||||||
)
|
)
|
||||||
List<CatPresetModel> categoriesInscrites = new ArrayList<>();
|
List<CatPresetModel> categoriesInscrites = new ArrayList<>();
|
||||||
|
|
||||||
public RegisterModel(CompetitionModel competition, MembreModel membre, Integer weight, int overCategory,
|
public RegisterModel(CompetitionModel competition, MembreModel membre, Float weight, int overCategory,
|
||||||
Categorie categorie, ClubModel club) {
|
Categorie categorie, ClubModel club) {
|
||||||
this.id = new RegisterId(competition.getId(), membre.getId());
|
this.id = new RegisterId(competition.getId(), membre.getId());
|
||||||
this.competition = competition;
|
this.competition = competition;
|
||||||
@@ -91,7 +91,7 @@ public class RegisterModel {
|
|||||||
return Categorie.values()[Math.min(tmp.ordinal() + this.overCategory, Categorie.values().length - 1)];
|
return Categorie.values()[Math.min(tmp.ordinal() + this.overCategory, Categorie.values().length - 1)];
|
||||||
}
|
}
|
||||||
|
|
||||||
public Integer getWeight2() {
|
public Float getWeight2() {
|
||||||
if (weightReal != null)
|
if (weightReal != null)
|
||||||
return weightReal;
|
return weightReal;
|
||||||
return weight;
|
return weight;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package fr.titionfire.ffsaf.domain.entity;
|
package fr.titionfire.ffsaf.domain.entity;
|
||||||
|
|
||||||
|
import fr.titionfire.ffsaf.data.model.CatPresetModel;
|
||||||
import fr.titionfire.ffsaf.data.model.CompetitionGuestModel;
|
import fr.titionfire.ffsaf.data.model.CompetitionGuestModel;
|
||||||
import fr.titionfire.ffsaf.data.model.MembreModel;
|
import fr.titionfire.ffsaf.data.model.MembreModel;
|
||||||
import fr.titionfire.ffsaf.data.model.RegisterModel;
|
import fr.titionfire.ffsaf.data.model.RegisterModel;
|
||||||
@@ -26,8 +27,9 @@ public class CombEntity {
|
|||||||
Genre genre;
|
Genre genre;
|
||||||
String country;
|
String country;
|
||||||
int overCategory;
|
int overCategory;
|
||||||
Integer weight;
|
Float weight;
|
||||||
List<CombEntity> teamMembers;
|
List<CombEntity> teamMembers;
|
||||||
|
List<Long> categoriesInscrites;
|
||||||
|
|
||||||
public static CombEntity fromModel(MembreModel model) {
|
public static CombEntity fromModel(MembreModel model) {
|
||||||
if (model == null)
|
if (model == null)
|
||||||
@@ -36,7 +38,7 @@ public class CombEntity {
|
|||||||
return new CombEntity(model.getId(), model.getLname(), model.getFname(), model.getCategorie(),
|
return new CombEntity(model.getId(), model.getLname(), model.getFname(), model.getCategorie(),
|
||||||
model.getClub() == null ? null : model.getClub().getClubId(),
|
model.getClub() == null ? null : model.getClub().getClubId(),
|
||||||
model.getClub() == null ? "Sans club" : model.getClub().getName(), model.getGenre(), model.getCountry(),
|
model.getClub() == null ? "Sans club" : model.getClub().getName(), model.getGenre(), model.getCountry(),
|
||||||
0, null, new ArrayList<>());
|
0, null, new ArrayList<>(), new ArrayList<>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -45,9 +47,18 @@ public class CombEntity {
|
|||||||
return null;
|
return null;
|
||||||
|
|
||||||
return new CombEntity(model.getId() * -1, model.getLname(), model.getFname(), model.getCategorie(), null,
|
return new CombEntity(model.getId() * -1, model.getLname(), model.getFname(), model.getCategorie(), null,
|
||||||
model.getClub(), model.getGenre(), model.getCountry(), 0, model.getWeight(),
|
model.getClub(), model.getGenre(), model.getCountry(), 0,
|
||||||
|
model.getWeight2() != null ? model.getWeight2() : model.getWeight(),
|
||||||
Stream.concat(model.getComb().stream().map(CombEntity::fromModel),
|
Stream.concat(model.getComb().stream().map(CombEntity::fromModel),
|
||||||
model.getGuest().stream().map(CombEntity::fromModel)).toList());
|
model.getGuest().stream().map(CombEntity::fromModel)).toList(),
|
||||||
|
new ArrayList<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public CombEntity addCategoriesInscrites(List<CatPresetModel> categoriesInscrites) {
|
||||||
|
if (categoriesInscrites == null)
|
||||||
|
return this;
|
||||||
|
this.categoriesInscrites = categoriesInscrites.stream().map(CatPresetModel::getId).toList();
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CombEntity fromModel(RegisterModel registerModel) {
|
public static CombEntity fromModel(RegisterModel registerModel) {
|
||||||
@@ -58,6 +69,8 @@ public class CombEntity {
|
|||||||
return new CombEntity(model.getId(), model.getLname(), model.getFname(), registerModel.getCategorie(),
|
return new CombEntity(model.getId(), model.getLname(), model.getFname(), registerModel.getCategorie(),
|
||||||
registerModel.getClub2() == null ? null : registerModel.getClub2().getClubId(),
|
registerModel.getClub2() == null ? null : registerModel.getClub2().getClubId(),
|
||||||
registerModel.getClub2() == null ? "Sans club" : registerModel.getClub2().getName(), model.getGenre(),
|
registerModel.getClub2() == null ? "Sans club" : registerModel.getClub2().getName(), model.getGenre(),
|
||||||
model.getCountry(), registerModel.getOverCategory(), registerModel.getWeight(), new ArrayList<>());
|
model.getCountry(), registerModel.getOverCategory(),
|
||||||
|
registerModel.getWeight2() != null ? registerModel.getWeight2() : registerModel.getWeight(),
|
||||||
|
new ArrayList<>(), new ArrayList<>());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,6 +94,38 @@ public class CardService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Uni<List<CardModel>> addTeamCartToNewComb(Long combId, String teamUuid, String teamName,
|
||||||
|
CompetitionModel competition) {
|
||||||
|
return clubCardRepository.list("competition = ?1 AND (teamUuid = ?2 OR teamName = ?3)",
|
||||||
|
Sort.ascending("type"), competition.getId(), teamUuid, teamName)
|
||||||
|
.chain(clubCards -> {
|
||||||
|
Uni<?> queue = Uni.createFrom().voidItem();
|
||||||
|
List<CardModel> addCards = new ArrayList<>();
|
||||||
|
for (ClubCardModel clubCard : clubCards) {
|
||||||
|
CardModel model = new CardModel();
|
||||||
|
model.setCompetition(competition);
|
||||||
|
model.setCompetitionId(competition.getId());
|
||||||
|
model.setComb(combId);
|
||||||
|
model.setTeamCard(true);
|
||||||
|
model.setType(clubCard.getType());
|
||||||
|
model.setDate(clubCard.getDate());
|
||||||
|
|
||||||
|
queue = queue.call(__ -> Panache.withTransaction(() -> cardRepository.persist(model))
|
||||||
|
.invoke(addCards::add)
|
||||||
|
.call(() -> {
|
||||||
|
clubCard.getCardIds().add(model.getId());
|
||||||
|
return Panache.withTransaction(() -> clubCardRepository.persist(clubCard));
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
return queue.replaceWith(addCards);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public Uni<Void> rmTeamCardFromComb(Long combId, String uuid) {
|
||||||
|
return cardRepository.delete("comb = ?1 AND competition.uuid = ?2 AND teamCard = True", combId, uuid)
|
||||||
|
.replaceWithVoid();
|
||||||
|
}
|
||||||
|
|
||||||
public Uni<List<CardModel>> addTeamCard(CompetitionModel competition, String teamUuid, String teamName,
|
public Uni<List<CardModel>> addTeamCard(CompetitionModel competition, String teamUuid, String teamName,
|
||||||
CardModel.CardType type, String reason) {
|
CardModel.CardType type, String reason) {
|
||||||
return clubCardRepository.find("competition = ?1 AND (teamUuid = ?2 OR teamName = ?3)",
|
return clubCardRepository.find("competition = ?1 AND (teamUuid = ?2 OR teamName = ?3)",
|
||||||
|
|||||||
@@ -368,6 +368,9 @@ public class CompetitionService {
|
|||||||
return Panache.withTransaction(() -> repository.persist(c));
|
return Panache.withTransaction(() -> repository.persist(c));
|
||||||
})
|
})
|
||||||
.chain(combModel -> updateRegister(data, c, combModel, true, false)))
|
.chain(combModel -> updateRegister(data, c, combModel, true, false)))
|
||||||
|
.call(r -> r.getCompetition().getSystem() == CompetitionSystem.INTERNAL ?
|
||||||
|
sRegister.sendRegisterNoFetch(r.getCompetition().getUuid(), r) : Uni.createFrom()
|
||||||
|
.voidItem())
|
||||||
.map(r -> SimpleRegisterComb.fromModel(r, r.getMembre().getLicences())
|
.map(r -> SimpleRegisterComb.fromModel(r, r.getMembre().getLicences())
|
||||||
.setCategorieInscrite(r.getCategoriesInscrites()));
|
.setCategorieInscrite(r.getCategoriesInscrites()));
|
||||||
} else {
|
} else {
|
||||||
@@ -406,8 +409,8 @@ public class CompetitionService {
|
|||||||
}))
|
}))
|
||||||
.chain(model -> Panache.withTransaction(() -> competitionGuestRepository.persist(model))
|
.chain(model -> Panache.withTransaction(() -> competitionGuestRepository.persist(model))
|
||||||
.call(r -> model.getCompetition().getSystem() == CompetitionSystem.INTERNAL ?
|
.call(r -> model.getCompetition().getSystem() == CompetitionSystem.INTERNAL ?
|
||||||
sRegister.sendRegister(model.getCompetition().getUuid(),
|
sRegister.sendRegisterNoFetch(model.getCompetition().getUuid(), r)
|
||||||
r) : Uni.createFrom().voidItem()))
|
: Uni.createFrom().voidItem()))
|
||||||
.map(g -> SimpleRegisterComb.fromModel(g).setCategorieInscrite(g.getCategoriesInscrites()));
|
.map(g -> SimpleRegisterComb.fromModel(g).setCategorieInscrite(g.getCategoriesInscrites()));
|
||||||
}
|
}
|
||||||
if ("club".equals(source))
|
if ("club".equals(source))
|
||||||
@@ -428,6 +431,9 @@ public class CompetitionService {
|
|||||||
throw new DForbiddenException(trad.t("insc.err1"));
|
throw new DForbiddenException(trad.t("insc.err1"));
|
||||||
}))
|
}))
|
||||||
.chain(combModel -> updateRegister(data, c, combModel, false, false)))
|
.chain(combModel -> updateRegister(data, c, combModel, false, false)))
|
||||||
|
.call(r -> r.getCompetition().getSystem() == CompetitionSystem.INTERNAL ?
|
||||||
|
sRegister.sendRegisterNoFetch(r.getCompetition().getUuid(), r) : Uni.createFrom()
|
||||||
|
.voidItem())
|
||||||
.map(r -> SimpleRegisterComb.fromModel(r, r.getMembre().getLicences())
|
.map(r -> SimpleRegisterComb.fromModel(r, r.getMembre().getLicences())
|
||||||
.setCategorieInscrite(r.getCategoriesInscrites()));
|
.setCategorieInscrite(r.getCategoriesInscrites()));
|
||||||
|
|
||||||
@@ -444,6 +450,8 @@ public class CompetitionService {
|
|||||||
throw new DForbiddenException(trad.t("insc.err2"));
|
throw new DForbiddenException(trad.t("insc.err2"));
|
||||||
}))
|
}))
|
||||||
.chain(combModel -> updateRegister(data, c, combModel, false, false)))
|
.chain(combModel -> updateRegister(data, c, combModel, false, false)))
|
||||||
|
.call(r -> r.getCompetition().getSystem() == CompetitionSystem.INTERNAL ?
|
||||||
|
sRegister.sendRegisterNoFetch(r.getCompetition().getUuid(), r) : Uni.createFrom().voidItem())
|
||||||
.map(r -> SimpleRegisterComb.fromModel(r, List.of()).setCategorieInscrite(r.getCategoriesInscrites()));
|
.map(r -> SimpleRegisterComb.fromModel(r, List.of()).setCategorieInscrite(r.getCategoriesInscrites()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -453,20 +461,21 @@ public class CompetitionService {
|
|||||||
if (!"admin".equals(source))
|
if (!"admin".equals(source))
|
||||||
return Uni.createFrom().failure(new DForbiddenException());
|
return Uni.createFrom().failure(new DForbiddenException());
|
||||||
|
|
||||||
return Multi.createFrom().iterable(datas).onItem().transformToUni(data ->
|
return permService.hasEditPerm(securityCtx, id)
|
||||||
makeImportUpdate(securityCtx, id, data).onFailure().recoverWithItem(t -> {
|
.chain(cm -> Multi.createFrom().iterable(datas).onItem().transformToUni(data ->
|
||||||
|
makeImportUpdate(cm, data).onFailure().recoverWithItem(t -> {
|
||||||
SimpleRegisterComb errorComb = new SimpleRegisterComb();
|
SimpleRegisterComb errorComb = new SimpleRegisterComb();
|
||||||
errorComb.setLicence(-42);
|
errorComb.setLicence(-42);
|
||||||
errorComb.setFname("ERROR");
|
errorComb.setFname("ERROR");
|
||||||
errorComb.setLname(t.getMessage());
|
errorComb.setLname(t.getMessage());
|
||||||
return errorComb;
|
return errorComb;
|
||||||
})).concatenate().collect().asList();
|
})).concatenate().collect().asList());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Uni<SimpleRegisterComb> makeImportUpdate(SecurityCtx securityCtx, Long id, RegisterRequestData data) {
|
@WithSession
|
||||||
|
public Uni<SimpleRegisterComb> makeImportUpdate(CompetitionModel c, RegisterRequestData data) {
|
||||||
if (data.getLicence() == null || data.getLicence() != -1) { // not a guest
|
if (data.getLicence() == null || data.getLicence() != -1) { // not a guest
|
||||||
return permService.hasEditPerm(securityCtx, id)
|
return findComb(data.getLicence(), data.getFname(), data.getLname())
|
||||||
.chain(c -> findComb(data.getLicence(), data.getFname(), data.getLname())
|
|
||||||
.call(combModel -> Mutiny.fetch(combModel.getLicences()))
|
.call(combModel -> Mutiny.fetch(combModel.getLicences()))
|
||||||
.call(combModel -> {
|
.call(combModel -> {
|
||||||
if (c.getBanMembre() == null)
|
if (c.getBanMembre() == null)
|
||||||
@@ -474,12 +483,11 @@ public class CompetitionService {
|
|||||||
c.getBanMembre().remove(combModel.getId());
|
c.getBanMembre().remove(combModel.getId());
|
||||||
return Panache.withTransaction(() -> repository.persist(c));
|
return Panache.withTransaction(() -> repository.persist(c));
|
||||||
})
|
})
|
||||||
.chain(combModel -> updateRegister(data, c, combModel, true, true)))
|
.chain(combModel -> updateRegister(data, c, combModel, true, true))
|
||||||
.map(r -> SimpleRegisterComb.fromModel(r, r.getMembre().getLicences())
|
.map(r -> SimpleRegisterComb.fromModel(r, r.getMembre().getLicences())
|
||||||
.setCategorieInscrite(r.getCategoriesInscrites()));
|
.setCategorieInscrite(r.getCategoriesInscrites()));
|
||||||
} else {
|
} else {
|
||||||
return permService.hasEditPerm(securityCtx, id)
|
return findGuestOrInit(data.getFname(), data.getLname(), c)
|
||||||
.chain(c -> findGuestOrInit(data.getFname(), data.getLname(), c))
|
|
||||||
.invoke(Unchecked.consumer(model -> {
|
.invoke(Unchecked.consumer(model -> {
|
||||||
if (data.getCategorie() == null)
|
if (data.getCategorie() == null)
|
||||||
throw new DBadRequestException(trad.t("categorie.requise"));
|
throw new DBadRequestException(trad.t("categorie.requise"));
|
||||||
@@ -503,13 +511,13 @@ public class CompetitionService {
|
|||||||
} else
|
} else
|
||||||
model.setCountry(data.getCountry());
|
model.setCountry(data.getCountry());
|
||||||
|
|
||||||
if (model.getCompetition().getRequiredWeight().contains(model.getCategorie())) {
|
if (c.getRequiredWeight().contains(model.getCategorie())) {
|
||||||
if (data.getCountry() != null)
|
if (data.getCountry() != null)
|
||||||
model.setWeight(data.getWeight());
|
model.setWeight(data.getWeight());
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.call(g -> Mutiny.fetch(g.getCategoriesInscrites()))
|
.call(g -> Mutiny.fetch(g.getCategoriesInscrites()))
|
||||||
.call(g -> catPresetRepository.list("competition = ?1 AND id IN ?2", g.getCompetition(),
|
.call(g -> catPresetRepository.list("competition = ?1 AND id IN ?2", c,
|
||||||
data.getCategoriesInscrites())
|
data.getCategoriesInscrites())
|
||||||
.invoke(cats -> {
|
.invoke(cats -> {
|
||||||
g.getCategoriesInscrites().clear();
|
g.getCategoriesInscrites().clear();
|
||||||
@@ -518,9 +526,8 @@ public class CompetitionService {
|
|||||||
.noneMatch(e -> e.getCategorie().equals(g.getCategorie())));
|
.noneMatch(e -> e.getCategorie().equals(g.getCategorie())));
|
||||||
}))
|
}))
|
||||||
.chain(model -> Panache.withTransaction(() -> competitionGuestRepository.persist(model))
|
.chain(model -> Panache.withTransaction(() -> competitionGuestRepository.persist(model))
|
||||||
.call(r -> model.getCompetition().getSystem() == CompetitionSystem.INTERNAL ?
|
.call(r -> c.getSystem() == CompetitionSystem.INTERNAL ?
|
||||||
sRegister.sendRegister(model.getCompetition().getUuid(),
|
sRegister.sendRegister(c.getUuid(), r) : Uni.createFrom().voidItem()))
|
||||||
r) : Uni.createFrom().voidItem()))
|
|
||||||
.map(g -> SimpleRegisterComb.fromModel(g).setCategorieInscrite(g.getCategoriesInscrites()));
|
.map(g -> SimpleRegisterComb.fromModel(g).setCategorieInscrite(g.getCategoriesInscrites()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -122,7 +122,9 @@ public class LicenceService {
|
|||||||
.call(genLicenceNumberAndAccountIfNeed())
|
.call(genLicenceNumberAndAccountIfNeed())
|
||||||
: Uni.createFrom().nullItem()
|
: Uni.createFrom().nullItem()
|
||||||
))
|
))
|
||||||
.call(licenceModel -> ls.logA(LogModel.ActionType.ADD, membreModel.getObjectName(),
|
.call(licenceModel -> ls.logA(LogModel.ActionType.ADD,
|
||||||
|
"%s (valid=%b, pay=%b, %s)" .formatted(membreModel.getObjectName(), model.isValidate(),
|
||||||
|
model.isPay(), model.getCertificate()),
|
||||||
licenceModel));
|
licenceModel));
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -184,11 +184,17 @@ public class MembreService {
|
|||||||
|
|
||||||
String finalSearch = search;
|
String finalSearch = search;
|
||||||
return getLicenceListe(licenceRequest, payState)
|
return getLicenceListe(licenceRequest, payState)
|
||||||
.map(l -> l.stream().map(l2 -> l2.getMembre().getId()).toList())
|
.chain(l -> {
|
||||||
.chain(ids -> {
|
|
||||||
PanacheQuery<MembreModel> query;
|
PanacheQuery<MembreModel> query;
|
||||||
query = repository.find(queryStr, sort, finalSearch, ids, club).page(Page.ofSize(limit));
|
query = repository.find(queryStr, sort, finalSearch,
|
||||||
return getPageResult(query, limit, page);
|
l.stream().map(l2 -> l2.getMembre().getId()).toList(), club).page(Page.ofSize(limit));
|
||||||
|
return getPageResult(query, limit, page)
|
||||||
|
.call(r -> (r.getResult().isEmpty() || !l.isEmpty() ?
|
||||||
|
Uni.createFrom().item(l) :
|
||||||
|
licenceRepository.list("saison = ?1 AND membre.id IN ?2", Utils.getSaison(),
|
||||||
|
r.getResult().stream().map(SimpleMembre::getId).toList()))
|
||||||
|
.invoke(l2 -> r.setAdditionalData(l2.stream().map(SimpleLicence::fromModel)))
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,7 +216,7 @@ public class MembreService {
|
|||||||
public Uni<List<SimpleMembreInOutData>> getAllExport(String subject) {
|
public Uni<List<SimpleMembreInOutData>> getAllExport(String subject) {
|
||||||
return repository.find("userId = ?1", subject).firstResult()
|
return repository.find("userId = ?1", subject).firstResult()
|
||||||
.chain(membreModel -> repository.list("club = ?1", membreModel.getClub()))
|
.chain(membreModel -> repository.list("club = ?1", membreModel.getClub()))
|
||||||
.chain(membres -> licenceRepository.list("saison = ?1 AND membre IN ?2", Utils.getSaison(), membres)
|
.chain(membres -> licenceRepository.list("membre IN ?1", membres)
|
||||||
.map(l -> membres.stream().map(m -> SimpleMembreInOutData.fromModel(m, l)).toList()));
|
.map(l -> membres.stream().map(m -> SimpleMembreInOutData.fromModel(m, l)).toList()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import fr.titionfire.ffsaf.rest.data.ResultCategoryData;
|
|||||||
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.utils.*;
|
import fr.titionfire.ffsaf.utils.*;
|
||||||
|
import io.quarkus.cache.Cache;
|
||||||
|
import io.quarkus.cache.CacheName;
|
||||||
import io.quarkus.hibernate.reactive.panache.common.WithSession;
|
import io.quarkus.hibernate.reactive.panache.common.WithSession;
|
||||||
import io.quarkus.runtime.annotations.RegisterForReflection;
|
import io.quarkus.runtime.annotations.RegisterForReflection;
|
||||||
import io.smallrye.mutiny.Multi;
|
import io.smallrye.mutiny.Multi;
|
||||||
@@ -15,6 +17,7 @@ import io.smallrye.mutiny.unchecked.Unchecked;
|
|||||||
import jakarta.enterprise.context.ApplicationScoped;
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
import org.hibernate.reactive.mutiny.Mutiny;
|
import org.hibernate.reactive.mutiny.Mutiny;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
@@ -52,6 +55,11 @@ public class ResultService {
|
|||||||
@Inject
|
@Inject
|
||||||
TradService trad;
|
TradService trad;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
@CacheName("club-classement")
|
||||||
|
Cache cacheClubClassement;
|
||||||
|
private static final HashMap<String, Long> cacheClubClassementExp = new HashMap<>();
|
||||||
|
|
||||||
private static final HashMap<Long, String> combTempIds = new HashMap<>();
|
private static final HashMap<Long, String> combTempIds = new HashMap<>();
|
||||||
|
|
||||||
private static String getCombTempId(Long key) {
|
private static String getCombTempId(Long key) {
|
||||||
@@ -188,6 +196,7 @@ public class ResultService {
|
|||||||
comb.getName(membreModel, ResultPrivacy.REGISTERED_ONLY_NO_DETAILS), stat.score, stat.w,
|
comb.getName(membreModel, ResultPrivacy.REGISTERED_ONLY_NO_DETAILS), stat.score, stat.w,
|
||||||
stat.pointMake, stat.pointTake, stat.getPointRate());
|
stat.pointMake, stat.pointTake, stat.getPointRate());
|
||||||
})
|
})
|
||||||
|
.filter(r -> r.getPointMake() > 0 || r.getPointTake() > 0)
|
||||||
.sorted(Comparator
|
.sorted(Comparator
|
||||||
.comparing(ResultCategoryData.RankArray::getScore)
|
.comparing(ResultCategoryData.RankArray::getScore)
|
||||||
.thenComparing(ResultCategoryData.RankArray::getWin)
|
.thenComparing(ResultCategoryData.RankArray::getWin)
|
||||||
@@ -212,7 +221,7 @@ public class ResultService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void getClassementArray(CategoryModel categoryModel, MembreModel membreModel, List<CardModel> cards,
|
public void getClassementArray(CategoryModel categoryModel, MembreModel membreModel, List<CardModel> cards,
|
||||||
ResultCategoryData out) {
|
ResultCategoryData out) {
|
||||||
if ((categoryModel.getType() & 2) != 0) {
|
if ((categoryModel.getType() & 2) != 0) {
|
||||||
AtomicInteger rank = new AtomicInteger(0);
|
AtomicInteger rank = new AtomicInteger(0);
|
||||||
@@ -230,7 +239,8 @@ public class ResultService {
|
|||||||
} else {
|
} else {
|
||||||
for (List<ResultCategoryData.RankArray> list : out.getRankArray().values()) {
|
for (List<ResultCategoryData.RankArray> list : out.getRankArray().values()) {
|
||||||
for (ResultCategoryData.RankArray r : list) {
|
for (ResultCategoryData.RankArray r : list) {
|
||||||
out.getClassement().add(new ResultCategoryData.ClassementData(r.getRank(), r.getComb(), r.getName()));
|
out.getClassement()
|
||||||
|
.add(new ResultCategoryData.ClassementData(r.getRank(), r.getComb(), r.getName()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -258,7 +268,7 @@ public class ResultService {
|
|||||||
.add(new ResultCategoryData.ClassementData(rank.incrementAndGet(), m.getC1(),
|
.add(new ResultCategoryData.ClassementData(rank.incrementAndGet(), m.getC1(),
|
||||||
m.getC1Name(membreModel, ResultPrivacy.REGISTERED_ONLY_NO_DETAILS)));
|
m.getC1Name(membreModel, ResultPrivacy.REGISTERED_ONLY_NO_DETAILS)));
|
||||||
out.getClassement()
|
out.getClassement()
|
||||||
.add(new ResultCategoryData.ClassementData(rank.getAndIncrement(), m.getC2(),
|
.add(new ResultCategoryData.ClassementData(rank.get(), m.getC2(),
|
||||||
m.getC2Name(membreModel, ResultPrivacy.REGISTERED_ONLY_NO_DETAILS)));
|
m.getC2Name(membreModel, ResultPrivacy.REGISTERED_ONLY_NO_DETAILS)));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -606,21 +616,32 @@ public class ResultService {
|
|||||||
new ArrayList<>(), membreModel)
|
new ArrayList<>(), membreModel)
|
||||||
)));
|
)));
|
||||||
} else {
|
} else {
|
||||||
return clubRepository.findById(id).chain(clubModel ->
|
return cardRepository.list("competition.uuid = ?1", uuid)
|
||||||
|
.chain(cards -> clubRepository.findById(id).chain(clubModel ->
|
||||||
registerRepository.list("competition.uuid = ?1 AND membre.club = ?2", uuid, clubModel)
|
registerRepository.list("competition.uuid = ?1 AND membre.club = ?2", uuid, clubModel)
|
||||||
.chain(registers -> matchRepository.list(
|
.chain(registers -> matchRepository.list(
|
||||||
"SELECT DISTINCT m FROM MatchModel m LEFT JOIN m.c1_guest.comb c1g LEFT JOIN m.c2_guest.comb c2g " +
|
"SELECT DISTINCT m FROM MatchModel m LEFT JOIN m.c1_guest.comb c1g LEFT JOIN m.c2_guest.comb c2g " +
|
||||||
"WHERE m.category.compet.uuid = ?1 AND (m.c1_id IN ?2 OR m.c2_id IN ?2 OR c1g IN ?2 OR c2g IN ?2)",
|
"WHERE m.category.compet.uuid = ?1 AND (m.c1_id IN ?2 OR m.c2_id IN ?2 OR c1g IN ?2 OR c2g IN ?2)",
|
||||||
uuid, registers.stream().map(RegisterModel::getMembre).toList())
|
uuid, registers.stream().map(RegisterModel::getMembre).toList())
|
||||||
.chain(matchModels -> cardRepository.list("competition.uuid = ?1", uuid)
|
.map(matchModels -> matchModels.stream()
|
||||||
.map(cards ->
|
.map(m -> new MatchModelExtend(m, cards)))
|
||||||
|
.chain(s -> competitionGuestRepository.list(
|
||||||
|
"competition.uuid = ?1 AND club = ?2", uuid, clubModel.getName())
|
||||||
|
.chain(guests -> matchRepository.list(
|
||||||
|
"SELECT DISTINCT m FROM MatchModel m LEFT JOIN m.c1_guest.guest c1g LEFT JOIN m.c2_guest.guest c2g " +
|
||||||
|
"WHERE m.category.compet.uuid = ?1 AND (m.c1_guest IN ?2 OR m.c2_guest IN ?2 OR c1g IN ?2 OR c2g IN ?2)",
|
||||||
|
uuid, guests)
|
||||||
|
.map(mm -> new Pair<>(guests, Stream.concat(s, mm.stream()
|
||||||
|
.map(m -> new MatchModelExtend(m, cards)))
|
||||||
|
.distinct().toList()))
|
||||||
|
))
|
||||||
|
.map(p ->
|
||||||
getClubArray2(clubModel.getName(),
|
getClubArray2(clubModel.getName(),
|
||||||
registers.stream().map(o -> (CombModel) o.getMembre())
|
Stream.concat(
|
||||||
.toList(),
|
registers.stream().map(RegisterModel::getMembre),
|
||||||
matchModels.stream()
|
p.getKey().stream()
|
||||||
.map(m -> new MatchModelExtend(m, cards)).toList(),
|
).toList(),
|
||||||
registers, membreModel)
|
p.getValue(), registers, membreModel)
|
||||||
|
|
||||||
))));
|
))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -723,6 +744,112 @@ public class ResultService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Uni<List<ClubClassement>> getAllClubArray(String uuid) {
|
||||||
|
return getAllClubArray(uuid, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Uni<List<ClubClassement>> getAllClubArray(String uuid, SecurityCtx securityCtx) {
|
||||||
|
return hasAccess(uuid, securityCtx).chain(membreModel -> getAllClubArray(uuid, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Uni<List<ClubClassement>> getAllClubArray(String uuid, boolean cache) {
|
||||||
|
List<CardModel> cards = new java.util.ArrayList<>();
|
||||||
|
|
||||||
|
return (!cache || cacheClubClassementExp.getOrDefault(uuid, 0L) < System.currentTimeMillis() ?
|
||||||
|
cacheClubClassement.invalidate(uuid).invoke(() -> cacheClubClassementExp.remove(uuid)) :
|
||||||
|
Uni.createFrom().voidItem()
|
||||||
|
).chain(o -> cacheClubClassement.getAsync(uuid, k -> cardRepository.list("competition.uuid = ?1", uuid)
|
||||||
|
.invoke(__ -> System.out.println("Cache miss for club classement with uuid " + uuid))
|
||||||
|
.invoke(cards::addAll)
|
||||||
|
.chain(__ -> matchRepository.list("category.compet.uuid = ?1", uuid))
|
||||||
|
.chain(matchs -> {
|
||||||
|
HashMap<CategoryModel, List<MatchModel>> map = new HashMap<>();
|
||||||
|
for (MatchModel match : matchs) {
|
||||||
|
if (!map.containsKey(match.getCategory()))
|
||||||
|
map.put(match.getCategory(), new java.util.ArrayList<>());
|
||||||
|
map.get(match.getCategory()).add(match);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Multi.createFrom().iterable(map.entrySet())
|
||||||
|
.onItem().call(entry -> Mutiny.fetch(entry.getKey().getTree()))
|
||||||
|
.map(entry -> {
|
||||||
|
ResultCategoryData tmp = new ResultCategoryData();
|
||||||
|
|
||||||
|
getArray2(entry.getValue().stream().map(m -> new MatchModelExtend(m, cards))
|
||||||
|
.toList(),
|
||||||
|
null, tmp);
|
||||||
|
getClassementArray(entry.getKey(), null, cards, tmp);
|
||||||
|
|
||||||
|
return tmp;
|
||||||
|
})
|
||||||
|
.collect().asList();
|
||||||
|
})
|
||||||
|
.map(categoryData -> {
|
||||||
|
HashMap<String, ClubClassement> clubMap = new HashMap<>();
|
||||||
|
|
||||||
|
categoryData.forEach(
|
||||||
|
c -> c.getClassement().stream().map(ResultCategoryData.ClassementData::comb)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.distinct()
|
||||||
|
.map(comb -> {
|
||||||
|
if (comb instanceof MembreModel membreModel2) {
|
||||||
|
return (membreModel2.getClub() != null) ? membreModel2.getClub()
|
||||||
|
.getName() : "";
|
||||||
|
} else if (comb instanceof CompetitionGuestModel guestModel) {
|
||||||
|
return guestModel.getClub();
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
})
|
||||||
|
.filter(s -> s != null && !s.isBlank() && !s.equals("Team"))
|
||||||
|
.distinct()
|
||||||
|
.forEach(clubName -> clubMap.putIfAbsent(clubName,
|
||||||
|
new ClubClassement(clubName))));
|
||||||
|
|
||||||
|
categoryData.forEach(c -> c.getClassement().forEach(classementData -> {
|
||||||
|
if (classementData.rank() > 3)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (classementData.comb() != null) {
|
||||||
|
String clubName = "";
|
||||||
|
if (classementData.comb() instanceof MembreModel membreModel2) {
|
||||||
|
clubName = (membreModel2.getClub() != null) ? membreModel2.getClub()
|
||||||
|
.getName() : "";
|
||||||
|
} else if (classementData.comb() instanceof CompetitionGuestModel guestModel) {
|
||||||
|
clubName = guestModel.getClub();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clubName != null && !clubName.isBlank()
|
||||||
|
&& !clubName.equals("Team") && clubMap.containsKey(clubName)) {
|
||||||
|
ClubClassement entity = clubMap.get(clubName);
|
||||||
|
entity.score[classementData.rank() - 1]++;
|
||||||
|
entity.tt_score += 4 - classementData.rank();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
return clubMap.values().stream()
|
||||||
|
.sorted(Comparator.comparingInt((ClubClassement c) -> c.tt_score)
|
||||||
|
.thenComparingInt(c -> c.score[0])
|
||||||
|
.thenComparingInt(c -> c.score[1])
|
||||||
|
.thenComparingInt(c -> c.score[2]).reversed())
|
||||||
|
.toList();
|
||||||
|
})
|
||||||
|
.invoke(__ -> cacheClubClassementExp.put(uuid, System.currentTimeMillis() + 60 * 1000L))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@RegisterForReflection
|
||||||
|
public static class ClubClassement {
|
||||||
|
String name;
|
||||||
|
Integer[] score = new Integer[]{0, 0, 0};
|
||||||
|
int tt_score = 0;
|
||||||
|
|
||||||
|
public ClubClassement(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@RegisterForReflection
|
@RegisterForReflection
|
||||||
public static class CombStat {
|
public static class CombStat {
|
||||||
public int w;
|
public int w;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import jakarta.ws.rs.*;
|
|||||||
import jakarta.ws.rs.core.MediaType;
|
import jakarta.ws.rs.core.MediaType;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@Path("api/public/result/{id}")
|
@Path("api/public/result/{id}")
|
||||||
public class ExternalResultEndpoints {
|
public class ExternalResultEndpoints {
|
||||||
@@ -73,6 +74,13 @@ public class ExternalResultEndpoints {
|
|||||||
return resultService.getClubList(id);
|
return resultService.getClubList(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("/club/classement")
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
public Uni<List<ResultService.ClubClassement>> clubClassement() {
|
||||||
|
return resultService.getAllClubArray(id);
|
||||||
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("/club/data")
|
@Path("/club/data")
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
|||||||
@@ -47,6 +47,12 @@ public class ResultEndpoints {
|
|||||||
return resultService.getClubList(uuid, securityCtx);
|
return resultService.getClubList(uuid, securityCtx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("{uuid}/club/classement")
|
||||||
|
public Uni<List<ResultService.ClubClassement>> getClubClassement(@PathParam("uuid") String uuid) {
|
||||||
|
return resultService.getAllClubArray(uuid, securityCtx);
|
||||||
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("{uuid}/club/{id}")
|
@Path("{uuid}/club/{id}")
|
||||||
public Uni<ResultService.ClubArrayData> getClub(@PathParam("uuid") String uuid, @PathParam("id") long id) {
|
public Uni<ResultService.ClubArrayData> getClub(@PathParam("uuid") String uuid, @PathParam("id") long id) {
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ public class CompetitionData {
|
|||||||
public static class SimpleRegister {
|
public static class SimpleRegister {
|
||||||
long id;
|
long id;
|
||||||
int overCategory;
|
int overCategory;
|
||||||
Integer weight;
|
Float weight;
|
||||||
Categorie categorie;
|
Categorie categorie;
|
||||||
Long club;
|
Long club;
|
||||||
String club_str;
|
String club_str;
|
||||||
|
|||||||
@@ -4,10 +4,12 @@ import fr.titionfire.ffsaf.data.model.CatPresetModel;
|
|||||||
import io.quarkus.runtime.annotations.RegisterForReflection;
|
import io.quarkus.runtime.annotations.RegisterForReflection;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@RegisterForReflection
|
@RegisterForReflection
|
||||||
public class PresetData {
|
public class PresetData {
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ public class RegisterRequestData {
|
|||||||
private String fname;
|
private String fname;
|
||||||
private String lname;
|
private String lname;
|
||||||
|
|
||||||
private Integer weight;
|
private Float weight;
|
||||||
private Integer weightReal;
|
private Float weightReal;
|
||||||
private Integer overCategory;
|
private Integer overCategory;
|
||||||
private boolean lockEdit = false;
|
private boolean lockEdit = false;
|
||||||
private List<Long> categoriesInscrites;
|
private List<Long> categoriesInscrites;
|
||||||
|
|||||||
@@ -2,10 +2,14 @@ package fr.titionfire.ffsaf.rest.data;
|
|||||||
|
|
||||||
import fr.titionfire.ffsaf.data.model.LicenceModel;
|
import fr.titionfire.ffsaf.data.model.LicenceModel;
|
||||||
import fr.titionfire.ffsaf.data.model.MembreModel;
|
import fr.titionfire.ffsaf.data.model.MembreModel;
|
||||||
|
import fr.titionfire.ffsaf.utils.Utils;
|
||||||
import io.quarkus.runtime.annotations.RegisterForReflection;
|
import io.quarkus.runtime.annotations.RegisterForReflection;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -24,7 +28,27 @@ public class SimpleMembreInOutData {
|
|||||||
|
|
||||||
public static SimpleMembreInOutData fromModel(MembreModel membreModel, List<LicenceModel> lc) {
|
public static SimpleMembreInOutData fromModel(MembreModel membreModel, List<LicenceModel> lc) {
|
||||||
LicenceModel currentLicence = lc.stream().filter(l -> l.getMembre().getId().equals(membreModel.getId()))
|
LicenceModel currentLicence = lc.stream().filter(l -> l.getMembre().getId().equals(membreModel.getId()))
|
||||||
.findFirst().orElse(null);
|
.max(Comparator.comparingInt(LicenceModel::getSaison)).orElse(null);
|
||||||
|
|
||||||
|
String certif = null;
|
||||||
|
if (currentLicence != null && currentLicence.getCertificate() != null) {
|
||||||
|
String[] strings = currentLicence.getCertificate().split("¤");
|
||||||
|
if (currentLicence.getSaison() == Utils.getSaison()) {
|
||||||
|
certif = currentLicence.getCertificate();
|
||||||
|
} else if (strings.length > 1) {
|
||||||
|
try {
|
||||||
|
Date date = new SimpleDateFormat("yyyy-MM-dd").parse(strings[1]);
|
||||||
|
Calendar max = Utils.getFirstDateOfSaison();
|
||||||
|
max.add(Calendar.YEAR, -2);
|
||||||
|
|
||||||
|
if (max.getTime().compareTo(date) < 0) {
|
||||||
|
certif = currentLicence.getCertificate();
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
return new SimpleMembreInOutData(
|
return new SimpleMembreInOutData(
|
||||||
membreModel.getLicence(),
|
membreModel.getLicence(),
|
||||||
@@ -33,8 +57,8 @@ public class SimpleMembreInOutData {
|
|||||||
membreModel.getEmail(),
|
membreModel.getEmail(),
|
||||||
membreModel.getGenre().str,
|
membreModel.getGenre().str,
|
||||||
membreModel.getBirth_date(),
|
membreModel.getBirth_date(),
|
||||||
currentLicence != null,
|
currentLicence != null && currentLicence.getSaison() == Utils.getSaison(),
|
||||||
currentLicence == null ? null : currentLicence.getCertificate()
|
certif
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ public class SimpleRegisterComb {
|
|||||||
private Categorie categorie;
|
private Categorie categorie;
|
||||||
private SimpleClubModel club;
|
private SimpleClubModel club;
|
||||||
private Integer licence;
|
private Integer licence;
|
||||||
private Integer weight;
|
private Float weight;
|
||||||
private Integer weightReal;
|
private Float weightReal;
|
||||||
private int overCategory;
|
private int overCategory;
|
||||||
private boolean hasLicenceActive;
|
private boolean hasLicenceActive;
|
||||||
private boolean lockEdit;
|
private boolean lockEdit;
|
||||||
|
|||||||
@@ -10,13 +10,14 @@ import java.util.List;
|
|||||||
@Data
|
@Data
|
||||||
@RegisterForReflection
|
@RegisterForReflection
|
||||||
public class PageResult<T> {
|
public class PageResult<T> {
|
||||||
@Schema(description = "Le numéro de la page courante.", example = "1")
|
@Schema(description = "Le numéro de la page courante.", examples = "1")
|
||||||
private int page;
|
private int page;
|
||||||
@Schema(description = "Le nombre d'éléments par page.", example = "10")
|
@Schema(description = "Le nombre d'éléments par page.", examples = "10")
|
||||||
private int page_size;
|
private int page_size;
|
||||||
@Schema(description = "Le nombre total de pages.", example = "5")
|
@Schema(description = "Le nombre total de pages.", examples = "5")
|
||||||
private int page_count;
|
private int page_count;
|
||||||
@Schema(description = "Le nombre total d'éléments.", example = "47")
|
@Schema(description = "Le nombre total d'éléments.", examples = "47")
|
||||||
private long result_count;
|
private long result_count;
|
||||||
private List<T> result = new ArrayList<>();
|
private List<T> result = new ArrayList<>();
|
||||||
|
private Object additionalData;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ import java.io.*;
|
|||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.net.URLConnection;
|
import java.net.URLConnection;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.StandardCopyOption;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
@@ -117,6 +119,15 @@ public class Utils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Calendar getFirstDateOfSaison() {
|
||||||
|
Calendar calendar = Calendar.getInstance();
|
||||||
|
|
||||||
|
calendar.set(getSaison(), Calendar.SEPTEMBER, 1, 0, 0, 0);
|
||||||
|
calendar.set(Calendar.MILLISECOND, 0);
|
||||||
|
|
||||||
|
return calendar;
|
||||||
|
}
|
||||||
|
|
||||||
public static Calendar toCalendar(Date date) {
|
public static Calendar toCalendar(Date date) {
|
||||||
Calendar cal = Calendar.getInstance();
|
Calendar cal = Calendar.getInstance();
|
||||||
cal.setTime(date);
|
cal.setTime(date);
|
||||||
@@ -244,15 +255,16 @@ public class Utils {
|
|||||||
FilenameFilter filter = (directory, filename) -> filename.startsWith(id + ".");
|
FilenameFilter filter = (directory, filename) -> filename.startsWith(id + ".");
|
||||||
File[] files = dirFile.listFiles(filter);
|
File[] files = dirFile.listFiles(filter);
|
||||||
if (files != null) {
|
if (files != null) {
|
||||||
for (File f : files) {
|
for (File f2 : files) {
|
||||||
//noinspection ResultOfMethodCallIgnored
|
//noinspection ResultOfMethodCallIgnored
|
||||||
f.delete();
|
f2.delete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
File f = file.filePath().toFile();
|
Path f = file.filePath();
|
||||||
//noinspection ResultOfMethodCallIgnored
|
System.out.println(f + " -> " + new File(dirFile, id + "." + detectedExtensions[0]));
|
||||||
f.renameTo(new File(dirFile, id + "." + detectedExtensions[0]));
|
Files.copy(f, new File(dirFile, id + "." + detectedExtensions[0]).toPath(),
|
||||||
|
StandardCopyOption.REPLACE_EXISTING);
|
||||||
return "ok";
|
return "ok";
|
||||||
})));
|
})));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,6 +54,9 @@ public class CompetitionWS {
|
|||||||
@Inject
|
@Inject
|
||||||
RState rState;
|
RState rState;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
RPDF rpdf;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
SecurityCtx securityCtx;
|
SecurityCtx securityCtx;
|
||||||
|
|
||||||
@@ -99,6 +102,7 @@ public class CompetitionWS {
|
|||||||
getWSReceiverMethods(RCard.class, rCard);
|
getWSReceiverMethods(RCard.class, rCard);
|
||||||
getWSReceiverMethods(RTeam.class, rTeam);
|
getWSReceiverMethods(RTeam.class, rTeam);
|
||||||
getWSReceiverMethods(RState.class, rState);
|
getWSReceiverMethods(RState.class, rState);
|
||||||
|
getWSReceiverMethods(RPDF.class, rpdf);
|
||||||
|
|
||||||
executor = notifyExecutor;
|
executor = notifyExecutor;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,9 +97,10 @@ public class RCard {
|
|||||||
|
|
||||||
@WSReceiver(code = "sendCardRm", permission = PermLevel.ADMIN)
|
@WSReceiver(code = "sendCardRm", permission = PermLevel.ADMIN)
|
||||||
public Uni<Void> sendCardRm(WebSocketConnection connection, SendCardAdd card) {
|
public Uni<Void> sendCardRm(WebSocketConnection connection, SendCardAdd card) {
|
||||||
return getById(card.matchId(), connection)
|
return competitionRepository.find("uuid", connection.pathParam("uuid")).firstResult()
|
||||||
.chain(matchModel -> cardRepository.find("match = ?1 AND comb = ?2 AND type = ?3",
|
.chain(competition -> cardRepository.find(
|
||||||
matchModel.getId(), card.combId(), card.type())
|
"match IS NULL AND comb = ?1 AND type = ?2 AND competition = ?3 AND match " + (card.matchId() == null ? "IS NULL" : "= " + card.matchId()),
|
||||||
|
card.combId(), card.type(), competition)
|
||||||
.firstResult()
|
.firstResult()
|
||||||
.invoke(Unchecked.consumer(o -> {
|
.invoke(Unchecked.consumer(o -> {
|
||||||
if (o == null)
|
if (o == null)
|
||||||
@@ -143,7 +144,7 @@ public class RCard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@RegisterForReflection
|
@RegisterForReflection
|
||||||
public record SendCardAdd(long matchId, long combId, CardModel.CardType type, String reason) {
|
public record SendCardAdd(Long matchId, long combId, CardModel.CardType type, String reason) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@RegisterForReflection
|
@RegisterForReflection
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import lombok.Data;
|
|||||||
import org.hibernate.reactive.mutiny.Mutiny;
|
import org.hibernate.reactive.mutiny.Mutiny;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
@WithSession
|
@WithSession
|
||||||
@@ -55,6 +56,9 @@ public class RCategorie {
|
|||||||
@Inject
|
@Inject
|
||||||
CardRepository cardRepository;
|
CardRepository cardRepository;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
CatPresetRepository catPresetRepository;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ResultService resultService;
|
ResultService resultService;
|
||||||
|
|
||||||
@@ -114,23 +118,67 @@ public class RCategorie {
|
|||||||
categoryModel.setTree(new ArrayList<>());
|
categoryModel.setTree(new ArrayList<>());
|
||||||
categoryModel.setType(categorie.type);
|
categoryModel.setType(categorie.type);
|
||||||
categoryModel.setLiceName(categorie.liceName);
|
categoryModel.setLiceName(categorie.liceName);
|
||||||
|
categoryModel.setTreeAreClassement(categorie.treeAreClassement);
|
||||||
|
categoryModel.setFullClassement(categorie.fullClassement);
|
||||||
|
|
||||||
|
if (categorie.preset() != null)
|
||||||
|
return catPresetRepository.findById(categorie.preset().getId())
|
||||||
|
.invoke(categoryModel::setPreset)
|
||||||
|
.chain(__ -> categoryRepository.create(categoryModel));
|
||||||
return categoryRepository.create(categoryModel);
|
return categoryRepository.create(categoryModel);
|
||||||
})
|
})
|
||||||
.invoke(cat -> SSCategorie.sendAddCategory(connection, cat))
|
.invoke(cat -> SSCategorie.sendAddCategory(connection, cat))
|
||||||
.map(CategoryModel::getId);
|
.map(CategoryModel::getId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@WSReceiver(code = "createOrReplaceCategory", permission = PermLevel.ADMIN)
|
||||||
|
public Uni<Long> createOrReplaceCategory(WebSocketConnection connection, JustCategorie categorie) {
|
||||||
|
return matchRepository.list("category.compet.uuid = ?1 AND category.name = ?2", connection.pathParam("uuid"),
|
||||||
|
categorie.name)
|
||||||
|
.chain(existing -> {
|
||||||
|
if (existing.isEmpty())
|
||||||
|
return createCategory(connection, categorie);
|
||||||
|
|
||||||
|
Map<Long, List<MatchModel>> matchesByCategory = existing.stream()
|
||||||
|
.filter(m -> m.getCategory() != null)
|
||||||
|
.collect(Collectors.groupingBy(m -> m.getCategory().getId()));
|
||||||
|
|
||||||
|
for (Map.Entry<Long, List<MatchModel>> entry : matchesByCategory.entrySet()) {
|
||||||
|
Long categoryId = entry.getKey();
|
||||||
|
List<MatchModel> matches = entry.getValue();
|
||||||
|
|
||||||
|
if (matches.stream().noneMatch(m -> !m.getScores().isEmpty() || m.isEnd()))
|
||||||
|
return Panache.withTransaction(() -> updateCategory(connection, categorie, categoryId)
|
||||||
|
.call(__ -> treeRepository.delete("category = ?1", categoryId))
|
||||||
|
.call(__ -> matchRepository.delete("category.id = ?1", categoryId)))
|
||||||
|
.replaceWith(categoryId);
|
||||||
|
}
|
||||||
|
return createCategory(connection, categorie);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@WSReceiver(code = "updateCategory", permission = PermLevel.ADMIN)
|
@WSReceiver(code = "updateCategory", permission = PermLevel.ADMIN)
|
||||||
public Uni<Void> updateCategory(WebSocketConnection connection, JustCategorie categorie) {
|
public Uni<Void> updateCategory(WebSocketConnection connection, JustCategorie categorie) {
|
||||||
return getById(categorie.id, connection)
|
return updateCategory(connection, categorie, categorie.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Uni<Void> updateCategory(WebSocketConnection connection, JustCategorie categorie, Long id) {
|
||||||
|
return getById(id, connection)
|
||||||
|
.call(cat -> {
|
||||||
|
if (categorie.preset() == null) {
|
||||||
|
cat.setPreset(null);
|
||||||
|
return Uni.createFrom().item(cat);
|
||||||
|
} else {
|
||||||
|
return catPresetRepository.findById(categorie.preset().getId())
|
||||||
|
.invoke(cat::setPreset);
|
||||||
|
}
|
||||||
|
})
|
||||||
.chain(cat -> {
|
.chain(cat -> {
|
||||||
cat.setName(categorie.name);
|
cat.setName(categorie.name);
|
||||||
cat.setLiceName(categorie.liceName);
|
cat.setLiceName(categorie.liceName);
|
||||||
cat.setType(categorie.type);
|
cat.setType(categorie.type);
|
||||||
cat.setTreeAreClassement(categorie.treeAreClassement);
|
cat.setTreeAreClassement(categorie.treeAreClassement);
|
||||||
cat.setFullClassement(categorie.fullClassement);
|
cat.setFullClassement(categorie.fullClassement);
|
||||||
// cat.setPreset(cat.getPreset()); //TODO preset update
|
|
||||||
return Panache.withTransaction(() -> categoryRepository.persist(cat));
|
return Panache.withTransaction(() -> categoryRepository.persist(cat));
|
||||||
})
|
})
|
||||||
.call(cat -> {
|
.call(cat -> {
|
||||||
@@ -239,6 +287,12 @@ public class RCategorie {
|
|||||||
.replaceWithVoid();
|
.replaceWithVoid();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@WSReceiver(code = "listPreset", permission = PermLevel.VIEW)
|
||||||
|
public Uni<List<PresetData>> listPreset(WebSocketConnection connection, Object o) {
|
||||||
|
return catPresetRepository.list("competition.uuid", connection.pathParam("uuid"))
|
||||||
|
.map(presets -> presets.stream().map(PresetData::fromModel).toList());
|
||||||
|
}
|
||||||
|
|
||||||
@WSReceiver(code = "createClassementMatchs", permission = PermLevel.TABLE)
|
@WSReceiver(code = "createClassementMatchs", permission = PermLevel.TABLE)
|
||||||
public Uni<Void> createClassementMatchs(WebSocketConnection connection, Long categoryId) {
|
public Uni<Void> createClassementMatchs(WebSocketConnection connection, Long categoryId) {
|
||||||
return getById(categoryId, connection)
|
return getById(categoryId, connection)
|
||||||
|
|||||||
112
src/main/java/fr/titionfire/ffsaf/ws/recv/RPDF.java
Normal file
112
src/main/java/fr/titionfire/ffsaf/ws/recv/RPDF.java
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
package fr.titionfire.ffsaf.ws.recv;
|
||||||
|
|
||||||
|
import fr.titionfire.ffsaf.data.model.CardModel;
|
||||||
|
import fr.titionfire.ffsaf.data.model.CategoryModel;
|
||||||
|
import fr.titionfire.ffsaf.data.model.MatchModel;
|
||||||
|
import fr.titionfire.ffsaf.data.repository.CardRepository;
|
||||||
|
import fr.titionfire.ffsaf.data.repository.CategoryRepository;
|
||||||
|
import fr.titionfire.ffsaf.data.repository.CompetitionRepository;
|
||||||
|
import fr.titionfire.ffsaf.data.repository.MatchRepository;
|
||||||
|
import fr.titionfire.ffsaf.domain.entity.MatchModelExtend;
|
||||||
|
import fr.titionfire.ffsaf.domain.service.ResultService;
|
||||||
|
import fr.titionfire.ffsaf.domain.service.TradService;
|
||||||
|
import fr.titionfire.ffsaf.rest.data.ResultCategoryData;
|
||||||
|
import fr.titionfire.ffsaf.utils.Categorie;
|
||||||
|
import fr.titionfire.ffsaf.ws.PermLevel;
|
||||||
|
import io.quarkus.hibernate.reactive.panache.common.WithSession;
|
||||||
|
import io.quarkus.runtime.annotations.RegisterForReflection;
|
||||||
|
import io.quarkus.websockets.next.WebSocketConnection;
|
||||||
|
import io.smallrye.mutiny.Multi;
|
||||||
|
import io.smallrye.mutiny.Uni;
|
||||||
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
|
import jakarta.inject.Inject;
|
||||||
|
import jakarta.transaction.Transactional;
|
||||||
|
import org.hibernate.reactive.mutiny.Mutiny;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
@WithSession
|
||||||
|
@ApplicationScoped
|
||||||
|
@RegisterForReflection
|
||||||
|
public class RPDF {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
CompetitionRepository competitionRepository;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
MatchRepository matchRepository;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
CardRepository cardRepository;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
CategoryRepository categoryRepository;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
ResultService resultService;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
TradService trad;
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@WSReceiver(code = "getPodium", permission = PermLevel.VIEW)
|
||||||
|
public Uni<List<PodiumEntity>> getPodium(WebSocketConnection connection, Object o) {
|
||||||
|
List<CardModel> cards = new java.util.ArrayList<>();
|
||||||
|
|
||||||
|
return cardRepository.list("competition.uuid = ?1", connection.pathParam("uuid"))
|
||||||
|
.invoke(cards::addAll)
|
||||||
|
.chain(__ -> matchRepository.list("category.compet.uuid = ?1", connection.pathParam("uuid")))
|
||||||
|
.chain(matchs -> {
|
||||||
|
HashMap<CategoryModel, List<MatchModel>> map = new HashMap<>();
|
||||||
|
for (MatchModel match : matchs) {
|
||||||
|
if (!map.containsKey(match.getCategory()))
|
||||||
|
map.put(match.getCategory(), new java.util.ArrayList<>());
|
||||||
|
map.get(match.getCategory()).add(match);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Multi.createFrom().iterable(map.entrySet())
|
||||||
|
.onItem().call(entry -> Mutiny.fetch(entry.getKey().getTree()))
|
||||||
|
.map(entry -> {
|
||||||
|
ResultCategoryData tmp = new ResultCategoryData();
|
||||||
|
|
||||||
|
double cmoy = entry.getValue().stream().flatMap(m -> Stream.of(m.getC1(), m.getC2()))
|
||||||
|
.filter(c -> c != null && c.getCategorie() != null)
|
||||||
|
.mapToInt(c -> c.getCategorie().ordinal())
|
||||||
|
.average().orElse(0);
|
||||||
|
Categorie categorie_moy = Categorie.values()[(int) Math.ceil(cmoy)];
|
||||||
|
|
||||||
|
resultService.getArray2(
|
||||||
|
entry.getValue().stream().map(m -> new MatchModelExtend(m, cards)).toList(),
|
||||||
|
null, tmp);
|
||||||
|
resultService.getClassementArray(entry.getKey(), null, cards, tmp);
|
||||||
|
|
||||||
|
String source = "";
|
||||||
|
if ((entry.getKey().getType() & 2) != 0) {
|
||||||
|
if (entry.getKey().isTreeAreClassement())
|
||||||
|
source = trad.t("podium.source.classement", connection);
|
||||||
|
else
|
||||||
|
source = trad.t("podium.source.tree", connection);
|
||||||
|
} else if ((entry.getKey().getType() & 1) != 0)
|
||||||
|
source = trad.t("podium.source.poule", connection);
|
||||||
|
|
||||||
|
|
||||||
|
return new PodiumEntity(entry.getKey().getName(), source, categorie_moy,
|
||||||
|
tmp.getClassement());
|
||||||
|
})
|
||||||
|
.collect().asList();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@WSReceiver(code = "getPodiumClub", permission = PermLevel.VIEW)
|
||||||
|
public Uni<List<ResultService.ClubClassement>> getPodiumClub(WebSocketConnection connection, Object o) {
|
||||||
|
return resultService.getAllClubArray(connection.pathParam("uuid"), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@RegisterForReflection
|
||||||
|
public static record PodiumEntity(String poule_name, String source, Categorie categorie,
|
||||||
|
List<ResultCategoryData.ClassementData> podium) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ import fr.titionfire.ffsaf.ws.PermLevel;
|
|||||||
import io.quarkus.hibernate.reactive.panache.common.WithSession;
|
import io.quarkus.hibernate.reactive.panache.common.WithSession;
|
||||||
import io.quarkus.runtime.annotations.RegisterForReflection;
|
import io.quarkus.runtime.annotations.RegisterForReflection;
|
||||||
import io.quarkus.websockets.next.WebSocketConnection;
|
import io.quarkus.websockets.next.WebSocketConnection;
|
||||||
|
import io.smallrye.mutiny.Multi;
|
||||||
import io.smallrye.mutiny.Uni;
|
import io.smallrye.mutiny.Uni;
|
||||||
import jakarta.enterprise.context.ApplicationScoped;
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
@@ -24,14 +25,20 @@ public class RRegister {
|
|||||||
|
|
||||||
@WSReceiver(code = "getRegister", permission = PermLevel.TABLE)
|
@WSReceiver(code = "getRegister", permission = PermLevel.TABLE)
|
||||||
public Uni<List<CombEntity>> getRegister(WebSocketConnection connection, Object o) {
|
public Uni<List<CombEntity>> getRegister(WebSocketConnection connection, Object o) {
|
||||||
return competitionRepository.find("uuid", connection.pathParam("uuid")).firstResult()
|
|
||||||
.call(cm -> Mutiny.fetch(cm.getInsc()))
|
|
||||||
.call(cm -> Mutiny.fetch(cm.getGuests()))
|
|
||||||
.map(cm -> {
|
|
||||||
ArrayList<CombEntity> combEntities = new ArrayList<>();
|
ArrayList<CombEntity> combEntities = new ArrayList<>();
|
||||||
combEntities.addAll(cm.getInsc().stream().map(CombEntity::fromModel).toList());
|
return competitionRepository.find("uuid", connection.pathParam("uuid")).firstResult()
|
||||||
combEntities.addAll(cm.getGuests().stream().map(CombEntity::fromModel).toList());
|
.call(cm -> Mutiny.fetch(cm.getInsc())
|
||||||
return combEntities;
|
.onItem().transformToMulti(Multi.createFrom()::iterable)
|
||||||
});
|
.call(r -> Mutiny.fetch(r.getCategoriesInscrites()))
|
||||||
|
.map(r -> CombEntity.fromModel(r).addCategoriesInscrites(r.getCategoriesInscrites()))
|
||||||
|
.collect().asList()
|
||||||
|
.invoke(combEntities::addAll))
|
||||||
|
.call(cm -> Mutiny.fetch(cm.getGuests())
|
||||||
|
.onItem().transformToMulti(Multi.createFrom()::iterable)
|
||||||
|
.call(r -> Mutiny.fetch(r.getCategoriesInscrites()))
|
||||||
|
.map(r -> CombEntity.fromModel(r).addCategoriesInscrites(r.getCategoriesInscrites()))
|
||||||
|
.collect().asList()
|
||||||
|
.invoke(combEntities::addAll))
|
||||||
|
.replaceWith(combEntities);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ public class RTeam {
|
|||||||
.max(Integer::compareTo)
|
.max(Integer::compareTo)
|
||||||
.map(i -> Categorie.values()[i]).orElse(Categorie.SENIOR1));
|
.map(i -> Categorie.values()[i]).orElse(Categorie.SENIOR1));
|
||||||
|
|
||||||
List<Integer> s = Stream.concat(
|
List<Float> s = Stream.concat(
|
||||||
pair.getKey().stream().map(RegisterModel::getWeight),
|
pair.getKey().stream().map(RegisterModel::getWeight),
|
||||||
pair.getValue().stream().map(CompetitionGuestModel::getWeight))
|
pair.getValue().stream().map(CompetitionGuestModel::getWeight))
|
||||||
.filter(Objects::nonNull).toList();
|
.filter(Objects::nonNull).toList();
|
||||||
@@ -95,7 +95,7 @@ public class RTeam {
|
|||||||
} else if (s.size() == 1) {
|
} else if (s.size() == 1) {
|
||||||
team.setWeight(s.get(0));
|
team.setWeight(s.get(0));
|
||||||
} else {
|
} else {
|
||||||
team.setWeight((int) s.stream().mapToInt(Integer::intValue)
|
team.setWeight((float) s.stream().mapToDouble(Float::doubleValue)
|
||||||
.average()
|
.average()
|
||||||
.orElse(0));
|
.orElse(0));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package fr.titionfire.ffsaf.ws.send;
|
|||||||
import fr.titionfire.ffsaf.data.model.CompetitionGuestModel;
|
import fr.titionfire.ffsaf.data.model.CompetitionGuestModel;
|
||||||
import fr.titionfire.ffsaf.data.model.RegisterModel;
|
import fr.titionfire.ffsaf.data.model.RegisterModel;
|
||||||
import fr.titionfire.ffsaf.domain.entity.CombEntity;
|
import fr.titionfire.ffsaf.domain.entity.CombEntity;
|
||||||
|
import fr.titionfire.ffsaf.domain.service.CardService;
|
||||||
import fr.titionfire.ffsaf.net2.MessageType;
|
import fr.titionfire.ffsaf.net2.MessageType;
|
||||||
import fr.titionfire.ffsaf.ws.CompetitionWS;
|
import fr.titionfire.ffsaf.ws.CompetitionWS;
|
||||||
import fr.titionfire.ffsaf.ws.MessageOut;
|
import fr.titionfire.ffsaf.ws.MessageOut;
|
||||||
@@ -13,6 +14,7 @@ import io.quarkus.websockets.next.UserData;
|
|||||||
import io.smallrye.mutiny.Uni;
|
import io.smallrye.mutiny.Uni;
|
||||||
import jakarta.enterprise.context.ApplicationScoped;
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
|
import org.hibernate.reactive.mutiny.Mutiny;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
@@ -25,16 +27,48 @@ public class SRegister {
|
|||||||
@Inject
|
@Inject
|
||||||
OpenConnections connections;
|
OpenConnections connections;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
CardService cardService;
|
||||||
|
|
||||||
public Uni<Void> sendRegister(String uuid, RegisterModel registerModel) {
|
public Uni<Void> sendRegister(String uuid, RegisterModel registerModel) {
|
||||||
return send(uuid, "sendRegister", CombEntity.fromModel(registerModel));
|
return Mutiny.fetch(registerModel.getCategoriesInscrites()).chain(o ->
|
||||||
|
send(uuid, "sendRegister", CombEntity.fromModel(registerModel).addCategoriesInscrites(o))
|
||||||
|
.call(__ -> registerModel.getClub2() == null ? Uni.createFrom().voidItem() :
|
||||||
|
cardService.addTeamCartToNewComb(registerModel.getMembre().getId(),
|
||||||
|
registerModel.getClub2().getClubId(), registerModel.getClub2().getName(),
|
||||||
|
registerModel.getCompetition())
|
||||||
|
.chain(cardModels -> send(uuid, "sendCards", cardModels))));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Uni<Void> sendRegisterNoFetch(String uuid, RegisterModel registerModel) {
|
||||||
|
return send(uuid, "sendRegister",
|
||||||
|
CombEntity.fromModel(registerModel).addCategoriesInscrites(registerModel.getCategoriesInscrites()))
|
||||||
|
.call(__ -> registerModel.getClub2() == null ? Uni.createFrom().voidItem() :
|
||||||
|
cardService.addTeamCartToNewComb(registerModel.getMembre().getId(),
|
||||||
|
registerModel.getClub2().getClubId(), registerModel.getClub2().getName(),
|
||||||
|
registerModel.getCompetition())
|
||||||
|
.chain(cardModels -> send(uuid, "sendCards", cardModels)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Uni<Void> sendRegister(String uuid, CompetitionGuestModel model) {
|
public Uni<Void> sendRegister(String uuid, CompetitionGuestModel model) {
|
||||||
return send(uuid, "sendRegister", CombEntity.fromModel(model));
|
return Mutiny.fetch(model.getCategoriesInscrites()).chain(o ->
|
||||||
|
send(uuid, "sendRegister", CombEntity.fromModel(model).addCategoriesInscrites(o))
|
||||||
|
.call(__ -> cardService.addTeamCartToNewComb(model.getId() * -1,
|
||||||
|
null, model.getClub(), model.getCompetition())
|
||||||
|
.chain(cardModels -> send(uuid, "sendCards", cardModels))));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Uni<Void> sendRegisterNoFetch(String uuid, CompetitionGuestModel model) {
|
||||||
|
return send(uuid, "sendRegister",
|
||||||
|
CombEntity.fromModel(model).addCategoriesInscrites(model.getCategoriesInscrites()))
|
||||||
|
.call(__ -> cardService.addTeamCartToNewComb(model.getId() * -1,
|
||||||
|
null, model.getClub(), model.getCompetition())
|
||||||
|
.chain(cardModels -> send(uuid, "sendCards", cardModels)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Uni<Void> sendRegisterRemove(String uuid, Long combId) {
|
public Uni<Void> sendRegisterRemove(String uuid, Long combId) {
|
||||||
return send(uuid, "sendRegisterRemove", combId);
|
return send(uuid, "sendRegisterRemove", combId)
|
||||||
|
.call(__ -> cardService.rmTeamCardFromComb(combId, uuid));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Uni<Void> send(String uuid, String code, Object data) {
|
public Uni<Void> send(String uuid, String code, Object data) {
|
||||||
|
|||||||
@@ -89,3 +89,6 @@ carton.non.trouver=Card not found
|
|||||||
card.cannot.be.added=Unable to add the card
|
card.cannot.be.added=Unable to add the card
|
||||||
configuration.non.supportee=Unsupported configuration
|
configuration.non.supportee=Unsupported configuration
|
||||||
err.match.termine=Error, a placement match has already been played
|
err.match.termine=Error, a placement match has already been played
|
||||||
|
podium.source.classement=Ranking
|
||||||
|
podium.source.tree=Tournaments
|
||||||
|
podium.source.poule=Pool
|
||||||
|
|||||||
@@ -85,3 +85,6 @@ carton.non.trouver=Carton introuvable
|
|||||||
card.cannot.be.added=Impossible d'ajouter le carton
|
card.cannot.be.added=Impossible d'ajouter le carton
|
||||||
configuration.non.supportee=Configuration non supportée
|
configuration.non.supportee=Configuration non supportée
|
||||||
err.match.termine=Erreur, un match de classement a déjà été joué
|
err.match.termine=Erreur, un match de classement a déjà été joué
|
||||||
|
podium.source.classement=Classement
|
||||||
|
podium.source.tree=Tournois
|
||||||
|
podium.source.poule=Poule
|
||||||
2416
src/main/webapp/package-lock.json
generated
2416
src/main/webapp/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -13,40 +13,42 @@
|
|||||||
"@dnd-kit/core": "^6.3.1",
|
"@dnd-kit/core": "^6.3.1",
|
||||||
"@dnd-kit/sortable": "^10.0.0",
|
"@dnd-kit/sortable": "^10.0.0",
|
||||||
"@dnd-kit/utilities": "^3.2.2",
|
"@dnd-kit/utilities": "^3.2.2",
|
||||||
"@fortawesome/fontawesome-svg-core": "^7.1.0",
|
"@fortawesome/fontawesome-svg-core": "^7.3.1",
|
||||||
"@fortawesome/free-brands-svg-icons": "^7.1.0",
|
"@fortawesome/free-brands-svg-icons": "^7.3.1",
|
||||||
"@fortawesome/free-regular-svg-icons": "^7.1.0",
|
"@fortawesome/free-regular-svg-icons": "^7.3.1",
|
||||||
"@fortawesome/free-solid-svg-icons": "^7.1.0",
|
"@fortawesome/free-solid-svg-icons": "^7.3.1",
|
||||||
"@fortawesome/react-fontawesome": "^3.1.1",
|
"@fortawesome/react-fontawesome": "^3.5.0",
|
||||||
"axios": "^1.13.2",
|
"axios": "1.18.0",
|
||||||
"browser-image-compression": "^2.0.2",
|
"browser-image-compression": "^2.0.2",
|
||||||
"i18next": "^25.8.0",
|
"i18next": "^26.3.6",
|
||||||
"i18next-browser-languagedetector": "^8.2.0",
|
"i18next-browser-languagedetector": "^8.2.1",
|
||||||
"i18next-http-backend": "^3.0.2",
|
"i18next-http-backend": "^4.0.1",
|
||||||
|
"jspdf": "4.2.1",
|
||||||
|
"jspdf-autotable": "^5.0.8",
|
||||||
"jszip": "^3.10.1",
|
"jszip": "^3.10.1",
|
||||||
"leaflet": "^1.9.4",
|
"leaflet": "^1.9.4",
|
||||||
"obs-websocket-js": "^5.0.7",
|
"obs-websocket-js": "^5.0.8",
|
||||||
"proj4": "^2.20.2",
|
"proj4": "^2.21.0",
|
||||||
"react": "^19.2.3",
|
"react": "^19.2.8",
|
||||||
"react-dom": "^19.2.3",
|
"react-dom": "^19.2.8",
|
||||||
"react-i18next": "^16.5.3",
|
"react-i18next": "^17.0.11",
|
||||||
"react-is": "^19.2.3",
|
"react-is": "^19.2.8",
|
||||||
"react-leaflet": "^5.0.0",
|
"react-leaflet": "^5.0.0",
|
||||||
"react-loader-spinner": "^8.0.2",
|
"react-loader-spinner": "^8.0.2",
|
||||||
"react-router-dom": "^7.12.0",
|
"react-router-dom": "^7.18.2",
|
||||||
"react-toastify": "^11.0.5",
|
"react-toastify": "^11.1.0",
|
||||||
"recharts": "^3.7.0",
|
"recharts": "^3.10.1",
|
||||||
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz",
|
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz",
|
||||||
"xlsx-js-style": "^1.2.0"
|
"xlsx-js-style": "^1.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/react": "^19.2.9",
|
"@types/react": "^19.2.18",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.4",
|
||||||
"@vitejs/plugin-react": "^5.1.2",
|
"@vitejs/plugin-react": "^6.0.5",
|
||||||
"eslint": "^9.39.2",
|
"eslint": "^9.39.5",
|
||||||
"eslint-plugin-react": "^7.37.5",
|
"eslint-plugin-react": "^7.37.5",
|
||||||
"eslint-plugin-react-hooks": "^7.0.1",
|
"eslint-plugin-react-hooks": "^7.1.1",
|
||||||
"eslint-plugin-react-refresh": "^0.4.26",
|
"eslint-plugin-react-refresh": "^0.5.3",
|
||||||
"vite": "^7.3.1"
|
"vite": "8.0.16"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,6 +39,9 @@ function setSubPage(name) {
|
|||||||
case 'club':
|
case 'club':
|
||||||
clubPage(location);
|
clubPage(location);
|
||||||
break;
|
break;
|
||||||
|
case 'clubRank':
|
||||||
|
clubRankPage();
|
||||||
|
break;
|
||||||
case 'all':
|
case 'all':
|
||||||
combsPage();
|
combsPage();
|
||||||
break;
|
break;
|
||||||
@@ -54,6 +57,7 @@ function homePage() {
|
|||||||
<li><a id="pouleLink" href="javascript:void(0);">${i18next.t('parCatégorie')}</a></li>
|
<li><a id="pouleLink" href="javascript:void(0);">${i18next.t('parCatégorie')}</a></li>
|
||||||
<li><a id="combLink" href="javascript:void(0);">${i18next.t('parCombattant')}</a></li>
|
<li><a id="combLink" href="javascript:void(0);">${i18next.t('parCombattant')}</a></li>
|
||||||
<li><a id="clubLink" href="javascript:void(0);">${i18next.t('parClub')}</a></li>
|
<li><a id="clubLink" href="javascript:void(0);">${i18next.t('parClub')}</a></li>
|
||||||
|
<li><a id="clubClassement" href="javascript:void(0);">${i18next.t('classementClub')}</a></li>
|
||||||
<li><a id="allLink" href="javascript:void(0);">${i18next.t('tousLesCombattants')}</a></li>
|
<li><a id="allLink" href="javascript:void(0);">${i18next.t('tousLesCombattants')}</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
`
|
`
|
||||||
@@ -62,6 +66,7 @@ function homePage() {
|
|||||||
document.getElementById('pouleLink').addEventListener('click', () => setSubPage('poule'));
|
document.getElementById('pouleLink').addEventListener('click', () => setSubPage('poule'));
|
||||||
document.getElementById('combLink').addEventListener('click', () => setSubPage('comb'));
|
document.getElementById('combLink').addEventListener('click', () => setSubPage('comb'));
|
||||||
document.getElementById('clubLink').addEventListener('click', () => setSubPage('club'));
|
document.getElementById('clubLink').addEventListener('click', () => setSubPage('club'));
|
||||||
|
document.getElementById('clubClassement').addEventListener('click', () => setSubPage('clubRank'));
|
||||||
document.getElementById('allLink').addEventListener('click', () => setSubPage('all'));
|
document.getElementById('allLink').addEventListener('click', () => setSubPage('all'));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -270,6 +275,35 @@ function buildTree(treeData) {
|
|||||||
return drawGraph(initTree(treeData.filter(d => d.data.level >= 0)))
|
return drawGraph(initTree(treeData.filter(d => d.data.level >= 0)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildClassementArray(classement) {
|
||||||
|
const classement2 = classement.sort((a, b) => {
|
||||||
|
if (a.rank === b.rank)
|
||||||
|
return a.name.localeCompare(b.name);
|
||||||
|
return a.rank - b.rank;
|
||||||
|
})
|
||||||
|
|
||||||
|
const arrayDiv = document.createElement('div');
|
||||||
|
let arrayContent = `<figure class="wp-block-table is-style-stripes" style="font-size: 16px; margin-top: 2em">
|
||||||
|
<table style="width: 600px;overflow: auto">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="has-text-align-center" data-align="center">${i18next.t('place')}</th>
|
||||||
|
<th class="has-text-align-center" data-align="center">${i18next.t('nom')}</th>
|
||||||
|
</tr>
|
||||||
|
</thead><tbody>`
|
||||||
|
for (const row of classement2) {
|
||||||
|
arrayContent += `
|
||||||
|
<tr>
|
||||||
|
<td class="has-text-align-center" data-align="center">${row.rank}</td>
|
||||||
|
<td class="has-text-align-left" data-align="left">${row.name}</td>
|
||||||
|
</tr>`
|
||||||
|
|
||||||
|
}
|
||||||
|
arrayContent += `</tbody></table></figure>`
|
||||||
|
arrayDiv.innerHTML = arrayContent;
|
||||||
|
return arrayDiv;
|
||||||
|
}
|
||||||
|
|
||||||
function poulePage(location) {
|
function poulePage(location) {
|
||||||
rootDiv.innerHTML = `<h4>${i18next.t('résultatDeLaCompétition')} :</h4><a id="homeLink" href="javascript:void(0);">${i18next.t('back')}</a>`;
|
rootDiv.innerHTML = `<h4>${i18next.t('résultatDeLaCompétition')} :</h4><a id="homeLink" href="javascript:void(0);">${i18next.t('back')}</a>`;
|
||||||
document.getElementById('homeLink').addEventListener('click', () => setSubPage('home'));
|
document.getElementById('homeLink').addEventListener('click', () => setSubPage('home'));
|
||||||
@@ -343,6 +377,9 @@ function poulePage(location) {
|
|||||||
date: d.data.date,
|
date: d.data.date,
|
||||||
}))));
|
}))));
|
||||||
}
|
}
|
||||||
|
if (poule['treeIsClassement']){
|
||||||
|
dataContainer.append(buildClassementArray(poule['classement']));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
location[2] = isPoule ? 1 : 2;
|
location[2] = isPoule ? 1 : 2;
|
||||||
@@ -608,6 +645,60 @@ function clubPage(location) {
|
|||||||
rootDiv.append(content)
|
rootDiv.append(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildClubsView(clubs) {
|
||||||
|
const pouleDiv = document.createElement('div');
|
||||||
|
let arrayContent = `
|
||||||
|
<h3>${i18next.t('classementDesClub')} :</h3>
|
||||||
|
|
||||||
|
<figure class="wp-block-table is-style-stripes" style="font-size: 16px">
|
||||||
|
<table style="width: 1200px;overflow: auto"><thead>
|
||||||
|
<tr>
|
||||||
|
<th class="has-text-align-center" data-align="center">${i18next.t('club')}</th>
|
||||||
|
<th class="has-text-align-center" data-align="center">${i18next.t('1er')}</th>
|
||||||
|
<th class="has-text-align-center" data-align="center">${i18next.t('2eme')}</th>
|
||||||
|
<th class="has-text-align-center" data-align="center">${i18next.t('3eme')}</th>
|
||||||
|
<th class="has-text-align-center" data-align="center">${i18next.t('scores')}</th>
|
||||||
|
</tr>
|
||||||
|
</thead><tbody>`
|
||||||
|
for (const club of clubs) {
|
||||||
|
arrayContent += `
|
||||||
|
<tr>
|
||||||
|
<td class="has-text-align-center" data-align="center">${club.name}</td>
|
||||||
|
<td class="has-text-align-center" data-align="center">${club.score[0]}</td>
|
||||||
|
<td class="has-text-align-center" data-align="center">${club.score[1]}</td>
|
||||||
|
<td class="has-text-align-center" data-align="center">${club.score[2]}</td>
|
||||||
|
<td class="has-text-align-center" data-align="center">${club.tt_score}</td>
|
||||||
|
</tr>`
|
||||||
|
}
|
||||||
|
arrayContent += `</tbody></table></figure>`
|
||||||
|
pouleDiv.innerHTML = arrayContent;
|
||||||
|
return pouleDiv;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clubRankPage() {
|
||||||
|
rootDiv.innerHTML = `<h4>${i18next.t('résultatDeLaCompétition')} :</h4><a id="homeLink" href="javascript:void(0);">${i18next.t('back')}</a>`;
|
||||||
|
document.getElementById('homeLink').addEventListener('click', () => setSubPage('home'));
|
||||||
|
|
||||||
|
const content = document.createElement('div');
|
||||||
|
content.style.marginTop = '1em';
|
||||||
|
|
||||||
|
const dataContainer = document.createElement('div');
|
||||||
|
dataContainer.id = 'data-container';
|
||||||
|
|
||||||
|
const loading = startLoading(content);
|
||||||
|
fetch(`${apiUrlRoot}/club/classement`)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(clubs => {
|
||||||
|
console.log(clubs);
|
||||||
|
dataContainer.replaceChildren(buildClubsView(clubs));
|
||||||
|
})
|
||||||
|
.catch(() => dataContainer.replaceChildren(new Text(i18next.t('erreurDeChargementDeLaListe'))))
|
||||||
|
.finally(() => stopLoading(loading));
|
||||||
|
|
||||||
|
content.append(dataContainer);
|
||||||
|
rootDiv.append(content)
|
||||||
|
}
|
||||||
|
|
||||||
function buildCombsView(combs) {
|
function buildCombsView(combs) {
|
||||||
const pouleDiv = document.createElement('div');
|
const pouleDiv = document.createElement('div');
|
||||||
let arrayContent = `
|
let arrayContent = `
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
"actuel": "Current",
|
"actuel": "Current",
|
||||||
"administration": "Administration",
|
"administration": "Administration",
|
||||||
"adresseDuServeur": "Server address",
|
"adresseDuServeur": "Server address",
|
||||||
|
"afficher": "Show",
|
||||||
|
"ajoutAutomatique": "Automatic addition",
|
||||||
"ajouter": "Add",
|
"ajouter": "Add",
|
||||||
"ajouterDesCombattants": "Add fighters",
|
"ajouterDesCombattants": "Add fighters",
|
||||||
"ajouterUn": "Add one",
|
"ajouterUn": "Add one",
|
||||||
@@ -22,7 +24,11 @@
|
|||||||
"cartonNoir": "Black card",
|
"cartonNoir": "Black card",
|
||||||
"cartonRouge": "Red card",
|
"cartonRouge": "Red card",
|
||||||
"catégorie": "Category",
|
"catégorie": "Category",
|
||||||
|
"catégorieDâgeMoyenne": "Middle-aged category",
|
||||||
|
"catégorieSélectionnée": "Selected category",
|
||||||
|
"catégoriesVontêtreCréées": "weight categories will be created",
|
||||||
"ceCartonEstIssuDunCartonDéquipe": "This card comes from a team card, do you really want to delete it?",
|
"ceCartonEstIssuDunCartonDéquipe": "This card comes from a team card, do you really want to delete it?",
|
||||||
|
"certainsCombattantsNontPasDePoidsRenseigné": "Some fighters do not have a weight listed; they will NOT be included in the categories.",
|
||||||
"chrono.+/-...S": "+/- ... s",
|
"chrono.+/-...S": "+/- ... s",
|
||||||
"chrono.+10S": "+10 s",
|
"chrono.+10S": "+10 s",
|
||||||
"chrono.+1S": "+1 s",
|
"chrono.+1S": "+1 s",
|
||||||
@@ -37,6 +43,7 @@
|
|||||||
"chronomètre": "Stopwatch",
|
"chronomètre": "Stopwatch",
|
||||||
"classement": "Ranking",
|
"classement": "Ranking",
|
||||||
"club": "Club",
|
"club": "Club",
|
||||||
|
"combattantsCorrespondentAuxSélectionnés": "fighter(s) match the selections above",
|
||||||
"compétition": "Competition",
|
"compétition": "Competition",
|
||||||
"compétitionManager": "Competition manager",
|
"compétitionManager": "Competition manager",
|
||||||
"config.obs.dossierDesResources": "Resources folder",
|
"config.obs.dossierDesResources": "Resources folder",
|
||||||
@@ -56,11 +63,15 @@
|
|||||||
"conserverUniquementLesMatchsTerminés": "Keep only finished matches",
|
"conserverUniquementLesMatchsTerminés": "Keep only finished matches",
|
||||||
"contre": "vs",
|
"contre": "vs",
|
||||||
"couleur": "Color",
|
"couleur": "Color",
|
||||||
|
"créationDeLaLesCatégories": "Creating the category(ies)",
|
||||||
|
"créerLaPhaseFinaleSilYADesPoules": "Create the final phase if there are groups.",
|
||||||
"créerLesMatchesDeClassement": "Create the ranking matches",
|
"créerLesMatchesDeClassement": "Create the ranking matches",
|
||||||
"créerLesMatchesDeClassement.msg": "Ranking matches have already been set up/played; recreating these matches will delete them all (you will therefore lose any results). Please note down any information you wish to keep.",
|
"créerLesMatchesDeClassement.msg": "Ranking matches have already been set up/played; recreating these matches will delete them all (you will therefore lose any results). Please note down any information you wish to keep.",
|
||||||
"créerLesMatchs": "Create matches",
|
"créerLesMatchs": "Create matches",
|
||||||
|
"créerToutesLesCatégories": "Create all categories",
|
||||||
"date": "Date",
|
"date": "Date",
|
||||||
"demi-finalesEtFinales": "Semi-finals and finals",
|
"demi-finalesEtFinales": "Semi-finals and finals",
|
||||||
|
"depuisUneCatégoriePrédéfinie": "From a predefined category",
|
||||||
"duréePause": "Pause duration",
|
"duréePause": "Pause duration",
|
||||||
"duréeRound": "Round duration",
|
"duréeRound": "Round duration",
|
||||||
"editionDeLaCatégorie": "Edit category",
|
"editionDeLaCatégorie": "Edit category",
|
||||||
@@ -76,19 +87,26 @@
|
|||||||
"etatDesTablesDeMarque": "State of marque tables",
|
"etatDesTablesDeMarque": "State of marque tables",
|
||||||
"exporter": "Export",
|
"exporter": "Export",
|
||||||
"fermer": "Close",
|
"fermer": "Close",
|
||||||
|
"feuilleVierge": "Blank sheet",
|
||||||
"finalesUniquement": "Finals only",
|
"finalesUniquement": "Finals only",
|
||||||
"genre": "Gender",
|
"genre": "Gender",
|
||||||
"genre.f": "F",
|
"genre.f": "F",
|
||||||
"genre.h": "M",
|
"genre.h": "M",
|
||||||
"genre.na": "NA",
|
"genre.na": "NA",
|
||||||
|
"imprimer": "Print",
|
||||||
"individuelle": "Individual",
|
"individuelle": "Individual",
|
||||||
|
"informationCatégorie": "Category information",
|
||||||
"inscrit": "Registered",
|
"inscrit": "Registered",
|
||||||
|
"jusquauRang": "Up to the rank",
|
||||||
"leTournoiServiraDePhaseFinaleAuxPoules": "The tournament will serve as the final phase for the group stage.",
|
"leTournoiServiraDePhaseFinaleAuxPoules": "The tournament will serve as the final phase for the group stage.",
|
||||||
"lesCombattantsEnDehors": "Fighters not participating in the tournament will have a ranking match.",
|
"lesCombattantsEnDehors": "Fighters not participating in the tournament will have a ranking match.",
|
||||||
|
"lesCombattantsEnDehors2": "Fighters outside the ranking tournament will have a ranking match",
|
||||||
"listeDesCartons": "List of cards",
|
"listeDesCartons": "List of cards",
|
||||||
"manche": "Round",
|
"manche": "Round",
|
||||||
"matchPourLesPerdantsDuTournoi": "Match for tournament losers:",
|
"matchPourLesPerdantsDuTournoi": "Match for tournament losers:",
|
||||||
|
"matchTerminé": "Match over",
|
||||||
"matches": "Matches",
|
"matches": "Matches",
|
||||||
|
"modeDeCréation": "Creation method",
|
||||||
"modifier": "Edit",
|
"modifier": "Edit",
|
||||||
"msg1": "There are already matches in this pool; what do you want to do with them?",
|
"msg1": "There are already matches in this pool; what do you want to do with them?",
|
||||||
"neRienConserver": "Keep nothing",
|
"neRienConserver": "Keep nothing",
|
||||||
@@ -97,13 +115,19 @@
|
|||||||
"nomDeLaZone": "Area name",
|
"nomDeLaZone": "Area name",
|
||||||
"nomDeLéquipe": "team name",
|
"nomDeLéquipe": "team name",
|
||||||
"nomDesZonesDeCombat": "Combat zone names <1>(separated by ';')</1>",
|
"nomDesZonesDeCombat": "Combat zone names <1>(separated by ';')</1>",
|
||||||
|
"nombreDeCombattants": "Number of fighters",
|
||||||
"nouvelle...": "New...",
|
"nouvelle...": "New...",
|
||||||
"obs.préfixDesSources": "Source prefix",
|
"obs.préfixDesSources": "Source prefix",
|
||||||
"pays": "Country",
|
"pays": "Country",
|
||||||
|
"personnaliser": "Personalize",
|
||||||
|
"podium": "Podium",
|
||||||
|
"podiumDesClubs": "Club podium",
|
||||||
"poids": "Weight",
|
"poids": "Weight",
|
||||||
"poule": "Pool",
|
"poule": "Pool",
|
||||||
"poulePour": "Pool for: ",
|
"poulePour": "Pool for: ",
|
||||||
"préparation...": "Preparing...",
|
"préparation...": "Preparing...",
|
||||||
|
"quoiImprimer?": "What print?",
|
||||||
|
"remplacer": "Replace",
|
||||||
"rouge": "Red",
|
"rouge": "Red",
|
||||||
"réinitialiser": "Reset",
|
"réinitialiser": "Reset",
|
||||||
"résultat": "Result",
|
"résultat": "Result",
|
||||||
@@ -122,11 +146,13 @@
|
|||||||
"select.sélectionnerDesCombatants": "Select fighters",
|
"select.sélectionnerDesCombatants": "Select fighters",
|
||||||
"select.à": "to",
|
"select.à": "to",
|
||||||
"serveur": "Server",
|
"serveur": "Server",
|
||||||
|
"source": "Source",
|
||||||
"suivant": "Next",
|
"suivant": "Next",
|
||||||
"supprimer": "Delete",
|
"supprimer": "Delete",
|
||||||
"supprimerUn": "Delete one",
|
"supprimerUn": "Delete one",
|
||||||
"sélectionneLesModesDaffichage": "Select display modes",
|
"sélectionneLesModesDaffichage": "Select display modes",
|
||||||
"sélectionner": "Select",
|
"sélectionner": "Select",
|
||||||
|
"taille": "Size",
|
||||||
"team": "Team",
|
"team": "Team",
|
||||||
"terminé": "Finished",
|
"terminé": "Finished",
|
||||||
"texteCopiéDansLePresse": "Text copied to clipboard! Paste it into an HTML tag on your WordPress.",
|
"texteCopiéDansLePresse": "Text copied to clipboard! Paste it into an HTML tag on your WordPress.",
|
||||||
@@ -145,6 +171,9 @@
|
|||||||
"toast.matchs.create.error": "Error while creating matches.",
|
"toast.matchs.create.error": "Error while creating matches.",
|
||||||
"toast.matchs.create.pending": "Creating matches in progress...",
|
"toast.matchs.create.pending": "Creating matches in progress...",
|
||||||
"toast.matchs.create.success": "Matches created successfully.",
|
"toast.matchs.create.success": "Matches created successfully.",
|
||||||
|
"toast.print.error": "Error while preparing print",
|
||||||
|
"toast.print.pending": "Preparing print...",
|
||||||
|
"toast.print.success": "Print ready!",
|
||||||
"toast.team.update.error": "Error while updating team",
|
"toast.team.update.error": "Error while updating team",
|
||||||
"toast.team.update.pending": "Updating team...",
|
"toast.team.update.pending": "Updating team...",
|
||||||
"toast.team.update.success": "Team updated!",
|
"toast.team.update.success": "Team updated!",
|
||||||
@@ -164,6 +193,8 @@
|
|||||||
"tournois": "Tournaments",
|
"tournois": "Tournaments",
|
||||||
"tousLesMatchs": "All matches",
|
"tousLesMatchs": "All matches",
|
||||||
"toutConserver": "Keep all",
|
"toutConserver": "Keep all",
|
||||||
|
"touteLaCatégorie": "The entire category",
|
||||||
|
"toutesLesCatégories": "All categories",
|
||||||
"ttm.admin.obs": "Short click: Download resources. Long click: Create OBS configuration",
|
"ttm.admin.obs": "Short click: Download resources. Long click: Create OBS configuration",
|
||||||
"ttm.admin.scripte": "Copy integration script",
|
"ttm.admin.scripte": "Copy integration script",
|
||||||
"ttm.table.inverserLaPosition": "Reverse fighter positions on this screen",
|
"ttm.table.inverserLaPosition": "Reverse fighter positions on this screen",
|
||||||
@@ -175,6 +206,7 @@
|
|||||||
"téléchargementEnCours": "Downloading...",
|
"téléchargementEnCours": "Downloading...",
|
||||||
"téléchargementTerminé!": "Download completed!",
|
"téléchargementTerminé!": "Download completed!",
|
||||||
"uneCatégorie": "a category",
|
"uneCatégorie": "a category",
|
||||||
|
"uneCatégorieNePeutContenirPlusDe10Combattants": "A category cannot contain more than 10 fighters, please create weight categories.",
|
||||||
"valider": "Validate",
|
"valider": "Validate",
|
||||||
"zone": "Zone",
|
"zone": "Zone",
|
||||||
"zoneDeCombat": "Combat zone"
|
"zoneDeCombat": "Combat zone"
|
||||||
|
|||||||
@@ -619,9 +619,9 @@
|
|||||||
"supprimerLeCompte": "Delete account",
|
"supprimerLeCompte": "Delete account",
|
||||||
"supprimerLeCompte.msg": "Are you sure you want to delete this account?",
|
"supprimerLeCompte.msg": "Are you sure you want to delete this account?",
|
||||||
"sword.none": "$t(sans) / $t(nonDéfinie)",
|
"sword.none": "$t(sans) / $t(nonDéfinie)",
|
||||||
"sword.oneHand": "One hand",
|
"sword.oneHand": "One hand sword",
|
||||||
"sword.saber": "Saber",
|
"sword.saber": "Saber",
|
||||||
"sword.twoHand": "Two hands",
|
"sword.twoHand": "Two hands sword",
|
||||||
"sélectionEnéquipeDeFrance": "Selection in the French team",
|
"sélectionEnéquipeDeFrance": "Selection in the French team",
|
||||||
"sélectionner...": "Select...",
|
"sélectionner...": "Select...",
|
||||||
"toast.edit.error": "Failed to save changes",
|
"toast.edit.error": "Failed to save changes",
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
"--sélectionnerUnClub--": "--Select a club--",
|
"--sélectionnerUnClub--": "--Select a club--",
|
||||||
"--sélectionnerUnCombattant--": "--Select a fighter--",
|
"--sélectionnerUnCombattant--": "--Select a fighter--",
|
||||||
"--sélectionnerUneCatégorie--": "--Select a category--",
|
"--sélectionnerUneCatégorie--": "--Select a category--",
|
||||||
|
"1er": "Gold medal",
|
||||||
|
"2eme": "Silver medal",
|
||||||
|
"3eme": "Bronze medal",
|
||||||
"abs.": "abs.",
|
"abs.": "abs.",
|
||||||
"adversaire": "Opponent",
|
"adversaire": "Opponent",
|
||||||
"aujourdhuià": "Today at {{time}}",
|
"aujourdhuià": "Today at {{time}}",
|
||||||
@@ -11,6 +14,9 @@
|
|||||||
"catégorie": "Category",
|
"catégorie": "Category",
|
||||||
"chargement": "Loading",
|
"chargement": "Loading",
|
||||||
"classement": "Ranking",
|
"classement": "Ranking",
|
||||||
|
"classementClub": "Club ranking",
|
||||||
|
"classementDesClub": "Clubs ranking",
|
||||||
|
"classementFinal": "Final ranking",
|
||||||
"club": "Club",
|
"club": "Club",
|
||||||
"combattant": "Fighter",
|
"combattant": "Fighter",
|
||||||
"combattants": "Fighters",
|
"combattants": "Fighters",
|
||||||
@@ -65,6 +71,5 @@
|
|||||||
"tournois": "Tournaments",
|
"tournois": "Tournaments",
|
||||||
"tousLesCombattants": "All fighters",
|
"tousLesCombattants": "All fighters",
|
||||||
"victoire": "Win",
|
"victoire": "Win",
|
||||||
"victoires": "Wins",
|
"victoires": "Wins"
|
||||||
"classementFinal": "Final standings"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
"actuel": "Actuel",
|
"actuel": "Actuel",
|
||||||
"administration": "Administration",
|
"administration": "Administration",
|
||||||
"adresseDuServeur": "Adresse du serveur",
|
"adresseDuServeur": "Adresse du serveur",
|
||||||
|
"afficher": "Afficher",
|
||||||
|
"ajoutAutomatique": "Ajout automatique",
|
||||||
"ajouter": "Ajouter",
|
"ajouter": "Ajouter",
|
||||||
"ajouterDesCombattants": "Ajouter des combattants",
|
"ajouterDesCombattants": "Ajouter des combattants",
|
||||||
"ajouterUn": "Ajouter un ",
|
"ajouterUn": "Ajouter un ",
|
||||||
@@ -22,7 +24,11 @@
|
|||||||
"cartonNoir": "Carton noir",
|
"cartonNoir": "Carton noir",
|
||||||
"cartonRouge": "Carton rouge",
|
"cartonRouge": "Carton rouge",
|
||||||
"catégorie": "Catégorie",
|
"catégorie": "Catégorie",
|
||||||
|
"catégorieDâgeMoyenne": "Catégorie d'âge moyenne",
|
||||||
|
"catégorieSélectionnée": "Catégorie sélectionnée",
|
||||||
|
"catégoriesVontêtreCréées": "catégories de poids vont être créées",
|
||||||
"ceCartonEstIssuDunCartonDéquipe": "Ce carton est issu d'un carton d'équipe, voulez-vous vraiment le supprimer ?",
|
"ceCartonEstIssuDunCartonDéquipe": "Ce carton est issu d'un carton d'équipe, voulez-vous vraiment le supprimer ?",
|
||||||
|
"certainsCombattantsNontPasDePoidsRenseigné": "Certains combattants n'ont pas de poids renseigné, ils ne seront PAS insert dans les catégories",
|
||||||
"chrono.+/-...S": "+/- ... s",
|
"chrono.+/-...S": "+/- ... s",
|
||||||
"chrono.+10S": "+10 s",
|
"chrono.+10S": "+10 s",
|
||||||
"chrono.+1S": "+1 s",
|
"chrono.+1S": "+1 s",
|
||||||
@@ -37,6 +43,7 @@
|
|||||||
"chronomètre": "Chronomètre",
|
"chronomètre": "Chronomètre",
|
||||||
"classement": "Classement",
|
"classement": "Classement",
|
||||||
"club": "Club",
|
"club": "Club",
|
||||||
|
"combattantsCorrespondentAuxSélectionnés": "combattant(s) correspondent aux sélections ci-dessus.",
|
||||||
"compétition": "Compétition",
|
"compétition": "Compétition",
|
||||||
"compétitionManager": "Compétition manager",
|
"compétitionManager": "Compétition manager",
|
||||||
"config.obs.dossierDesResources": "Dossier des resources",
|
"config.obs.dossierDesResources": "Dossier des resources",
|
||||||
@@ -56,11 +63,15 @@
|
|||||||
"conserverUniquementLesMatchsTerminés": "Conserver uniquement les matchs terminés",
|
"conserverUniquementLesMatchsTerminés": "Conserver uniquement les matchs terminés",
|
||||||
"contre": "contre",
|
"contre": "contre",
|
||||||
"couleur": "Couleur",
|
"couleur": "Couleur",
|
||||||
|
"créationDeLaLesCatégories": "Création de la/les catégories",
|
||||||
|
"créerLaPhaseFinaleSilYADesPoules": "Créer la phase finale s'il y a des poules",
|
||||||
"créerLesMatchesDeClassement": "Créer les matches de classement",
|
"créerLesMatchesDeClassement": "Créer les matches de classement",
|
||||||
"créerLesMatchesDeClassement.msg": "Des matches de classement ont déjà été configurer/jouer, la recréation de ces matches vont tous les supprimer (vous perdre donc les résultats s'il y en a). Mercie de noter de votre côté les informations que vous voulez conserver.",
|
"créerLesMatchesDeClassement.msg": "Des matches de classement ont déjà été configurer/jouer, la recréation de ces matches vont tous les supprimer (vous perdre donc les résultats s'il y en a). Mercie de noter de votre côté les informations que vous voulez conserver.",
|
||||||
"créerLesMatchs": "Créer les matchs",
|
"créerLesMatchs": "Créer les matchs",
|
||||||
|
"créerToutesLesCatégories": "Créer toutes les catégories",
|
||||||
"date": "Date",
|
"date": "Date",
|
||||||
"demi-finalesEtFinales": "Demi-finales et finales",
|
"demi-finalesEtFinales": "Demi-finales et finales",
|
||||||
|
"depuisUneCatégoriePrédéfinie": "Depuis une catégorie prédéfinie",
|
||||||
"duréePause": "Durée pause",
|
"duréePause": "Durée pause",
|
||||||
"duréeRound": "Durée round",
|
"duréeRound": "Durée round",
|
||||||
"editionDeLaCatégorie": "Edition de la catégorie",
|
"editionDeLaCatégorie": "Edition de la catégorie",
|
||||||
@@ -76,19 +87,26 @@
|
|||||||
"etatDesTablesDeMarque": "Etat des tables de marque",
|
"etatDesTablesDeMarque": "Etat des tables de marque",
|
||||||
"exporter": "Exporter",
|
"exporter": "Exporter",
|
||||||
"fermer": "Fermer",
|
"fermer": "Fermer",
|
||||||
|
"feuilleVierge": "Feuille vierge",
|
||||||
"finalesUniquement": "Finales uniquement",
|
"finalesUniquement": "Finales uniquement",
|
||||||
"genre": "Genre",
|
"genre": "Genre",
|
||||||
"genre.f": "F",
|
"genre.f": "F",
|
||||||
"genre.h": "H",
|
"genre.h": "H",
|
||||||
"genre.na": "NA",
|
"genre.na": "NA",
|
||||||
|
"imprimer": "Imprimer",
|
||||||
"individuelle": "Individuelle",
|
"individuelle": "Individuelle",
|
||||||
|
"informationCatégorie": "Information catégorie",
|
||||||
"inscrit": "Inscrit",
|
"inscrit": "Inscrit",
|
||||||
|
"jusquauRang": "Jusqu'au rang",
|
||||||
"leTournoiServiraDePhaseFinaleAuxPoules": "Le tournoi servira de phase finale aux poules",
|
"leTournoiServiraDePhaseFinaleAuxPoules": "Le tournoi servira de phase finale aux poules",
|
||||||
"lesCombattantsEnDehors": "Les combattants en dehors du tournoi auront un match de classement",
|
"lesCombattantsEnDehors": "Les combattants en dehors du tournoi auront un match de classement",
|
||||||
|
"lesCombattantsEnDehors2": "Les combattants en dehors du tournoi de classement auront un match de classement",
|
||||||
"listeDesCartons": "Liste des cartons",
|
"listeDesCartons": "Liste des cartons",
|
||||||
"manche": "Manche",
|
"manche": "Manche",
|
||||||
"matchPourLesPerdantsDuTournoi": "Match pour les perdants du tournoi:",
|
"matchPourLesPerdantsDuTournoi": "Match pour les perdants du tournoi:",
|
||||||
|
"matchTerminé": "Match terminé",
|
||||||
"matches": "Matches",
|
"matches": "Matches",
|
||||||
|
"modeDeCréation": "Mode de création",
|
||||||
"modifier": "Modifier",
|
"modifier": "Modifier",
|
||||||
"msg1": "Il y a déjà des matchs dans cette poule, que voulez-vous faire avec ?",
|
"msg1": "Il y a déjà des matchs dans cette poule, que voulez-vous faire avec ?",
|
||||||
"neRienConserver": "Ne rien conserver",
|
"neRienConserver": "Ne rien conserver",
|
||||||
@@ -97,13 +115,19 @@
|
|||||||
"nomDeLaZone": "Nom de la zone",
|
"nomDeLaZone": "Nom de la zone",
|
||||||
"nomDeLéquipe": "Nom de l'équipe",
|
"nomDeLéquipe": "Nom de l'équipe",
|
||||||
"nomDesZonesDeCombat": "Nom des zones de combat <1>(séparée par des ';')</1>",
|
"nomDesZonesDeCombat": "Nom des zones de combat <1>(séparée par des ';')</1>",
|
||||||
|
"nombreDeCombattants": "Nombre de combattants",
|
||||||
"nouvelle...": "Nouvelle...",
|
"nouvelle...": "Nouvelle...",
|
||||||
"obs.préfixDesSources": "Préfix des sources",
|
"obs.préfixDesSources": "Préfix des sources",
|
||||||
"pays": "Pays",
|
"pays": "Pays",
|
||||||
|
"personnaliser": "Personnaliser",
|
||||||
|
"podium": "Podium",
|
||||||
|
"podiumDesClubs": "Podium des clubs",
|
||||||
"poids": "Poids",
|
"poids": "Poids",
|
||||||
"poule": "Poule",
|
"poule": "Poule",
|
||||||
"poulePour": "Poule pour: ",
|
"poulePour": "Poule pour: ",
|
||||||
"préparation...": "Préparation...",
|
"préparation...": "Préparation...",
|
||||||
|
"quoiImprimer?": "Quoi imprimer ?",
|
||||||
|
"remplacer": "Remplacer",
|
||||||
"rouge": "Rouge",
|
"rouge": "Rouge",
|
||||||
"réinitialiser": "Réinitialiser",
|
"réinitialiser": "Réinitialiser",
|
||||||
"résultat": "Résultat",
|
"résultat": "Résultat",
|
||||||
@@ -122,11 +146,13 @@
|
|||||||
"select.sélectionnerDesCombatants": "Sélectionner des combatants",
|
"select.sélectionnerDesCombatants": "Sélectionner des combatants",
|
||||||
"select.à": "à",
|
"select.à": "à",
|
||||||
"serveur": "Serveur",
|
"serveur": "Serveur",
|
||||||
|
"source": "Source",
|
||||||
"suivant": "Suivant",
|
"suivant": "Suivant",
|
||||||
"supprimer": "Supprimer",
|
"supprimer": "Supprimer",
|
||||||
"supprimerUn": "Supprimer un",
|
"supprimerUn": "Supprimer un",
|
||||||
"sélectionneLesModesDaffichage": "Sélectionne les modes d'affichage",
|
"sélectionneLesModesDaffichage": "Sélectionne les modes d'affichage",
|
||||||
"sélectionner": "Sélectionner",
|
"sélectionner": "Sélectionner",
|
||||||
|
"taille": "Taille",
|
||||||
"team": "Équipe",
|
"team": "Équipe",
|
||||||
"terminé": "Terminé",
|
"terminé": "Terminé",
|
||||||
"texteCopiéDansLePresse": "Texte copié dans le presse-papier ! Collez-le dans une balise HTML sur votre WordPress.",
|
"texteCopiéDansLePresse": "Texte copié dans le presse-papier ! Collez-le dans une balise HTML sur votre WordPress.",
|
||||||
@@ -145,6 +171,9 @@
|
|||||||
"toast.matchs.create.error": "Erreur lors de la création des matchs.",
|
"toast.matchs.create.error": "Erreur lors de la création des matchs.",
|
||||||
"toast.matchs.create.pending": "Création des matchs en cours...",
|
"toast.matchs.create.pending": "Création des matchs en cours...",
|
||||||
"toast.matchs.create.success": "Matchs créés avec succès.",
|
"toast.matchs.create.success": "Matchs créés avec succès.",
|
||||||
|
"toast.print.error": "Erreur lors de la génération du PDF",
|
||||||
|
"toast.print.pending": "Génération du PDF en cours...",
|
||||||
|
"toast.print.success": "PDF généré !",
|
||||||
"toast.team.update.error": "Erreur lors de la mise à jour de l'équipe",
|
"toast.team.update.error": "Erreur lors de la mise à jour de l'équipe",
|
||||||
"toast.team.update.pending": "Mise à jour de l'équipe...",
|
"toast.team.update.pending": "Mise à jour de l'équipe...",
|
||||||
"toast.team.update.success": "Équipe mise à jour !",
|
"toast.team.update.success": "Équipe mise à jour !",
|
||||||
@@ -164,6 +193,8 @@
|
|||||||
"tournois": "Tournois",
|
"tournois": "Tournois",
|
||||||
"tousLesMatchs": "Tous les matchs",
|
"tousLesMatchs": "Tous les matchs",
|
||||||
"toutConserver": "Tout conserver",
|
"toutConserver": "Tout conserver",
|
||||||
|
"touteLaCatégorie": "Toute la catégorie",
|
||||||
|
"toutesLesCatégories": "Toutes les catégories",
|
||||||
"ttm.admin.obs": "Clique court : Télécharger les ressources. Clique long : Créer la configuration obs",
|
"ttm.admin.obs": "Clique court : Télécharger les ressources. Clique long : Créer la configuration obs",
|
||||||
"ttm.admin.scripte": "Copier le scripte d'intégration",
|
"ttm.admin.scripte": "Copier le scripte d'intégration",
|
||||||
"ttm.table.inverserLaPosition": "Inverser la position des combattants sur cette écran",
|
"ttm.table.inverserLaPosition": "Inverser la position des combattants sur cette écran",
|
||||||
@@ -175,6 +206,7 @@
|
|||||||
"téléchargementEnCours": "Téléchargement en cours...",
|
"téléchargementEnCours": "Téléchargement en cours...",
|
||||||
"téléchargementTerminé!": "Téléchargement terminé !",
|
"téléchargementTerminé!": "Téléchargement terminé !",
|
||||||
"uneCatégorie": "une catégorie",
|
"uneCatégorie": "une catégorie",
|
||||||
|
"uneCatégorieNePeutContenirPlusDe10Combattants": "Une catégorie ne peut contenir plus de 10 combattants, veuillez créer des catégories de poids.",
|
||||||
"valider": "Valider",
|
"valider": "Valider",
|
||||||
"zone": "Zone",
|
"zone": "Zone",
|
||||||
"zoneDeCombat": "Zone de combat"
|
"zoneDeCombat": "Zone de combat"
|
||||||
|
|||||||
@@ -623,9 +623,9 @@
|
|||||||
"supprimerLeCompte": "Supprimer le compte",
|
"supprimerLeCompte": "Supprimer le compte",
|
||||||
"supprimerLeCompte.msg": "Êtes-vous sûr de vouloir supprimer ce compte ?",
|
"supprimerLeCompte.msg": "Êtes-vous sûr de vouloir supprimer ce compte ?",
|
||||||
"sword.none": "$t(sans) / $t(nonDéfinie)",
|
"sword.none": "$t(sans) / $t(nonDéfinie)",
|
||||||
"sword.oneHand": "Une main",
|
"sword.oneHand": "Épée une main",
|
||||||
"sword.saber": "Sabre",
|
"sword.saber": "Sabre",
|
||||||
"sword.twoHand": "Deux mains",
|
"sword.twoHand": "Épée deux mains",
|
||||||
"sélectionEnéquipeDeFrance": "Sélection en équipe de France",
|
"sélectionEnéquipeDeFrance": "Sélection en équipe de France",
|
||||||
"sélectionner...": "Sélectionner...",
|
"sélectionner...": "Sélectionner...",
|
||||||
"toast.edit.error": "Échec de l'enregistrement des modifications",
|
"toast.edit.error": "Échec de l'enregistrement des modifications",
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
"--sélectionnerUnClub--": "--Sélectionner un club--",
|
"--sélectionnerUnClub--": "--Sélectionner un club--",
|
||||||
"--sélectionnerUnCombattant--": "--Sélectionner un combattant--",
|
"--sélectionnerUnCombattant--": "--Sélectionner un combattant--",
|
||||||
"--sélectionnerUneCatégorie--": "--Sélectionner une catégorie--",
|
"--sélectionnerUneCatégorie--": "--Sélectionner une catégorie--",
|
||||||
|
"1er": "Médaille d'or",
|
||||||
|
"2eme": "Médaille d'argent",
|
||||||
|
"3eme": "Médaille de bronze",
|
||||||
"abs.": "abs.",
|
"abs.": "abs.",
|
||||||
"adversaire": "Adversaire",
|
"adversaire": "Adversaire",
|
||||||
"aujourdhuià": "Aujourd'hui à {{time}}",
|
"aujourdhuià": "Aujourd'hui à {{time}}",
|
||||||
@@ -11,6 +14,9 @@
|
|||||||
"catégorie": "Catégorie",
|
"catégorie": "Catégorie",
|
||||||
"chargement": "Chargement",
|
"chargement": "Chargement",
|
||||||
"classement": "Classement",
|
"classement": "Classement",
|
||||||
|
"classementClub": "Classement club",
|
||||||
|
"classementDesClub": "Classement des clubs",
|
||||||
|
"classementFinal": "Classement final",
|
||||||
"club": "Club",
|
"club": "Club",
|
||||||
"combattant": "Combattant",
|
"combattant": "Combattant",
|
||||||
"combattants": "Combattants",
|
"combattants": "Combattants",
|
||||||
@@ -65,6 +71,5 @@
|
|||||||
"tournois": "Tournois",
|
"tournois": "Tournois",
|
||||||
"tousLesCombattants": "Tous les combattants",
|
"tousLesCombattants": "Tous les combattants",
|
||||||
"victoire": "Victoire",
|
"victoire": "Victoire",
|
||||||
"victoires": "Victoires",
|
"victoires": "Victoires"
|
||||||
"classementFinal": "Classement final"
|
|
||||||
}
|
}
|
||||||
|
|||||||
84
src/main/webapp/public/processor-dtmf.js
Normal file
84
src/main/webapp/public/processor-dtmf.js
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
class ProcessorDTMF extends AudioWorkletProcessor {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.sampleRate = sampleRate;
|
||||||
|
this.symbolDuration = 0.03; // 50 ms par symbole
|
||||||
|
this.samplesPerSymbol = Math.floor(this.sampleRate * this.symbolDuration);
|
||||||
|
this.encodeLowPrio = [];
|
||||||
|
this.symbolSamples = [];
|
||||||
|
this.lastBlackStep = 0;
|
||||||
|
this.port.onmessage = (e) => {
|
||||||
|
if (e.data.type === 'encode') {
|
||||||
|
this.symbolSamples.push(...this.encodeSymbols(e.data.symbols));
|
||||||
|
this.symbolSamples.push(...this.encodeBlack(this.sampleRate * 0.02));
|
||||||
|
}
|
||||||
|
if (e.data.type === 'encodeLowPrio') {
|
||||||
|
this.encodeLowPrio.push(e.data.symbols);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
dtmfFrequencies = [
|
||||||
|
[697, 770, 852, 941], // Fréquences basses
|
||||||
|
[1209, 1336, 1477, 1633] // Fréquences hautes
|
||||||
|
];
|
||||||
|
|
||||||
|
encodeSymbols(symbols) {
|
||||||
|
const samples = [];
|
||||||
|
for (const symbol of symbols) {
|
||||||
|
const lf = this.dtmfFrequencies[0][symbol % 4]; // Fréquence basse
|
||||||
|
const hf = this.dtmfFrequencies[1][Math.floor(symbol / 4)]; // Fréquence haute
|
||||||
|
// console.log(`Symbol: ${symbol}, LF: ${lf} Hz, HF: ${hf} Hz`);
|
||||||
|
for (let i = 0; i < this.samplesPerSymbol; i++) {
|
||||||
|
const t = i / this.sampleRate;
|
||||||
|
const t2 = (this.lastBlackStep + i) / this.sampleRate;
|
||||||
|
samples.push(0.5 * Math.sin(2 * Math.PI * lf * t) + 0.5 * Math.sin(2 * Math.PI * hf * t) // Signal DTMF
|
||||||
|
+ Math.sin(2 * Math.PI * 150 * t2) * (0.0625 * (Math.sin(2 * Math.PI * 0.5 * t2) + 1))); // Ajouter un signal à 150 Hz pour le "black"
|
||||||
|
}
|
||||||
|
this.lastBlackStep += this.samplesPerSymbol;
|
||||||
|
|
||||||
|
// ajouter un silence de 10 ms entre les symboles
|
||||||
|
samples.push(...this.encodeBlack(this.sampleRate * 0.01)); // Silence
|
||||||
|
}
|
||||||
|
return samples;
|
||||||
|
}
|
||||||
|
|
||||||
|
encodeBlack(size) {
|
||||||
|
const samples = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < size; i++) {
|
||||||
|
const t = (this.lastBlackStep + i) / this.sampleRate;
|
||||||
|
samples.push(Math.sin(2 * Math.PI * 150 * t) * (0.0625 * (Math.sin(2 * Math.PI * 0.5 * t) + 1))); // Signal à 350 Hz pour le "black"
|
||||||
|
}
|
||||||
|
this.lastBlackStep += size;
|
||||||
|
this.lastBlackStep %= this.sampleRate * 2; // Réinitialiser tous les 2 secondes pour éviter les débordements
|
||||||
|
|
||||||
|
return samples;
|
||||||
|
}
|
||||||
|
|
||||||
|
process(inputs, outputs, parameters) {
|
||||||
|
const output = outputs[0]; // output est un tableau de canaux (ex: [Float32Array, ...])
|
||||||
|
const channelData = output[0]; // Accéder au premier canal (mono)
|
||||||
|
|
||||||
|
if (this.symbolSamples.length === 0 && this.encodeLowPrio.length > 0) {
|
||||||
|
this.symbolSamples.push(...this.encodeSymbols(this.encodeLowPrio.shift()));
|
||||||
|
this.symbolSamples.push(...this.encodeBlack(this.sampleRate * 0.02));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.symbolSamples.length === 0) {
|
||||||
|
const samples = this.encodeBlack(channelData.length)
|
||||||
|
for (let i = 0; i < channelData.length; i++) {
|
||||||
|
channelData[i] = samples[i] || 0;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < channelData.length; i++) {
|
||||||
|
channelData[i] = this.symbolSamples.shift() || 0; // Prendre le prochain échantillon ou 0 si vide
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
registerProcessor('dtmf-processor', ProcessorDTMF);
|
||||||
@@ -164,3 +164,27 @@ const ProtectionSelector = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default ProtectionSelector;
|
export default ProtectionSelector;
|
||||||
|
|
||||||
|
|
||||||
|
export function getMandatoryProtectionsList(mandatoryProtection, shield, t) {
|
||||||
|
const protections = [];
|
||||||
|
const isOn = (bit) => (mandatoryProtection & (1 << (bit - 1))) !== 0;
|
||||||
|
|
||||||
|
if (isOn(1)) protections.push(t('casque', {ns: "common"}));
|
||||||
|
if (isOn(2)) protections.push(t('gorgerin', {ns: "common"}));
|
||||||
|
if (isOn(3)) protections.push(t('coquilleProtectionPelvienne', {ns: "common"}));
|
||||||
|
if (isOn(4) && !shield) protections.push(t('gants', {ns: "common"}));
|
||||||
|
if (isOn(4) && shield) protections.push(t('gantMainsArmées', {ns: "common"}));
|
||||||
|
if (isOn(5) && shield) protections.push(t('gantMainBouclier', {ns: "common"}));
|
||||||
|
if (isOn(6)) protections.push(t('plastron', {ns: "common"}));
|
||||||
|
if (isOn(7) && !shield) protections.push(t('protectionDeBras', {ns: "common"}));
|
||||||
|
if (isOn(7) && shield) protections.push(t('protectionDeBrasArmé', {ns: "common"}));
|
||||||
|
if (isOn(8) && shield) protections.push(t('protectionDeBrasDeBouclier', {ns: "common"}));
|
||||||
|
if (isOn(9)) protections.push(t('protectionDeJambes', {ns: "common"}));
|
||||||
|
if (isOn(10)) protections.push(t('protectionDeGenoux', {ns: "common"}));
|
||||||
|
if (isOn(11)) protections.push(t('protectionDeCoudes', {ns: "common"}));
|
||||||
|
if (isOn(12)) protections.push(t('protectionDorsale', {ns: "common"}));
|
||||||
|
if (isOn(13)) protections.push(t('protectionDePieds', {ns: "common"}));
|
||||||
|
|
||||||
|
return protections;
|
||||||
|
}
|
||||||
|
|||||||
207
src/main/webapp/src/components/cm/AudioEncoder.jsx
Normal file
207
src/main/webapp/src/components/cm/AudioEncoder.jsx
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
import React, {useEffect, useRef, useState} from 'react';
|
||||||
|
import {useTablesState} from "../../pages/competition/editor/StateWindow.jsx";
|
||||||
|
import {timePrint} from "../../utils/Tools.js";
|
||||||
|
|
||||||
|
let initialized = false;
|
||||||
|
const AudioEncoder = () => {
|
||||||
|
const audioContextRef = useRef(null);
|
||||||
|
const qpskProcessorRef = useRef(null);
|
||||||
|
const [isReady, setIsReady] = useState(false);
|
||||||
|
const [table, setTable] = useState('1');
|
||||||
|
|
||||||
|
const lastSend = useRef({id: 0});
|
||||||
|
const {state} = useTablesState();
|
||||||
|
|
||||||
|
// Initialisation de l'AudioContext et du AudioWorklet
|
||||||
|
useEffect(() => {
|
||||||
|
const initAudio = async () => {
|
||||||
|
if (initialized)
|
||||||
|
return;
|
||||||
|
console.log("Initialisation de l'audio après interaction utilisateur");
|
||||||
|
initialized = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
|
||||||
|
console.log("AudioContext state:", audioContext.state);
|
||||||
|
|
||||||
|
if (audioContext.state === 'suspended') {
|
||||||
|
await audioContext.resume(); // Nécessaire pour démarrer le contexte audio
|
||||||
|
console.log("AudioContext resumed");
|
||||||
|
}
|
||||||
|
|
||||||
|
await audioContext.audioWorklet.addModule('/processor-dtmf.js');
|
||||||
|
await new Promise(r => setTimeout(r, 100));
|
||||||
|
const processor = new AudioWorkletNode(audioContext, 'dtmf-processor');
|
||||||
|
processor.connect(audioContext.destination);
|
||||||
|
qpskProcessorRef.current = processor;
|
||||||
|
audioContextRef.current = audioContext;
|
||||||
|
|
||||||
|
await new Promise(r => setTimeout(r, 100));
|
||||||
|
setIsReady(true);
|
||||||
|
} catch (err) {
|
||||||
|
initialized = false;
|
||||||
|
console.error("Erreur d'initialisation AudioWorklet:", err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initialiser après un clic utilisateur (pour contourner les restrictions des navigateurs)
|
||||||
|
const handleUserInteraction = () => {
|
||||||
|
document.removeEventListener('click', handleUserInteraction);
|
||||||
|
initAudio();
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener('click', handleUserInteraction);
|
||||||
|
return () => {
|
||||||
|
if (audioContextRef.current?.state !== 'closed') {
|
||||||
|
audioContextRef.current?.close();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Fonction pour encoder et envoyer un message
|
||||||
|
const encodeAndSend = (data, lowPrio = false) => {
|
||||||
|
if (!isReady) return;
|
||||||
|
|
||||||
|
const symbols = Array.from(data).flatMap(byte => [byte >> 4, byte & 0x0F]);
|
||||||
|
console.log("Bits :", symbols);
|
||||||
|
|
||||||
|
// 5. Envoyer les symboles au processeur audio
|
||||||
|
if (lowPrio) {
|
||||||
|
qpskProcessorRef.current.port.postMessage({type: 'encodeLowPrio', symbols});
|
||||||
|
} else {
|
||||||
|
qpskProcessorRef.current.port.postMessage({type: 'encode', symbols});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const t = state.find(o => o.liceName === table)
|
||||||
|
if (!t)
|
||||||
|
return
|
||||||
|
|
||||||
|
// console.log("Data for table 1:", t, t.selectedMatch)
|
||||||
|
const last = lastSend.current;
|
||||||
|
if (t.selectedMatch !== last.id) {
|
||||||
|
clearTimeout(last.time_id)
|
||||||
|
last.time_id = setTimeout(() => {
|
||||||
|
last.time_id = null
|
||||||
|
if (t.selectedMatch === null) {
|
||||||
|
encodeAndSend(new Uint8Array([0, 0, 0, 0, 0, 0, 0]));
|
||||||
|
} else {
|
||||||
|
const data = [];
|
||||||
|
for (let i = 0; i < 7; i++) { // MaxSafeInteger est sur 7 bytes (53 bits de précision)
|
||||||
|
data.unshift(Number((BigInt(t.selectedMatch) >> BigInt(i * 8)) & 0xFFn))
|
||||||
|
}
|
||||||
|
data[0] = data[0] & 0x1F // 3 premiers bits à 0 pour différencier des autres types de messages (ex: score, chrono, etc.)
|
||||||
|
// console.log("Data to send (selectedMatch):", data)
|
||||||
|
encodeAndSend(new Uint8Array(data), false);
|
||||||
|
}
|
||||||
|
}, 250)
|
||||||
|
|
||||||
|
last.id = t.selectedMatch
|
||||||
|
}
|
||||||
|
|
||||||
|
const isRunning = (c) => c.startTime !== 0
|
||||||
|
const getTime = (c) => {
|
||||||
|
if (c.startTime === 0)
|
||||||
|
return c.time
|
||||||
|
return c.time + Date.now() - c.startTime
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeStr = last.chronoState ? timePrint((last.chronoState.state === 2) ? last.chronoState.configPause : last.chronoState.configTime - getTime(last.chronoState)) : "-"
|
||||||
|
const timeStr2 = timePrint((t.chronoState.state === 2) ? t.chronoState.configPause : t.chronoState.configTime - getTime(t.chronoState))
|
||||||
|
|
||||||
|
if (timeStr !== timeStr2) {
|
||||||
|
clearInterval(lastSend.current.time_chronoInter)
|
||||||
|
clearTimeout(lastSend.current.time_chronoText)
|
||||||
|
lastSend.current.time_chronoText = setTimeout(() => {
|
||||||
|
let time = (t.chronoState.state === 2) ? t.chronoState.configPause : t.chronoState.configTime - getTime(t.chronoState)
|
||||||
|
const ms = time % 1000
|
||||||
|
time = (time - ms) / 1000
|
||||||
|
|
||||||
|
const data = [((time >> 8) & 0x1F) + 0x20, time & 0xFF];
|
||||||
|
// console.log("Data to send (time):", data)
|
||||||
|
encodeAndSend(new Uint8Array(data));
|
||||||
|
|
||||||
|
lastSend.current.time_chronoInter = setInterval(() => {
|
||||||
|
let time = (t.chronoState.state === 2) ? t.chronoState.configPause : t.chronoState.configTime - getTime(t.chronoState)
|
||||||
|
const ms = time % 1000
|
||||||
|
time = (time - ms) / 1000
|
||||||
|
|
||||||
|
const data = [((time >> 8) & 0x1F) + 0x20, time & 0xFF];
|
||||||
|
// console.log("Data to send (time-auto):", data)
|
||||||
|
encodeAndSend(new Uint8Array(data), true);
|
||||||
|
}, 10000);
|
||||||
|
}, 150)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!last.chronoState || last.chronoState.state !== t.chronoState.state || isRunning(last.chronoState) !== isRunning(t.chronoState)) {
|
||||||
|
let time = (t.chronoState.state === 2) ? t.chronoState.configPause : t.chronoState.configTime - getTime(t.chronoState)
|
||||||
|
const ms = Math.round((time % 1000) / 250)
|
||||||
|
|
||||||
|
const data = [0x40 + (t.chronoState.state << 3) + (isRunning(t.chronoState) << 2) + (ms & 0x03)];
|
||||||
|
// console.log("Data to send (chrono state):", data)
|
||||||
|
encodeAndSend(new Uint8Array(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
last.chronoState = {...t.chronoState}
|
||||||
|
|
||||||
|
// console.log(timeStr, timeStr2)
|
||||||
|
// console.log(last.chronoState, t.chronoState)
|
||||||
|
|
||||||
|
if (last.scoreRouge !== t.scoreState.scoreRouge) {
|
||||||
|
clearTimeout(last.time_sr)
|
||||||
|
last.time_sr = setTimeout(() => {
|
||||||
|
if (last.scoreRouge !== t.scoreState.scoreRouge) {
|
||||||
|
const b = t.scoreState.scoreRouge < 0
|
||||||
|
const s = b ? -t.scoreState.scoreRouge : t.scoreState.scoreRouge
|
||||||
|
const data = [0x60 + (b << 3) + ((s >> 8) & 0x07), (s & 0xFF)];
|
||||||
|
console.log("Data to send (score r):", data)
|
||||||
|
encodeAndSend(new Uint8Array(data), true);
|
||||||
|
last.scoreRouge = t.scoreState.scoreRouge
|
||||||
|
}
|
||||||
|
}, 250)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (last.scoreBleu !== t.scoreState.scoreBleu) {
|
||||||
|
clearTimeout(last.time_sb)
|
||||||
|
last.time_sb = setTimeout(() => {
|
||||||
|
if (last.scoreBleu !== t.scoreState.scoreBleu) {
|
||||||
|
const b = t.scoreState.scoreBleu < 0
|
||||||
|
const s = b ? -t.scoreState.scoreBleu : t.scoreState.scoreBleu
|
||||||
|
const data = [0x60 + 0x10 + (b << 3) + ((s >> 8) & 0x07), (s & 0xFF)];
|
||||||
|
console.log("Data to send (score b):", data)
|
||||||
|
encodeAndSend(new Uint8Array(data), true);
|
||||||
|
last.scoreBleu = t.scoreState.scoreBleu
|
||||||
|
}
|
||||||
|
}, 250)
|
||||||
|
}
|
||||||
|
|
||||||
|
}, [state])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const last = lastSend.current;
|
||||||
|
clearTimeout(last.scoreBleu)
|
||||||
|
clearTimeout(last.scoreRouge)
|
||||||
|
clearTimeout(last.time_id)
|
||||||
|
clearTimeout(last.time_chronoText)
|
||||||
|
clearInterval(last.time_chronoInter)
|
||||||
|
last.id = 0
|
||||||
|
last.scoreBleu = 0
|
||||||
|
last.scoreRouge = 0
|
||||||
|
last.chronoState = null
|
||||||
|
}, [table])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={table}
|
||||||
|
onChange={(e) => setTable(e.target.value)}
|
||||||
|
placeholder="Nom de la zone"
|
||||||
|
/>
|
||||||
|
<span>{isReady ? 'Actif' : "Zone non configurée"}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AudioEncoder;
|
||||||
802
src/main/webapp/src/components/cm/AutoCatModalContent.jsx
Normal file
802
src/main/webapp/src/components/cm/AutoCatModalContent.jsx
Normal file
@@ -0,0 +1,802 @@
|
|||||||
|
import React, {useEffect, useId, useState} from "react";
|
||||||
|
import {Trans, useTranslation} from "react-i18next";
|
||||||
|
import {useCountries} from "../../hooks/useCountries.jsx";
|
||||||
|
import {ListPresetSelect} from "./ListPresetSelect.jsx";
|
||||||
|
import {CatList, getCatName} from "../../utils/Tools.js";
|
||||||
|
import {useCombs} from "../../hooks/useComb.jsx";
|
||||||
|
import {toast} from "react-toastify";
|
||||||
|
import {build_tree} from "../../utils/TreeUtils.js";
|
||||||
|
import {createMatch} from "../../utils/CompetitionTools.js";
|
||||||
|
import {useRequestWS, useWS} from "../../hooks/useWS.jsx";
|
||||||
|
import {AxiosError} from "../AxiosError.jsx";
|
||||||
|
|
||||||
|
export function AutoCatModalContent({data, groups, setGroups, defaultPreset = -1}) {
|
||||||
|
const country = useCountries('fr')
|
||||||
|
const {t} = useTranslation("cm");
|
||||||
|
|
||||||
|
const [country_, setCountry_] = useState("")
|
||||||
|
const [gender, setGender] = useState({H: true, F: true, NA: true})
|
||||||
|
const [cat, setCat] = useState([])
|
||||||
|
const [weightMin, setWeightMin] = useState(0)
|
||||||
|
const [weightMax, setWeightMax] = useState(0)
|
||||||
|
const [team, setTeam] = useState(false)
|
||||||
|
const [preset, setPreset] = useState(-1)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setPreset(defaultPreset)
|
||||||
|
}, [defaultPreset])
|
||||||
|
|
||||||
|
const setCat_ = (e, index) => {
|
||||||
|
if (e.target.checked) {
|
||||||
|
if (!cat.includes(index)) {
|
||||||
|
setCat([...cat, index])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setCat(cat.filter(c => c !== index))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyFilter(dataIn, dataOut) {
|
||||||
|
dataIn.forEach(comb_ => {
|
||||||
|
const comb = data.find(d => d.id === comb_.id);
|
||||||
|
if (comb == null)
|
||||||
|
return;
|
||||||
|
if ((country_ === "" || comb.country === country_)
|
||||||
|
&& (gender.H && comb.genre === 'H' || gender.F && comb.genre === 'F' || gender.NA && comb.genre === 'NA')
|
||||||
|
&& (cat.includes(Math.min(CatList.length, CatList.indexOf(comb.categorie) + comb.overCategory)))
|
||||||
|
&& (weightMin === 0 || comb.weight !== null && comb.weight >= weightMin)
|
||||||
|
&& (weightMax === 0 || comb.weight !== null && comb.weight <= weightMax)
|
||||||
|
&& ((comb.teamMembers == null || comb.teamMembers.length === 0) !== team)
|
||||||
|
&& (preset === -1 || comb.categoriesInscrites.includes(preset))) {
|
||||||
|
dataOut.push(comb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const dispoFiltered = [];
|
||||||
|
if (data != null)
|
||||||
|
applyFilter(data, dispoFiltered);
|
||||||
|
|
||||||
|
const handleSubmit = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const toReplace = makePoule(dispoFiltered, groups);
|
||||||
|
setGroups(prev => [...prev.filter(g => !toReplace.some(r => r.id === g.id)), ...toReplace]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleReplace = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const toReplace = makePoule(dispoFiltered, []);
|
||||||
|
setGroups(prev => [...prev.map(g => ({id: g.id, poule: "-"})).filter(g => !toReplace.some(r => r.id === g.id)), ...toReplace]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>
|
||||||
|
<div className="modal-header">
|
||||||
|
<h1 className="modal-title fs-5" id="autoCatModalLabel">{t('ajoutAutomatique')}</h1>
|
||||||
|
<button type="button" className="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div className="modal-body">
|
||||||
|
<div className="d-flex flex-wrap justify-content-around mb-1">
|
||||||
|
<div style={{width: "12em"}}>
|
||||||
|
<label htmlFor="inputState0" className="form-label">{t('pays')}</label>
|
||||||
|
<select id="inputState0" className="form-select" value={country_} onChange={(e) => setCountry_(e.target.value)}>
|
||||||
|
<option value={""}>{t('--Tous--')}</option>
|
||||||
|
{country && Object.keys(country).sort((a, b) => {
|
||||||
|
if (a < b) return -1
|
||||||
|
if (a > b) return 1
|
||||||
|
return 0
|
||||||
|
}).map((key, _) => {
|
||||||
|
return (<option key={key} value={key}>{country[key]}</option>)
|
||||||
|
})}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ListPresetSelect value={preset} onChange={setPreset}/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="d-flex flex-wrap justify-content-around mb-3">
|
||||||
|
<div>
|
||||||
|
<label className="form-label">{t('genre')}</label>
|
||||||
|
<div className="d-flex align-items-center">
|
||||||
|
<div className="form-check" style={{marginRight: '10px'}}>
|
||||||
|
<input className="form-check-input" type="checkbox" id="gridCheck" checked={gender.H}
|
||||||
|
onChange={e => setGender((prev) => {
|
||||||
|
return {...prev, H: e.target.checked}
|
||||||
|
})}/>
|
||||||
|
<label className="form-check-label" htmlFor="gridCheck">{t('genre.h')}</label>
|
||||||
|
</div>
|
||||||
|
<div className="form-check" style={{marginRight: '10px'}}>
|
||||||
|
<input className="form-check-input" type="checkbox" id="gridCheck2" checked={gender.F}
|
||||||
|
onChange={e => setGender((prev) => {
|
||||||
|
return {...prev, F: e.target.checked}
|
||||||
|
})}/>
|
||||||
|
<label className="form-check-label" htmlFor="gridCheck2">{t('genre.f')}</label>
|
||||||
|
</div>
|
||||||
|
<div className="form-check">
|
||||||
|
<input className="form-check-input" type="checkbox" id="gridCheck3" checked={gender.NA}
|
||||||
|
onChange={e => setGender((prev) => {
|
||||||
|
return {...prev, NA: e.target.checked}
|
||||||
|
})}/>
|
||||||
|
<label className="form-check-label" htmlFor="gridCheck3">{t('genre.na')}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="form-label">{t('team')}</label>
|
||||||
|
<div className="d-flex align-items-center">
|
||||||
|
<div className="form-check" style={{marginRight: '10px'}}>
|
||||||
|
<input className="form-check-input" type="checkbox" id="gridCheck" checked={team}
|
||||||
|
onChange={e => setTeam(e.target.checked)}/>
|
||||||
|
<label className="form-check-label" htmlFor="gridCheck">{t('team')}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label htmlFor="input5" className="form-label">{t('poids')}</label>
|
||||||
|
<div className="row-cols-sm-auto d-flex align-items-center">
|
||||||
|
<div style={{width: "4.25em"}}><input type="number" className="form-control" id="input5" value={weightMin} min="0"
|
||||||
|
name="999"
|
||||||
|
onChange={e => setWeightMin(Number(e.target.value))}/></div>
|
||||||
|
<div><span>{t('select.à')}</span></div>
|
||||||
|
<div style={{width: "4.25em"}}><input type="number" className="form-control" value={weightMax} min="0" name="999"
|
||||||
|
onChange={e => setWeightMax(Number(e.target.value))}/></div>
|
||||||
|
<div><small>{t('select.msg1')}</small></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="d-flex flex-wrap justify-content-around mb-1">
|
||||||
|
<div className="d-flex flex-wrap mb-3">
|
||||||
|
<label htmlFor="inputState2" className="form-label align-self-center" style={{margin: "0 0.5em 0 0"}}>
|
||||||
|
{t('catégorie')} :
|
||||||
|
</label>
|
||||||
|
{CatList.map((cat_, index) => {
|
||||||
|
return <div key={index} className="input-group"
|
||||||
|
style={{display: "contents"}}>
|
||||||
|
<div className="input-group-text">
|
||||||
|
<input className="form-check-input mt-0" type="checkbox"
|
||||||
|
id={"categoriesInput" + index} checked={cat.includes(index)} aria-label={getCatName(cat_)}
|
||||||
|
onChange={e => setCat_(e, index)}/>
|
||||||
|
<label style={{marginLeft: "0.5em"}} htmlFor={"categoriesInput" + index}>{getCatName(cat_)}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span>{dispoFiltered.length} {t('combattantsCorrespondentAuxSélectionnés')} {dispoFiltered.length > 10 &&
|
||||||
|
<span style={{color: "red"}}>{t('uneCatégorieNePeutContenirPlusDe10Combattants')}</span>}</span>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div className="modal-footer">
|
||||||
|
<button type="button" className="btn btn-secondary" data-bs-dismiss="modal">{t('fermer')}</button>
|
||||||
|
<button type="submit" className="btn btn-primary" data-bs-dismiss="modal" onClick={handleSubmit}
|
||||||
|
disabled={dispoFiltered.length <= 0 || dispoFiltered.length > 10}>{t('ajouter')}</button>
|
||||||
|
<button type="submit" className="btn btn-warning" data-bs-dismiss="modal" onClick={handleReplace}
|
||||||
|
disabled={dispoFiltered.length <= 0 || dispoFiltered.length > 10}>{t('remplacer')}</button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function makePoule(combIn, groups) {
|
||||||
|
combIn = combIn.sort(() => Math.random() - 0.5);
|
||||||
|
const maxInPoule = Math.ceil(combIn.length / 2);
|
||||||
|
const out = []
|
||||||
|
|
||||||
|
const pa = [];
|
||||||
|
const pb = [];
|
||||||
|
|
||||||
|
let nameA;
|
||||||
|
let nameB;
|
||||||
|
groups.forEach(g => {
|
||||||
|
const existsInCombIn = combIn.some(c => c.id === g.id);
|
||||||
|
if (existsInCombIn) {
|
||||||
|
if ((pa.length === 0 || g.poule === nameA) && pa.length < maxInPoule) {
|
||||||
|
nameA = g.poule || "1";
|
||||||
|
pa.push(g.id);
|
||||||
|
} else if ((pb.length === 0 || g.poule === nameB) && pb.length < maxInPoule) {
|
||||||
|
if (!(nameA === (g.poule || (nameA === "1" ? "2" : "1")))) {
|
||||||
|
nameB = g.poule || (nameA === "1" ? "2" : "1");
|
||||||
|
pb.push(g.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
nameA = nameA || (nameB === "1" ? "2" : "1");
|
||||||
|
nameB = nameB || (nameA === "1" ? "2" : "1");
|
||||||
|
|
||||||
|
if (combIn.length <= 5) {
|
||||||
|
combIn.forEach(c => {
|
||||||
|
if (!pa.includes(c.id))
|
||||||
|
pa.push(c.id)
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
for (const c of combIn) {
|
||||||
|
if (pa.includes(c.id) || pb.includes(c.id))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const club = c.club_str || (c.teamMembers && c.teamMembers[0].club_str) || "";
|
||||||
|
|
||||||
|
const countInPa = pa.filter(p => (p.club_str || (p.teamMembers && p.teamMembers[0].club_str) || "") === club).length;
|
||||||
|
const countInPb = pb.filter(p => (p.club_str || (p.teamMembers && p.teamMembers[0].club_str) || "") === club).length;
|
||||||
|
|
||||||
|
if (pa.length < maxInPoule && (countInPa <= countInPb || pb.length >= maxInPoule)) {
|
||||||
|
pa.push(c.id);
|
||||||
|
} else if (pb.length < maxInPoule) {
|
||||||
|
pb.push(c.id);
|
||||||
|
} else {
|
||||||
|
pa.push(c.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pa.forEach(id => out.push({id: id, poule: nameA}));
|
||||||
|
pb.forEach(id => out.push({id: id, poule: nameB}));
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeWeightCategories(combs) {
|
||||||
|
combs = combs.filter(c => c.weight != null).sort((a, b) => a.weight - b.weight); // Add random for same weight ?
|
||||||
|
const catCount = Math.ceil(combs.length / 10);
|
||||||
|
const catSize = combs.length / catCount;
|
||||||
|
const catMaxSize = Math.min(Math.ceil(catSize), 10);
|
||||||
|
const catMinSize = Math.max(Math.floor(catSize), 3); // Add marge ?
|
||||||
|
|
||||||
|
const categories = Array.from({length: catCount}, () => []);
|
||||||
|
for (let i = 0; i < combs.length; i++) {
|
||||||
|
categories[Math.floor(i / catSize)].push(combs[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let change = false;
|
||||||
|
let maxIterations = 500;
|
||||||
|
do {
|
||||||
|
change = false;
|
||||||
|
|
||||||
|
// ------ move in upper direction if better and possible ------
|
||||||
|
|
||||||
|
let needFree = -1;
|
||||||
|
let dIfFree = 0;
|
||||||
|
for (let i = 0; i < catCount - 1; i++) {
|
||||||
|
const weightDiff = categories.at(i).at(-1).weight - categories.at(i).at(-2).weight;
|
||||||
|
const nextWeightDiff = categories.at(i + 1).at(0).weight - categories.at(i).at(-1).weight;
|
||||||
|
|
||||||
|
if (weightDiff > nextWeightDiff && categories.at(i).length > catMinSize) {
|
||||||
|
if (categories.at(i + 1).length < catMaxSize) {
|
||||||
|
const movedComb = categories.at(i).pop();
|
||||||
|
categories.at(i + 1).unshift(movedComb);
|
||||||
|
change = true;
|
||||||
|
} else if (weightDiff - nextWeightDiff > dIfFree) {
|
||||||
|
needFree = i;
|
||||||
|
dIfFree = weightDiff - nextWeightDiff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (needFree !== -1) {
|
||||||
|
let haveSpace = -1;
|
||||||
|
let maxDiff = 0;
|
||||||
|
for (let i = needFree + 1; i < catCount; i++) {
|
||||||
|
if (categories.at(i).length < catMaxSize) {
|
||||||
|
haveSpace = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (haveSpace !== -1) {
|
||||||
|
for (let i = needFree + 1; i < haveSpace; i++) {
|
||||||
|
const weightDiff = categories.at(i).at(-1).weight - categories.at(i).at(-2).weight;
|
||||||
|
const nextWeightDiff = categories.at(i + 1).at(0).weight - categories.at(i).at(-1).weight;
|
||||||
|
const diffIfFree = weightDiff - nextWeightDiff;
|
||||||
|
if (diffIfFree > maxDiff) {
|
||||||
|
maxDiff = diffIfFree;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maxDiff < dIfFree) {
|
||||||
|
for (let i = needFree; i < haveSpace; i++) {
|
||||||
|
const movedComb = categories.at(i).pop();
|
||||||
|
categories.at(i + 1).unshift(movedComb);
|
||||||
|
change = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------ move in lower direction if better and possible ------
|
||||||
|
|
||||||
|
needFree = -1;
|
||||||
|
dIfFree = 0;
|
||||||
|
for (let i = 1; i < catCount; i++) {
|
||||||
|
const currentFirst = categories[i][0];
|
||||||
|
const currentSecondFirst = categories[i][1];
|
||||||
|
const prevLast = categories[i - 1][categories[i - 1].length - 1];
|
||||||
|
|
||||||
|
const weightDiff = currentSecondFirst.weight - currentFirst.weight;
|
||||||
|
const prevWeightDiff = currentFirst.weight - prevLast.weight;
|
||||||
|
|
||||||
|
if (weightDiff > prevWeightDiff && categories.at(i).length > catMinSize) {
|
||||||
|
if (categories.at(i - 1).length < catMaxSize) {
|
||||||
|
const movedComb = categories.at(i).shift();
|
||||||
|
categories.at(i - 1).push(movedComb);
|
||||||
|
change = true;
|
||||||
|
} else if (weightDiff - prevWeightDiff > dIfFree) {
|
||||||
|
needFree = i;
|
||||||
|
dIfFree = weightDiff - prevWeightDiff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (needFree !== -1) {
|
||||||
|
let haveSpace = -1;
|
||||||
|
let maxDiff = 0;
|
||||||
|
for (let i = needFree - 1; i >= 0; i--) {
|
||||||
|
if (categories.at(i).length < catMaxSize) {
|
||||||
|
haveSpace = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (haveSpace !== -1) {
|
||||||
|
for (let i = needFree - 1; i > haveSpace; i--) {
|
||||||
|
const currentFirst = categories[i][0];
|
||||||
|
const currentSecondFirst = categories[i][1];
|
||||||
|
const prevLast = categories[i - 1][categories[i - 1].length - 1];
|
||||||
|
|
||||||
|
const weightDiff = currentSecondFirst.weight - currentFirst.weight;
|
||||||
|
const prevWeightDiff = currentFirst.weight - prevLast.weight;
|
||||||
|
|
||||||
|
const diffIfFree = weightDiff - prevWeightDiff;
|
||||||
|
if (diffIfFree > maxDiff) {
|
||||||
|
maxDiff = diffIfFree;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maxDiff < dIfFree) {
|
||||||
|
for (let i = needFree; i > haveSpace; i--) {
|
||||||
|
const movedComb = categories.at(i).shift();
|
||||||
|
categories.at(i - 1).push(movedComb);
|
||||||
|
change = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (change && maxIterations-- > 0);
|
||||||
|
|
||||||
|
return categories;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getCatNameList = (count) => {
|
||||||
|
const catNameList = [];
|
||||||
|
if (count >= 10) catNameList.push("Paille");
|
||||||
|
if (count >= 9) catNameList.push("Mouche");
|
||||||
|
if (count >= 8) catNameList.push("Coq");
|
||||||
|
if (count >= 7) catNameList.push("Plume");
|
||||||
|
if (count >= 2) catNameList.push("Léger");
|
||||||
|
if (count >= 5) catNameList.push("Mi-moyen");
|
||||||
|
if (count >= 3) catNameList.push("Moyen");
|
||||||
|
if (count >= 6) catNameList.push("Mi-lourd");
|
||||||
|
if (count >= 1) catNameList.push("Lourd");
|
||||||
|
if (count >= 4) catNameList.push("Super-lourd");
|
||||||
|
|
||||||
|
return catNameList;
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeCategory(combs) {
|
||||||
|
const out = Array.from(CatList, (v, i) => ({
|
||||||
|
h: combs.filter(c => c.categorie === v && c.genre !== "F"),
|
||||||
|
f: combs.filter(c => c.categorie === v && c.genre === "F"),
|
||||||
|
m: [],
|
||||||
|
canMakeGenreFusion: i <= CatList.indexOf("BENJAMIN"),
|
||||||
|
c: v,
|
||||||
|
c_index: i,
|
||||||
|
min_c_index: i,
|
||||||
|
done: false
|
||||||
|
}))
|
||||||
|
|
||||||
|
for (let i = 0; i < out.length; i++)
|
||||||
|
out[i].done = out[i].h.length === 0 && out[i].f.length === 0;
|
||||||
|
|
||||||
|
for (let i = 0; i < out.length - 1; i++) {
|
||||||
|
const p = i === 0 ? undefined : out[i - 1];
|
||||||
|
const c = out[i];
|
||||||
|
const n = out[i + 1];
|
||||||
|
|
||||||
|
if (c.done)
|
||||||
|
continue;
|
||||||
|
if (c.canMakeGenreFusion) {
|
||||||
|
if (c.f.length < 6 || c.h.length < 5) {
|
||||||
|
if (c.f.length + c.h.length >= 3) {
|
||||||
|
c.m = c.h.concat(c.f);
|
||||||
|
c.h = [];
|
||||||
|
c.f = [];
|
||||||
|
c.done = true;
|
||||||
|
} else {
|
||||||
|
n.h = n.h.concat(c.h);
|
||||||
|
n.f = n.f.concat(c.f);
|
||||||
|
n.min_c_index = c.min_c_index
|
||||||
|
c.h = [];
|
||||||
|
c.f = [];
|
||||||
|
c.done = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
c.done = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (c.h.length < 3 && c.h.length > 0) {
|
||||||
|
if (p) {
|
||||||
|
if (p.h.length > 0 && p.h.length + c.h.length <= c.h.length + n.h.length && p.min_c_index - p.c_index < 1) {
|
||||||
|
p.h = p.h.concat(c.h);
|
||||||
|
c.h = [];
|
||||||
|
} else {
|
||||||
|
n.h = n.h.concat(c.h);
|
||||||
|
c.h = [];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
n.h = n.h.concat(c.h);
|
||||||
|
c.h = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (c.f.length < 3 && c.f.length > 0) {
|
||||||
|
if (p) {
|
||||||
|
if (p.f.length > 0 && p.f.length + c.f.length <= c.f.length + n.f.length && p.min_c_index - p.c_index < 1) {
|
||||||
|
p.f = p.f.concat(c.f);
|
||||||
|
c.f = [];
|
||||||
|
} else {
|
||||||
|
n.f = n.f.concat(c.f);
|
||||||
|
c.f = [];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
n.f = n.f.concat(c.f);
|
||||||
|
c.f = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.done = (c.h.length >= 3 || c.h.length === 0) && (c.f.length >= 3 || c.f.length === 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Down fusion if not done
|
||||||
|
for (let i = out.length - 1; i > 0; i--) {
|
||||||
|
const p = out[i - 1];
|
||||||
|
const c = out[i];
|
||||||
|
|
||||||
|
if (c.done)
|
||||||
|
continue;
|
||||||
|
if (c.h.length > 0 && c.h.length < 3) {
|
||||||
|
p.h = p.h.concat(c.h);
|
||||||
|
c.h = [];
|
||||||
|
}
|
||||||
|
if (c.f.length > 0 && c.f.length < 3) {
|
||||||
|
p.f = p.f.concat(c.f);
|
||||||
|
c.f = [];
|
||||||
|
}
|
||||||
|
c.done = (c.h.length >= 3 || c.h.length === 0) && (c.f.length >= 3 || c.f.length === 0);
|
||||||
|
p.done = (p.h.length >= 3 || p.h.length === 0) && (p.f.length >= 3 || p.f.length === 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return out.map(c => [c.h, c.f, c.m]).flat().filter(l => l.length > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendCatList(toastId, t, catList, sendRequest) {
|
||||||
|
toastId.current = toast(t('créationDeLaLesCatégories'), {progress: 0});
|
||||||
|
|
||||||
|
new Promise(async (resolve) => {
|
||||||
|
for (let i = 0; i < catList.length; i++) {
|
||||||
|
const progress = (i + 1) / catList.length;
|
||||||
|
toast.update(toastId.current, {progress});
|
||||||
|
|
||||||
|
const g = []
|
||||||
|
if (catList[i].combs.some(c => c.genre === "H")) g.push('H');
|
||||||
|
if (catList[i].combs.some(c => c.genre === "F")) g.push('F');
|
||||||
|
|
||||||
|
const cat = []
|
||||||
|
catList[i].combs.forEach(c => {
|
||||||
|
if (!cat.includes(c.categorie))
|
||||||
|
cat.push(c.categorie);
|
||||||
|
})
|
||||||
|
|
||||||
|
const type = catList[i].combs.length > 5 && catList[i].classement ? 3 : 1;
|
||||||
|
const newCat = {
|
||||||
|
name: catList[i].preset.name + " - " + cat.map(c => getCatName(c)).join(", ") +
|
||||||
|
(g.length === 2 ? "" : " - " + g.join("/")) + (catList[i].size === 1 ? "" : " - " + getCatNameList(catList[i].size)[catList[i].index]),
|
||||||
|
liceName: catList[i].lice,
|
||||||
|
type: type,
|
||||||
|
treeAreClassement: catList[i].classement,
|
||||||
|
fullClassement: catList[i].fullClassement,
|
||||||
|
preset: {id: catList[i].preset.id}
|
||||||
|
}
|
||||||
|
console.log(newCat)
|
||||||
|
|
||||||
|
await sendRequest('createOrReplaceCategory', newCat).then(id => {
|
||||||
|
newCat["id"] = id;
|
||||||
|
const groups = makePoule(catList[i].combs, []);
|
||||||
|
const {newMatch, matchOrderToUpdate, matchPouleToUpdate} = createMatch(newCat, [], groups);
|
||||||
|
|
||||||
|
const p = [];
|
||||||
|
p.push(sendRequest("recalculateMatch", {
|
||||||
|
categorie: newCat.id,
|
||||||
|
newMatch,
|
||||||
|
matchOrderToUpdate: Object.fromEntries(matchOrderToUpdate),
|
||||||
|
matchPouleToUpdate: Object.fromEntries(matchPouleToUpdate),
|
||||||
|
matchesToRemove: []
|
||||||
|
}).then(() => {
|
||||||
|
console.log("Finished creating matches for category", newCat.name);
|
||||||
|
}).catch(err => {
|
||||||
|
console.error("Error creating matches for category", newCat.name, err);
|
||||||
|
}))
|
||||||
|
|
||||||
|
if (type === 3) {
|
||||||
|
const trees = build_tree(4, 1)
|
||||||
|
console.log("Creating trees for new category:", trees);
|
||||||
|
|
||||||
|
p.push(sendRequest('updateTrees', {
|
||||||
|
categoryId: id,
|
||||||
|
trees: trees
|
||||||
|
}).then(() => {
|
||||||
|
console.log("Finished creating trees for category", newCat.name);
|
||||||
|
}).catch(err => {
|
||||||
|
console.error("Error creating trees for category", newCat.name, err);
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.allSettled(p)
|
||||||
|
}).catch(err => {
|
||||||
|
console.error("Error creating category", newCat.name, err);
|
||||||
|
})
|
||||||
|
console.log("Finished category", i + 1, "/", catList.length);
|
||||||
|
}
|
||||||
|
resolve();
|
||||||
|
}).finally(() => {
|
||||||
|
toast.done(toastId.current);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AutoNewCatModalContent() {
|
||||||
|
const {t} = useTranslation("cm");
|
||||||
|
const {combs} = useCombs();
|
||||||
|
const {sendRequest} = useWS();
|
||||||
|
const toastId = React.useRef(null);
|
||||||
|
|
||||||
|
const [gender, setGender] = useState({H: false, F: false, NA: false})
|
||||||
|
const [cat, setCat] = useState([])
|
||||||
|
const [preset, setPreset] = useState(undefined)
|
||||||
|
const [lice, setLice] = useState("1")
|
||||||
|
const [classement, setClassement] = useState(true)
|
||||||
|
const [fullClassement, setFullClassement] = useState(false)
|
||||||
|
|
||||||
|
const setCat_ = (e, index) => {
|
||||||
|
if (e.target.checked) {
|
||||||
|
if (!cat.includes(index)) {
|
||||||
|
setCat([...cat, index])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setCat(cat.filter(c => c !== index))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyFilter(dataIn, dataOut) {
|
||||||
|
dataIn.forEach(comb => {
|
||||||
|
if (comb == null)
|
||||||
|
return;
|
||||||
|
if ((gender.H && comb.genre === 'H' || gender.F && comb.genre === 'F' || gender.NA && comb.genre === 'NA')
|
||||||
|
&& (cat.includes(Math.min(CatList.length, CatList.indexOf(comb.categorie) + comb.overCategory)))
|
||||||
|
&& (preset === undefined || comb.categoriesInscrites?.includes(preset.id))) {
|
||||||
|
dataOut.push(comb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const dispoFiltered = [];
|
||||||
|
if (combs != null)
|
||||||
|
applyFilter(Object.values(combs), dispoFiltered);
|
||||||
|
|
||||||
|
const handleSubmit = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
let catList
|
||||||
|
if (dispoFiltered.length > 10) {
|
||||||
|
catList = makeWeightCategories(dispoFiltered);
|
||||||
|
} else {
|
||||||
|
catList = [[...dispoFiltered]];
|
||||||
|
}
|
||||||
|
console.log(catList.map(c => c.map(c => ({id: c.id, weight: c.weight, fname: c.fname, lname: c.lname}))))
|
||||||
|
|
||||||
|
sendCatList(toastId, t, catList
|
||||||
|
.map((combs, index, a) => ({combs, classement, preset, lice, fullClassement, index, size: a.length})), sendRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>
|
||||||
|
<div className="modal-header">
|
||||||
|
<h1 className="modal-title fs-5" id="autoNewCatModalLabel">{t('depuisUneCatégoriePrédéfinie')}</h1>
|
||||||
|
<button type="button" className="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div className="modal-body">
|
||||||
|
<div className="d-flex flex-wrap justify-content-around">
|
||||||
|
<ListPresetSelect value={preset} onChange={setPreset} returnId={false}/>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="form-label">{t('genre')}</label>
|
||||||
|
<div className="d-flex align-items-center">
|
||||||
|
<div className="form-check" style={{marginRight: '10px'}}>
|
||||||
|
<input className="form-check-input" type="checkbox" id="gridCheck" checked={gender.H}
|
||||||
|
onChange={e => setGender((prev) => {
|
||||||
|
return {...prev, H: e.target.checked}
|
||||||
|
})}/>
|
||||||
|
<label className="form-check-label" htmlFor="gridCheck">{t('genre.h')}</label>
|
||||||
|
</div>
|
||||||
|
<div className="form-check" style={{marginRight: '10px'}}>
|
||||||
|
<input className="form-check-input" type="checkbox" id="gridCheck2" checked={gender.F}
|
||||||
|
onChange={e => setGender((prev) => {
|
||||||
|
return {...prev, F: e.target.checked}
|
||||||
|
})}/>
|
||||||
|
<label className="form-check-label" htmlFor="gridCheck2">{t('genre.f')}</label>
|
||||||
|
</div>
|
||||||
|
<div className="form-check">
|
||||||
|
<input className="form-check-input" type="checkbox" id="gridCheck3" checked={gender.NA}
|
||||||
|
onChange={e => setGender((prev) => {
|
||||||
|
return {...prev, NA: e.target.checked}
|
||||||
|
})}/>
|
||||||
|
<label className="form-check-label" htmlFor="gridCheck3">{t('genre.na')}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{preset !== undefined && <>
|
||||||
|
<div className="d-flex flex-wrap justify-content-around mb-1">
|
||||||
|
<div className="d-flex flex-wrap mb-3">
|
||||||
|
<label htmlFor="inputState2" className="form-label align-self-center" style={{margin: "0 0.5em 0 0"}}>
|
||||||
|
{t('catégorie')} :
|
||||||
|
</label>
|
||||||
|
{preset.categories.map(c => [c.categorie, CatList.indexOf(c.categorie)]).sort((a, b) => a[1] - b[1])
|
||||||
|
.map(([cat_, index]) => {
|
||||||
|
return <div key={index} className="input-group"
|
||||||
|
style={{display: "contents"}}>
|
||||||
|
<div className="input-group-text">
|
||||||
|
<input className="form-check-input mt-0" type="checkbox"
|
||||||
|
id={"categoriesInput" + index} checked={cat.includes(index)} aria-label={getCatName(cat_)}
|
||||||
|
onChange={e => setCat_(e, index)}/>
|
||||||
|
<label style={{marginLeft: "0.5em"}} htmlFor={"categoriesInput" + index}>{getCatName(cat_)}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>}
|
||||||
|
|
||||||
|
<div className="mb-3">
|
||||||
|
<label htmlFor="liceInput2" className="form-label"><Trans i18nKey="nomDesZonesDeCombat" ns="cm">t <small>(séparée par des ';')</small></Trans></label>
|
||||||
|
<input type="text" className="form-control" id="liceInput2" placeholder="1;2" name="zone de combat" value={lice}
|
||||||
|
onChange={e => setLice(e.target.value)}/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-check">
|
||||||
|
<input className="form-check-input" type="checkbox" value="" id="checkDefault" checked={classement}
|
||||||
|
onChange={e => setClassement(e.target.checked)}/>
|
||||||
|
<label className="form-check-label" htmlFor="checkDefault">
|
||||||
|
{t('créerLaPhaseFinaleSilYADesPoules')}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className="form-check">
|
||||||
|
<input className="form-check-input" type="checkbox" value="" id="checkDefault2" disabled={!classement}
|
||||||
|
checked={fullClassement} onChange={e => setFullClassement(e.target.checked)}/>
|
||||||
|
<label className="form-check-label" htmlFor="checkDefault2">
|
||||||
|
{t('lesCombattantsEnDehors2')}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span>{dispoFiltered.length} {t('combattantsCorrespondentAuxSélectionnés')}</span><br/>
|
||||||
|
<span>{Math.ceil(dispoFiltered.length / 10)} {t('catégoriesVontêtreCréées')}</span><br/>
|
||||||
|
{dispoFiltered.length > 10 && dispoFiltered.some(c => !c.weight) &&
|
||||||
|
<span style={{color: "red"}}>{t('certainsCombattantsNontPasDePoidsRenseigné')}</span>}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div className="modal-footer">
|
||||||
|
<button type="button" className="btn btn-secondary" data-bs-dismiss="modal">{t('fermer')}</button>
|
||||||
|
<button type="submit" className="btn btn-primary" onClick={handleSubmit}
|
||||||
|
disabled={dispoFiltered.length <= 0} data-bs-dismiss="modal">{t('ajouter')}</button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function AutoNewCatSModalContent() {
|
||||||
|
const {t} = useTranslation("cm");
|
||||||
|
const {combs} = useCombs();
|
||||||
|
const {sendRequest} = useWS();
|
||||||
|
const toastId = React.useRef(null);
|
||||||
|
const {data, error} = useRequestWS("listPreset", {}, null);
|
||||||
|
|
||||||
|
const id = useId()
|
||||||
|
const [categories, setCategories] = useState([])
|
||||||
|
const [lice, setLice] = useState("1")
|
||||||
|
const [classement, setClassement] = useState(true)
|
||||||
|
const [fullClassement, setFullClassement] = useState(false)
|
||||||
|
|
||||||
|
const setCategories_ = (e, catId) => {
|
||||||
|
if (e.target.checked) {
|
||||||
|
if (!categories.includes(catId)) {
|
||||||
|
setCategories([...categories, catId])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setCategories(categories.filter(c => c !== catId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
let catList2 = []
|
||||||
|
for (const catId of categories) {
|
||||||
|
const preset = data.find(p => p.id === catId);
|
||||||
|
const dispoFiltered = Object.values(combs).filter(comb => comb.categoriesInscrites?.includes(catId)).sort(() => Math.random() - 0.5)
|
||||||
|
.map(comb => ({...comb, categorie: CatList[Math.min(CatList.length, CatList.indexOf(comb.categorie) + comb.overCategory)]}));
|
||||||
|
console.log("Creating category for preset", preset.name, "and", dispoFiltered.length, "combattants");
|
||||||
|
|
||||||
|
const catList = makeCategory(dispoFiltered);
|
||||||
|
console.log(catList)
|
||||||
|
|
||||||
|
for (const list of catList) {
|
||||||
|
if (list.length > 10) {
|
||||||
|
catList2.push(...makeWeightCategories(list)
|
||||||
|
.map((combs, index, a) => ({combs, classement, preset, lice, fullClassement, index, size: a.length})));
|
||||||
|
} else {
|
||||||
|
catList2.push(({combs: [...list], classement, preset, lice, fullClassement, index: 1, size: 1}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sendCatList(toastId, t, catList2, sendRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>
|
||||||
|
<div className="modal-header">
|
||||||
|
<h1 className="modal-title fs-5" id="autoNewCatSModalLabel">{t('créerToutesLesCatégories')}</h1>
|
||||||
|
<button type="button" className="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div className="modal-body">
|
||||||
|
<div className="d-flex flex-wrap justify-content-around">
|
||||||
|
<div className="d-flex flex-wrap mb-3">
|
||||||
|
<label htmlFor="inputState2" className="form-label align-self-center" style={{margin: "0 0.5em 0 0"}}>
|
||||||
|
{t('catégorie')} :
|
||||||
|
</label>
|
||||||
|
{error ? <AxiosError error={error}/> : <>
|
||||||
|
{data && data.length === 0 && <div>{t('aucuneCatégorieDisponible')}</div>}
|
||||||
|
{data && data.map((cat, index) =>
|
||||||
|
<div key={cat.id} className="input-group"
|
||||||
|
style={{display: "contents"}}>
|
||||||
|
<div className="input-group-text">
|
||||||
|
<input className="form-check-input mt-0" type="checkbox"
|
||||||
|
id={id + "categoriesInput" + index} checked={categories.includes(cat.id)} aria-label={cat.name}
|
||||||
|
onChange={e => setCategories_(e, cat.id)}/>
|
||||||
|
<label style={{marginLeft: "0.5em"}} htmlFor={id + "categoriesInput" + index}>{cat.name}</label>
|
||||||
|
</div>
|
||||||
|
</div>)}
|
||||||
|
</>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mb-3">
|
||||||
|
<label htmlFor="liceInput3" className="form-label"><Trans i18nKey="nomDesZonesDeCombat" ns="cm">t <small>(séparée par des ';')</small></Trans></label>
|
||||||
|
<input type="text" className="form-control" id="liceInput3" placeholder="1;2" name="zone de combat" value={lice}
|
||||||
|
onChange={e => setLice(e.target.value)}/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-check">
|
||||||
|
<input className="form-check-input" type="checkbox" value="" id="checkDefault" checked={classement}
|
||||||
|
onChange={e => setClassement(e.target.checked)}/>
|
||||||
|
<label className="form-check-label" htmlFor="checkDefault">
|
||||||
|
{t('créerLaPhaseFinaleSilYADesPoules')}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className="form-check">
|
||||||
|
<input className="form-check-input" type="checkbox" value="" id="checkDefault2" disabled={!classement}
|
||||||
|
checked={fullClassement} onChange={e => setFullClassement(e.target.checked)}/>
|
||||||
|
<label className="form-check-label" htmlFor="checkDefault2">
|
||||||
|
{t('lesCombattantsEnDehors2')}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="modal-footer">
|
||||||
|
<button type="button" className="btn btn-secondary" data-bs-dismiss="modal">{t('fermer')}</button>
|
||||||
|
<button type="submit" className="btn btn-primary" onClick={handleSubmit}>{t('ajouter')}</button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
44
src/main/webapp/src/components/cm/ListPresetSelect.jsx
Normal file
44
src/main/webapp/src/components/cm/ListPresetSelect.jsx
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import {useRequestWS} from "../../hooks/useWS.jsx";
|
||||||
|
import {AxiosError} from "../AxiosError.jsx";
|
||||||
|
import {useTranslation} from "react-i18next";
|
||||||
|
import React, {useId} from "react";
|
||||||
|
|
||||||
|
export function ListPresetSelect({disabled, value, onChange, returnId = true}) {
|
||||||
|
const id = useId()
|
||||||
|
const {data, error} = useRequestWS("listPreset", {}, null);
|
||||||
|
const {t} = useTranslation();
|
||||||
|
return <>
|
||||||
|
{data
|
||||||
|
? <div className="mb-3">
|
||||||
|
<label className="form-label" htmlFor={id}>{t('catégorie')}</label>
|
||||||
|
<select className="form-select" id={id} disabled={disabled}
|
||||||
|
value={returnId ? value : (value ? value.id : -1)}
|
||||||
|
onChange={e => {
|
||||||
|
if (returnId) {
|
||||||
|
onChange(Number(e.target.value))
|
||||||
|
} else {
|
||||||
|
onChange(data.find(c => c.id === Number(e.target.value)))
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
<option value={-1}>{t('sélectionner...')}</option>
|
||||||
|
{data.sort((a, b) => a.name.localeCompare(b.name)).map(o => (<option key={o.id} value={o.id}>{o.name}</option>))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
: error
|
||||||
|
? <AxiosError error={error}/>
|
||||||
|
: <Def/>
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
function Def() {
|
||||||
|
const {t} = useTranslation();
|
||||||
|
|
||||||
|
return <div className="input-group mb-3">
|
||||||
|
<label className="input-group-text" id="inputGroupSelect02">{t('catégorie')}</label>
|
||||||
|
<select className="form-select" id="inputGroupSelect02"
|
||||||
|
defaultValue={t('chargement...')}>
|
||||||
|
<option>{t('chargement...')}</option>
|
||||||
|
</select>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
@@ -6,8 +6,18 @@ import {initReactI18next} from 'react-i18next';
|
|||||||
const vite_url = import.meta.env.VITE_URL;
|
const vite_url = import.meta.env.VITE_URL;
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
order: [ 'querystring', 'cookie', 'localStorage', 'sessionStorage', 'navigator', 'htmlTag'],
|
order: ['querystring', 'cookie', 'localStorage', 'sessionStorage', 'navigator', 'htmlTag'],
|
||||||
caches: [],
|
caches: [],
|
||||||
|
convertDetectedLanguage: (lng) => {
|
||||||
|
const supportedLngs = ['en', 'fr'];
|
||||||
|
const languagePart = lng.split('-')[0];
|
||||||
|
|
||||||
|
if (supportedLngs.includes(languagePart)) {
|
||||||
|
return languagePart; // Manually return 'en' from 'en-US'
|
||||||
|
}
|
||||||
|
|
||||||
|
return lng;
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
i18n
|
i18n
|
||||||
@@ -22,8 +32,12 @@ i18n
|
|||||||
// init i18next
|
// init i18next
|
||||||
// for all options read: https://www.i18next.com/overview/configuration-options
|
// for all options read: https://www.i18next.com/overview/configuration-options
|
||||||
.init({
|
.init({
|
||||||
|
fallbackLng: {
|
||||||
|
'fr-FR': ['fr'],
|
||||||
|
default: ['en']
|
||||||
|
},
|
||||||
supportedLngs: ['fr', 'en'],
|
supportedLngs: ['fr', 'en'],
|
||||||
fallbackLng: 'fr',
|
nonExplicitSupportedLngs: true,
|
||||||
debug: vite_url.startsWith('http://localhost'),
|
debug: vite_url.startsWith('http://localhost'),
|
||||||
interpolation: {
|
interpolation: {
|
||||||
escapeValue: false, // not needed for react as it escapes by default
|
escapeValue: false, // not needed for react as it escapes by default
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ export function OBSProvider({children}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getElementName(element) {
|
function getElementName(element) {
|
||||||
return `sub${sessionStorage.getItem("obs_prefix") || 1}.${element}`
|
return `sub${sessionStorage.getItem("liceName") || 1}.${element}`
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useOBS() {
|
export function useOBS() {
|
||||||
|
|||||||
@@ -23,8 +23,6 @@ export function MemberList({source}) {
|
|||||||
const {t} = useTranslation();
|
const {t} = useTranslation();
|
||||||
|
|
||||||
const [memberData, setMemberData] = useState([]);
|
const [memberData, setMemberData] = useState([]);
|
||||||
const [licenceData, setLicenceData] = useState([]);
|
|
||||||
const [showLicenceState, setShowLicenceState] = useState((sessionStorage.getItem("showLicenceState") || "false") === "true");
|
|
||||||
|
|
||||||
const setFilter = (filter) => {
|
const setFilter = (filter) => {
|
||||||
navigate("#" + encodeURI(JSON.stringify(filter)))
|
navigate("#" + encodeURI(JSON.stringify(filter)))
|
||||||
@@ -37,7 +35,7 @@ export function MemberList({source}) {
|
|||||||
payment: 2,
|
payment: 2,
|
||||||
order: "",
|
order: "",
|
||||||
categorie: "",
|
categorie: "",
|
||||||
archived: false,
|
archived: sessionStorage.getItem("showMembreArchived") || true,
|
||||||
...JSON.parse(decodeURI(hash.substring(1)) || "{}"),
|
...JSON.parse(decodeURI(hash.substring(1)) || "{}"),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,23 +70,11 @@ export function MemberList({source}) {
|
|||||||
club: e.club,
|
club: e.club,
|
||||||
categorie: e.categorie,
|
categorie: e.categorie,
|
||||||
licence_number: e.licence,
|
licence_number: e.licence,
|
||||||
licence: showLicenceState ? licenceData.find(licence => licence.membre === e.id) : null
|
licence: data.additionalData?.find(licence => licence.membre === e.id)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
setMemberData(data2);
|
setMemberData(data2);
|
||||||
}, [data, licenceData]);
|
}, [data]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
sessionStorage.setItem("showLicenceState", showLicenceState);
|
|
||||||
if (!showLicenceState)
|
|
||||||
return;
|
|
||||||
|
|
||||||
toast.promise(
|
|
||||||
apiAxios.get(`/licence/current/${source}`), getToastMessage("membre.toast.licences.load"))
|
|
||||||
.then(data => {
|
|
||||||
setLicenceData(data.data);
|
|
||||||
});
|
|
||||||
}, [showLicenceState]);
|
|
||||||
|
|
||||||
const search = (search) => {
|
const search = (search) => {
|
||||||
if (search === filter.search)
|
if (search === filter.search)
|
||||||
@@ -102,8 +88,8 @@ export function MemberList({source}) {
|
|||||||
<div className="col-lg-9">
|
<div className="col-lg-9">
|
||||||
<SearchBar search={search} defaultValue={filter.search}/>
|
<SearchBar search={search} defaultValue={filter.search}/>
|
||||||
{data
|
{data
|
||||||
? <MakeCentralPanel data={data} visibleMember={memberData} navigate={navigate} showLicenceState={showLicenceState}
|
? <MakeCentralPanel data={data} visibleMember={memberData} navigate={navigate} page={filter.page}
|
||||||
page={filter.page} setPage={e => setFilter({...filter, page: e})} source={source}/>
|
setPage={e => setFilter({...filter, page: e})} source={source}/>
|
||||||
: error
|
: error
|
||||||
? <AxiosError error={error}/>
|
? <AxiosError error={error}/>
|
||||||
: <Def/>
|
: <Def/>
|
||||||
@@ -130,10 +116,11 @@ export function MemberList({source}) {
|
|||||||
<div className="card mb-4">
|
<div className="card mb-4">
|
||||||
<div className="card-header">{t('filtre')}</div>
|
<div className="card-header">{t('filtre')}</div>
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
<FiltreBar showLicenceState={showLicenceState}
|
<FiltreBar showArchived={filter.archived}
|
||||||
setShowLicenceState={setShowLicenceState}
|
setShowArchived={e => {
|
||||||
showArchived={filter.archived}
|
setFilter({...filter, archived: e})
|
||||||
setShowArchived={e => setFilter({...filter, archived: e})}
|
sessionStorage.setItem("showMembreArchived", e);
|
||||||
|
}}
|
||||||
clubFilter={filter.club}
|
clubFilter={filter.club}
|
||||||
setClubFilter={e => setFilter({...filter, club: e})}
|
setClubFilter={e => setFilter({...filter, club: e})}
|
||||||
source={source}
|
source={source}
|
||||||
@@ -163,6 +150,7 @@ export function MemberList({source}) {
|
|||||||
|
|
||||||
function FileOutput() {
|
function FileOutput() {
|
||||||
const {t} = useTranslation();
|
const {t} = useTranslation();
|
||||||
|
|
||||||
function formatColumnDate(worksheet, col) {
|
function formatColumnDate(worksheet, col) {
|
||||||
const range = XLSX.utils.decode_range(worksheet['!ref'])
|
const range = XLSX.utils.decode_range(worksheet['!ref'])
|
||||||
// note: range.s.r + 1 skips the header row
|
// note: range.s.r + 1 skips the header row
|
||||||
@@ -181,6 +169,7 @@ function FileOutput() {
|
|||||||
toast.promise(
|
toast.promise(
|
||||||
apiAxios.get(`/member/club/export`), getToastMessage("membre.toast.licences.export"))
|
apiAxios.get(`/member/club/export`), getToastMessage("membre.toast.licences.export"))
|
||||||
.then(data => {
|
.then(data => {
|
||||||
|
console.log(data)
|
||||||
const dataOut = []
|
const dataOut = []
|
||||||
for (const e of data.data) {
|
for (const e of data.data) {
|
||||||
const tmp = {
|
const tmp = {
|
||||||
@@ -354,7 +343,7 @@ function FileInput() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function MakeCentralPanel({data, visibleMember, navigate, showLicenceState, page, setPage, source}) {
|
function MakeCentralPanel({data, visibleMember, navigate, page, setPage, source}) {
|
||||||
const {t} = useTranslation();
|
const {t} = useTranslation();
|
||||||
|
|
||||||
const pages = []
|
const pages = []
|
||||||
@@ -374,7 +363,7 @@ function MakeCentralPanel({data, visibleMember, navigate, showLicenceState, page
|
|||||||
})}</small>
|
})}</small>
|
||||||
<div className="list-group">
|
<div className="list-group">
|
||||||
{visibleMember.map(member => (
|
{visibleMember.map(member => (
|
||||||
<MakeRow key={member.id} member={member} navigate={navigate} showLicenceState={showLicenceState} source={source}/>))}
|
<MakeRow key={member.id} member={member} navigate={navigate} source={source}/>))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
@@ -391,11 +380,11 @@ function MakeCentralPanel({data, visibleMember, navigate, showLicenceState, page
|
|||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|
||||||
function MakeRow({member, showLicenceState, navigate, source}) {
|
function MakeRow({member, navigate, source}) {
|
||||||
const rowContent = <>
|
const rowContent = <>
|
||||||
<div className="row" style={{padding: "0.6em 0"}}>
|
<div className="row" style={{padding: "0.6em 0"}}>
|
||||||
<span className="col-auto">{(member.licence_number ? String(member.licence_number).padStart(5, '0') : "-------") + " "}
|
<span className="col-auto">{(member.licence_number ? String(member.licence_number).padStart(5, '0') : "-------") + " "}
|
||||||
{(showLicenceState && member.licence != null && member.licence.pay) ? <FontAwesomeIcon icon={faEuroSign}/> : <> </>}</span>
|
{(member.licence != null && member.licence.pay) ? <FontAwesomeIcon icon={faEuroSign}/> : <> </>}</span>
|
||||||
<div className="ms-2 col-auto">
|
<div className="ms-2 col-auto">
|
||||||
<div className="fw-bold">{member.fname} {member.lname}</div>
|
<div className="fw-bold">{member.fname} {member.lname}</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -411,7 +400,7 @@ function MakeRow({member, showLicenceState, navigate, source}) {
|
|||||||
|
|
||||||
</>
|
</>
|
||||||
|
|
||||||
if (showLicenceState && member.licence != null) {
|
if (member.licence != null) {
|
||||||
return <a className={"list-group-item d-flex justify-content-between align-items-start list-group-item-action list-group-item-"
|
return <a className={"list-group-item d-flex justify-content-between align-items-start list-group-item-action list-group-item-"
|
||||||
+ (member.licence.validate ? "success" : (member.licence.certificate.length > 1 ? "warning" : "danger"))}
|
+ (member.licence.validate ? "success" : (member.licence.certificate.length > 1 ? "warning" : "danger"))}
|
||||||
style={{padding: "0 1em"}}
|
style={{padding: "0 1em"}}
|
||||||
@@ -509,8 +498,6 @@ function OrderBar({onOrderChange, defaultValues = "", source}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function FiltreBar({
|
function FiltreBar({
|
||||||
showLicenceState,
|
|
||||||
setShowLicenceState,
|
|
||||||
showArchived,
|
showArchived,
|
||||||
setShowArchived,
|
setShowArchived,
|
||||||
clubFilter,
|
clubFilter,
|
||||||
@@ -526,9 +513,6 @@ function FiltreBar({
|
|||||||
const {t} = useTranslation();
|
const {t} = useTranslation();
|
||||||
|
|
||||||
return <div>
|
return <div>
|
||||||
<div className="mb-3">
|
|
||||||
<Checkbox value={showLicenceState} onChange={setShowLicenceState} label={t('membre.filtre.licence')}/>
|
|
||||||
</div>
|
|
||||||
<div className="mb-3">
|
<div className="mb-3">
|
||||||
<Checkbox value={showArchived} onChange={setShowArchived} name="checkbox2" label={t('membre.filtre.inactif')}/>
|
<Checkbox value={showArchived} onChange={setShowArchived} name="checkbox2" label={t('membre.filtre.inactif')}/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {useEffect, useReducer, useState} from "react";
|
|||||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||||
import {faEuroSign, faPen} from "@fortawesome/free-solid-svg-icons";
|
import {faEuroSign, faPen} from "@fortawesome/free-solid-svg-icons";
|
||||||
import {AxiosError} from "../../../components/AxiosError.jsx";
|
import {AxiosError} from "../../../components/AxiosError.jsx";
|
||||||
import {apiAxios, getSaison, getToastMessage} from "../../../utils/Tools.js";
|
import {apiAxios, getFirstDateOfSaison, getSaison, getToastMessage} from "../../../utils/Tools.js";
|
||||||
import {toast} from "react-toastify";
|
import {toast} from "react-toastify";
|
||||||
import {useTranslation} from "react-i18next";
|
import {useTranslation} from "react-i18next";
|
||||||
|
|
||||||
@@ -51,13 +51,34 @@ export function LicenceCard({userData}) {
|
|||||||
dispatch({type: 'SORT'})
|
dispatch({type: 'SORT'})
|
||||||
}, [data]);
|
}, [data]);
|
||||||
|
|
||||||
|
const handleAsk = () => {
|
||||||
|
const currentLicence = licences[0];
|
||||||
|
|
||||||
|
let certif = undefined;
|
||||||
|
if (currentLicence != null && currentLicence.certificate != null) {
|
||||||
|
const strings = currentLicence.certificate.split('¤');
|
||||||
|
if (currentLicence.saison === getSaison()) {
|
||||||
|
certif = currentLicence.certificate;
|
||||||
|
} else if (strings.length > 1) {
|
||||||
|
const date = new Date(strings[1]);
|
||||||
|
const max = getFirstDateOfSaison();
|
||||||
|
max.setFullYear(max.getFullYear() - 2);
|
||||||
|
|
||||||
|
if (max < date) {
|
||||||
|
certif = currentLicence.certificate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setModal({id: -1, membre: userData.id, certificate: certif});
|
||||||
|
}
|
||||||
|
|
||||||
return <div className="card mb-4 mb-md-0">
|
return <div className="card mb-4 mb-md-0">
|
||||||
<div className="card-header container-fluid">
|
<div className="card-header container-fluid">
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col">{t('licence')}</div>
|
<div className="col">{t('licence')}</div>
|
||||||
<div className="col" style={{textAlign: 'right'}}>
|
<div className="col" style={{textAlign: 'right'}}>
|
||||||
<button className="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#LicenceModal"
|
<button className="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#LicenceModal"
|
||||||
onClick={_ => setModal({id: -1, membre: userData.id})}>{t('button.ajouter')}
|
onClick={handleAsk}>{t('button.ajouter')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -141,13 +162,6 @@ function ModalContent({licence, dispatch}) {
|
|||||||
if (licence.id !== -1) {
|
if (licence.id !== -1) {
|
||||||
setNew(false)
|
setNew(false)
|
||||||
setSaison(licence.saison)
|
setSaison(licence.saison)
|
||||||
if (licence.certificate === null) {
|
|
||||||
setCertificateBy("")
|
|
||||||
setCertificateDate("")
|
|
||||||
} else {
|
|
||||||
setCertificateBy(licence.certificate.split('¤')[0])
|
|
||||||
setCertificateDate(licence.certificate.split('¤')[1])
|
|
||||||
}
|
|
||||||
setValidate(licence.validate)
|
setValidate(licence.validate)
|
||||||
setPay(licence.pay);
|
setPay(licence.pay);
|
||||||
} else {
|
} else {
|
||||||
@@ -158,6 +172,13 @@ function ModalContent({licence, dispatch}) {
|
|||||||
setValidate(false)
|
setValidate(false)
|
||||||
setPay(false);
|
setPay(false);
|
||||||
}
|
}
|
||||||
|
if (licence.certificate) {
|
||||||
|
setCertificateBy(licence.certificate.split('¤')[0])
|
||||||
|
setCertificateDate(licence.certificate.split('¤')[1])
|
||||||
|
} else {
|
||||||
|
setCertificateBy("")
|
||||||
|
setCertificateDate("")
|
||||||
|
}
|
||||||
}, [licence]);
|
}, [licence]);
|
||||||
|
|
||||||
return <form onSubmit={e => sendLicence(e, dispatch)}>
|
return <form onSubmit={e => sendLicence(e, dispatch)}>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {useEffect, useReducer, useState} from "react";
|
|||||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||||
import {faInfo, faPen} from "@fortawesome/free-solid-svg-icons";
|
import {faInfo, faPen} from "@fortawesome/free-solid-svg-icons";
|
||||||
import {AxiosError} from "../../../components/AxiosError.jsx";
|
import {AxiosError} from "../../../components/AxiosError.jsx";
|
||||||
import {apiAxios, getSaison, getToastMessage} from "../../../utils/Tools.js";
|
import {apiAxios, getFirstDateOfSaison, getSaison, getToastMessage} from "../../../utils/Tools.js";
|
||||||
import {toast} from "react-toastify";
|
import {toast} from "react-toastify";
|
||||||
import {ColoredText} from "../../../components/ColoredCircle.jsx";
|
import {ColoredText} from "../../../components/ColoredCircle.jsx";
|
||||||
import {useTranslation} from "react-i18next";
|
import {useTranslation} from "react-i18next";
|
||||||
@@ -32,7 +32,7 @@ function licenceReducer(licences, action) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function LicenceCard({userData}) {
|
export function LicenceCard({userData}) {
|
||||||
const defaultLicence = {id: -1, membre: userData.id, validate: false, saison: getSaison(), certificate: false}
|
const defaultLicence = {id: -1, membre: userData.id, validate: false, saison: getSaison(), certificate: null}
|
||||||
const {t} = useTranslation();
|
const {t} = useTranslation();
|
||||||
|
|
||||||
const setLoading = useLoadingSwitcher()
|
const setLoading = useLoadingSwitcher()
|
||||||
@@ -48,13 +48,33 @@ export function LicenceCard({userData}) {
|
|||||||
dispatch({type: 'SORT'})
|
dispatch({type: 'SORT'})
|
||||||
}, [data]);
|
}, [data]);
|
||||||
|
|
||||||
|
const handleAsk = () => {
|
||||||
|
const currentLicence = licences[0];
|
||||||
|
|
||||||
|
let certif = undefined;
|
||||||
|
if (currentLicence != null && currentLicence.certificate != null) {
|
||||||
|
const strings = currentLicence.certificate.split('¤');
|
||||||
|
if (currentLicence.saison === getSaison()) {
|
||||||
|
certif = currentLicence.certificate;
|
||||||
|
} else if (strings.length > 1) {
|
||||||
|
const date = new Date(strings[1]);
|
||||||
|
const max = getFirstDateOfSaison();
|
||||||
|
max.setFullYear(max.getFullYear() - 2);
|
||||||
|
|
||||||
|
if (max < date)
|
||||||
|
certif = currentLicence.certificate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setModal({...defaultLicence, certificate: certif});
|
||||||
|
}
|
||||||
|
|
||||||
return <div className="card mb-4 mb-md-0">
|
return <div className="card mb-4 mb-md-0">
|
||||||
<div className="card-header container-fluid">
|
<div className="card-header container-fluid">
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col">{t('licence')}</div>
|
<div className="col">{t('licence')}</div>
|
||||||
<div className="col" style={{textAlign: 'right'}}>
|
<div className="col" style={{textAlign: 'right'}}>
|
||||||
<button className="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#LicenceModal"
|
<button className="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#LicenceModal"
|
||||||
onClick={() => setModal(defaultLicence)}
|
onClick={handleAsk}
|
||||||
disabled={licences.some(licence => licence.saison === getSaison())}>{t('demander')}
|
disabled={licences.some(licence => licence.saison === getSaison())}>{t('demander')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -127,15 +147,14 @@ function ModalContent({licence, dispatch}) {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (licence.id !== -1) {
|
if (licence.id !== -1) {
|
||||||
setNew(false)
|
setNew(false)
|
||||||
if (licence.certificate === null) {
|
|
||||||
setCertificateBy("")
|
|
||||||
setCertificateDate("")
|
|
||||||
} else {
|
|
||||||
setCertificateBy(licence.certificate.split('¤')[0])
|
|
||||||
setCertificateDate(licence.certificate.split('¤')[1])
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
setNew(true)
|
setNew(true)
|
||||||
|
}
|
||||||
|
if (licence.certificate) {
|
||||||
|
setCertificateBy(licence.certificate.split('¤')[0])
|
||||||
|
setCertificateDate(licence.certificate.split('¤')[1])
|
||||||
|
} else {
|
||||||
setCertificateBy("")
|
setCertificateBy("")
|
||||||
setCertificateDate("")
|
setCertificateDate("")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -554,11 +554,11 @@ function Modal_({data2, data3, error2, sendRegister, modalState, setModalState,
|
|||||||
<div className="input-group mb-3">
|
<div className="input-group mb-3">
|
||||||
<span className="input-group-text" id="weight">{t('comp.modal.poids')}</span>
|
<span className="input-group-text" id="weight">{t('comp.modal.poids')}</span>
|
||||||
{source === "admin" && <span className="input-group-text" id="weightReal">{t('comp.modal.annoncé')}</span>}
|
{source === "admin" && <span className="input-group-text" id="weightReal">{t('comp.modal.annoncé')}</span>}
|
||||||
<input type="number" min={1} step={1} className="form-control" placeholder="--" aria-label="weight"
|
<input type="number" min={1} step={0.1} className="form-control" placeholder="--" aria-label="weight"
|
||||||
name="weight" aria-describedby="weight" disabled={!(data3.requiredWeight.includes(currenCat))} value={weight}
|
name="weight" aria-describedby="weight" disabled={!(data3.requiredWeight.includes(currenCat))} value={weight}
|
||||||
onChange={e => setWeight(e.target.value)}/>
|
onChange={e => setWeight(e.target.value)}/>
|
||||||
{source === "admin" && <><span className="input-group-text" id="weightReal">{t('comp.modal.pesé')}</span>
|
{source === "admin" && <><span className="input-group-text" id="weightReal">{t('comp.modal.pesé')}</span>
|
||||||
<input type="number" min={1} step={1} className="form-control" placeholder="--" aria-label="weightReal"
|
<input type="number" min={1} step={0.1} className="form-control" placeholder="--" aria-label="weightReal"
|
||||||
name="weightReal" aria-describedby="weightReal" value={weightReal}
|
name="weightReal" aria-describedby="weightReal" value={weightReal}
|
||||||
onChange={e => setWeightReal(e.target.value)}/></>}
|
onChange={e => setWeightReal(e.target.value)}/></>}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -156,11 +156,11 @@ function SelfRegister({data2}) {
|
|||||||
<h4 style={{textAlign: "left"}}>{t('comp.monInscription')}</h4>
|
<h4 style={{textAlign: "left"}}>{t('comp.monInscription')}</h4>
|
||||||
<div className="input-group mb-3" hidden={!(data2?.requiredWeight.includes(currenCat))}>
|
<div className="input-group mb-3" hidden={!(data2?.requiredWeight.includes(currenCat))}>
|
||||||
<span className="input-group-text" id="weight">{t("comp.modal.poids")}</span>
|
<span className="input-group-text" id="weight">{t("comp.modal.poids")}</span>
|
||||||
<input type="number" min={1} step={1} className="form-control" placeholder="--" aria-label="weight" disabled={disabled}
|
<input type="number" min={1} step={0.1} className="form-control" placeholder="--" aria-label="weight" disabled={disabled}
|
||||||
name="weight" aria-describedby="weight" value={weight} onChange={e => setWeight(e.target.value)}/>
|
name="weight" aria-describedby="weight" value={weight} onChange={e => setWeight(e.target.value)}/>
|
||||||
{data[0]?.weightReal && <>
|
{data[0]?.weightReal && <>
|
||||||
<span className="input-group-text" id="weight">{t("comp.modal.pesé")}</span>
|
<span className="input-group-text" id="weight">{t("comp.modal.pesé")}</span>
|
||||||
<input type="number" min={1} step={1} className="form-control" placeholder="--" aria-label="weight" disabled={true}
|
<input type="number" min={1} step={0.1} className="form-control" placeholder="--" aria-label="weight" disabled={true}
|
||||||
name="weight" aria-describedby="weight" value={data[0]?.weightReal} onChange={() => {
|
name="weight" aria-describedby="weight" value={data[0]?.weightReal} onChange={() => {
|
||||||
}}/>
|
}}/>
|
||||||
</>}
|
</>}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import React, {useEffect, useRef, useState} from "react";
|
import React, {useEffect, useId, useRef, useState} from "react";
|
||||||
import {useRequestWS, useWS} from "../../../hooks/useWS.jsx";
|
import {useRequestWS, useWS} from "../../../hooks/useWS.jsx";
|
||||||
import {LoadingProvider, useLoadingSwitcher} from "../../../hooks/useLoading.jsx";
|
import {LoadingProvider, useLoadingSwitcher} from "../../../hooks/useLoading.jsx";
|
||||||
import {toast} from "react-toastify";
|
import {toast} from "react-toastify";
|
||||||
import {build_tree, resize_tree} from "../../../utils/TreeUtils.js"
|
import {build_tree, from_sendTree, resize_tree} from "../../../utils/TreeUtils.js"
|
||||||
import {ConfirmDialog} from "../../../components/ConfirmDialog.jsx";
|
import {ConfirmDialog} from "../../../components/ConfirmDialog.jsx";
|
||||||
import {CategoryContent} from "./CategoryAdminContent.jsx";
|
import {CategoryContent} from "./CategoryAdminContent.jsx";
|
||||||
import {exportOBSConfiguration} from "../../../hooks/useOBS.jsx";
|
import {exportOBSConfiguration} from "../../../hooks/useOBS.jsx";
|
||||||
@@ -11,14 +11,17 @@ import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
|||||||
import {SimpleIconsOBS} from "../../../assets/SimpleIconsOBS.ts";
|
import {SimpleIconsOBS} from "../../../assets/SimpleIconsOBS.ts";
|
||||||
import JSZip from "jszip";
|
import JSZip from "jszip";
|
||||||
import {detectOptimalBackground} from "../../../components/SmartLogoBackground.jsx";
|
import {detectOptimalBackground} from "../../../components/SmartLogoBackground.jsx";
|
||||||
import {faFile, faGlobe, faTableCellsLarge, faTrash} from "@fortawesome/free-solid-svg-icons";
|
import {faFile, faGlobe, faPrint, faTableCellsLarge, faTrash} from "@fortawesome/free-solid-svg-icons";
|
||||||
import {Trans, useTranslation} from "react-i18next";
|
import {Trans, useTranslation} from "react-i18next";
|
||||||
import i18n from "i18next";
|
import i18n from "i18next";
|
||||||
import {getToastMessage} from "../../../utils/Tools.js";
|
import {getToastMessage, toDataURL, win_end} from "../../../utils/Tools.js";
|
||||||
import {copyStyles} from "../../../utils/copyStyles.js";
|
import {copyStyles} from "../../../utils/copyStyles.js";
|
||||||
import {StateWindow} from "./StateWindow.jsx";
|
import {StateWindow} from "./StateWindow.jsx";
|
||||||
import {CombName, useCombs} from "../../../hooks/useComb.jsx";
|
import {CombName, useCombs} from "../../../hooks/useComb.jsx";
|
||||||
import {useCards, useCardsDispatch} from "../../../hooks/useCard.jsx";
|
import {useCards, useCardsDispatch} from "../../../hooks/useCard.jsx";
|
||||||
|
import {ListPresetSelect} from "../../../components/cm/ListPresetSelect.jsx";
|
||||||
|
import {AutoNewCatModalContent, AutoNewCatSModalContent} from "../../../components/cm/AutoCatModalContent.jsx";
|
||||||
|
import {makePDF} from "../../../utils/cmPdf.js";
|
||||||
|
|
||||||
const vite_url = import.meta.env.VITE_URL;
|
const vite_url = import.meta.env.VITE_URL;
|
||||||
|
|
||||||
@@ -49,7 +52,7 @@ export function CMAdmin({compUuid}) {
|
|||||||
return <>
|
return <>
|
||||||
<div className="card">
|
<div className="card">
|
||||||
<div className='card-header'>
|
<div className='card-header'>
|
||||||
<CategoryHeader cat={cat} setCatId={setCatId}/>
|
<CategoryHeader cat={cat} setCatId={setCatId} menuActions={menuActions}/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
@@ -175,6 +178,7 @@ function Menu({menuActions, compUuid}) {
|
|||||||
const longPress = useRef({time: null, timer: null, button: null});
|
const longPress = useRef({time: null, timer: null, button: null});
|
||||||
const obsModal = useRef(null);
|
const obsModal = useRef(null);
|
||||||
const teamCardModal = useRef(null);
|
const teamCardModal = useRef(null);
|
||||||
|
const printModal = useRef(null);
|
||||||
const {t} = useTranslation("cm");
|
const {t} = useTranslation("cm");
|
||||||
|
|
||||||
const [showStateWin, setShowStateWin] = useState(false)
|
const [showStateWin, setShowStateWin] = useState(false)
|
||||||
@@ -263,7 +267,8 @@ function Menu({menuActions, compUuid}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const copyScriptToClipboard = () => {
|
const copyScriptToClipboard = () => {
|
||||||
navigator.clipboard.writeText(`<!--suppress ALL -->
|
// noinspection JSFileReferences
|
||||||
|
navigator.clipboard.writeText(`
|
||||||
<div id='safca_api_data'></div>
|
<div id='safca_api_data'></div>
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import {initCompetitionApi} from '${vite_url}/competition.js';
|
import {initCompetitionApi} from '${vite_url}/competition.js';
|
||||||
@@ -288,6 +293,11 @@ function Menu({menuActions, compUuid}) {
|
|||||||
onMouseUp={() => longPressUp("cards")}
|
onMouseUp={() => longPressUp("cards")}
|
||||||
data-bs-toggle="tooltip2" data-bs-placement="top"
|
data-bs-toggle="tooltip2" data-bs-placement="top"
|
||||||
data-bs-title={t("carton")}/>
|
data-bs-title={t("carton")}/>
|
||||||
|
<FontAwesomeIcon icon={faPrint} size="xl"
|
||||||
|
style={{color: "#6c757d", cursor: "pointer"}}
|
||||||
|
onClick={() => printModal.current.click()}
|
||||||
|
data-bs-toggle="tooltip2" data-bs-placement="top"
|
||||||
|
data-bs-title={t('imprimer')}/>
|
||||||
<FontAwesomeIcon icon={SimpleIconsOBS} size="xl"
|
<FontAwesomeIcon icon={SimpleIconsOBS} size="xl"
|
||||||
style={{color: "#6c757d", cursor: "pointer"}}
|
style={{color: "#6c757d", cursor: "pointer"}}
|
||||||
onMouseDown={() => longPressDown("obs")}
|
onMouseDown={() => longPressDown("obs")}
|
||||||
@@ -321,9 +331,8 @@ function Menu({menuActions, compUuid}) {
|
|||||||
<strong>{t('config.obs.warn1')}</strong>
|
<strong>{t('config.obs.warn1')}</strong>
|
||||||
<div className="input-group mb-3">
|
<div className="input-group mb-3">
|
||||||
<span className="input-group-text">{t('adresseDuServeur')}</span>
|
<span className="input-group-text">{t('adresseDuServeur')}</span>
|
||||||
<span className="input-group-text">{t('config.obs.ws')}</span>
|
<input type="text" className="form-control" placeholder="ws://127.0.0.1:4455/" aria-label=""
|
||||||
<input type="text" className="form-control" placeholder="127.0.0.1:4455" aria-label=""
|
defaultValue={"ws://127.0.0.1:4455/"}/>
|
||||||
defaultValue={"127.0.0.1:4455"}/>
|
|
||||||
<span className="input-group-text">/</span>
|
<span className="input-group-text">/</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="input-group mb-3">
|
<div className="input-group mb-3">
|
||||||
@@ -373,6 +382,169 @@ function Menu({menuActions, compUuid}) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<button ref={printModal} type="button" className="btn btn-link" data-bs-toggle="modal" data-bs-target="#PrintModal"
|
||||||
|
style={{display: 'none'}}>
|
||||||
|
Launch printModal
|
||||||
|
</button>
|
||||||
|
<div className="modal fade" id="PrintModal" tabIndex="-1" aria-labelledby="PrintModalLabel" aria-hidden="true">
|
||||||
|
<div className="modal-dialog">
|
||||||
|
<div className="modal-content">
|
||||||
|
<PrintModal menuActions={menuActions}/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
function PrintModal({menuActions}) {
|
||||||
|
const [categorie, setCategorie] = useState(false);
|
||||||
|
const [categorieEmpty, setCategorieEmpty] = useState(false);
|
||||||
|
const [preset, setPreset] = useState(false);
|
||||||
|
const [presetEmpty, setPresetEmpty] = useState(false);
|
||||||
|
const [allCat, setAllCat] = useState(false);
|
||||||
|
const [allCatEmpty, setAllCatEmpty] = useState(false);
|
||||||
|
const [podium, setPodium] = useState(false);
|
||||||
|
const [podiumRank, setPodiumRank] = useState(4);
|
||||||
|
const [podiumClub, setPodiumClub] = useState(false);
|
||||||
|
|
||||||
|
const [presetSelect, setPresetSelect] = useState(-1)
|
||||||
|
|
||||||
|
const {sendRequest, welcomeData} = useWS();
|
||||||
|
const {getComb} = useCombs();
|
||||||
|
const {t} = useTranslation("cm");
|
||||||
|
|
||||||
|
const podiumPromise = (podiumRank_) => {
|
||||||
|
return sendRequest("getPodium", {}).then(data => {
|
||||||
|
return [welcomeData?.name + " - " + t('podium'), [
|
||||||
|
{type: "podium", params: ({data, maxRank: podiumRank_, minRank: Math.min(4, podiumRank_)})},
|
||||||
|
]];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const podiumClubPromise = () => {
|
||||||
|
return sendRequest("getPodiumClub", {}).then(data => {
|
||||||
|
return [welcomeData?.name + " - " + t('classementDesClub', {ns: "result"}), [
|
||||||
|
{type: "podiumClub", params: ({data})},
|
||||||
|
]];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const print = (action) => {
|
||||||
|
const pagesPromise = [];
|
||||||
|
|
||||||
|
if (categorie && menuActions.printCategorie)
|
||||||
|
pagesPromise.push(menuActions.printCategorie(categorieEmpty))
|
||||||
|
|
||||||
|
if (preset && menuActions.printCategoriePreset)
|
||||||
|
pagesPromise.push(menuActions.printCategoriePreset(presetEmpty, presetSelect))
|
||||||
|
|
||||||
|
if (allCat && menuActions.printAllCategorie)
|
||||||
|
pagesPromise.push(menuActions.printAllCategorie(categorieEmpty, welcomeData?.name + " - " + t('toutesLesCatégories')))
|
||||||
|
|
||||||
|
if (podium)
|
||||||
|
pagesPromise.push(podiumPromise(podiumRank));
|
||||||
|
|
||||||
|
if (podiumClub)
|
||||||
|
pagesPromise.push(podiumClubPromise());
|
||||||
|
|
||||||
|
toast.promise(
|
||||||
|
toDataURL("/Logo-FFSAF-2023.png").then(logo => {
|
||||||
|
return Promise.allSettled(pagesPromise).then(results => {
|
||||||
|
const pages = [];
|
||||||
|
const names = [];
|
||||||
|
let errors = 0;
|
||||||
|
|
||||||
|
for (const result of results) {
|
||||||
|
if (result.status === "fulfilled") {
|
||||||
|
const [name, page, error] = result.value;
|
||||||
|
pages.push(...page);
|
||||||
|
names.push(name);
|
||||||
|
errors += error
|
||||||
|
} else if (result.status === "rejected") {
|
||||||
|
errors += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errors > 0) {
|
||||||
|
toast.error(t('erreurGénérationPages', {count: errors}));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pages.length !== 0) {
|
||||||
|
makePDF(action, pages, names.join(" - "), welcomeData?.name, getComb, t, logo)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}), getToastMessage("toast.print", "cm"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>
|
||||||
|
<div className="modal-header">
|
||||||
|
<h5 className="modal-title">{t('quoiImprimer?')}</h5>
|
||||||
|
<button type="button" className="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div className="modal-body">
|
||||||
|
<div className="form-check">
|
||||||
|
<input className="form-check-input" type="checkbox" checked={categorie} id="checkPrint"
|
||||||
|
onChange={e => setCategorie(e.target.checked)}/>
|
||||||
|
<label className="form-check-label" htmlFor="checkPrint">{t('catégorieSélectionnée')}</label>
|
||||||
|
</div>
|
||||||
|
{categorie &&
|
||||||
|
<div className="form-check" style={{marginLeft: "1em"}}>
|
||||||
|
<input className="form-check-input" type="checkbox" checked={categorieEmpty} id="checkPrint2"
|
||||||
|
onChange={e => setCategorieEmpty(e.target.checked)}/>
|
||||||
|
<label className="form-check-label" htmlFor="checkPrint2">{t('feuilleVierge')}</label>
|
||||||
|
</div>}
|
||||||
|
|
||||||
|
<div className="form-check">
|
||||||
|
<input className="form-check-input" type="checkbox" checked={preset} id="checkPrint3"
|
||||||
|
onChange={e => setPreset(e.target.checked)}/>
|
||||||
|
<label className="form-check-label" htmlFor="checkPrint3">{t('touteLaCatégorie')}</label>
|
||||||
|
</div>
|
||||||
|
{preset && <div style={{marginLeft: "1em"}}>
|
||||||
|
<div className="form-check">
|
||||||
|
<input className="form-check-input" type="checkbox" checked={presetEmpty} id="checkPrint4"
|
||||||
|
onChange={e => setPresetEmpty(e.target.checked)}/>
|
||||||
|
<label className="form-check-label" htmlFor="checkPrint4">{t('feuilleVierge')}</label>
|
||||||
|
</div>
|
||||||
|
<ListPresetSelect value={presetSelect} onChange={setPresetSelect}/>
|
||||||
|
</div>}
|
||||||
|
|
||||||
|
<div className="form-check">
|
||||||
|
<input className="form-check-input" type="checkbox" checked={allCat} id="checkPrint5"
|
||||||
|
onChange={e => setAllCat(e.target.checked)}/>
|
||||||
|
<label className="form-check-label" htmlFor="checkPrint5">{t('toutesLesCatégories')}</label>
|
||||||
|
</div>
|
||||||
|
{allCat &&
|
||||||
|
<div className="form-check" style={{marginLeft: "1em"}}>
|
||||||
|
<input className="form-check-input" type="checkbox" checked={allCatEmpty} id="checkPrint6"
|
||||||
|
onChange={e => setAllCatEmpty(e.target.checked)}/>
|
||||||
|
<label className="form-check-label" htmlFor="checkPrint6">{t('feuilleVierge')}</label>
|
||||||
|
</div>}
|
||||||
|
|
||||||
|
<div className="form-check">
|
||||||
|
<input className="form-check-input" type="checkbox" checked={podium} id="checkPrint7"
|
||||||
|
onChange={e => setPodium(e.target.checked)}/>
|
||||||
|
<label className="form-check-label" htmlFor="checkPrint7">{t('podium')}</label>
|
||||||
|
</div>
|
||||||
|
{podium &&
|
||||||
|
<div style={{marginLeft: "1em"}}>
|
||||||
|
<label htmlFor="range3" className="form-label">{t('jusquauRang')} {podiumRank} </label>
|
||||||
|
<input type="range" className="form-range" min="1" max="20" step="1" id="range3" value={podiumRank}
|
||||||
|
onChange={e => setPodiumRank(Number(e.target.value))}/>
|
||||||
|
</div>}
|
||||||
|
|
||||||
|
<div className="form-check">
|
||||||
|
<input className="form-check-input" type="checkbox" checked={podiumClub} id="checkPrint8"
|
||||||
|
onChange={e => setPodiumClub(e.target.checked)}/>
|
||||||
|
<label className="form-check-label" htmlFor="checkPrint8">{t('classementDesClub', {ns: "result"})}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="modal-footer">
|
||||||
|
<button type="submit" className="btn btn-primary" data-bs-dismiss="modal" onClick={() => print("show")}>{t('afficher')}</button>
|
||||||
|
<button type="submit" className="btn btn-primary" data-bs-dismiss="modal" onClick={() => print("download")}>{t('enregistrer')}</button>
|
||||||
|
<button type="submit" className="btn btn-primary" data-bs-dismiss="modal" onClick={() => print("print")}>{t('imprimer')}</button>
|
||||||
|
<button type="button" className="btn btn-secondary" data-bs-dismiss="modal">{t('fermer')}</button>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -499,12 +671,11 @@ function TeamCardModal() {
|
|||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|
||||||
function CategoryHeader({
|
function CategoryHeader({cat, setCatId, menuActions}) {
|
||||||
cat, setCatId
|
|
||||||
}) {
|
|
||||||
const setLoading = useLoadingSwitcher()
|
const setLoading = useLoadingSwitcher()
|
||||||
const bthRef = useRef();
|
const bthRef = useRef(null);
|
||||||
const confirmRef = useRef();
|
const newBthRef = useRef(null);
|
||||||
|
const confirmRef = useRef(null);
|
||||||
const [modal, setModal] = useState({})
|
const [modal, setModal] = useState({})
|
||||||
const [confirm, setConfirm] = useState({})
|
const [confirm, setConfirm] = useState({})
|
||||||
const {t} = useTranslation("cm");
|
const {t} = useTranslation("cm");
|
||||||
@@ -520,6 +691,7 @@ function CategoryHeader({
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
const sendAddCategory = ({data}) => {
|
const sendAddCategory = ({data}) => {
|
||||||
|
console.log("add cat", data);
|
||||||
setCats([...cats, data])
|
setCats([...cats, data])
|
||||||
}
|
}
|
||||||
const sendDelCategory = ({data}) => {
|
const sendDelCategory = ({data}) => {
|
||||||
@@ -553,8 +725,7 @@ function CategoryHeader({
|
|||||||
if (selectedCatId !== "-1") {
|
if (selectedCatId !== "-1") {
|
||||||
setCatId(selectedCatId);
|
setCatId(selectedCatId);
|
||||||
} else { // New category
|
} else { // New category
|
||||||
setModal({});
|
newBthRef.current.click();
|
||||||
bthRef.current.click();
|
|
||||||
e.target.value = cat?.id;
|
e.target.value = cat?.id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -594,14 +765,155 @@ function CategoryHeader({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="modal fade" id="autoNewCatModal" aria-hidden="true" aria-labelledby="autoNewCatModalLabel" tabIndex="-1">
|
||||||
|
<div className="modal-dialog modal-dialog-centered">
|
||||||
|
<div className="modal-content">
|
||||||
|
<AutoNewCatModalContent/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="modal fade" id="autoNewCatsModal" aria-hidden="true" aria-labelledby="autoNewCatsModalLabel" tabIndex="-1">
|
||||||
|
<div className="modal-dialog modal-dialog-centered">
|
||||||
|
<div className="modal-content">
|
||||||
|
<AutoNewCatSModalContent/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button ref={confirmRef} data-bs-toggle="modal" data-bs-target="#confirm-dialog" style={{display: "none"}}>open</button>
|
<button ref={confirmRef} data-bs-toggle="modal" data-bs-target="#confirm-dialog" style={{display: "none"}}>open</button>
|
||||||
<ConfirmDialog id="confirm-dialog" onConfirm={confirm.confirm ? confirm.confirm : () => {
|
<ConfirmDialog id="confirm-dialog" onConfirm={confirm.confirm ? confirm.confirm : () => {
|
||||||
}} onCancel={confirm.cancel ? confirm.cancel : () => {
|
}} onCancel={confirm.cancel ? confirm.cancel : () => {
|
||||||
}} title={confirm ? confirm.title : ""} message={confirm ? confirm.message : ""}/>
|
}} title={confirm ? confirm.title : ""} message={confirm ? confirm.message : ""}/>
|
||||||
|
|
||||||
|
|
||||||
|
<button ref={newBthRef} data-bs-toggle="modal" data-bs-target="#newCatModal" style={{display: "none"}}>open</button>
|
||||||
|
|
||||||
|
<div className="modal fade" id="newCatModal" tabIndex="-1" aria-labelledby="newCatModalLabel" aria-hidden="true">
|
||||||
|
<div className="modal-dialog modal-dialog-centered">
|
||||||
|
<div className="modal-content">
|
||||||
|
<div className="modal-header">
|
||||||
|
<h1 className="modal-title fs-5" id="newCatModalLabel">{t('ajouter')} {t('uneCatégorie')}</h1>
|
||||||
|
<button type="button" className="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div className="modal-body text-center">
|
||||||
|
{t('modeDeCréation')} :
|
||||||
|
<div className="mb-2">
|
||||||
|
<button className="btn btn-primary" onClick={() => {
|
||||||
|
setModal({});
|
||||||
|
bthRef.current.click();
|
||||||
|
e.target.value = cat?.id;
|
||||||
|
}}>{t('personnaliser')}</button>
|
||||||
|
</div>
|
||||||
|
<div className="mb-2">
|
||||||
|
<button className="btn btn-primary" data-bs-target="#autoNewCatModal"
|
||||||
|
data-bs-toggle="modal">{t('depuisUneCatégoriePrédéfinie')}</button>
|
||||||
|
</div>
|
||||||
|
<div className="mb-2">
|
||||||
|
<button className="btn btn-primary" data-bs-target="#autoNewCatsModal"
|
||||||
|
data-bs-toggle="modal">{t('créerToutesLesCatégories')}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<PrintCats menuActions={menuActions} cats={cats}/>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function PrintCats({menuActions, cats}) {
|
||||||
|
const {cards_v} = useCards();
|
||||||
|
const {sendRequest} = useWS();
|
||||||
|
|
||||||
|
function readAndConvertMatch(matches, data) {
|
||||||
|
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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const run = (categorieEmpty, cats2, name = "") => {
|
||||||
|
const pagesPromise = cats2.sort((a, b) => a.name.localeCompare(b.name)).map(cat_ => {
|
||||||
|
return sendRequest('getFullCategory', cat_.id)
|
||||||
|
.then((data) => {
|
||||||
|
const cat = {
|
||||||
|
id: data.id,
|
||||||
|
name: data.name,
|
||||||
|
liceName: data.liceName,
|
||||||
|
type: data.type,
|
||||||
|
trees: data.trees.sort((a, b) => a.level - b.level).map(d => from_sendTree(d, true)),
|
||||||
|
raw_trees: data.trees.sort((a, b) => a.level - b.level),
|
||||||
|
treeAreClassement: data.treeAreClassement,
|
||||||
|
fullClassement: data.fullClassement,
|
||||||
|
preset: data.preset,
|
||||||
|
}
|
||||||
|
if (name === "") {
|
||||||
|
name = data.preset.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newCards = {};
|
||||||
|
for (const o of data.cards)
|
||||||
|
newCards[o.id] = o
|
||||||
|
|
||||||
|
let matches2 = [];
|
||||||
|
data.trees.flatMap(d => from_sendTree(d, false).flat()).forEach((data_) => readAndConvertMatch(matches2, data_));
|
||||||
|
data.matches.forEach((data_) => readAndConvertMatch(matches2, data_));
|
||||||
|
|
||||||
|
const activeMatches = matches2.filter(m => m.poule !== '-')
|
||||||
|
const groups = 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)
|
||||||
|
poule = '-'
|
||||||
|
return {id: d, poule: poule}
|
||||||
|
})
|
||||||
|
|
||||||
|
matches2 = matches2.filter(m => m.categorie === cat.id)
|
||||||
|
.map(m => ({...m, ...win_end(m, cards_v)}))
|
||||||
|
matches2.forEach(m => {
|
||||||
|
if (m.end && (!m.scores || m.scores.length === 0))
|
||||||
|
m.scores = [{n_round: 0, s1: 0, s2: 0}];
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: "categorie",
|
||||||
|
params: ({cat, matches: matches2, groups, cards_v: Object.values({...cards_v, ...newCards}), categorieEmpty})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return Promise.allSettled(pagesPromise)
|
||||||
|
.then((results) => {
|
||||||
|
const pages = [];
|
||||||
|
let error = 0;
|
||||||
|
|
||||||
|
for (const result of results) {
|
||||||
|
if (result.status === "fulfilled") {
|
||||||
|
pages.push(result.value);
|
||||||
|
} else {
|
||||||
|
console.error(result.error);
|
||||||
|
error++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [name, pages, error];
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
menuActions.printCategoriePreset = (categorieEmpty, preset) => {
|
||||||
|
return run(categorieEmpty, cats.filter(cat => cat.preset?.id === preset))
|
||||||
|
}
|
||||||
|
|
||||||
|
menuActions.printAllCategorie = (categorieEmpty, name) => {
|
||||||
|
return run(categorieEmpty, cats, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function ModalContent({state, setCatId, setConfirm, confirmRef}) {
|
function ModalContent({state, setCatId, setConfirm, confirmRef}) {
|
||||||
|
const id = useId()
|
||||||
const [name, setName] = useState("")
|
const [name, setName] = useState("")
|
||||||
const [lice, setLice] = useState("1")
|
const [lice, setLice] = useState("1")
|
||||||
const [poule, setPoule] = useState(true)
|
const [poule, setPoule] = useState(true)
|
||||||
@@ -610,19 +922,27 @@ function ModalContent({state, setCatId, setConfirm, confirmRef}) {
|
|||||||
const [fullClassement, setFullClassement] = useState(false)
|
const [fullClassement, setFullClassement] = useState(false)
|
||||||
const [size, setSize] = useState(4)
|
const [size, setSize] = useState(4)
|
||||||
const [loserMatch, setLoserMatch] = useState(1)
|
const [loserMatch, setLoserMatch] = useState(1)
|
||||||
|
const [preset, setPreset] = useState(-1)
|
||||||
const {t} = useTranslation("cm");
|
const {t} = useTranslation("cm");
|
||||||
|
|
||||||
const {sendRequest} = useWS();
|
const {sendRequest} = useWS();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
console.log(state);
|
||||||
setName(state.name || "");
|
setName(state.name || "");
|
||||||
setLice(state.liceName || "1");
|
setLice(state.liceName || "1");
|
||||||
setPoule(((state.type || 1) & 1) !== 0);
|
setPoule(((state.type || 1) & 1) !== 0);
|
||||||
setTournoi((state.type & 2) !== 0);
|
setTournoi((state.type & 2) !== 0);
|
||||||
setClassement(state.treeAreClassement !== undefined && state.treeAreClassement !== false);
|
setClassement(state.treeAreClassement !== undefined && state.treeAreClassement !== false);
|
||||||
setFullClassement(state.fullClassement !== undefined && state.fullClassement !== false);
|
setFullClassement(state.fullClassement !== undefined && state.fullClassement !== false);
|
||||||
|
setPreset(state.preset?.id || -1);
|
||||||
|
|
||||||
const trees_ = state?.trees ? state.trees.filter(t => t.level > 0) : null;
|
let trees_ = []
|
||||||
|
for (let i = 0; i < state?.raw_trees?.length; i++) {
|
||||||
|
if (state.raw_trees.at(i).level > 0) {
|
||||||
|
trees_.push(state.trees.at(i))
|
||||||
|
}
|
||||||
|
}
|
||||||
if (trees_ && trees_.length >= 1) {
|
if (trees_ && trees_.length >= 1) {
|
||||||
const tree = trees_[0];
|
const tree = trees_[0];
|
||||||
setSize(tree.getMaxChildrenAtDepth(tree.death() - 1) * 2);
|
setSize(tree.getMaxChildrenAtDepth(tree.death() - 1) * 2);
|
||||||
@@ -655,7 +975,12 @@ function ModalContent({state, setCatId, setConfirm, confirmRef}) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const trees_ = state?.trees ? state.trees.filter(t => t.level > 0) : null;
|
let trees_ = []
|
||||||
|
for (let i = 0; i < state?.raw_trees?.length; i++) {
|
||||||
|
if (state.raw_trees.at(i).level > 0) {
|
||||||
|
trees_.push(state.trees.at(i))
|
||||||
|
}
|
||||||
|
}
|
||||||
if (state?.id) {
|
if (state?.id) {
|
||||||
const applyChanges = () => {
|
const applyChanges = () => {
|
||||||
const newData = {
|
const newData = {
|
||||||
@@ -664,7 +989,8 @@ function ModalContent({state, setCatId, setConfirm, confirmRef}) {
|
|||||||
liceName: lice.trim(),
|
liceName: lice.trim(),
|
||||||
type: nType,
|
type: nType,
|
||||||
treeAreClassement: classement,
|
treeAreClassement: classement,
|
||||||
fullClassement: fullClassement
|
fullClassement: fullClassement,
|
||||||
|
preset: {id: preset !== -1 ? preset : null}
|
||||||
}
|
}
|
||||||
|
|
||||||
let nbMatch = -1;
|
let nbMatch = -1;
|
||||||
@@ -735,7 +1061,10 @@ function ModalContent({state, setCatId, setConfirm, confirmRef}) {
|
|||||||
toast.promise(sendRequest('createCategory', {
|
toast.promise(sendRequest('createCategory', {
|
||||||
name: name.trim(),
|
name: name.trim(),
|
||||||
liceName: lice.trim(),
|
liceName: lice.trim(),
|
||||||
type: nType
|
type: nType,
|
||||||
|
treeAreClassement: classement,
|
||||||
|
fullClassement: fullClassement,
|
||||||
|
preset: {id: preset !== -1 ? preset : null}
|
||||||
}), getToastMessage("toast.createCategory", "cm")
|
}), getToastMessage("toast.createCategory", "cm")
|
||||||
).then(id => {
|
).then(id => {
|
||||||
if (tournoi) {
|
if (tournoi) {
|
||||||
@@ -764,9 +1093,12 @@ function ModalContent({state, setCatId, setConfirm, confirmRef}) {
|
|||||||
onChange={e => setName(e.target.value)}/>
|
onChange={e => setName(e.target.value)}/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<ListPresetSelect value={preset} onChange={setPreset}/>
|
||||||
|
|
||||||
<div className="mb-3">
|
<div className="mb-3">
|
||||||
<label htmlFor="liceInput1" className="form-label"><Trans i18nKey="nomDesZonesDeCombat" ns="cm">t <small>(séparée par des ';')</small></Trans></label>
|
<label htmlFor={id + "liceInput1"} className="form-label"><Trans i18nKey="nomDesZonesDeCombat" ns="cm">t <small>(séparée par des
|
||||||
<input type="text" className="form-control" id="liceInput1" placeholder="1;2" name="zone de combat" value={lice}
|
';')</small></Trans></label>
|
||||||
|
<input type="text" className="form-control" id={id + "liceInput1"} placeholder="1;2" name="zone de combat" value={lice}
|
||||||
onChange={e => setLice(e.target.value)}/>
|
onChange={e => setLice(e.target.value)}/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {timePrint} from "../../../utils/Tools.js";
|
|||||||
import {useTranslation} from "react-i18next";
|
import {useTranslation} from "react-i18next";
|
||||||
import {useWS} from "../../../hooks/useWS.jsx";
|
import {useWS} from "../../../hooks/useWS.jsx";
|
||||||
|
|
||||||
export function ChronoPanel() {
|
export function ChronoPanel({menuActions}) {
|
||||||
const [config, setConfig] = useState({
|
const [config, setConfig] = useState({
|
||||||
time: Number(sessionStorage.getItem("chronoTime") || "90999"),
|
time: Number(sessionStorage.getItem("chronoTime") || "90999"),
|
||||||
pause: Number(sessionStorage.getItem("chronoPause") || "60999")
|
pause: Number(sessionStorage.getItem("chronoPause") || "60999")
|
||||||
@@ -25,6 +25,10 @@ export function ChronoPanel() {
|
|||||||
return chrono.time + Date.now() - chrono.startTime
|
return chrono.time + Date.now() - chrono.startTime
|
||||||
}
|
}
|
||||||
|
|
||||||
|
menuActions.current.setTimerConfig = (time, pause) => {
|
||||||
|
setConfig({time: time + 999, pause: pause + 999})
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
publicAffDispatch({type: 'CALL_TIME', payload: {timeStr: state.current.lastTimeStr, timeColor: state.current.color}})
|
publicAffDispatch({type: 'CALL_TIME', payload: {timeStr: state.current.lastTimeStr, timeColor: state.current.color}})
|
||||||
}, [])
|
}, [])
|
||||||
|
|||||||
@@ -6,13 +6,24 @@ import {DrawGraph} from "../../result/DrawGraph.jsx";
|
|||||||
import {LoadingProvider, useLoadingSwitcher} from "../../../hooks/useLoading.jsx";
|
import {LoadingProvider, useLoadingSwitcher} from "../../../hooks/useLoading.jsx";
|
||||||
import {useRequestWS, useWS} from "../../../hooks/useWS.jsx";
|
import {useRequestWS, useWS} from "../../../hooks/useWS.jsx";
|
||||||
import {MarchReducer} from "../../../utils/MatchReducer.jsx";
|
import {MarchReducer} from "../../../utils/MatchReducer.jsx";
|
||||||
import {getToastMessage, virtualScore, win_end} from "../../../utils/Tools.js";
|
import {
|
||||||
|
CatList,
|
||||||
|
getCatName, getShieldSize,
|
||||||
|
getShieldTypeName, getSwordSize,
|
||||||
|
getSwordTypeName,
|
||||||
|
getToastMessage, timePrint,
|
||||||
|
virtual_end,
|
||||||
|
virtualScore,
|
||||||
|
win_end
|
||||||
|
} from "../../../utils/Tools.js";
|
||||||
import "./CMTMatchPanel.css"
|
import "./CMTMatchPanel.css"
|
||||||
import {useOBS} from "../../../hooks/useOBS.jsx";
|
import {useOBS} from "../../../hooks/useOBS.jsx";
|
||||||
import {useTranslation} from "react-i18next";
|
import {useTranslation} from "react-i18next";
|
||||||
import {hasEffectCard, useCards, useCardsDispatch} from "../../../hooks/useCard.jsx";
|
import {hasEffectCard, useCards, useCardsDispatch} from "../../../hooks/useCard.jsx";
|
||||||
import {ScorePanel} from "./ScoreAndCardPanel.jsx";
|
import {ScorePanel} from "./ScoreAndCardPanel.jsx";
|
||||||
import {toast} from "react-toastify";
|
import {toast} from "react-toastify";
|
||||||
|
import {createPortal} from "react-dom";
|
||||||
|
import ProtectionSelector from "../../../components/ProtectionSelector.jsx";
|
||||||
|
|
||||||
function CupImg() {
|
function CupImg() {
|
||||||
return <img decoding="async" loading="lazy" width={"16"} height={"16"} className="wp-image-1635"
|
return <img decoding="async" loading="lazy" width={"16"} height={"16"} className="wp-image-1635"
|
||||||
@@ -20,10 +31,17 @@ function CupImg() {
|
|||||||
alt=""/>
|
alt=""/>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function CupImg2() {
|
||||||
|
return <img decoding="async" loading="lazy" width={"16"} height={"16"} className="wp-image-1635"
|
||||||
|
style={{width: "16px"}} src="/img/171892.png"
|
||||||
|
alt=""/>
|
||||||
|
}
|
||||||
|
|
||||||
export function CategorieSelect({catId, setCatId, menuActions}) {
|
export function CategorieSelect({catId, setCatId, menuActions}) {
|
||||||
const setLoading = useLoadingSwitcher()
|
const setLoading = useLoadingSwitcher()
|
||||||
const {data: cats, setData: setCats} = useRequestWS('getAllCategory', {}, setLoading);
|
const {data: cats, setData: setCats} = useRequestWS('getAllCategory', {}, setLoading);
|
||||||
const {dispatch} = useWS();
|
const {dispatch} = useWS();
|
||||||
|
const publicAffDispatch = usePubAffDispatch();
|
||||||
const {connected, setText} = useOBS();
|
const {connected, setText} = useOBS();
|
||||||
const {t} = useTranslation("cm");
|
const {t} = useTranslation("cm");
|
||||||
|
|
||||||
@@ -53,6 +71,8 @@ export function CategorieSelect({catId, setCatId, menuActions}) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setText("poule", cat ? cat.name : "");
|
setText("poule", cat ? cat.name : "");
|
||||||
|
if (!cat)
|
||||||
|
publicAffDispatch({type: 'SET_DATA', payload: {c1: undefined, c2: undefined, next: []}});
|
||||||
}, [cat, connected]);
|
}, [cat, connected]);
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
@@ -149,7 +169,97 @@ function CMTMatchPanel({catId, cat, menuActions}) {
|
|||||||
}
|
}
|
||||||
}, [catId]);
|
}, [catId]);
|
||||||
|
|
||||||
return <ListMatch cat={cat} matches={matches} trees={trees} menuActions={menuActions}/>
|
return <>
|
||||||
|
<ListMatch cat={cat} matches={matches} trees={trees} menuActions={menuActions}/>
|
||||||
|
<SetTimeToChrono cat={cat} matches={matches} menuActions={menuActions}/>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
function SetTimeToChrono({cat, matches, menuActions}) {
|
||||||
|
const [catAverage, setCatAverage] = useState("---");
|
||||||
|
const [genreAverage, setGenreAverage] = useState("H");
|
||||||
|
const [nbComb, setNbComb] = useState(0);
|
||||||
|
const [preset, setPreset] = useState(undefined);
|
||||||
|
const [time, setTime] = useState({round: 0, pause: 0});
|
||||||
|
const {cards_v} = useCards();
|
||||||
|
|
||||||
|
const {t} = useTranslation("cm");
|
||||||
|
const {getComb} = useCombs();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!cat || matches.filter(m => m.categorie === cat.id).length === 0) {
|
||||||
|
setCatAverage("---");
|
||||||
|
setNbComb(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setPreset(cat.preset);
|
||||||
|
|
||||||
|
const genres = [];
|
||||||
|
const cats = [];
|
||||||
|
const combs = [];
|
||||||
|
for (const m of matches.filter(m => m.categorie === cat.id)) {
|
||||||
|
if (m.c1 && !combs.includes(m.c1))
|
||||||
|
combs.push(m.c1);
|
||||||
|
if (m.c2 && !combs.includes(m.c2))
|
||||||
|
combs.push(m.c2);
|
||||||
|
}
|
||||||
|
setNbComb(combs.length);
|
||||||
|
|
||||||
|
combs.map(cId => getComb(cId, null)).filter(c => c && c.categorie)
|
||||||
|
.forEach(c => {
|
||||||
|
cats.push(Math.min(CatList.length, CatList.indexOf(c.categorie) + c.overCategory))
|
||||||
|
genres.push(c.genre)
|
||||||
|
});
|
||||||
|
|
||||||
|
const catAvg = Math.round(cats.reduce((a, b) => a + b, 0) / cats.length);
|
||||||
|
setCatAverage(CatList.at(catAvg) || "---");
|
||||||
|
|
||||||
|
const genreAvg = Math.round(genres.reduce((a, b) => a + (b === "F" ? 1 : 0), 0) / genres.length);
|
||||||
|
setGenreAverage(genreAvg > 0.5 ? "F" : "H");
|
||||||
|
|
||||||
|
if (!cat.preset || !cat.preset.categories)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const catAvailable = cat.preset.categories.map(c => CatList.indexOf(c.categorie));
|
||||||
|
|
||||||
|
let p;
|
||||||
|
if (catAvailable.includes(catAvg)) {
|
||||||
|
p = cat.preset.categories.find(c => CatList.indexOf(c.categorie) === catAvg);
|
||||||
|
} else {
|
||||||
|
const closest = catAvailable.reduce((a, b) => Math.abs(b - catAvg) < Math.abs(a - catAvg) ? b : a);
|
||||||
|
p = cat.preset.categories.find(c => CatList.indexOf(c.categorie) === closest);
|
||||||
|
}
|
||||||
|
menuActions.current.setTimerConfig(p.roundDuration, p.pauseDuration)
|
||||||
|
setTime({round: p.roundDuration, pause: p.pauseDuration})
|
||||||
|
|
||||||
|
}, [cat, matches]);
|
||||||
|
|
||||||
|
const marches2 = matches.filter(m => m.categorie === cat.id)
|
||||||
|
.map(m => ({...m, end: m.end || virtual_end(m, cards_v)}))
|
||||||
|
|
||||||
|
return createPortal(<div className="card mb-3">
|
||||||
|
<div className="card-header">{t('informationCatégorie')}</div>
|
||||||
|
<div className="card-body">
|
||||||
|
<div className="row">
|
||||||
|
<div className="col text-start">
|
||||||
|
<div>{t('catégorie')} : {getCatName(catAverage)}</div>
|
||||||
|
<div>{t('arme', {ns: 'common'})} : {getSwordTypeName(preset?.sword)} - {t('taille')} {getSwordSize(preset?.sword, catAverage, genreAverage)}</div>
|
||||||
|
<div>{t('bouclier', {ns: 'common'})} : {getShieldTypeName(preset?.shield)} - {t('taille')} {getShieldSize(preset?.shield, catAverage)}</div>
|
||||||
|
<div>{t('duréeRound')} : {timePrint(time.round)}</div>
|
||||||
|
<div>{t('duréePause')} : {timePrint(time.pause)}</div>
|
||||||
|
<div>{t('matchTerminé')}: {marches2.filter(m => m.end).length} sur {marches2.length}</div>
|
||||||
|
<div>{t('nombreDeCombattants')} : {nbComb}</div>
|
||||||
|
</div>
|
||||||
|
<div className="col text-center">
|
||||||
|
<h6>{t('protectionObligatoire', {ns: 'common'})} :</h6>
|
||||||
|
<ProtectionSelector shield={preset?.shield !== "NONE"}
|
||||||
|
mandatoryProtection={CatList.indexOf(catAverage) <= CatList.indexOf("JUNIOR") ?
|
||||||
|
preset?.mandatoryProtection1 : preset?.mandatoryProtection2} setMandatoryProtection={() => {
|
||||||
|
}}/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>, document.getElementById("infoCategory"))
|
||||||
}
|
}
|
||||||
|
|
||||||
function ListMatch({cat, matches, trees, menuActions}) {
|
function ListMatch({cat, matches, trees, menuActions}) {
|
||||||
@@ -210,9 +320,9 @@ function MatchList({matches, cat, menuActions, classement = false, currentMatch
|
|||||||
|
|
||||||
const liceName = (cat.liceName || "N/A").split(";");
|
const liceName = (cat.liceName || "N/A").split(";");
|
||||||
const marches2 = classement
|
const marches2 = classement
|
||||||
? matches.filter(m => m.categorie_ord === -42)
|
? matches.filter(m => m.categorie_ord === -42 && m.categorie === cat.id)
|
||||||
.map(m => ({...m, ...win_end(m, cards_v)}))
|
.map(m => ({...m, ...win_end(m, cards_v)}))
|
||||||
: matches.filter(m => m.categorie_ord !== -42)
|
: matches.filter(m => m.categorie_ord !== -42 && m.categorie === cat.id)
|
||||||
.sort((a, b) => a.categorie_ord - b.categorie_ord)
|
.sort((a, b) => a.categorie_ord - b.categorie_ord)
|
||||||
.map(m => ({...m, ...win_end(m, cards_v)}))
|
.map(m => ({...m, ...win_end(m, cards_v)}))
|
||||||
const firstIndex = marches2.findLastIndex(m => m.poule === '-') + 1;
|
const firstIndex = marches2.findLastIndex(m => m.poule === '-') + 1;
|
||||||
@@ -342,14 +452,14 @@ function MatchList({matches, cat, menuActions, classement = false, currentMatch
|
|||||||
{!classement && <td style={{textAlign: "center", paddingLeft: "0.2em", paddingRight: "0.2em"}}>{m.poule}</td>}
|
{!classement && <td style={{textAlign: "center", paddingLeft: "0.2em", paddingRight: "0.2em"}}>{m.poule}</td>}
|
||||||
<th style={{textAlign: "center", paddingLeft: "0.2em", paddingRight: "0.2em"}}>
|
<th style={{textAlign: "center", paddingLeft: "0.2em", paddingRight: "0.2em"}}>
|
||||||
{index >= firstIndex ? index + 1 - firstIndex : ""}</th>
|
{index >= firstIndex ? index + 1 - firstIndex : ""}</th>
|
||||||
<td style={{textAlign: "right", paddingRight: "0"}}>{m.end && m.win > 0 && <CupImg/>}</td>
|
<td style={{textAlign: "right", paddingRight: "0"}}>{m.end && ((m.win > 0 && <CupImg/>) || (m.win === 0 && <CupImg2/>))}</td>
|
||||||
<td style={{textAlign: "center", minWidth: "11em", paddingLeft: "0.2em"}}>
|
<td style={{textAlign: "center", minWidth: "11em", paddingLeft: "0.2em"}}>
|
||||||
<small className="position-relative"><CombName combId={m.c1}/>
|
<small className="position-relative"><CombName combId={m.c1}/>
|
||||||
<GetCard match={m} combId={m.c1} cat={cat}/></small></td>
|
<GetCard match={m} combId={m.c1} cat={cat}/></small></td>
|
||||||
<td style={{textAlign: "center", minWidth: "11em", paddingRight: "0.2em"}}>
|
<td style={{textAlign: "center", minWidth: "11em", paddingRight: "0.2em"}}>
|
||||||
<small className="position-relative"><CombName combId={m.c2}/>
|
<small className="position-relative"><CombName combId={m.c2}/>
|
||||||
<GetCard match={m} combId={m.c2} cat={cat}/></small></td>
|
<GetCard match={m} combId={m.c2} cat={cat}/></small></td>
|
||||||
<td style={{textAlign: "left", paddingLeft: "0"}}>{m.end && m.win < 0 && <CupImg/>}</td>
|
<td style={{textAlign: "left", paddingLeft: "0"}}>{m.end && ((m.win < 0 && <CupImg/>) || (m.win === 0 && <CupImg2/>))}</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export function PointPanel({menuActions}) {
|
|||||||
|
|
||||||
<div className="col row align-items-center">
|
<div className="col row align-items-center">
|
||||||
<button className="btn btn-danger" onClick={handleReset}>{t('réinitialiser')}</button>
|
<button className="btn btn-danger" onClick={handleReset}>{t('réinitialiser')}</button>
|
||||||
<button className="btn btn-success" onClick={handleSave}>{t('sauvegarder')}</button>
|
{ /*<button className="btn btn-success" onClick={handleSave}>{t('sauvegarder')}</button> */}
|
||||||
</div>
|
</div>
|
||||||
<SendScore scoreRouge={scoreRouge} scoreBleu={scoreBleu}/>
|
<SendScore scoreRouge={scoreRouge} scoreBleu={scoreBleu}/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import React, {useEffect, useRef, useState} from "react";
|
import React, {useEffect, useRef, useState} from "react";
|
||||||
import {useRequestWS, useWS} from "../../../hooks/useWS.jsx";
|
import {useRequestWS, useWS} from "../../../hooks/useWS.jsx";
|
||||||
import {CombName, 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";
|
||||||
import {copyStyles} from "../../../utils/copyStyles.js";
|
import {copyStyles} from "../../../utils/copyStyles.js";
|
||||||
import {PubAffProvider, usePubAffDispatch, usePubAffState} from "../../../hooks/useExternalWindow.jsx";
|
import {PubAffProvider, usePubAffDispatch, usePubAffState} from "../../../hooks/useExternalWindow.jsx";
|
||||||
import {faArrowRightArrowLeft, faDisplay, faFile, faTrash} from "@fortawesome/free-solid-svg-icons";
|
import {faArrowRightArrowLeft, faDisplay, faFile} from "@fortawesome/free-solid-svg-icons";
|
||||||
import {PubAffWindow} from "./PubAffWindow.jsx";
|
import {PubAffWindow} from "./PubAffWindow.jsx";
|
||||||
import {SimpleIconsScore} from "../../../assets/SimpleIconsScore.ts";
|
import {SimpleIconsScore} from "../../../assets/SimpleIconsScore.ts";
|
||||||
import {ChronoPanel} from "./CMTChronoPanel.jsx";
|
import {ChronoPanel} from "./CMTChronoPanel.jsx";
|
||||||
@@ -15,7 +15,6 @@ import {importOBSConfiguration, OBSProvider, useOBS} from "../../../hooks/useOBS
|
|||||||
import {SimpleIconsOBS} from "../../../assets/SimpleIconsOBS.ts";
|
import {SimpleIconsOBS} from "../../../assets/SimpleIconsOBS.ts";
|
||||||
import {Flip, toast} from "react-toastify";
|
import {Flip, toast} from "react-toastify";
|
||||||
import {useTranslation} from "react-i18next";
|
import {useTranslation} from "react-i18next";
|
||||||
import {useCards, useCardsDispatch} from "../../../hooks/useCard.jsx";
|
|
||||||
import {getToastMessage} from "../../../utils/Tools.js";
|
import {getToastMessage} from "../../../utils/Tools.js";
|
||||||
|
|
||||||
export function CMTable() {
|
export function CMTable() {
|
||||||
@@ -39,7 +38,7 @@ export function CMTable() {
|
|||||||
<div className="card mb-3">
|
<div className="card mb-3">
|
||||||
<div className="card-header">{t('chronomètre')}</div>
|
<div className="card-header">{t('chronomètre')}</div>
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
<ChronoPanel/>
|
<ChronoPanel menuActions={menuActions}/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -49,6 +48,8 @@ export function CMTable() {
|
|||||||
<PointPanel menuActions={menuActions}/>
|
<PointPanel menuActions={menuActions}/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="infoCategory"></div>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-md-12 col-xl-6 col-xxl-5">
|
<div className="col-md-12 col-xl-6 col-xxl-5">
|
||||||
<div className="card mb-3">
|
<div className="card mb-3">
|
||||||
@@ -159,7 +160,7 @@ function Menu({menuActions}) {
|
|||||||
} else {
|
} else {
|
||||||
importOBSConfiguration()
|
importOBSConfiguration()
|
||||||
.then(config => {
|
.then(config => {
|
||||||
connect("ws://" + config.adresse + "/", config.password, config.assets_dir);
|
connect(config.adresse, config.password, config.assets_dir);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
toast.error(t('aucuneConfigurationObs'));
|
toast.error(t('aucuneConfigurationObs'));
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import {hasEffectCard, useCards, useCardsDispatch} from "../../../hooks/useCard.
|
|||||||
|
|
||||||
import {ScorePanel} from "./ScoreAndCardPanel.jsx";
|
import {ScorePanel} from "./ScoreAndCardPanel.jsx";
|
||||||
import {ConfirmDialog} from "../../../components/ConfirmDialog.jsx";
|
import {ConfirmDialog} from "../../../components/ConfirmDialog.jsx";
|
||||||
|
import {AutoCatModalContent} from "../../../components/cm/AutoCatModalContent.jsx";
|
||||||
|
|
||||||
const vite_url = import.meta.env.VITE_URL;
|
const vite_url = import.meta.env.VITE_URL;
|
||||||
|
|
||||||
@@ -30,6 +31,12 @@ function CupImg() {
|
|||||||
alt=""/>
|
alt=""/>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function CupImg2() {
|
||||||
|
return <img decoding="async" loading="lazy" width={"16"} height={"16"} className="wp-image-1635"
|
||||||
|
style={{width: "16px"}} src="/img/171892.png"
|
||||||
|
alt=""/>
|
||||||
|
}
|
||||||
|
|
||||||
export function CategoryContent({cat, catId, setCat, menuActions}) {
|
export function CategoryContent({cat, catId, setCat, menuActions}) {
|
||||||
const setLoading = useLoadingSwitcher()
|
const setLoading = useLoadingSwitcher()
|
||||||
const {sendRequest, dispatch} = useWS();
|
const {sendRequest, dispatch} = useWS();
|
||||||
@@ -79,6 +86,9 @@ export function CategoryContent({cat, catId, setCat, menuActions}) {
|
|||||||
reducer({type: 'UPDATE_OR_ADD', payload: {...data, c1: data.c1?.id, c2: data.c2?.id}});
|
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)}});
|
combDispatch({type: 'SET_ALL', payload: {source: "match", data: [data.c1, data.c2].filter(d => d != null)}});
|
||||||
|
|
||||||
|
if (data.categorie !== cat.id)
|
||||||
|
continue;
|
||||||
|
|
||||||
setGroups(prev => {
|
setGroups(prev => {
|
||||||
if (data.c1 !== null && !prev.some(g => g.id === data.c1?.id))
|
if (data.c1 !== null && !prev.some(g => g.id === data.c1?.id))
|
||||||
return [...prev, {id: data.c1?.id, poule: data.poule}];
|
return [...prev, {id: data.c1?.id, poule: data.poule}];
|
||||||
@@ -169,15 +179,34 @@ export function CategoryContent({cat, catId, setCat, menuActions}) {
|
|||||||
|
|
||||||
return <>
|
return <>
|
||||||
<div className="col-md-3">
|
<div className="col-md-3">
|
||||||
<AddComb groups={groups} setGroups={setGroups} removeGroup={removeGroup} menuActions={menuActions}/>
|
<AddComb groups={groups} setGroups={setGroups} removeGroup={removeGroup} menuActions={menuActions} cat={cat}/>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-md-9">
|
<div className="col-md-9">
|
||||||
{cat && <ListMatch cat={cat} matches={matches} groups={groups} reducer={reducer}/>}
|
{cat && <ListMatch cat={cat} matches={matches} groups={groups} reducer={reducer}/>}
|
||||||
</div>
|
</div>
|
||||||
|
<PrintMatch menuActions={menuActions} matches={matches} groups={groups} cat={cat}/>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|
||||||
function AddComb({groups, setGroups, removeGroup, menuActions}) {
|
function PrintMatch({menuActions, cat, groups, matches}) {
|
||||||
|
const {cards_v} = useCards();
|
||||||
|
const marches2 = matches.filter(m => m.categorie === cat.id)
|
||||||
|
.map(m => ({...m, ...win_end(m, cards_v)}))
|
||||||
|
|
||||||
|
marches2.forEach(m => {
|
||||||
|
if (m.end && (!m.scores || m.scores.length === 0))
|
||||||
|
m.scores = [{n_round: 0, s1: 0, s2: 0}];
|
||||||
|
})
|
||||||
|
|
||||||
|
menuActions.printCategorie = (categorieEmpty) => {
|
||||||
|
return [cat.name, [
|
||||||
|
{type: "categorie", params: ({cat, matches: marches2, groups, cards_v, categorieEmpty})}
|
||||||
|
]]
|
||||||
|
}
|
||||||
|
return <></>
|
||||||
|
}
|
||||||
|
|
||||||
|
function AddComb({groups, setGroups, removeGroup, menuActions, cat}) {
|
||||||
const {data, setData} = useRequestWS("getRegister", null)
|
const {data, setData} = useRequestWS("getRegister", null)
|
||||||
const combDispatch = useCombsDispatch()
|
const combDispatch = useCombsDispatch()
|
||||||
const {dispatch} = useWS()
|
const {dispatch} = useWS()
|
||||||
@@ -240,10 +269,23 @@ function AddComb({groups, setGroups, removeGroup, menuActions}) {
|
|||||||
disabled={data === null} onClick={() => setModalMode(true)}>{t('ajouterUneTeam')}
|
disabled={data === null} onClick={() => setModalMode(true)}>{t('ajouterUneTeam')}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<button type="button" className="btn btn-primary mt-3 w-100" data-bs-toggle="modal" data-bs-target="#autoCatModal"
|
||||||
|
disabled={data === null}>{t('ajoutAutomatique')}
|
||||||
|
</button>
|
||||||
|
|
||||||
<div className="modal fade" id="selectCombModal" tabIndex="-1" aria-labelledby="selectCombModalLabel" aria-hidden="true">
|
<div className="modal fade" id="selectCombModal" tabIndex="-1" aria-labelledby="selectCombModalLabel" aria-hidden="true">
|
||||||
<div className="modal-dialog modal-dialog-scrollable modal-lg modal-fullscreen-lg-down">
|
<div className="modal-dialog modal-dialog-scrollable modal-lg modal-fullscreen-lg-down">
|
||||||
<div className="modal-content">
|
<div className="modal-content">
|
||||||
<SelectCombModalContent data={data} setGroups={setGroups} teamMode={modalMode} groups={groups}/>
|
<SelectCombModalContent data={data} setGroups={setGroups} teamMode={modalMode} groups={groups}
|
||||||
|
defaultPreset={cat?.preset?.id || -1}/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="modal fade" id="autoCatModal" tabIndex="-1" aria-labelledby="autoCatModalLabel" aria-hidden="true">
|
||||||
|
<div className="modal-dialog modal-dialog-scrollable modal-lg modal-fullscreen-lg-down">
|
||||||
|
<div className="modal-content">
|
||||||
|
<AutoCatModalContent data={data} groups={groups} setGroups={setGroups} defaultPreset={cat?.preset?.id || -1}/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -414,7 +456,7 @@ function ListMatch({cat, matches, groups, reducer}) {
|
|||||||
matchOrderToUpdate: Object.fromEntries(matchOrderToUpdate),
|
matchOrderToUpdate: Object.fromEntries(matchOrderToUpdate),
|
||||||
matchPouleToUpdate: Object.fromEntries(matchPouleToUpdate),
|
matchPouleToUpdate: Object.fromEntries(matchPouleToUpdate),
|
||||||
matchesToRemove: matchesToRemove.map(m => m.id)
|
matchesToRemove: matchesToRemove.map(m => m.id)
|
||||||
}), getToastMessage("toast.matchs.create", "ns"))
|
}), getToastMessage("toast.matchs.create", "cm"))
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
console.log("Finished creating matches");
|
console.log("Finished creating matches");
|
||||||
})
|
})
|
||||||
@@ -502,9 +544,9 @@ function MatchList({matches, cat, groups, reducer, classement = false}) {
|
|||||||
|
|
||||||
const liceName = (cat.liceName || "N/A").split(";");
|
const liceName = (cat.liceName || "N/A").split(";");
|
||||||
const marches2 = classement
|
const marches2 = classement
|
||||||
? matches.filter(m => m.categorie_ord === -42)
|
? matches.filter(m => m.categorie_ord === -42 && m.categorie === cat.id)
|
||||||
.map(m => ({...m, ...win_end(m, cards_v)}))
|
.map(m => ({...m, ...win_end(m, cards_v)}))
|
||||||
: matches.filter(m => m.categorie_ord !== -42)
|
: matches.filter(m => m.categorie_ord !== -42 && m.categorie === cat.id)
|
||||||
.sort((a, b) => a.categorie_ord - b.categorie_ord)
|
.sort((a, b) => a.categorie_ord - b.categorie_ord)
|
||||||
.map(m => ({...m, ...win_end(m, cards_v)}))
|
.map(m => ({...m, ...win_end(m, cards_v)}))
|
||||||
|
|
||||||
@@ -526,7 +568,9 @@ function MatchList({matches, cat, groups, reducer, classement = false}) {
|
|||||||
|
|
||||||
const {active, over} = event;
|
const {active, over} = event;
|
||||||
if (active.id !== over.id) {
|
if (active.id !== over.id) {
|
||||||
const newIndex = marches2.findIndex(m => m.id === over.id);
|
let newIndex = marches2.findIndex(m => m.id === over.id);
|
||||||
|
if (newIndex > 0)
|
||||||
|
newIndex = marches2[newIndex].categorie_ord;
|
||||||
reducer({type: 'REORDER', payload: {id: active.id, pos: newIndex}});
|
reducer({type: 'REORDER', payload: {id: active.id, pos: newIndex}});
|
||||||
sendRequest('updateMatchOrder', {id: active.id, pos: newIndex}).then(__ => {
|
sendRequest('updateMatchOrder', {id: active.id, pos: newIndex}).then(__ => {
|
||||||
})
|
})
|
||||||
@@ -698,7 +742,8 @@ function MatchList({matches, cat, groups, reducer, classement = false}) {
|
|||||||
<th style={{textAlign: "center", cursor: "auto"}} scope="row">{index + 1}</th>
|
<th style={{textAlign: "center", cursor: "auto"}} scope="row">{index + 1}</th>
|
||||||
{!classement && <td style={{textAlign: "center", cursor: "auto"}}>{m.poule}</td>}
|
{!classement && <td style={{textAlign: "center", cursor: "auto"}}>{m.poule}</td>}
|
||||||
{!classement && <td style={{textAlign: "center", cursor: "auto"}}>{liceName[index % liceName.length]}</td>}
|
{!classement && <td style={{textAlign: "center", cursor: "auto"}}>{liceName[index % liceName.length]}</td>}
|
||||||
<td style={{textAlign: "right", cursor: "auto", paddingRight: "0"}}>{m.end && m.win > 0 && <CupImg/>}</td>
|
<td style={{textAlign: "right", cursor: "auto", paddingRight: "0"}}>{m.end && ((m.win > 0 &&
|
||||||
|
<CupImg/>) || (m.win === 0 && <CupImg2/>))}</td>
|
||||||
<td style={{textAlign: "center", minWidth: "11em", paddingLeft: "0.2em"}}
|
<td style={{textAlign: "center", minWidth: "11em", paddingLeft: "0.2em"}}
|
||||||
onClick={e => handleCombClick(e, m.id, m.c1)}>
|
onClick={e => handleCombClick(e, m.id, m.c1)}>
|
||||||
<small className="position-relative"><CombName combId={m.c1}/>
|
<small className="position-relative"><CombName combId={m.c1}/>
|
||||||
@@ -707,7 +752,8 @@ function MatchList({matches, cat, groups, reducer, classement = false}) {
|
|||||||
onClick={e => handleCombClick(e, m.id, m.c2)}>
|
onClick={e => handleCombClick(e, m.id, m.c2)}>
|
||||||
<small className="position-relative"><CombName combId={m.c2}/>
|
<small className="position-relative"><CombName combId={m.c2}/>
|
||||||
<GetCard match={m} combId={m.c2} cat={cat}/></small></td>
|
<GetCard match={m} combId={m.c2} cat={cat}/></small></td>
|
||||||
<td style={{textAlign: "left", cursor: "auto", paddingLeft: "0"}}>{m.end && m.win < 0 && <CupImg/>}</td>
|
<td style={{textAlign: "left", cursor: "auto", paddingLeft: "0"}}>{m.end && ((m.win < 0 &&
|
||||||
|
<CupImg/>) || (m.win === 0 && <CupImg2/>))}</td>
|
||||||
<td style={{textAlign: "center", cursor: "auto"}}>{scoreToString2(m, cards_v)}</td>
|
<td style={{textAlign: "center", cursor: "auto"}}>{scoreToString2(m, cards_v)}</td>
|
||||||
<td style={{textAlign: "center", cursor: "pointer", color: "#1381ff"}} onClick={_ => handleEditMatch(m.id)}>
|
<td style={{textAlign: "center", cursor: "pointer", color: "#1381ff"}} onClick={_ => handleEditMatch(m.id)}>
|
||||||
<FontAwesomeIcon icon={faPen}/></td>
|
<FontAwesomeIcon icon={faPen}/></td>
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {AxiosError} from "../../../components/AxiosError.jsx";
|
|||||||
import {useFetch} from "../../../hooks/useFetch.js";
|
import {useFetch} from "../../../hooks/useFetch.js";
|
||||||
import {useTranslation} from "react-i18next";
|
import {useTranslation} from "react-i18next";
|
||||||
import {CardsProvider} from "../../../hooks/useCard.jsx";
|
import {CardsProvider} from "../../../hooks/useCard.jsx";
|
||||||
|
import AudioEncoder from "../../../components/cm/AudioEncoder.jsx";
|
||||||
|
|
||||||
const vite_url = import.meta.env.VITE_URL;
|
const vite_url = import.meta.env.VITE_URL;
|
||||||
|
|
||||||
@@ -73,6 +74,7 @@ function HomeComp() {
|
|||||||
<Route path="/" element={<Home2 perm={perm}/>}/>
|
<Route path="/" element={<Home2 perm={perm}/>}/>
|
||||||
<Route path="/admin" element={<CMAdmin compUuid={compUuid}/>}/>
|
<Route path="/admin" element={<CMAdmin compUuid={compUuid}/>}/>
|
||||||
<Route path="/table" element={<CMTable/>}/>
|
<Route path="/table" element={<CMTable/>}/>
|
||||||
|
<Route path="/view/audio" element={<AudioEncoder/>}/>
|
||||||
</Routes>
|
</Routes>
|
||||||
</LoadingProvider>
|
</LoadingProvider>
|
||||||
</CardsProvider>
|
</CardsProvider>
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import {useCountries} from "../../../hooks/useCountries.jsx";
|
import {useCountries} from "../../../hooks/useCountries.jsx";
|
||||||
import {useEffect, useReducer, useRef, useState} from "react";
|
import React, {useEffect, useReducer, useRef, useState} from "react";
|
||||||
import {CatList, getCatName, getToastMessage} from "../../../utils/Tools.js";
|
import {CatList, getCatName, getToastMessage} from "../../../utils/Tools.js";
|
||||||
import {CombName} from "../../../hooks/useComb.jsx";
|
import {CombName} from "../../../hooks/useComb.jsx";
|
||||||
import {useWS} from "../../../hooks/useWS.jsx";
|
import {useWS} from "../../../hooks/useWS.jsx";
|
||||||
import {useTranslation} from "react-i18next";
|
import {useTranslation} from "react-i18next";
|
||||||
import {toast} from "react-toastify";
|
import {toast} from "react-toastify";
|
||||||
|
import {ListPresetSelect} from "../../../components/cm/ListPresetSelect.jsx";
|
||||||
|
|
||||||
function SelectReducer(state, action) {
|
function SelectReducer(state, action) {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
@@ -59,7 +60,7 @@ function SelectReducer(state, action) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SelectCombModalContent({data, groups, setGroups, teamMode = false}) {
|
export function SelectCombModalContent({data, groups, setGroups, teamMode = false, defaultPreset = -1}) {
|
||||||
const country = useCountries('fr')
|
const country = useCountries('fr')
|
||||||
const {t} = useTranslation("cm");
|
const {t} = useTranslation("cm");
|
||||||
const {sendRequest, dispatch} = useWS()
|
const {sendRequest, dispatch} = useWS()
|
||||||
@@ -77,6 +78,11 @@ export function SelectCombModalContent({data, groups, setGroups, teamMode = fals
|
|||||||
const [weightMax, setWeightMax] = useState(0)
|
const [weightMax, setWeightMax] = useState(0)
|
||||||
const [team, setTeam] = useState(false)
|
const [team, setTeam] = useState(false)
|
||||||
const [teamName, setTeamName] = useState("");
|
const [teamName, setTeamName] = useState("");
|
||||||
|
const [preset, setPreset] = useState(-1)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setPreset(defaultPreset)
|
||||||
|
}, [defaultPreset])
|
||||||
|
|
||||||
const handleSubmit = (e) => {
|
const handleSubmit = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -149,7 +155,8 @@ export function SelectCombModalContent({data, groups, setGroups, teamMode = fals
|
|||||||
&& (weightMin === 0 || comb.weight !== null && comb.weight >= weightMin)
|
&& (weightMin === 0 || comb.weight !== null && comb.weight >= weightMin)
|
||||||
&& (weightMax === 0 || comb.weight !== null && comb.weight <= weightMax)
|
&& (weightMax === 0 || comb.weight !== null && comb.weight <= weightMax)
|
||||||
&& (teamMode && (comb.teamMembers == null || comb.teamMembers.length === 0) || !teamMode
|
&& (teamMode && (comb.teamMembers == null || comb.teamMembers.length === 0) || !teamMode
|
||||||
&& ((comb.teamMembers == null || comb.teamMembers.length === 0) !== team))) {
|
&& ((comb.teamMembers == null || comb.teamMembers.length === 0) !== team))
|
||||||
|
&& (preset === -1 || comb.categoriesInscrites?.includes(preset))) {
|
||||||
dataOut[id] = dataIn[id];
|
dataOut[id] = dataIn[id];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -215,6 +222,8 @@ export function SelectCombModalContent({data, groups, setGroups, teamMode = fals
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<ListPresetSelect value={preset} onChange={setPreset}/>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="inputState1" className="form-label">{t('club')}</label>
|
<label htmlFor="inputState1" className="form-label">{t('club')}</label>
|
||||||
<select id="inputState1" className="form-select" value={club} onChange={(e) => setClub(e.target.value)}>
|
<select id="inputState1" className="form-select" value={club} onChange={(e) => setClub(e.target.value)}>
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ function reducer(state, action) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function StateWindow({document}) {
|
export function useTablesState() {
|
||||||
const {sendRequest, dispatch} = useWS();
|
const {sendRequest, dispatch} = useWS();
|
||||||
const [state, dispatchState] = useReducer(reducer, [])
|
const [state, dispatchState] = useReducer(reducer, [])
|
||||||
|
|
||||||
@@ -58,6 +58,12 @@ export function StateWindow({document}) {
|
|||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
return {state};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function StateWindow({document}) {
|
||||||
|
const {state} = useTablesState();
|
||||||
|
|
||||||
document.title = "État des tables de marque";
|
document.title = "État des tables de marque";
|
||||||
document.body.className = "overflow-hidden";
|
document.body.className = "overflow-hidden";
|
||||||
|
|
||||||
@@ -66,7 +72,7 @@ export function StateWindow({document}) {
|
|||||||
<div className="d-flex flex-row flex-wrap justify-content-around align-items-center align-content-around h-100 p-2 overflow-auto">
|
<div className="d-flex flex-row flex-wrap justify-content-around align-items-center align-content-around h-100 p-2 overflow-auto">
|
||||||
{state.sort((a, b) => a.liceName.localeCompare(b.liceName)).map((table, index) =>
|
{state.sort((a, b) => a.liceName.localeCompare(b.liceName)).map((table, index) =>
|
||||||
<div key={index} className="card d-inline-flex flex-grow-1 align-self-stretch" style={{minWidth: "25em", maxWidth: "30em"}}>
|
<div key={index} className="card d-inline-flex flex-grow-1 align-self-stretch" style={{minWidth: "25em", maxWidth: "30em"}}>
|
||||||
<ShowState table={table} dispatch={dispatchState}/>
|
<ShowState table={table}/>
|
||||||
</div>)
|
</div>)
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@@ -191,7 +197,7 @@ function PrintChrono({chrono}) {
|
|||||||
const timer = setInterval(() => {
|
const timer = setInterval(() => {
|
||||||
let currentDuration = chrono.configTime
|
let currentDuration = chrono.configTime
|
||||||
if (chrono.state === 2) {
|
if (chrono.state === 2) {
|
||||||
currentDuration = (chrono.state === 0) ? 10000 : chrono.configPause
|
currentDuration = chrono.configPause
|
||||||
}
|
}
|
||||||
const timeStr = (chrono.state === 1 ? " Match - " : " Pause - ") + timePrint(currentDuration - getTime()) + (isRunning() ? "" : " (arrêté)")
|
const timeStr = (chrono.state === 1 ? " Match - " : " Pause - ") + timePrint(currentDuration - getTime()) + (isRunning() ? "" : " (arrêté)")
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import {useEffect, useRef} from "react";
|
import {useEffect, useRef} from "react";
|
||||||
import {scorePrint} from "../../utils/Tools.js";
|
import {scorePrint} from "../../utils/Tools.js";
|
||||||
import {compareCardOrder, useCardsStatic} from "../../hooks/useCard.jsx";
|
import {useCardsStatic} from "../../hooks/useCard.jsx";
|
||||||
|
|
||||||
const max_x = 500;
|
const max_x = 500;
|
||||||
|
|
||||||
@@ -451,3 +451,279 @@ export function DrawGraph({
|
|||||||
<canvas ref={canvasRef} style={{border: "1px solid grey", marginTop: "10px", position: "relative", opacity: 1}} id="myCanvas"></canvas>
|
<canvas ref={canvasRef} style={{border: "1px solid grey", marginTop: "10px", position: "relative", opacity: 1}} id="myCanvas"></canvas>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function drawGraphForPdf(root = [], size = 14, cards = []) {
|
||||||
|
const {getHeightCardForCombInMatch} = useCardsStatic(cards);
|
||||||
|
const sizeY = size * 0.5;
|
||||||
|
|
||||||
|
function getBounds(root) {
|
||||||
|
let px = max_x;
|
||||||
|
let py;
|
||||||
|
let maxx, minx, miny, maxy
|
||||||
|
|
||||||
|
function drawNode(tree, px, py) {
|
||||||
|
let death = tree.death() - 1
|
||||||
|
|
||||||
|
if (death === 0) {
|
||||||
|
if (miny > py - sizeY - ((sizeY * 1.5 / 2) | 0)) miny = py - sizeY - (sizeY * 1.5 / 2) | 0;
|
||||||
|
if (maxy < py + sizeY + ((sizeY * 1.5 / 2) | 0)) maxy = py + sizeY + (sizeY * 1.5 / 2) | 0;
|
||||||
|
} else {
|
||||||
|
if (miny > py - sizeY * 2 * death - ((sizeY * 1.5 / 2) | 0))
|
||||||
|
miny = py - sizeY * 2 * death - ((sizeY * 1.5 / 2) | 0);
|
||||||
|
if (maxy < py + sizeY * 2 * death + ((sizeY * 1.5 / 2) | 0))
|
||||||
|
maxy = py + sizeY * 2 * death + ((sizeY * 1.5 / 2) | 0);
|
||||||
|
}
|
||||||
|
if (minx > px - size * 2 - size * 8) minx = px - size * 2 - size * 8;
|
||||||
|
|
||||||
|
if (tree.left != null) drawNode(tree.left, px - size * 2 - size * 8, py - sizeY * 2 * death);
|
||||||
|
if (tree.right != null) drawNode(tree.right, px - size * 2 - size * 8, py + sizeY * 2 * death);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (root != null) {
|
||||||
|
py = (sizeY * 2 * root.at(0).death() + (((sizeY * 1.5 / 2) | 0) + sizeY) * root.at(0).death()) * 2;
|
||||||
|
|
||||||
|
maxx = px;
|
||||||
|
minx = px;
|
||||||
|
miny = py - (sizeY * 1.5 / 2) | 0;
|
||||||
|
maxy = py + (sizeY * 1.5 / 2) | 0;
|
||||||
|
|
||||||
|
for (const node of root) {
|
||||||
|
px = px - size * 2 - size * 8;
|
||||||
|
if (minx > px) minx = px;
|
||||||
|
|
||||||
|
drawNode(node, px, py);
|
||||||
|
//graphics2D.drawRect(minx, miny, maxx - minx, maxy - miny);
|
||||||
|
py = maxy + ((sizeY * 2 * node.death() + ((sizeY * 1.5 / 2) | 0)));
|
||||||
|
px = maxx;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
minx = 0;
|
||||||
|
maxx = 0;
|
||||||
|
miny = 0;
|
||||||
|
maxy = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [minx, maxx, miny, maxy];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fonction pour dessiner du texte avec gestion de la taille
|
||||||
|
const printText = (ctx, s, x, y, width, height, lineG, lineD) => {
|
||||||
|
ctx.save();
|
||||||
|
ctx.translate(x, y);
|
||||||
|
let tSize = 17;
|
||||||
|
let ratioX = height * 1.0 / 20.0;
|
||||||
|
ctx.font = "100 " + tSize + "px Arial";
|
||||||
|
|
||||||
|
let mw = width - (ratioX * 2) | 0;
|
||||||
|
if (ctx.measureText(s).width > mw) {
|
||||||
|
do {
|
||||||
|
tSize--;
|
||||||
|
ctx.font = tSize + "px Arial";
|
||||||
|
} while (ctx.measureText(s).width > mw && tSize > 10);
|
||||||
|
|
||||||
|
if (ctx.measureText(s).width > mw) {
|
||||||
|
let truncated = "";
|
||||||
|
const words = s.split(" ");
|
||||||
|
for (const word of words) {
|
||||||
|
if (ctx.measureText(truncated + word).width >= mw) {
|
||||||
|
truncated += "...";
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
truncated += word + " ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s = truncated;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const text = ctx.measureText(s);
|
||||||
|
let dx = (width - text.width) / 2;
|
||||||
|
let dy = ((height - text.actualBoundingBoxDescent) / 2) + (text.actualBoundingBoxAscent / 2);
|
||||||
|
ctx.fillText(s, dx, dy, width - dy);
|
||||||
|
ctx.restore();
|
||||||
|
|
||||||
|
ctx.beginPath();
|
||||||
|
if (lineD) {
|
||||||
|
ctx.moveTo((ratioX * 2.5 + x + dx + text.width) | 0, y + height / 2);
|
||||||
|
ctx.lineTo(x + width, y + height / 2);
|
||||||
|
}
|
||||||
|
if (lineG) {
|
||||||
|
ctx.moveTo(x, y + height / 2);
|
||||||
|
ctx.lineTo((dx + x - ratioX * 2.5) | 0, y + height / 2);
|
||||||
|
}
|
||||||
|
ctx.stroke();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Fonction pour afficher les scores
|
||||||
|
const printScores = (ctx, scores, px, py, scale) => {
|
||||||
|
ctx.save();
|
||||||
|
ctx.translate(px - size * 2, py - size * scale);
|
||||||
|
ctx.font = "100 14px Arial";
|
||||||
|
ctx.textBaseline = 'top';
|
||||||
|
|
||||||
|
for (let i = 0; i < scores.length; i++) {
|
||||||
|
const score = scorePrint(scores[i].s1) + "-" + scorePrint(scores[i].s2);
|
||||||
|
const div = (scores.length <= 2) ? 2 : (scores.length >= 4) ? 4 : 3;
|
||||||
|
const text = ctx.measureText(score);
|
||||||
|
let dx = (size * 2 - text.width) / 2;
|
||||||
|
let dy = ((size * 2 / div - text.actualBoundingBoxDescent) / 2) + (text.actualBoundingBoxAscent / 2);
|
||||||
|
|
||||||
|
ctx.fillStyle = '#ffffffdd';
|
||||||
|
ctx.fillRect(dx, size * 2 * scale / div * i + dy, text.width, 14);
|
||||||
|
ctx.fillStyle = "#000000";
|
||||||
|
ctx.fillText(score, dx, size * 2 * scale / div * i + dy, size * 2);
|
||||||
|
}
|
||||||
|
ctx.restore();
|
||||||
|
};
|
||||||
|
|
||||||
|
const printCard = (ctx, pos, combId, match) => {
|
||||||
|
const cards2 = getHeightCardForCombInMatch(combId, match)
|
||||||
|
if (cards2 != null) {
|
||||||
|
let oldColor = ctx.fillStyle;
|
||||||
|
switch (cards2.type) {
|
||||||
|
case "BLUE":
|
||||||
|
ctx.fillStyle = "#2e2efd";
|
||||||
|
break;
|
||||||
|
case "YELLOW":
|
||||||
|
ctx.fillStyle = "#d8d800";
|
||||||
|
break;
|
||||||
|
case "RED":
|
||||||
|
ctx.fillStyle = "#FF0000";
|
||||||
|
break;
|
||||||
|
case "BLACK":
|
||||||
|
ctx.fillStyle = "#000000";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ctx.fillStyle = "#FFFFFF00";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cards2.match === match.id) {
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.strokeStyle = ctx.fillStyle
|
||||||
|
ctx.arc(pos.x + pos.width - 10, pos.y + 5, 5, 0, 2 * Math.PI);
|
||||||
|
ctx.fill();
|
||||||
|
ctx.stroke();
|
||||||
|
ctx.strokeStyle = "#000000"
|
||||||
|
} else
|
||||||
|
ctx.fillRect(pos.x + pos.width - 18, pos.y - 5, 12, 12);
|
||||||
|
ctx.fillStyle = oldColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fonction pour dessiner un nœud
|
||||||
|
const drawNode = (ctx, tree, px, py, max_y) => {
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(px, py);
|
||||||
|
ctx.lineTo(px - size, py);
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
let death = tree.death() - 1;
|
||||||
|
let match = tree.data;
|
||||||
|
|
||||||
|
if (death === 0) {
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(px - size, py + sizeY);
|
||||||
|
ctx.lineTo(px - size, py - sizeY);
|
||||||
|
ctx.moveTo(px - size, py + sizeY);
|
||||||
|
ctx.lineTo(px - size * 2, py + sizeY);
|
||||||
|
ctx.moveTo(px - size, py - sizeY);
|
||||||
|
ctx.lineTo(px - size * 2, py - sizeY);
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
printScores(ctx, match.scores, px, py, 1);
|
||||||
|
|
||||||
|
|
||||||
|
const pos = {x: px - size * 2 - size * 8, y: py - sizeY - (sizeY * 1.5 / 2 | 0), width: size * 8, height: (sizeY * 1.5 | 0)}
|
||||||
|
printCard(ctx, pos, match.c1, match)
|
||||||
|
ctx.fillStyle = "#FF0000"
|
||||||
|
printText(ctx, (match.c1FullName == null) ? "" : match.c1FullName, pos.x, pos.y, pos.width, pos.height, false, true)
|
||||||
|
|
||||||
|
const pos2 = {x: px - size * 2 - size * 8, y: py + sizeY - (sizeY * 1.5 / 2 | 0), width: size * 8, height: (sizeY * 1.5 | 0)}
|
||||||
|
printCard(ctx, pos2, match.c2, match)
|
||||||
|
ctx.fillStyle = "#0000FF"
|
||||||
|
printText(ctx, (match.c2FullName == null) ? "" : match.c2FullName, pos2.x, pos2.y, pos2.width, pos2.height, false, true)
|
||||||
|
|
||||||
|
if (max_y.current < py + sizeY + ((sizeY * 1.5 / 2) | 0)) {
|
||||||
|
max_y.current = py + sizeY + (sizeY * 1.5 / 2 | 0);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(px - size, py);
|
||||||
|
ctx.lineTo(px - size, py + sizeY * 2 * death);
|
||||||
|
ctx.moveTo(px - size, py);
|
||||||
|
ctx.lineTo(px - size, py - sizeY * 2 * death);
|
||||||
|
ctx.moveTo(px - size, py + sizeY * 2 * death);
|
||||||
|
ctx.lineTo(px - size * 2, py + sizeY * 2 * death);
|
||||||
|
ctx.moveTo(px - size, py - sizeY * 2 * death);
|
||||||
|
ctx.lineTo(px - size * 2, py - sizeY * 2 * death);
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
printScores(ctx, match.scores, px, py, 1.5);
|
||||||
|
|
||||||
|
const pos = {x: px - size * 2 - size * 8, y: py - sizeY * 2 * death - (sizeY * 1.5 / 2 | 0), width: size * 8, height: (sizeY * 1.5 | 0)}
|
||||||
|
printCard(ctx, pos, match.c1, match)
|
||||||
|
ctx.fillStyle = "#FF0000"
|
||||||
|
printText(ctx, (match.c1FullName == null) ? "" : match.c1FullName, pos.x, pos.y, pos.width, pos.height, true, true)
|
||||||
|
|
||||||
|
const pos2 = {x: px - size * 2 - size * 8, y: py + sizeY * 2 * death - (sizeY * 1.5 / 2 | 0), width: size * 8, height: (sizeY * 1.5 | 0)}
|
||||||
|
printCard(ctx, pos2, match.c2, match)
|
||||||
|
ctx.fillStyle = "#0000FF"
|
||||||
|
printText(ctx, (match.c2FullName == null) ? "" : match.c2FullName, pos2.x, pos2.y, pos2.width, pos2.height, true, true)
|
||||||
|
|
||||||
|
if (max_y.current < py + sizeY * 2 * death + ((sizeY * 1.5 / 2) | 0)) {
|
||||||
|
max_y.current = py + sizeY * 2 * death + ((sizeY * 1.5 / 2 | 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tree.left != null) {
|
||||||
|
drawNode(ctx, tree.left, px - size * 2 - size * 8, py - sizeY * 2 * death, max_y);
|
||||||
|
}
|
||||||
|
if (tree.right != null) {
|
||||||
|
drawNode(ctx, tree.right, px - size * 2 - size * 8, py + sizeY * 2 * death, max_y);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Dessiner sur le canvas principal
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
canvas.id = "myCanvas";
|
||||||
|
canvas.style.border = "1px solid grey";
|
||||||
|
canvas.style.marginTop = "10px";
|
||||||
|
|
||||||
|
const ctx = canvas.getContext("2d");
|
||||||
|
|
||||||
|
const [minx, maxx, miny, maxy] = getBounds(root);
|
||||||
|
canvas.width = maxx - minx;
|
||||||
|
canvas.height = maxy - miny;
|
||||||
|
ctx.translate(-minx, -miny);
|
||||||
|
ctx.fillStyle = "#000000";
|
||||||
|
ctx.lineWidth = 2;
|
||||||
|
ctx.strokeStyle = "#000000";
|
||||||
|
|
||||||
|
let px = maxx;
|
||||||
|
let py;
|
||||||
|
const max_y = {current: 0};
|
||||||
|
|
||||||
|
py = (sizeY * 2 * root[0].death() + (((sizeY * 1.5 / 2) | 0) + sizeY) * root[0].death()) * 2;
|
||||||
|
max_y.current = py + (sizeY * 1.5 / 2 | 0);
|
||||||
|
for (const node of root) {
|
||||||
|
let win_name = "";
|
||||||
|
if (node.data.end) {
|
||||||
|
win_name = node.data.win > 0
|
||||||
|
? (node.data.c1FullName === null ? "???" : node.data.c1FullName)
|
||||||
|
: (node.data.c2FullName === null ? "???" : node.data.c2FullName);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.fillStyle = "#18A918";
|
||||||
|
printText(ctx, win_name,
|
||||||
|
px - size * 2 - size * 8, py - ((sizeY * 1.5 / 2) | 0),
|
||||||
|
size * 8, (sizeY * 1.5 | 0), true, false);
|
||||||
|
|
||||||
|
px = px - size * 2 - size * 8;
|
||||||
|
drawNode(ctx, node, px, py, max_y);
|
||||||
|
py = max_y.current + ((sizeY * 2 * node.death() + ((sizeY * 1.5 / 2) | 0)));
|
||||||
|
px = maxx;
|
||||||
|
}
|
||||||
|
|
||||||
|
return canvas;
|
||||||
|
}
|
||||||
|
|||||||
@@ -39,7 +39,8 @@ export function ResultView() {
|
|||||||
{resultShow && resultShow === "cat" && <CategoryList uuid={uuid}/>
|
{resultShow && resultShow === "cat" && <CategoryList uuid={uuid}/>
|
||||||
|| resultShow && resultShow === "club" && <ClubList uuid={uuid}/>
|
|| resultShow && resultShow === "club" && <ClubList uuid={uuid}/>
|
||||||
|| resultShow && resultShow === "comb" && <CombList uuid={uuid}/>
|
|| resultShow && resultShow === "comb" && <CombList uuid={uuid}/>
|
||||||
|| resultShow && resultShow === "combs" && <CombsResult uuid={uuid}/>}
|
|| resultShow && resultShow === "combs" && <CombsResult uuid={uuid}/>
|
||||||
|
|| resultShow && resultShow === "clubs" && <ClubsResult uuid={uuid}/>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
@@ -66,6 +67,10 @@ function MenuBar({resultShow, setResultShow}) {
|
|||||||
<a className={"nav-link my-1" + (resultShow === "combs" ? " active" : "")} aria-current={(resultShow === "combs" ? " page" : "false")}
|
<a className={"nav-link my-1" + (resultShow === "combs" ? " active" : "")} aria-current={(resultShow === "combs" ? " page" : "false")}
|
||||||
href="#" onClick={_ => setResultShow("combs")}>{t('combattants')}</a>
|
href="#" onClick={_ => setResultShow("combs")}>{t('combattants')}</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li className="nav-item">
|
||||||
|
<a className={"nav-link my-1" + (resultShow === "clubs" ? " active" : "")} aria-current={(resultShow === "clubs" ? " page" : "false")}
|
||||||
|
href="#" onClick={_ => setResultShow("clubs")}>{t('classementClub')}</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -181,8 +186,8 @@ function ClassementList({classement}) {
|
|||||||
<table className="table table-striped">
|
<table className="table table-striped">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col" style={{textAlign: "center"}}>Place</th>
|
<th scope="col" style={{textAlign: "center"}}>{t('place')}</th>
|
||||||
<th scope="col" style={{textAlign: "center"}}>Nom</th>
|
<th scope="col" style={{textAlign: "center"}}>{t('nom')}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -450,6 +455,43 @@ function CombResult({uuid, combId}) {
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ClubsResult({uuid}) {
|
||||||
|
const setLoading = useLoadingSwitcher()
|
||||||
|
const {data, error} = useFetch(`/result/${uuid}/club/classement`, setLoading, 1)
|
||||||
|
const {t} = useTranslation('result');
|
||||||
|
|
||||||
|
return <>
|
||||||
|
{data ? <>
|
||||||
|
<h3>{t('classementDesClub')} :</h3>
|
||||||
|
|
||||||
|
<table className="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col" style={{textAlign: "center"}}>{t('club')}</th>
|
||||||
|
<th scope="col" style={{textAlign: "center"}}>{t('1er')}</th>
|
||||||
|
<th scope="col" style={{textAlign: "center"}}>{t('2eme')}</th>
|
||||||
|
<th scope="col" style={{textAlign: "center"}}>{t('3eme')}</th>
|
||||||
|
<th scope="col" style={{textAlign: "center"}}>{t('scores')}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{data.map((club, idx) => <tr key={idx}>
|
||||||
|
<td style={{textAlign: "center"}}>{club.name}</td>
|
||||||
|
<td style={{textAlign: "center"}}>{club.score[0]}</td>
|
||||||
|
<td style={{textAlign: "center"}}>{club.score[1]}</td>
|
||||||
|
<td style={{textAlign: "center"}}>{club.score[2]}</td>
|
||||||
|
<td style={{textAlign: "center"}}>{club.tt_score}</td>
|
||||||
|
</tr>)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</>
|
||||||
|
: error
|
||||||
|
? <AxiosError error={error}/>
|
||||||
|
: <Def/>
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
function CombsResult({uuid}) {
|
function CombsResult({uuid}) {
|
||||||
const setLoading = useLoadingSwitcher()
|
const setLoading = useLoadingSwitcher()
|
||||||
const {data, error} = useFetch(`/result/${uuid}/comb`, setLoading, 1)
|
const {data, error} = useFetch(`/result/${uuid}/comb`, setLoading, 1)
|
||||||
|
|||||||
@@ -56,8 +56,10 @@ export function MarchReducer(datas, action) {
|
|||||||
return datas.sort(action.payload)
|
return datas.sort(action.payload)
|
||||||
case 'REORDER':
|
case 'REORDER':
|
||||||
const oldIndex = datas.findIndex(data => data.id === action.payload.id)
|
const oldIndex = datas.findIndex(data => data.id === action.payload.id)
|
||||||
if (oldIndex === -1 || datas[oldIndex].categorie_ord === action.payload.pos)
|
if (oldIndex === -1)
|
||||||
return datas // Do nothing
|
return datas // Do nothing
|
||||||
|
if (datas[oldIndex].categorie_ord === action.payload.pos)
|
||||||
|
return [...datas] // Do nothing
|
||||||
|
|
||||||
const oldPos = datas[oldIndex].categorie_ord
|
const oldPos = datas[oldIndex].categorie_ord
|
||||||
const newPos = action.payload.pos
|
const newPos = action.payload.pos
|
||||||
|
|||||||
@@ -117,6 +117,14 @@ export function getSaison(currentDate = new Date()) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getFirstDateOfSaison() {
|
||||||
|
const year = getSaison();
|
||||||
|
const firstDate = new Date(year, 8, 1); // 8 = septembre, 1 = premier jour
|
||||||
|
firstDate.setHours(0, 0, 0, 0); // Heure, minutes, secondes, millisecondes à 0
|
||||||
|
|
||||||
|
return firstDate;
|
||||||
|
}
|
||||||
|
|
||||||
export function getCatName(cat) {
|
export function getCatName(cat) {
|
||||||
switch (cat) {
|
switch (cat) {
|
||||||
case "SUPER_MINI":
|
case "SUPER_MINI":
|
||||||
@@ -199,6 +207,38 @@ export function getSwordTypeName(type) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getSwordSize(type, cat, gender) {
|
||||||
|
const catIndex = CatList.indexOf(cat)
|
||||||
|
if (type === "ONE_HAND") {
|
||||||
|
if (catIndex <= 1) {
|
||||||
|
return "XS"
|
||||||
|
} else if (catIndex <= 3) {
|
||||||
|
return "S"
|
||||||
|
} else if (catIndex <= 6 || gender === "F") {
|
||||||
|
return "M"
|
||||||
|
} else {
|
||||||
|
return "L"
|
||||||
|
}
|
||||||
|
} else if (type === "TWO_HAND") {
|
||||||
|
if (catIndex <= 5) {
|
||||||
|
return "--"
|
||||||
|
} else if (catIndex <= 6 || gender === "F") {
|
||||||
|
return "XL"
|
||||||
|
} else {
|
||||||
|
return "XXL"
|
||||||
|
}
|
||||||
|
} else if (type === "SABER") {
|
||||||
|
if (catIndex <= 3) {
|
||||||
|
return "--"
|
||||||
|
} else if (catIndex <= 6) {
|
||||||
|
return "S"
|
||||||
|
} else {
|
||||||
|
return "L"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "--"
|
||||||
|
}
|
||||||
|
|
||||||
export const ShieldList = [
|
export const ShieldList = [
|
||||||
"NONE",
|
"NONE",
|
||||||
"STANDARD",
|
"STANDARD",
|
||||||
@@ -224,6 +264,18 @@ export function getShieldTypeName(type) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getShieldSize(type, cat) {
|
||||||
|
if (type === "STANDARD") {
|
||||||
|
const catIndex = CatList.indexOf(cat)
|
||||||
|
if (catIndex <= 1) {
|
||||||
|
return "S"
|
||||||
|
} else {
|
||||||
|
return "L"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "--"
|
||||||
|
}
|
||||||
|
|
||||||
export function getToastMessage(msgKey, ns = 'common') {
|
export function getToastMessage(msgKey, ns = 'common') {
|
||||||
return {
|
return {
|
||||||
pending: i18n.t(msgKey + '.pending', {ns}),
|
pending: i18n.t(msgKey + '.pending', {ns}),
|
||||||
@@ -374,3 +426,29 @@ export function hex2rgb(hex) {
|
|||||||
// return {r, g, b}
|
// return {r, g, b}
|
||||||
return {r, g, b};
|
return {r, g, b};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function toDataURL(src, outputFormat) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const img = new Image();
|
||||||
|
img.crossOrigin = 'Anonymous';
|
||||||
|
img.onload = function() {
|
||||||
|
const canvas = document.createElement('CANVAS');
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
let dataURL;
|
||||||
|
canvas.height = this.naturalHeight;
|
||||||
|
canvas.width = this.naturalWidth;
|
||||||
|
ctx.drawImage(this, 0, 0);
|
||||||
|
dataURL = canvas.toDataURL(outputFormat);
|
||||||
|
resolve(dataURL);
|
||||||
|
};
|
||||||
|
img.onerror = function() {
|
||||||
|
reject(new Error('Could not load image at ' + src));
|
||||||
|
}
|
||||||
|
|
||||||
|
img.src = src;
|
||||||
|
if (img.complete || img.complete === undefined) {
|
||||||
|
img.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==";
|
||||||
|
img.src = src;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
502
src/main/webapp/src/utils/cmPdf.js
Normal file
502
src/main/webapp/src/utils/cmPdf.js
Normal file
@@ -0,0 +1,502 @@
|
|||||||
|
import {jsPDF} from 'jspdf'
|
||||||
|
import {useCardsStatic} from "../hooks/useCard.jsx";
|
||||||
|
import {
|
||||||
|
CatList,
|
||||||
|
getCatName,
|
||||||
|
getShieldSize,
|
||||||
|
getShieldTypeName,
|
||||||
|
getSwordSize,
|
||||||
|
getSwordTypeName,
|
||||||
|
sortCategories,
|
||||||
|
timePrint,
|
||||||
|
virtualScore,
|
||||||
|
win_end
|
||||||
|
} from "./Tools.js";
|
||||||
|
import {getMandatoryProtectionsList} from "../components/ProtectionSelector.jsx";
|
||||||
|
import {scoreToString2} from "./CompetitionTools.js";
|
||||||
|
import {TreeNode} from "./TreeUtils.js";
|
||||||
|
import {drawGraphForPdf} from "../pages/result/DrawGraph.jsx";
|
||||||
|
import autoTable from 'jspdf-autotable'
|
||||||
|
|
||||||
|
function combName(getComb, combId) {
|
||||||
|
if (!combId)
|
||||||
|
return " "
|
||||||
|
const comb = getComb(combId, null);
|
||||||
|
if (comb) {
|
||||||
|
if (comb.lname === "__team")
|
||||||
|
return `${comb.fname}`
|
||||||
|
return `${comb.fname} ${comb.lname}`
|
||||||
|
} else {
|
||||||
|
return `[Comb #${combId}]`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function makePDF(action, pagesList, name, c_name, getComb, t, logo) {
|
||||||
|
//https://github.com/parallax/jsPDF/blob/ddbfc0f0250ca908f8061a72fa057116b7613e78/jspdf.js#L59
|
||||||
|
const doc = new jsPDF('p', 'pt', 'a4');
|
||||||
|
doc.setProperties({title: name, author: "FFSAF - Intranet", subject: c_name + " - " + name, creator: "FFSAF - Intranet"});
|
||||||
|
|
||||||
|
for (let i = 0; i < pagesList.length; i++) {
|
||||||
|
const context = {pdf_doc: doc, pdf_name: name, c_name, getComb, t, ...pagesList[i].params, logo}
|
||||||
|
switch (pagesList[i].type) {
|
||||||
|
case "categorie":
|
||||||
|
generateCategoriePDF(context);
|
||||||
|
break;
|
||||||
|
case "podium":
|
||||||
|
generatePodium(context);
|
||||||
|
break;
|
||||||
|
case "podiumClub":
|
||||||
|
generateClubPodium(context);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (i !== pagesList.length - 1)
|
||||||
|
doc.addPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (action) {
|
||||||
|
case "show":
|
||||||
|
window.open(doc.output('bloburl', {filename: name + '.pdf'}));
|
||||||
|
break;
|
||||||
|
case "print":
|
||||||
|
const iframe = document.createElement('iframe'); //load content in an iframe to print later
|
||||||
|
document.body.appendChild(iframe);
|
||||||
|
|
||||||
|
iframe.style.display = 'none';
|
||||||
|
iframe.src = doc.output('bloburl', {filename: name + '.pdf'});
|
||||||
|
iframe.onload = function () {
|
||||||
|
setTimeout(function () {
|
||||||
|
iframe.focus();
|
||||||
|
iframe.contentWindow.print();
|
||||||
|
}, 1);
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case "download":
|
||||||
|
default:
|
||||||
|
doc.save(name + '.pdf');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function matchList(pdf_doc, matches, cat, cards_v, classement, getComb, t) {
|
||||||
|
const {getHeightCardForCombInMatch} = useCardsStatic(cards_v);
|
||||||
|
const liceName = (cat.liceName || "N/A").split(";");
|
||||||
|
|
||||||
|
const getBG = (combId, match) => {
|
||||||
|
const c = getHeightCardForCombInMatch(combId, match)
|
||||||
|
if (!c)
|
||||||
|
return ""
|
||||||
|
let bg = "";
|
||||||
|
let text = "#000";
|
||||||
|
switch (c.type) {
|
||||||
|
case "YELLOW":
|
||||||
|
bg = "#ffc107";
|
||||||
|
break;
|
||||||
|
case "RED":
|
||||||
|
bg = "#dc3545";
|
||||||
|
text = "#FFF";
|
||||||
|
break;
|
||||||
|
case "BLACK":
|
||||||
|
bg = "#000000";
|
||||||
|
text = "#FFF";
|
||||||
|
break;
|
||||||
|
case "BLUE":
|
||||||
|
bg = "#0d6efd";
|
||||||
|
text = "#FFF";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return {fillColor: bg, textColor: text}
|
||||||
|
}
|
||||||
|
|
||||||
|
const head = [
|
||||||
|
...(classement ? [
|
||||||
|
{content: t('place', {ns: "result"}), styles: {halign: "center"}},
|
||||||
|
] : [
|
||||||
|
{content: t('no'), styles: {halign: "center"}},
|
||||||
|
{content: t('poule'), styles: {halign: "center"}},
|
||||||
|
{content: t('zone'), styles: {halign: "center"}},
|
||||||
|
]),
|
||||||
|
{content: "", styles: {halign: "center"}},
|
||||||
|
{content: t('rouge'), styles: {halign: "center"}},
|
||||||
|
{content: t('résultat'), styles: {halign: "center"}},
|
||||||
|
{content: t('blue'), styles: {halign: "center"}},
|
||||||
|
{content: "", styles: {halign: "center"}},
|
||||||
|
]
|
||||||
|
|
||||||
|
const body = matches.map((m, index) => ([
|
||||||
|
...(classement ? [
|
||||||
|
{content: `${m.categorie_ord + 1}-${m.categorie_ord + 2}`, styles: {halign: "center", padding: "2pt 0"}}
|
||||||
|
] : [
|
||||||
|
{content: index + 1, styles: {halign: "center", padding: "2pt 0"}},
|
||||||
|
{content: m.poule, styles: {halign: "center"}},
|
||||||
|
{content: liceName[index % liceName.length], styles: {halign: "center"}},
|
||||||
|
]),
|
||||||
|
{content: m.end ? (m.win > 0 ? "X" : (m.win === 0 ? "-" : "")) : " ", styles: {halign: "center", ...getBG(m.c1, m)}},
|
||||||
|
{content: combName(getComb, m.c1), styles: {halign: "center", minWidth: "11em", paddingLeft: "0.2em"}},
|
||||||
|
{content: scoreToString2(m, cards_v), styles: {halign: "center"}},
|
||||||
|
{content: combName(getComb, m.c2), styles: {halign: "center", minWidth: "11em", paddingRight: "0.2em"}},
|
||||||
|
{content: m.end ? (m.win < 0 ? "X" : (m.win === 0 ? "-" : "")) : " ", styles: {halign: "center", ...getBG(m.c2, m)}},
|
||||||
|
]))
|
||||||
|
|
||||||
|
autoTable(pdf_doc, {
|
||||||
|
startY: pdf_doc.lastAutoTable.finalY + 7,
|
||||||
|
styles: {fontSize: 10, cellPadding: 3},
|
||||||
|
columnStyles: classement ? {
|
||||||
|
0: {cellWidth: 35},
|
||||||
|
1: {cellWidth: 15},
|
||||||
|
2: {cellWidth: "auto"},
|
||||||
|
3: {cellWidth: 110},
|
||||||
|
4: {cellWidth: "auto"},
|
||||||
|
5: {cellWidth: 15},
|
||||||
|
} : {
|
||||||
|
0: {cellWidth: 20},
|
||||||
|
1: {cellWidth: 35},
|
||||||
|
2: {cellWidth: 30},
|
||||||
|
3: {cellWidth: 15},
|
||||||
|
4: {cellWidth: "auto"},
|
||||||
|
5: {cellWidth: 110},
|
||||||
|
6: {cellWidth: "auto"},
|
||||||
|
7: {cellWidth: 15},
|
||||||
|
},
|
||||||
|
head: [head],
|
||||||
|
body: body,
|
||||||
|
theme: 'grid',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildTree(pdf_doc, treeData, treeRaw, matches, cat, cards_v, getComb, t, categorieEmpty, comb_count = 0) {
|
||||||
|
function parseTree(data_in) {
|
||||||
|
if (data_in?.data == null)
|
||||||
|
return null
|
||||||
|
|
||||||
|
const matchData = matches.find(m => m.id === data_in.data)
|
||||||
|
const c1 = categorieEmpty ? null : getComb(matchData?.c1)
|
||||||
|
const c2 = categorieEmpty ? null : getComb(matchData?.c2)
|
||||||
|
|
||||||
|
const scores2 = []
|
||||||
|
for (const score of matchData?.scores) {
|
||||||
|
scores2.push({
|
||||||
|
...score,
|
||||||
|
s1: virtualScore(matchData?.c1, score, matchData, cards_v),
|
||||||
|
s2: virtualScore(matchData?.c2, score, matchData, cards_v)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let node = new TreeNode({
|
||||||
|
...matchData,
|
||||||
|
...win_end(matchData, cards_v),
|
||||||
|
scores: scores2,
|
||||||
|
c1FullName: c1 !== null ? c1.fname + " " + c1.lname : null,
|
||||||
|
c2FullName: c2 !== null ? c2.fname + " " + c2.lname : null
|
||||||
|
})
|
||||||
|
node.left = parseTree(data_in?.left)
|
||||||
|
node.right = parseTree(data_in?.right)
|
||||||
|
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let out = []
|
||||||
|
for (let i = 0; i < treeRaw.length; i++) {
|
||||||
|
if (treeRaw.at(i).level > -10) {
|
||||||
|
out.push(parseTree(treeData.at(i)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const canvas = drawGraphForPdf(out, 24, cards_v)
|
||||||
|
const imgData = canvas.toDataURL('image/png');
|
||||||
|
const height = canvas.height * (pdf_doc.internal.pageSize.getWidth() - 80) / canvas.width;
|
||||||
|
pdf_doc.addImage(imgData, 'PNG', 40, pdf_doc.lastAutoTable.finalY, pdf_doc.internal.pageSize.getWidth() - 80, height);
|
||||||
|
|
||||||
|
pdf_doc.lastAutoTable.finalY += height;
|
||||||
|
|
||||||
|
if (cat.fullClassement) {
|
||||||
|
let size = 0;
|
||||||
|
if (out.length > 0)
|
||||||
|
size = out[0].getMaxChildrenAtDepth(out[0].death() - 1)
|
||||||
|
|
||||||
|
const matches2 = cat.raw_trees?.filter(n => n.level <= -10).reverse().map(d => categorieEmpty ? ({}) : matches.find(m => m.id === d.match?.id))
|
||||||
|
if (matches2.length === 0) {
|
||||||
|
while (Math.ceil(comb_count / 2) - size > matches2.length)
|
||||||
|
matches2.push({})
|
||||||
|
}
|
||||||
|
|
||||||
|
matches2.forEach((v, i) => {
|
||||||
|
v.categorie_ord = (i + size) * 2
|
||||||
|
})
|
||||||
|
|
||||||
|
matchList(pdf_doc, matches2, cat, cards_v, true, getComb, t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function pouleList(pdf_doc, groups, getComb, t) {
|
||||||
|
const groups2 = groups.map(g => {
|
||||||
|
const comb = getComb(g.id);
|
||||||
|
return {...g, name: comb ? comb.fname + " " + comb.lname : "", teamMembers: comb ? comb.teamMembers : []};
|
||||||
|
}).sort((a, b) => {
|
||||||
|
if (a.poule !== b.poule) {
|
||||||
|
if (a.poule === '-') return 1;
|
||||||
|
if (b.poule === '-') return -1;
|
||||||
|
return a.poule.localeCompare(b.poule);
|
||||||
|
}
|
||||||
|
return a.name.localeCompare(b.name);
|
||||||
|
}).reduce((acc, curr) => {
|
||||||
|
const poule = curr.poule;
|
||||||
|
if (!acc[poule]) {
|
||||||
|
acc[poule] = [];
|
||||||
|
}
|
||||||
|
acc[poule].push(curr);
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
autoTable(pdf_doc, {
|
||||||
|
startY: pdf_doc.lastAutoTable.finalY + 7,
|
||||||
|
styles: {fontSize: 10, cellPadding: 3},
|
||||||
|
head: [Object.keys(groups2).map((poule) => ({content: `${t('poule')} ${poule}`, styles: {halign: "center"}}))],
|
||||||
|
body: [Object.keys(groups2).map((poule) => ({
|
||||||
|
content: groups2[poule].map(o => o.name).join(", "),
|
||||||
|
styles: {halign: "center", valign: "middle"}
|
||||||
|
}))],
|
||||||
|
theme: 'grid',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeHeader(pdf_doc, c_name, p_name, logo) {
|
||||||
|
autoTable(pdf_doc, {
|
||||||
|
startY: 20,
|
||||||
|
body: [[
|
||||||
|
{content: "", src: `${logo}`, rowSpan: 2, styles: {halign: 'center', valign: 'middle'}},
|
||||||
|
{content: c_name, colSpan: 3, styles: {halign: "center", valign: 'middle', fontSize: 20}},
|
||||||
|
], [
|
||||||
|
{content: p_name, colSpan: 3, styles: {halign: "center", valign: 'middle', fontSize: 20}}
|
||||||
|
]],
|
||||||
|
theme: 'plain',
|
||||||
|
columnStyles: {
|
||||||
|
0: {cellWidth: 60, minCellHeight: 60},
|
||||||
|
},
|
||||||
|
didDrawCell: function (data) {
|
||||||
|
if (data.column.index === 0 && data.row.index === 0) {
|
||||||
|
const dim = data.cell.height - data.cell.padding('vertical');
|
||||||
|
const textPos = data.cell;
|
||||||
|
pdf_doc.addImage(data.cell.raw.src, textPos.x, textPos.y + data.cell.padding('vertical') / 2, dim, dim);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateCategoriePDF({pdf_doc, cat, matches, groups, getComb, cards_v, c_name, categorieEmpty, t, logo}) {
|
||||||
|
let catAverage = "---";
|
||||||
|
let genreAverage = "H";
|
||||||
|
let nbComb = 0;
|
||||||
|
let time = 0;
|
||||||
|
|
||||||
|
const marches2 = matches.filter(m => m.categorie === cat.id)
|
||||||
|
.map(m => categorieEmpty ? ({...m, end: false, scores: []}) : m)
|
||||||
|
|
||||||
|
if (marches2.length !== 0) {
|
||||||
|
const genres = [];
|
||||||
|
const cats = [];
|
||||||
|
const combs_ = [];
|
||||||
|
for (const m of marches2) {
|
||||||
|
if (m.c1 && !combs_.includes(m.c1))
|
||||||
|
combs_.push(m.c1);
|
||||||
|
if (m.c2 && !combs_.includes(m.c2))
|
||||||
|
combs_.push(m.c2);
|
||||||
|
}
|
||||||
|
|
||||||
|
combs_.map(cId => getComb(cId, null)).filter(c => c && c.categorie)
|
||||||
|
.forEach(c => {
|
||||||
|
cats.push(Math.min(CatList.length, CatList.indexOf(c.categorie) + c.overCategory))
|
||||||
|
genres.push(c.genre)
|
||||||
|
});
|
||||||
|
|
||||||
|
const catAvg = Math.round(cats.reduce((a, b) => a + b, 0) / cats.length);
|
||||||
|
|
||||||
|
nbComb = combs_.length;
|
||||||
|
catAverage = CatList.at(catAvg) || "---";
|
||||||
|
genreAverage = Math.round(genres.reduce((a, b) => a + (b === "F" ? 1 : 0), 0) / genres.length) > 0.5 ? "F" : "H";
|
||||||
|
|
||||||
|
if (cat.preset && cat.preset.categories) {
|
||||||
|
const catAvailable = cat.preset.categories.map(c => CatList.indexOf(c.categorie));
|
||||||
|
let p;
|
||||||
|
if (catAvailable.includes(catAvg)) {
|
||||||
|
p = cat.preset.categories.find(c => CatList.indexOf(c.categorie) === catAvg);
|
||||||
|
} else {
|
||||||
|
p = cat.preset.categories.find(c => CatList.indexOf(c.categorie) ===
|
||||||
|
catAvailable.reduce((a, b) => Math.abs(b - catAvg) < Math.abs(a - catAvg) ? b : a));
|
||||||
|
}
|
||||||
|
time = {round: p.roundDuration, pause: p.pauseDuration}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
makeHeader(pdf_doc, c_name, cat.name, logo)
|
||||||
|
|
||||||
|
autoTable(pdf_doc, {
|
||||||
|
startY: pdf_doc.lastAutoTable.finalY,
|
||||||
|
styles: {fontSize: 10, cellPadding: 3},
|
||||||
|
body: [[
|
||||||
|
{content: `${t('catégorieDâgeMoyenne')} : ${getCatName(catAverage)}`, styles: {halign: "left"}},
|
||||||
|
{
|
||||||
|
content: `${t('arme', {ns: 'common'})} : ${getSwordTypeName(cat.preset?.sword)} - ${t('taille')} ${getSwordSize(cat.preset?.sword, catAverage, genreAverage)}`,
|
||||||
|
styles: {halign: "left"}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
content: `${t('bouclier', {ns: 'common'})} : ${getShieldTypeName(cat.preset?.shield)} - ${t('taille')} ${getShieldSize(cat.preset?.shield, catAverage)}`,
|
||||||
|
styles: {halign: "left"}
|
||||||
|
},
|
||||||
|
], [
|
||||||
|
{content: `${t('duréeRound')} : ${timePrint(time.round)}`, styles: {halign: "left"}},
|
||||||
|
{content: `${t('duréePause')} : ${timePrint(time.pause)}`, styles: {halign: "left"}},
|
||||||
|
{content: `${t('nombreDeCombattants')} : ${nbComb}`, styles: {halign: "left"}},
|
||||||
|
], [
|
||||||
|
{
|
||||||
|
content: `${t('protectionObligatoire', {ns: 'common'})} ${getMandatoryProtectionsList(CatList.indexOf(catAverage) <= CatList.indexOf("JUNIOR") ?
|
||||||
|
cat.preset?.mandatoryProtection1 : cat.preset?.mandatoryProtection2, cat.preset?.shield, t).join(", ") || "---"}`,
|
||||||
|
colSpan: 3,
|
||||||
|
styles: {halign: "left"}
|
||||||
|
},
|
||||||
|
]],
|
||||||
|
theme: 'grid',
|
||||||
|
})
|
||||||
|
|
||||||
|
pouleList(pdf_doc, groups, getComb, t)
|
||||||
|
|
||||||
|
if ((cat.type & 1) === 1)
|
||||||
|
matchList(pdf_doc, marches2.filter(m => m.categorie_ord !== -42).sort((a, b) => a.categorie_ord - b.categorie_ord),
|
||||||
|
cat, categorieEmpty ? [] : cards_v, false, getComb, t)
|
||||||
|
|
||||||
|
if ((cat.type & 2) === 2) {
|
||||||
|
pdf_doc.setFontSize(12);
|
||||||
|
pdf_doc.text((cat.treeAreClassement ? t('classement') : t('tournois')) + ':', 40, pdf_doc.lastAutoTable.finalY + 16)
|
||||||
|
pdf_doc.lastAutoTable.finalY += 16;
|
||||||
|
buildTree(pdf_doc, cat.trees, cat.raw_trees, marches2, cat, categorieEmpty ? [] : cards_v, getComb, t, categorieEmpty, nbComb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function generatePodium({pdf_doc, data, t, logo, c_name, minRank = 4, maxRank = 4}) {
|
||||||
|
makeHeader(pdf_doc, c_name, t('podium'), logo)
|
||||||
|
|
||||||
|
const data2 = data.sort((a, b) => {
|
||||||
|
let tmp = sortCategories(a.categorie, b.categorie);
|
||||||
|
if (tmp !== 0)
|
||||||
|
return tmp;
|
||||||
|
return a.poule_name.localeCompare(b.poule_name)
|
||||||
|
})
|
||||||
|
|
||||||
|
let finalY2 = pdf_doc.lastAutoTable.finalY;
|
||||||
|
let finalY3 = pdf_doc.lastAutoTable.finalY;
|
||||||
|
for (let i = 0; i < data2.length; i++) {
|
||||||
|
const p = data2[i];
|
||||||
|
let pageNumber = pdf_doc.internal.getNumberOfPages()
|
||||||
|
|
||||||
|
const body = Array.from({length: minRank}, () => []);
|
||||||
|
for (const c of p.podium) {
|
||||||
|
if (c.rank > maxRank)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (body[c.rank - 1])
|
||||||
|
body[c.rank - 1].push(c.name)
|
||||||
|
else
|
||||||
|
body[c.rank - 1] = [c.name]
|
||||||
|
}
|
||||||
|
for (let j = 0; j < body.length; j++) {
|
||||||
|
if (body[j].length === 0)
|
||||||
|
body[j].push(" ")
|
||||||
|
body[j] = [
|
||||||
|
{content: j + 1, styles: {halign: "center"}},
|
||||||
|
{content: body[j].join(", "), styles: {halign: "center"}}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
autoTable(pdf_doc, {
|
||||||
|
startY: finalY3 + 7,
|
||||||
|
margin: i % 2 ? {left: pdf_doc.internal.pageSize.getWidth() / 2 + 7} : {right: pdf_doc.internal.pageSize.getWidth() / 2 + 7},
|
||||||
|
styles: {fontSize: 10, cellPadding: 3},
|
||||||
|
columnStyles: {
|
||||||
|
0: {cellWidth: 35},
|
||||||
|
1: {cellWidth: "auto"},
|
||||||
|
},
|
||||||
|
pageBreak: "avoid",
|
||||||
|
showHead: 'firstPage',
|
||||||
|
head: [[
|
||||||
|
{content: p.poule_name, colSpan: 2, styles: {halign: "center"}},
|
||||||
|
], [
|
||||||
|
{content: t('place', {ns: "result"}), styles: {halign: "center"}},
|
||||||
|
{content: t('combattants', {ns: 'result'}), styles: {halign: "center"}},
|
||||||
|
]],
|
||||||
|
body: body,
|
||||||
|
foot: [[
|
||||||
|
{content: t('source') + " : " + p.source, colSpan: 2, styles: {halign: "left", fontSize: 8}},
|
||||||
|
]],
|
||||||
|
rowPageBreak: 'auto',
|
||||||
|
theme: 'grid',
|
||||||
|
})
|
||||||
|
|
||||||
|
if (i % 2 === 0) {
|
||||||
|
finalY2 = pdf_doc.lastAutoTable.finalY;
|
||||||
|
if (pageNumber !== pdf_doc.internal.getNumberOfPages())
|
||||||
|
finalY3 = 33
|
||||||
|
} else {
|
||||||
|
pdf_doc.lastAutoTable.finalY = Math.max(finalY2, pdf_doc.lastAutoTable.finalY);
|
||||||
|
finalY3 = pdf_doc.lastAutoTable.finalY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pdf_doc.lastAutoTable.finalY = Math.max(finalY2, pdf_doc.lastAutoTable.finalY);
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateClubPodium({pdf_doc, data, t, logo, c_name}) {
|
||||||
|
makeHeader(pdf_doc, c_name, t('podiumDesClubs'), logo)
|
||||||
|
|
||||||
|
const body = data.map(c => [
|
||||||
|
{content: [c.name], styles: {halign: "center"}},
|
||||||
|
{content: c.score[0], styles: {halign: "center"}},
|
||||||
|
{content: c.score[1], styles: {halign: "center"}},
|
||||||
|
{content: c.score[2], styles: {halign: "center"}},
|
||||||
|
{content: c.tt_score, styles: {halign: "center"}},
|
||||||
|
]);
|
||||||
|
|
||||||
|
let rank = 0;
|
||||||
|
let lastScores = null;
|
||||||
|
let lastB = null;
|
||||||
|
for (const b of body) {
|
||||||
|
const scores = [b[1].content, b[2].content, b[3].content, b[4].content];
|
||||||
|
if (lastScores !== scores) {
|
||||||
|
rank++;
|
||||||
|
b.unshift({content: rank, styles: {halign: "center"}})
|
||||||
|
lastB = b
|
||||||
|
} else {
|
||||||
|
lastB[1].content.push(b[0].content);
|
||||||
|
delete body.indexOf(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const b of body) {
|
||||||
|
b[1].content = b[1].content.join(", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
autoTable(pdf_doc, {
|
||||||
|
startY: pdf_doc.lastAutoTable.finalY + 7,
|
||||||
|
styles: {fontSize: 10, cellPadding: 3},
|
||||||
|
columnStyles: {
|
||||||
|
0: {cellWidth: 35},
|
||||||
|
1: {cellWidth: "auto"},
|
||||||
|
2: {cellWidth: 45},
|
||||||
|
3: {cellWidth: 45},
|
||||||
|
4: {cellWidth: 45},
|
||||||
|
5: {cellWidth: 40},
|
||||||
|
},
|
||||||
|
pageBreak: "avoid",
|
||||||
|
showHead: 'firstPage',
|
||||||
|
head: [[
|
||||||
|
{content: t('place', {ns: "result"}), styles: {halign: "center"}},
|
||||||
|
{content: t('club', {ns: 'result'}), styles: {halign: "center"}},
|
||||||
|
{content: t('1er', {ns: 'result'}), styles: {halign: "center"}},
|
||||||
|
{content: t('2eme', {ns: 'result'}), styles: {halign: "center"}},
|
||||||
|
{content: t('3eme', {ns: 'result'}), styles: {halign: "center"}},
|
||||||
|
{content: t('scores', {ns: 'result'}), styles: {halign: "center"}},
|
||||||
|
]],
|
||||||
|
body: body,
|
||||||
|
rowPageBreak: 'auto',
|
||||||
|
theme: 'grid',
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user