dev #106
37
src/main/java/fr/titionfire/ffsaf/data/model/CardModel.java
Normal file
37
src/main/java/fr/titionfire/ffsaf/data/model/CardModel.java
Normal file
@ -0,0 +1,37 @@
|
||||
package fr.titionfire.ffsaf.data.model;
|
||||
|
||||
import io.quarkus.runtime.annotations.RegisterForReflection;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@RegisterForReflection
|
||||
|
||||
@Entity
|
||||
@Table(name = "card")
|
||||
public class CardModel {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
Long id;
|
||||
|
||||
Long comb;
|
||||
Long match;
|
||||
Long category;
|
||||
Long competition;
|
||||
|
||||
CardType type;
|
||||
|
||||
public enum CardType {
|
||||
BLUE,
|
||||
YELLOW,
|
||||
RED,
|
||||
BLACK
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
package fr.titionfire.ffsaf.data.repository;
|
||||
|
||||
import fr.titionfire.ffsaf.data.model.CardModel;
|
||||
import io.quarkus.hibernate.reactive.panache.PanacheRepositoryBase;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
|
||||
@ApplicationScoped
|
||||
public class CardRepository implements PanacheRepositoryBase<CardModel, Long> {
|
||||
|
||||
}
|
||||
@ -0,0 +1,91 @@
|
||||
package fr.titionfire.ffsaf.domain.service;
|
||||
|
||||
import fr.titionfire.ffsaf.data.model.CardModel;
|
||||
import fr.titionfire.ffsaf.data.model.MatchModel;
|
||||
import fr.titionfire.ffsaf.data.repository.CardRepository;
|
||||
import fr.titionfire.ffsaf.domain.entity.MatchEntity;
|
||||
import fr.titionfire.ffsaf.rest.exception.DBadRequestException;
|
||||
import fr.titionfire.ffsaf.ws.recv.RCard;
|
||||
import io.quarkus.hibernate.reactive.panache.common.WithSession;
|
||||
import io.quarkus.panache.common.Sort;
|
||||
import io.smallrye.mutiny.Uni;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
@WithSession
|
||||
@ApplicationScoped
|
||||
public class CardService {
|
||||
@Inject
|
||||
CardRepository cardRepository;
|
||||
|
||||
@Inject
|
||||
TradService trad;
|
||||
|
||||
private static final List<CardModel.CardType> COMPETITION_LEVEL_CARDS = List.of(
|
||||
CardModel.CardType.YELLOW,
|
||||
CardModel.CardType.RED,
|
||||
CardModel.CardType.BLACK
|
||||
);
|
||||
|
||||
private List<Long> extractCombIds(MatchModel match) {
|
||||
List<Long> ids = new ArrayList<>();
|
||||
if (match.getC1_id() != null)
|
||||
ids.add(match.getC1_id().getId());
|
||||
if (match.getC2_id() != null)
|
||||
ids.add(match.getC2_id().getId());
|
||||
if (match.getC1_guest() != null)
|
||||
ids.add(match.getC1_guest().getId() * -1);
|
||||
if (match.getC2_guest() != null)
|
||||
ids.add(match.getC2_guest().getId() * -1);
|
||||
return ids;
|
||||
}
|
||||
|
||||
private Collection<Long> extractCombIds(MatchEntity match) {
|
||||
List<Long> ids = new ArrayList<>();
|
||||
if (match.getC1() != null)
|
||||
ids.add(match.getC1().getId());
|
||||
if (match.getC2() != null)
|
||||
ids.add(match.getC2().getId());
|
||||
return ids;
|
||||
}
|
||||
|
||||
public Uni<List<CardModel>> getForMatch(MatchModel match) {
|
||||
return cardRepository.list(
|
||||
"competition = ?1 AND (type IN ?2 OR (type = CardType.BLUE AND category = ?4)) AND comb IN ?3",
|
||||
match.getCategory().getCompet().getId(), COMPETITION_LEVEL_CARDS,
|
||||
extractCombIds(match), match.getCategory().getId());
|
||||
}
|
||||
|
||||
public Uni<List<CardModel>> getAll(Long competitionId) {
|
||||
return cardRepository.list("competition = ?1", competitionId);
|
||||
}
|
||||
|
||||
public Uni<RCard.SendCardAdd> checkCanBeAdded(RCard.SendCardAdd card, MatchModel matchModel) {
|
||||
return cardRepository.find("competition = ?1 AND comb = ?2",
|
||||
Sort.descending("type"),
|
||||
matchModel.getCategory().getCompet().getId(), card.combId())
|
||||
.firstResult()
|
||||
.map(card_ -> {
|
||||
if (card.type() == CardModel.CardType.BLUE) {
|
||||
return card_ == null || (card_.getType() == CardModel.CardType.BLUE
|
||||
&& !Objects.equals(card_.getCategory(), matchModel.getCategory().getId()));
|
||||
}
|
||||
if (card.type() == CardModel.CardType.BLACK) {
|
||||
return card_ != null && card_.getType() == CardModel.CardType.RED;
|
||||
}
|
||||
|
||||
return card_ == null || card_.getType().ordinal() < card.type().ordinal();
|
||||
})
|
||||
.chain(b -> {
|
||||
if (b)
|
||||
return Uni.createFrom().item(card);
|
||||
else
|
||||
return Uni.createFrom().failure(new DBadRequestException(trad.t("card.cannot.be.added")));
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -46,7 +46,7 @@ public class CompetitionWS {
|
||||
RRegister rRegister;
|
||||
|
||||
@Inject
|
||||
RCardboard rCardboard;
|
||||
RCard rCard;
|
||||
|
||||
@Inject
|
||||
RTeam rTeam;
|
||||
@ -93,7 +93,7 @@ public class CompetitionWS {
|
||||
getWSReceiverMethods(RMatch.class, rMatch);
|
||||
getWSReceiverMethods(RCategorie.class, rCategorie);
|
||||
getWSReceiverMethods(RRegister.class, rRegister);
|
||||
getWSReceiverMethods(RCardboard.class, rCardboard);
|
||||
getWSReceiverMethods(RCard.class, rCard);
|
||||
getWSReceiverMethods(RTeam.class, rTeam);
|
||||
|
||||
executor = notifyExecutor;
|
||||
|
||||
102
src/main/java/fr/titionfire/ffsaf/ws/recv/RCard.java
Normal file
102
src/main/java/fr/titionfire/ffsaf/ws/recv/RCard.java
Normal file
@ -0,0 +1,102 @@
|
||||
package fr.titionfire.ffsaf.ws.recv;
|
||||
|
||||
import fr.titionfire.ffsaf.data.model.CardModel;
|
||||
import fr.titionfire.ffsaf.data.model.MatchModel;
|
||||
import fr.titionfire.ffsaf.data.repository.CardRepository;
|
||||
import fr.titionfire.ffsaf.data.repository.CombRepository;
|
||||
import fr.titionfire.ffsaf.data.repository.CompetitionGuestRepository;
|
||||
import fr.titionfire.ffsaf.data.repository.MatchRepository;
|
||||
import fr.titionfire.ffsaf.domain.service.CardService;
|
||||
import fr.titionfire.ffsaf.domain.service.TradService;
|
||||
import fr.titionfire.ffsaf.rest.exception.DForbiddenException;
|
||||
import fr.titionfire.ffsaf.rest.exception.DNotFoundException;
|
||||
import fr.titionfire.ffsaf.ws.PermLevel;
|
||||
import fr.titionfire.ffsaf.ws.send.SSCard;
|
||||
import io.quarkus.hibernate.reactive.panache.Panache;
|
||||
import io.quarkus.hibernate.reactive.panache.common.WithSession;
|
||||
import io.quarkus.runtime.annotations.RegisterForReflection;
|
||||
import io.quarkus.websockets.next.WebSocketConnection;
|
||||
import io.smallrye.mutiny.Uni;
|
||||
import io.smallrye.mutiny.unchecked.Unchecked;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@WithSession
|
||||
@ApplicationScoped
|
||||
@RegisterForReflection
|
||||
public class RCard {
|
||||
|
||||
@Inject
|
||||
MatchRepository matchRepository;
|
||||
|
||||
@Inject
|
||||
CardRepository cardRepository;
|
||||
|
||||
@Inject
|
||||
CardService cardService;
|
||||
|
||||
@Inject
|
||||
CombRepository combRepository;
|
||||
|
||||
@Inject
|
||||
CompetitionGuestRepository competitionGuestRepository;
|
||||
|
||||
@Inject
|
||||
TradService trad;
|
||||
|
||||
private Uni<MatchModel> getById(long id, WebSocketConnection connection) {
|
||||
return matchRepository.findById(id)
|
||||
.invoke(Unchecked.consumer(o -> {
|
||||
if (o == null)
|
||||
throw new DNotFoundException(trad.t("matche.non.trouver"));
|
||||
if (!o.getCategory().getCompet().getUuid().equals(connection.pathParam("uuid")))
|
||||
throw new DForbiddenException(trad.t("permission.denied"));
|
||||
}));
|
||||
}
|
||||
|
||||
@WSReceiver(code = "getCardForMatch", permission = PermLevel.VIEW)
|
||||
public Uni<List<CardModel>> getCardForMatch(WebSocketConnection connection, Long matchId) {
|
||||
return getById(matchId, connection).chain(matchModel -> cardService.getForMatch(matchModel));
|
||||
}
|
||||
|
||||
@WSReceiver(code = "sendCardAdd", permission = PermLevel.TABLE)
|
||||
public Uni<Void> sendCardAdd(WebSocketConnection connection, SendCardAdd card) {
|
||||
return getById(card.matchId(), connection)
|
||||
.chain(matchModel -> cardService.checkCanBeAdded(card, matchModel)
|
||||
.chain(c -> {
|
||||
CardModel model = new CardModel();
|
||||
model.setComb(card.combId());
|
||||
model.setMatch(card.matchId());
|
||||
model.setCategory(matchModel.getCategory().getId());
|
||||
model.setCompetition(matchModel.getCategory().getCompet().getId());
|
||||
model.setType(card.type());
|
||||
|
||||
return Panache.withTransaction(() -> cardRepository.persist(model));
|
||||
})
|
||||
)
|
||||
.invoke(cardModel -> SSCard.sendCard(connection, cardModel))
|
||||
.replaceWithVoid();
|
||||
}
|
||||
|
||||
@WSReceiver(code = "sendCardRm", permission = PermLevel.ADMIN)
|
||||
public Uni<Void> sendCardRm(WebSocketConnection connection, SendCardAdd card) {
|
||||
return getById(card.matchId(), connection)
|
||||
.chain(matchModel -> cardRepository.find("match = ?1 AND comb = ?2 AND type = ?3",
|
||||
matchModel.getId(), card.combId(), card.type())
|
||||
.firstResult()
|
||||
.invoke(Unchecked.consumer(o -> {
|
||||
if (o == null)
|
||||
throw new DNotFoundException(trad.t("carton.non.trouver"));
|
||||
SSCard.sendRmCard(connection, o.getId());
|
||||
}))
|
||||
.chain(cardModel -> Panache.withTransaction(() -> cardRepository.delete(cardModel)))
|
||||
)
|
||||
.replaceWithVoid();
|
||||
}
|
||||
|
||||
@RegisterForReflection
|
||||
public record SendCardAdd(long matchId, long combId, CardModel.CardType type) {
|
||||
}
|
||||
}
|
||||
@ -1,129 +0,0 @@
|
||||
package fr.titionfire.ffsaf.ws.recv;
|
||||
|
||||
import fr.titionfire.ffsaf.data.model.CardboardModel;
|
||||
import fr.titionfire.ffsaf.data.model.MatchModel;
|
||||
import fr.titionfire.ffsaf.data.repository.CardboardRepository;
|
||||
import fr.titionfire.ffsaf.data.repository.MatchRepository;
|
||||
import fr.titionfire.ffsaf.domain.entity.CardboardEntity;
|
||||
import fr.titionfire.ffsaf.domain.service.TradService;
|
||||
import fr.titionfire.ffsaf.rest.exception.DForbiddenException;
|
||||
import fr.titionfire.ffsaf.rest.exception.DNotFoundException;
|
||||
import fr.titionfire.ffsaf.ws.PermLevel;
|
||||
import fr.titionfire.ffsaf.ws.send.SSCardboard;
|
||||
import io.quarkus.hibernate.reactive.panache.Panache;
|
||||
import io.quarkus.hibernate.reactive.panache.common.WithSession;
|
||||
import io.quarkus.runtime.annotations.RegisterForReflection;
|
||||
import io.quarkus.websockets.next.WebSocketConnection;
|
||||
import io.smallrye.mutiny.Uni;
|
||||
import io.smallrye.mutiny.unchecked.Unchecked;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@WithSession
|
||||
@ApplicationScoped
|
||||
@RegisterForReflection
|
||||
public class RCardboard {
|
||||
|
||||
@Inject
|
||||
MatchRepository matchRepository;
|
||||
|
||||
@Inject
|
||||
CardboardRepository cardboardRepository;
|
||||
|
||||
@Inject
|
||||
TradService trad;
|
||||
|
||||
private Uni<MatchModel> getById(long id, WebSocketConnection connection) {
|
||||
return matchRepository.findById(id)
|
||||
.invoke(Unchecked.consumer(o -> {
|
||||
if (o == null)
|
||||
throw new DNotFoundException(trad.t("matche.non.trouver"));
|
||||
if (!o.getCategory().getCompet().getUuid().equals(connection.pathParam("uuid")))
|
||||
throw new DForbiddenException(trad.t("permission.denied"));
|
||||
}));
|
||||
}
|
||||
|
||||
@WSReceiver(code = "sendCardboardChange", permission = PermLevel.TABLE)
|
||||
public Uni<Void> sendCardboardChange(WebSocketConnection connection, SendCardboard card) {
|
||||
return getById(card.matchId, connection)
|
||||
.chain(matchModel -> cardboardRepository.find("(comb.id = ?1 OR guestComb.id = ?2) AND match.id = ?3",
|
||||
card.combId, card.combId * -1, card.matchId).firstResult()
|
||||
.chain(model -> {
|
||||
if (model != null) {
|
||||
model.setRed(model.getRed() + card.red);
|
||||
model.setYellow(model.getYellow() + card.yellow);
|
||||
return Panache.withTransaction(() -> cardboardRepository.persist(model));
|
||||
}
|
||||
CardboardModel cardboardModel = new CardboardModel();
|
||||
|
||||
cardboardModel.setCompet(matchModel.getCategory().getCompet());
|
||||
cardboardModel.setMatch(matchModel);
|
||||
cardboardModel.setRed(card.red);
|
||||
cardboardModel.setYellow(card.yellow);
|
||||
cardboardModel.setComb(null);
|
||||
cardboardModel.setGuestComb(null);
|
||||
|
||||
if (card.combId >= 0) {
|
||||
if (matchModel.getC1_id() != null && matchModel.getC1_id().getId() == card.combId)
|
||||
cardboardModel.setComb(matchModel.getC1_id());
|
||||
if (matchModel.getC2_id() != null && matchModel.getC2_id().getId() == card.combId)
|
||||
cardboardModel.setComb(matchModel.getC2_id());
|
||||
} else {
|
||||
if (matchModel.getC1_guest() != null && matchModel.getC1_guest()
|
||||
.getId() == card.combId * -1)
|
||||
cardboardModel.setGuestComb(matchModel.getC1_guest());
|
||||
if (matchModel.getC2_guest() != null && matchModel.getC2_guest()
|
||||
.getId() == card.combId * -1)
|
||||
cardboardModel.setGuestComb(matchModel.getC2_guest());
|
||||
}
|
||||
|
||||
if (cardboardModel.getComb() == null && cardboardModel.getGuestComb() == null)
|
||||
return Uni.createFrom().nullItem();
|
||||
return Panache.withTransaction(() -> cardboardRepository.persist(cardboardModel));
|
||||
}))
|
||||
.invoke(model -> SSCardboard.sendCardboard(connection, CardboardEntity.fromModel(model)))
|
||||
.replaceWithVoid();
|
||||
}
|
||||
|
||||
@WSReceiver(code = "getCardboardWithoutThis", permission = PermLevel.VIEW)
|
||||
public Uni<CardboardAllMatch> getCardboardWithoutThis(WebSocketConnection connection, Long matchId) {
|
||||
return getById(matchId, connection)
|
||||
.chain(matchModel -> cardboardRepository.list("compet = ?1 AND match != ?2",
|
||||
matchModel.getCategory().getCompet(), matchModel)
|
||||
.map(models -> {
|
||||
CardboardAllMatch out = new CardboardAllMatch();
|
||||
|
||||
for (CardboardModel c : models) {
|
||||
if ((matchModel.getC1_id() != null && Objects.equals(c.getComb(),
|
||||
matchModel.getC1_id())) || (matchModel.getC1_guest() != null && Objects.equals(
|
||||
c.getGuestComb(), matchModel.getC1_guest()))) {
|
||||
out.c1_yellow += c.getYellow();
|
||||
out.c1_red += c.getRed();
|
||||
}
|
||||
if ((matchModel.getC2_id() != null && Objects.equals(c.getComb(),
|
||||
matchModel.getC2_id())) || (matchModel.getC2_guest() != null && Objects.equals(
|
||||
c.getGuestComb(), matchModel.getC2_guest()))) {
|
||||
out.c2_yellow += c.getYellow();
|
||||
out.c2_red += c.getRed();
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}));
|
||||
}
|
||||
|
||||
@RegisterForReflection
|
||||
public record SendCardboard(long matchId, long combId, int yellow, int red) {
|
||||
}
|
||||
|
||||
@Data
|
||||
@RegisterForReflection
|
||||
public static class CardboardAllMatch {
|
||||
int c1_yellow = 0;
|
||||
int c1_red = 0;
|
||||
int c2_yellow = 0;
|
||||
int c2_red = 0;
|
||||
}
|
||||
}
|
||||
@ -1,11 +1,13 @@
|
||||
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.model.TreeModel;
|
||||
import fr.titionfire.ffsaf.data.repository.*;
|
||||
import fr.titionfire.ffsaf.domain.entity.MatchEntity;
|
||||
import fr.titionfire.ffsaf.domain.entity.TreeEntity;
|
||||
import fr.titionfire.ffsaf.domain.service.CardService;
|
||||
import fr.titionfire.ffsaf.domain.service.TradService;
|
||||
import fr.titionfire.ffsaf.rest.exception.DForbiddenException;
|
||||
import fr.titionfire.ffsaf.rest.exception.DNotFoundException;
|
||||
@ -47,6 +49,9 @@ public class RCategorie {
|
||||
@Inject
|
||||
CardboardRepository cardboardRepository;
|
||||
|
||||
@Inject
|
||||
CardService cardService;
|
||||
|
||||
@Inject
|
||||
TradService trad;
|
||||
|
||||
@ -84,6 +89,8 @@ public class RCategorie {
|
||||
.call(cat -> treeRepository.list("category = ?1 AND level != 0", cat.getId())
|
||||
.map(treeModels -> treeModels.stream().map(TreeEntity::fromModel).toList())
|
||||
.invoke(fullCategory::setTrees))
|
||||
.call(cat -> cardService.getAll(cat.getCompet().getId())
|
||||
.invoke(fullCategory::setCards))
|
||||
.map(__ -> fullCategory);
|
||||
}
|
||||
|
||||
@ -241,5 +248,6 @@ public class RCategorie {
|
||||
String liceName;
|
||||
List<TreeEntity> trees = null;
|
||||
List<MatchEntity> matches;
|
||||
List<CardModel> cards;
|
||||
}
|
||||
}
|
||||
|
||||
16
src/main/java/fr/titionfire/ffsaf/ws/send/SSCard.java
Normal file
16
src/main/java/fr/titionfire/ffsaf/ws/send/SSCard.java
Normal file
@ -0,0 +1,16 @@
|
||||
package fr.titionfire.ffsaf.ws.send;
|
||||
|
||||
import fr.titionfire.ffsaf.data.model.CardModel;
|
||||
import fr.titionfire.ffsaf.ws.CompetitionWS;
|
||||
import io.quarkus.websockets.next.WebSocketConnection;
|
||||
|
||||
public class SSCard {
|
||||
|
||||
public static void sendCard(WebSocketConnection connection, CardModel cardModel) {
|
||||
CompetitionWS.sendNotifyToOtherEditor(connection, "sendCard", cardModel);
|
||||
}
|
||||
|
||||
public static void sendRmCard(WebSocketConnection connection, Long id) {
|
||||
CompetitionWS.sendNotifyToOtherEditor(connection, "rmCard", id);
|
||||
}
|
||||
}
|
||||
@ -1,12 +0,0 @@
|
||||
package fr.titionfire.ffsaf.ws.send;
|
||||
|
||||
import fr.titionfire.ffsaf.domain.entity.CardboardEntity;
|
||||
import fr.titionfire.ffsaf.ws.CompetitionWS;
|
||||
import io.quarkus.websockets.next.WebSocketConnection;
|
||||
|
||||
public class SSCardboard {
|
||||
|
||||
public static void sendCardboard(WebSocketConnection connection, CardboardEntity cardboardEntity) {
|
||||
CompetitionWS.sendNotifyToOtherEditor(connection, "sendCardboard", cardboardEntity);
|
||||
}
|
||||
}
|
||||
@ -85,3 +85,5 @@ licence.membre.n.1.inconnue=License member no. 1 unknown
|
||||
licence.membre.n.2.inconnue=License member no. 2 unknown
|
||||
licence.membre.n.3.inconnue=License member no. 3 unknown
|
||||
demande.d.affiliation.non.trouve=Affiliation request not found
|
||||
carton.non.trouver=Card not found
|
||||
card.cannot.be.added=Unable to add the card
|
||||
|
||||
@ -81,3 +81,5 @@ licence.membre.n.1.inconnue=Licence du membre n
|
||||
licence.membre.n.2.inconnue=Licence du membre n°2 inconnue
|
||||
licence.membre.n.3.inconnue=Licence du membre n°3 inconnue
|
||||
demande.d.affiliation.non.trouve=Demande d'affiliation introuvable
|
||||
carton.non.trouver=Carton introuvable
|
||||
card.cannot.be.added=Impossible d'ajouter le carton
|
||||
@ -5,13 +5,21 @@
|
||||
"actuel": "Current",
|
||||
"administration": "Administration",
|
||||
"adresseDuServeur": "Server address",
|
||||
"advertisement": "",
|
||||
"ajouter": "Add",
|
||||
"ajouterDesCombattants": "Add fighters",
|
||||
"ajouterUn": "Add one",
|
||||
"ajouterUneTeam": "Add team",
|
||||
"attention": "Warning",
|
||||
"aucuneConfigurationObs": "No OBS configuration found, please import one",
|
||||
"avertissement": "Warning",
|
||||
"bleu": "Blue",
|
||||
"blue": "Blue",
|
||||
"cardAdded": "Card added",
|
||||
"cardRemoved": "Card removed",
|
||||
"cartonJaune": "Yellow card",
|
||||
"cartonNoir": "Black card",
|
||||
"cartonRouge": "Red card",
|
||||
"catégorie": "Category",
|
||||
"chrono.+/-...S": "+/- ... s",
|
||||
"chrono.+10S": "+10 s",
|
||||
@ -47,6 +55,7 @@
|
||||
"duréePause": "Pause duration",
|
||||
"duréeRound": "Round duration",
|
||||
"editionDeLaCatégorie": "Edit category",
|
||||
"editionDuMatch": "Match edition",
|
||||
"enregister": "Save",
|
||||
"enregistrer": "Save",
|
||||
"epéeBouclier": "Sword and shield",
|
||||
@ -100,6 +109,7 @@
|
||||
"serveur": "Server",
|
||||
"suivant": "Next",
|
||||
"supprimer": "Delete",
|
||||
"supprimerUn": "Delete one",
|
||||
"sélectionneLesModesDaffichage": "Select display modes",
|
||||
"sélectionner": "Select",
|
||||
"team": "Team",
|
||||
|
||||
@ -247,6 +247,9 @@
|
||||
"comp.toast.register.self.del.error": "Error during unregistration",
|
||||
"comp.toast.register.self.del.pending": "Unregistration in progress",
|
||||
"comp.toast.register.self.del.success": "Unregistration completed",
|
||||
"comp.toast.registers.addMultiple.error": "Import failed",
|
||||
"comp.toast.registers.addMultiple.pending": "Import in progress",
|
||||
"comp.toast.registers.addMultiple.success": "Import completed successfully 🎉",
|
||||
"comp.toast.save.error": "Failed to save competition",
|
||||
"comp.toast.save.pending": "Saving competition in progress",
|
||||
"comp.toast.save.success": "Competition saved successfully 🎉",
|
||||
@ -309,6 +312,66 @@
|
||||
"f": "F",
|
||||
"faitPar": "Done by",
|
||||
"femme": "Female",
|
||||
"fileImport.variants": {
|
||||
"categorie": [
|
||||
"category",
|
||||
"catégorie",
|
||||
"weight category",
|
||||
"age category"
|
||||
],
|
||||
"club": [
|
||||
"club",
|
||||
"club name",
|
||||
"association",
|
||||
"association name"
|
||||
],
|
||||
"genre": [
|
||||
"gender",
|
||||
"genre",
|
||||
"sex",
|
||||
"civility"
|
||||
],
|
||||
"licence": [
|
||||
"license",
|
||||
"licence",
|
||||
"license number",
|
||||
"license ID",
|
||||
"ID license",
|
||||
"licence no"
|
||||
],
|
||||
"nom": [
|
||||
"last name",
|
||||
"nom",
|
||||
"family name",
|
||||
"surname",
|
||||
"lastname"
|
||||
],
|
||||
"overCategory": [
|
||||
"over category",
|
||||
"surclassement",
|
||||
"category override",
|
||||
"over classification"
|
||||
],
|
||||
"pays": [
|
||||
"country",
|
||||
"pays",
|
||||
"country of residence",
|
||||
"origin country"
|
||||
],
|
||||
"prenom": [
|
||||
"first name",
|
||||
"prénom",
|
||||
"given name",
|
||||
"first given name"
|
||||
],
|
||||
"weight": [
|
||||
"weight",
|
||||
"poids",
|
||||
"weight (kg)",
|
||||
"actual weight",
|
||||
"mass"
|
||||
]
|
||||
},
|
||||
"filtre": "Filter",
|
||||
"gantMainBouclier": "Shield hand glove",
|
||||
"gantMainsArmées": "Armed hand(s) glove(s)",
|
||||
@ -597,68 +660,5 @@
|
||||
"voirLesStatues": "View statues",
|
||||
"vousNêtesPasEncoreInscrit": "You are not yet registered or your registration has not yet been entered on the intranet",
|
||||
"à": "at",
|
||||
"étatDeLaDemande": "Request status",
|
||||
"fileImport.variants": {
|
||||
"licence": [
|
||||
"license",
|
||||
"licence",
|
||||
"license number",
|
||||
"license ID",
|
||||
"ID license",
|
||||
"licence no"
|
||||
],
|
||||
"pays": [
|
||||
"country",
|
||||
"pays",
|
||||
"country of residence",
|
||||
"origin country"
|
||||
],
|
||||
"nom": [
|
||||
"last name",
|
||||
"nom",
|
||||
"family name",
|
||||
"surname",
|
||||
"lastname"
|
||||
],
|
||||
"prenom": [
|
||||
"first name",
|
||||
"prénom",
|
||||
"given name",
|
||||
"first given name"
|
||||
],
|
||||
"genre": [
|
||||
"gender",
|
||||
"genre",
|
||||
"sex",
|
||||
"civility"
|
||||
],
|
||||
"weight": [
|
||||
"weight",
|
||||
"poids",
|
||||
"weight (kg)",
|
||||
"actual weight",
|
||||
"mass"
|
||||
],
|
||||
"categorie": [
|
||||
"category",
|
||||
"catégorie",
|
||||
"weight category",
|
||||
"age category"
|
||||
],
|
||||
"overCategory": [
|
||||
"over category",
|
||||
"surclassement",
|
||||
"category override",
|
||||
"over classification"
|
||||
],
|
||||
"club": [
|
||||
"club",
|
||||
"club name",
|
||||
"association",
|
||||
"association name"
|
||||
]
|
||||
},
|
||||
"comp.toast.registers.addMultiple.error": "Import failed",
|
||||
"comp.toast.registers.addMultiple.pending": "Import in progress",
|
||||
"comp.toast.registers.addMultiple.success": "Import completed successfully 🎉"
|
||||
"étatDeLaDemande": "Request status"
|
||||
}
|
||||
|
||||
@ -5,13 +5,21 @@
|
||||
"actuel": "Actuel",
|
||||
"administration": "Administration",
|
||||
"adresseDuServeur": "Adresse du serveur",
|
||||
"advertisement": "Advertisement",
|
||||
"ajouter": "Ajouter",
|
||||
"ajouterDesCombattants": "Ajouter des combattants",
|
||||
"ajouterUn": "Ajouter un ",
|
||||
"ajouterUneTeam": "Ajouter une équipe",
|
||||
"attention": "Attention",
|
||||
"aucuneConfigurationObs": "Aucune configuration OBS trouvée, veuillez en importer une",
|
||||
"avertissement": "Avertissement",
|
||||
"bleu": "Bleu",
|
||||
"blue": "Blue",
|
||||
"cardAdded": "Carton ajouté",
|
||||
"cardRemoved": "Carton retiré",
|
||||
"cartonJaune": "Carton jaune",
|
||||
"cartonNoir": "Carton noir",
|
||||
"cartonRouge": "Carton rouge",
|
||||
"catégorie": "Catégorie",
|
||||
"chrono.+/-...S": "+/- ... s",
|
||||
"chrono.+10S": "+10 s",
|
||||
@ -47,6 +55,7 @@
|
||||
"duréePause": "Durée pause",
|
||||
"duréeRound": "Durée round",
|
||||
"editionDeLaCatégorie": "Edition de la catégorie",
|
||||
"editionDuMatch": "Edition du match",
|
||||
"enregister": "Enregister",
|
||||
"enregistrer": "Enregistrer",
|
||||
"epéeBouclier": "Epée bouclier",
|
||||
@ -100,6 +109,7 @@
|
||||
"serveur": "Serveur",
|
||||
"suivant": "Suivant",
|
||||
"supprimer": "Supprimer",
|
||||
"supprimerUn": "Supprimer un",
|
||||
"sélectionneLesModesDaffichage": "Sélectionne les modes d'affichage",
|
||||
"sélectionner": "Sélectionner",
|
||||
"team": "Équipe",
|
||||
|
||||
@ -247,6 +247,9 @@
|
||||
"comp.toast.register.self.del.error": "Erreur lors de la désinscription",
|
||||
"comp.toast.register.self.del.pending": "Désinscription en cours",
|
||||
"comp.toast.register.self.del.success": "Désinscription réalisée",
|
||||
"comp.toast.registers.addMultiple.error": "Erreur lors de l'importation des combattants",
|
||||
"comp.toast.registers.addMultiple.pending": "Importation des combattants en cours...",
|
||||
"comp.toast.registers.addMultiple.success": "Importation des combattants réussie 🎉",
|
||||
"comp.toast.save.error": "Échec de l'enregistrement de la compétition",
|
||||
"comp.toast.save.pending": "Enregistrement de la compétition en cours",
|
||||
"comp.toast.save.success": "Compétition enregistrée avec succès 🎉",
|
||||
@ -309,6 +312,70 @@
|
||||
"f": "F",
|
||||
"faitPar": "Fait par",
|
||||
"femme": "Femme",
|
||||
"fileImport.variants": {
|
||||
"categorie": [
|
||||
"catégorie",
|
||||
"category",
|
||||
"catégorie de poids",
|
||||
"weight category",
|
||||
"catégorie d'âge"
|
||||
],
|
||||
"club": [
|
||||
"club",
|
||||
"nom du club",
|
||||
"club name",
|
||||
"association",
|
||||
"nom de l'association"
|
||||
],
|
||||
"genre": [
|
||||
"genre",
|
||||
"sexe",
|
||||
"gender",
|
||||
"sex",
|
||||
"civilité"
|
||||
],
|
||||
"licence": [
|
||||
"licence",
|
||||
"n° licence",
|
||||
"num licence",
|
||||
"id licence",
|
||||
"license",
|
||||
"licence id"
|
||||
],
|
||||
"nom": [
|
||||
"nom",
|
||||
"nom de famille",
|
||||
"lastname",
|
||||
"family name",
|
||||
"nom complet"
|
||||
],
|
||||
"overCategory": [
|
||||
"surclassement",
|
||||
"over category",
|
||||
"surcatégorie",
|
||||
"surclassement de catégorie"
|
||||
],
|
||||
"pays": [
|
||||
"pays",
|
||||
"country",
|
||||
"pays de résidence",
|
||||
"pays d'origine"
|
||||
],
|
||||
"prenom": [
|
||||
"prénom",
|
||||
"prenom",
|
||||
"first name",
|
||||
"given name",
|
||||
"prénom usuel"
|
||||
],
|
||||
"weight": [
|
||||
"poids",
|
||||
"weight",
|
||||
"poids (kg)",
|
||||
"poids réel",
|
||||
"masse"
|
||||
]
|
||||
},
|
||||
"filtre": "Filtre",
|
||||
"gantMainBouclier": "Gant main de bouclier",
|
||||
"gantMainsArmées": "Gant main(s) armée(s)",
|
||||
@ -597,72 +664,5 @@
|
||||
"voirLesStatues": "Voir les statues",
|
||||
"vousNêtesPasEncoreInscrit": "Vous n'êtes pas encore inscrit ou votre inscription n'a pas encore été rentrée sur l'intranet",
|
||||
"à": "à",
|
||||
"étatDeLaDemande": "État de la demande",
|
||||
"fileImport.variants": {
|
||||
"licence": [
|
||||
"licence",
|
||||
"n° licence",
|
||||
"num licence",
|
||||
"id licence",
|
||||
"license",
|
||||
"licence id"
|
||||
],
|
||||
"pays": [
|
||||
"pays",
|
||||
"country",
|
||||
"pays de résidence",
|
||||
"pays d'origine"
|
||||
],
|
||||
"nom": [
|
||||
"nom",
|
||||
"nom de famille",
|
||||
"lastname",
|
||||
"family name",
|
||||
"nom complet"
|
||||
],
|
||||
"prenom": [
|
||||
"prénom",
|
||||
"prenom",
|
||||
"first name",
|
||||
"given name",
|
||||
"prénom usuel"
|
||||
],
|
||||
"genre": [
|
||||
"genre",
|
||||
"sexe",
|
||||
"gender",
|
||||
"sex",
|
||||
"civilité"
|
||||
],
|
||||
"weight": [
|
||||
"poids",
|
||||
"weight",
|
||||
"poids (kg)",
|
||||
"poids réel",
|
||||
"masse"
|
||||
],
|
||||
"categorie": [
|
||||
"catégorie",
|
||||
"category",
|
||||
"catégorie de poids",
|
||||
"weight category",
|
||||
"catégorie d'âge"
|
||||
],
|
||||
"overCategory": [
|
||||
"surclassement",
|
||||
"over category",
|
||||
"surcatégorie",
|
||||
"surclassement de catégorie"
|
||||
],
|
||||
"club": [
|
||||
"club",
|
||||
"nom du club",
|
||||
"club name",
|
||||
"association",
|
||||
"nom de l'association"
|
||||
]
|
||||
},
|
||||
"comp.toast.registers.addMultiple.error": "Erreur lors de l'importation des combattants",
|
||||
"comp.toast.registers.addMultiple.pending": "Importation des combattants en cours...",
|
||||
"comp.toast.registers.addMultiple.success": "Importation des combattants réussie 🎉"
|
||||
"étatDeLaDemande": "État de la demande"
|
||||
}
|
||||
|
||||
135
src/main/webapp/src/hooks/useCard.jsx
Normal file
135
src/main/webapp/src/hooks/useCard.jsx
Normal file
@ -0,0 +1,135 @@
|
||||
import {createContext, useContext, useEffect, useReducer} from "react";
|
||||
import {useWS} from "./useWS.jsx";
|
||||
|
||||
const CardContext = createContext({});
|
||||
const CardDispatchContext = createContext(() => {
|
||||
});
|
||||
|
||||
function compareCards(a, b) {
|
||||
for (const keys of Object.keys(a)) {
|
||||
if (a[keys] !== b[keys]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const CARD_TYPE_ORDER = [
|
||||
'BLUE',
|
||||
'YELLOW',
|
||||
'RED',
|
||||
'BLACK'
|
||||
]
|
||||
|
||||
export function compareCardOrder(a, b) {
|
||||
if (!a || !b) return 0;
|
||||
return CARD_TYPE_ORDER.indexOf(a.type) - CARD_TYPE_ORDER.indexOf(b.type);
|
||||
}
|
||||
|
||||
function reducer(state, action) {
|
||||
switch (action.type) {
|
||||
case 'SET_CARD':
|
||||
if (state[action.payload.id] === undefined || !compareCards(action.payload, state[action.payload.id])) {
|
||||
return {
|
||||
...state,
|
||||
[action.payload.id]: action.payload
|
||||
}
|
||||
}
|
||||
return state
|
||||
case 'SET_ALL':
|
||||
if (action.payload.some(e => state[e.id] === undefined || !compareCards(e, state[e.id]))) {
|
||||
const newCombs = {};
|
||||
for (const o of action.payload) {
|
||||
newCombs[o.id] = o;
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
...newCombs
|
||||
}
|
||||
}
|
||||
return state
|
||||
case 'REMOVE_CARD':
|
||||
console.log("Removing card", action.payload, state[action.payload]);
|
||||
if (state[action.payload] !== undefined) {
|
||||
const newState = {...state}
|
||||
delete newState[action.payload]
|
||||
return newState
|
||||
}
|
||||
return state
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
function WSListener({dispatch}) {
|
||||
const {dispatch: dispatchWS} = useWS()
|
||||
|
||||
useEffect(() => {
|
||||
const sendCard = ({data}) => {
|
||||
dispatch({type: 'SET_CARD', payload: data});
|
||||
}
|
||||
const rmCard = ({data}) => {
|
||||
dispatch({type: 'REMOVE_CARD', payload: data});
|
||||
}
|
||||
|
||||
dispatchWS({type: 'addListener', payload: {callback: sendCard, code: 'sendCard'}})
|
||||
dispatchWS({type: 'addListener', payload: {callback: rmCard, code: 'rmCard'}})
|
||||
return () => {
|
||||
dispatchWS({type: 'removeListener', payload: sendCard})
|
||||
dispatchWS({type: 'removeListener', payload: rmCard})
|
||||
}
|
||||
}, []);
|
||||
|
||||
return <></>
|
||||
}
|
||||
|
||||
export function CardsProvider({children}) {
|
||||
const [cards, dispatch] = useReducer(reducer, {})
|
||||
|
||||
return <CardContext.Provider value={cards}>
|
||||
<CardDispatchContext.Provider value={dispatch}>
|
||||
{children}
|
||||
<WSListener dispatch={dispatch}/>
|
||||
</CardDispatchContext.Provider>
|
||||
</CardContext.Provider>
|
||||
}
|
||||
|
||||
export function useCards() {
|
||||
const cards = useContext(CardContext);
|
||||
return {
|
||||
cards,
|
||||
cards_v: Object.values(cards),
|
||||
...useCardsStatic(Object.values(cards))
|
||||
}
|
||||
}
|
||||
|
||||
export function useCardsStatic(cards_v) {
|
||||
return {
|
||||
getCardInMatch: (match) => {
|
||||
return cards_v.filter(card => (card.comb === match.c1 || card.comb === match.c2) && card.match === match.id);
|
||||
},
|
||||
getHeightCardForCombInMatch: (combId, match) => {
|
||||
return cards_v.filter(card => card.comb === combId && (card.category === match.categorie || (card.match !== match.id && card.type !== "BLUE"))).sort(compareCardOrder).pop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function useCardsDispatch() {
|
||||
return useContext(CardDispatchContext);
|
||||
}
|
||||
|
||||
export function hasEffectCard(card, matchId, categoryId) {
|
||||
switch (card.type) {
|
||||
case 'BLUE':
|
||||
return false;
|
||||
case 'YELLOW':
|
||||
return card.match === matchId;
|
||||
case 'RED':
|
||||
return card.match === matchId || card.category === categoryId;
|
||||
case 'BLACK':
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -78,7 +78,7 @@ function WSListener({dispatch}) {
|
||||
|
||||
dispatchWS({type: 'addListener', payload: {callback: sendRegister, code: 'sendRegister'}})
|
||||
return () => {
|
||||
dispatchWS({type: 'removeListener', payload: {callback: sendRegister, code: 'sendRegister'}})
|
||||
dispatchWS({type: 'removeListener', payload: sendRegister})
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React, {useEffect, useRef, useState, useReducer} from "react";
|
||||
import React, {useEffect, useReducer, useRef, useState} from "react";
|
||||
import {CombName, useCombs, useCombsDispatch} from "../../../hooks/useComb.jsx";
|
||||
import {usePubAffDispatch} from "../../../hooks/useExternalWindow.jsx";
|
||||
import {from_sendTree, TreeNode} from "../../../utils/TreeUtils.js";
|
||||
@ -6,13 +6,12 @@ import {DrawGraph} from "../../result/DrawGraph.jsx";
|
||||
import {LoadingProvider, useLoadingSwitcher} from "../../../hooks/useLoading.jsx";
|
||||
import {useRequestWS, useWS} from "../../../hooks/useWS.jsx";
|
||||
import {MarchReducer} from "../../../utils/MatchReducer.jsx";
|
||||
import {getToastMessage, scorePrint, win} from "../../../utils/Tools.js";
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
import {faCircleQuestion} from "@fortawesome/free-regular-svg-icons";
|
||||
import {toast} from "react-toastify";
|
||||
import {virtualScore, win_end} from "../../../utils/Tools.js";
|
||||
import "./CMTMatchPanel.css"
|
||||
import {useOBS} from "../../../hooks/useOBS.jsx";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {hasEffectCard, useCards, useCardsDispatch} from "../../../hooks/useCard.jsx";
|
||||
import {ScorePanel} from "./ScoreAndCardPanel.jsx";
|
||||
|
||||
function CupImg() {
|
||||
return <img decoding="async" loading="lazy" width={"16"} height={"16"} className="wp-image-1635"
|
||||
@ -74,6 +73,7 @@ function CMTMatchPanel({catId, cat, menuActions}) {
|
||||
const [trees, setTrees] = useState([]);
|
||||
const [matches, reducer] = useReducer(MarchReducer, []);
|
||||
const combDispatch = useCombsDispatch();
|
||||
const cardDispatch = useCardsDispatch();
|
||||
|
||||
function readAndConvertMatch(matches, data, combsToAdd) {
|
||||
matches.push({...data, c1: data.c1?.id, c2: data.c2?.id})
|
||||
@ -91,6 +91,8 @@ function CMTMatchPanel({catId, cat, menuActions}) {
|
||||
.then((data) => {
|
||||
setTrees(data.trees.sort((a, b) => a.level - b.level).map(d => from_sendTree(d, true)))
|
||||
|
||||
cardDispatch({type: 'SET_ALL', payload: data.cards});
|
||||
|
||||
let matches2 = [];
|
||||
let combsToAdd = [];
|
||||
data.trees.flatMap(d => from_sendTree(d, false).flat()).forEach((data_) => readAndConvertMatch(matches2, data_, combsToAdd));
|
||||
@ -128,21 +130,15 @@ function CMTMatchPanel({catId, cat, menuActions}) {
|
||||
reducer({type: 'REMOVE', payload: data})
|
||||
}
|
||||
|
||||
const sendCardboard = ({data}) => {
|
||||
reducer({type: 'UPDATE_CARDBOARD', payload: {...data}})
|
||||
}
|
||||
|
||||
dispatch({type: 'addListener', payload: {callback: treeListener, code: 'sendTreeCategory'}})
|
||||
dispatch({type: 'addListener', payload: {callback: matchListener, code: 'sendMatch'}})
|
||||
dispatch({type: 'addListener', payload: {callback: matchOrder, code: 'sendMatchOrder'}})
|
||||
dispatch({type: 'addListener', payload: {callback: deleteMatch, code: 'sendDeleteMatch'}})
|
||||
dispatch({type: 'addListener', payload: {callback: sendCardboard, code: 'sendCardboard'}})
|
||||
return () => {
|
||||
dispatch({type: 'removeListener', payload: treeListener})
|
||||
dispatch({type: 'removeListener', payload: matchListener})
|
||||
dispatch({type: 'removeListener', payload: matchOrder})
|
||||
dispatch({type: 'removeListener', payload: deleteMatch})
|
||||
dispatch({type: 'removeListener', payload: sendCardboard})
|
||||
}
|
||||
}, [catId]);
|
||||
|
||||
@ -195,11 +191,12 @@ function MatchList({matches, cat, menuActions}) {
|
||||
const [lice, setLice] = useState(localStorage.getItem("cm_lice") || "1")
|
||||
const publicAffDispatch = usePubAffDispatch();
|
||||
const {t} = useTranslation("cm");
|
||||
const {cards, getHeightCardForCombInMatch} = useCards();
|
||||
|
||||
const liceName = (cat.liceName || "N/A").split(";");
|
||||
const marches2 = matches.filter(m => m.categorie_ord !== -42)
|
||||
.sort((a, b) => a.categorie_ord - b.categorie_ord)
|
||||
.map(m => ({...m, win: win(m.scores)}))
|
||||
.map(m => ({...m, ...win_end(m, Object.values(cards))}))
|
||||
const firstIndex = marches2.findLastIndex(m => m.poule === '-') + 1;
|
||||
|
||||
const isActiveMatch = (index) => {
|
||||
@ -242,6 +239,33 @@ function MatchList({matches, cat, menuActions}) {
|
||||
|
||||
setActiveMatch(marches2.find((m, index) => !m.end && isActiveMatch(index))?.id);
|
||||
}, [matches])
|
||||
|
||||
|
||||
const GetCard = ({combId, match, cat}) => {
|
||||
const c = getHeightCardForCombInMatch(combId, match)
|
||||
if (!c)
|
||||
return <></>
|
||||
let bg = "";
|
||||
switch (c.type) {
|
||||
case "YELLOW":
|
||||
bg = " bg-warning";
|
||||
break;
|
||||
case "RED":
|
||||
bg = " bg-danger";
|
||||
break;
|
||||
case "BLACK":
|
||||
bg = " bg-dark text-white";
|
||||
break;
|
||||
case "BLUE":
|
||||
bg = " bg-primary text-white";
|
||||
break;
|
||||
}
|
||||
return <span
|
||||
className={"position-absolute top-0 start-100 translate-middle-y badge border border-light p-2" + bg +
|
||||
(c.match === match.id ? " rounded-circle" : (hasEffectCard(c, match.id, cat.id) ? "" : " bg-opacity-50"))}>
|
||||
<span className="visually-hidden">card</span></span>
|
||||
}
|
||||
|
||||
return <>
|
||||
{liceName.length > 1 &&
|
||||
<div className="input-group" style={{maxWidth: "15em", marginTop: "0.5em"}}>
|
||||
@ -273,7 +297,7 @@ function MatchList({matches, cat, menuActions}) {
|
||||
<tbody className="table-group-divider">
|
||||
{marches2.map((m, index) => (
|
||||
<tr key={m.id}
|
||||
className={m.id === activeMatch ? "table-info" : (isActiveMatch(index) ? "" : "table-warning")}
|
||||
className={m.id === activeMatch ? "table-primary" : (m.end ? "table-success" : (isActiveMatch(index) ? "" : "table-warning"))}
|
||||
onClick={() => setActiveMatch(m.id)}>
|
||||
<td style={{textAlign: "center", paddingLeft: "0.2em", paddingRight: "0.2em"}}>
|
||||
{liceName[(index - firstIndex) % liceName.length]}</td>
|
||||
@ -282,9 +306,11 @@ function MatchList({matches, cat, menuActions}) {
|
||||
{index >= firstIndex ? index + 1 - firstIndex : ""}</th>
|
||||
<td style={{textAlign: "right", paddingRight: "0"}}>{m.end && m.win > 0 && <CupImg/>}</td>
|
||||
<td style={{textAlign: "center", minWidth: "11em", paddingLeft: "0.2em"}}>
|
||||
<small><CombName combId={m.c1}/></small></td>
|
||||
<small className="position-relative"><CombName combId={m.c1}/>
|
||||
<GetCard match={m} combId={m.c1} cat={cat}/></small></td>
|
||||
<td style={{textAlign: "center", minWidth: "11em", paddingRight: "0.2em"}}>
|
||||
<small><CombName combId={m.c2}/></small></td>
|
||||
<small className="position-relative"><CombName combId={m.c2}/>
|
||||
<GetCard match={m} combId={m.c2} cat={cat}/></small></td>
|
||||
<td style={{textAlign: "left", paddingLeft: "0"}}>{m.end && m.win < 0 && <CupImg/>}</td>
|
||||
</tr>
|
||||
))}
|
||||
@ -302,6 +328,7 @@ function BuildTree({treeData, matches, menuActions}) {
|
||||
const [currentMatch, setCurrentMatch] = useState(null)
|
||||
const {getComb} = useCombs()
|
||||
const publicAffDispatch = usePubAffDispatch();
|
||||
const {cards_v} = useCards();
|
||||
|
||||
const match = matches.find(m => m.id === currentMatch?.matchSelect)
|
||||
useEffect(() => {
|
||||
@ -328,9 +355,20 @@ function BuildTree({treeData, matches, menuActions}) {
|
||||
const c1 = getComb(matchData?.c1)
|
||||
const c2 = 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
|
||||
})
|
||||
@ -362,7 +400,7 @@ function BuildTree({treeData, matches, menuActions}) {
|
||||
<div className="overflow-y-auto" style={{maxHeight: "50vh"}}>
|
||||
<div ref={scrollRef} className="overflow-x-auto" style={{position: "relative"}}>
|
||||
<DrawGraph root={trees} scrollRef={scrollRef} onMatchClick={onMatchClick} onClickVoid={onClickVoid}
|
||||
matchSelect={currentMatch?.matchSelect} matchNext={currentMatch?.matchNext} size={23}/>
|
||||
matchSelect={currentMatch?.matchSelect} matchNext={currentMatch?.matchNext} size={23} cards={cards_v}/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -371,301 +409,3 @@ function BuildTree({treeData, matches, menuActions}) {
|
||||
menuActions={menuActions}/></LoadingProvider>}
|
||||
</div>
|
||||
}
|
||||
|
||||
function ScorePanel({matchId, matchs, match, menuActions}) {
|
||||
const onClickVoid = useRef(() => {
|
||||
});
|
||||
|
||||
return <div className="row" onClick={onClickVoid.current}>
|
||||
<ScorePanel_ matchId={matchId} matchs={matchs} match={match} menuActions={menuActions} onClickVoid_={onClickVoid}/>
|
||||
<CardPanel matchId={matchId} match={match}/>
|
||||
</div>
|
||||
}
|
||||
|
||||
function ScorePanel_({matchId, matchs, match, menuActions, onClickVoid_}) {
|
||||
const {sendRequest} = useWS()
|
||||
const setLoading = useLoadingSwitcher()
|
||||
const {t} = useTranslation("cm");
|
||||
|
||||
const [end, setEnd] = useState(match?.end || false)
|
||||
const [scoreIn, setScoreIn] = useState("")
|
||||
const inputRef = useRef(null)
|
||||
const tableRef = useRef(null)
|
||||
const scoreRef = useRef([])
|
||||
const lastScoreClick = useRef(null)
|
||||
const scoreInRef = useRef(null)
|
||||
|
||||
useEffect(() => {
|
||||
scoreInRef.current = scoreIn;
|
||||
}, [scoreIn]);
|
||||
|
||||
useEffect(() => {
|
||||
menuActions.current.saveScore = (scoreRed, scoreBlue) => {
|
||||
const maxRound = (Math.max(...match.scores.map(s => s.n_round), -1) + 1) || 0;
|
||||
const newScore = {n_round: maxRound, s1: scoreRed, s2: scoreBlue};
|
||||
toast.promise(sendRequest('updateMatchScore', {matchId: matchId, ...newScore}), getToastMessage("toast.updateMatchScore", "cm"));
|
||||
}
|
||||
return () => menuActions.current.saveScore = undefined;
|
||||
}, [matchId])
|
||||
|
||||
const handleScoreClick = (e, round, comb) => {
|
||||
e.stopPropagation();
|
||||
const tableRect = tableRef.current.getBoundingClientRect();
|
||||
const rect = e.currentTarget.getBoundingClientRect();
|
||||
|
||||
updateScore();
|
||||
|
||||
const sel = inputRef.current;
|
||||
sel.style.top = (rect.y - tableRect.y) + "px";
|
||||
sel.style.left = (rect.x - tableRect.x) + "px";
|
||||
sel.style.width = rect.width + "px";
|
||||
sel.style.height = rect.height + "px";
|
||||
sel.style.display = "block";
|
||||
|
||||
if (round === -1) {
|
||||
const maxRound = (Math.max(...match.scores.map(s => s.n_round), -1) + 1) || 0;
|
||||
setScoreIn("");
|
||||
console.log("Setting for new round", maxRound);
|
||||
lastScoreClick.current = {matchId: matchId, round: maxRound, comb};
|
||||
} else {
|
||||
const score = match.scores.find(s => s.n_round === round);
|
||||
setScoreIn((comb === 1 ? score?.s1 : score?.s2) || "");
|
||||
lastScoreClick.current = {matchId: matchId, round, comb};
|
||||
setTimeout(() => inputRef.current.select(), 100);
|
||||
}
|
||||
}
|
||||
|
||||
const updateScore = () => {
|
||||
if (lastScoreClick.current !== null) {
|
||||
const {matchId, round, comb} = lastScoreClick.current;
|
||||
lastScoreClick.current = null;
|
||||
|
||||
const scoreIn_ = String(scoreInRef.current).trim() === "" ? -1000 : Number(scoreInRef.current);
|
||||
|
||||
const score = matchs.find(m => m.id === matchId).scores.find(s => s.n_round === round);
|
||||
|
||||
let newScore;
|
||||
if (score) {
|
||||
if (comb === 1)
|
||||
newScore = {...score, s1: scoreIn_};
|
||||
else
|
||||
newScore = {...score, s2: scoreIn_};
|
||||
|
||||
if (newScore.s1 === score?.s1 && newScore.s2 === score?.s2)
|
||||
return
|
||||
} else {
|
||||
newScore = {n_round: round, s1: (comb === 1 ? scoreIn_ : -1000), s2: (comb === 2 ? scoreIn_ : -1000)};
|
||||
if (newScore.s1 === -1000 && newScore.s2 === -1000)
|
||||
return
|
||||
}
|
||||
|
||||
setLoading(1)
|
||||
sendRequest('updateMatchScore', {matchId: matchId, ...newScore})
|
||||
.finally(() => {
|
||||
setLoading(0)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const onClickVoid = () => {
|
||||
updateScore();
|
||||
|
||||
const sel = inputRef.current;
|
||||
sel.style.display = "none";
|
||||
lastScoreClick.current = null;
|
||||
}
|
||||
onClickVoid_.current = onClickVoid;
|
||||
|
||||
useEffect(() => {
|
||||
if (!match || match?.end === end)
|
||||
return;
|
||||
|
||||
if (end) {
|
||||
if (win(match?.scores) === 0 && match.categorie_ord === -42) {
|
||||
toast.error(t('score.err1'));
|
||||
setEnd(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
setLoading(1)
|
||||
sendRequest('updateMatchEnd', {matchId: matchId, end})
|
||||
.finally(() => {
|
||||
setLoading(0)
|
||||
})
|
||||
}, [end]);
|
||||
|
||||
useEffect(() => {
|
||||
onClickVoid()
|
||||
}, [matchId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (match?.scores)
|
||||
scoreRef.current = scoreRef.current.slice(0, match.scores.length);
|
||||
}, [match?.scores]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!match)
|
||||
return;
|
||||
setEnd(match.end);
|
||||
}, [match]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event) => {
|
||||
if (inputRef.current && !inputRef.current.contains(event.target)) {
|
||||
onClickVoid();
|
||||
}
|
||||
};
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener("mousedown", handleClickOutside);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]')
|
||||
const o = [...tooltipTriggerList]
|
||||
o.map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl))
|
||||
|
||||
const tt = t('score.spe')
|
||||
|
||||
const maxRound = (match?.scores) ? (Math.max(...match.scores.map(s => s.n_round), -1) + 1) : 0;
|
||||
return <div ref={tableRef} className="col" style={{position: "relative"}}>
|
||||
<h6>{t('scores')} <FontAwesomeIcon icon={faCircleQuestion} role="button" data-bs-toggle="tooltip" data-bs-placement="right" data-bs-title={tt}
|
||||
data-bs-html="true"/></h6>
|
||||
<table className="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style={{textAlign: "center"}} scope="col">{t('manche')}</th>
|
||||
<th style={{textAlign: "center", minWidth: "4em"}} scope="col">{t('rouge')}</th>
|
||||
<th style={{textAlign: "center", minWidth: "4em"}} scope="col">{t('bleu')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="table-group-divider">
|
||||
{match?.scores && match.scores.sort((a, b) => a.n_round - b.n_round).map(score => (
|
||||
<tr key={score.n_round}>
|
||||
<th style={{textAlign: "center"}}>{score.n_round + 1}</th>
|
||||
<td style={{textAlign: "center"}} ref={e => scoreRef.current[score.n_round * 2] = e}
|
||||
onClick={e => handleScoreClick(e, score.n_round, 1)}>{scorePrint(score.s1)}</td>
|
||||
<td style={{textAlign: "center"}} ref={e => scoreRef.current[score.n_round * 2 + 1] = e}
|
||||
onClick={e => handleScoreClick(e, score.n_round, 2)}>{scorePrint(score.s2)}</td>
|
||||
</tr>
|
||||
))}
|
||||
<tr>
|
||||
<th style={{textAlign: "center"}}></th>
|
||||
<td style={{textAlign: "center"}} ref={e => scoreRef.current[maxRound * 2] = e} onClick={e => handleScoreClick(e, -1, 1)}>-</td>
|
||||
<td style={{textAlign: "center"}} ref={e => scoreRef.current[maxRound * 2 + 1] = e} onClick={e => handleScoreClick(e, -1, 2)}>-
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div style={{textAlign: "right"}}>
|
||||
<div className="form-check" style={{display: "inline-block"}}>
|
||||
<input className="form-check-input" type="checkbox" id="checkboxEnd" name="checkboxEnd" checked={end}
|
||||
onChange={e => setEnd(e.target.checked)}/>
|
||||
<label className="form-check-label" htmlFor="checkboxEnd">{t('terminé')}</label>
|
||||
</div>
|
||||
</div>
|
||||
<input ref={inputRef} type="number" className="form-control" style={{position: "absolute", top: 0, left: 0, display: "none"}} min="-999"
|
||||
max="999"
|
||||
value={scoreIn} onChange={e => setScoreIn(e.target.value)}
|
||||
onClick={e => e.stopPropagation()}
|
||||
onKeyDown={e => {
|
||||
if (e.key === "Tab") {
|
||||
if (lastScoreClick.current !== null) {
|
||||
const {round, comb} = lastScoreClick.current;
|
||||
const nextIndex = (round * 2 + (comb - 1)) + (e.shiftKey ? -1 : 1);
|
||||
if (nextIndex >= 0 && nextIndex < scoreRef.current.length) {
|
||||
e.preventDefault();
|
||||
scoreRef.current[nextIndex].click();
|
||||
}
|
||||
}
|
||||
} else if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
onClickVoid();
|
||||
}
|
||||
}}/>
|
||||
</div>
|
||||
}
|
||||
|
||||
function CardPanel({matchId, match}) {
|
||||
const {sendRequest, dispatch} = useWS();
|
||||
const setLoading = useLoadingSwitcher()
|
||||
|
||||
const {data, refresh} = useRequestWS('getCardboardWithoutThis', matchId, setLoading);
|
||||
|
||||
useEffect(() => {
|
||||
refresh('getCardboardWithoutThis', matchId);
|
||||
|
||||
const sendCardboard = ({data}) => {
|
||||
if (data.comb_id === match.c1 || data.comb_id === match.c2) {
|
||||
refresh('getCardboardWithoutThis', matchId);
|
||||
}
|
||||
}
|
||||
|
||||
dispatch({type: 'addListener', payload: {callback: sendCardboard, code: 'sendCardboard'}})
|
||||
return () => dispatch({type: 'removeListener', payload: sendCardboard})
|
||||
}, [matchId])
|
||||
|
||||
if (!match) {
|
||||
return <div className="col"></div>
|
||||
}
|
||||
|
||||
const c1Cards = match.cardboard?.find(c => c.comb_id === match.c1) || {red: 0, yellow: 0};
|
||||
const c2Cards = match.cardboard?.find(c => c.comb_id === match.c2) || {red: 0, yellow: 0};
|
||||
|
||||
const handleCard = (combId, yellow, red) => {
|
||||
if (combId === match.c1) {
|
||||
if (c1Cards.red + red < 0 || c1Cards.yellow + yellow < 0)
|
||||
return;
|
||||
} else if (combId === match.c2) {
|
||||
if (c2Cards.red + red < 0 || c2Cards.yellow + yellow < 0)
|
||||
return;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(1)
|
||||
sendRequest('sendCardboardChange', {matchId, combId, yellow, red})
|
||||
.finally(() => {
|
||||
setLoading(0)
|
||||
})
|
||||
}
|
||||
|
||||
return <div className="col">
|
||||
<h6>Carton</h6>
|
||||
<div className="bg-danger-subtle text-danger-emphasis" style={{padding: ".25em", borderRadius: "1em 1em 0 0"}}>
|
||||
<div>Competition: <span className="badge text-bg-danger">{(data?.c1_red || 0) + c1Cards.red}</span> <span
|
||||
className="badge text-bg-warning">{(data?.c1_yellow || 0) + c1Cards.yellow}</span></div>
|
||||
<div className="d-flex justify-content-center align-items-center" style={{margin: ".25em"}}>
|
||||
Match:
|
||||
<div className="d-flex flex-column" style={{marginLeft: ".25em"}}>
|
||||
<button className="col btn btn-xs btn-danger" onClick={__ => handleCard(match.c1, 0, +1)}>+</button>
|
||||
<span className="badge text-bg-danger">{c1Cards.red}</span>
|
||||
<button className="col btn btn-xs btn-danger" onClick={__ => handleCard(match.c1, 0, -1)}>-</button>
|
||||
</div>
|
||||
<div className="d-flex flex-column" style={{marginLeft: ".25em"}}>
|
||||
<button className="col btn btn-xs btn-warning" onClick={__ => handleCard(match.c1, +1, 0)}>+</button>
|
||||
<span className="badge text-bg-warning">{c1Cards.yellow}</span>
|
||||
<button className="col btn btn-xs btn-warning" onClick={__ => handleCard(match.c1, -1, 0)}>-</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-info-subtle text-info-emphasis" style={{padding: ".25em", borderRadius: "0 0 1em 1em"}}>
|
||||
<div>Competition: <span className="badge text-bg-danger">{(data?.c2_red || 0) + c2Cards.red}</span> <span
|
||||
className="badge text-bg-warning">{(data?.c2_yellow || 0) + c2Cards.yellow}</span></div>
|
||||
<div className="d-flex justify-content-center align-items-center" style={{margin: ".25em"}}>
|
||||
Match:
|
||||
<div className="d-flex flex-column" style={{marginLeft: ".25em"}}>
|
||||
<button className="col btn btn-xs btn-danger" onClick={__ => handleCard(match.c2, 0, +1)}>+</button>
|
||||
<span className="badge text-bg-danger">{c2Cards.red}</span>
|
||||
<button className="col btn btn-xs btn-danger" onClick={__ => handleCard(match.c2, 0, -1)}>-</button>
|
||||
</div>
|
||||
<div className="d-flex flex-column" style={{marginLeft: ".25em"}}>
|
||||
<button className="col btn btn-xs btn-warning" onClick={__ => handleCard(match.c2, +1, 0)}>+</button>
|
||||
<span className="badge text-bg-warning">{c2Cards.yellow}</span>
|
||||
<button className="col btn btn-xs btn-warning" onClick={__ => handleCard(match.c2, -1, 0)}>-</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@ -6,7 +6,7 @@ import {CombName, useCombs, useCombsDispatch} from "../../../hooks/useComb.jsx";
|
||||
import {from_sendTree, TreeNode} from "../../../utils/TreeUtils.js";
|
||||
import {DrawGraph} from "../../result/DrawGraph.jsx";
|
||||
import {SelectCombModalContent} from "./SelectCombModalContent.jsx";
|
||||
import {createMatch, scoreToString} from "../../../utils/CompetitionTools.js";
|
||||
import {createMatch, scoreToString2} from "../../../utils/CompetitionTools.js";
|
||||
|
||||
import {DndContext, closestCenter, KeyboardSensor, PointerSensor, useSensor, useSensors} from '@dnd-kit/core';
|
||||
import {SortableContext, sortableKeyboardCoordinates, verticalListSortingStrategy} from '@dnd-kit/sortable';
|
||||
@ -14,9 +14,12 @@ import {useSortable} from '@dnd-kit/sortable';
|
||||
import {CSS} from '@dnd-kit/utilities';
|
||||
import {toast} from "react-toastify";
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
import {faTrash} from "@fortawesome/free-solid-svg-icons";
|
||||
import {getToastMessage, win} from "../../../utils/Tools.js";
|
||||
import {faPen, faTrash} from "@fortawesome/free-solid-svg-icons";
|
||||
import {getToastMessage, virtualScore, win_end} from "../../../utils/Tools.js";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {hasEffectCard, useCards, useCardsDispatch} from "../../../hooks/useCard.jsx";
|
||||
|
||||
import {ScorePanel} from "./ScoreAndCardPanel.jsx";
|
||||
|
||||
const vite_url = import.meta.env.VITE_URL;
|
||||
|
||||
@ -29,6 +32,7 @@ function CupImg() {
|
||||
export function CategoryContent({cat, catId, setCat, menuActions}) {
|
||||
const setLoading = useLoadingSwitcher()
|
||||
const {sendRequest, dispatch} = useWS();
|
||||
const cardDispatch = useCardsDispatch();
|
||||
const [matches, reducer] = useReducer(MarchReducer, []);
|
||||
const [groups, setGroups] = useState([])
|
||||
const groupsRef = useRef(groups);
|
||||
@ -121,6 +125,8 @@ export function CategoryContent({cat, catId, setCat, menuActions}) {
|
||||
trees: data.trees.sort((a, b) => a.level - b.level).map(d => from_sendTree(d, true))
|
||||
})
|
||||
|
||||
cardDispatch({type: 'SET_ALL', payload: data.cards});
|
||||
|
||||
let matches2 = [];
|
||||
let combsToAdd = [];
|
||||
data.trees.flatMap(d => from_sendTree(d, false).flat()).forEach((data_) => readAndConvertMatch(matches2, data_, combsToAdd));
|
||||
@ -463,12 +469,19 @@ function MatchList({matches, cat, groups, reducer}) {
|
||||
const [combSelect, setCombSelect] = useState(0)
|
||||
const [combC1nm, setCombC1nm] = useState(null)
|
||||
const [combC2nm, setCombC2nm] = useState(null)
|
||||
const [modalMatchId, setModalMatchId] = useState(null)
|
||||
const {t} = useTranslation("cm");
|
||||
const {cards_v, getHeightCardForCombInMatch} = useCards();
|
||||
const matchModal = useRef(null);
|
||||
|
||||
const liceName = (cat.liceName || "N/A").split(";");
|
||||
const marches2 = matches.filter(m => m.categorie_ord !== -42)
|
||||
.sort((a, b) => a.categorie_ord - b.categorie_ord)
|
||||
.map(m => ({...m, win: win(m.scores)}))
|
||||
.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}];
|
||||
})
|
||||
|
||||
const sensors = useSensors(
|
||||
useSensor(PointerSensor),
|
||||
@ -568,6 +581,14 @@ function MatchList({matches, cat, groups, reducer}) {
|
||||
.finally(() => setLoading(0))
|
||||
}
|
||||
|
||||
const handleEditMatch = (matchId) => {
|
||||
const match = matches.find(m => m.id === matchId)
|
||||
if (!match)
|
||||
return;
|
||||
setModalMatchId (matchId);
|
||||
matchModal.current.click();
|
||||
}
|
||||
|
||||
const handleCombClick = (e, matchId, combId) => {
|
||||
e.stopPropagation();
|
||||
const tableRect = tableRef.current.getBoundingClientRect();
|
||||
@ -590,6 +611,31 @@ function MatchList({matches, cat, groups, reducer}) {
|
||||
lastMatchClick.current = null;
|
||||
}
|
||||
|
||||
const GetCard = ({combId, match, cat}) => {
|
||||
const c = getHeightCardForCombInMatch(combId, match)
|
||||
if (!c)
|
||||
return <></>
|
||||
let bg = "";
|
||||
switch (c.type) {
|
||||
case "YELLOW":
|
||||
bg = " bg-warning";
|
||||
break;
|
||||
case "RED":
|
||||
bg = " bg-danger";
|
||||
break;
|
||||
case "BLACK":
|
||||
bg = " bg-dark text-white";
|
||||
break;
|
||||
case "BLUE":
|
||||
bg = " bg-primary text-white";
|
||||
break;
|
||||
}
|
||||
return <span
|
||||
className={"position-absolute top-0 start-100 translate-middle-y badge border border-light p-2" + bg +
|
||||
(c.match === match.id ? " rounded-circle" : (hasEffectCard(c, match.id, cat.id) ? "" : " bg-opacity-50"))}>
|
||||
<span className="visually-hidden">card</span></span>
|
||||
}
|
||||
|
||||
const combsIDs = groups.map(m => m.id);
|
||||
return <div style={{position: "relative"}}>
|
||||
<DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
|
||||
@ -608,6 +654,7 @@ function MatchList({matches, cat, groups, reducer}) {
|
||||
<th style={{textAlign: "center"}} scope="col">{t('résultat')}</th>
|
||||
<th style={{textAlign: "center"}} scope="col"></th>
|
||||
<th style={{textAlign: "center"}} scope="col"></th>
|
||||
<th style={{textAlign: "center"}} scope="col"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="table-group-divider">
|
||||
@ -617,14 +664,16 @@ function MatchList({matches, cat, groups, reducer}) {
|
||||
<td style={{textAlign: "center", cursor: "auto"}}>{m.poule}</td>
|
||||
<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: "center", minWidth: "11em", paddingLeft: "0.2em"}}
|
||||
onClick={e => handleCombClick(e, m.id, m.c1)}>
|
||||
<small><CombName combId={m.c1}/></small></td>
|
||||
<td style={{textAlign: "center", minWidth: "11em", paddingRight: "0.2em"}}
|
||||
onClick={e => handleCombClick(e, m.id, m.c2)}>
|
||||
<small><CombName combId={m.c2}/></small></td>
|
||||
<td style={{textAlign: "center", minWidth: "11em", paddingLeft: "0.2em"}}>
|
||||
<small className="position-relative"><CombName combId={m.c1}/>
|
||||
<GetCard match={m} combId={m.c1} cat={cat}/></small></td>
|
||||
<td style={{textAlign: "center", minWidth: "11em", paddingRight: "0.2em"}}>
|
||||
<small className="position-relative"><CombName combId={m.c2}/>
|
||||
<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: "center", cursor: "auto"}}>{scoreToString(m.scores)}</td>
|
||||
<td style={{textAlign: "center", cursor: "auto"}}>{scoreToString2(m, cards_v)}</td>
|
||||
<td style={{textAlign: "center", cursor: "pointer", color: "#1381ff"}} onClick={_ => handleEditMatch(m.id)}>
|
||||
<FontAwesomeIcon icon={faPen}/></td>
|
||||
<td style={{textAlign: "center", cursor: "pointer", color: "#ff1313"}} onClick={_ => handleDelMatch(m.id)}>
|
||||
<FontAwesomeIcon icon={faTrash}/></td>
|
||||
<td style={{textAlign: "center", cursor: "grab"}}>☰</td>
|
||||
@ -659,7 +708,35 @@ function MatchList({matches, cat, groups, reducer}) {
|
||||
<option key={combId} value={combId}><CombName combId={combId}/></option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
<button ref={matchModal} type="button" style={{display: "none"}} data-bs-toggle="modal" data-bs-target="#editMatchModal">open</button>
|
||||
<div className="modal fade" id="editMatchModal" tabIndex="-1" aria-labelledby="editMatchModalLabel" aria-hidden="true">
|
||||
<div className="modal-dialog modal-dialog-scrollable modal-lg modal-fullscreen-lg-down">
|
||||
<div className="modal-content">
|
||||
<MatchEditModalContent matchId={modalMatchId} matches={matches}/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
function MatchEditModalContent({matchId, matches}) {
|
||||
const menuActionsLocal = useRef({});
|
||||
const match = matches.find(m => m.id === matchId)
|
||||
const {t} = useTranslation("cm");
|
||||
|
||||
return <>
|
||||
<div className="modal-header">
|
||||
<h1 className="modal-title fs-5" id="editMatchModalLabel">{t('editionDuMatch')}</h1>
|
||||
<button type="button" className="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div className="modal-body" style={{textAlign: "center"}}>
|
||||
<ScorePanel matchId={matchId} match={match} matchs={matches} menuActions={menuActionsLocal} admin={true}/>
|
||||
</div>
|
||||
<div className="modal-footer">
|
||||
<button type="button" className="btn btn-secondary" data-bs-dismiss="modal">{t('fermer')}</button>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
|
||||
function SortableRow({id, children}) {
|
||||
@ -701,6 +778,7 @@ function BuildTree({treeData, matches, groups}) {
|
||||
const {sendRequest} = useWS();
|
||||
const setLoading = useLoadingSwitcher()
|
||||
const {t} = useTranslation("cm");
|
||||
const {cards_v} = useCards();
|
||||
|
||||
function parseTree(data_in) {
|
||||
if (data_in?.data == null)
|
||||
@ -710,9 +788,19 @@ function BuildTree({treeData, matches, groups}) {
|
||||
const c1 = getComb(matchData?.c1)
|
||||
const c2 = 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
|
||||
})
|
||||
@ -789,7 +877,7 @@ function BuildTree({treeData, matches, groups}) {
|
||||
const combsIDs = groups.map(m => m.id);
|
||||
|
||||
return <div ref={scrollRef} className="overflow-x-auto" style={{position: "relative"}}>
|
||||
<DrawGraph root={initTree(treeData)} scrollRef={scrollRef} onMatchClick={onMatchClick} onClickVoid={onClickVoid}/>
|
||||
<DrawGraph root={initTree(treeData)} scrollRef={scrollRef} onMatchClick={onMatchClick} onClickVoid={onClickVoid} cards={cards_v}/>
|
||||
<select ref={selectRef} className="form-select" style={{position: "absolute", top: 0, left: 0, display: "none"}}
|
||||
value={combSelect} onChange={e => setCombSelect(Number(e.target.value))}>
|
||||
<option value={0}>{t('--SélectionnerUnCombattant--')}</option>
|
||||
|
||||
@ -10,6 +10,7 @@ import {ThreeDots} from "react-loader-spinner";
|
||||
import {AxiosError} from "../../../components/AxiosError.jsx";
|
||||
import {useFetch} from "../../../hooks/useFetch.js";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {CardsProvider} from "../../../hooks/useCard.jsx";
|
||||
|
||||
const vite_url = import.meta.env.VITE_URL;
|
||||
|
||||
@ -66,6 +67,7 @@ function HomeComp() {
|
||||
return <WSProvider url={`${vite_url.replace('http', 'ws')}/api/ws/competition/${compUuid}`} onmessage={messageHandler}>
|
||||
<WSStatus setPerm={setPerm}/>
|
||||
<CombsProvider>
|
||||
<CardsProvider>
|
||||
<LoadingProvider>
|
||||
<Routes>
|
||||
<Route path="/" element={<Home2 perm={perm}/>}/>
|
||||
@ -73,6 +75,7 @@ function HomeComp() {
|
||||
<Route path="/table" element={<CMTable/>}/>
|
||||
</Routes>
|
||||
</LoadingProvider>
|
||||
</CardsProvider>
|
||||
</CombsProvider>
|
||||
</WSProvider>
|
||||
}
|
||||
|
||||
@ -0,0 +1,384 @@
|
||||
import {useRequestWS, useWS} from "../../../hooks/useWS.jsx";
|
||||
import {useLoadingSwitcher} from "../../../hooks/useLoading.jsx";
|
||||
import {compareCardOrder, useCards, useCardsDispatch} from "../../../hooks/useCard.jsx";
|
||||
import React, {useEffect, useRef, useState} from "react";
|
||||
import {toast} from "react-toastify";
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
import {faCaretLeft} from "@fortawesome/free-solid-svg-icons";
|
||||
import {getToastMessage, scorePrint, virtual_end, virtualScore, win} from "../../../utils/Tools.js";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {faCircleQuestion} from "@fortawesome/free-regular-svg-icons";
|
||||
|
||||
export function ScorePanel({matchId, matchs, match, menuActions, admin = false}) {
|
||||
const {cards_v} = useCards();
|
||||
|
||||
const onClickVoid = useRef(() => {
|
||||
});
|
||||
|
||||
const vEnd = virtual_end(match, cards_v);
|
||||
return <div className="row" onClick={onClickVoid.current}>
|
||||
<ScorePanel_ matchId={matchId} matchs={matchs} match={match} menuActions={menuActions} onClickVoid_={onClickVoid} vEnd={vEnd}/>
|
||||
<CardPanel matchId={matchId} match={match} vEnd={admin ? false : vEnd} admin={admin}/>
|
||||
</div>
|
||||
}
|
||||
|
||||
function ScorePanel_({matchId, matchs, match, menuActions, onClickVoid_, vEnd}) {
|
||||
const {sendRequest} = useWS()
|
||||
const setLoading = useLoadingSwitcher()
|
||||
const {t} = useTranslation("cm");
|
||||
|
||||
const [end, setEnd] = useState(match?.end || false)
|
||||
const [scoreIn, setScoreIn] = useState("")
|
||||
const inputRef = useRef(null)
|
||||
const tableRef = useRef(null)
|
||||
const scoreRef = useRef([])
|
||||
const lastScoreClick = useRef(null)
|
||||
const scoreInRef = useRef(null)
|
||||
const {cards_v} = useCards();
|
||||
|
||||
useEffect(() => {
|
||||
scoreInRef.current = scoreIn;
|
||||
}, [scoreIn]);
|
||||
|
||||
useEffect(() => {
|
||||
menuActions.current.saveScore = (scoreRed, scoreBlue) => {
|
||||
const maxRound = (Math.max(...match.scores.map(s => s.n_round), -1) + 1) || 0;
|
||||
const newScore = {n_round: maxRound, s1: scoreRed, s2: scoreBlue};
|
||||
toast.promise(sendRequest('updateMatchScore', {matchId: matchId, ...newScore}), getToastMessage("toast.updateMatchScore", "cm"));
|
||||
}
|
||||
return () => menuActions.current.saveScore = undefined;
|
||||
}, [matchId])
|
||||
|
||||
const handleScoreClick = (e, round, comb) => {
|
||||
e.stopPropagation();
|
||||
|
||||
if (vEnd)
|
||||
return;
|
||||
|
||||
const tableRect = tableRef.current.getBoundingClientRect();
|
||||
const rect = e.currentTarget.getBoundingClientRect();
|
||||
|
||||
updateScore();
|
||||
|
||||
const sel = inputRef.current;
|
||||
sel.style.top = (rect.y - tableRect.y) + "px";
|
||||
sel.style.left = (rect.x - tableRect.x) + "px";
|
||||
sel.style.width = rect.width + "px";
|
||||
sel.style.height = rect.height + "px";
|
||||
sel.style.display = "block";
|
||||
|
||||
if (round === -1) {
|
||||
const maxRound = (Math.max(...match.scores.map(s => s.n_round), -1) + 1) || 0;
|
||||
setScoreIn("");
|
||||
console.log("Setting for new round", maxRound);
|
||||
lastScoreClick.current = {matchId: matchId, round: maxRound, comb};
|
||||
} else {
|
||||
const score = match.scores.find(s => s.n_round === round);
|
||||
setScoreIn((comb === 1 ? score?.s1 : score?.s2) || "");
|
||||
lastScoreClick.current = {matchId: matchId, round, comb};
|
||||
setTimeout(() => inputRef.current.select(), 100);
|
||||
}
|
||||
}
|
||||
|
||||
const updateScore = () => {
|
||||
if (lastScoreClick.current !== null) {
|
||||
const {matchId, round, comb} = lastScoreClick.current;
|
||||
lastScoreClick.current = null;
|
||||
|
||||
const scoreIn_ = String(scoreInRef.current).trim() === "" ? -1000 : Number(scoreInRef.current);
|
||||
|
||||
const score = matchs?.find(m => m.id === matchId)?.scores?.find(s => s.n_round === round);
|
||||
|
||||
let newScore;
|
||||
if (score) {
|
||||
if (comb === 1)
|
||||
newScore = {...score, s1: scoreIn_};
|
||||
else
|
||||
newScore = {...score, s2: scoreIn_};
|
||||
|
||||
if (newScore.s1 === score?.s1 && newScore.s2 === score?.s2)
|
||||
return
|
||||
} else {
|
||||
newScore = {n_round: round, s1: (comb === 1 ? scoreIn_ : -1000), s2: (comb === 2 ? scoreIn_ : -1000)};
|
||||
if (newScore.s1 === -1000 && newScore.s2 === -1000)
|
||||
return
|
||||
}
|
||||
|
||||
setLoading(1)
|
||||
sendRequest('updateMatchScore', {matchId: matchId, ...newScore})
|
||||
.finally(() => {
|
||||
setLoading(0)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const onClickVoid = () => {
|
||||
updateScore();
|
||||
|
||||
const sel = inputRef.current;
|
||||
sel.style.display = "none";
|
||||
lastScoreClick.current = null;
|
||||
}
|
||||
onClickVoid_.current = onClickVoid;
|
||||
|
||||
useEffect(() => {
|
||||
if (!match || match?.end === end)
|
||||
return;
|
||||
|
||||
if (end) {
|
||||
if (win(match) === 0 && match.categorie_ord === -42) {
|
||||
toast.error(t('score.err1'));
|
||||
setEnd(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
setLoading(1)
|
||||
sendRequest('updateMatchEnd', {matchId: matchId, end})
|
||||
.finally(() => {
|
||||
setLoading(0)
|
||||
})
|
||||
}, [end]);
|
||||
|
||||
useEffect(() => {
|
||||
onClickVoid()
|
||||
}, [matchId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (match?.scores)
|
||||
scoreRef.current = scoreRef.current.slice(0, match.scores.length);
|
||||
}, [match?.scores]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!match)
|
||||
return;
|
||||
setEnd(match.end);
|
||||
}, [match]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event) => {
|
||||
if (inputRef.current && !inputRef.current.contains(event.target)) {
|
||||
onClickVoid();
|
||||
}
|
||||
};
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener("mousedown", handleClickOutside);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]')
|
||||
const o = [...tooltipTriggerList]
|
||||
o.map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl))
|
||||
|
||||
const tt = t('score.spe')
|
||||
|
||||
const maxRound = (match?.scores) ? (Math.max(...match.scores.map(s => s.n_round), -1) + 1) : 0;
|
||||
return <div ref={tableRef} className="col" style={{position: "relative"}}>
|
||||
<h6>{t('scores')} <FontAwesomeIcon icon={faCircleQuestion} role="button" data-bs-toggle="tooltip" data-bs-placement="right" data-bs-title={tt}
|
||||
data-bs-html="true"/></h6>
|
||||
<table className="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style={{textAlign: "center"}} scope="col">{t('manche')}</th>
|
||||
<th style={{textAlign: "center", minWidth: "4em"}} scope="col">{t('rouge')}</th>
|
||||
<th style={{textAlign: "center", minWidth: "4em"}} scope="col">{t('bleu')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className={"table-group-divider" + (vEnd ? " table-secondary" : "")}>
|
||||
{match?.scores && match.scores.sort((a, b) => a.n_round - b.n_round).map(score => (
|
||||
<tr key={score.n_round}>
|
||||
<th style={{textAlign: "center"}}>{score.n_round + 1}</th>
|
||||
<td style={{textAlign: "center"}} ref={e => scoreRef.current[score.n_round * 2] = e}
|
||||
onClick={e => handleScoreClick(e, score.n_round, 1)}>{scorePrint(virtualScore(match.c1, score, match, cards_v))}</td>
|
||||
<td style={{textAlign: "center"}} ref={e => scoreRef.current[score.n_round * 2 + 1] = e}
|
||||
onClick={e => handleScoreClick(e, score.n_round, 2)}>{scorePrint(virtualScore(match.c2, score, match, cards_v))}</td>
|
||||
</tr>
|
||||
))}
|
||||
<tr>
|
||||
<th style={{textAlign: "center"}}></th>
|
||||
<td style={{textAlign: "center"}} ref={e => scoreRef.current[maxRound * 2] = e} onClick={e => handleScoreClick(e, -1, 1)}>-</td>
|
||||
<td style={{textAlign: "center"}} ref={e => scoreRef.current[maxRound * 2 + 1] = e} onClick={e => handleScoreClick(e, -1, 2)}>-
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div style={{textAlign: "right"}}>
|
||||
<div className="form-check" style={{display: "inline-block"}}>
|
||||
<input className="form-check-input" type="checkbox" id="checkboxEnd" name="checkboxEnd" checked={end || vEnd} disabled={vEnd}
|
||||
onChange={e => setEnd(e.target.checked)}/>
|
||||
<label className="form-check-label" htmlFor="checkboxEnd">{t('terminé')}</label>
|
||||
</div>
|
||||
</div>
|
||||
<input ref={inputRef} type="number" className="form-control" style={{position: "absolute", top: 0, left: 0, display: "none"}} min="-999"
|
||||
max="999"
|
||||
value={scoreIn} onChange={e => setScoreIn(e.target.value)}
|
||||
onClick={e => e.stopPropagation()}
|
||||
onKeyDown={e => {
|
||||
if (e.key === "Tab") {
|
||||
if (lastScoreClick.current !== null) {
|
||||
const {round, comb} = lastScoreClick.current;
|
||||
const nextIndex = (round * 2 + (comb - 1)) + (e.shiftKey ? -1 : 1);
|
||||
if (nextIndex >= 0 && nextIndex < scoreRef.current.length) {
|
||||
e.preventDefault();
|
||||
scoreRef.current[nextIndex].click();
|
||||
}
|
||||
}
|
||||
} else if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
onClickVoid();
|
||||
}
|
||||
}}/>
|
||||
</div>
|
||||
}
|
||||
|
||||
function CardPanel({matchId, match, vEnd, admin}) {
|
||||
const {sendRequest} = useWS();
|
||||
const setLoading = useLoadingSwitcher()
|
||||
const cardDispatch = useCardsDispatch();
|
||||
const {getCardInMatch, getHeightCardForCombInMatch} = useCards();
|
||||
const {t} = useTranslation("cm");
|
||||
|
||||
const {data, refresh} = useRequestWS('getCardForMatch', matchId, setLoading);
|
||||
useEffect(() => {
|
||||
refresh('getCardForMatch', matchId);
|
||||
}, [matchId])
|
||||
|
||||
useEffect(() => {
|
||||
if (!data)
|
||||
return;
|
||||
cardDispatch({type: 'SET_ALL', payload: data})
|
||||
}, [data])
|
||||
|
||||
if (!match) {
|
||||
return <div className="col"></div>
|
||||
}
|
||||
const handleCard = (combId, type) => {
|
||||
setLoading(1)
|
||||
sendRequest('sendCardAdd', {matchId, combId, type})
|
||||
.then(() => {
|
||||
toast.success(t('cardAdded'));
|
||||
})
|
||||
.catch(err => {
|
||||
toast.error(err);
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(0)
|
||||
})
|
||||
}
|
||||
|
||||
const handleCardRm = (combId, type) => {
|
||||
setLoading(1)
|
||||
sendRequest('sendCardRm', {matchId, combId, type})
|
||||
.then(() => {
|
||||
toast.success(t('cardRemoved'));
|
||||
})
|
||||
.catch(err => {
|
||||
toast.error(err);
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(0)
|
||||
})
|
||||
}
|
||||
|
||||
const cards = getCardInMatch(match)
|
||||
const MakeList = ({comb}) => {
|
||||
const card = getHeightCardForCombInMatch(comb, match);
|
||||
|
||||
return <div className="btn-group-vertical" role="group">
|
||||
<button type="button" className="btn btn-sm btn-primary position-relative"
|
||||
onClick={() => handleCard(comb, "BLUE")}
|
||||
disabled={card && compareCardOrder(card, {type: "BLUE"}) >= 0 || vEnd}>{t('avertissement')}
|
||||
<span className="position-absolute top-0 start-100 p-2 text-danger-emphasis"
|
||||
hidden={!cards.some(c => c.comb === comb && c.type === "BLUE")}>
|
||||
<FontAwesomeIcon icon={faCaretLeft}/></span>
|
||||
</button>
|
||||
<button type="button" className="btn btn-sm btn-warning position-relative"
|
||||
onClick={() => handleCard(comb, "YELLOW")}
|
||||
disabled={card && compareCardOrder(card, {type: "YELLOW"}) >= 0 || vEnd}>{t('cartonJaune')}
|
||||
<span className="position-absolute top-0 start-100 p-2 text-danger-emphasis"
|
||||
hidden={!cards.some(c => c.comb === comb && c.type === "YELLOW")}>
|
||||
<FontAwesomeIcon icon={faCaretLeft}/></span>
|
||||
</button>
|
||||
<button type="button" className="btn btn-sm btn-danger position-relative"
|
||||
onClick={() => handleCard(comb, "RED")}
|
||||
disabled={card && compareCardOrder(card, {type: "RED"}) >= 0 || vEnd}>{t('cartonRouge')}
|
||||
<span className="position-absolute top-0 start-100 p-2 text-danger-emphasis"
|
||||
hidden={!cards.some(c => c.comb === comb && c.type === "RED")}>
|
||||
<FontAwesomeIcon icon={faCaretLeft}/></span>
|
||||
</button>
|
||||
<button type="button" className="btn btn-sm btn-dark position-relative"
|
||||
onClick={() => handleCard(comb, "BLACK")}
|
||||
disabled={card?.type !== "RED" || vEnd}>{t('cartonNoir')}
|
||||
<span className="position-absolute top-0 start-100 p-2 text-danger-emphasis"
|
||||
hidden={!cards.some(c => c.comb === comb && c.type === "BLACK")}>
|
||||
<FontAwesomeIcon icon={faCaretLeft}/></span>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
|
||||
const MakeListAdmin = ({comb}) => {
|
||||
const card = getHeightCardForCombInMatch(comb, match);
|
||||
|
||||
return <div className="btn-group-vertical" role="group">
|
||||
<div className="btn-group" role="group">
|
||||
<button type="button" className="btn btn-sm btn-secondary">{t('ajouterUn')}</button>
|
||||
<button type="button" className="btn btn-sm btn-secondary">{t('supprimerUn')}</button>
|
||||
</div>
|
||||
<div className="btn-group" role="group">
|
||||
<button type="button" className="btn btn-sm btn-primary"
|
||||
onClick={() => handleCard(comb, "BLUE")}
|
||||
disabled={card && compareCardOrder(card, {type: "BLUE"}) >= 0 || vEnd}>{t('avertissement')}
|
||||
</button>
|
||||
<button type="button" className="btn btn-sm btn-primary"
|
||||
onClick={() => handleCardRm(comb, "BLUE")}
|
||||
disabled={!cards.some(c => c.comb === comb && c.type === "BLUE")}>{t('avertissement')}
|
||||
</button>
|
||||
</div>
|
||||
<div className="btn-group" role="group">
|
||||
<button type="button" className="btn btn-sm btn-warning"
|
||||
onClick={() => handleCard(comb, "YELLOW")}
|
||||
disabled={card && compareCardOrder(card, {type: "YELLOW"}) >= 0 || vEnd}>{t('cartonJaune')}
|
||||
</button>
|
||||
<button type="button" className="btn btn-sm btn-warning"
|
||||
onClick={() => handleCardRm(comb, "YELLOW")}
|
||||
disabled={!cards.some(c => c.comb === comb && c.type === "YELLOW")}>{t('cartonJaune')}
|
||||
</button>
|
||||
</div>
|
||||
<div className="btn-group" role="group">
|
||||
<button type="button" className="btn btn-sm btn-danger"
|
||||
onClick={() => handleCard(comb, "RED")}
|
||||
disabled={card && compareCardOrder(card, {type: "RED"}) >= 0 || vEnd}>{t('cartonRouge')}
|
||||
</button>
|
||||
<button type="button" className="btn btn-sm btn-danger"
|
||||
onClick={() => handleCardRm(comb, "RED")}
|
||||
disabled={!cards.some(c => c.comb === comb && c.type === "RED")}>{t('cartonRouge')}
|
||||
</button>
|
||||
</div>
|
||||
<div className="btn-group" role="group">
|
||||
<button type="button" className="btn btn-sm btn-dark"
|
||||
onClick={() => handleCard(comb, "BLACK")}
|
||||
disabled={card?.type !== "RED" || vEnd}>{t('cartonNoir')}
|
||||
</button>
|
||||
<button type="button" className="btn btn-sm btn-dark"
|
||||
onClick={() => handleCardRm(comb, "BLACK")}
|
||||
disabled={!cards.some(c => c.comb === comb && c.type === "BLACK")}>{t('cartonNoir')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
return <div className="col">
|
||||
<h6>Carton</h6>
|
||||
<div className="bg-danger-subtle text-danger-emphasis" style={{padding: ".25em", borderRadius: "1em 1em 0 0"}}>
|
||||
<h6>Combattant rouge</h6>
|
||||
{admin ? <MakeListAdmin comb={match.c1}/> :
|
||||
<><span>{t('ajouterUn')}</span><MakeList comb={match.c1}/></>}
|
||||
</div>
|
||||
<div className="bg-info-subtle text-info-emphasis" style={{padding: ".25em", borderRadius: "0 0 1em 1em"}}>
|
||||
<h6>Combattant bleu</h6>
|
||||
{admin ? <MakeListAdmin comb={match.c2}/> :
|
||||
<><span>{t('ajouterUn')}</span><MakeList comb={match.c2}/></>}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
import {useEffect, useRef} from "react";
|
||||
import {scorePrint, win} from "../../utils/Tools.js";
|
||||
import {scorePrint} from "../../utils/Tools.js";
|
||||
import {compareCardOrder, useCardsStatic} from "../../hooks/useCard.jsx";
|
||||
|
||||
const max_x = 500;
|
||||
|
||||
@ -20,12 +21,14 @@ export function DrawGraph({
|
||||
},
|
||||
matchSelect = null,
|
||||
matchNext = null,
|
||||
size = 24
|
||||
size = 24,
|
||||
cards = []
|
||||
}) {
|
||||
const canvasRef = useRef(null);
|
||||
const actionCanvasRef = useRef(null);
|
||||
const ctxARef = useRef(null);
|
||||
const actionMapRef = useRef({});
|
||||
const {getHeightCardForCombInMatch} = useCardsStatic(cards);
|
||||
|
||||
const selectColor = "#30cc30";
|
||||
|
||||
@ -149,6 +152,40 @@ export function DrawGraph({
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
const newColor = () => {
|
||||
const letters = '0123456789ABCDEF'
|
||||
let color
|
||||
@ -182,7 +219,9 @@ export function DrawGraph({
|
||||
|
||||
printScores(ctx, match.scores, px, py, 1);
|
||||
|
||||
|
||||
const pos = {x: px - size * 2 - size * 8, y: py - size - (size * 1.5 / 2 | 0), width: size * 8, height: (size * 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)
|
||||
ctxA.fillStyle = newColor()
|
||||
@ -190,6 +229,7 @@ export function DrawGraph({
|
||||
actionMapRef.current[ctxA.fillStyle] = {type: 'match', rect: pos, match: match.id, comb: 1}
|
||||
|
||||
const pos2 = {x: px - size * 2 - size * 8, y: py + size - (size * 1.5 / 2 | 0), width: size * 8, height: (size * 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)
|
||||
ctxA.fillStyle = newColor()
|
||||
@ -228,6 +268,7 @@ export function DrawGraph({
|
||||
printScores(ctx, match.scores, px, py, 1.5);
|
||||
|
||||
const pos = {x: px - size * 2 - size * 8, y: py - size * 2 * death - (size * 1.5 / 2 | 0), width: size * 8, height: (size * 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)
|
||||
ctxA.fillStyle = newColor()
|
||||
@ -235,6 +276,7 @@ export function DrawGraph({
|
||||
actionMapRef.current[ctxA.fillStyle] = {type: 'match', rect: pos, match: match.id, comb: 1}
|
||||
|
||||
const pos2 = {x: px - size * 2 - size * 8, y: py + size * 2 * death - (size * 1.5 / 2 | 0), width: size * 8, height: (size * 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)
|
||||
ctxA.fillStyle = newColor()
|
||||
@ -310,7 +352,7 @@ export function DrawGraph({
|
||||
for (const node of root) {
|
||||
let win_name = "";
|
||||
if (node.data.end) {
|
||||
win_name = win(node.data.scores) > 0
|
||||
win_name = node.data.win > 0
|
||||
? (node.data.c1FullName === null ? "???" : node.data.c1FullName)
|
||||
: (node.data.c2FullName === null ? "???" : node.data.c2FullName);
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import {scorePrint} from "./Tools.js";
|
||||
import {scorePrint, virtualScore} from "./Tools.js";
|
||||
|
||||
export function scoreToString(score) {
|
||||
if (score === null || score === undefined || score.length === 0)
|
||||
@ -10,6 +10,18 @@ export function scoreToString(score) {
|
||||
return score.sort((a, b) => a.n_round - b.n_round).map(o => scorePrint(o.s1) + "-" + scorePrint(o.s2)).join(" | ")
|
||||
}
|
||||
|
||||
export function scoreToString2(match, cards = []) {
|
||||
if (!match || match.scores === null || match.scores === undefined || match.scores.length === 0)
|
||||
return ""
|
||||
|
||||
if (Array.isArray(match.scores[0]))
|
||||
return match.scores.map(o => scorePrint(o.at(0)) + "-" + scorePrint(o.at(1))).join(" | ")
|
||||
else {
|
||||
return match.scores.sort((a, b) => a.n_round - b.n_round).map(o =>
|
||||
scorePrint(virtualScore(match.c1, o, match, cards)) + "-" + scorePrint(virtualScore(match.c2, o, match, cards))).join(" | ")
|
||||
}
|
||||
}
|
||||
|
||||
export function createMatch(category, matchs, groups) {
|
||||
const combs_2 = {}
|
||||
for (const group of groups) {
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import axios from "axios";
|
||||
import i18n from "../config/i18n.js";
|
||||
import {compareCardOrder, hasEffectCard} from "../hooks/useCard.jsx";
|
||||
|
||||
const vite_url = import.meta.env.VITE_URL;
|
||||
|
||||
@ -225,9 +226,18 @@ export function getToastMessage(msgKey, ns = 'common') {
|
||||
}
|
||||
}
|
||||
|
||||
export function win(scores) {
|
||||
export function win(match, cards = []) {
|
||||
const cards2 = cards.filter(c => (c.comb === match.c1 || c.comb === match.c2) && hasEffectCard(c, match.id, match.categorie)).sort(compareCardOrder).pop()
|
||||
|
||||
if (cards2) {
|
||||
if (cards2.comb === match.c1)
|
||||
return 1
|
||||
else
|
||||
return -1
|
||||
}
|
||||
|
||||
let sum = 0
|
||||
for (const score of scores) {
|
||||
for (const score of match?.scores) {
|
||||
if (score.s1 === -1000 || score.s2 === -1000) continue
|
||||
if (score.s1 > score.s2) sum++
|
||||
else if (score.s1 < score.s2) sum--
|
||||
@ -235,7 +245,62 @@ export function win(scores) {
|
||||
return sum
|
||||
}
|
||||
|
||||
export function win_end(match, cards = []) {
|
||||
const cards2 = cards?.filter(c => (c.comb === match.c1 || c.comb === match.c2) && hasEffectCard(c, match.id, match.categorie)).sort(compareCardOrder)
|
||||
|
||||
if (cards2.length > 1) {
|
||||
if (cards2[0].comb !== cards2[1].comb)
|
||||
return {win: 0, end: true}
|
||||
}
|
||||
if (cards2.length > 0) {
|
||||
if (cards2[0].comb === match.c1)
|
||||
return {win: -1, end: true}
|
||||
else
|
||||
return {win: 1, end: true}
|
||||
}
|
||||
|
||||
|
||||
let sum = 0
|
||||
for (const score of match?.scores) {
|
||||
if (score.s1 === -1000 || score.s2 === -1000) continue
|
||||
if (score.s1 > score.s2) sum++
|
||||
else if (score.s1 < score.s2) sum--
|
||||
}
|
||||
return {win: sum, end: match?.end}
|
||||
}
|
||||
|
||||
export function virtual_end(match, cards) {
|
||||
if (!cards)
|
||||
return false
|
||||
console.log(cards.filter(c => (c.comb === match?.c1 || c.comb === match?.c2) && hasEffectCard(c, match.id, match.categorie)))
|
||||
return cards.some(c => (c.comb === match?.c1 || c.comb === match?.c2) && hasEffectCard(c, match.id, match.categorie))
|
||||
}
|
||||
|
||||
export function virtualScore(combId, score, match, cards) {
|
||||
const cards2 = cards?.filter(c => (c.comb === match?.c1 || c.comb === match?.c2) && hasEffectCard(c, match.id, match.categorie)).sort(compareCardOrder)
|
||||
|
||||
if (cards2.length > 1) {
|
||||
if (cards2[0].comb !== cards2[1].comb)
|
||||
return -997
|
||||
}
|
||||
if (cards2.length > 0) {
|
||||
if (cards2[0].comb === combId)
|
||||
return -997
|
||||
else
|
||||
return 10
|
||||
}
|
||||
|
||||
if (combId === match?.c1) {
|
||||
return score.s1
|
||||
} else if (combId === match?.c2) {
|
||||
return score.s2
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
export function scorePrint(s1) {
|
||||
|
||||
switch (s1) {
|
||||
case -997:
|
||||
return "disc."
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user