feat: categorie on CM
This commit is contained in:
parent
b78b3f005b
commit
b1bcf75e56
@ -0,0 +1,42 @@
|
||||
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 = "cardboard")
|
||||
public class CardboardModel {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
Long id;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "comb", referencedColumnName = "id")
|
||||
MembreModel comb;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "guest_comb", referencedColumnName = "id")
|
||||
CompetitionGuestModel guestComb;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "match", referencedColumnName = "id")
|
||||
MatchModel match;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "compet", referencedColumnName = "id")
|
||||
CompetitionModel compet;
|
||||
|
||||
int red;
|
||||
int yellow;
|
||||
}
|
||||
@ -42,4 +42,6 @@ public class CategoryModel {
|
||||
List<TreeModel> tree;
|
||||
|
||||
Integer type;
|
||||
|
||||
String liceName = "1";
|
||||
}
|
||||
|
||||
@ -0,0 +1,48 @@
|
||||
package fr.titionfire.ffsaf.data.model;
|
||||
|
||||
import fr.titionfire.ffsaf.utils.Categorie;
|
||||
import fr.titionfire.ffsaf.utils.Genre;
|
||||
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 = "cardboard")
|
||||
public class CompetitionGuestModel {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
Long id;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "competition", referencedColumnName = "id")
|
||||
CompetitionModel competition;
|
||||
|
||||
String lname = "";
|
||||
String fname = "";
|
||||
|
||||
Categorie categorie = null;
|
||||
|
||||
String club = null;
|
||||
|
||||
Genre genre = null;
|
||||
|
||||
String country = "fr";
|
||||
|
||||
public CompetitionGuestModel(String s) {
|
||||
this.fname = s.substring(0, s.indexOf(" "));
|
||||
this.lname = s.substring(s.indexOf(" ") + 1);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return fname + " " + lname;
|
||||
}
|
||||
}
|
||||
@ -55,6 +55,10 @@ public class CompetitionModel {
|
||||
@OneToMany(mappedBy = "competition", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
|
||||
List<RegisterModel> insc;
|
||||
|
||||
@OneToMany(mappedBy = "competition", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
|
||||
List<CompetitionGuestModel> guests = new ArrayList<>();
|
||||
|
||||
|
||||
List<Long> banMembre = new ArrayList<>();
|
||||
|
||||
String owner;
|
||||
|
||||
@ -7,6 +7,7 @@ import jakarta.persistence.*;
|
||||
import lombok.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
@ -32,13 +33,17 @@ public class MatchModel {
|
||||
@JoinColumn(name = "c1", referencedColumnName = "id")
|
||||
MembreModel c1_id = null;
|
||||
|
||||
String c1_str = null;
|
||||
@ManyToOne(fetch = FetchType.EAGER)
|
||||
@JoinColumn(name = "c1_guest", referencedColumnName = "id")
|
||||
CompetitionGuestModel c1_guest = null;
|
||||
|
||||
@ManyToOne(fetch = FetchType.EAGER)
|
||||
@JoinColumn(name = "c2", referencedColumnName = "id")
|
||||
MembreModel c2_id = null;
|
||||
|
||||
String c2_str = null;
|
||||
@ManyToOne(fetch = FetchType.EAGER)
|
||||
@JoinColumn(name = "c2_guest", referencedColumnName = "id")
|
||||
CompetitionGuestModel c2_guest = null;
|
||||
|
||||
@ManyToOne(fetch = FetchType.EAGER)
|
||||
@JoinColumn(name = "id_category", referencedColumnName = "id")
|
||||
@ -48,22 +53,27 @@ public class MatchModel {
|
||||
|
||||
boolean isEnd = true;
|
||||
|
||||
Date date = null;
|
||||
|
||||
@ElementCollection(fetch = FetchType.EAGER)
|
||||
@CollectionTable(name = "score", joinColumns = @JoinColumn(name = "id_match"))
|
||||
List<ScoreEmbeddable> scores = new ArrayList<>();
|
||||
|
||||
char poule = 'A';
|
||||
|
||||
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
|
||||
@JoinColumn(name = "match", referencedColumnName = "id")
|
||||
List<CardboardModel> cardboard;
|
||||
|
||||
public String getC1Name() {
|
||||
if (c1_id == null)
|
||||
return c1_str;
|
||||
return c1_guest.fname + " " + c1_guest.lname;
|
||||
return c1_id.fname + " " + c1_id.lname;
|
||||
}
|
||||
|
||||
public String getC2Name() {
|
||||
if (c2_id == null)
|
||||
return c2_str;
|
||||
return c2_guest.fname + " " + c2_guest.lname;
|
||||
return c2_id.fname + " " + c2_id.lname;
|
||||
}
|
||||
|
||||
|
||||
@ -1,9 +1,19 @@
|
||||
package fr.titionfire.ffsaf.data.repository;
|
||||
|
||||
import fr.titionfire.ffsaf.data.model.CategoryModel;
|
||||
import fr.titionfire.ffsaf.utils.CompetitionSystem;
|
||||
import io.quarkus.hibernate.reactive.panache.Panache;
|
||||
import io.quarkus.hibernate.reactive.panache.PanacheRepositoryBase;
|
||||
import io.smallrye.mutiny.Uni;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
|
||||
@ApplicationScoped
|
||||
public class CategoryRepository implements PanacheRepositoryBase<CategoryModel, Long> {
|
||||
|
||||
public Uni<CategoryModel> create(CategoryModel categoryModel) {
|
||||
categoryModel.setSystem(CompetitionSystem.INTERNAL);
|
||||
return Panache.withTransaction(() -> this.persist(categoryModel)
|
||||
.invoke(categoryModel1 -> categoryModel1.setSystemId(categoryModel1.getId())))
|
||||
.chain(this::persist);
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
package fr.titionfire.ffsaf.data.repository;
|
||||
|
||||
import fr.titionfire.ffsaf.data.model.CompetitionGuestModel;
|
||||
import io.quarkus.hibernate.reactive.panache.PanacheRepositoryBase;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
|
||||
@ApplicationScoped
|
||||
public class CompetitionGuestRepository implements PanacheRepositoryBase<CompetitionGuestModel, Long> {
|
||||
}
|
||||
@ -1,9 +1,19 @@
|
||||
package fr.titionfire.ffsaf.data.repository;
|
||||
|
||||
import fr.titionfire.ffsaf.data.model.MatchModel;
|
||||
import fr.titionfire.ffsaf.utils.CompetitionSystem;
|
||||
import io.quarkus.hibernate.reactive.panache.Panache;
|
||||
import io.quarkus.hibernate.reactive.panache.PanacheRepositoryBase;
|
||||
import io.smallrye.mutiny.Uni;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
|
||||
@ApplicationScoped
|
||||
public class MatchRepository implements PanacheRepositoryBase<MatchModel, Long> {
|
||||
|
||||
public Uni<MatchModel> create(MatchModel matchModel) {
|
||||
matchModel.setSystem(CompetitionSystem.INTERNAL);
|
||||
return Panache.withTransaction(() -> this.persistAndFlush(matchModel)
|
||||
.invoke(matchModel1 -> matchModel1.setSystemId(matchModel1.getId())))
|
||||
.chain(this::persist);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,8 +2,22 @@ package fr.titionfire.ffsaf.data.repository;
|
||||
|
||||
import fr.titionfire.ffsaf.data.model.TreeModel;
|
||||
import io.quarkus.hibernate.reactive.panache.PanacheRepositoryBase;
|
||||
import io.quarkus.hibernate.reactive.panache.common.WithTransaction;
|
||||
import io.smallrye.mutiny.Uni;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
|
||||
@ApplicationScoped
|
||||
public class TreeRepository implements PanacheRepositoryBase<TreeModel, Long> {
|
||||
|
||||
@WithTransaction
|
||||
public Uni<Boolean> deleteTree(TreeModel entity) {
|
||||
Uni<Boolean> uni = Uni.createFrom().item(false);
|
||||
if (entity == null)
|
||||
return uni;
|
||||
if (entity.getLeft() != null)
|
||||
uni = uni.chain(__ -> this.deleteTree(entity.getLeft()));
|
||||
if (entity.getRight() != null)
|
||||
uni = uni.chain(__ -> this.deleteTree(entity.getRight()));
|
||||
return uni.chain(__ -> this.deleteById(entity.getId()));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,9 +1,6 @@
|
||||
package fr.titionfire.ffsaf.domain.service;
|
||||
|
||||
import fr.titionfire.ffsaf.data.model.MatchModel;
|
||||
import fr.titionfire.ffsaf.data.model.MembreModel;
|
||||
import fr.titionfire.ffsaf.data.model.CategoryModel;
|
||||
import fr.titionfire.ffsaf.data.model.TreeModel;
|
||||
import fr.titionfire.ffsaf.data.model.*;
|
||||
import fr.titionfire.ffsaf.data.repository.*;
|
||||
import fr.titionfire.ffsaf.rest.data.CategoryData;
|
||||
import fr.titionfire.ffsaf.rest.data.CategoryFullData;
|
||||
@ -45,10 +42,13 @@ public class CategoryService {
|
||||
@Inject
|
||||
CompetPermService permService;
|
||||
|
||||
@Inject
|
||||
CompetitionGuestRepository competitionGuestRepository;
|
||||
|
||||
public Uni<CategoryData> getByIdAdmin(SecurityCtx securityCtx, CompetitionSystem system, Long id) {
|
||||
return repository.find("systemId = ?1 AND system = ?2", id, system)
|
||||
.firstResult()
|
||||
.onItem().ifNull().failWith(() -> new RuntimeException("Poule not found"))
|
||||
.onItem().ifNull().failWith(() -> new RuntimeException("Category not found"))
|
||||
.call(data -> permService.hasAdminViewPerm(securityCtx, data.getCompet()))
|
||||
.map(CategoryData::fromModel);
|
||||
}
|
||||
@ -171,6 +171,15 @@ public class CategoryService {
|
||||
.invoke(o2 -> o2.forEach(m -> o.membres.put(m.getId(), m)))
|
||||
)
|
||||
)
|
||||
.call(o -> Mutiny.fetch(o.category.getCompet().getGuests())
|
||||
.invoke(o2 -> o2.forEach(m -> o.guest.put(m.getFname() + " " + m.getLname(), m)))
|
||||
.map(o2 -> data.getMatches().stream().flatMap(m -> Stream.of(m.getC1_str(), m.getC2_str())
|
||||
.filter(Objects::nonNull)).distinct().filter(s -> !o.guest.containsKey(s)).map(
|
||||
CompetitionGuestModel::new).toList())
|
||||
.call(o3 -> o3.isEmpty() ? Uni.createFrom().nullItem() :
|
||||
Uni.join().all(o3.stream().map(o4 -> competitionGuestRepository.persist(o4)).toList())
|
||||
.andFailFast())
|
||||
.invoke(o2 -> o2.forEach(m -> o.guest.put(m.getFname() + " " + m.getLname(), m))))
|
||||
.invoke(in -> {
|
||||
ArrayList<TreeModel> node = new ArrayList<>();
|
||||
for (TreeModel treeModel : in.category.getTree())
|
||||
@ -214,8 +223,8 @@ public class CategoryService {
|
||||
}
|
||||
mm.setCategory(in.category);
|
||||
mm.setCategory_ord(m.getCategory_ord());
|
||||
mm.setC1_str(m.getC1_str());
|
||||
mm.setC2_str(m.getC2_str());
|
||||
mm.setC1_guest(in.guest.getOrDefault(m.getC1_str(), null));
|
||||
mm.setC2_guest(in.guest.getOrDefault(m.getC2_str(), null));
|
||||
mm.setC1_id(in.membres.getOrDefault(m.getC1_id(), null));
|
||||
mm.setC2_id(in.membres.getOrDefault(m.getC2_id(), null));
|
||||
mm.setEnd(m.isEnd());
|
||||
@ -238,6 +247,7 @@ public class CategoryService {
|
||||
private static class WorkData {
|
||||
CategoryModel category;
|
||||
HashMap<Long, MembreModel> membres = new HashMap<>();
|
||||
HashMap<String, CompetitionGuestModel> guest = new HashMap<>();
|
||||
List<MatchModel> match = new ArrayList<>();
|
||||
List<Long> toRmMatch;
|
||||
List<TreeModel> unlinkNode;
|
||||
@ -246,7 +256,7 @@ public class CategoryService {
|
||||
|
||||
public Uni<?> delete(SecurityCtx securityCtx, CompetitionSystem system, Long id) {
|
||||
return repository.find("systemId = ?1 AND system = ?2", id, system).firstResult()
|
||||
.onItem().ifNull().failWith(() -> new RuntimeException("Poule not found"))
|
||||
.onItem().ifNull().failWith(() -> new RuntimeException("Category not found"))
|
||||
.call(o -> permService.hasEditPerm(securityCtx, o.getCompet()))
|
||||
.call(o -> Mutiny.fetch(o.getTree())
|
||||
.call(o2 -> o2.isEmpty() ? Uni.createFrom().nullItem() :
|
||||
@ -260,7 +270,7 @@ public class CategoryService {
|
||||
Panache.withTransaction(() -> treeRepository.delete("id IN ?1", in)))
|
||||
)
|
||||
)
|
||||
.call(o -> matchRepository.delete("poule.id = ?1", o.getId()))
|
||||
.call(o -> matchRepository.delete("category.id = ?1", o.getId()))
|
||||
.chain(model -> Panache.withTransaction(() -> repository.delete("id", model.getId())));
|
||||
}
|
||||
}
|
||||
|
||||
@ -89,7 +89,7 @@ public class CompetPermService {
|
||||
.onFailure().call(throwable -> cacheAccess.invalidate(securityCtx.getSubject()));
|
||||
|
||||
Uni<HashMap<Long, String>> none = cacheNoneAccess.getAsync(securityCtx.getSubject(),
|
||||
k -> competitionRepository.list("system = ?1", CompetitionSystem.NONE)
|
||||
k -> competitionRepository.list("system = ?1", CompetitionSystem.INTERNAL)
|
||||
.map(competitionModels -> {
|
||||
HashMap<Long, String> map = new HashMap<>();
|
||||
for (CompetitionModel model : competitionModels) {
|
||||
@ -184,7 +184,7 @@ public class CompetPermService {
|
||||
if (!securityCtx.isInClubGroup(o.getClub().getId())) // Only membre club pass here
|
||||
throw new DForbiddenException();
|
||||
|
||||
if (o.getSystem() == CompetitionSystem.NONE)
|
||||
if (o.getSystem() == CompetitionSystem.INTERNAL)
|
||||
if (securityCtx.roleHas("club_president") || securityCtx.roleHas("club_respo_intra")
|
||||
|| securityCtx.roleHas("club_secretaire") || securityCtx.roleHas("club_tresorier"))
|
||||
return Uni.createFrom().nullItem();
|
||||
@ -219,7 +219,7 @@ public class CompetPermService {
|
||||
if (o.getSystem() == CompetitionSystem.SAFCA)
|
||||
return hasSafcaEditPerm(securityCtx, o.getId());
|
||||
|
||||
if (o.getSystem() == CompetitionSystem.NONE) {
|
||||
if (o.getSystem() == CompetitionSystem.INTERNAL) {
|
||||
if (securityCtx.isInClubGroup(o.getClub().getId()) && securityCtx.isClubAdmin())
|
||||
return Uni.createFrom().nullItem();
|
||||
|
||||
@ -259,7 +259,7 @@ public class CompetPermService {
|
||||
if (o.getSystem() == CompetitionSystem.SAFCA)
|
||||
return hasSafcaTablePerm(securityCtx, o.getId());
|
||||
|
||||
if (o.getSystem() == CompetitionSystem.NONE) {
|
||||
if (o.getSystem() == CompetitionSystem.INTERNAL) {
|
||||
if (securityCtx.isInClubGroup(o.getClub().getId()) && securityCtx.isClubAdmin())
|
||||
return Uni.createFrom().nullItem();
|
||||
|
||||
|
||||
@ -103,7 +103,7 @@ public class CompetitionService {
|
||||
if (id == 0) {
|
||||
return Uni.createFrom()
|
||||
.item(new CompetitionData(null, "", "", "", "", new Date(), new Date(),
|
||||
CompetitionSystem.NONE, RegisterMode.FREE, new Date(), new Date(), true,
|
||||
CompetitionSystem.INTERNAL, RegisterMode.FREE, new Date(), new Date(), true,
|
||||
null, "", "", null, true, "", "", "", ""));
|
||||
}
|
||||
return permService.hasAdminViewPerm(securityCtx, id)
|
||||
@ -184,7 +184,7 @@ public class CompetitionService {
|
||||
}).map(CompetitionData::fromModel)
|
||||
.call(c -> (c.getSystem() == CompetitionSystem.SAFCA) ? cacheAccess.invalidate(
|
||||
securityCtx.getSubject()) : Uni.createFrom().nullItem())
|
||||
.call(c -> (c.getSystem() == CompetitionSystem.NONE) ? cacheNoneAccess.invalidate(
|
||||
.call(c -> (c.getSystem() == CompetitionSystem.INTERNAL) ? cacheNoneAccess.invalidate(
|
||||
securityCtx.getSubject()) : Uni.createFrom().nullItem());
|
||||
} else {
|
||||
return permService.hasEditPerm(securityCtx, data.getId())
|
||||
@ -208,7 +208,7 @@ public class CompetitionService {
|
||||
}).map(CompetitionData::fromModel)
|
||||
.call(c -> (c.getSystem() == CompetitionSystem.SAFCA) ? cacheAccess.invalidate(
|
||||
securityCtx.getSubject()) : Uni.createFrom().nullItem())
|
||||
.call(c -> (c.getSystem() == CompetitionSystem.NONE) ? cacheNoneAccess.invalidate(
|
||||
.call(c -> (c.getSystem() == CompetitionSystem.INTERNAL) ? cacheNoneAccess.invalidate(
|
||||
securityCtx.getSubject()) : Uni.createFrom().nullItem());
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
package fr.titionfire.ffsaf.domain.service;
|
||||
|
||||
import fr.titionfire.ffsaf.data.model.CompetitionGuestModel;
|
||||
import fr.titionfire.ffsaf.data.model.MatchModel;
|
||||
import fr.titionfire.ffsaf.data.repository.CombRepository;
|
||||
import fr.titionfire.ffsaf.data.repository.MatchRepository;
|
||||
import fr.titionfire.ffsaf.data.repository.CategoryRepository;
|
||||
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.rest.data.MatchData;
|
||||
import fr.titionfire.ffsaf.rest.exception.DNotFoundException;
|
||||
import fr.titionfire.ffsaf.utils.CompetitionSystem;
|
||||
@ -33,6 +35,9 @@ public class MatchService {
|
||||
@Inject
|
||||
CompetPermService permService;
|
||||
|
||||
@Inject
|
||||
CompetitionGuestRepository competitionGuestRepository;
|
||||
|
||||
public Uni<MatchData> getByIdAdmin(SecurityCtx securityCtx, CompetitionSystem system, Long id) {
|
||||
return repository.find("systemId = ?1 AND system = ?2", id, system).firstResult()
|
||||
.onItem().ifNull().failWith(() -> new DNotFoundException("Match not found"))
|
||||
@ -75,8 +80,6 @@ public class MatchService {
|
||||
}
|
||||
)
|
||||
.chain(o -> {
|
||||
o.setC1_str(data.getC1_str());
|
||||
o.setC2_str(data.getC2_str());
|
||||
o.setCategory_ord(data.getCategory_ord());
|
||||
o.getScores().clear();
|
||||
o.getScores().addAll(data.getScores());
|
||||
@ -88,6 +91,20 @@ public class MatchService {
|
||||
.chain(() -> (data.getC1_id() == null) ?
|
||||
Uni.createFrom().nullItem() : combRepository.findById(data.getC2_id()))
|
||||
.invoke(o::setC2_id)
|
||||
.chain(() -> (data.getC1_str() == null) ?
|
||||
Uni.createFrom()
|
||||
.item((CompetitionGuestModel) null) : competitionGuestRepository.find(
|
||||
"fname = ?1 AND lname = ?2",
|
||||
data.getC1_str().substring(0, data.getC1_str().indexOf(" ")),
|
||||
data.getC1_str().substring(data.getC1_str().indexOf(" ") + 1)).firstResult())
|
||||
.invoke(o::setC1_guest)
|
||||
.chain(() -> (data.getC2_str() == null) ?
|
||||
Uni.createFrom()
|
||||
.item((CompetitionGuestModel) null) : competitionGuestRepository.find(
|
||||
"fname = ?1 AND lname = ?2",
|
||||
data.getC2_str().substring(0, data.getC2_str().indexOf(" ")),
|
||||
data.getC2_str().substring(data.getC2_str().indexOf(" ") + 1)).firstResult())
|
||||
.invoke(o::setC2_guest)
|
||||
.chain(() -> Panache.withTransaction(() -> repository.persist(o)));
|
||||
})
|
||||
.map(MatchData::fromModel);
|
||||
|
||||
@ -0,0 +1,23 @@
|
||||
package fr.titionfire.ffsaf.net2.data;
|
||||
|
||||
import fr.titionfire.ffsaf.data.model.CardboardModel;
|
||||
import io.quarkus.runtime.annotations.RegisterForReflection;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@RegisterForReflection
|
||||
public class CardboardEntity {
|
||||
long comb_id;
|
||||
long match_id;
|
||||
long compet_id;
|
||||
|
||||
int red;
|
||||
int yellow;
|
||||
|
||||
public static CardboardEntity fromModel(CardboardModel model) {
|
||||
return new CardboardEntity(model.getComb().getId(), model.getMatch().getId(), model.getCompet().getId(),
|
||||
model.getRed(), model.getYellow());
|
||||
}
|
||||
}
|
||||
40
src/main/java/fr/titionfire/ffsaf/net2/data/CombEntity.java
Normal file
40
src/main/java/fr/titionfire/ffsaf/net2/data/CombEntity.java
Normal file
@ -0,0 +1,40 @@
|
||||
package fr.titionfire.ffsaf.net2.data;
|
||||
|
||||
import fr.titionfire.ffsaf.data.model.CompetitionGuestModel;
|
||||
import fr.titionfire.ffsaf.data.model.MembreModel;
|
||||
import fr.titionfire.ffsaf.utils.Categorie;
|
||||
import fr.titionfire.ffsaf.utils.Genre;
|
||||
import io.quarkus.runtime.annotations.RegisterForReflection;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@RegisterForReflection
|
||||
public class CombEntity {
|
||||
private long id;
|
||||
private String lname = "";
|
||||
private String fname = "";
|
||||
Categorie categorie = null;
|
||||
Long club = null;
|
||||
String club_str = null;
|
||||
Genre genre = null;
|
||||
String country = "fr";
|
||||
|
||||
public static CombEntity fromModel(MembreModel model) {
|
||||
if (model == null)
|
||||
return null;
|
||||
|
||||
return new CombEntity(model.getId(), model.getLname(), model.getFname(), model.getCategorie(),
|
||||
model.getClub().getId(), model.getClub().getName(), model.getGenre(), model.getCountry());
|
||||
}
|
||||
|
||||
|
||||
public static CombEntity fromModel(CompetitionGuestModel model) {
|
||||
if (model == null)
|
||||
return null;
|
||||
|
||||
return new CombEntity(model.getId() * -1, model.getLname(), model.getFname(), model.getCategorie(), null,
|
||||
model.getClub(), model.getGenre(), model.getCountry());
|
||||
}
|
||||
}
|
||||
56
src/main/java/fr/titionfire/ffsaf/net2/data/MatchEntity.java
Normal file
56
src/main/java/fr/titionfire/ffsaf/net2/data/MatchEntity.java
Normal file
@ -0,0 +1,56 @@
|
||||
package fr.titionfire.ffsaf.net2.data;
|
||||
|
||||
import fr.titionfire.ffsaf.data.model.MatchModel;
|
||||
import fr.titionfire.ffsaf.utils.ScoreEmbeddable;
|
||||
import io.quarkus.runtime.annotations.RegisterForReflection;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@RegisterForReflection
|
||||
public class MatchEntity {
|
||||
private long id;
|
||||
private CombEntity c1;
|
||||
private CombEntity c2;
|
||||
private long categorie_ord = 0;
|
||||
private boolean isEnd;
|
||||
private long categorie;
|
||||
private Date date;
|
||||
private List<ScoreEmbeddable> scores;
|
||||
private char poule;
|
||||
private List<CardboardEntity> cardboard;
|
||||
|
||||
public static MatchEntity fromModel(MatchModel model) {
|
||||
if (model == null)
|
||||
return null;
|
||||
return new MatchEntity(model.getId(),
|
||||
(model.getC1_id() == null) ? CombEntity.fromModel(model.getC1_guest()) : CombEntity.fromModel(
|
||||
model.getC1_id()),
|
||||
(model.getC2_id() == null) ? CombEntity.fromModel(model.getC2_guest()) : CombEntity.fromModel(
|
||||
model.getC2_id()),
|
||||
model.getCategory_ord(), model.isEnd(), model.getCategory().getId(), model.getDate(),
|
||||
model.getScores(),
|
||||
model.getPoule(),
|
||||
(model.getCardboard() == null) ? new ArrayList<>() : model.getCardboard().stream()
|
||||
.map(CardboardEntity::fromModel).toList());
|
||||
}
|
||||
|
||||
public int win() {
|
||||
int sum = 0;
|
||||
for (ScoreEmbeddable score : scores) {
|
||||
if (score.getS1() == -1000 || score.getS2() == -1000)
|
||||
continue;
|
||||
|
||||
if (score.getS1() > score.getS2())
|
||||
sum++;
|
||||
else if (score.getS1() < score.getS2())
|
||||
sum--;
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
}
|
||||
71
src/main/java/fr/titionfire/ffsaf/net2/data/TreeEntity.java
Normal file
71
src/main/java/fr/titionfire/ffsaf/net2/data/TreeEntity.java
Normal file
@ -0,0 +1,71 @@
|
||||
package fr.titionfire.ffsaf.net2.data;
|
||||
|
||||
import fr.titionfire.ffsaf.data.model.TreeModel;
|
||||
import io.quarkus.runtime.annotations.RegisterForReflection;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@RegisterForReflection
|
||||
public class TreeEntity {
|
||||
private Long id;
|
||||
private Long categorie;
|
||||
private Integer level;
|
||||
private MatchEntity match;
|
||||
private TreeEntity left;
|
||||
private TreeEntity right;
|
||||
private TreeEntity associatedNode;
|
||||
|
||||
public static TreeEntity fromModel(TreeModel model) {
|
||||
if (model == null)
|
||||
return null;
|
||||
|
||||
return new TreeEntity(model.getId(), model.getCategory(), model.getLevel(), MatchEntity.fromModel(model.getMatch()), fromModel(model.getLeft()),
|
||||
fromModel(model.getRight()), null);
|
||||
}
|
||||
|
||||
public TreeEntity getMatchNode(Long matchId) {
|
||||
if (this.match != null && this.match.getId() == matchId) {
|
||||
return this;
|
||||
} else {
|
||||
if (this.left != null) {
|
||||
TreeEntity left = this.left.getMatchNode(matchId);
|
||||
if (left != null) {
|
||||
return left;
|
||||
}
|
||||
}
|
||||
if (this.right != null) {
|
||||
TreeEntity right = this.right.getMatchNode(matchId);
|
||||
if (right != null) {
|
||||
return right;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static TreeEntity getParent(TreeEntity current, TreeEntity target) {
|
||||
if (current == null) {
|
||||
return null;
|
||||
} else if (current.equals(target)) {
|
||||
return null;
|
||||
} else if (target.equals(current.left) || target.equals(current.right)) {
|
||||
return current;
|
||||
} else {
|
||||
TreeEntity left = getParent(current.left, target);
|
||||
if (left != null)
|
||||
return left;
|
||||
return getParent(current.right, target);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setAssociated(TreeEntity current, TreeEntity next) {
|
||||
if (current == null || next == null) {
|
||||
return;
|
||||
}
|
||||
current.setAssociatedNode(next);
|
||||
setAssociated(current.getLeft(), next.getLeft());
|
||||
setAssociated(current.getRight(), next.getRight());
|
||||
}
|
||||
}
|
||||
@ -28,8 +28,10 @@ public class MatchData {
|
||||
return null;
|
||||
|
||||
return new MatchData(model.getSystemId(),
|
||||
(model.getC1_id() == null) ? null : model.getC1_id().getId(), model.getC1_str(),
|
||||
(model.getC2_id() == null) ? null : model.getC2_id().getId(), model.getC2_str(),
|
||||
(model.getC1_id() == null) ? null : model.getC1_id().getId(),
|
||||
(model.getC1_guest() == null) ? null : model.getC1_guest().getName(),
|
||||
(model.getC2_id() == null) ? null : model.getC2_id().getId(),
|
||||
(model.getC2_guest() == null) ? null : model.getC2_guest().getName(),
|
||||
model.getCategory().getId(), model.getCategory_ord(), model.isEnd(), model.getPoule(),
|
||||
model.getScores());
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
package fr.titionfire.ffsaf.utils;
|
||||
|
||||
public enum CompetitionSystem {
|
||||
SAFCA, NONE
|
||||
SAFCA, INTERNAL
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ import fr.titionfire.ffsaf.domain.service.CompetPermService;
|
||||
import fr.titionfire.ffsaf.net2.MessageType;
|
||||
import fr.titionfire.ffsaf.utils.SecurityCtx;
|
||||
import fr.titionfire.ffsaf.ws.data.WelcomeInfo;
|
||||
import fr.titionfire.ffsaf.ws.recv.RCategorie;
|
||||
import fr.titionfire.ffsaf.ws.recv.RMatch;
|
||||
import fr.titionfire.ffsaf.ws.recv.WSReceiver;
|
||||
import fr.titionfire.ffsaf.ws.send.JsonUni;
|
||||
@ -22,9 +23,7 @@ import org.jboss.logging.Logger;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.*;
|
||||
|
||||
import static fr.titionfire.ffsaf.net2.Client_Thread.MAPPER;
|
||||
|
||||
@ -38,6 +37,9 @@ public class CompetitionWS {
|
||||
@Inject
|
||||
RMatch rMatch;
|
||||
|
||||
@Inject
|
||||
RCategorie rCategorie;
|
||||
|
||||
@Inject
|
||||
SecurityCtx securityCtx;
|
||||
|
||||
@ -69,6 +71,7 @@ public class CompetitionWS {
|
||||
@PostConstruct
|
||||
void init() {
|
||||
getWSReceiverMethods(RMatch.class, rMatch);
|
||||
getWSReceiverMethods(RCategorie.class, rCategorie);
|
||||
}
|
||||
|
||||
@OnOpen
|
||||
@ -82,12 +85,12 @@ public class CompetitionWS {
|
||||
if (cm == null)
|
||||
throw new ForbiddenException();
|
||||
}))
|
||||
.call(cm -> competPermService.hasEditPerm(securityCtx, cm).map(__ -> "admin")
|
||||
.call(cm -> competPermService.hasEditPerm(securityCtx, cm).map(__ -> PermLevel.ADMIN)
|
||||
.onFailure()
|
||||
.recoverWithUni(competPermService.hasTablePerm(securityCtx, cm).map(__ -> "table"))
|
||||
.recoverWithUni(competPermService.hasTablePerm(securityCtx, cm).map(__ -> PermLevel.TABLE))
|
||||
.onFailure()
|
||||
.recoverWithUni(competPermService.hasViewPerm(securityCtx, cm).map(__ -> "view"))
|
||||
.invoke(prem -> connection.userData().put(UserData.TypedKey.forString("prem"), prem))
|
||||
.recoverWithUni(competPermService.hasViewPerm(securityCtx, cm).map(__ -> PermLevel.VIEW))
|
||||
.invoke(prem -> connection.userData().put(UserData.TypedKey.forString("prem"), prem.toString()))
|
||||
.invoke(prem -> LOGGER.infof("Connection permission: %s", prem))
|
||||
.onFailure().transform(t -> new ForbiddenException()))
|
||||
.invoke(__ -> {
|
||||
@ -150,12 +153,19 @@ public class CompetitionWS {
|
||||
try {
|
||||
for (Map.Entry<Method, Object> entry : wsMethods.entrySet()) {
|
||||
Method method = entry.getKey();
|
||||
if (method.getAnnotation(WSReceiver.class).code().equalsIgnoreCase(message.code())) {
|
||||
WSReceiver wsReceiver = method.getAnnotation(WSReceiver.class);
|
||||
PermLevel perm = PermLevel.valueOf(connection.userData().get(UserData.TypedKey.forString("prem")));
|
||||
if (wsReceiver.code().equalsIgnoreCase(message.code())) {
|
||||
if (wsReceiver.permission().ordinal() > perm.ordinal())
|
||||
return Uni.createFrom().item(makeError(message, "Permission denied")).toMulti();
|
||||
return ((Uni<?>) method.invoke(entry.getValue(), connection,
|
||||
MAPPER.treeToValue(message.data(), method.getParameterTypes()[1])))
|
||||
.map(o -> makeReply(message, o))
|
||||
.onFailure()
|
||||
.recoverWithItem(t -> makeError(message, t.getMessage())).toMulti()
|
||||
.recoverWithItem(t -> {
|
||||
LOGGER.error(t.getMessage(), t);
|
||||
return makeError(message, t.getMessage());
|
||||
}).toMulti()
|
||||
.filter(__ -> message.type() == MessageType.REQUEST);
|
||||
}
|
||||
}
|
||||
@ -168,6 +178,21 @@ public class CompetitionWS {
|
||||
// return Uni.createFrom().item(new Message<>(message.uuid(), message.code(), MessageType.REPLY, "ko"));
|
||||
}
|
||||
|
||||
public static Uni<Void> sendNotifyToOtherEditor(WebSocketConnection connection, String code, Object data) {
|
||||
String uuid = connection.pathParam("uuid");
|
||||
|
||||
List<Uni<Void>> queue = new ArrayList<>();
|
||||
queue.add(Uni.createFrom().voidItem()); // For avoid empty queue
|
||||
|
||||
connection.getOpenConnections().forEach(c -> {
|
||||
if (uuid.equals(c.pathParam("uuid"))) {
|
||||
queue.add(c.sendText(new MessageOut(UUID.randomUUID(), code, MessageType.NOTIFY, data)));
|
||||
}
|
||||
});
|
||||
|
||||
return Uni.join().all(queue).andCollectFailures().onFailure().recoverWithNull().replaceWithVoid();
|
||||
}
|
||||
|
||||
@OnError
|
||||
Uni<Void> error(WebSocketConnection connection, ForbiddenException t) {
|
||||
return connection.close(CloseReason.INTERNAL_SERVER_ERROR);
|
||||
|
||||
8
src/main/java/fr/titionfire/ffsaf/ws/PermLevel.java
Normal file
8
src/main/java/fr/titionfire/ffsaf/ws/PermLevel.java
Normal file
@ -0,0 +1,8 @@
|
||||
package fr.titionfire.ffsaf.ws;
|
||||
|
||||
public enum PermLevel {
|
||||
NONE,
|
||||
VIEW,
|
||||
TABLE,
|
||||
ADMIN,
|
||||
}
|
||||
231
src/main/java/fr/titionfire/ffsaf/ws/recv/RCategorie.java
Normal file
231
src/main/java/fr/titionfire/ffsaf/ws/recv/RCategorie.java
Normal file
@ -0,0 +1,231 @@
|
||||
package fr.titionfire.ffsaf.ws.recv;
|
||||
|
||||
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.CategoryRepository;
|
||||
import fr.titionfire.ffsaf.data.repository.CompetitionRepository;
|
||||
import fr.titionfire.ffsaf.data.repository.MatchRepository;
|
||||
import fr.titionfire.ffsaf.data.repository.TreeRepository;
|
||||
import fr.titionfire.ffsaf.net2.data.MatchEntity;
|
||||
import fr.titionfire.ffsaf.net2.data.TreeEntity;
|
||||
import fr.titionfire.ffsaf.rest.exception.DNotFoundException;
|
||||
import fr.titionfire.ffsaf.utils.TreeNode;
|
||||
import fr.titionfire.ffsaf.ws.CompetitionWS;
|
||||
import fr.titionfire.ffsaf.ws.PermLevel;
|
||||
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 org.hibernate.reactive.mutiny.Mutiny;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@WithSession
|
||||
@ApplicationScoped
|
||||
@RegisterForReflection
|
||||
public class RCategorie {
|
||||
//private static final Logger LOGGER = Logger.getLogger(RCategorie.class);
|
||||
|
||||
@Inject
|
||||
CategoryRepository categoryRepository;
|
||||
|
||||
@Inject
|
||||
CompetitionRepository competitionRepository;
|
||||
|
||||
@Inject
|
||||
MatchRepository matchRepository;
|
||||
|
||||
@Inject
|
||||
TreeRepository treeRepository;
|
||||
|
||||
@WSReceiver(code = "getAllCategory", permission = PermLevel.VIEW)
|
||||
public Uni<List<JustCategorie>> getAllCategory(WebSocketConnection connection, Object o) {
|
||||
return categoryRepository.list("compet.uuid", connection.pathParam("uuid"))
|
||||
.map(category -> category.stream().map(JustCategorie::from).toList());
|
||||
}
|
||||
|
||||
@WSReceiver(code = "getFullCategory", permission = PermLevel.VIEW)
|
||||
public Uni<FullCategory> getFullCategory(WebSocketConnection connection, Long id) {
|
||||
FullCategory fullCategory = new FullCategory();
|
||||
|
||||
return categoryRepository.findById(id)
|
||||
.invoke(Unchecked.consumer(o -> {
|
||||
if (o == null)
|
||||
throw new DNotFoundException("Catégorie non trouver");
|
||||
}))
|
||||
.invoke(cat -> {
|
||||
fullCategory.setId(cat.getId());
|
||||
fullCategory.setName(cat.getName());
|
||||
fullCategory.setLiceName(cat.getLiceName());
|
||||
fullCategory.setType(cat.getType());
|
||||
})
|
||||
.call(cat -> Mutiny.fetch(cat.getMatchs())
|
||||
.map(matchModels -> matchModels.stream().filter(o -> o.getCategory_ord() >= 0)
|
||||
.map(MatchEntity::fromModel).toList())
|
||||
.invoke(fullCategory::setMatches))
|
||||
.call(cat -> treeRepository.list("category = ?1 AND level != 0", cat.getId())
|
||||
.map(treeModels -> treeModels.stream().map(TreeEntity::fromModel).toList())
|
||||
.invoke(fullCategory::setTrees))
|
||||
.map(__ -> fullCategory);
|
||||
}
|
||||
|
||||
@WSReceiver(code = "createCategory", permission = PermLevel.ADMIN)
|
||||
public Uni<Long> createCategory(WebSocketConnection connection, JustCategorie categorie) {
|
||||
return competitionRepository.find("uuid", connection.pathParam("uuid")).firstResult()
|
||||
.chain(cm -> {
|
||||
CategoryModel categoryModel = new CategoryModel();
|
||||
categoryModel.setName(categorie.name);
|
||||
categoryModel.setCompet(cm);
|
||||
categoryModel.setMatchs(new ArrayList<>());
|
||||
categoryModel.setTree(new ArrayList<>());
|
||||
categoryModel.setType(categorie.type);
|
||||
categoryModel.setLiceName(categorie.liceName);
|
||||
|
||||
return categoryRepository.create(categoryModel);
|
||||
})
|
||||
.call(cat -> CompetitionWS.sendNotifyToOtherEditor(connection, "sendAddCategory",
|
||||
JustCategorie.from(cat)))
|
||||
.map(CategoryModel::getId);
|
||||
}
|
||||
|
||||
@WSReceiver(code = "updateCategory", permission = PermLevel.ADMIN)
|
||||
public Uni<Void> updateCategory(WebSocketConnection connection, JustCategorie categorie) {
|
||||
return categoryRepository.findById(categorie.id)
|
||||
.invoke(Unchecked.consumer(o -> {
|
||||
if (o == null)
|
||||
throw new DNotFoundException("Catégorie non trouver");
|
||||
}))
|
||||
.chain(cat -> {
|
||||
cat.setName(categorie.name);
|
||||
cat.setLiceName(categorie.liceName);
|
||||
cat.setType(categorie.type);
|
||||
return Panache.withTransaction(() -> categoryRepository.persist(cat));
|
||||
})
|
||||
.call(cat -> {
|
||||
Uni<Long> uni = Uni.createFrom().nullItem();
|
||||
if ((categorie.type() & 1) == 0)
|
||||
uni = uni.chain(__ -> matchRepository.delete("category = ?1 AND category_ord >= 0", cat));
|
||||
if ((categorie.type() & 2) == 0) {
|
||||
uni = uni.chain(__ -> treeRepository.delete("category = ?1", cat.getId()))
|
||||
.chain(__ -> matchRepository.delete("category = ?1 AND category_ord = -42", cat));
|
||||
}
|
||||
return uni;
|
||||
})
|
||||
.call(cat -> CompetitionWS.sendNotifyToOtherEditor(connection, "sendCategory", JustCategorie.from(cat)))
|
||||
.replaceWithVoid();
|
||||
}
|
||||
|
||||
private Uni<TreeModel> getOrCreateTreeNode(Long id, Long categorieId, int level) {
|
||||
if (id == null) {
|
||||
TreeModel treeModel = new TreeModel();
|
||||
treeModel.setCategory(categorieId);
|
||||
treeModel.setLevel(level);
|
||||
|
||||
return Panache.withTransaction(() -> treeRepository.persistAndFlush(treeModel));
|
||||
} else {
|
||||
return Panache.withTransaction(
|
||||
() -> treeRepository.find("match.id = ?1", id).firstResult()
|
||||
.invoke(t -> t.setLevel(level))
|
||||
.chain(t -> treeRepository.persist(t)));
|
||||
}
|
||||
}
|
||||
|
||||
private Uni<Void> updateNode(TreeNode<Long> current, TreeModel currentTreeModel, CategoryModel category) {
|
||||
return Uni.createFrom().item(currentTreeModel)
|
||||
.chain(t -> {
|
||||
if (current.getData() != null)
|
||||
return Uni.createFrom().item(t);
|
||||
|
||||
MatchModel matchModel = new MatchModel();
|
||||
matchModel.setCategory(category);
|
||||
matchModel.setCategory_ord(-42);
|
||||
matchModel.setEnd(false);
|
||||
|
||||
return matchRepository.create(matchModel).onItem()
|
||||
.invoke(t::setMatch)
|
||||
.chain(__ -> treeRepository.persistAndFlush(t));
|
||||
})
|
||||
.call(t -> {
|
||||
if (current.getLeft() == null)
|
||||
return Uni.createFrom().item(t);
|
||||
|
||||
return getOrCreateTreeNode(current.getLeft().getData(), category.getId(), 0)
|
||||
.invoke(t::setLeft)
|
||||
.call(treeModel -> updateNode(current.getLeft(), treeModel, category));
|
||||
})
|
||||
.call(t -> {
|
||||
if (current.getRight() == null)
|
||||
return Uni.createFrom().item(t);
|
||||
|
||||
return getOrCreateTreeNode(current.getRight().getData(), category.getId(), 0)
|
||||
.invoke(t::setRight)
|
||||
.call(treeModel -> updateNode(current.getRight(), treeModel, category));
|
||||
})
|
||||
.chain(t -> treeRepository.persist(t))
|
||||
.replaceWithVoid();
|
||||
}
|
||||
|
||||
|
||||
@WSReceiver(code = "updateTrees", permission = PermLevel.ADMIN)
|
||||
public Uni<Void> updateTrees(WebSocketConnection connection, TreeUpdate data) {
|
||||
return categoryRepository.findById(data.categoryId)
|
||||
.invoke(Unchecked.consumer(o -> {
|
||||
if (o == null)
|
||||
throw new DNotFoundException("Catégorie non trouver");
|
||||
}))
|
||||
.call(cat -> treeRepository.update("level = -1, left = NULL, right = NULL WHERE category = ?1",
|
||||
cat.getId()))
|
||||
.call(cat -> {
|
||||
Uni<?> uni = Uni.createFrom().voidItem();
|
||||
|
||||
for (int i = 0; i < data.trees().size(); i++) {
|
||||
TreeNode<Long> current = data.trees().get(i);
|
||||
|
||||
int finalI = i;
|
||||
uni = uni.chain(() -> getOrCreateTreeNode(current.getData(), cat.getId(), finalI + 1)
|
||||
.call(treeModel -> updateNode(current, treeModel, cat)));
|
||||
|
||||
}
|
||||
Uni<?> finalUni = uni;
|
||||
return Panache.withTransaction(() -> finalUni);
|
||||
})
|
||||
.call(cat -> treeRepository.list("category = ?1 AND level = -1", cat.getId())
|
||||
.map(l -> l.stream().map(o -> o.getMatch().getId()).toList())
|
||||
.call(__ -> treeRepository.delete("category = ?1 AND level = -1", cat.getId()))
|
||||
.call(ids -> matchRepository.delete("id IN ?1", ids)))
|
||||
.call(__ -> treeRepository.flush())
|
||||
.call(cat -> treeRepository.list("category = ?1 AND level != 0", cat.getId())
|
||||
.map(treeModels -> treeModels.stream().map(TreeEntity::fromModel).toList())
|
||||
.chain(trees -> CompetitionWS.sendNotifyToOtherEditor(connection, "sendTreeCategory", trees)))
|
||||
.replaceWithVoid();
|
||||
}
|
||||
|
||||
@RegisterForReflection
|
||||
public record JustCategorie(long id, String name, int type, String liceName) {
|
||||
static JustCategorie from(CategoryModel m) {
|
||||
return new JustCategorie(m.getId(), m.getName(), m.getType(), m.getLiceName());
|
||||
}
|
||||
}
|
||||
|
||||
@RegisterForReflection
|
||||
public record TreeUpdate(long categoryId, List<TreeNode<Long>> trees) {
|
||||
}
|
||||
|
||||
@Data
|
||||
@RegisterForReflection
|
||||
public static class FullCategory {
|
||||
long id;
|
||||
String name;
|
||||
int type;
|
||||
String liceName;
|
||||
List<TreeEntity> trees = null;
|
||||
List<MatchEntity> matches;
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
package fr.titionfire.ffsaf.ws.recv;
|
||||
|
||||
import fr.titionfire.ffsaf.ws.PermLevel;
|
||||
import io.quarkus.runtime.annotations.RegisterForReflection;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
@ -11,4 +12,6 @@ public @interface WSReceiver {
|
||||
|
||||
String code();
|
||||
|
||||
PermLevel permission() default PermLevel.VIEW;
|
||||
|
||||
}
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
import {createContext, useContext, useEffect, useId, useReducer, useRef, useState} from "react";
|
||||
import {apiAxios} from "../utils/Tools.js";
|
||||
import {toast} from "react-toastify";
|
||||
|
||||
function uuidv4() {
|
||||
return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, c =>
|
||||
@ -45,6 +47,11 @@ export function WSProvider({url, onmessage, children}) {
|
||||
const ws = useRef(null)
|
||||
const listenersRef = useRef([])
|
||||
const callbackRef = useRef({})
|
||||
const isReadyRef = useRef(isReady)
|
||||
|
||||
useEffect(() => {
|
||||
isReadyRef.current = isReady
|
||||
}, [isReady])
|
||||
|
||||
useEffect(() => {
|
||||
listenersRef.current = state.listener
|
||||
@ -129,7 +136,7 @@ export function WSProvider({url, onmessage, children}) {
|
||||
const send = (uuid, code, type, data, resolve = () => {
|
||||
}, reject = () => {
|
||||
}) => {
|
||||
if (!isReady) {
|
||||
if (!isReadyRef.current) {
|
||||
reject("WebSocket is not connected");
|
||||
return;
|
||||
}
|
||||
@ -150,6 +157,8 @@ export function WSProvider({url, onmessage, children}) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
console.log("WSProvider: sending message", {uuid, code, type, data});
|
||||
ws.current?.send(JSON.stringify({
|
||||
uuid: uuid,
|
||||
code: code,
|
||||
@ -191,3 +200,37 @@ export function useWS() {
|
||||
send,
|
||||
}
|
||||
}
|
||||
|
||||
export function useRequestWS(code, payload, setLoading = null, loadingLevel = 1) {
|
||||
const [data, setData] = useState(null)
|
||||
const [error, setErrors] = useState(null)
|
||||
const {isReady, sendRequest} = useWS()
|
||||
|
||||
const refresh = (code, payload) => {
|
||||
if (setLoading)
|
||||
setLoading(loadingLevel)
|
||||
|
||||
sendRequest(code, payload).then((data) => {
|
||||
setData(data);
|
||||
}).catch((err) => {
|
||||
setErrors(err);
|
||||
}).finally(() => {
|
||||
if (setLoading)
|
||||
setLoading(0)
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (isReady)
|
||||
refresh(code, payload)
|
||||
else{
|
||||
if (setLoading)
|
||||
setLoading(loadingLevel)
|
||||
setTimeout(() => refresh(code, payload), 1000)
|
||||
}
|
||||
}, []);
|
||||
|
||||
return {
|
||||
data, error, refresh, setData
|
||||
}
|
||||
}
|
||||
|
||||
445
src/main/webapp/src/pages/competition/editor/CMAdmin.jsx
Normal file
445
src/main/webapp/src/pages/competition/editor/CMAdmin.jsx
Normal file
@ -0,0 +1,445 @@
|
||||
import {useEffect, useReducer, useRef, useState} from "react";
|
||||
import {useRequestWS, useWS} from "../../../hooks/useWS.jsx";
|
||||
import {LoadingProvider, useLoadingSwitcher} from "../../../hooks/useLoading.jsx";
|
||||
import {CheckField, TextField} from "../../../components/MemberCustomFiels.jsx";
|
||||
import {toast} from "react-toastify";
|
||||
import {build_tree, from_sendTree, resize_tree, TreeNode} from "../../../utils/TreeUtils.js"
|
||||
import {ConfirmDialog} from "../../../components/ConfirmDialog.jsx";
|
||||
import {SimpleReducer} from "../../../utils/SimpleReducer.jsx";
|
||||
import {MarchReducer} from "../../../utils/MatchReducer.jsx";
|
||||
|
||||
export function CMAdmin() {
|
||||
const [catId, setCatId] = useState(null);
|
||||
const [cat, setCat] = useState(null);
|
||||
const [combs, setCombs] = useState([])
|
||||
// const [cats, setCats] = useState([])
|
||||
|
||||
const {dispatch} = useWS();
|
||||
|
||||
useEffect(() => {
|
||||
const categoryListener = ({data}) => {
|
||||
if (!cat || data.id !== cat.id)
|
||||
return
|
||||
setCat({
|
||||
...cat,
|
||||
name: data.name,
|
||||
liceName: data.liceName,
|
||||
type: data.type
|
||||
})
|
||||
}
|
||||
dispatch({type: 'addListener', payload: {callback: categoryListener, code: 'sendCategory'}})
|
||||
return () => dispatch({type: 'removeListener', payload: categoryListener})
|
||||
}, []);
|
||||
|
||||
/*useEffect(() => {
|
||||
toast.promise(sendRequest("getAllCategory", {}),
|
||||
{
|
||||
pending: 'Chargement des catégories...',
|
||||
success: 'Catégories chargées !',
|
||||
error: 'Erreur lors du chargement des catégories'
|
||||
}
|
||||
).then((data) => {
|
||||
setCats(data);
|
||||
})
|
||||
}, []);*/
|
||||
|
||||
return <>
|
||||
<div className="card">
|
||||
<div className='card-header'>
|
||||
<CategoryHeader cat={cat} setCatId={setCatId}/>
|
||||
</div>
|
||||
|
||||
<div className="card-body">
|
||||
<LoadingProvider>
|
||||
<div className="row">
|
||||
<CategoryContent cat={cat} catId={catId} setCat={setCat}/>
|
||||
</div>
|
||||
</LoadingProvider>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
|
||||
function CategoryHeader({cat, setCatId}) {
|
||||
const setLoading = useLoadingSwitcher()
|
||||
const bthRef = useRef();
|
||||
const confirmRef = useRef();
|
||||
const [modal, setModal] = useState({})
|
||||
const [confirm, setConfirm] = useState({})
|
||||
|
||||
const {data: cats, setData: setCats} = useRequestWS('getAllCategory', {}, setLoading);
|
||||
const {dispatch} = useWS();
|
||||
|
||||
useEffect(() => {
|
||||
const categoryListener = ({data}) => {
|
||||
setCats([
|
||||
...cats.filter(c => c.id !== data.id),
|
||||
data
|
||||
])
|
||||
}
|
||||
dispatch({type: 'addListener', payload: {callback: categoryListener, code: 'sendCategory'}})
|
||||
return () => dispatch({type: 'removeListener', payload: categoryListener})
|
||||
}, [cats]);
|
||||
|
||||
useEffect(() => {
|
||||
if (cats && cats.length > 0 && !cat) {
|
||||
setCatId(cats[0].id);
|
||||
}
|
||||
}, [cats]);
|
||||
|
||||
const handleCatChange = (e) => {
|
||||
const selectedCatId = e.target.value;
|
||||
if (selectedCatId !== "-1") {
|
||||
setCatId(selectedCatId);
|
||||
} else { // New category
|
||||
setModal({});
|
||||
bthRef.current.click();
|
||||
console.log(cat);
|
||||
e.target.value = cat?.id;
|
||||
}
|
||||
}
|
||||
|
||||
return <div className="row">
|
||||
<div className="col-auto">
|
||||
<div className="input-group">
|
||||
<h5 style={{margin: "auto 0.5em auto 0"}}>Edition de la catégorie</h5>
|
||||
<select className="form-select" onChange={handleCatChange}>
|
||||
{cats && cats.map(c => (
|
||||
<option key={c.id} value={c.id}>{c.name}</option>))}
|
||||
{cats && <option value={-1}>Nouvelle...</option>}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col">
|
||||
</div>
|
||||
<div className="col">
|
||||
<button className="btn btn-primary float-end" onClick={() => {
|
||||
setModal(cat);
|
||||
bthRef.current.click();
|
||||
}} disabled={cat === null}>Modifier
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
<button ref={bthRef} data-bs-toggle="modal" data-bs-target="#CategorieModal" style={{display: "none"}}>open</button>
|
||||
<div className="modal fade" id="CategorieModal" tabIndex="-1" aria-labelledby="CategorieModalLabel"
|
||||
aria-hidden="true">
|
||||
<div className="modal-dialog">
|
||||
<div className="modal-content">
|
||||
<ModalContent state={modal} setCatId={setCatId} setConfirm={setConfirm} confirmRef={confirmRef}/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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 : () => {
|
||||
}} onCancel={confirm.cancel ? confirm.cancel : () => {
|
||||
}} title={confirm ? confirm.title : ""} message={confirm ? confirm.message : ""}/>
|
||||
</div>
|
||||
}
|
||||
|
||||
function ModalContent({state, setCatId, setConfirm, confirmRef}) {
|
||||
const [name, setName] = useState("")
|
||||
const [lice, setLice] = useState("1")
|
||||
const [poule, setPoule] = useState(true)
|
||||
const [tournoi, setTournoi] = useState(false)
|
||||
const [size, setSize] = useState(4)
|
||||
const [loserMatch, setLoserMatch] = useState(1)
|
||||
|
||||
const {sendRequest} = useWS();
|
||||
|
||||
useEffect(() => {
|
||||
setName(state.name || "");
|
||||
setLice(state.liceName || "1");
|
||||
setPoule(((state.type || 1) & 1) !== 0);
|
||||
setTournoi((state.type & 2) !== 0);
|
||||
|
||||
if (state?.trees && state.trees.length >= 1) {
|
||||
const tree = state.trees[0];
|
||||
setSize(tree.getMaxChildrenAtDepth(tree.death() - 1) * 2);
|
||||
|
||||
if (state.trees.length === 1) {
|
||||
setLoserMatch(0);
|
||||
} else if (state.trees.length === 2) {
|
||||
setLoserMatch(1);
|
||||
} else {
|
||||
setLoserMatch(-1);
|
||||
}
|
||||
} else {
|
||||
setSize(4);
|
||||
setLoserMatch(1);
|
||||
}
|
||||
}, [state])
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const regex = /^([^;]+;)*[^;]+$/;
|
||||
if (regex.test(lice.trim()) === false) {
|
||||
toast.error("Le format du nom des lices est invalide. Veuillez séparer les noms par des ';'.");
|
||||
return;
|
||||
}
|
||||
|
||||
const nType = (poule ? 1 : 0) + (tournoi ? 2 : 0);
|
||||
if (nType === 0) {
|
||||
toast.error("Au moins un type (poule ou tournoi) doit être sélectionné.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (state?.id) {
|
||||
const applyChanges = () => {
|
||||
const newData = {
|
||||
id: state.id,
|
||||
name: name.trim(),
|
||||
liceName: lice.trim(),
|
||||
type: nType
|
||||
}
|
||||
|
||||
let nbMatch = -1;
|
||||
let oldSubTree = -1;
|
||||
const oldTrees = state?.trees || [];
|
||||
if (oldTrees.length >= 1) {
|
||||
const tree = state.trees[0];
|
||||
nbMatch = tree.getMaxChildrenAtDepth(tree.death() - 1);
|
||||
if (state.trees.length === 1)
|
||||
oldSubTree = 0
|
||||
else if (state.trees.length === 2)
|
||||
oldSubTree = 1
|
||||
}
|
||||
|
||||
console.log(tournoi, size, nbMatch, loserMatch, oldSubTree);
|
||||
if (tournoi && (size !== nbMatch * 2 || loserMatch !== oldSubTree)) {
|
||||
setConfirm({
|
||||
title: "Changement de l'arbre du tournoi",
|
||||
message: `Voulez-vous vraiment changer la taille de l'arbre du tournoi ou les matchs pour les perdants ? Cela va modifier les matchs existants (incluant des possibles suppressions)!`,
|
||||
confirm: () => {
|
||||
const trees2 = build_tree(size, loserMatch)
|
||||
const newTrees = []
|
||||
|
||||
let i = 0;
|
||||
for (; i < oldTrees.length && i < trees2.length; i++) {
|
||||
newTrees.push(oldTrees[i]);
|
||||
resize_tree(newTrees.at(i), trees2.at(i));
|
||||
}
|
||||
|
||||
for (; i < trees2.length; i++) {
|
||||
newTrees.push(trees2.at(i));
|
||||
}
|
||||
|
||||
toast.promise(sendRequest('updateTrees', {categoryId: state.id, trees: newTrees}),
|
||||
{
|
||||
pending: 'Mise à jour des arbres du tournoi...',
|
||||
success: 'Arbres mis à jour !',
|
||||
error: 'Erreur lors de la mise à jour des arbres'
|
||||
}
|
||||
).then(__ => {
|
||||
toast.promise(sendRequest('updateCategory', newData),
|
||||
{
|
||||
pending: 'Mise à jour de la catégorie...',
|
||||
success: 'Catégorie mise à jour !',
|
||||
error: 'Erreur lors de la mise à jour de la catégorie'
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
})
|
||||
confirmRef.current.click();
|
||||
} else {
|
||||
toast.promise(sendRequest('updateCategory', newData),
|
||||
{
|
||||
pending: 'Mise à jour de la catégorie...',
|
||||
success: 'Catégorie mise à jour !',
|
||||
error: 'Erreur lors de la mise à jour de la catégorie'
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (nType !== state.type) {
|
||||
let typeStr = "";
|
||||
if ((state.type & 1) !== 0 && (nType & 1) === 0)
|
||||
typeStr += "poule ";
|
||||
if ((state.type & 2) !== 0 && (nType & 2) === 0)
|
||||
typeStr += "tournoi ";
|
||||
|
||||
setConfirm({
|
||||
title: "Changement de type de catégorie",
|
||||
message: `Voulez-vous vraiment enlever la partie ${typeStr} de la catégorie. Cela va supprimer les matchs contenus dans cette partie !`,
|
||||
confirm: () => {
|
||||
applyChanges();
|
||||
}
|
||||
})
|
||||
confirmRef.current.click();
|
||||
} else {
|
||||
applyChanges();
|
||||
}
|
||||
} else {
|
||||
toast.promise(sendRequest('createCategory', {name: name.trim(), liceName: lice.trim(), type: nType}),
|
||||
{
|
||||
pending: 'Création de la catégorie...',
|
||||
success: 'Catégorie créée !',
|
||||
error: 'Erreur lors de la création de la catégorie'
|
||||
}
|
||||
).then(id => {
|
||||
setCatId(id);
|
||||
|
||||
if (tournoi) {
|
||||
const trees = build_tree(size, loserMatch)
|
||||
console.log("Creating trees for new category:", trees);
|
||||
|
||||
toast.promise(sendRequest('updateTrees', {categoryId: id, trees: trees}),
|
||||
{
|
||||
pending: 'Création des arbres du tournoi...',
|
||||
success: 'Arbres créés !',
|
||||
error: 'Erreur lors de la création des arbres'
|
||||
}
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const data = {
|
||||
name: name.trim(),
|
||||
liceName: lice.trim(),
|
||||
type: poule + (tournoi << 1),
|
||||
size: size,
|
||||
loserMatch: loserMatch
|
||||
}
|
||||
console.log("Submitting category data:", data);
|
||||
}
|
||||
|
||||
return <form onSubmit={handleSubmit}>
|
||||
<div className="modal-header">
|
||||
<h1 className="modal-title fs-5" id="CategorieModalLabel">Ajouter une catégorie</h1>
|
||||
<button type="button" className="btn-close" data-bs-dismiss="modal"
|
||||
aria-label="Close"></button>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
<div className="mb-3">
|
||||
<label htmlFor="nameInput1" className="form-label">Nom</label>
|
||||
<input type="text" className="form-control" id="nameInput1" placeholder="Epée bouclier" name="name" value={name}
|
||||
onChange={e => setName(e.target.value)}/>
|
||||
</div>
|
||||
|
||||
<div className="mb-3">
|
||||
<label htmlFor="liceInput1" className="form-label">Nom des lices <small>(séparée par des ';')</small></label>
|
||||
<input type="text" className="form-control" id="liceInput1" placeholder="1;2" name="lice" value={lice}
|
||||
onChange={e => setLice(e.target.value)}/>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="mb-3">
|
||||
<label className="form-label">Type</label>
|
||||
<div className="form-check form-switch">
|
||||
<input className="form-check-input" type="checkbox" role="switch" id="switchCheckDefault" name="poule" checked={poule}
|
||||
onChange={e => setPoule(e.target.checked)}/>
|
||||
<label className="form-check-label" htmlFor="switchCheckDefault">Poule</label>
|
||||
</div>
|
||||
|
||||
<div className="form-check form-switch">
|
||||
<input className="form-check-input" type="checkbox" role="switch" id="switchCheckDefault2" name="trournoi" checked={tournoi}
|
||||
onChange={e => setTournoi(e.target.checked)}/>
|
||||
<label className="form-check-label" htmlFor="switchCheckDefault2">Tournoi</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mb-3">
|
||||
<label htmlFor="sizeInput1" className="form-label">Nombre de combattants</label>
|
||||
<input type="number" className="form-control" id="sizeInput1" placeholder="4" name="size" disabled={!tournoi} value={size}
|
||||
onChange={e => setSize(Number.parseInt(e.target.value))}/>
|
||||
</div>
|
||||
|
||||
<div className="mb-3">
|
||||
<span>Match pour les perdants du tournoi:</span>
|
||||
<div className="form-check">
|
||||
<input className="form-check-input" type="radio" name="radioDefault" id="radioDefault1" disabled={!tournoi}
|
||||
checked={loserMatch === -1} onChange={e => {
|
||||
if (e.target.checked) setLoserMatch(-1)
|
||||
}}/>
|
||||
<label className="form-check-label" htmlFor="radioDefault1">
|
||||
Tous les matchs
|
||||
</label>
|
||||
</div>
|
||||
<div className="form-check">
|
||||
<input className="form-check-input" type="radio" name="radioDefault" id="radioDefault2" disabled={!tournoi}
|
||||
checked={loserMatch === 1} onChange={e => {
|
||||
if (e.target.checked) setLoserMatch(1)
|
||||
}}/>
|
||||
<label className="form-check-label" htmlFor="radioDefault2">
|
||||
Demi-finales et finales
|
||||
</label>
|
||||
</div>
|
||||
<div className="form-check">
|
||||
<input className="form-check-input" type="radio" name="radioDefault" id="radioDefault3" disabled={!tournoi}
|
||||
checked={loserMatch === 0} onChange={e => {
|
||||
if (e.target.checked) setLoserMatch(0)
|
||||
}}/>
|
||||
<label className="form-check-label" htmlFor="radioDefault3">
|
||||
Finales uniquement
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div className="modal-footer">
|
||||
<button type="button" className="btn btn-secondary" data-bs-dismiss="modal">Fermer</button>
|
||||
<button type="submit" className="btn btn-primary" data-bs-dismiss="modal">Enregistrer</button>
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
|
||||
function CategoryContent({cat, catId, setCat}) {
|
||||
const setLoading = useLoadingSwitcher()
|
||||
const {sendRequest, dispatch} = useWS();
|
||||
const [matches, reducer] = useReducer(MarchReducer, []);
|
||||
|
||||
useEffect(() => {
|
||||
const treeListener = ({data}) => {
|
||||
if (!cat || data.length < 1 || data[0].categorie !== cat.id)
|
||||
return
|
||||
setCat({
|
||||
...cat,
|
||||
trees: data.map(d => from_sendTree(d, true))
|
||||
})
|
||||
let matches2 = [];
|
||||
data.flatMap(d => from_sendTree(d, false).flat()).forEach((data_) => matches2.push({...data_}));
|
||||
reducer({type: 'REPLACE_TREE', payload: matches2});
|
||||
}
|
||||
dispatch({type: 'addListener', payload: {callback: treeListener, code: 'sendTreeCategory'}})
|
||||
return () => dispatch({type: 'removeListener', payload: treeListener})
|
||||
}, [cat]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!catId)
|
||||
return;
|
||||
setLoading(1);
|
||||
sendRequest('getFullCategory', catId)
|
||||
.then((data) => {
|
||||
setCat({
|
||||
id: data.id,
|
||||
name: data.name,
|
||||
liceName: data.liceName,
|
||||
type: data.type,
|
||||
trees: data.trees.map(d => from_sendTree(d, true))
|
||||
})
|
||||
|
||||
let matches2 = [];
|
||||
data.trees.flatMap(d => from_sendTree(d, false).flat()).forEach((data_) => matches2.push({...data_}));
|
||||
data.matches.forEach((data_) => matches2.push({...data_}));
|
||||
|
||||
reducer({type: 'REPLACE_ALL', payload: matches2});
|
||||
}).finally(() => setLoading(0))
|
||||
}, [catId]);
|
||||
|
||||
console.log("Matches in category content:", matches);
|
||||
|
||||
return <>
|
||||
<div className="col">
|
||||
|
||||
|
||||
<div className="vr"></div>
|
||||
</div>
|
||||
<div className="col">
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
@ -3,6 +3,7 @@ import {LoadingProvider} from "../../../hooks/useLoading.jsx";
|
||||
import {useEffect, useState} from "react";
|
||||
import {useWS, WSProvider} from "../../../hooks/useWS.jsx";
|
||||
import {ColoredCircle} from "../../../components/ColoredCircle.jsx";
|
||||
import {CMAdmin} from "./CMAdmin.jsx";
|
||||
|
||||
const vite_url = import.meta.env.VITE_URL;
|
||||
|
||||
@ -36,10 +37,13 @@ function HomeComp() {
|
||||
|
||||
return <WSProvider url={`${vite_url.replace('http', 'ws')}/api/ws/competition/${compUuid}`} onmessage={messageHandler}>
|
||||
<WSStatus setPerm={setPerm}/>
|
||||
<Routes>
|
||||
<Route path="/" element={<Home2 perm={perm}/>}/>
|
||||
<Route path="/test" element={<Test2/>}/>
|
||||
</Routes>
|
||||
<LoadingProvider>
|
||||
<Routes>
|
||||
<Route path="/" element={<Home2 perm={perm}/>}/>
|
||||
<Route path="/admin" element={<CMAdmin/>}/>
|
||||
<Route path="/test" element={<Test2/>}/>
|
||||
</Routes>
|
||||
</LoadingProvider>
|
||||
</WSProvider>
|
||||
}
|
||||
|
||||
@ -79,8 +83,8 @@ function Home2({perm}) {
|
||||
<h4 className="col-auto" style={{margin: "auto 0"}}>Sélectionne les modes d'affichage</h4>
|
||||
<div className="col">
|
||||
{perm === "admin" && <>
|
||||
<button className="btn btn-primary" onClick={() => nav("admin")}>Administration</button>
|
||||
<button className="btn btn-primary ms-3" onClick={() => nav("table")}>Table de marque</button>
|
||||
<button className="btn btn-primary" onClick={() => nav("table")}>Table de marque</button>
|
||||
<button className="btn btn-primary ms-3" onClick={() => nav("admin")}>Administration</button>
|
||||
</>}
|
||||
{perm === "table" && <>
|
||||
<button className="btn btn-primary" onClick={() => nav("table")}>Table de marque</button>
|
||||
|
||||
44
src/main/webapp/src/utils/MatchReducer.jsx
Normal file
44
src/main/webapp/src/utils/MatchReducer.jsx
Normal file
@ -0,0 +1,44 @@
|
||||
export function MarchReducer(datas, action) {
|
||||
switch (action.type) {
|
||||
case 'ADD':
|
||||
return [
|
||||
...datas,
|
||||
action.payload
|
||||
]
|
||||
case 'ADD_ALL':
|
||||
return [
|
||||
...datas,
|
||||
...action.payload
|
||||
]
|
||||
case 'REPLACE_ALL':
|
||||
return [
|
||||
...action.payload
|
||||
]
|
||||
case 'REPLACE_TREE':
|
||||
return [
|
||||
...datas.filter(data => data.categorie_ord !== -42),
|
||||
...action.payload
|
||||
]
|
||||
case 'CLEAR':
|
||||
return []
|
||||
case 'REMOVE':
|
||||
return datas.filter(data => data.id !== action.payload)
|
||||
case 'REMOVE_TREE':
|
||||
return datas.filter(data => data.categorie_ord !== -42)
|
||||
case 'UPDATE_OR_ADD':
|
||||
const index = datas.findIndex(data => data.id === action.payload.id)
|
||||
if (index === -1) {
|
||||
return [
|
||||
...datas,
|
||||
action.payload
|
||||
]
|
||||
} else {
|
||||
datas[index] = action.payload
|
||||
return [...datas]
|
||||
}
|
||||
case 'SORT':
|
||||
return datas.sort(action.payload)
|
||||
default:
|
||||
throw new Error()
|
||||
}
|
||||
}
|
||||
129
src/main/webapp/src/utils/TreeUtils.js
Normal file
129
src/main/webapp/src/utils/TreeUtils.js
Normal file
@ -0,0 +1,129 @@
|
||||
export function TreeNode(data, left = null, right = null) {
|
||||
this.data = data;
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
TreeNode.prototype = {
|
||||
getMaxChildrenAtDepth: function (death, current = 0) {
|
||||
if (current === death)
|
||||
return 1;
|
||||
|
||||
let tmp = 0;
|
||||
if (this.right != null)
|
||||
tmp += this.right.getMaxChildrenAtDepth(death, current + 1);
|
||||
|
||||
if (this.left != null)
|
||||
tmp += this.left.getMaxChildrenAtDepth(death, current + 1);
|
||||
|
||||
return tmp;
|
||||
},
|
||||
death: function () {
|
||||
let dg = 0;
|
||||
let dd = 0;
|
||||
|
||||
if (this.right != null)
|
||||
dg = this.right.death();
|
||||
|
||||
if (this.left != null)
|
||||
dg = this.left.death();
|
||||
|
||||
return 1 + Math.max(dg, dd);
|
||||
},
|
||||
isEnd: function (data) {
|
||||
if (this.data === data) {
|
||||
return this.right == null && this.left == null;
|
||||
} else {
|
||||
return (this.right != null && this.right.isEnd(data)) || (this.left != null && this.left.isEnd(data));
|
||||
}
|
||||
},
|
||||
flat: function () {
|
||||
let out = []
|
||||
this.__flat(out)
|
||||
return out;
|
||||
},
|
||||
__flat: function (out) {
|
||||
out.push(this.data)
|
||||
if (this.left != null)
|
||||
this.left.__flat(out)
|
||||
if (this.right != null)
|
||||
this.right.__flat(out)
|
||||
}
|
||||
}
|
||||
|
||||
export function build_tree(nb_comb, sub_tree_level = 0) {
|
||||
const nb_match = Math.ceil(nb_comb / 2)
|
||||
const level = Math.ceil(Math.log(nb_match) / Math.log(2))
|
||||
|
||||
const treeNodes = []
|
||||
|
||||
for (let i = level; i >= 0; i--) {
|
||||
const treeNode = new TreeNode(null)
|
||||
build_tree_(treeNode, 0, i, 0, nb_match)
|
||||
treeNodes.push(treeNode)
|
||||
|
||||
if (sub_tree_level !== -1) {
|
||||
if (sub_tree_level === 0)
|
||||
break
|
||||
if (i > sub_tree_level)
|
||||
i = sub_tree_level
|
||||
}
|
||||
}
|
||||
|
||||
return treeNodes
|
||||
}
|
||||
|
||||
export function build_tree_(treeNode, death, death_target, leave, leave_target) {
|
||||
if (death === death_target) {
|
||||
return (leave < leave_target) ? 1 : 0;
|
||||
}
|
||||
|
||||
if (leave + 1 === leave_target) {
|
||||
return 1;
|
||||
} else {
|
||||
let to_add = 0;
|
||||
if (leave < leave_target) {
|
||||
treeNode.left = new TreeNode(null)
|
||||
to_add += build_tree_(treeNode.left, death + 1, death_target, leave, leave_target)
|
||||
}
|
||||
|
||||
if (leave + to_add < leave_target) {
|
||||
treeNode.right = new TreeNode(null)
|
||||
to_add += build_tree_(treeNode.right, death + 1, death_target, leave + to_add, leave_target)
|
||||
}
|
||||
|
||||
return to_add;
|
||||
}
|
||||
}
|
||||
|
||||
export function resize_tree(src, to) {
|
||||
if (src === null || to === null)
|
||||
return null;
|
||||
|
||||
if (src.left === null && to.left != null) {
|
||||
src.left = to.left;
|
||||
} else if (src.left != null && to.left === null) {
|
||||
src.left = null;
|
||||
} else {
|
||||
resize_tree(src.left, to.left);
|
||||
}
|
||||
|
||||
if (src.right === null && to.right != null) {
|
||||
src.right = to.right;
|
||||
} else if (src.right != null && to.right === null) {
|
||||
src.right = null;
|
||||
} else {
|
||||
resize_tree(src.right, to.right);
|
||||
}
|
||||
}
|
||||
|
||||
export function from_sendTree(tree, matchId = true) {
|
||||
if (tree == null)
|
||||
return null;
|
||||
|
||||
const node = new TreeNode(matchId ? tree.match?.id : tree.match);
|
||||
node.left = from_sendTree(tree.left, matchId);
|
||||
node.right = from_sendTree(tree.right, matchId);
|
||||
|
||||
return node;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user