feat: competition result
This commit is contained in:
parent
587173c79f
commit
d1c7f37a94
@ -53,4 +53,31 @@ public class MatchModel {
|
||||
List<ScoreEmbeddable> scores = new ArrayList<>();
|
||||
|
||||
char poule = 'A';
|
||||
|
||||
|
||||
public String getC1Name() {
|
||||
if (c1_id == null)
|
||||
return c1_str;
|
||||
return c1_id.fname + " " + c1_id.lname;
|
||||
}
|
||||
|
||||
public String getC2Name() {
|
||||
if (c2_id == null)
|
||||
return c2_str;
|
||||
return c2_id.fname + " " + c2_id.lname;
|
||||
}
|
||||
|
||||
public int win() {
|
||||
int sum = 0;
|
||||
for (ScoreEmbeddable score : this.getScores()) {
|
||||
if (score.getS1() == -1000 || score.getS2() == -1000)
|
||||
continue;
|
||||
|
||||
if (score.getS1() > score.getS2())
|
||||
sum++;
|
||||
else if (score.getS1() < score.getS2())
|
||||
sum--;
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
}
|
||||
|
||||
@ -53,4 +53,14 @@ public class RegisterModel {
|
||||
this.categorie = categorie;
|
||||
this.club = club;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return membre.fname + " " + membre.lname;
|
||||
}
|
||||
|
||||
public ClubModel getClub2() {
|
||||
if (club == null)
|
||||
return membre.club;
|
||||
return club;
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,400 @@
|
||||
package fr.titionfire.ffsaf.domain.service;
|
||||
|
||||
import fr.titionfire.ffsaf.data.model.*;
|
||||
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.RegisterRepository;
|
||||
import fr.titionfire.ffsaf.rest.data.ResultCategoryData;
|
||||
import fr.titionfire.ffsaf.rest.exception.DForbiddenException;
|
||||
import fr.titionfire.ffsaf.utils.*;
|
||||
import io.quarkus.hibernate.reactive.panache.common.WithSession;
|
||||
import io.quarkus.runtime.annotations.RegisterForReflection;
|
||||
import io.smallrye.mutiny.Multi;
|
||||
import io.smallrye.mutiny.Uni;
|
||||
import io.smallrye.mutiny.unchecked.Unchecked;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import lombok.Builder;
|
||||
import org.hibernate.reactive.mutiny.Mutiny;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@WithSession
|
||||
@ApplicationScoped
|
||||
public class ResultService {
|
||||
|
||||
@Inject
|
||||
CompetitionRepository compRepository;
|
||||
|
||||
@Inject
|
||||
RegisterRepository registerRepository;
|
||||
|
||||
@Inject
|
||||
MembreService membreService;
|
||||
|
||||
@Inject
|
||||
CategoryRepository categoryRepository;
|
||||
|
||||
@Inject
|
||||
MatchRepository matchRepository;
|
||||
|
||||
private static final ResourceBundle BUNDLE = ResourceBundle.getBundle("lang.String");
|
||||
|
||||
public Uni<List<Object[]>> getList(SecurityCtx securityCtx) {
|
||||
return membreService.getByAccountId(securityCtx.getSubject())
|
||||
.chain(m -> registerRepository.list("membre = ?1", m))
|
||||
.onItem().transformToMulti(Multi.createFrom()::iterable)
|
||||
.onItem().call(r -> Mutiny.fetch(r.getCompetition()))
|
||||
.onItem().transform(r -> new Object[]{r.getCompetition().getUuid(), r.getCompetition().getName(),
|
||||
r.getCompetition().getDate()})
|
||||
.collect().asList();
|
||||
}
|
||||
|
||||
public Uni<List<ResultCategoryData>> getCategory(String uuid, SecurityCtx securityCtx) {
|
||||
return hasAccess(uuid, securityCtx)
|
||||
.chain(m -> categoryRepository.list("compet.uuid = ?1", uuid)
|
||||
.chain(cats -> matchRepository.list("(c1_id = ?1 OR c2_id = ?1) OR category IN ?2", //TODO AND
|
||||
m.getMembre(), cats)))
|
||||
.map(matchModels -> {
|
||||
HashMap<Long, List<MatchModel>> map = new HashMap<>();
|
||||
for (MatchModel matchModel : matchModels) {
|
||||
if (!map.containsKey(matchModel.getCategory().getId()))
|
||||
map.put(matchModel.getCategory().getId(), new ArrayList<>());
|
||||
map.get(matchModel.getCategory().getId()).add(matchModel);
|
||||
}
|
||||
|
||||
return map.values();
|
||||
})
|
||||
.onItem()
|
||||
.transformToMulti(Multi.createFrom()::iterable)
|
||||
.onItem().call(list -> Mutiny.fetch(list.get(0).getCategory().getTree()))
|
||||
.onItem().transform(this::getData)
|
||||
.collect().asList();
|
||||
|
||||
}
|
||||
|
||||
private ResultCategoryData getData(List<MatchModel> matchModels) {
|
||||
ResultCategoryData out = new ResultCategoryData();
|
||||
|
||||
CategoryModel categoryModel = matchModels.get(0).getCategory();
|
||||
out.setName(categoryModel.getName());
|
||||
out.setType(categoryModel.getType());
|
||||
|
||||
getArray2(matchModels, out);
|
||||
getTree(categoryModel.getTree(), out);
|
||||
return out;
|
||||
}
|
||||
|
||||
private void getArray2(List<MatchModel> matchModels_, ResultCategoryData out) {
|
||||
List<MatchModel> matchModels = matchModels_.stream().filter(o -> o.getCategory_ord() >= 0).toList();
|
||||
|
||||
HashMap<Character, List<MatchModel>> matchMap = new HashMap<>();
|
||||
for (MatchModel model : matchModels) {
|
||||
char g = model.getPoule();
|
||||
if (!matchMap.containsKey(g))
|
||||
matchMap.put(g, new ArrayList<>());
|
||||
matchMap.get(g).add(model);
|
||||
}
|
||||
|
||||
matchMap.forEach((c, matchEntities) -> {
|
||||
List<ResultCategoryData.PouleArrayData> matchs = matchEntities.stream()
|
||||
.sorted(Comparator.comparing(MatchModel::getCategory_ord))
|
||||
.map(ResultCategoryData.PouleArrayData::fromModel)
|
||||
.toList();
|
||||
|
||||
List<ResultCategoryData.RankArray> rankArray = matchEntities.stream()
|
||||
.flatMap(m -> Stream.of(m.getC1Name(), m.getC2Name()))
|
||||
.distinct()
|
||||
.map(combName -> {
|
||||
AtomicInteger w = new AtomicInteger(0);
|
||||
AtomicInteger pointMake = new AtomicInteger(0);
|
||||
AtomicInteger pointTake = new AtomicInteger(0);
|
||||
|
||||
matchEntities.stream()
|
||||
.filter(m -> m.isEnd() && (m.getC1Name().equals(combName) || m.getC2Name()
|
||||
.equals(combName)))
|
||||
.forEach(matchModel -> {
|
||||
int win = matchModel.win();
|
||||
if ((matchModel.getC1Name()
|
||||
.equals(combName) && win > 0) || matchModel.getC2Name()
|
||||
.equals(combName) && win < 0)
|
||||
w.getAndIncrement();
|
||||
|
||||
for (ScoreEmbeddable score : matchModel.getScores()) {
|
||||
if (score.getS1() <= -900 || score.getS2() <= -900)
|
||||
continue;
|
||||
if (matchModel.getC1Name().equals(combName)) {
|
||||
pointMake.addAndGet(score.getS1());
|
||||
pointTake.addAndGet(score.getS2());
|
||||
} else {
|
||||
pointMake.addAndGet(score.getS2());
|
||||
pointTake.addAndGet(score.getS1());
|
||||
}
|
||||
}
|
||||
});
|
||||
float pointRate = (pointTake.get() == 0) ? pointMake.get() : (float) pointMake.get() / pointTake.get();
|
||||
|
||||
return new ResultCategoryData.RankArray(0, combName, w.get(),
|
||||
pointMake.get(), pointTake.get(), pointRate);
|
||||
})
|
||||
.sorted(Comparator
|
||||
.comparing(ResultCategoryData.RankArray::getWin)
|
||||
.thenComparing(ResultCategoryData.RankArray::getPointRate).reversed())
|
||||
.toList();
|
||||
out.getMatchs().put(c, matchs);
|
||||
|
||||
int lastWin = -1;
|
||||
float pointRate = 0;
|
||||
int rank = 0;
|
||||
for (ResultCategoryData.RankArray rankArray1 : rankArray) {
|
||||
if (rankArray1.getWin() != lastWin || pointRate != rankArray1.getPointRate()) {
|
||||
lastWin = rankArray1.getWin();
|
||||
pointRate = rankArray1.getPointRate();
|
||||
rank++;
|
||||
}
|
||||
rankArray1.setRank(rank);
|
||||
}
|
||||
out.getRankArray().put(c, rankArray);
|
||||
});
|
||||
}
|
||||
|
||||
private static void convertTree(TreeModel src, TreeNode<ResultCategoryData.TreeData> dst) {
|
||||
dst.setData(ResultCategoryData.TreeData.from(src.getMatch()));
|
||||
if (src.getLeft() != null) {
|
||||
dst.setLeft(new TreeNode<>());
|
||||
convertTree(src.getLeft(), dst.getLeft());
|
||||
}
|
||||
if (src.getRight() != null) {
|
||||
dst.setRight(new TreeNode<>());
|
||||
convertTree(src.getRight(), dst.getRight());
|
||||
}
|
||||
}
|
||||
|
||||
private void getTree(List<TreeModel> treeModels, ResultCategoryData out) {
|
||||
ArrayList<TreeNode<ResultCategoryData.TreeData>> trees = new ArrayList<>();
|
||||
treeModels.stream().filter(t -> t.getLevel() != 0).forEach(treeModel -> {
|
||||
TreeNode<ResultCategoryData.TreeData> root = new TreeNode<>();
|
||||
convertTree(treeModel, root);
|
||||
trees.add(root);
|
||||
});
|
||||
out.setTrees(trees);
|
||||
}
|
||||
|
||||
public Uni<CombsArrayData> getAllCombArray(String uuid, SecurityCtx securityCtx) {
|
||||
return hasAccess(uuid, securityCtx)
|
||||
.chain(cm_register -> registerRepository.list("competition.uuid = ?1", uuid)
|
||||
.chain(registers -> matchRepository.list("category.compet.uuid = ?1", uuid)
|
||||
.map(matchModels -> new Pair<>(registers, matchModels)))
|
||||
.map(pair -> {
|
||||
List<RegisterModel> registers = pair.getKey();
|
||||
List<MatchModel> matchModels = pair.getValue();
|
||||
|
||||
CombsArrayData.CombsArrayDataBuilder builder = CombsArrayData.builder();
|
||||
|
||||
List<CombsArrayData.CombsData> combs = matchModels.stream()
|
||||
.flatMap(m -> Stream.of(m.getC1Name(), m.getC2Name()))
|
||||
.filter(Objects::nonNull)
|
||||
.distinct()
|
||||
.map(combName -> {
|
||||
var builder2 = CombsArrayData.CombsData.builder();
|
||||
AtomicInteger w = new AtomicInteger(0);
|
||||
AtomicInteger l = new AtomicInteger(0);
|
||||
AtomicInteger pointMake = new AtomicInteger();
|
||||
AtomicInteger pointTake = new AtomicInteger();
|
||||
|
||||
matchModels.stream()
|
||||
.filter(m -> m.isEnd() && (m.getC1Name().equals(combName)
|
||||
|| m.getC2Name().equals(combName)))
|
||||
.forEach(matchModel -> {
|
||||
int win = matchModel.win();
|
||||
if ((combName.equals(matchModel.getC1Name()) && win > 0) ||
|
||||
combName.equals(matchModel.getC2Name()) && win < 0) {
|
||||
w.getAndIncrement();
|
||||
} else {
|
||||
l.getAndIncrement();
|
||||
}
|
||||
|
||||
matchModel.getScores().stream()
|
||||
.filter(s -> s.getS1() > -900 && s.getS2() > -900)
|
||||
.forEach(score -> {
|
||||
if (combName.equals(matchModel.getC1Name())) {
|
||||
pointMake.addAndGet(score.getS1());
|
||||
pointTake.addAndGet(score.getS2());
|
||||
} else {
|
||||
pointMake.addAndGet(score.getS2());
|
||||
pointTake.addAndGet(score.getS1());
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Categorie categorie = null;
|
||||
ClubModel club = null;
|
||||
|
||||
Optional<RegisterModel> register = registers.stream()
|
||||
.filter(r -> r.getName().equals(combName)).findFirst();
|
||||
if (register.isPresent()) {
|
||||
categorie = register.get().getCategorie();
|
||||
club = register.get().getClub2();
|
||||
}
|
||||
|
||||
builder2.cat((categorie == null) ? "---" : categorie.getName(BUNDLE));
|
||||
builder2.name(combName);
|
||||
builder2.w(w.get());
|
||||
builder2.l(l.get());
|
||||
builder2.ratioVictoire((l.get() == 0) ? w.get() : (float) w.get() / l.get());
|
||||
builder2.club((club == null) ? BUNDLE.getString("no.licence") : club.getName());
|
||||
builder2.pointMake(pointMake.get());
|
||||
builder2.pointTake(pointTake.get());
|
||||
builder2.ratioPoint(
|
||||
(pointTake.get() == 0) ? pointMake.get() : (float) pointMake.get() / pointTake.get());
|
||||
|
||||
return builder2.build();
|
||||
})
|
||||
.sorted(Comparator.comparing(CombsArrayData.CombsData::name))
|
||||
.toList();
|
||||
|
||||
builder.nb_insc(combs.size());
|
||||
builder.tt_match((int) matchModels.stream().filter(MatchModel::isEnd).count());
|
||||
builder.point(combs.stream().mapToInt(CombsArrayData.CombsData::pointMake).sum());
|
||||
builder.combs(combs);
|
||||
|
||||
return builder.build();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@Builder
|
||||
@RegisterForReflection
|
||||
public static record CombsArrayData(int nb_insc, int tt_match, long point, List<CombsData> combs) {
|
||||
@Builder
|
||||
@RegisterForReflection
|
||||
public static record CombsData(String cat, String club, String name, int w, int l, float ratioVictoire,
|
||||
float ratioPoint, int pointMake, int pointTake) {
|
||||
}
|
||||
}
|
||||
|
||||
public Uni<ClubArrayData> getClubArray(String uuid, SecurityCtx securityCtx) {
|
||||
ClubArrayData.ClubArrayDataBuilder builder = ClubArrayData.builder();
|
||||
|
||||
return hasAccess(uuid, securityCtx)
|
||||
.invoke(cm_register -> builder.name(cm_register.getClub2().getName()))
|
||||
.chain(cm_register -> registerRepository.list("competition.uuid = ?1 AND membre.club = ?2", uuid,
|
||||
cm_register.getClub2())
|
||||
.chain(registers -> matchRepository.list("category.compet.uuid = ?1", uuid)
|
||||
.map(matchModels -> new Pair<>(registers, matchModels)))
|
||||
.map(pair -> {
|
||||
List<RegisterModel> registers = pair.getKey();
|
||||
List<MatchModel> matchModels = pair.getValue();
|
||||
|
||||
builder.nb_insc(registers.size());
|
||||
|
||||
AtomicInteger tt_win = new AtomicInteger(0);
|
||||
AtomicInteger tt_match = new AtomicInteger(0);
|
||||
|
||||
List<ClubArrayData.CombData> combData = registers.stream()
|
||||
.map(register -> {
|
||||
|
||||
var builder2 = ClubArrayData.CombData.builder();
|
||||
AtomicInteger w = new AtomicInteger(0);
|
||||
AtomicInteger l = new AtomicInteger(0);
|
||||
AtomicInteger pointMake = new AtomicInteger();
|
||||
AtomicInteger pointTake = new AtomicInteger();
|
||||
|
||||
matchModels.stream()
|
||||
.filter(m -> m.isEnd() && (register.getMembre().equals(m.getC1_id())
|
||||
|| register.getMembre().equals(m.getC2_id())))
|
||||
.forEach(matchModel -> {
|
||||
int win = matchModel.win();
|
||||
if ((register.getMembre()
|
||||
.equals(matchModel.getC1_id()) && win > 0) ||
|
||||
register.getMembre()
|
||||
.equals(matchModel.getC2_id()) && win < 0) {
|
||||
w.getAndIncrement();
|
||||
} else {
|
||||
l.getAndIncrement();
|
||||
}
|
||||
|
||||
matchModel.getScores().stream()
|
||||
.filter(s -> s.getS1() > -900 && s.getS2() > -900)
|
||||
.forEach(score -> {
|
||||
if (register.getMembre()
|
||||
.equals(matchModel.getC1_id())) {
|
||||
pointMake.addAndGet(score.getS1());
|
||||
pointTake.addAndGet(score.getS2());
|
||||
} else {
|
||||
pointMake.addAndGet(score.getS2());
|
||||
pointTake.addAndGet(score.getS1());
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Categorie categorie = register.getCategorie();
|
||||
if (categorie == null)
|
||||
categorie = register.getMembre().getCategorie();
|
||||
|
||||
builder2.cat((categorie == null) ? "---" : categorie.getName(BUNDLE));
|
||||
builder2.name(register.getName());
|
||||
builder2.w(w.get());
|
||||
builder2.l(l.get());
|
||||
builder2.ratioVictoire((l.get() == 0) ? w.get() : (float) w.get() / l.get());
|
||||
builder2.pointMake(pointMake.get());
|
||||
builder2.pointTake(pointTake.get());
|
||||
builder2.ratioPoint(
|
||||
(pointTake.get() == 0) ? pointMake.get() : (float) pointMake.get() / pointTake.get());
|
||||
|
||||
tt_win.addAndGet(w.get());
|
||||
tt_match.addAndGet(w.get() + l.get());
|
||||
|
||||
return builder2.build();
|
||||
})
|
||||
.sorted(Comparator.comparing(ClubArrayData.CombData::name))
|
||||
.toList();
|
||||
|
||||
builder.nb_match(tt_match.get());
|
||||
builder.match_w(tt_win.get());
|
||||
builder.ratioVictoire((float) combData.stream().filter(c -> c.l + c.w != 0)
|
||||
.mapToDouble(ClubArrayData.CombData::ratioVictoire).average().orElse(0L));
|
||||
builder.pointMake(combData.stream().mapToInt(ClubArrayData.CombData::pointMake).sum());
|
||||
builder.pointTake(combData.stream().mapToInt(ClubArrayData.CombData::pointTake).sum());
|
||||
builder.ratioPoint((float) combData.stream().filter(c -> c.l + c.w != 0)
|
||||
.mapToDouble(ClubArrayData.CombData::ratioPoint).average().orElse(0L));
|
||||
builder.combs(combData);
|
||||
|
||||
return builder.build();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@Builder
|
||||
@RegisterForReflection
|
||||
public static record ClubArrayData(String name, int nb_insc, int nb_match, int match_w, float ratioVictoire,
|
||||
float ratioPoint, int pointMake, int pointTake, List<CombData> combs) {
|
||||
@Builder
|
||||
@RegisterForReflection
|
||||
public static record CombData(String cat, String name, int w, int l, float ratioVictoire,
|
||||
float ratioPoint, int pointMake, int pointTake) {
|
||||
}
|
||||
}
|
||||
|
||||
private Uni<RegisterModel> hasAccess(String uuid, SecurityCtx securityCtx) {
|
||||
return registerRepository.find("membre.userId = ?1 AND competition.uuid = ?2", securityCtx.getSubject(), uuid)
|
||||
.firstResult()
|
||||
.invoke(Unchecked.consumer(o -> {
|
||||
if (o == null)
|
||||
throw new DForbiddenException("Access denied");
|
||||
}));
|
||||
}
|
||||
|
||||
private Uni<RegisterModel> hasAccess(Long compId, SecurityCtx securityCtx) {
|
||||
return registerRepository.find("membre.userId = ?1 AND competition.id = ?2", securityCtx.getSubject(), compId)
|
||||
.firstResult()
|
||||
.invoke(Unchecked.consumer(o -> {
|
||||
if (o == null)
|
||||
throw new DForbiddenException("Access denied");
|
||||
}));
|
||||
}
|
||||
}
|
||||
48
src/main/java/fr/titionfire/ffsaf/rest/ResultEndpoints.java
Normal file
48
src/main/java/fr/titionfire/ffsaf/rest/ResultEndpoints.java
Normal file
@ -0,0 +1,48 @@
|
||||
package fr.titionfire.ffsaf.rest;
|
||||
|
||||
import fr.titionfire.ffsaf.domain.service.ResultService;
|
||||
import fr.titionfire.ffsaf.rest.data.ResultCategoryData;
|
||||
import fr.titionfire.ffsaf.utils.SecurityCtx;
|
||||
import io.quarkus.security.Authenticated;
|
||||
import io.smallrye.mutiny.Uni;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.PathParam;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Authenticated
|
||||
@Path("api/result")
|
||||
public class ResultEndpoints {
|
||||
|
||||
@Inject
|
||||
ResultService resultService;
|
||||
|
||||
@Inject
|
||||
SecurityCtx securityCtx;
|
||||
|
||||
@GET
|
||||
@Path("list")
|
||||
public Uni<List<Object[]>> getList() {
|
||||
return resultService.getList(securityCtx);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("{uuid}")
|
||||
public Uni<List<ResultCategoryData>> getCategory(@PathParam("uuid") String uuid) {
|
||||
return resultService.getCategory(uuid, securityCtx);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("{uuid}/club")
|
||||
public Uni<ResultService.ClubArrayData> getClub(@PathParam("uuid") String uuid) {
|
||||
return resultService.getClubArray(uuid, securityCtx);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("{uuid}/comb")
|
||||
public Uni<ResultService.CombsArrayData> getComb(@PathParam("uuid") String uuid) {
|
||||
return resultService.getAllCombArray(uuid, securityCtx);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,59 @@
|
||||
package fr.titionfire.ffsaf.rest.data;
|
||||
|
||||
import fr.titionfire.ffsaf.data.model.MatchModel;
|
||||
import fr.titionfire.ffsaf.utils.ScoreEmbeddable;
|
||||
import fr.titionfire.ffsaf.utils.TreeNode;
|
||||
import io.quarkus.runtime.annotations.RegisterForReflection;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@RegisterForReflection
|
||||
public class ResultCategoryData {
|
||||
int type;
|
||||
String name;
|
||||
HashMap<Character, List<PouleArrayData>> matchs = new HashMap<>();
|
||||
HashMap<Character, List<RankArray>> rankArray = new HashMap<>();
|
||||
ArrayList<TreeNode<TreeData>> trees;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@RegisterForReflection
|
||||
public static class RankArray {
|
||||
int rank;
|
||||
String name;
|
||||
int win;
|
||||
int pointMake;
|
||||
int pointTake;
|
||||
float pointRate;
|
||||
}
|
||||
|
||||
@RegisterForReflection
|
||||
public record PouleArrayData(String red, boolean red_w, List<Integer[]> score, boolean blue_w, String blue, boolean end) {
|
||||
public static PouleArrayData fromModel(MatchModel matchModel) {
|
||||
return new PouleArrayData(
|
||||
matchModel.getC1Name(),
|
||||
matchModel.isEnd() && matchModel.win() > 0,
|
||||
matchModel.isEnd() ?
|
||||
matchModel.getScores().stream().map(s -> new Integer[]{s.getS1(), s.getS2()}).toList()
|
||||
: new ArrayList<>(),
|
||||
matchModel.isEnd() && matchModel.win() < 0,
|
||||
matchModel.getC2Name(),
|
||||
matchModel.isEnd());
|
||||
}
|
||||
}
|
||||
|
||||
@RegisterForReflection
|
||||
public static record TreeData(long id, String c1FullName, String c2FullName, List<ScoreEmbeddable> scores,
|
||||
boolean end) {
|
||||
public static TreeData from(MatchModel match) {
|
||||
return new TreeData(match.getId(), match.getC1Name(), match.getC2Name(), match.getScores(), match.isEnd());
|
||||
}
|
||||
}
|
||||
}
|
||||
35
src/main/java/fr/titionfire/ffsaf/utils/TreeNode.java
Normal file
35
src/main/java/fr/titionfire/ffsaf/utils/TreeNode.java
Normal file
@ -0,0 +1,35 @@
|
||||
package fr.titionfire.ffsaf.utils;
|
||||
|
||||
import io.quarkus.runtime.annotations.RegisterForReflection;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@RegisterForReflection
|
||||
public class TreeNode<T> {
|
||||
private T data;
|
||||
private TreeNode<T> left;
|
||||
private TreeNode<T> right;
|
||||
|
||||
public TreeNode(T data) {
|
||||
this(data, null, null);
|
||||
}
|
||||
|
||||
public int death() {
|
||||
int dg = 0;
|
||||
int dd = 0;
|
||||
|
||||
if (this.right != null)
|
||||
dg = this.right.death();
|
||||
|
||||
if (this.left != null)
|
||||
dg = this.left.death();
|
||||
|
||||
return 1 + Math.max(dg, dd);
|
||||
}
|
||||
}
|
||||
16
src/main/resources/lang/String.properties
Normal file
16
src/main/resources/lang/String.properties
Normal file
@ -0,0 +1,16 @@
|
||||
filtre.all=--tout--
|
||||
no.licence=Non licenci\u00E9
|
||||
|
||||
|
||||
# Categories
|
||||
Cat.SUPER_MINI=Super Mini
|
||||
Cat.MINI_POUSSIN=Mini Poussin
|
||||
Cat.POUSSIN= Poussin
|
||||
Cat.BENJAMIN=Benjamin
|
||||
Cat.MINIME=Minime
|
||||
Cat.CADET=Cadet
|
||||
Cat.JUNIOR=Junior
|
||||
Cat.SENIOR1=Senior 1
|
||||
Cat.SENIOR2=Senior 2
|
||||
Cat.VETERAN1=V\u00E9t\u00E9ran 1
|
||||
Cat.VETERAN2=V\u00E9t\u00E9ran 2
|
||||
BIN
src/main/webapp/public/img/171891.png
Normal file
BIN
src/main/webapp/public/img/171891.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
@ -14,6 +14,7 @@ import {ClubRoot, getClubChildren} from "./pages/club/ClubRoot.jsx";
|
||||
import {DemandeAff, DemandeAffOk} from "./pages/DemandeAff.jsx";
|
||||
import {MePage} from "./pages/MePage.jsx";
|
||||
import {CompetitionRoot, getCompetitionChildren} from "./pages/competition/CompetitionRoot.jsx";
|
||||
import {getResultChildren, ResultRoot} from "./pages/result/ResultRoot.jsx";
|
||||
|
||||
const router = createBrowserRouter([
|
||||
{
|
||||
@ -53,6 +54,11 @@ const router = createBrowserRouter([
|
||||
element: <CompetitionRoot/>,
|
||||
children: getCompetitionChildren()
|
||||
},
|
||||
{
|
||||
path: 'result',
|
||||
element: <ResultRoot/>,
|
||||
children: getResultChildren()
|
||||
},
|
||||
{
|
||||
path: 'me',
|
||||
element: <MePage/>
|
||||
|
||||
@ -21,6 +21,7 @@ export function Nav() {
|
||||
<div className="collapse-item">
|
||||
<ul className="navbar-nav">
|
||||
<li className="nav-item"><NavLink className="nav-link" to="/">Accueil</NavLink></li>
|
||||
<CompMenu/>
|
||||
<ClubMenu/>
|
||||
<AdminMenu/>
|
||||
<AffiliationMenu/>
|
||||
@ -41,6 +42,23 @@ function AffiliationMenu() {
|
||||
return <li className="nav-item"><NavLink className="nav-link" to="/affiliation">Demande d'affiliation</NavLink></li>
|
||||
}
|
||||
|
||||
function CompMenu() {
|
||||
const {is_authenticated} = useAuth()
|
||||
|
||||
if (!is_authenticated)
|
||||
return <></>
|
||||
|
||||
return <li className="nav-item dropdown">
|
||||
<div className="nav-link dropdown-toggle" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
Compétitions
|
||||
</div>
|
||||
<ul className="dropdown-menu">
|
||||
<li className="nav-item"><NavLink className="nav-link" to="/competition">Inscription</NavLink></li>
|
||||
<li className="nav-item"><NavLink className="nav-link" to="/result">Mes résultats</NavLink></li>
|
||||
</ul>
|
||||
</li>
|
||||
}
|
||||
|
||||
function ClubMenu() {
|
||||
const {is_authenticated, userinfo} = useAuth()
|
||||
|
||||
@ -99,4 +117,4 @@ function LoginMenu() {
|
||||
</li>
|
||||
}
|
||||
</>
|
||||
}
|
||||
}
|
||||
|
||||
252
src/main/webapp/src/pages/result/DrawGraph.jsx
Normal file
252
src/main/webapp/src/pages/result/DrawGraph.jsx
Normal file
@ -0,0 +1,252 @@
|
||||
import {useEffect, useRef} from "react";
|
||||
|
||||
const max_x = 500;
|
||||
const size = 24;
|
||||
|
||||
export function DrawGraph({root = []}) {
|
||||
const canvasRef = useRef(null);
|
||||
|
||||
function getBounds(root) {
|
||||
let px = max_x;
|
||||
let py;
|
||||
let maxx, minx, miny, maxy
|
||||
|
||||
function drawNode(tree, px, py) {
|
||||
let death = tree.death() - 1
|
||||
|
||||
if (death === 0) {
|
||||
if (miny > py - size - ((size * 1.5 / 2) | 0)) miny = py - size - (size * 1.5 / 2) | 0;
|
||||
if (maxy < py + size + ((size * 1.5 / 2) | 0)) maxy = py + size + (size * 1.5 / 2) | 0;
|
||||
} else {
|
||||
if (miny > py - size * 2 * death - ((size * 1.5 / 2) | 0))
|
||||
miny = py - size * 2 * death - ((size * 1.5 / 2) | 0);
|
||||
if (maxy < py + size * 2 * death + ((size * 1.5 / 2) | 0))
|
||||
maxy = py + size * 2 * death + ((size * 1.5 / 2) | 0);
|
||||
}
|
||||
if (minx > px - size * 2 - size * 8) minx = px - size * 2 - size * 8;
|
||||
|
||||
if (tree.left != null) drawNode(tree.left, px - size * 2 - size * 8, py - size * 2 * death);
|
||||
if (tree.right != null) drawNode(tree.right, px - size * 2 - size * 8, py + size * 2 * death);
|
||||
}
|
||||
|
||||
if (root != null) {
|
||||
py = (size * 2 * root.at(0).death() + (((size * 1.5 / 2) | 0) + size) * root.at(0).death()) * 2;
|
||||
|
||||
maxx = px;
|
||||
minx = px;
|
||||
miny = py - (size * 1.5 / 2) | 0;
|
||||
maxy = py + (size * 1.5 / 2) | 0;
|
||||
|
||||
for (const node of root) {
|
||||
px = px - size * 2 - size * 8;
|
||||
if (minx > px) minx = px;
|
||||
|
||||
drawNode(node, px, py);
|
||||
//graphics2D.drawRect(minx, miny, maxx - minx, maxy - miny);
|
||||
py = maxy + ((size * 2 * node.death() + ((size * 1.5 / 2) | 0)));
|
||||
px = maxx;
|
||||
}
|
||||
} else {
|
||||
minx = 0;
|
||||
maxx = 0;
|
||||
miny = 0;
|
||||
maxy = 0;
|
||||
}
|
||||
|
||||
return [minx, maxx, miny, maxy];
|
||||
}
|
||||
|
||||
// Fonction pour dessiner du texte avec gestion de la taille
|
||||
const printText = (ctx, s, x, y, width, height, lineG, lineD) => {
|
||||
ctx.save();
|
||||
ctx.translate(x, y);
|
||||
let tSize = 17;
|
||||
let ratioX = height * 1.0 / 20.0;
|
||||
ctx.font = "100 " + tSize + "px Arial";
|
||||
|
||||
let mw = width - (ratioX * 2) | 0;
|
||||
if (ctx.measureText(s).width > mw) {
|
||||
do {
|
||||
tSize--;
|
||||
ctx.font = tSize + "px Arial";
|
||||
} while (ctx.measureText(s).width > mw && tSize > 10);
|
||||
|
||||
if (ctx.measureText(s).width > mw) {
|
||||
let truncated = "";
|
||||
const words = s.split(" ");
|
||||
for (const word of words) {
|
||||
if (ctx.measureText(truncated + word).width >= mw) {
|
||||
truncated += "...";
|
||||
break;
|
||||
} else {
|
||||
truncated += word + " ";
|
||||
}
|
||||
}
|
||||
s = truncated;
|
||||
}
|
||||
}
|
||||
|
||||
const text = ctx.measureText(s);
|
||||
let dx = (width - text.width) / 2;
|
||||
let dy = ((height - text.actualBoundingBoxDescent) / 2) + (text.actualBoundingBoxAscent / 2);
|
||||
ctx.fillText(s, dx, dy, width - dy);
|
||||
ctx.restore();
|
||||
|
||||
ctx.beginPath();
|
||||
if (lineD) {
|
||||
ctx.moveTo((ratioX * 2.5 + x + dx + text.width) | 0, y + height / 2);
|
||||
ctx.lineTo(x + width, y + height / 2);
|
||||
}
|
||||
if (lineG) {
|
||||
ctx.moveTo(x, y + height / 2);
|
||||
ctx.lineTo((dx + x - ratioX * 2.5) | 0, y + height / 2);
|
||||
}
|
||||
ctx.stroke();
|
||||
};
|
||||
|
||||
// Fonction pour afficher les scores
|
||||
const printScores = (ctx, scores, px, py, scale) => {
|
||||
ctx.save();
|
||||
ctx.translate(px - size * 2, py - size * scale);
|
||||
ctx.font = "100 14px Arial";
|
||||
ctx.textBaseline = 'top';
|
||||
|
||||
for (let i = 0; i < scores.length; i++) {
|
||||
const score = scores[i].s1 + "-" + scores[i].s2;
|
||||
const div = (scores.length <= 2) ? 2 : (scores.length >= 4) ? 4 : 3;
|
||||
const text = ctx.measureText(score);
|
||||
let dx = (size * 2 - text.width) / 2;
|
||||
let dy = ((size * 2 / div - text.actualBoundingBoxDescent) / 2) + (text.actualBoundingBoxAscent / 2);
|
||||
|
||||
ctx.fillStyle = '#ffffffdd';
|
||||
ctx.fillRect(dx, size * 2 * scale / div * i + dy, text.width, 14);
|
||||
ctx.fillStyle = "#000000";
|
||||
ctx.fillText(score, dx, size * 2 * scale / div * i + dy, size * 2);
|
||||
}
|
||||
ctx.restore();
|
||||
};
|
||||
|
||||
// Fonction pour dessiner un nœud
|
||||
const drawNode = (ctx, tree, px, py, max_y) => {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(px, py);
|
||||
ctx.lineTo(px - size, py);
|
||||
ctx.stroke();
|
||||
|
||||
let death = tree.death() - 1;
|
||||
let match = tree.data;
|
||||
|
||||
if (death === 0) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(px - size, py + size);
|
||||
ctx.lineTo(px - size, py - size);
|
||||
ctx.moveTo(px - size, py + size);
|
||||
ctx.lineTo(px - size * 2, py + size);
|
||||
ctx.moveTo(px - size, py - size);
|
||||
ctx.lineTo(px - size * 2, py - size);
|
||||
ctx.stroke();
|
||||
|
||||
printScores(ctx, match.scores, px, py, 1);
|
||||
ctx.fillStyle = "#FF0000";
|
||||
printText(ctx, (match.c1FullName == null) ? "" : match.c1FullName,
|
||||
px - size * 2 - size * 8, py - size - (size * 1.5 / 2 | 0),
|
||||
size * 8, (size * 1.5 | 0), false, true);
|
||||
|
||||
ctx.fillStyle = "#0000FF";
|
||||
printText(ctx, (match.c2FullName == null) ? "" : match.c2FullName,
|
||||
px - size * 2 - size * 8, py + size - (size * 1.5 / 2 | 0),
|
||||
size * 8, (size * 1.5 | 0), false, true);
|
||||
|
||||
if (max_y.current < py + size + ((size * 1.5 / 2) | 0)) {
|
||||
max_y.current = py + size + (size * 1.5 / 2 | 0);
|
||||
}
|
||||
} else {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(px - size, py);
|
||||
ctx.lineTo(px - size, py + size * 2 * death);
|
||||
ctx.moveTo(px - size, py);
|
||||
ctx.lineTo(px - size, py - size * 2 * death);
|
||||
ctx.moveTo(px - size, py + size * 2 * death);
|
||||
ctx.lineTo(px - size * 2, py + size * 2 * death);
|
||||
ctx.moveTo(px - size, py - size * 2 * death);
|
||||
ctx.lineTo(px - size * 2, py - size * 2 * death);
|
||||
ctx.stroke();
|
||||
|
||||
printScores(ctx, match.scores, px, py, 1.5);
|
||||
ctx.fillStyle = "#FF0000";
|
||||
printText(ctx, (match.c1FullName == null) ? "" : match.c1FullName,
|
||||
px - size * 2 - size * 8, py - size * 2 * death - (size * 1.5 / 2 | 0),
|
||||
size * 8, (size * 1.5 | 0), true, true);
|
||||
|
||||
ctx.fillStyle = "#0000FF";
|
||||
printText(ctx, (match.c2FullName == null) ? "" : match.c2FullName,
|
||||
px - size * 2 - size * 8, py + size * 2 * death - (size * 1.5 / 2 | 0),
|
||||
size * 8, (size * 1.5 | 0), true, true);
|
||||
|
||||
if (max_y.current < py + size * 2 * death + ((size * 1.5 / 2) | 0)) {
|
||||
max_y.current = py + size * 2 * death + ((size * 1.5 / 2 | 0));
|
||||
}
|
||||
}
|
||||
|
||||
if (tree.left != null) {
|
||||
drawNode(ctx, tree.left, px - size * 2 - size * 8, py - size * 2 * death, max_y);
|
||||
}
|
||||
if (tree.right != null) {
|
||||
drawNode(ctx, tree.right, px - size * 2 - size * 8, py + size * 2 * death, max_y);
|
||||
}
|
||||
};
|
||||
|
||||
// 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;
|
||||
|
||||
const canvas = canvasRef.current;
|
||||
const ctx = canvas.getContext("2d");
|
||||
const [minx, maxx, miny, maxy] = getBounds(root);
|
||||
|
||||
canvas.width = maxx - minx;
|
||||
canvas.height = maxy - miny;
|
||||
ctx.translate(-minx, -miny);
|
||||
ctx.fillStyle = "#000000";
|
||||
ctx.lineWidth = 2;
|
||||
ctx.strokeStyle = "#000000";
|
||||
|
||||
let px = maxx;
|
||||
let py;
|
||||
const max_y = {current: 0};
|
||||
|
||||
py = (size * 2 * root[0].death() + (((size * 1.5 / 2) | 0) + size) * root[0].death()) * 2;
|
||||
max_y.current = py + (size * 1.5 / 2 | 0);
|
||||
for (const node of root) {
|
||||
let win_name = "";
|
||||
if (node.data.end) {
|
||||
win_name = win(node.data.scores) > 0
|
||||
? (node.data.c1FullName === null ? "???" : node.data.c1FullName)
|
||||
: (node.data.c2FullName === null ? "???" : node.data.c2FullName);
|
||||
}
|
||||
|
||||
ctx.fillStyle = "#18A918";
|
||||
printText(ctx, win_name,
|
||||
px - size * 2 - size * 8, py - ((size * 1.5 / 2) | 0),
|
||||
size * 8, (size * 1.5 | 0), true, false);
|
||||
|
||||
px = px - size * 2 - size * 8;
|
||||
drawNode(ctx, node, px, py, max_y);
|
||||
py = max_y.current + ((size * 2 * node.death() + ((size * 1.5 / 2) | 0)));
|
||||
px = maxx;
|
||||
}
|
||||
}, [root]);
|
||||
|
||||
return <canvas ref={canvasRef} style={{border: "1px solid grey", marginTop: "10px"}} id="myCanvas"></canvas>;
|
||||
}
|
||||
49
src/main/webapp/src/pages/result/ResultList.jsx
Normal file
49
src/main/webapp/src/pages/result/ResultList.jsx
Normal file
@ -0,0 +1,49 @@
|
||||
import {useNavigate} from "react-router-dom";
|
||||
import {useLoadingSwitcher} from "../../hooks/useLoading.jsx";
|
||||
import {useFetch} from "../../hooks/useFetch.js";
|
||||
import {AxiosError} from "../../components/AxiosError.jsx";
|
||||
import {ThreeDots} from "react-loader-spinner";
|
||||
import {useAuth} from "../../hooks/useAuth.jsx";
|
||||
|
||||
export function ResultList() {
|
||||
const navigate = useNavigate();
|
||||
const setLoading = useLoadingSwitcher()
|
||||
const {data, error} = useFetch(`/result/list`, setLoading, 1)
|
||||
|
||||
return <>
|
||||
<div>
|
||||
<div className="row">
|
||||
{data
|
||||
? <MakeCentralPanel data={data} navigate={navigate}/>
|
||||
: error
|
||||
? <AxiosError error={error}/>
|
||||
: <Def/>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
|
||||
function MakeCentralPanel({data, navigate}) {
|
||||
|
||||
return <>
|
||||
<div className="mb-4">
|
||||
<h4>Compétition:</h4>
|
||||
<div className="list-group">
|
||||
{data.sort((a, b) => new Date(b[2].split('T')[0]) - new Date(a[2].split('T')[0])).map((o) => (
|
||||
<li className="list-group-item list-group-item-action" key={o[0]}
|
||||
onClick={e => navigate(`${o[0]}`)}>{o[1]}</li>))}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
|
||||
function Def() {
|
||||
return <div className="list-group">
|
||||
<li className="list-group-item"><ThreeDots/></li>
|
||||
<li className="list-group-item"><ThreeDots/></li>
|
||||
<li className="list-group-item"><ThreeDots/></li>
|
||||
<li className="list-group-item"><ThreeDots/></li>
|
||||
<li className="list-group-item"><ThreeDots/></li>
|
||||
</div>
|
||||
}
|
||||
26
src/main/webapp/src/pages/result/ResultRoot.jsx
Normal file
26
src/main/webapp/src/pages/result/ResultRoot.jsx
Normal file
@ -0,0 +1,26 @@
|
||||
import {LoadingProvider} from "../../hooks/useLoading.jsx";
|
||||
import {Outlet} from "react-router-dom";
|
||||
import {ResultList} from "./ResultList.jsx";
|
||||
import {ResultView} from "./ResultView.jsx";
|
||||
|
||||
export function ResultRoot() {
|
||||
return <>
|
||||
<h1>Résultat</h1>
|
||||
<LoadingProvider>
|
||||
<Outlet/>
|
||||
</LoadingProvider>
|
||||
</>
|
||||
}
|
||||
|
||||
export function getResultChildren() {
|
||||
return [
|
||||
{
|
||||
path: '',
|
||||
element: <ResultList/>
|
||||
},
|
||||
{
|
||||
path: ':uuid',
|
||||
element: <ResultView/>
|
||||
},
|
||||
]
|
||||
}
|
||||
347
src/main/webapp/src/pages/result/ResultView.jsx
Normal file
347
src/main/webapp/src/pages/result/ResultView.jsx
Normal file
@ -0,0 +1,347 @@
|
||||
import {useNavigate, useParams} from "react-router-dom";
|
||||
import {useLoadingSwitcher} from "../../hooks/useLoading.jsx";
|
||||
import {useFetch} from "../../hooks/useFetch.js";
|
||||
import {AxiosError} from "../../components/AxiosError.jsx";
|
||||
import {ThreeDots} from "react-loader-spinner";
|
||||
import {useEffect, useState} from "react";
|
||||
import {DrawGraph} from "./DrawGraph.jsx";
|
||||
|
||||
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 ResultView() {
|
||||
const {uuid} = useParams()
|
||||
const navigate = useNavigate();
|
||||
const [resultShow, setResultShow] = useState(null)
|
||||
|
||||
const setLoading = useLoadingSwitcher()
|
||||
const {data, error} = useFetch(`/result/${uuid}`, setLoading, 1)
|
||||
|
||||
return <>
|
||||
<button type="button" className="btn btn-link" onClick={() => navigate("/result")}>
|
||||
« retour
|
||||
</button>
|
||||
|
||||
{data ? <MenuBar data={data} resultShow={resultShow} setResultShow={setResultShow}/>
|
||||
: error
|
||||
? <AxiosError error={error}/>
|
||||
: <Def/>}
|
||||
|
||||
<div className="row">
|
||||
<div className="col-auto">
|
||||
{resultShow && resultShow.type !== undefined && <PouleResult data={resultShow}/>
|
||||
|| resultShow && resultShow === "club" && <ClubResult uuid={uuid}/>
|
||||
|| resultShow && resultShow === "comb" && <CombResult uuid={uuid}/>}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
// || resultShow && resultShow === "club_all" && <ClubAllResult uuid={uuid}/>
|
||||
|
||||
function MenuBar({data, resultShow, setResultShow}) {
|
||||
return <ul className="nav nav-tabs">
|
||||
<li className="nav-item dropdown">
|
||||
<a className={"nav-link dropdown-toggle my-1"} data-bs-toggle="dropdown" href="#"
|
||||
aria-current={(resultShow?.type !== undefined ? " page" : "false")} role="button" aria-expanded="false">Catégorie</a>
|
||||
<ul className="dropdown-menu">
|
||||
{data.map(item => <li><a key={item.name} className={"dropdown-item" + (resultShow === item ? " active" : "")}
|
||||
aria-current={(resultShow === item ? " page" : "false")} href="#" data-bs-toggle="collapse"
|
||||
onClick={_ => setResultShow(item)}>{item.name}</a></li>)}
|
||||
</ul>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<a className={"nav-link my-1" + (resultShow === "club" ? " active" : "")} aria-current={(resultShow === "club" ? " page" : "false")}
|
||||
href="#" onClick={_ => setResultShow("club")}>Mon club</a>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<a className={"nav-link my-1" + (resultShow === "comb" ? " active" : "")} aria-current={(resultShow === "comb" ? " page" : "false")}
|
||||
href="#" onClick={_ => setResultShow("comb")}>Combattants</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
/*
|
||||
<li className="nav-item">
|
||||
<a className={"nav-link my-1" + (resultShow === "club_all" ? " active" : "")}
|
||||
aria-current={(resultShow === "club_all" ? " page" : "false")}
|
||||
href="#" onClick={_ => setResultShow("club_all")}>Clubs</a>
|
||||
</li>
|
||||
*/
|
||||
}
|
||||
|
||||
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">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" style={{textAlign: "center"}}>Rouge</th>
|
||||
<th scope="col" style={{textAlign: "center"}}></th>
|
||||
<th scope="col" style={{textAlign: "center"}}>Scores</th>
|
||||
<th scope="col" style={{textAlign: "center"}}></th>
|
||||
<th scope="col" style={{textAlign: "center"}}>Bleu</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{matchs.map((match, idx) => <tr key={idx}>
|
||||
<td style={{textAlign: "right"}}>{match.red}</td>
|
||||
<td style={{textAlign: "center"}}>{match.red_w ? <CupImg/> : ""}</td>
|
||||
<td style={{textAlign: "center"}}>{scoreToString(match.score)}</td>
|
||||
<td style={{textAlign: "center"}}>{match.blue_w ? <CupImg/> : ""}</td>
|
||||
<td style={{textAlign: "left"}}>{match.blue}</td>
|
||||
</tr>)}
|
||||
</tbody>
|
||||
</table>
|
||||
</>
|
||||
}
|
||||
|
||||
function BuildRankArray({rankArray}) {
|
||||
return <>
|
||||
<table className="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" style={{textAlign: "center"}}>Place</th>
|
||||
<th scope="col" style={{textAlign: "center"}}>Nom</th>
|
||||
<th scope="col" style={{textAlign: "center"}}>Victoire</th>
|
||||
<th scope="col" style={{textAlign: "center"}}>Ratio</th>
|
||||
<th scope="col" style={{textAlign: "center"}}>Points marqués</th>
|
||||
<th scope="col" style={{textAlign: "center"}}>Points reçus</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{rankArray.map((row, idx) => <tr key={idx}>
|
||||
<td style={{textAlign: "center"}}>{row.rank}</td>
|
||||
<td style={{textAlign: "left"}}>{row.name}</td>
|
||||
<td style={{textAlign: "center"}}>{row.win}</td>
|
||||
<td style={{textAlign: "center"}}>{row.pointRate.toFixed(3)}</td>
|
||||
<td style={{textAlign: "center"}}>{row.pointMake}</td>
|
||||
<td style={{textAlign: "center"}}>{row.pointTake}</td>
|
||||
</tr>)}
|
||||
</tbody>
|
||||
</table>
|
||||
</>
|
||||
}
|
||||
|
||||
function BuildTree({treeData}) {
|
||||
class TreeNode {
|
||||
constructor(data) {
|
||||
this.data = data;
|
||||
this.left = null;
|
||||
this.right = null;
|
||||
}
|
||||
|
||||
death() {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
function parseTree(data_in) {
|
||||
if (data_in?.data == null)
|
||||
return null;
|
||||
|
||||
let node = new TreeNode(data_in.data);
|
||||
node.left = parseTree(data_in?.left);
|
||||
node.right = parseTree(data_in?.right);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
function initTree(data_in) {
|
||||
let out = [];
|
||||
for (const din of data_in) {
|
||||
out.push(parseTree(din));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
return <DrawGraph root={initTree(treeData)}/>
|
||||
}
|
||||
|
||||
function PouleResult({data}) {
|
||||
const [type, setType] = useState(data.type === 3 ? 1 : data.type)
|
||||
|
||||
useEffect(() => {
|
||||
setType(data.type === 3 ? 1 : data.type)
|
||||
}, [data]);
|
||||
|
||||
return <>
|
||||
{data.type === 3 && <>
|
||||
<ul className="nav nav-tabs">
|
||||
<li className="nav-item">
|
||||
<a className={"nav-link" + (type === 1 ? " active" : "")} aria-current={(type === 1 ? " page" : "false")} href="#"
|
||||
onClick={_ => setType(1)}>Poule</a>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<a className={"nav-link" + (type === 2 ? " active" : "")} aria-current={(type === 2 ? " page" : "false")} href="#"
|
||||
onClick={_ => setType(2)}>Tournois</a>
|
||||
</li>
|
||||
</ul>
|
||||
</>}
|
||||
|
||||
{type === 1 && <>
|
||||
{Object.keys(data.matchs).map(p => <div key={p}>
|
||||
{Object.keys(data.matchs).length > 1 && <h4 style={{marginTop: "2em"}}>Poule {p}</h4>}
|
||||
<BuildMatchArray matchs={data.matchs[p]}/>
|
||||
<BuildRankArray rankArray={data.rankArray[p]}/>
|
||||
</div>)}
|
||||
</>}
|
||||
|
||||
{type === 2 && <>
|
||||
<BuildTree treeData={data.trees}/>
|
||||
</>}
|
||||
|
||||
</>
|
||||
}
|
||||
|
||||
function ClubResult({uuid}) {
|
||||
const setLoading = useLoadingSwitcher()
|
||||
const {data, error} = useFetch(`/result/${uuid}/club`, setLoading, 1)
|
||||
|
||||
return <>
|
||||
{data ? <>
|
||||
<h3>Info :</h3>
|
||||
<ul>
|
||||
<li>Nom : {data.name}</li>
|
||||
<li>Nombre d'inscris : {data.nb_insc}</li>
|
||||
</ul>
|
||||
<h3>Statistique :</h3>
|
||||
<ul>
|
||||
<li>Nombre de match disputé : {data.nb_match}</li>
|
||||
<li>Nombre de victoires : {data.match_w}</li>
|
||||
<li>Ratio de victoires moyen : {data.ratioVictoire.toFixed(3)}</li>
|
||||
<li>Points marqués : {data.pointMake}</li>
|
||||
<li>Points reçus : {data.pointTake}</li>
|
||||
<li>Ratio de points moyen : {data.ratioPoint.toFixed(3)}</li>
|
||||
</ul>
|
||||
|
||||
<h3>Liste des membres :</h3>
|
||||
|
||||
<table className="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" style={{textAlign: "center"}}>Catégorie</th>
|
||||
<th scope="col" style={{textAlign: "center"}}>Nom</th>
|
||||
<th scope="col" style={{textAlign: "center"}}>Victoires</th>
|
||||
<th scope="col" style={{textAlign: "center"}}>Défaites</th>
|
||||
<th scope="col" style={{textAlign: "center"}}>Ratio victoires</th>
|
||||
<th scope="col" style={{textAlign: "center"}}>Points marqués</th>
|
||||
<th scope="col" style={{textAlign: "center"}}>Points reçus</th>
|
||||
<th scope="col" style={{textAlign: "center"}}>Ratio points</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{data.combs.map((comb, idx) => <tr key={idx}>
|
||||
<td style={{textAlign: "center"}}>{comb.cat}</td>
|
||||
<td style={{textAlign: "center"}}>{comb.name}</td>
|
||||
<td style={{textAlign: "center"}}>{comb.w}</td>
|
||||
<td style={{textAlign: "center"}}>{comb.l}</td>
|
||||
<td style={{textAlign: "center"}}>{comb.ratioVictoire.toFixed(3)}</td>
|
||||
<td style={{textAlign: "center"}}>{comb.pointMake}</td>
|
||||
<td style={{textAlign: "center"}}>{comb.pointTake}</td>
|
||||
<td style={{textAlign: "center"}}>{comb.ratioPoint.toFixed(3)}</td>
|
||||
</tr>)}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</>
|
||||
: error
|
||||
? <AxiosError error={error}/>
|
||||
: <Def/>
|
||||
}
|
||||
</>
|
||||
}
|
||||
|
||||
/*function ClubAllResult({uuid}) {
|
||||
return <div></div>
|
||||
|
||||
}*/
|
||||
|
||||
function CombResult({uuid}) {
|
||||
const setLoading = useLoadingSwitcher()
|
||||
const {data, error} = useFetch(`/result/${uuid}/comb`, setLoading, 1)
|
||||
|
||||
return <>
|
||||
{data ? <>
|
||||
<h3>Statistique :</h3>
|
||||
<ul>
|
||||
<li>Nombre d'inscris : {data.nb_insc}</li>
|
||||
<li>Nombre de match disputé : {data.tt_match}</li>
|
||||
<li>Points marqués : {data.point}</li>
|
||||
</ul>
|
||||
|
||||
<h3>Liste des combattants :</h3>
|
||||
|
||||
<table className="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" style={{textAlign: "center"}}>Catégorie</th>
|
||||
<th scope="col" style={{textAlign: "center"}}>Club</th>
|
||||
<th scope="col" style={{textAlign: "center"}}>Nom</th>
|
||||
<th scope="col" style={{textAlign: "center"}}>Victoires</th>
|
||||
<th scope="col" style={{textAlign: "center"}}>Défaites</th>
|
||||
<th scope="col" style={{textAlign: "center"}}>Ratio victoires</th>
|
||||
<th scope="col" style={{textAlign: "center"}}>Points marqués</th>
|
||||
<th scope="col" style={{textAlign: "center"}}>Points reçus</th>
|
||||
<th scope="col" style={{textAlign: "center"}}>Ratios points</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{data.combs.map((comb, idx) => <tr key={idx}>
|
||||
<td style={{textAlign: "center"}}>{comb.cat}</td>
|
||||
<td style={{textAlign: "center"}}>{comb.club}</td>
|
||||
<td style={{textAlign: "center"}}>{comb.name}</td>
|
||||
<td style={{textAlign: "center"}}>{comb.w}</td>
|
||||
<td style={{textAlign: "center"}}>{comb.l}</td>
|
||||
<td style={{textAlign: "center"}}>{comb.ratioVictoire.toFixed(3)}</td>
|
||||
<td style={{textAlign: "center"}}>{comb.pointMake}</td>
|
||||
<td style={{textAlign: "center"}}>{comb.pointTake}</td>
|
||||
<td style={{textAlign: "center"}}>{comb.ratioPoint.toFixed(3)}</td>
|
||||
</tr>)}
|
||||
</tbody>
|
||||
</table>
|
||||
</>
|
||||
: error
|
||||
? <AxiosError error={error}/>
|
||||
: <Def/>
|
||||
}
|
||||
</>
|
||||
}
|
||||
|
||||
function Def() {
|
||||
return <div className="list-group">
|
||||
<li className="list-group-item"><ThreeDots/></li>
|
||||
<li className="list-group-item"><ThreeDots/></li>
|
||||
<li className="list-group-item"><ThreeDots/></li>
|
||||
<li className="list-group-item"><ThreeDots/></li>
|
||||
<li className="list-group-item"><ThreeDots/></li>
|
||||
</div>
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user