diff --git a/docker-compose.yml b/docker-compose.yml
index 9e3e8fc..de813ce 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -16,6 +16,7 @@ services:
condition: service_healthy
restart: true
networks:
+ - default
- intra
- nginx
@@ -27,7 +28,7 @@ services:
restart: always
networks:
- pgadmin
- - intra
+ - default
healthcheck:
test: [ "CMD-SHELL", "pg_isready" ]
interval: 5s
@@ -37,28 +38,9 @@ services:
- ${PWD}/postgres-data:/var/lib/postgresql/data
env_file: prod.env
-# ftpd:
-# build:
-# context: ./pure_ftpd
-# dockerfile: Dockerfile2
-# container_name: ftpd
-# ports:
-# - "10042:21"
-# - "30000-30009:30000-30009"
-# volumes:
-# - /data/git_data:/home/data/
-# - ${PWD}/pure_ftpd/passwd:/etc/pure-ftpd/passwd
-# - ${PWD}/pure_ftpd/ssl:/etc/ssl/private/:ro
-# environment:
-# PUBLICHOST: 0.0.0.0
-# FTP_USER_NAME: test
-# FTP_USER_PASS: test
-# FTP_USER_HOME: /home/data
-# ADDED_FLAGS: --tls=1
-# restart: no
-
networks:
intra:
+ name: intra
driver: bridge
pgadmin:
name: pgadmin
diff --git a/src/main/java/fr/titionfire/ffsaf/domain/service/StatsService.java b/src/main/java/fr/titionfire/ffsaf/domain/service/StatsService.java
new file mode 100644
index 0000000..0f08139
--- /dev/null
+++ b/src/main/java/fr/titionfire/ffsaf/domain/service/StatsService.java
@@ -0,0 +1,71 @@
+package fr.titionfire.ffsaf.domain.service;
+
+import fr.titionfire.ffsaf.data.repository.LicenceRepository;
+import fr.titionfire.ffsaf.rest.data.LicenceStats;
+import fr.titionfire.ffsaf.utils.Categorie;
+import fr.titionfire.ffsaf.utils.Genre;
+import fr.titionfire.ffsaf.utils.Utils;
+import io.quarkus.hibernate.reactive.panache.common.WithSession;
+import io.smallrye.mutiny.Multi;
+import io.smallrye.mutiny.Uni;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import org.hibernate.reactive.mutiny.Mutiny;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+@WithSession
+@ApplicationScoped
+public class StatsService {
+
+ @Inject
+ LicenceRepository licenceRepository;
+
+ public Uni getStats() {
+ ConcurrentHashMap categories = new ConcurrentHashMap<>();
+ LicenceStats stats = new LicenceStats();
+ int currentSaison = Utils.getSaison();
+
+ //noinspection ReactiveStreamsUnusedPublisher
+ return licenceRepository.listAll()
+ .onItem().transformToMulti(licences -> Multi.createFrom().iterable(licences))
+ .call(licence -> Mutiny.fetch(licence.getMembre()))
+ .invoke(licence -> {
+ if (!stats.getLicences().containsKey(licence.getSaison()))
+ stats.getLicences().put(licence.getSaison(), new LicenceStats.YearStats());
+
+ LicenceStats.YearStats yearStats = stats.getLicences().get(licence.getSaison());
+
+ System.out.println("stats: " + licence.getMembre().getFname());
+ if (licence.isValidate()) {
+ if (licence.getMembre().getGenre() == Genre.H)
+ yearStats.addH();
+ else if (licence.getMembre().getGenre() == Genre.F)
+ yearStats.addF();
+ else
+ yearStats.addNa();
+
+ if (licence.getSaison() == currentSaison && licence.getMembre().getCategorie() != null) {
+ categories.put(licence.getMembre().getCategorie(),
+ categories.getOrDefault(licence.getMembre().getCategorie(), 0) + 1);
+ }
+ } else {
+ yearStats.addNotValid();
+ }
+
+ })
+ .collect().asList()
+ .map(__ -> {
+ for (Categorie c : Categorie.values()) {
+ LicenceStats.CategoriesStats o = new LicenceStats.CategoriesStats();
+
+ o.setName(c.getName());
+ o.setCount(categories.getOrDefault(c, 0));
+
+ stats.getCategories().add(o);
+ }
+
+ return stats;
+ });
+ }
+}
diff --git a/src/main/java/fr/titionfire/ffsaf/net2/data/SimpleCombModel.java b/src/main/java/fr/titionfire/ffsaf/net2/data/SimpleCombModel.java
index fb9fe39..c607d83 100644
--- a/src/main/java/fr/titionfire/ffsaf/net2/data/SimpleCombModel.java
+++ b/src/main/java/fr/titionfire/ffsaf/net2/data/SimpleCombModel.java
@@ -26,12 +26,12 @@ public class SimpleCombModel {
int licence = 0;
String country = "fr";
-public static SimpleCombModel fromModel(MembreModel model) {
+ public static SimpleCombModel fromModel(MembreModel model) {
if (model == null)
return null;
return new SimpleCombModel(model.getId(), model.getLname(), model.getFname(), model.getCategorie(),
(model.getClub() == null) ? null : SimpleClubModel.fromModel(model.getClub()),
- model.getGenre(), model.getLicence(), model.getCountry());
+ model.getGenre(), (model.getLicence() == null) ? -1 : model.getLicence(), model.getCountry());
}
}
diff --git a/src/main/java/fr/titionfire/ffsaf/rest/StatsEndpoints.java b/src/main/java/fr/titionfire/ffsaf/rest/StatsEndpoints.java
new file mode 100644
index 0000000..97806fd
--- /dev/null
+++ b/src/main/java/fr/titionfire/ffsaf/rest/StatsEndpoints.java
@@ -0,0 +1,25 @@
+package fr.titionfire.ffsaf.rest;
+
+import fr.titionfire.ffsaf.domain.service.StatsService;
+import fr.titionfire.ffsaf.rest.data.LicenceStats;
+import io.smallrye.mutiny.Uni;
+import jakarta.annotation.security.RolesAllowed;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+
+@Path("api/stats")
+public class StatsEndpoints {
+
+ @Inject
+ StatsService service;
+
+ @GET
+ @RolesAllowed("federation_admin")
+ @Produces(MediaType.APPLICATION_JSON)
+ public Uni getStats() {
+ return service.getStats();
+ }
+}
diff --git a/src/main/java/fr/titionfire/ffsaf/rest/data/LicenceStats.java b/src/main/java/fr/titionfire/ffsaf/rest/data/LicenceStats.java
new file mode 100644
index 0000000..d8c254b
--- /dev/null
+++ b/src/main/java/fr/titionfire/ffsaf/rest/data/LicenceStats.java
@@ -0,0 +1,48 @@
+package fr.titionfire.ffsaf.rest.data;
+
+import io.quarkus.runtime.annotations.RegisterForReflection;
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+
+@Data
+@RegisterForReflection
+public class LicenceStats {
+ private ConcurrentHashMap licences = new ConcurrentHashMap<>();
+ private List categories = new ArrayList<>();
+
+ @Data
+ @RegisterForReflection
+ public static class CategoriesStats {
+ private String name;
+ private int count = 0;
+ }
+
+ @Data
+ @RegisterForReflection
+ public static class YearStats {
+ private int h = 0;
+ private int f = 0;
+ private int na = 0;
+ private int not_valid = 0;
+
+ public void addH() {
+ this.h++;
+ }
+
+ public void addF() {
+ this.f++;
+ }
+
+ public void addNa() {
+ this.na++;
+ }
+
+ public void addNotValid() {
+ this.not_valid++;
+ }
+
+ }
+}
diff --git a/src/main/java/fr/titionfire/ffsaf/rest/data/MeData.java b/src/main/java/fr/titionfire/ffsaf/rest/data/MeData.java
index a65e097..33ef840 100644
--- a/src/main/java/fr/titionfire/ffsaf/rest/data/MeData.java
+++ b/src/main/java/fr/titionfire/ffsaf/rest/data/MeData.java
@@ -47,7 +47,7 @@ public class MeData {
this.lname = membreModel.getLname();
this.fname = membreModel.getFname();
this.categorie = membreModel.getCategorie().getName();
- this.club = membreModel.getClub().getName();
+ this.club = membreModel.getClub() == null ? "Sans club" : membreModel.getClub().getName();
this.genre = membreModel.getGenre().str;
this.licence = membreModel.getLicence();
this.country = membreModel.getCountry();
diff --git a/src/main/webapp/package-lock.json b/src/main/webapp/package-lock.json
index 78674e5..b83100b 100644
--- a/src/main/webapp/package-lock.json
+++ b/src/main/webapp/package-lock.json
@@ -19,10 +19,12 @@
"proj4": "^2.11.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
+ "react-is": "^19.0.0",
"react-leaflet": "^4.2.1",
"react-loader-spinner": "^6.1.6",
"react-router-dom": "^6.21.2",
- "react-toastify": "^10.0.4"
+ "react-toastify": "^10.0.4",
+ "recharts": "^2.15.1"
},
"devDependencies": {
"@types/react": "^18.2.43",
@@ -335,6 +337,17 @@
"@babel/core": "^7.0.0-0"
}
},
+ "node_modules/@babel/runtime": {
+ "version": "7.26.7",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.7.tgz",
+ "integrity": "sha512-AOPI3D+a8dXnja+iwsUqGRjr1BbZIe771sXdapOtYI531gSqpi92vXivKcq2asu/DFpdl1ceFAKZyRzK2PCVcQ==",
+ "dependencies": {
+ "regenerator-runtime": "^0.14.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
"node_modules/@babel/template": {
"version": "7.22.15",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz",
@@ -1254,6 +1267,60 @@
"@babel/types": "^7.20.7"
}
},
+ "node_modules/@types/d3-array": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz",
+ "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg=="
+ },
+ "node_modules/@types/d3-color": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
+ "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="
+ },
+ "node_modules/@types/d3-ease": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz",
+ "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA=="
+ },
+ "node_modules/@types/d3-interpolate": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
+ "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
+ "dependencies": {
+ "@types/d3-color": "*"
+ }
+ },
+ "node_modules/@types/d3-path": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz",
+ "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg=="
+ },
+ "node_modules/@types/d3-scale": {
+ "version": "4.0.9",
+ "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz",
+ "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==",
+ "dependencies": {
+ "@types/d3-time": "*"
+ }
+ },
+ "node_modules/@types/d3-shape": {
+ "version": "3.1.7",
+ "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz",
+ "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==",
+ "dependencies": {
+ "@types/d3-path": "*"
+ }
+ },
+ "node_modules/@types/d3-time": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz",
+ "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g=="
+ },
+ "node_modules/@types/d3-timer": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz",
+ "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw=="
+ },
"node_modules/@types/estree": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
@@ -1726,8 +1793,117 @@
"node_modules/csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
- "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
- "dev": true
+ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
+ },
+ "node_modules/d3-array": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
+ "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
+ "dependencies": {
+ "internmap": "1 - 2"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-color": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
+ "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-ease": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
+ "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-format": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz",
+ "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-interpolate": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
+ "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
+ "dependencies": {
+ "d3-color": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-path": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz",
+ "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-scale": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
+ "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
+ "dependencies": {
+ "d3-array": "2.10.0 - 3",
+ "d3-format": "1 - 3",
+ "d3-interpolate": "1.2.0 - 3",
+ "d3-time": "2.1.1 - 3",
+ "d3-time-format": "2 - 4"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-shape": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz",
+ "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
+ "dependencies": {
+ "d3-path": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-time": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz",
+ "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
+ "dependencies": {
+ "d3-array": "2 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-time-format": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz",
+ "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
+ "dependencies": {
+ "d3-time": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-timer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
+ "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
+ "engines": {
+ "node": ">=12"
+ }
},
"node_modules/debug": {
"version": "4.3.4",
@@ -1746,6 +1922,11 @@
}
}
},
+ "node_modules/decimal.js-light": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
+ "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg=="
+ },
"node_modules/deep-is": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
@@ -1803,6 +1984,15 @@
"node": ">=6.0.0"
}
},
+ "node_modules/dom-helpers": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
+ "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
+ "dependencies": {
+ "@babel/runtime": "^7.8.7",
+ "csstype": "^3.0.2"
+ }
+ },
"node_modules/electron-to-chromium": {
"version": "1.4.630",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.630.tgz",
@@ -2282,12 +2472,25 @@
"node": ">=0.10.0"
}
},
+ "node_modules/eventemitter3": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
+ "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="
+ },
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"dev": true
},
+ "node_modules/fast-equals": {
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.2.2.tgz",
+ "integrity": "sha512-V7/RktU11J3I36Nwq2JnZEM7tNm17eBJz+u25qdxBZeCKiX6BkVSZQjwWIr+IobgnZy+ag73tTZgZi7tr0LrBw==",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
"node_modules/fast-json-stable-stringify": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
@@ -2713,6 +2916,14 @@
"node": ">= 0.4"
}
},
+ "node_modules/internmap": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
+ "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/is-array-buffer": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz",
@@ -3159,6 +3370,11 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
+ },
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@@ -3528,6 +3744,11 @@
"react-is": "^16.13.1"
}
},
+ "node_modules/prop-types/node_modules/react-is": {
+ "version": "16.13.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
+ },
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
@@ -3586,9 +3807,9 @@
}
},
"node_modules/react-is": {
- "version": "16.13.1",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
- "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
+ "version": "19.0.0",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.0.0.tgz",
+ "integrity": "sha512-H91OHcwjZsbq3ClIDHMzBShc1rotbfACdWENsmEf0IFvZ3FgGPtdHMcsv45bQ1hAbgdfiA8SnxTKfDS+x/8m2g=="
},
"node_modules/react-leaflet": {
"version": "4.2.1",
@@ -3663,6 +3884,20 @@
"react-dom": ">=16.8"
}
},
+ "node_modules/react-smooth": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz",
+ "integrity": "sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==",
+ "dependencies": {
+ "fast-equals": "^5.0.1",
+ "prop-types": "^15.8.1",
+ "react-transition-group": "^4.4.5"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
"node_modules/react-toastify": {
"version": "10.0.4",
"resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-10.0.4.tgz",
@@ -3675,6 +3910,56 @@
"react-dom": ">=16"
}
},
+ "node_modules/react-transition-group": {
+ "version": "4.4.5",
+ "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
+ "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
+ "dependencies": {
+ "@babel/runtime": "^7.5.5",
+ "dom-helpers": "^5.0.1",
+ "loose-envify": "^1.4.0",
+ "prop-types": "^15.6.2"
+ },
+ "peerDependencies": {
+ "react": ">=16.6.0",
+ "react-dom": ">=16.6.0"
+ }
+ },
+ "node_modules/recharts": {
+ "version": "2.15.1",
+ "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.1.tgz",
+ "integrity": "sha512-v8PUTUlyiDe56qUj82w/EDVuzEFXwEHp9/xOowGAZwfLjB9uAy3GllQVIYMWF6nU+qibx85WF75zD7AjqoT54Q==",
+ "dependencies": {
+ "clsx": "^2.0.0",
+ "eventemitter3": "^4.0.1",
+ "lodash": "^4.17.21",
+ "react-is": "^18.3.1",
+ "react-smooth": "^4.0.4",
+ "recharts-scale": "^0.4.4",
+ "tiny-invariant": "^1.3.1",
+ "victory-vendor": "^36.6.8"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "peerDependencies": {
+ "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/recharts-scale": {
+ "version": "0.4.5",
+ "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz",
+ "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==",
+ "dependencies": {
+ "decimal.js-light": "^2.4.1"
+ }
+ },
+ "node_modules/recharts/node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="
+ },
"node_modules/reflect.getprototypeof": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.4.tgz",
@@ -3695,6 +3980,11 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/regenerator-runtime": {
+ "version": "0.14.1",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
+ "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
+ },
"node_modules/regexp.prototype.flags": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz",
@@ -4130,6 +4420,11 @@
"integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
"dev": true
},
+ "node_modules/tiny-invariant": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
+ "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="
+ },
"node_modules/to-fast-properties": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
@@ -4292,6 +4587,27 @@
"resolved": "https://registry.npmjs.org/uzip/-/uzip-0.20201231.0.tgz",
"integrity": "sha512-OZeJfZP+R0z9D6TmBgLq2LHzSSptGMGDGigGiEe0pr8UBe/7fdflgHlHBNDASTXB5jnFuxHpNaJywSg8YFeGng=="
},
+ "node_modules/victory-vendor": {
+ "version": "36.9.2",
+ "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz",
+ "integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==",
+ "dependencies": {
+ "@types/d3-array": "^3.0.3",
+ "@types/d3-ease": "^3.0.0",
+ "@types/d3-interpolate": "^3.0.1",
+ "@types/d3-scale": "^4.0.2",
+ "@types/d3-shape": "^3.1.0",
+ "@types/d3-time": "^3.0.0",
+ "@types/d3-timer": "^3.0.0",
+ "d3-array": "^3.1.6",
+ "d3-ease": "^3.0.1",
+ "d3-interpolate": "^3.0.1",
+ "d3-scale": "^4.0.2",
+ "d3-shape": "^3.1.0",
+ "d3-time": "^3.0.0",
+ "d3-timer": "^3.0.1"
+ }
+ },
"node_modules/vite": {
"version": "5.0.11",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.0.11.tgz",
diff --git a/src/main/webapp/package.json b/src/main/webapp/package.json
index 32fbdf3..3384fa4 100644
--- a/src/main/webapp/package.json
+++ b/src/main/webapp/package.json
@@ -21,10 +21,12 @@
"proj4": "^2.11.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
+ "react-is": "^19.0.0",
"react-leaflet": "^4.2.1",
"react-loader-spinner": "^6.1.6",
"react-router-dom": "^6.21.2",
- "react-toastify": "^10.0.4"
+ "react-toastify": "^10.0.4",
+ "recharts": "^2.15.1"
},
"devDependencies": {
"@types/react": "^18.2.43",
diff --git a/src/main/webapp/src/components/Nav.jsx b/src/main/webapp/src/components/Nav.jsx
index 5f64f86..825a6a0 100644
--- a/src/main/webapp/src/components/Nav.jsx
+++ b/src/main/webapp/src/components/Nav.jsx
@@ -72,6 +72,7 @@ function AdminMenu() {
- Member
- Club
+ - Statistiques
}
diff --git a/src/main/webapp/src/pages/MePage.jsx b/src/main/webapp/src/pages/MePage.jsx
index cdd105b..27e74b3 100644
--- a/src/main/webapp/src/pages/MePage.jsx
+++ b/src/main/webapp/src/pages/MePage.jsx
@@ -112,10 +112,18 @@ export function InformationForm({data}) {
|| }{data.genre}
{data.birth_date ? data.birth_date.split('T')[0] : ''}
{data.categorie}
- Nationalité : 
+ Nationalité : 
+ Club : {data.club}
Rôle au sien du club : {data.role}
Formation d'arbitrage : {data.grade_arbitrage}
+
+
+
;
diff --git a/src/main/webapp/src/pages/admin/AdminRoot.jsx b/src/main/webapp/src/pages/admin/AdminRoot.jsx
index 3a4ddfa..948503b 100644
--- a/src/main/webapp/src/pages/admin/AdminRoot.jsx
+++ b/src/main/webapp/src/pages/admin/AdminRoot.jsx
@@ -9,6 +9,8 @@ import {AffiliationReqPage} from "./affiliation/AffiliationReqPage.jsx";
import {NewClubPage} from "./club/NewClubPage.jsx";
import {ClubPage} from "./club/ClubPage.jsx";
import {AffiliationReqList} from "./affiliation/AffiliationReqList.jsx";
+import {Scale} from "leaflet/src/control/Control.Scale.js";
+import {StatsPage} from "./StatsPage.jsx";
export function AdminRoot() {
return <>
@@ -54,8 +56,8 @@ export function getAdminChildren() {
element:
},
{
- path: 'b',
- element: Admin B
+ path: 'stats',
+ element:
}
]
}
\ No newline at end of file
diff --git a/src/main/webapp/src/pages/admin/StatsLazy.jsx b/src/main/webapp/src/pages/admin/StatsLazy.jsx
new file mode 100644
index 0000000..a704b06
--- /dev/null
+++ b/src/main/webapp/src/pages/admin/StatsLazy.jsx
@@ -0,0 +1,170 @@
+import {Area, AreaChart, CartesianGrid, Cell, Pie, PieChart, ResponsiveContainer, Sector, Tooltip, XAxis, YAxis} from "recharts";
+import {useEffect, useState} from "react";
+import {getSaison} from "../../utils/Tools.js";
+
+export default function StatsLazy({data}) {
+ return
+
+
+
Nombre de licences par catégorie pour {getSaison()}
+
+
+
+}
+
+function NbGraph({raw_data}) {
+ const [showData, setShowData] = useState([])
+
+ useEffect(() => {
+ const data = []
+ for (const k in raw_data.licences) {
+ data.push({
+ name: k + "-" + (parseInt(k) + 1),
+ h: raw_data.licences[k].h,
+ f: raw_data.licences[k].f,
+ na: raw_data.licences[k].na,
+ not_valid: raw_data.licences[k].not_valid,
+ })
+ }
+ setShowData(data)
+ }, [raw_data])
+
+ const colors = ['#6e6e6e', '#1a07f8', '#f659d5', '#f80000']
+
+ return
+
+
+
+
+
+
+
+
+
+
+
+}
+
+function CatGraph({raw_data}) {
+ const [showData, setShowData] = useState([])
+ const [activeIndex, setActiveIndex] = useState(0)
+
+
+ useEffect(() => {
+ const data = []
+ for (const k of raw_data.categories) {
+ data.push({
+ name: k.name,
+ value: k.count,
+ })
+ }
+ setShowData(data)
+ }, [raw_data])
+
+ const COLORS = ['#33FF8F', '#30E393', '#2DC697', '#2AAA9B'
+ , '#278E9F', '#2471A3', '#2155A7', '#1E39AB'
+ , '#1B1CAF', '#1800B3'];
+
+ const RADIAN = Math.PI / 180;
+ const renderCustomizedLabel = ({cx, cy, midAngle, innerRadius, outerRadius, index}) => {
+ const radius = innerRadius + (outerRadius - innerRadius) * 0.5;
+ const x = cx + radius * Math.cos(-midAngle * RADIAN);
+ const y = cy + radius * Math.sin(-midAngle * RADIAN);
+
+ return <> {
+ showData[index]['value'] > 0 &&
+ cx ? 'start' : 'end'} dominantBaseline="central">
+ {`${showData[index]['name']}`}
+
+
+ }>;
+ };
+
+ const onPieEnter = (_, index) => {
+ setActiveIndex(index);
+ };
+
+ const renderActiveShape = (props) => {
+ const RADIAN = Math.PI / 180;
+ const {cx, cy, midAngle, innerRadius, outerRadius, startAngle, endAngle, fill, payload, percent, value} = props;
+ const sin = Math.sin(-RADIAN * midAngle);
+ const cos = Math.cos(-RADIAN * midAngle);
+ const sx = cx + (outerRadius + 10) * cos;
+ const sy = cy + (outerRadius + 10) * sin;
+ const mx = cx + (outerRadius + 30) * cos;
+ const my = cy + (outerRadius + 30) * sin;
+ const ex = mx + (cos >= 0 ? 1 : -1) * 22;
+ const ey = my;
+ const textAnchor = cos >= 0 ? 'start' : 'end';
+
+ return
+
+ {payload.name}
+
+
+
+
+
+ = 0 ? 1 : -1) * 12} y={ey} textAnchor={textAnchor} fill="#333">{`${payload.name}`}
+ = 0 ? 1 : -1) * 12} y={ey} dy={18} textAnchor={textAnchor} fill="#999">
+ {`${value} (${(percent * 100).toFixed(0)}%)`}
+
+ ;
+ };
+
+ return
+
+
+ {showData.map((entry, index) => (
+ |
+ ))}
+
+
+
+}
\ No newline at end of file
diff --git a/src/main/webapp/src/pages/admin/StatsPage.jsx b/src/main/webapp/src/pages/admin/StatsPage.jsx
new file mode 100644
index 0000000..6a1a8ed
--- /dev/null
+++ b/src/main/webapp/src/pages/admin/StatsPage.jsx
@@ -0,0 +1,23 @@
+import {lazy, Suspense} from "react";
+import {FallingLines} from "react-loader-spinner";
+import {useLoadingSwitcher} from "../../hooks/useLoading.jsx";
+import {useFetch} from "../../hooks/useFetch.js";
+import {AxiosError} from "../../components/AxiosError.jsx";
+
+export function StatsPage() {
+ const StatsLazy = lazy(() => import('./StatsLazy.jsx'))
+ const setLoading = useLoadingSwitcher()
+ const {data, error} = useFetch(`/stats`, setLoading, 1)
+
+ return
+
Statistiques
+
}>
+ {data
+ ? <>
+
+ >
+ : error &&
+ }
+
+
+}
\ No newline at end of file