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;
|
List<TreeModel> tree;
|
||||||
|
|
||||||
Integer type;
|
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)
|
@OneToMany(mappedBy = "competition", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
|
||||||
List<RegisterModel> insc;
|
List<RegisterModel> insc;
|
||||||
|
|
||||||
|
@OneToMany(mappedBy = "competition", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
|
||||||
|
List<CompetitionGuestModel> guests = new ArrayList<>();
|
||||||
|
|
||||||
|
|
||||||
List<Long> banMembre = new ArrayList<>();
|
List<Long> banMembre = new ArrayList<>();
|
||||||
|
|
||||||
String owner;
|
String owner;
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import jakarta.persistence.*;
|
|||||||
import lombok.*;
|
import lombok.*;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@ -32,13 +33,17 @@ public class MatchModel {
|
|||||||
@JoinColumn(name = "c1", referencedColumnName = "id")
|
@JoinColumn(name = "c1", referencedColumnName = "id")
|
||||||
MembreModel c1_id = null;
|
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)
|
@ManyToOne(fetch = FetchType.EAGER)
|
||||||
@JoinColumn(name = "c2", referencedColumnName = "id")
|
@JoinColumn(name = "c2", referencedColumnName = "id")
|
||||||
MembreModel c2_id = null;
|
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)
|
@ManyToOne(fetch = FetchType.EAGER)
|
||||||
@JoinColumn(name = "id_category", referencedColumnName = "id")
|
@JoinColumn(name = "id_category", referencedColumnName = "id")
|
||||||
@ -48,22 +53,27 @@ public class MatchModel {
|
|||||||
|
|
||||||
boolean isEnd = true;
|
boolean isEnd = true;
|
||||||
|
|
||||||
|
Date date = null;
|
||||||
|
|
||||||
@ElementCollection(fetch = FetchType.EAGER)
|
@ElementCollection(fetch = FetchType.EAGER)
|
||||||
@CollectionTable(name = "score", joinColumns = @JoinColumn(name = "id_match"))
|
@CollectionTable(name = "score", joinColumns = @JoinColumn(name = "id_match"))
|
||||||
List<ScoreEmbeddable> scores = new ArrayList<>();
|
List<ScoreEmbeddable> scores = new ArrayList<>();
|
||||||
|
|
||||||
char poule = 'A';
|
char poule = 'A';
|
||||||
|
|
||||||
|
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
|
||||||
|
@JoinColumn(name = "match", referencedColumnName = "id")
|
||||||
|
List<CardboardModel> cardboard;
|
||||||
|
|
||||||
public String getC1Name() {
|
public String getC1Name() {
|
||||||
if (c1_id == null)
|
if (c1_id == null)
|
||||||
return c1_str;
|
return c1_guest.fname + " " + c1_guest.lname;
|
||||||
return c1_id.fname + " " + c1_id.lname;
|
return c1_id.fname + " " + c1_id.lname;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getC2Name() {
|
public String getC2Name() {
|
||||||
if (c2_id == null)
|
if (c2_id == null)
|
||||||
return c2_str;
|
return c2_guest.fname + " " + c2_guest.lname;
|
||||||
return c2_id.fname + " " + c2_id.lname;
|
return c2_id.fname + " " + c2_id.lname;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,19 @@
|
|||||||
package fr.titionfire.ffsaf.data.repository;
|
package fr.titionfire.ffsaf.data.repository;
|
||||||
|
|
||||||
import fr.titionfire.ffsaf.data.model.CategoryModel;
|
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.quarkus.hibernate.reactive.panache.PanacheRepositoryBase;
|
||||||
|
import io.smallrye.mutiny.Uni;
|
||||||
import jakarta.enterprise.context.ApplicationScoped;
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
|
|
||||||
@ApplicationScoped
|
@ApplicationScoped
|
||||||
public class CategoryRepository implements PanacheRepositoryBase<CategoryModel, Long> {
|
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;
|
package fr.titionfire.ffsaf.data.repository;
|
||||||
|
|
||||||
import fr.titionfire.ffsaf.data.model.MatchModel;
|
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.quarkus.hibernate.reactive.panache.PanacheRepositoryBase;
|
||||||
|
import io.smallrye.mutiny.Uni;
|
||||||
import jakarta.enterprise.context.ApplicationScoped;
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
|
|
||||||
@ApplicationScoped
|
@ApplicationScoped
|
||||||
public class MatchRepository implements PanacheRepositoryBase<MatchModel, Long> {
|
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 fr.titionfire.ffsaf.data.model.TreeModel;
|
||||||
import io.quarkus.hibernate.reactive.panache.PanacheRepositoryBase;
|
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;
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
|
|
||||||
@ApplicationScoped
|
@ApplicationScoped
|
||||||
public class TreeRepository implements PanacheRepositoryBase<TreeModel, Long> {
|
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;
|
package fr.titionfire.ffsaf.domain.service;
|
||||||
|
|
||||||
import fr.titionfire.ffsaf.data.model.MatchModel;
|
import fr.titionfire.ffsaf.data.model.*;
|
||||||
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.repository.*;
|
import fr.titionfire.ffsaf.data.repository.*;
|
||||||
import fr.titionfire.ffsaf.rest.data.CategoryData;
|
import fr.titionfire.ffsaf.rest.data.CategoryData;
|
||||||
import fr.titionfire.ffsaf.rest.data.CategoryFullData;
|
import fr.titionfire.ffsaf.rest.data.CategoryFullData;
|
||||||
@ -45,10 +42,13 @@ public class CategoryService {
|
|||||||
@Inject
|
@Inject
|
||||||
CompetPermService permService;
|
CompetPermService permService;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
CompetitionGuestRepository competitionGuestRepository;
|
||||||
|
|
||||||
public Uni<CategoryData> getByIdAdmin(SecurityCtx securityCtx, CompetitionSystem system, Long id) {
|
public Uni<CategoryData> getByIdAdmin(SecurityCtx securityCtx, CompetitionSystem system, Long id) {
|
||||||
return repository.find("systemId = ?1 AND system = ?2", id, system)
|
return repository.find("systemId = ?1 AND system = ?2", id, system)
|
||||||
.firstResult()
|
.firstResult()
|
||||||
.onItem().ifNull().failWith(() -> new RuntimeException("Poule not found"))
|
.onItem().ifNull().failWith(() -> new RuntimeException("Category not found"))
|
||||||
.call(data -> permService.hasAdminViewPerm(securityCtx, data.getCompet()))
|
.call(data -> permService.hasAdminViewPerm(securityCtx, data.getCompet()))
|
||||||
.map(CategoryData::fromModel);
|
.map(CategoryData::fromModel);
|
||||||
}
|
}
|
||||||
@ -171,6 +171,15 @@ public class CategoryService {
|
|||||||
.invoke(o2 -> o2.forEach(m -> o.membres.put(m.getId(), m)))
|
.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 -> {
|
.invoke(in -> {
|
||||||
ArrayList<TreeModel> node = new ArrayList<>();
|
ArrayList<TreeModel> node = new ArrayList<>();
|
||||||
for (TreeModel treeModel : in.category.getTree())
|
for (TreeModel treeModel : in.category.getTree())
|
||||||
@ -214,8 +223,8 @@ public class CategoryService {
|
|||||||
}
|
}
|
||||||
mm.setCategory(in.category);
|
mm.setCategory(in.category);
|
||||||
mm.setCategory_ord(m.getCategory_ord());
|
mm.setCategory_ord(m.getCategory_ord());
|
||||||
mm.setC1_str(m.getC1_str());
|
mm.setC1_guest(in.guest.getOrDefault(m.getC1_str(), null));
|
||||||
mm.setC2_str(m.getC2_str());
|
mm.setC2_guest(in.guest.getOrDefault(m.getC2_str(), null));
|
||||||
mm.setC1_id(in.membres.getOrDefault(m.getC1_id(), null));
|
mm.setC1_id(in.membres.getOrDefault(m.getC1_id(), null));
|
||||||
mm.setC2_id(in.membres.getOrDefault(m.getC2_id(), null));
|
mm.setC2_id(in.membres.getOrDefault(m.getC2_id(), null));
|
||||||
mm.setEnd(m.isEnd());
|
mm.setEnd(m.isEnd());
|
||||||
@ -238,6 +247,7 @@ public class CategoryService {
|
|||||||
private static class WorkData {
|
private static class WorkData {
|
||||||
CategoryModel category;
|
CategoryModel category;
|
||||||
HashMap<Long, MembreModel> membres = new HashMap<>();
|
HashMap<Long, MembreModel> membres = new HashMap<>();
|
||||||
|
HashMap<String, CompetitionGuestModel> guest = new HashMap<>();
|
||||||
List<MatchModel> match = new ArrayList<>();
|
List<MatchModel> match = new ArrayList<>();
|
||||||
List<Long> toRmMatch;
|
List<Long> toRmMatch;
|
||||||
List<TreeModel> unlinkNode;
|
List<TreeModel> unlinkNode;
|
||||||
@ -246,7 +256,7 @@ public class CategoryService {
|
|||||||
|
|
||||||
public Uni<?> delete(SecurityCtx securityCtx, CompetitionSystem system, Long id) {
|
public Uni<?> delete(SecurityCtx securityCtx, CompetitionSystem system, Long id) {
|
||||||
return repository.find("systemId = ?1 AND system = ?2", id, system).firstResult()
|
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 -> permService.hasEditPerm(securityCtx, o.getCompet()))
|
||||||
.call(o -> Mutiny.fetch(o.getTree())
|
.call(o -> Mutiny.fetch(o.getTree())
|
||||||
.call(o2 -> o2.isEmpty() ? Uni.createFrom().nullItem() :
|
.call(o2 -> o2.isEmpty() ? Uni.createFrom().nullItem() :
|
||||||
@ -260,7 +270,7 @@ public class CategoryService {
|
|||||||
Panache.withTransaction(() -> treeRepository.delete("id IN ?1", in)))
|
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())));
|
.chain(model -> Panache.withTransaction(() -> repository.delete("id", model.getId())));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -89,7 +89,7 @@ public class CompetPermService {
|
|||||||
.onFailure().call(throwable -> cacheAccess.invalidate(securityCtx.getSubject()));
|
.onFailure().call(throwable -> cacheAccess.invalidate(securityCtx.getSubject()));
|
||||||
|
|
||||||
Uni<HashMap<Long, String>> none = cacheNoneAccess.getAsync(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 -> {
|
.map(competitionModels -> {
|
||||||
HashMap<Long, String> map = new HashMap<>();
|
HashMap<Long, String> map = new HashMap<>();
|
||||||
for (CompetitionModel model : competitionModels) {
|
for (CompetitionModel model : competitionModels) {
|
||||||
@ -184,7 +184,7 @@ public class CompetPermService {
|
|||||||
if (!securityCtx.isInClubGroup(o.getClub().getId())) // Only membre club pass here
|
if (!securityCtx.isInClubGroup(o.getClub().getId())) // Only membre club pass here
|
||||||
throw new DForbiddenException();
|
throw new DForbiddenException();
|
||||||
|
|
||||||
if (o.getSystem() == CompetitionSystem.NONE)
|
if (o.getSystem() == CompetitionSystem.INTERNAL)
|
||||||
if (securityCtx.roleHas("club_president") || securityCtx.roleHas("club_respo_intra")
|
if (securityCtx.roleHas("club_president") || securityCtx.roleHas("club_respo_intra")
|
||||||
|| securityCtx.roleHas("club_secretaire") || securityCtx.roleHas("club_tresorier"))
|
|| securityCtx.roleHas("club_secretaire") || securityCtx.roleHas("club_tresorier"))
|
||||||
return Uni.createFrom().nullItem();
|
return Uni.createFrom().nullItem();
|
||||||
@ -219,7 +219,7 @@ public class CompetPermService {
|
|||||||
if (o.getSystem() == CompetitionSystem.SAFCA)
|
if (o.getSystem() == CompetitionSystem.SAFCA)
|
||||||
return hasSafcaEditPerm(securityCtx, o.getId());
|
return hasSafcaEditPerm(securityCtx, o.getId());
|
||||||
|
|
||||||
if (o.getSystem() == CompetitionSystem.NONE) {
|
if (o.getSystem() == CompetitionSystem.INTERNAL) {
|
||||||
if (securityCtx.isInClubGroup(o.getClub().getId()) && securityCtx.isClubAdmin())
|
if (securityCtx.isInClubGroup(o.getClub().getId()) && securityCtx.isClubAdmin())
|
||||||
return Uni.createFrom().nullItem();
|
return Uni.createFrom().nullItem();
|
||||||
|
|
||||||
@ -259,7 +259,7 @@ public class CompetPermService {
|
|||||||
if (o.getSystem() == CompetitionSystem.SAFCA)
|
if (o.getSystem() == CompetitionSystem.SAFCA)
|
||||||
return hasSafcaTablePerm(securityCtx, o.getId());
|
return hasSafcaTablePerm(securityCtx, o.getId());
|
||||||
|
|
||||||
if (o.getSystem() == CompetitionSystem.NONE) {
|
if (o.getSystem() == CompetitionSystem.INTERNAL) {
|
||||||
if (securityCtx.isInClubGroup(o.getClub().getId()) && securityCtx.isClubAdmin())
|
if (securityCtx.isInClubGroup(o.getClub().getId()) && securityCtx.isClubAdmin())
|
||||||
return Uni.createFrom().nullItem();
|
return Uni.createFrom().nullItem();
|
||||||
|
|
||||||
|
|||||||
@ -103,7 +103,7 @@ public class CompetitionService {
|
|||||||
if (id == 0) {
|
if (id == 0) {
|
||||||
return Uni.createFrom()
|
return Uni.createFrom()
|
||||||
.item(new CompetitionData(null, "", "", "", "", new Date(), new Date(),
|
.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, "", "", "", ""));
|
null, "", "", null, true, "", "", "", ""));
|
||||||
}
|
}
|
||||||
return permService.hasAdminViewPerm(securityCtx, id)
|
return permService.hasAdminViewPerm(securityCtx, id)
|
||||||
@ -184,7 +184,7 @@ public class CompetitionService {
|
|||||||
}).map(CompetitionData::fromModel)
|
}).map(CompetitionData::fromModel)
|
||||||
.call(c -> (c.getSystem() == CompetitionSystem.SAFCA) ? cacheAccess.invalidate(
|
.call(c -> (c.getSystem() == CompetitionSystem.SAFCA) ? cacheAccess.invalidate(
|
||||||
securityCtx.getSubject()) : Uni.createFrom().nullItem())
|
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());
|
securityCtx.getSubject()) : Uni.createFrom().nullItem());
|
||||||
} else {
|
} else {
|
||||||
return permService.hasEditPerm(securityCtx, data.getId())
|
return permService.hasEditPerm(securityCtx, data.getId())
|
||||||
@ -208,7 +208,7 @@ public class CompetitionService {
|
|||||||
}).map(CompetitionData::fromModel)
|
}).map(CompetitionData::fromModel)
|
||||||
.call(c -> (c.getSystem() == CompetitionSystem.SAFCA) ? cacheAccess.invalidate(
|
.call(c -> (c.getSystem() == CompetitionSystem.SAFCA) ? cacheAccess.invalidate(
|
||||||
securityCtx.getSubject()) : Uni.createFrom().nullItem())
|
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());
|
securityCtx.getSubject()) : Uni.createFrom().nullItem());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
package fr.titionfire.ffsaf.domain.service;
|
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.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.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.data.MatchData;
|
||||||
import fr.titionfire.ffsaf.rest.exception.DNotFoundException;
|
import fr.titionfire.ffsaf.rest.exception.DNotFoundException;
|
||||||
import fr.titionfire.ffsaf.utils.CompetitionSystem;
|
import fr.titionfire.ffsaf.utils.CompetitionSystem;
|
||||||
@ -33,6 +35,9 @@ public class MatchService {
|
|||||||
@Inject
|
@Inject
|
||||||
CompetPermService permService;
|
CompetPermService permService;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
CompetitionGuestRepository competitionGuestRepository;
|
||||||
|
|
||||||
public Uni<MatchData> getByIdAdmin(SecurityCtx securityCtx, CompetitionSystem system, Long id) {
|
public Uni<MatchData> getByIdAdmin(SecurityCtx securityCtx, CompetitionSystem system, Long id) {
|
||||||
return repository.find("systemId = ?1 AND system = ?2", id, system).firstResult()
|
return repository.find("systemId = ?1 AND system = ?2", id, system).firstResult()
|
||||||
.onItem().ifNull().failWith(() -> new DNotFoundException("Match not found"))
|
.onItem().ifNull().failWith(() -> new DNotFoundException("Match not found"))
|
||||||
@ -75,8 +80,6 @@ public class MatchService {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
.chain(o -> {
|
.chain(o -> {
|
||||||
o.setC1_str(data.getC1_str());
|
|
||||||
o.setC2_str(data.getC2_str());
|
|
||||||
o.setCategory_ord(data.getCategory_ord());
|
o.setCategory_ord(data.getCategory_ord());
|
||||||
o.getScores().clear();
|
o.getScores().clear();
|
||||||
o.getScores().addAll(data.getScores());
|
o.getScores().addAll(data.getScores());
|
||||||
@ -88,6 +91,20 @@ public class MatchService {
|
|||||||
.chain(() -> (data.getC1_id() == null) ?
|
.chain(() -> (data.getC1_id() == null) ?
|
||||||
Uni.createFrom().nullItem() : combRepository.findById(data.getC2_id()))
|
Uni.createFrom().nullItem() : combRepository.findById(data.getC2_id()))
|
||||||
.invoke(o::setC2_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)));
|
.chain(() -> Panache.withTransaction(() -> repository.persist(o)));
|
||||||
})
|
})
|
||||||
.map(MatchData::fromModel);
|
.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 null;
|
||||||
|
|
||||||
return new MatchData(model.getSystemId(),
|
return new MatchData(model.getSystemId(),
|
||||||
(model.getC1_id() == null) ? null : model.getC1_id().getId(), model.getC1_str(),
|
(model.getC1_id() == null) ? null : model.getC1_id().getId(),
|
||||||
(model.getC2_id() == null) ? null : model.getC2_id().getId(), model.getC2_str(),
|
(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.getCategory().getId(), model.getCategory_ord(), model.isEnd(), model.getPoule(),
|
||||||
model.getScores());
|
model.getScores());
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
package fr.titionfire.ffsaf.utils;
|
package fr.titionfire.ffsaf.utils;
|
||||||
|
|
||||||
public enum CompetitionSystem {
|
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.net2.MessageType;
|
||||||
import fr.titionfire.ffsaf.utils.SecurityCtx;
|
import fr.titionfire.ffsaf.utils.SecurityCtx;
|
||||||
import fr.titionfire.ffsaf.ws.data.WelcomeInfo;
|
import fr.titionfire.ffsaf.ws.data.WelcomeInfo;
|
||||||
|
import fr.titionfire.ffsaf.ws.recv.RCategorie;
|
||||||
import fr.titionfire.ffsaf.ws.recv.RMatch;
|
import fr.titionfire.ffsaf.ws.recv.RMatch;
|
||||||
import fr.titionfire.ffsaf.ws.recv.WSReceiver;
|
import fr.titionfire.ffsaf.ws.recv.WSReceiver;
|
||||||
import fr.titionfire.ffsaf.ws.send.JsonUni;
|
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.InvocationTargetException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.HashMap;
|
import java.util.*;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import static fr.titionfire.ffsaf.net2.Client_Thread.MAPPER;
|
import static fr.titionfire.ffsaf.net2.Client_Thread.MAPPER;
|
||||||
|
|
||||||
@ -38,6 +37,9 @@ public class CompetitionWS {
|
|||||||
@Inject
|
@Inject
|
||||||
RMatch rMatch;
|
RMatch rMatch;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
RCategorie rCategorie;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
SecurityCtx securityCtx;
|
SecurityCtx securityCtx;
|
||||||
|
|
||||||
@ -69,6 +71,7 @@ public class CompetitionWS {
|
|||||||
@PostConstruct
|
@PostConstruct
|
||||||
void init() {
|
void init() {
|
||||||
getWSReceiverMethods(RMatch.class, rMatch);
|
getWSReceiverMethods(RMatch.class, rMatch);
|
||||||
|
getWSReceiverMethods(RCategorie.class, rCategorie);
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnOpen
|
@OnOpen
|
||||||
@ -82,12 +85,12 @@ public class CompetitionWS {
|
|||||||
if (cm == null)
|
if (cm == null)
|
||||||
throw new ForbiddenException();
|
throw new ForbiddenException();
|
||||||
}))
|
}))
|
||||||
.call(cm -> competPermService.hasEditPerm(securityCtx, cm).map(__ -> "admin")
|
.call(cm -> competPermService.hasEditPerm(securityCtx, cm).map(__ -> PermLevel.ADMIN)
|
||||||
.onFailure()
|
.onFailure()
|
||||||
.recoverWithUni(competPermService.hasTablePerm(securityCtx, cm).map(__ -> "table"))
|
.recoverWithUni(competPermService.hasTablePerm(securityCtx, cm).map(__ -> PermLevel.TABLE))
|
||||||
.onFailure()
|
.onFailure()
|
||||||
.recoverWithUni(competPermService.hasViewPerm(securityCtx, cm).map(__ -> "view"))
|
.recoverWithUni(competPermService.hasViewPerm(securityCtx, cm).map(__ -> PermLevel.VIEW))
|
||||||
.invoke(prem -> connection.userData().put(UserData.TypedKey.forString("prem"), prem))
|
.invoke(prem -> connection.userData().put(UserData.TypedKey.forString("prem"), prem.toString()))
|
||||||
.invoke(prem -> LOGGER.infof("Connection permission: %s", prem))
|
.invoke(prem -> LOGGER.infof("Connection permission: %s", prem))
|
||||||
.onFailure().transform(t -> new ForbiddenException()))
|
.onFailure().transform(t -> new ForbiddenException()))
|
||||||
.invoke(__ -> {
|
.invoke(__ -> {
|
||||||
@ -150,12 +153,19 @@ public class CompetitionWS {
|
|||||||
try {
|
try {
|
||||||
for (Map.Entry<Method, Object> entry : wsMethods.entrySet()) {
|
for (Map.Entry<Method, Object> entry : wsMethods.entrySet()) {
|
||||||
Method method = entry.getKey();
|
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,
|
return ((Uni<?>) method.invoke(entry.getValue(), connection,
|
||||||
MAPPER.treeToValue(message.data(), method.getParameterTypes()[1])))
|
MAPPER.treeToValue(message.data(), method.getParameterTypes()[1])))
|
||||||
.map(o -> makeReply(message, o))
|
.map(o -> makeReply(message, o))
|
||||||
.onFailure()
|
.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);
|
.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"));
|
// 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
|
@OnError
|
||||||
Uni<Void> error(WebSocketConnection connection, ForbiddenException t) {
|
Uni<Void> error(WebSocketConnection connection, ForbiddenException t) {
|
||||||
return connection.close(CloseReason.INTERNAL_SERVER_ERROR);
|
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;
|
package fr.titionfire.ffsaf.ws.recv;
|
||||||
|
|
||||||
|
import fr.titionfire.ffsaf.ws.PermLevel;
|
||||||
import io.quarkus.runtime.annotations.RegisterForReflection;
|
import io.quarkus.runtime.annotations.RegisterForReflection;
|
||||||
|
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
@ -11,4 +12,6 @@ public @interface WSReceiver {
|
|||||||
|
|
||||||
String code();
|
String code();
|
||||||
|
|
||||||
|
PermLevel permission() default PermLevel.VIEW;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
import {createContext, useContext, useEffect, useId, useReducer, useRef, useState} from "react";
|
import {createContext, useContext, useEffect, useId, useReducer, useRef, useState} from "react";
|
||||||
|
import {apiAxios} from "../utils/Tools.js";
|
||||||
|
import {toast} from "react-toastify";
|
||||||
|
|
||||||
function uuidv4() {
|
function uuidv4() {
|
||||||
return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, c =>
|
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 ws = useRef(null)
|
||||||
const listenersRef = useRef([])
|
const listenersRef = useRef([])
|
||||||
const callbackRef = useRef({})
|
const callbackRef = useRef({})
|
||||||
|
const isReadyRef = useRef(isReady)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
isReadyRef.current = isReady
|
||||||
|
}, [isReady])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
listenersRef.current = state.listener
|
listenersRef.current = state.listener
|
||||||
@ -129,7 +136,7 @@ export function WSProvider({url, onmessage, children}) {
|
|||||||
const send = (uuid, code, type, data, resolve = () => {
|
const send = (uuid, code, type, data, resolve = () => {
|
||||||
}, reject = () => {
|
}, reject = () => {
|
||||||
}) => {
|
}) => {
|
||||||
if (!isReady) {
|
if (!isReadyRef.current) {
|
||||||
reject("WebSocket is not connected");
|
reject("WebSocket is not connected");
|
||||||
return;
|
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({
|
ws.current?.send(JSON.stringify({
|
||||||
uuid: uuid,
|
uuid: uuid,
|
||||||
code: code,
|
code: code,
|
||||||
@ -191,3 +200,37 @@ export function useWS() {
|
|||||||
send,
|
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 {useEffect, useState} from "react";
|
||||||
import {useWS, WSProvider} from "../../../hooks/useWS.jsx";
|
import {useWS, WSProvider} from "../../../hooks/useWS.jsx";
|
||||||
import {ColoredCircle} from "../../../components/ColoredCircle.jsx";
|
import {ColoredCircle} from "../../../components/ColoredCircle.jsx";
|
||||||
|
import {CMAdmin} from "./CMAdmin.jsx";
|
||||||
|
|
||||||
const vite_url = import.meta.env.VITE_URL;
|
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}>
|
return <WSProvider url={`${vite_url.replace('http', 'ws')}/api/ws/competition/${compUuid}`} onmessage={messageHandler}>
|
||||||
<WSStatus setPerm={setPerm}/>
|
<WSStatus setPerm={setPerm}/>
|
||||||
<Routes>
|
<LoadingProvider>
|
||||||
<Route path="/" element={<Home2 perm={perm}/>}/>
|
<Routes>
|
||||||
<Route path="/test" element={<Test2/>}/>
|
<Route path="/" element={<Home2 perm={perm}/>}/>
|
||||||
</Routes>
|
<Route path="/admin" element={<CMAdmin/>}/>
|
||||||
|
<Route path="/test" element={<Test2/>}/>
|
||||||
|
</Routes>
|
||||||
|
</LoadingProvider>
|
||||||
</WSProvider>
|
</WSProvider>
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,8 +83,8 @@ function Home2({perm}) {
|
|||||||
<h4 className="col-auto" style={{margin: "auto 0"}}>Sélectionne les modes d'affichage</h4>
|
<h4 className="col-auto" style={{margin: "auto 0"}}>Sélectionne les modes d'affichage</h4>
|
||||||
<div className="col">
|
<div className="col">
|
||||||
{perm === "admin" && <>
|
{perm === "admin" && <>
|
||||||
<button className="btn btn-primary" onClick={() => nav("admin")}>Administration</button>
|
<button className="btn btn-primary" onClick={() => nav("table")}>Table de marque</button>
|
||||||
<button className="btn btn-primary ms-3" onClick={() => nav("table")}>Table de marque</button>
|
<button className="btn btn-primary ms-3" onClick={() => nav("admin")}>Administration</button>
|
||||||
</>}
|
</>}
|
||||||
{perm === "table" && <>
|
{perm === "table" && <>
|
||||||
<button className="btn btn-primary" onClick={() => nav("table")}>Table de marque</button>
|
<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