feat: cm admin poule editor
This commit is contained in:
parent
6f61db6817
commit
bb901392fc
@ -63,7 +63,7 @@ public class MatchModel {
|
||||
|
||||
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
|
||||
@JoinColumn(name = "match", referencedColumnName = "id")
|
||||
List<CardboardModel> cardboard;
|
||||
List<CardboardModel> cardboard = new ArrayList<>();
|
||||
|
||||
public String getC1Name() {
|
||||
if (c1_id == null)
|
||||
|
||||
@ -7,6 +7,8 @@ import io.quarkus.hibernate.reactive.panache.PanacheRepositoryBase;
|
||||
import io.smallrye.mutiny.Uni;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@ApplicationScoped
|
||||
public class MatchRepository implements PanacheRepositoryBase<MatchModel, Long> {
|
||||
|
||||
@ -16,4 +18,13 @@ public class MatchRepository implements PanacheRepositoryBase<MatchModel, Long>
|
||||
.invoke(matchModel1 -> matchModel1.setSystemId(matchModel1.getId())))
|
||||
.chain(this::persist);
|
||||
}
|
||||
|
||||
public Uni<Void> create(List<MatchModel> matchModel) {
|
||||
matchModel.forEach(model -> model.setSystem(CompetitionSystem.INTERNAL));
|
||||
return Panache.withTransaction(() -> this.persist(matchModel)
|
||||
.call(__ -> this.flush())
|
||||
.invoke(__ -> matchModel.forEach(model -> model.setSystemId(model.getId())))
|
||||
.map(__ -> matchModel))
|
||||
.chain(this::persist);
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,8 +12,8 @@ import fr.titionfire.ffsaf.domain.entity.TreeEntity;
|
||||
import fr.titionfire.ffsaf.rest.exception.DForbiddenException;
|
||||
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 fr.titionfire.ffsaf.ws.send.SSCategorie;
|
||||
import io.quarkus.hibernate.reactive.panache.Panache;
|
||||
import io.quarkus.hibernate.reactive.panache.common.WithSession;
|
||||
import io.quarkus.runtime.annotations.RegisterForReflection;
|
||||
@ -97,8 +97,7 @@ public class RCategorie {
|
||||
|
||||
return categoryRepository.create(categoryModel);
|
||||
})
|
||||
.call(cat -> CompetitionWS.sendNotifyToOtherEditor(connection, "sendAddCategory",
|
||||
JustCategorie.from(cat)))
|
||||
.call(cat -> SSCategorie.sendAddCategory(connection, cat))
|
||||
.map(CategoryModel::getId);
|
||||
}
|
||||
|
||||
@ -121,7 +120,7 @@ public class RCategorie {
|
||||
}
|
||||
return uni;
|
||||
})
|
||||
.call(cat -> CompetitionWS.sendNotifyToOtherEditor(connection, "sendCategory", JustCategorie.from(cat)))
|
||||
.call(cat -> SSCategorie.sendCategory(connection, cat))
|
||||
.replaceWithVoid();
|
||||
}
|
||||
|
||||
@ -202,13 +201,13 @@ public class RCategorie {
|
||||
.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)))
|
||||
.chain(trees -> SSCategorie.sendTreeCategory(connection, trees)))
|
||||
.replaceWithVoid();
|
||||
}
|
||||
|
||||
@RegisterForReflection
|
||||
public record JustCategorie(long id, String name, int type, String liceName) {
|
||||
static JustCategorie from(CategoryModel m) {
|
||||
public static JustCategorie from(CategoryModel m) {
|
||||
return new JustCategorie(m.getId(), m.getName(), m.getType(), m.getLiceName());
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,14 +1,16 @@
|
||||
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.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.domain.entity.MatchEntity;
|
||||
import fr.titionfire.ffsaf.rest.exception.DForbiddenException;
|
||||
import fr.titionfire.ffsaf.rest.exception.DNotFoundException;
|
||||
import fr.titionfire.ffsaf.ws.CompetitionWS;
|
||||
import fr.titionfire.ffsaf.ws.PermLevel;
|
||||
import fr.titionfire.ffsaf.ws.send.SSMatch;
|
||||
import io.quarkus.hibernate.reactive.panache.Panache;
|
||||
import io.quarkus.hibernate.reactive.panache.common.WithSession;
|
||||
import io.quarkus.runtime.annotations.RegisterForReflection;
|
||||
@ -19,6 +21,11 @@ import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@WithSession
|
||||
@ApplicationScoped
|
||||
@RegisterForReflection
|
||||
@ -31,6 +38,9 @@ public class RMatch {
|
||||
@Inject
|
||||
CombRepository combRepository;
|
||||
|
||||
@Inject
|
||||
CategoryRepository categoryRepository;
|
||||
|
||||
@Inject
|
||||
CompetitionGuestRepository competitionGuestRepository;
|
||||
|
||||
@ -44,12 +54,38 @@ public class RMatch {
|
||||
}));
|
||||
}
|
||||
|
||||
@WSReceiver(code = "getAllMatch")
|
||||
public Uni<?> getAllMatch(WebSocketConnection connection, Long l) {
|
||||
LOGGER.info("getAllMatch " + l);
|
||||
private Uni<MatchModel> creatMatch(CategoryModel categoryModel, AddMatch m) {
|
||||
return Uni.createFrom().item(() -> {
|
||||
MatchModel matchModel = new MatchModel();
|
||||
|
||||
return Uni.createFrom().item(l);
|
||||
//return matchRepository.count();
|
||||
matchModel.setCategory(categoryModel);
|
||||
matchModel.setCategory_ord(m.categorie_ord);
|
||||
matchModel.setEnd(false);
|
||||
matchModel.setPoule(m.poule);
|
||||
|
||||
return matchModel;
|
||||
})
|
||||
.call(mm -> m.c1() >= 0 ?
|
||||
combRepository.findById(m.c1()).invoke(mm::setC1_id) :
|
||||
competitionGuestRepository.findById(m.c1() * -1).invoke(mm::setC1_guest))
|
||||
.call(mm -> m.c2() >= 0 ?
|
||||
combRepository.findById(m.c2()).invoke(mm::setC2_id) :
|
||||
competitionGuestRepository.findById(m.c2() * -1).invoke(mm::setC2_guest));
|
||||
}
|
||||
|
||||
@WSReceiver(code = "addMatch", permission = PermLevel.ADMIN)
|
||||
public Uni<Void> addMatch(WebSocketConnection connection, AddMatch m) {
|
||||
return categoryRepository.findById(m.categorie)
|
||||
.invoke(Unchecked.consumer(o -> {
|
||||
if (o == null)
|
||||
throw new DNotFoundException("Catégorie non trouver");
|
||||
if (!o.getCompet().getUuid().equals(connection.pathParam("uuid")))
|
||||
throw new DForbiddenException("Permission denied");
|
||||
}))
|
||||
.chain(categoryModel -> creatMatch(categoryModel, m))
|
||||
.chain(mm -> Panache.withTransaction(() -> matchRepository.create(mm)))
|
||||
.call(mm -> SSMatch.sendMatch(connection, MatchEntity.fromModel(mm)))
|
||||
.replaceWithVoid();
|
||||
}
|
||||
|
||||
@WSReceiver(code = "updateMatchComb", permission = PermLevel.ADMIN)
|
||||
@ -85,12 +121,84 @@ public class RMatch {
|
||||
mm.setC2_guest(null);
|
||||
}))
|
||||
.chain(mm -> Panache.withTransaction(() -> matchRepository.persist(mm)))
|
||||
.call(mm -> CompetitionWS.sendNotifyToOtherEditor(connection, "sendMatch",
|
||||
MatchEntity.fromModel(mm)))
|
||||
.call(mm -> SSMatch.sendMatch(connection, MatchEntity.fromModel(mm)))
|
||||
.replaceWithVoid();
|
||||
}
|
||||
|
||||
@WSReceiver(code = "updateMatchOrder", permission = PermLevel.ADMIN)
|
||||
public Uni<Void> updateMatchComb(WebSocketConnection connection, MatchOrder order) {
|
||||
return getById(order.id(), connection)
|
||||
.call(m -> matchRepository.update(
|
||||
"category_ord = category_ord + 1 WHERE category_ord >= ?1 AND category_ord < ?2", order.pos,
|
||||
m.getCategory_ord()))
|
||||
.call(m -> matchRepository.update(
|
||||
"category_ord = category_ord - 1 WHERE category_ord <= ?1 AND category_ord > ?2", order.pos,
|
||||
m.getCategory_ord()))
|
||||
.invoke(m -> m.setCategory_ord(order.pos))
|
||||
.call(m -> Panache.withTransaction(() -> matchRepository.persist(m)))
|
||||
.call(mm -> SSMatch.sendMatchOrder(connection, order))
|
||||
.replaceWithVoid();
|
||||
}
|
||||
|
||||
@WSReceiver(code = "deleteMatch", permission = PermLevel.ADMIN)
|
||||
public Uni<Void> deleteMatch(WebSocketConnection connection, Long idMatch) {
|
||||
return getById(idMatch, connection)
|
||||
.chain(matchModel -> Panache.withTransaction(() -> matchRepository.delete(matchModel)))
|
||||
.call(__ -> SSMatch.sendDeleteMatch(connection, idMatch))
|
||||
.replaceWithVoid();
|
||||
}
|
||||
|
||||
@WSReceiver(code = "recalculateMatch", permission = PermLevel.ADMIN)
|
||||
public Uni<Void> recalculateMatch(WebSocketConnection connection, RecalculateMatch data) {
|
||||
ArrayList<MatchEntity> matches = new ArrayList<>();
|
||||
return categoryRepository.findById(data.categorie)
|
||||
.invoke(Unchecked.consumer(o -> {
|
||||
if (o == null)
|
||||
throw new DNotFoundException("Catégorie non trouver");
|
||||
if (!o.getCompet().getUuid().equals(connection.pathParam("uuid")))
|
||||
throw new DForbiddenException("Permission denied");
|
||||
}))
|
||||
.call(cm -> matchRepository.delete("id IN ?1 AND category = ?2", data.matchesToRemove, cm)
|
||||
.call(__ -> SSMatch.sendDeleteMatch(connection, data.matchesToRemove)))
|
||||
.call(cm -> matchRepository.list("id IN ?1 AND category = ?2",
|
||||
Stream.concat(data.matchOrderToUpdate.keySet().stream(),
|
||||
data.matchPouleToUpdate.keySet().stream())
|
||||
.distinct().toList(), cm)
|
||||
.invoke(matchModels -> matchModels.forEach(model -> {
|
||||
if (data.matchPouleToUpdate.containsKey(model.getId()))
|
||||
model.setPoule(data.matchPouleToUpdate.get(model.getId()));
|
||||
if (data.matchOrderToUpdate.containsKey(model.getId()))
|
||||
model.setCategory_ord(data.matchOrderToUpdate.get(model.getId()));
|
||||
}))
|
||||
.call(mm -> Panache.withTransaction(() -> matchRepository.persist(mm)))
|
||||
.invoke(mm -> matches.addAll(mm.stream().map(MatchEntity::fromModel).toList()))
|
||||
)
|
||||
.chain(categoryModel -> {
|
||||
Uni<List<MatchModel>> uni = Uni.createFrom().item(new ArrayList<>());
|
||||
for (AddMatch match : data.newMatch)
|
||||
uni = uni.call(l -> creatMatch(categoryModel, match).invoke(l::add));
|
||||
return uni;
|
||||
}
|
||||
)
|
||||
.chain(mm -> Panache.withTransaction(() -> matchRepository.create(mm))
|
||||
.invoke(__ -> matches.addAll(mm.stream().map(MatchEntity::fromModel).toList())))
|
||||
.call(__ -> SSMatch.sendMatch(connection, matches))
|
||||
.replaceWithVoid();
|
||||
}
|
||||
|
||||
@RegisterForReflection
|
||||
public record MatchComb(long id, Long c1, Long c2) {
|
||||
}
|
||||
|
||||
@RegisterForReflection
|
||||
public record MatchOrder(long id, long pos) {
|
||||
}
|
||||
|
||||
@RegisterForReflection
|
||||
public record AddMatch(long categorie, long categorie_ord, char poule, long c1, long c2) {
|
||||
}
|
||||
|
||||
public record RecalculateMatch(long categorie, List<AddMatch> newMatch, HashMap<Long, Integer> matchOrderToUpdate,
|
||||
HashMap<Long, Character> matchPouleToUpdate, List<Long> matchesToRemove) {
|
||||
}
|
||||
}
|
||||
|
||||
33
src/main/java/fr/titionfire/ffsaf/ws/send/SSCategorie.java
Normal file
33
src/main/java/fr/titionfire/ffsaf/ws/send/SSCategorie.java
Normal file
@ -0,0 +1,33 @@
|
||||
package fr.titionfire.ffsaf.ws.send;
|
||||
|
||||
import fr.titionfire.ffsaf.data.model.CategoryModel;
|
||||
import fr.titionfire.ffsaf.domain.entity.TreeEntity;
|
||||
import fr.titionfire.ffsaf.ws.CompetitionWS;
|
||||
import fr.titionfire.ffsaf.ws.recv.RCategorie;
|
||||
import io.quarkus.websockets.next.WebSocketConnection;
|
||||
import io.smallrye.mutiny.Uni;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class SSCategorie {
|
||||
|
||||
public static Uni<Void> sendAddCategory(WebSocketConnection connection, CategoryModel category) {
|
||||
return SSCategorie.sendAddCategory(connection, RCategorie.JustCategorie.from(category));
|
||||
}
|
||||
|
||||
public static Uni<Void> sendAddCategory(WebSocketConnection connection, RCategorie.JustCategorie justCategorie) {
|
||||
return CompetitionWS.sendNotifyToOtherEditor(connection, "sendAddCategory", justCategorie);
|
||||
}
|
||||
|
||||
public static Uni<Void> sendCategory(WebSocketConnection connection, CategoryModel category) {
|
||||
return SSCategorie.sendCategory(connection, RCategorie.JustCategorie.from(category));
|
||||
}
|
||||
|
||||
public static Uni<Void> sendCategory(WebSocketConnection connection, RCategorie.JustCategorie justCategorie) {
|
||||
return CompetitionWS.sendNotifyToOtherEditor(connection, "sendCategory", justCategorie);
|
||||
}
|
||||
|
||||
public static Uni<Void> sendTreeCategory(WebSocketConnection connection, List<TreeEntity> treeEntities) {
|
||||
return CompetitionWS.sendNotifyToOtherEditor(connection, "sendTreeCategory", treeEntities);
|
||||
}
|
||||
}
|
||||
32
src/main/java/fr/titionfire/ffsaf/ws/send/SSMatch.java
Normal file
32
src/main/java/fr/titionfire/ffsaf/ws/send/SSMatch.java
Normal file
@ -0,0 +1,32 @@
|
||||
package fr.titionfire.ffsaf.ws.send;
|
||||
|
||||
import fr.titionfire.ffsaf.domain.entity.MatchEntity;
|
||||
import fr.titionfire.ffsaf.ws.CompetitionWS;
|
||||
import fr.titionfire.ffsaf.ws.recv.RMatch;
|
||||
import io.quarkus.websockets.next.WebSocketConnection;
|
||||
import io.smallrye.mutiny.Uni;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class SSMatch {
|
||||
|
||||
public static Uni<Void> sendMatch(WebSocketConnection connection, MatchEntity matchEntity) {
|
||||
return SSMatch.sendMatch(connection, List.of(matchEntity));
|
||||
}
|
||||
|
||||
public static Uni<Void> sendMatch(WebSocketConnection connection, List<MatchEntity> matchEntities) {
|
||||
return CompetitionWS.sendNotifyToOtherEditor(connection, "sendMatch", matchEntities);
|
||||
}
|
||||
|
||||
public static Uni<Void> sendMatchOrder(WebSocketConnection connection, RMatch.MatchOrder matchOrder) {
|
||||
return CompetitionWS.sendNotifyToOtherEditor(connection, "sendMatchOrder", matchOrder);
|
||||
}
|
||||
|
||||
public static Uni<Void> sendDeleteMatch(WebSocketConnection connection, Long l) {
|
||||
return SSMatch.sendDeleteMatch(connection, List.of(l));
|
||||
}
|
||||
|
||||
public static Uni<Void> sendDeleteMatch(WebSocketConnection connection, List<Long> longs) {
|
||||
return CompetitionWS.sendNotifyToOtherEditor(connection, "sendDeleteMatch", longs);
|
||||
}
|
||||
}
|
||||
52
src/main/webapp/package-lock.json
generated
52
src/main/webapp/package-lock.json
generated
@ -8,6 +8,9 @@
|
||||
"name": "webapp",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.5.1",
|
||||
"@fortawesome/free-brands-svg-icons": "^6.5.1",
|
||||
"@fortawesome/free-regular-svg-icons": "^6.5.1",
|
||||
@ -399,6 +402,55 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@dnd-kit/accessibility": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz",
|
||||
"integrity": "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@dnd-kit/core": {
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz",
|
||||
"integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==",
|
||||
"dependencies": {
|
||||
"@dnd-kit/accessibility": "^3.1.1",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
"tslib": "^2.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@dnd-kit/sortable": {
|
||||
"version": "10.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-10.0.0.tgz",
|
||||
"integrity": "sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==",
|
||||
"dependencies": {
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
"tslib": "^2.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@dnd-kit/core": "^6.3.0",
|
||||
"react": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@dnd-kit/utilities": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz",
|
||||
"integrity": "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emotion/is-prop-valid": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz",
|
||||
|
||||
@ -10,6 +10,9 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.5.1",
|
||||
"@fortawesome/free-brands-svg-icons": "^6.5.1",
|
||||
"@fortawesome/free-regular-svg-icons": "^6.5.1",
|
||||
|
||||
@ -1,11 +1,26 @@
|
||||
import {useLoadingSwitcher} from "../../../hooks/useLoading.jsx";
|
||||
import {useRequestWS, useWS} from "../../../hooks/useWS.jsx";
|
||||
import {useEffect, useReducer, useRef, useState} from "react";
|
||||
import React, {useEffect, useReducer, useRef, useState} from "react";
|
||||
import {MarchReducer} from "../../../utils/MatchReducer.jsx";
|
||||
import {CombName, useCombs, useCombsDispatch} from "../../../hooks/useComb.jsx";
|
||||
import {from_sendTree, TreeNode} from "../../../utils/TreeUtils.js";
|
||||
import {DrawGraph} from "../../result/DrawGraph.jsx";
|
||||
import {SelectCombModalContent} from "./SelectCombModalContent.jsx";
|
||||
import {createMatch, scoreToString, win} from "../../../utils/CompetitionTools.js";
|
||||
|
||||
import {DndContext, closestCenter, KeyboardSensor, PointerSensor, useSensor, useSensors} from '@dnd-kit/core';
|
||||
import {SortableContext, sortableKeyboardCoordinates, verticalListSortingStrategy} from '@dnd-kit/sortable';
|
||||
import {useSortable} from '@dnd-kit/sortable';
|
||||
import {CSS} from '@dnd-kit/utilities';
|
||||
import {toast} from "react-toastify";
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
import {faTrash} from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
function CupImg() {
|
||||
return <img decoding="async" loading="lazy" width={"16"} height={"16"} className="wp-image-1635"
|
||||
style={{width: "16px"}} src="/img/171891.png"
|
||||
alt=""/>
|
||||
}
|
||||
|
||||
export function CategoryContent({cat, catId, setCat}) {
|
||||
const setLoading = useLoadingSwitcher()
|
||||
@ -43,21 +58,36 @@ export function CategoryContent({cat, catId, setCat}) {
|
||||
combDispatch({type: 'SET_ALL', payload: {source: "match", data: combsToAdd}});
|
||||
}
|
||||
|
||||
const matchListener = ({data}) => {
|
||||
reducer({type: 'UPDATE_OR_ADD', payload: {...data, c1: data.c1?.id, c2: data.c2?.id}});
|
||||
combDispatch({type: 'SET_ALL', payload: {source: "match", data: [data.c1, data.c2].filter(d => d != null)}});
|
||||
const matchListener = ({data: datas}) => {
|
||||
for (const data of datas) {
|
||||
reducer({type: 'UPDATE_OR_ADD', payload: {...data, c1: data.c1?.id, c2: data.c2?.id}});
|
||||
combDispatch({type: 'SET_ALL', payload: {source: "match", data: [data.c1, data.c2].filter(d => d != null)}});
|
||||
|
||||
if (data.c1 !== null && !groupsRef.current.some(g => g.id === data.c1?.id))
|
||||
setGroups(prev => [...prev, {id: data.c1?.id, poule: data.poule}]);
|
||||
if (data.c2 !== null && !groupsRef.current.some(g => g.id === data.c2?.id))
|
||||
setGroups(prev => [...prev, {id: data.c2?.id, poule: data.poule}]);
|
||||
if (data.c1 !== null && !groupsRef.current.some(g => g.id === data.c1?.id))
|
||||
setGroups(prev => [...prev, {id: data.c1?.id, poule: data.poule}]);
|
||||
if (data.c2 !== null && !groupsRef.current.some(g => g.id === data.c2?.id))
|
||||
setGroups(prev => [...prev, {id: data.c2?.id, poule: data.poule}]);
|
||||
}
|
||||
}
|
||||
|
||||
const matchOrder = ({data}) => {
|
||||
reducer({type: 'REORDER', payload: data});
|
||||
}
|
||||
|
||||
const deleteMatch = ({data: datas}) => {
|
||||
for (const data of datas)
|
||||
reducer({type: 'REMOVE', payload: data});
|
||||
}
|
||||
|
||||
dispatch({type: 'addListener', payload: {callback: treeListener, code: 'sendTreeCategory'}})
|
||||
dispatch({type: 'addListener', payload: {callback: matchListener, code: 'sendMatch'}})
|
||||
dispatch({type: 'addListener', payload: {callback: matchOrder, code: 'sendMatchOrder'}})
|
||||
dispatch({type: 'addListener', payload: {callback: deleteMatch, code: 'sendDeleteMatch'}})
|
||||
return () => {
|
||||
dispatch({type: 'removeListener', payload: treeListener})
|
||||
dispatch({type: 'removeListener', payload: matchListener})
|
||||
dispatch({type: 'removeListener', payload: matchOrder})
|
||||
dispatch({type: 'removeListener', payload: deleteMatch})
|
||||
}
|
||||
}, [cat]);
|
||||
|
||||
@ -84,9 +114,13 @@ export function CategoryContent({cat, catId, setCat}) {
|
||||
combDispatch({type: 'SET_ALL', payload: {source: "match", data: combsToAdd}});
|
||||
|
||||
const activeMatches = matches2.filter(m => m.poule !== '-')
|
||||
|
||||
const combsIDs = activeMatches.flatMap(d => [d.c1, d.c2]).filter((v, i, a) => v != null && a.indexOf(v) === i)
|
||||
.map(d => {
|
||||
return {id: d, poule: activeMatches.find(m => m.c1 === d || m.c2 === d)?.poule}
|
||||
let poule = activeMatches.find(m => (m.c1 === d || m.c2 === d) && m.categorie_ord !== -42)?.poule
|
||||
if (!poule)
|
||||
poule = '-'
|
||||
return {id: d, poule: poule}
|
||||
})
|
||||
setGroups(combsIDs)
|
||||
}).finally(() => setLoading(0))
|
||||
@ -110,7 +144,7 @@ export function CategoryContent({cat, catId, setCat}) {
|
||||
<AddComb groups={groups} setGroups={setGroups} removeGroup={removeGroup}/>
|
||||
</div>
|
||||
<div className="col-md-9">
|
||||
{cat && <ListMatch cat={cat} matches={matches} groups={groups}/>}
|
||||
{cat && <ListMatch cat={cat} matches={matches} groups={groups} reducer={reducer}/>}
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
@ -266,8 +300,59 @@ function GroupModalContent({combId, groups, setGroups, removeGroup}) {
|
||||
|
||||
}
|
||||
|
||||
function ListMatch({cat, matches, groups}) {
|
||||
function ListMatch({cat, matches, groups, reducer}) {
|
||||
const {sendRequest} = useWS();
|
||||
const [type, setType] = useState(1);
|
||||
const bthRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
if ((cat.type & type) === 0)
|
||||
setType(cat.type);
|
||||
}, [cat]);
|
||||
|
||||
const handleCreatMatch = () => {
|
||||
if (matches.some(m => m.poule !== '-')) {
|
||||
bthRef.current.click();
|
||||
return;
|
||||
}
|
||||
recalculateMatch(0);
|
||||
}
|
||||
|
||||
const recalculateMatch = (mode) => {
|
||||
let matchesToKeep = [];
|
||||
let matchesToRemove = [];
|
||||
if (mode === 2) {
|
||||
matchesToKeep = matches.filter(m => m.categorie === cat.id && m.categorie_ord !== -42);
|
||||
} else if (mode === 1) {
|
||||
matchesToKeep = matches.filter(m => m.categorie === cat.id && m.end === true && m.categorie_ord !== -42)
|
||||
matchesToRemove = matches.filter(m => m.categorie === cat.id && m.end === false && m.categorie_ord !== -42)
|
||||
} else {
|
||||
matchesToRemove = matches.filter(m => m.categorie === cat.id && m.categorie_ord !== -42)
|
||||
}
|
||||
|
||||
try {
|
||||
const {newMatch, matchOrderToUpdate, matchPouleToUpdate} = createMatch(cat, matchesToKeep, groups.filter(g => g.poule !== '-'))
|
||||
|
||||
toast.promise(sendRequest("recalculateMatch", {
|
||||
categorie: cat.id,
|
||||
newMatch,
|
||||
matchOrderToUpdate: Object.fromEntries(matchOrderToUpdate),
|
||||
matchPouleToUpdate: Object.fromEntries(matchPouleToUpdate),
|
||||
matchesToRemove: matchesToRemove.map(m => m.id)
|
||||
}),
|
||||
{
|
||||
pending: 'Création des matchs en cours...',
|
||||
success: 'Matchs créés avec succès !',
|
||||
error: 'Erreur lors de la création des matchs',
|
||||
})
|
||||
.finally(() => {
|
||||
console.log("Finished creating matches");
|
||||
})
|
||||
|
||||
} catch (e) {
|
||||
toast.error("Erreur lors de la création des matchs: " + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
return <>
|
||||
{cat.type === 3 && <>
|
||||
@ -285,12 +370,281 @@ function ListMatch({cat, matches, groups}) {
|
||||
</ul>
|
||||
</>}
|
||||
|
||||
{type === 1 && <>
|
||||
<MatchList matches={matches} groups={groups} cat={cat} reducer={reducer}/>
|
||||
<button className="btn btn-primary float-end" onClick={handleCreatMatch}>Créer les matchs</button>
|
||||
</>}
|
||||
|
||||
{type === 2 && <>
|
||||
<BuildTree treeData={cat.trees} matches={matches} groups={groups}/>
|
||||
</>}
|
||||
|
||||
<button ref={bthRef} data-bs-toggle="modal" data-bs-target="#makeMatchMode" style={{display: "none"}}>open</button>
|
||||
<div className="modal fade" id="makeMatchMode" tabIndex="-1" role="dialog" aria-labelledby="makeMatchModeLabel" aria-hidden="true">
|
||||
<div className="modal-dialog">
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<h4 className="modal-title" id="makeMatchModeLabel">Attention</h4>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
Il y a déjà des matchs dans cette poule, que voulez-vous faire avec ?
|
||||
</div>
|
||||
<div className="modal-footer">
|
||||
<div className="btn-group" role="group" aria-label="Basic mixed styles example">
|
||||
<button className="btn btn-primary" data-dismiss="modal" data-bs-dismiss="modal" onClick={() => recalculateMatch(2)}>
|
||||
Tout conserver
|
||||
</button>
|
||||
<button className="btn btn-success" data-bs-dismiss="modal" onClick={() => recalculateMatch(1)}>
|
||||
Conserver uniquement les matchs terminés
|
||||
</button>
|
||||
<button className="btn btn-danger" data-bs-dismiss="modal" onClick={() => recalculateMatch(0)}>
|
||||
Ne rien conserver
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
|
||||
function MatchList({matches, cat, groups, reducer}) {
|
||||
const {sendRequest} = useWS();
|
||||
const setLoading = useLoadingSwitcher()
|
||||
const selectRef = useRef(null)
|
||||
const tableRef = useRef(null)
|
||||
const lastMatchClick = useRef(null)
|
||||
const [combSelect, setCombSelect] = useState(0)
|
||||
const [combC1nm, setCombC1nm] = useState(null)
|
||||
const [combC2nm, setCombC2nm] = useState(null)
|
||||
|
||||
const liceName = (cat.liceName || "N/A").split(";");
|
||||
const marches2 = matches.filter(m => m.categorie_ord !== -42)
|
||||
.sort((a, b) => a.categorie_ord - b.categorie_ord)
|
||||
.map(m => ({...m, win: win(m.scores)}))
|
||||
|
||||
const sensors = useSensors(
|
||||
useSensor(PointerSensor),
|
||||
useSensor(KeyboardSensor, {
|
||||
coordinateGetter: sortableKeyboardCoordinates,
|
||||
})
|
||||
);
|
||||
|
||||
const handleDragEnd = async (event) => {
|
||||
const {active, over} = event;
|
||||
if (active.id !== over.id) {
|
||||
const newIndex = marches2.findIndex(m => m.id === over.id);
|
||||
reducer({type: 'REORDER', payload: {id: active.id, pos: newIndex}});
|
||||
sendRequest('updateMatchOrder', {id: active.id, pos: newIndex}).then(__ => {
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!combC1nm || !combC2nm)
|
||||
return;
|
||||
if (combC1nm === combC2nm) {
|
||||
toast.error("Un combattant ne peut pas s'affronter lui-même !");
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(1)
|
||||
sendRequest('addMatch', {
|
||||
categorie: cat.id,
|
||||
categorie_ord: Math.max(...marches2.map(o => o.categorie_ord)) + 1,
|
||||
poule: groups.find(g => g.id === combC1nm).poule,
|
||||
c1: combC1nm,
|
||||
c2: combC2nm
|
||||
}).then(_ => {
|
||||
setCombC1nm(null)
|
||||
setCombC2nm(null)
|
||||
}).finally(() => setLoading(0))
|
||||
|
||||
}, [combC1nm, combC2nm]);
|
||||
|
||||
useEffect(() => {
|
||||
if (lastMatchClick.current == null)
|
||||
return
|
||||
|
||||
const {matchId, combId} = lastMatchClick.current
|
||||
if (matchId === -1) {
|
||||
setCombC1nm(combSelect === 0 ? null : combSelect);
|
||||
onClickVoid()
|
||||
return
|
||||
}
|
||||
if (matchId === -2) {
|
||||
setCombC2nm(combSelect === 0 ? null : combSelect);
|
||||
onClickVoid()
|
||||
return
|
||||
}
|
||||
|
||||
const match = matches.find(m => m.id === matchId)
|
||||
if (!match)
|
||||
return
|
||||
|
||||
const combSelect_ = combSelect === 0 ? null : combSelect;
|
||||
const data = {id: match.id, c1: match.c1, c2: match.c2}
|
||||
if (match.c1 === combId) {
|
||||
if (match.c1 === combSelect_)
|
||||
return
|
||||
data.c1 = combSelect_
|
||||
} else if (match.c2 === combId) {
|
||||
if (match.c2 === combSelect_)
|
||||
return
|
||||
data.c2 = combSelect_
|
||||
}
|
||||
|
||||
if (data.c1 === data.c2) {
|
||||
toast.error("Un combattant ne peut pas s'affronter lui-même !");
|
||||
onClickVoid()
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(1)
|
||||
sendRequest('updateMatchComb', data)
|
||||
.finally(() => {
|
||||
setLoading(0)
|
||||
onClickVoid()
|
||||
})
|
||||
}, [combSelect])
|
||||
|
||||
const handleDelMatch = (matchId) => {
|
||||
const match = matches.find(m => m.id === matchId)
|
||||
if (!match)
|
||||
return;
|
||||
|
||||
if (!confirm("Ce match a déjà des résultats, êtes-vous sûr de vouloir le supprimer ?"))
|
||||
return;
|
||||
|
||||
setLoading(1)
|
||||
sendRequest('deleteMatch', matchId)
|
||||
.finally(() => setLoading(0))
|
||||
}
|
||||
|
||||
const handleCombClick = (e, matchId, combId) => {
|
||||
e.stopPropagation();
|
||||
const tableRect = tableRef.current.getBoundingClientRect();
|
||||
const rect = e.currentTarget.getBoundingClientRect();
|
||||
|
||||
const sel = selectRef.current;
|
||||
sel.style.top = (rect.y - tableRect.y) + "px";
|
||||
sel.style.left = (rect.x - tableRect.x) + "px";
|
||||
sel.style.width = rect.width + "px";
|
||||
sel.style.height = rect.height + "px";
|
||||
sel.style.display = "block";
|
||||
|
||||
setCombSelect(combId || 0);
|
||||
lastMatchClick.current = {matchId, combId};
|
||||
}
|
||||
|
||||
const onClickVoid = () => {
|
||||
const sel = selectRef.current;
|
||||
sel.style.display = "none";
|
||||
lastMatchClick.current = null;
|
||||
}
|
||||
|
||||
const combsIDs = groups.map(m => m.id);
|
||||
return <div style={{position: "relative"}}>
|
||||
<DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
|
||||
<SortableContext items={marches2.map(m => m.id)} strategy={verticalListSortingStrategy}>
|
||||
<div ref={tableRef} className="table-responsive-lg" style={{fontSize: "1rem"}} onClick={onClickVoid}>
|
||||
<table className="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style={{textAlign: "center", paddingLeft: "0.2em", paddingRight: "0.2em"}} scope="col">N°</th>
|
||||
<th style={{textAlign: "center", paddingLeft: "0.2em", paddingRight: "0.2em"}} scope="col">Poule</th>
|
||||
<th style={{textAlign: "center", paddingLeft: "0.2em", paddingRight: "0.2em"}} scope="col">Lice</th>
|
||||
<th style={{textAlign: "center"}} scope="col"></th>
|
||||
<th style={{textAlign: "center"}} scope="col">Rouge</th>
|
||||
<th style={{textAlign: "center"}} scope="col">Blue</th>
|
||||
<th style={{textAlign: "center"}} scope="col"></th>
|
||||
<th style={{textAlign: "center"}} scope="col">Résultat</th>
|
||||
<th style={{textAlign: "center"}} scope="col"></th>
|
||||
<th style={{textAlign: "center"}} scope="col"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="table-group-divider">
|
||||
{marches2.map((m, index) => (
|
||||
<SortableRow key={m.id} id={m.id}>
|
||||
<th style={{textAlign: "center", cursor: "auto"}} scope="row">{index + 1}</th>
|
||||
<td style={{textAlign: "center", cursor: "auto"}}>{m.poule}</td>
|
||||
<td style={{textAlign: "center", cursor: "auto"}}>{liceName[index % liceName.length]}</td>
|
||||
<td style={{textAlign: "right", cursor: "auto", paddingRight: "0"}}>{m.end && m.win > 0 && <CupImg/>}</td>
|
||||
<td style={{textAlign: "center", minWidth: "11em", paddingLeft: "0.2em"}}
|
||||
onClick={e => handleCombClick(e, m.id, m.c1)}>
|
||||
<small><CombName combId={m.c1}/></small></td>
|
||||
<td style={{textAlign: "center", minWidth: "11em", paddingRight: "0.2em"}}
|
||||
onClick={e => handleCombClick(e, m.id, m.c2)}>
|
||||
<small><CombName combId={m.c2}/></small></td>
|
||||
<td style={{textAlign: "left", cursor: "auto", paddingLeft: "0"}}>{m.end && m.win < 0 && <CupImg/>}</td>
|
||||
<td style={{textAlign: "center", cursor: "auto"}}>{scoreToString(m.scores)}</td>
|
||||
<td style={{textAlign: "center", cursor: "pointer", color: "#ff1313"}} onClick={_ => handleDelMatch(m.id)}>
|
||||
<FontAwesomeIcon icon={faTrash}/></td>
|
||||
<td style={{textAlign: "center", cursor: "grab"}}>☰</td>
|
||||
</SortableRow>
|
||||
))}
|
||||
<tr>
|
||||
<td>-</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td style={{textAlign: "center", minWidth: "11em", paddingLeft: "0.2em"}}
|
||||
onClick={e => handleCombClick(e, -1, combC1nm)}>
|
||||
{combC1nm && <small><CombName combId={combC1nm}/></small>}</td>
|
||||
<td style={{textAlign: "center", minWidth: "11em", paddingRight: "0.2em"}}
|
||||
onClick={e => handleCombClick(e, -2, combC2nm)}>
|
||||
{combC2nm && <small><CombName combId={combC2nm}/></small>}</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</SortableContext>
|
||||
</DndContext>
|
||||
|
||||
<select ref={selectRef} className="form-select" style={{position: "absolute", top: 0, left: 0, display: "none"}}
|
||||
value={combSelect} onChange={e => setCombSelect(Number(e.target.value))}>
|
||||
<option value={0}>-- Sélectionner un combattant --</option>
|
||||
{combsIDs.map((combId) => (
|
||||
<option key={combId} value={combId}><CombName combId={combId}/></option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
}
|
||||
|
||||
function SortableRow({id, children}) {
|
||||
const {
|
||||
attributes,
|
||||
listeners,
|
||||
setNodeRef,
|
||||
transform,
|
||||
transition,
|
||||
isDragging,
|
||||
} = useSortable({id});
|
||||
|
||||
const style = {
|
||||
transform: CSS.Transform.toString(transform),
|
||||
transition,
|
||||
opacity: isDragging ? 0.5 : 1,
|
||||
touchAction: 'none', // Désactive le scroll par défaut pendant le drag
|
||||
};
|
||||
|
||||
return (
|
||||
<tr ref={setNodeRef} style={style} {...attributes}>
|
||||
{React.Children.map(children, (child, i) =>
|
||||
i === children.length - 1 // Dernière cellule (icône de drag)
|
||||
? React.cloneElement(child, {
|
||||
...child.props, ...attributes, ...listeners, style: {...child.props.style, cursor: "grab", touchAction: 'none'},
|
||||
})
|
||||
: child
|
||||
)}
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
function BuildTree({treeData, matches, groups}) {
|
||||
const scrollRef = useRef(null)
|
||||
const selectRef = useRef(null)
|
||||
|
||||
@ -88,7 +88,7 @@ export function SelectCombModalContent({data, setGroups}) {
|
||||
}
|
||||
dispatch({type: 'addListener', payload: {callback: sendRegisterRemove, code: 'sendRegisterRemove'}})
|
||||
return () => {
|
||||
dispatch({type: 'removeListener', payload: {callback: sendRegisterRemove, code: 'sendRegisterRemove'}})
|
||||
dispatch({type: 'removeListener', payload: sendRegisterRemove})
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import {useEffect, useRef} from "react";
|
||||
import {win} from "../../utils/CompetitionTools.js";
|
||||
|
||||
const max_x = 500;
|
||||
const size = 24;
|
||||
@ -235,17 +236,6 @@ export function DrawGraph({
|
||||
}
|
||||
};
|
||||
|
||||
// Fonction pour déterminer le gagnant
|
||||
const win = (scores) => {
|
||||
let sum = 0;
|
||||
for (const score of scores) {
|
||||
if (score.s1 === -1000 || score.s2 === -1000) continue;
|
||||
if (score.s1 > score.s2) sum++;
|
||||
else if (score.s1 < score.s2) sum--;
|
||||
}
|
||||
return sum;
|
||||
};
|
||||
|
||||
// Effet pour dessiner le graphique
|
||||
useEffect(() => {
|
||||
if (root.length === 0) return;
|
||||
|
||||
@ -6,6 +6,7 @@ import {ThreeDots} from "react-loader-spinner";
|
||||
import {useEffect, useState} from "react";
|
||||
import {DrawGraph} from "./DrawGraph.jsx";
|
||||
import {TreeNode} from "../../utils/TreeUtils.js";
|
||||
import {scoreToString} from "../../utils/CompetitionTools.js";
|
||||
|
||||
function CupImg() {
|
||||
return <img decoding="async" loading="lazy" width="16" height="16" className="wp-image-1635"
|
||||
@ -72,25 +73,6 @@ function MenuBar({data, resultShow, setResultShow}) {
|
||||
*/
|
||||
}
|
||||
|
||||
function scoreToString(score) {
|
||||
const scorePrint = (s1) => {
|
||||
switch (s1) {
|
||||
case -997:
|
||||
return "disc.";
|
||||
case -998:
|
||||
return "abs.";
|
||||
case -999:
|
||||
return "for.";
|
||||
case -1000:
|
||||
return "";
|
||||
default:
|
||||
return String(s1);
|
||||
}
|
||||
}
|
||||
|
||||
return score.map(o => scorePrint(o.at(0)) + "-" + scorePrint(o.at(1))).join(" | ");
|
||||
}
|
||||
|
||||
function BuildMatchArray({matchs}) {
|
||||
return <>
|
||||
<table className="table table-striped">
|
||||
|
||||
238
src/main/webapp/src/utils/CompetitionTools.js
Normal file
238
src/main/webapp/src/utils/CompetitionTools.js
Normal file
@ -0,0 +1,238 @@
|
||||
export function win(scores) {
|
||||
let sum = 0
|
||||
for (const score of scores) {
|
||||
if (score.s1 === -1000 || score.s2 === -1000) continue
|
||||
if (score.s1 > score.s2) sum++
|
||||
else if (score.s1 < score.s2) sum--
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
export function scoreToString(score) {
|
||||
const scorePrint = (s1) => {
|
||||
switch (s1) {
|
||||
case -997:
|
||||
return "disc."
|
||||
case -998:
|
||||
return "abs."
|
||||
case -999:
|
||||
return "for."
|
||||
case -1000:
|
||||
return ""
|
||||
default:
|
||||
return String(s1)
|
||||
}
|
||||
}
|
||||
if (score === null || score === undefined || score.length === 0)
|
||||
return ""
|
||||
|
||||
if (Array.isArray(score[0]))
|
||||
return score.map(o => scorePrint(o.at(0)) + "-" + scorePrint(o.at(1))).join(" | ")
|
||||
else
|
||||
return score.sort((a, b) => a.n_round - b.n_round).map(o => scorePrint(o.s1) + "-" + scorePrint(o.s2)).join(" | ")
|
||||
}
|
||||
|
||||
export function createMatch(category, matchs, groups) {
|
||||
const combs_2 = {}
|
||||
for (const group of groups) {
|
||||
if (!combs_2.hasOwnProperty(group.poule))
|
||||
combs_2[group.poule] = []
|
||||
if (!combs_2[group.poule].includes(group.id))
|
||||
combs_2[group.poule].push(group.id)
|
||||
}
|
||||
|
||||
const ord = new Map()
|
||||
const newMatch = []
|
||||
const matchPouleToUpdate = new Map()
|
||||
|
||||
let groupeNumber = 0
|
||||
const numberOfGroup = Object.keys(combs_2).length
|
||||
|
||||
for (const [k, combs_1] of Object.entries(combs_2)) {
|
||||
let matchsList = calcPoule(combs_1.length, category.liceName.split(";").length)
|
||||
|
||||
let indexMatch = 0;
|
||||
const combs = new Array(combs_1.length).fill(0);
|
||||
|
||||
// Trouver le premier match valide
|
||||
while (indexMatch < matchs.length && (!combs_1.includes(matchs[indexMatch].c1) || !combs_1.includes(matchs[indexMatch].c2))) {
|
||||
indexMatch++;
|
||||
}
|
||||
|
||||
// Attribution aléatoire des combattants
|
||||
for (const set of matchsList) {
|
||||
if (indexMatch < matchs.length) {
|
||||
if (!combs.includes(matchs[indexMatch].c1)) {
|
||||
if (combs[set[0] - 1] === 0)
|
||||
combs[set[0] - 1] = matchs[indexMatch].c1
|
||||
else if (combs[set[1] - 1] === 0)
|
||||
combs[set[1] - 1] = matchs[indexMatch].c1
|
||||
|
||||
if (combs.includes(matchs[indexMatch].c1)) {
|
||||
const index = combs_1.indexOf(matchs[indexMatch].c1)
|
||||
if (index !== -1)
|
||||
combs_1.splice(index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
if (!combs.includes(matchs[indexMatch].c2)) {
|
||||
if (combs[set[0] - 1] === 0)
|
||||
combs[set[0] - 1] = matchs[indexMatch].c2
|
||||
else if (combs[set[1] - 1] === 0)
|
||||
combs[set[1] - 1] = matchs[indexMatch].c2
|
||||
|
||||
if (combs.includes(matchs[indexMatch].c2)) {
|
||||
const index = combs_1.indexOf(matchs[indexMatch].c2)
|
||||
if (index !== -1)
|
||||
combs_1.splice(index, 1)
|
||||
}
|
||||
}
|
||||
// Passer aux matchs suivants si nécessaire
|
||||
while (indexMatch < matchs.length &&
|
||||
((combs.includes(matchs[indexMatch].c1) && combs.includes(matchs[indexMatch].c2)) ||
|
||||
!combs_1.includes(matchs[indexMatch].c1) || !combs_1.includes(matchs[indexMatch].c2))) {
|
||||
indexMatch++
|
||||
}
|
||||
}
|
||||
|
||||
// Remplir les places vides avec des combattants restants
|
||||
if (combs[set[0] - 1] === 0) {
|
||||
if (combs_1.length > 0) {
|
||||
const index = Math.floor(Math.random() * combs_1.length)
|
||||
combs[set[0] - 1] = combs_1[index]
|
||||
combs_1.splice(index, 1)
|
||||
}
|
||||
}
|
||||
if (combs[set[1] - 1] === 0) {
|
||||
if (combs_1.length > 0) {
|
||||
const index = Math.floor(Math.random() * combs_1.length)
|
||||
combs[set[1] - 1] = combs_1[index]
|
||||
combs_1.splice(index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
if (combs_1.length === 0)
|
||||
break
|
||||
}
|
||||
|
||||
// Créer les nouveaux matchs
|
||||
for (let i = 0; i < matchsList.length; i++) {
|
||||
const set = matchsList[i]
|
||||
const matchExistant = matchs.find(
|
||||
(match) =>
|
||||
(match.c1 === combs[set[0] - 1] && match.c2 === combs[set[1] - 1]) ||
|
||||
(match.c1 === combs[set[1] - 1] && match.c2 === combs[set[0] - 1]))
|
||||
|
||||
if (matchExistant) {
|
||||
ord.set(matchExistant.id, i * numberOfGroup + groupeNumber)
|
||||
if (matchExistant.poule !== k)
|
||||
matchPouleToUpdate.set(matchExistant.id, k)
|
||||
matchs = matchs.filter((match) => match.id !== matchExistant.id)
|
||||
continue
|
||||
}
|
||||
|
||||
newMatch.push({
|
||||
categorie: category.id,
|
||||
categorie_ord: i * numberOfGroup + groupeNumber,
|
||||
poule: k,
|
||||
c1: combs[set[0] - 1],
|
||||
c2: combs[set[1] - 1],
|
||||
})
|
||||
}
|
||||
groupeNumber++
|
||||
}
|
||||
|
||||
// Mise à jour des ordres et des groupes
|
||||
const lenToAdd = matchs.length
|
||||
newMatch.forEach((match) => {
|
||||
match.categorie_ord += lenToAdd
|
||||
})
|
||||
|
||||
ord.forEach((value, key) => {
|
||||
ord.set(key, value + lenToAdd)
|
||||
})
|
||||
|
||||
for (let i = 0; i < matchs.length; i++) {
|
||||
ord.set(matchs[i].id, i)
|
||||
matchPouleToUpdate.set(matchs[i].id, '-')
|
||||
}
|
||||
|
||||
return {newMatch, matchOrderToUpdate: ord, matchPouleToUpdate}
|
||||
}
|
||||
|
||||
const tt_ronde_opti = new Map()
|
||||
tt_ronde_opti.set(2, [[1, 2]])
|
||||
tt_ronde_opti.set(3, [[2, 3], [1, 3], [1, 2]])
|
||||
tt_ronde_opti.set(4, [[1, 4], [2, 3], [1, 3], [2, 4], [3, 4], [1, 2]])
|
||||
tt_ronde_opti.set(5, [[1, 2], [3, 4], [5, 1], [2, 3], [5, 4], [1, 3], [2, 5], [4, 1], [3, 5], [4, 2]])
|
||||
tt_ronde_opti.set(6, [[1, 2], [4, 5], [2, 3], [5, 6], [3, 1], [6, 4], [2, 5], [1, 4], [5, 3], [1, 6], [4, 2], [3, 6], [5, 1], [3, 4], [6, 2]])
|
||||
tt_ronde_opti.set(7, [[1, 4], [2, 5], [3, 6], [7, 1], [5, 4], [2, 3], [6, 7], [5, 1], [4, 3], [6, 2], [5, 7], [3, 1], [4, 6], [7, 2], [3, 5], [1, 6],
|
||||
[2, 4], [7, 3], [6, 5], [1, 2], [4, 7]])
|
||||
tt_ronde_opti.set(8, [[2, 3], [1, 5], [7, 4], [6, 8], [1, 2], [3, 4], [5, 6], [8, 7], [4, 1], [5, 2], [8, 3], [6, 7], [4, 2], [8, 1], [7, 5], [3, 6],
|
||||
[2, 8], [5, 4], [6, 1], [3, 7], [4, 8], [2, 6], [3, 5], [1, 7], [4, 6], [8, 5], [7, 2], [1, 3]])
|
||||
tt_ronde_opti.set(9, [[1, 9], [2, 8], [3, 7], [4, 6], [1, 5], [2, 9], [8, 3], [7, 4], [6, 5], [1, 2], [9, 3], [8, 4], [7, 5], [6, 1], [3, 2], [9, 4],
|
||||
[5, 8], [7, 6], [3, 1], [2, 4], [5, 9], [8, 6], [7, 1], [4, 3], [5, 2], [6, 9], [8, 7], [4, 1], [5, 3], [6, 2], [9, 7], [1, 8], [4, 5], [3, 6],
|
||||
[5, 7], [9, 8]])
|
||||
tt_ronde_opti.set(10, [[1, 4], [6, 9], [2, 5], [7, 10], [3, 1], [8, 6], [4, 5], [9, 10], [2, 3], [7, 8], [5, 1], [10, 6], [4, 2], [9, 7], [5, 3],
|
||||
[10, 8], [1, 2], [6, 7], [3, 4], [8, 9], [5, 10], [1, 6], [2, 7], [3, 8], [4, 9], [6, 5], [10, 2], [8, 1], [7, 4], [9, 3], [2, 6]
|
||||
, [5, 8], [4, 10], [1, 9], [3, 7], [8, 2], [6, 4], [9, 5], [10, 3], [7, 1], [4, 8], [2, 9], [3, 6], [5, 7], [1, 10]])
|
||||
tt_ronde_opti.set(11, [[1, 2], [7, 8], [4, 5], [10, 11], [2, 3], [8, 9], [5, 6], [3, 1], [9, 7], [6, 4], [2, 5], [8, 11], [1, 4], [7, 10], [5, 3],
|
||||
[11, 9], [1, 6], [4, 2], [10, 8], [3, 6], [5, 1], [11, 7], [3, 4], [9, 10], [6, 2], [1, 7], [3, 9], [10, 4], [8, 2], [5, 11], [1, 8], [9, 2],
|
||||
[3, 10], [4, 11], [6, 7], [9, 1], [2, 10], [11, 3], [7, 5], [6, 8], [10, 1], [11, 2], [4, 7], [8, 5], [6, 9], [11, 1], [7, 3], [4, 8], [9, 5],
|
||||
[6, 10], [2, 7], [8, 3], [4, 9], [10, 5], [6, 11]])
|
||||
|
||||
function calcPoule(n_comb, n_lice) {
|
||||
if (n_comb === 1)
|
||||
throw new Error("Le nombre de combattants doit être supérieur à 1.")
|
||||
if (n_comb / 2 < n_lice)
|
||||
n_lice = Math.floor(n_comb / 2)
|
||||
|
||||
if (n_lice === 1 && n_comb <= 11) {
|
||||
return [...tt_ronde_opti.get(n_comb)]
|
||||
} else {
|
||||
return rutsch(n_comb)
|
||||
}
|
||||
}
|
||||
|
||||
function rutsch(n_comb) {
|
||||
const out = []
|
||||
let virt_n_comb = n_comb
|
||||
if (n_comb % 2 === 1) virt_n_comb++
|
||||
|
||||
// Initialisation du tableau temporaire
|
||||
const tmp = Array.from({length: virt_n_comb / 2}, () => [0, 0])
|
||||
for (let i = 0; i < virt_n_comb / 2; i++) {
|
||||
tmp[i][0] = i + 1
|
||||
tmp[i][1] = virt_n_comb - i
|
||||
out.push([...tmp[i]])
|
||||
}
|
||||
|
||||
// Boucle pour les rondes
|
||||
for (let ronde = 1; ronde < virt_n_comb - 1; ronde++) {
|
||||
// Déplacement des éléments
|
||||
const old_val = tmp[virt_n_comb / 2 - 1][0]
|
||||
for (let i = virt_n_comb / 2 - 1; i > 0; i--) {
|
||||
tmp[i][0] = tmp[i - 1][0]
|
||||
}
|
||||
tmp[0][0] = tmp[0][1]
|
||||
for (let i = 0; i < virt_n_comb / 2 - 1; i++) {
|
||||
tmp[i][1] = tmp[i + 1][1]
|
||||
}
|
||||
tmp[virt_n_comb / 2 - 1][1] = old_val
|
||||
|
||||
// Échange des clés si nécessaire
|
||||
if (tmp[1][0] === virt_n_comb) {
|
||||
tmp[1][0] = tmp[0][0]
|
||||
tmp[0][0] = tmp[0][1]
|
||||
tmp[0][1] = virt_n_comb
|
||||
}
|
||||
|
||||
// Ajout des paires à la sortie
|
||||
for (let i = 0; i < virt_n_comb / 2; i++) {
|
||||
out.push([...tmp[i]])
|
||||
}
|
||||
}
|
||||
|
||||
// Filtrer les paires dont les valeurs dépassent n_comb
|
||||
return out.filter(pair => pair[0] <= n_comb && pair[1] <= n_comb)
|
||||
}
|
||||
@ -38,6 +38,34 @@ export function MarchReducer(datas, action) {
|
||||
}
|
||||
case 'SORT':
|
||||
return datas.sort(action.payload)
|
||||
case 'REORDER':
|
||||
const oldIndex = datas.findIndex(data => data.id === action.payload.id)
|
||||
if (oldIndex === -1 || datas[oldIndex].categorie_ord === action.payload.pos)
|
||||
return datas // Do nothing
|
||||
|
||||
const oldPos = datas[oldIndex].categorie_ord
|
||||
const newPos = action.payload.pos
|
||||
|
||||
const new_datas = []
|
||||
for (const data of datas) {
|
||||
if (data.id === action.payload.id) {
|
||||
data.categorie_ord = newPos
|
||||
} else {
|
||||
if (oldPos < newPos) {
|
||||
// Moving down
|
||||
if (data.categorie_ord > oldPos && data.categorie_ord <= newPos) {
|
||||
data.categorie_ord -= 1
|
||||
}
|
||||
} else if (oldPos > newPos) {
|
||||
// Moving up
|
||||
if (data.categorie_ord < oldPos && data.categorie_ord >= newPos) {
|
||||
data.categorie_ord += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
new_datas.push(data)
|
||||
}
|
||||
return new_datas
|
||||
default:
|
||||
throw new Error()
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user