add list user and part of user page
This commit is contained in:
@@ -6,3 +6,6 @@ Currently, two official plugins are available:
|
||||
|
||||
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
|
||||
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
||||
|
||||
|
||||
https://mhnpd.github.io/react-loader-spinner/docs/intro
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>Vite + React</title>
|
||||
|
||||
<link href="/index.css" rel="stylesheet">
|
||||
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css"
|
||||
@@ -15,12 +17,17 @@
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<div id="root">
|
||||
<div class="loader-container">
|
||||
<h1 style="color: #00aff2">Chargement de l'application FFSAF...</h1>
|
||||
<div class="spinner"></div>
|
||||
</div>
|
||||
</div>
|
||||
<script
|
||||
src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"
|
||||
integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p"
|
||||
crossorigin="anonymous"
|
||||
></script>
|
||||
<script type="module" src="/src/main.jsx"></script>
|
||||
<script async type="module" src="/src/main.jsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
169
src/main/webapp/package-lock.json
generated
169
src/main/webapp/package-lock.json
generated
@@ -14,8 +14,10 @@
|
||||
"@fortawesome/free-solid-svg-icons": "^6.5.1",
|
||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||
"axios": "^1.6.5",
|
||||
"browser-image-compression": "^2.0.2",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-loader-spinner": "^6.1.6",
|
||||
"react-router-dom": "^6.21.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -378,6 +380,24 @@
|
||||
"node": ">=6.9.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",
|
||||
"integrity": "sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw==",
|
||||
"dependencies": {
|
||||
"@emotion/memoize": "^0.8.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@emotion/memoize": {
|
||||
"version": "0.8.1",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz",
|
||||
"integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA=="
|
||||
},
|
||||
"node_modules/@emotion/unitless": {
|
||||
"version": "0.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.0.tgz",
|
||||
"integrity": "sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw=="
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.19.11",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.11.tgz",
|
||||
@@ -1258,6 +1278,11 @@
|
||||
"integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/stylis": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.0.tgz",
|
||||
"integrity": "sha512-n4sx2bqL0mW1tvDf/loQ+aMX7GQD3lc3fkCMC55VFNDu/vBOabO+LTIeXKM14xK0ppk5TUGcWRjiSpIlUpghKw=="
|
||||
},
|
||||
"node_modules/@ungap/structured-clone": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
|
||||
@@ -1501,6 +1526,14 @@
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/browser-image-compression": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/browser-image-compression/-/browser-image-compression-2.0.2.tgz",
|
||||
"integrity": "sha512-pBLlQyUf6yB8SmmngrcOw3EoS4RpQ1BcylI3T9Yqn7+4nrQTXJD4sJDe5ODnJdrvNMaio5OicFo75rDyJD2Ucw==",
|
||||
"dependencies": {
|
||||
"uzip": "0.20201231.0"
|
||||
}
|
||||
},
|
||||
"node_modules/browserslist": {
|
||||
"version": "4.22.2",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz",
|
||||
@@ -1556,6 +1589,14 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/camelize": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz",
|
||||
"integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001576",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001576.tgz",
|
||||
@@ -1642,6 +1683,24 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/css-color-keywords": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
|
||||
"integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/css-to-react-native": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz",
|
||||
"integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==",
|
||||
"dependencies": {
|
||||
"camelize": "^1.0.0",
|
||||
"css-color-keywords": "^1.0.0",
|
||||
"postcss-value-parser": "^4.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/csstype": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||
@@ -3140,7 +3199,6 @@
|
||||
"version": "3.3.7",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
|
||||
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@@ -3375,8 +3433,7 @@
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
|
||||
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.4.33",
|
||||
@@ -3406,6 +3463,11 @@
|
||||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-value-parser": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
||||
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
|
||||
},
|
||||
"node_modules/prelude-ls": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
||||
@@ -3487,6 +3549,27 @@
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||
},
|
||||
"node_modules/react-loader-spinner": {
|
||||
"version": "6.1.6",
|
||||
"resolved": "https://registry.npmjs.org/react-loader-spinner/-/react-loader-spinner-6.1.6.tgz",
|
||||
"integrity": "sha512-x5h1Jcit7Qn03MuKlrWcMG9o12cp9SNDVHVJTNRi9TgtGPKcjKiXkou4NRfLAtXaFB3+Z8yZsVzONmPzhv2ErA==",
|
||||
"dependencies": {
|
||||
"react-is": "^18.2.0",
|
||||
"styled-components": "^6.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.0.0 || ^17.0.0 || ^18.0.0",
|
||||
"react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-loader-spinner/node_modules/react-is": {
|
||||
"version": "18.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
|
||||
"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
|
||||
},
|
||||
"node_modules/react-refresh": {
|
||||
"version": "0.14.0",
|
||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz",
|
||||
@@ -3750,6 +3833,11 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/shallowequal": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
|
||||
"integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="
|
||||
},
|
||||
"node_modules/shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
@@ -3789,7 +3877,6 @@
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
|
||||
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -3883,6 +3970,70 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/styled-components": {
|
||||
"version": "6.1.8",
|
||||
"resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.8.tgz",
|
||||
"integrity": "sha512-PQ6Dn+QxlWyEGCKDS71NGsXoVLKfE1c3vApkvDYS5KAK+V8fNWGhbSUEo9Gg2iaID2tjLXegEW3bZDUGpofRWw==",
|
||||
"dependencies": {
|
||||
"@emotion/is-prop-valid": "1.2.1",
|
||||
"@emotion/unitless": "0.8.0",
|
||||
"@types/stylis": "4.2.0",
|
||||
"css-to-react-native": "3.2.0",
|
||||
"csstype": "3.1.2",
|
||||
"postcss": "8.4.31",
|
||||
"shallowequal": "1.1.0",
|
||||
"stylis": "4.3.1",
|
||||
"tslib": "2.5.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/styled-components"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">= 16.8.0",
|
||||
"react-dom": ">= 16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/styled-components/node_modules/csstype": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
|
||||
"integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ=="
|
||||
},
|
||||
"node_modules/styled-components/node_modules/postcss": {
|
||||
"version": "8.4.31",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
|
||||
"integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/postcss/"
|
||||
},
|
||||
{
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/funding/github/npm/postcss"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.6",
|
||||
"picocolors": "^1.0.0",
|
||||
"source-map-js": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/stylis": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.1.tgz",
|
||||
"integrity": "sha512-EQepAV+wMsIaGVGX1RECzgrcqRRU/0sYOHkeLsZ3fzHaHXZy4DaOOX0vOlGQdlsjkh3mFHAIlVimpwAs4dslyQ=="
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
@@ -3922,6 +4073,11 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz",
|
||||
"integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg=="
|
||||
},
|
||||
"node_modules/type-check": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
||||
@@ -4065,6 +4221,11 @@
|
||||
"punycode": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/uzip": {
|
||||
"version": "0.20201231.0",
|
||||
"resolved": "https://registry.npmjs.org/uzip/-/uzip-0.20201231.0.tgz",
|
||||
"integrity": "sha512-OZeJfZP+R0z9D6TmBgLq2LHzSSptGMGDGigGiEe0pr8UBe/7fdflgHlHBNDASTXB5jnFuxHpNaJywSg8YFeGng=="
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "5.0.11",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.0.11.tgz",
|
||||
|
||||
@@ -16,8 +16,10 @@
|
||||
"@fortawesome/free-solid-svg-icons": "^6.5.1",
|
||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||
"axios": "^1.6.5",
|
||||
"browser-image-compression": "^2.0.2",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-loader-spinner": "^6.1.6",
|
||||
"react-router-dom": "^6.21.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
33
src/main/webapp/public/index.css
Normal file
33
src/main/webapp/public/index.css
Normal file
@@ -0,0 +1,33 @@
|
||||
:root {
|
||||
|
||||
}
|
||||
|
||||
.loader-container {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: fixed;
|
||||
background: rgba(0, 0, 0, 0.834);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border: 8px solid;
|
||||
border-color: #3d5af1 transparent #3d5af1 transparent;
|
||||
border-radius: 50%;
|
||||
animation: spin-anim 1.2s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin-anim {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@@ -3,10 +3,11 @@ import './App.css'
|
||||
import {Nav} from "./components/Nav.jsx";
|
||||
import {createBrowserRouter, Outlet, RouterProvider, useRouteError} from "react-router-dom";
|
||||
import {Home} from "./pages/Homepage.jsx";
|
||||
import {AdminRoot} from "./pages/admin/AdminRoot.jsx";
|
||||
import {AdminRoot, getAdminChildren} from "./pages/admin/AdminRoot.jsx";
|
||||
import {AuthCallback} from "./components/auhCallback.jsx";
|
||||
import {useAuthDispatch} from "./hooks/useAuth.jsx";
|
||||
import {KeycloakContextProvider, useAuthDispatch} from "./hooks/useAuth.jsx";
|
||||
import {check_validity} from "./utils/auth.js";
|
||||
import {MemberList} from "./pages/admin/MemberList.jsx";
|
||||
|
||||
const router = createBrowserRouter([
|
||||
{
|
||||
@@ -21,16 +22,7 @@ const router = createBrowserRouter([
|
||||
{
|
||||
path: 'admin',
|
||||
element: <AdminRoot/>,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
element: <div>Admin</div>
|
||||
},
|
||||
{
|
||||
path: 'b',
|
||||
element: <div>Admin B</div>
|
||||
}
|
||||
]
|
||||
children: getAdminChildren()
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -51,6 +43,16 @@ function PageError() {
|
||||
}
|
||||
|
||||
function Root() {
|
||||
const dispatch = useAuthDispatch()
|
||||
const isInit = useRef(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (isInit.current)
|
||||
return;
|
||||
isInit.current = true
|
||||
check_validity(b => dispatch({type: 'init', val: b}))
|
||||
}, []);
|
||||
|
||||
return <>
|
||||
<header>
|
||||
<Nav/>
|
||||
@@ -64,19 +66,9 @@ function Root() {
|
||||
function App() {
|
||||
console.log('render')
|
||||
|
||||
const dispatch = useAuthDispatch()
|
||||
const isInit = useRef(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (isInit.current)
|
||||
return;
|
||||
isInit.current = true
|
||||
check_validity(b => dispatch({type: 'init', val: b}))
|
||||
}, []);
|
||||
|
||||
|
||||
|
||||
return <RouterProvider router={router}/>;
|
||||
return <KeycloakContextProvider>
|
||||
<RouterProvider router={router}/>
|
||||
</KeycloakContextProvider>;
|
||||
}
|
||||
|
||||
export default App
|
||||
|
||||
17
src/main/webapp/src/api/membre.js
Normal file
17
src/main/webapp/src/api/membre.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import axios from "axios";
|
||||
|
||||
const api_url = import.meta.env.VITE_API_URL;
|
||||
|
||||
export function getAllMembre() {
|
||||
return axios.get(`${api_url}/membre/all`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${localStorage.getItem("access_token")}`
|
||||
}
|
||||
}).then(data => {
|
||||
console.log(data.data)
|
||||
return data.data;
|
||||
}).catch((e) => {
|
||||
console.log(e);
|
||||
return null;
|
||||
})
|
||||
}
|
||||
3
src/main/webapp/src/components/AxiosError.jsx
Normal file
3
src/main/webapp/src/components/AxiosError.jsx
Normal file
@@ -0,0 +1,3 @@
|
||||
export function AxiosError(data) {
|
||||
return <span>{data.error.status} - {data.error.statusText}</span>
|
||||
}
|
||||
44
src/main/webapp/src/components/ClubSelect.jsx
Normal file
44
src/main/webapp/src/components/ClubSelect.jsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import {LoadingContextProvider, useLoadingSwitcher} from "../hooks/useLoading.jsx";
|
||||
import {useFetch} from "../hooks/useFetch.js";
|
||||
import {AxiosError} from "./AxiosError.jsx";
|
||||
|
||||
const api_url = import.meta.env.VITE_API_URL;
|
||||
|
||||
export function ClubSelect({defaultValue, name}) {
|
||||
return <LoadingContextProvider>
|
||||
<div className="input-group mb-3">
|
||||
<ClubSelect_ defaultValue={defaultValue} name={name}/>
|
||||
</div>
|
||||
</LoadingContextProvider>
|
||||
}
|
||||
|
||||
function ClubSelect_({defaultValue, name}) {
|
||||
const setLoading = useLoadingSwitcher()
|
||||
const {data, error} = useFetch(`${api_url}/club/no_detail`, setLoading, 1)
|
||||
|
||||
return <>
|
||||
{data
|
||||
? <div className="input-group mb-3">
|
||||
<label className="input-group-text" id="inputGroupSelect02">Club</label>
|
||||
<select className="form-select" id="inputGroupSelect02"
|
||||
defaultValue={defaultValue} name={name}>
|
||||
<option>Sélectionner...</option>
|
||||
{data.map(club => (<option key={club.id} value={club.id}>{club.name}</option>))}
|
||||
</select>
|
||||
</div>
|
||||
: error
|
||||
? <AxiosError error={error}/>
|
||||
: <Def/>
|
||||
}
|
||||
</>
|
||||
}
|
||||
|
||||
function Def() {
|
||||
return <div className="input-group mb-3">
|
||||
<label className="input-group-text" id="inputGroupSelect02">Club</label>
|
||||
<select className="form-select" id="inputGroupSelect02"
|
||||
defaultValue="Chargement...">
|
||||
<option>Chargement...</option>
|
||||
</select>
|
||||
</div>;
|
||||
}
|
||||
11
src/main/webapp/src/components/Input.jsx
Normal file
11
src/main/webapp/src/components/Input.jsx
Normal file
@@ -0,0 +1,11 @@
|
||||
export function Input({placeholder, value, onChange}) {
|
||||
return <div>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
value={value}
|
||||
placeholder={placeholder}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
@@ -6,8 +6,6 @@ import {logout, redirect_auth_page} from "../utils/auth.js";
|
||||
|
||||
|
||||
export function Nav() {
|
||||
const {token, refresh, is_authenticated} = useAuth()
|
||||
const dispatch = useAuthDispatch()
|
||||
|
||||
return <nav className="navbar navbar-light navbar-expand-md bg-body-tertiary " id="main-navbar">
|
||||
<div className="container-fluid">
|
||||
@@ -25,20 +23,43 @@ export function Nav() {
|
||||
<div className="collapse-item">
|
||||
<ul className="navbar-nav">
|
||||
<li className="nav-item"><NavLink className="nav-link" to="/">Accueil</NavLink></li>
|
||||
<li className="nav-item"><NavLink className="nav-link" to="/admin">Administration</NavLink></li>
|
||||
|
||||
<li className="nav-item">
|
||||
{!is_authenticated ? (
|
||||
<div className="nav-link" onClick={() => redirect_auth_page()}>Login</div>
|
||||
) : (
|
||||
<div className="nav-link" onClick={() => {
|
||||
logout(token, refresh).then(() => dispatch({type: 'invalidate'}))
|
||||
}}>Logout</div>
|
||||
)}
|
||||
</li>
|
||||
<AdminMenu/>
|
||||
<LoginMenu/>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
}
|
||||
|
||||
function AdminMenu() {
|
||||
const {is_authenticated, data} = useAuth()
|
||||
|
||||
if (!is_authenticated || !data?.realm_access?.roles?.includes("federation_admin"))
|
||||
return <></>
|
||||
|
||||
return <li className="nav-item dropdown">
|
||||
<div className="nav-link dropdown-toggle" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
Administration
|
||||
</div>
|
||||
<ul className="dropdown-menu">
|
||||
<li className="nav-item"><NavLink className="nav-link" to="/admin/member">Member</NavLink></li>
|
||||
<li className="nav-item"><NavLink className="nav-link" to="/admin/b">B</NavLink></li>
|
||||
</ul>
|
||||
</li>
|
||||
}
|
||||
|
||||
function LoginMenu() {
|
||||
const {token, refresh, is_authenticated} = useAuth()
|
||||
const dispatch = useAuthDispatch()
|
||||
|
||||
return <li className="nav-item">
|
||||
{!is_authenticated ? (
|
||||
<div className="nav-link" onClick={() => redirect_auth_page()}>Connexion</div>
|
||||
) : (
|
||||
<div className="nav-link" onClick={() => {
|
||||
logout(token, refresh).then(() => dispatch({type: 'invalidate'}))
|
||||
}}>Déconnexion</div>
|
||||
)}
|
||||
</li>
|
||||
}
|
||||
56
src/main/webapp/src/hooks/useFetch.js
Normal file
56
src/main/webapp/src/hooks/useFetch.js
Normal file
@@ -0,0 +1,56 @@
|
||||
import {useEffect, useState} from "react";
|
||||
import axios from "axios";
|
||||
|
||||
function stdAction(promise, setData, setErrors, setLoading = null, loadingLevel = 1) {
|
||||
if (setLoading)
|
||||
setLoading(loadingLevel)
|
||||
|
||||
promise.then(data => {
|
||||
setData(data.data)
|
||||
}).catch(e => {
|
||||
setErrors(e.response)
|
||||
}).finally(() => {
|
||||
if (setLoading)
|
||||
setLoading(0)
|
||||
})
|
||||
}
|
||||
|
||||
export function useFetch(url, setLoading = null, loadingLevel = 1, options = {}) {
|
||||
const [data, setData] = useState(null)
|
||||
const [error, setErrors] = useState(null)
|
||||
|
||||
useEffect(() => {
|
||||
stdAction(axios.get(url, {
|
||||
...options,
|
||||
headers: {
|
||||
'Authorization': `Bearer ${localStorage.getItem("access_token")}`,
|
||||
'Accept': 'application/json; charset=UTF-8',
|
||||
...options?.headers
|
||||
}
|
||||
}), setData, setErrors, setLoading, loadingLevel)
|
||||
}, []);
|
||||
|
||||
return {
|
||||
data, error
|
||||
}
|
||||
}
|
||||
|
||||
export function useFetchPut(url, dataRequest, setLoading = null, loadingLevel = 1, options = {}) {
|
||||
const [data, setData] = useState(null)
|
||||
const [error, setErrors] = useState(null)
|
||||
|
||||
useEffect(() => {
|
||||
stdAction(axios.put(url, dataRequest, {
|
||||
...options,
|
||||
headers: {
|
||||
'Authorization': `Bearer ${localStorage.getItem("access_token")}`,
|
||||
'Accept': 'application/json; charset=UTF-8',
|
||||
...options?.headers
|
||||
}
|
||||
}), setData, setErrors, setLoading, loadingLevel)
|
||||
}, []);
|
||||
|
||||
return {
|
||||
data, error
|
||||
}
|
||||
}
|
||||
22
src/main/webapp/src/hooks/useLoading.css
Normal file
22
src/main/webapp/src/hooks/useLoading.css
Normal file
@@ -0,0 +1,22 @@
|
||||
.overlayBG {
|
||||
display: block;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0,0,0,0.5) !important;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.overlayContent {
|
||||
position: absolute;
|
||||
box-sizing: border-box;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
cursor: default;
|
||||
z-index: 100;
|
||||
}
|
||||
43
src/main/webapp/src/hooks/useLoading.jsx
Normal file
43
src/main/webapp/src/hooks/useLoading.jsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import {createContext, useContext, useState} from "react";
|
||||
import './useLoading.css'
|
||||
import {FallingLines} from "react-loader-spinner";
|
||||
|
||||
const LoadingContext = createContext(0);
|
||||
const LoadingSwitcherContext = createContext(undefined);
|
||||
|
||||
export function useLoading() {
|
||||
return useContext(LoadingContext);
|
||||
}
|
||||
|
||||
export function useLoadingSwitcher() {
|
||||
return useContext(LoadingSwitcherContext);
|
||||
}
|
||||
|
||||
export function LoadingContextProvider({children}) {
|
||||
const [showOverlay, setOverlay] = useState(0);
|
||||
|
||||
return <LoadingContext.Provider value={showOverlay}>
|
||||
<LoadingSwitcherContext.Provider value={v => setOverlay(v)}>
|
||||
<div style={{position: 'relative'}}>
|
||||
{children}
|
||||
<LoadingOverLay/>
|
||||
</div>
|
||||
</LoadingSwitcherContext.Provider>
|
||||
</LoadingContext.Provider>
|
||||
}
|
||||
|
||||
function LoadingOverLay() {
|
||||
const showOverlay = useLoading()
|
||||
|
||||
if (showOverlay) {
|
||||
return <div className="overlayBG" style={{position: showOverlay === 1 ? 'absolute' : 'fixed'}}>
|
||||
<div className="overlayContent" onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
}}>
|
||||
<FallingLines/>
|
||||
</div>
|
||||
</div>
|
||||
} else {
|
||||
return <></>
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
:root {
|
||||
|
||||
}
|
||||
@@ -1,13 +1,9 @@
|
||||
import React from 'react'
|
||||
import React, {lazy, Suspense} from 'react'
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import App from './App.jsx'
|
||||
import './index.css'
|
||||
import {KeycloakContextProvider} from "./hooks/useAuth.jsx";
|
||||
import App from "./App.jsx";
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')).render(
|
||||
<React.StrictMode>
|
||||
<KeycloakContextProvider>
|
||||
<App/>
|
||||
</KeycloakContextProvider>
|
||||
<App/>
|
||||
</React.StrictMode>,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
#admin-navbar {
|
||||
background: #eeeeee;
|
||||
|
||||
.nav-link{
|
||||
color: #00aff2;
|
||||
}
|
||||
.nav-link.active{
|
||||
color: #004969;
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
color: #00aff2;
|
||||
}
|
||||
}
|
||||
@@ -1,29 +1,31 @@
|
||||
import {NavLink, Outlet} from "react-router-dom";
|
||||
import './AdminRoot.css'
|
||||
import {LoadingContextProvider} from "../../hooks/useLoading.jsx";
|
||||
import {MemberList} from "./MemberList.jsx";
|
||||
import {MemberPage} from "./MemberPage.jsx";
|
||||
|
||||
export function AdminRoot() {
|
||||
return <>
|
||||
<NavAdmin/>
|
||||
<Outlet/>
|
||||
<h1>Espace administration</h1>
|
||||
<LoadingContextProvider>
|
||||
<Outlet/>
|
||||
</LoadingContextProvider>
|
||||
</>
|
||||
}
|
||||
|
||||
function NavAdmin() {
|
||||
return <nav className="navbar navbar-expand-md bg-body-tertiary " id="admin-navbar">
|
||||
<div className="container-fluid">
|
||||
<button className="navbar-toggler" type="button" data-bs-toggle="collapse"
|
||||
data-bs-target="#adminNavbarNavDropdown" aria-controls="adminNavbarNavDropdown"
|
||||
aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span className="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div className="collapse navbar-collapse" id="adminNavbarNavDropdown">
|
||||
<div className="collapse-item">
|
||||
<ul className="navbar-nav">
|
||||
<li className="nav-item"><NavLink className="nav-link" to="/admin">A</NavLink></li>
|
||||
<li className="nav-item"><NavLink className="nav-link" to="/admin/b">B</NavLink></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
export function getAdminChildren () {
|
||||
return [
|
||||
{
|
||||
path: 'member',
|
||||
element: <MemberList/>
|
||||
},
|
||||
{
|
||||
path: 'member/:id',
|
||||
element: <MemberPage/>
|
||||
},
|
||||
{
|
||||
path: 'b',
|
||||
element: <div>Admin B</div>
|
||||
}
|
||||
]
|
||||
}
|
||||
62
src/main/webapp/src/pages/admin/MemberList.jsx
Normal file
62
src/main/webapp/src/pages/admin/MemberList.jsx
Normal file
@@ -0,0 +1,62 @@
|
||||
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 {useState} from "react";
|
||||
import {Input} from "../../components/Input.jsx";
|
||||
import {useNavigate} from "react-router-dom";
|
||||
|
||||
const api_url = import.meta.env.VITE_API_URL;
|
||||
|
||||
const removeDiacritics = str => {
|
||||
return str
|
||||
.normalize('NFD')
|
||||
.replace(/[\u0300-\u036f]/g, '')
|
||||
}
|
||||
|
||||
export function MemberList() {
|
||||
const setLoading = useLoadingSwitcher()
|
||||
const {data, error} = useFetch(`${api_url}/member/all`, setLoading, 1)
|
||||
const [searchInput, setSearchInput] = useState("");
|
||||
const navigate = useNavigate();
|
||||
|
||||
const visibleMember = data ? data.filter(member => {
|
||||
const lo = removeDiacritics(searchInput).toLowerCase()
|
||||
return !searchInput
|
||||
|| (removeDiacritics(member.fname).toLowerCase().startsWith(lo)
|
||||
|| removeDiacritics(member.lname).toLowerCase().startsWith(lo));
|
||||
}) : [];
|
||||
|
||||
return <>
|
||||
<SearchBar searchInput={searchInput} onSearchInputChange={setSearchInput}/>
|
||||
{data
|
||||
? <div className="list-group">
|
||||
{visibleMember.map(member => (
|
||||
<span key={member.id}
|
||||
onClick={() => navigate("/admin/member/" + member.id)}
|
||||
className="list-group-item list-group-item-action">{member.fname} {member.lname}</span>))}
|
||||
</div>
|
||||
: error
|
||||
? <AxiosError error={error}/>
|
||||
: <Def/>
|
||||
}
|
||||
</>
|
||||
}
|
||||
|
||||
function SearchBar({searchInput, onSearchInputChange}) {
|
||||
return <div>
|
||||
<div className="mb-3">
|
||||
<Input value={searchInput} onChange={onSearchInputChange} placeholder="Rechercher..."/>
|
||||
</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>
|
||||
}
|
||||
228
src/main/webapp/src/pages/admin/MemberPage.jsx
Normal file
228
src/main/webapp/src/pages/admin/MemberPage.jsx
Normal file
@@ -0,0 +1,228 @@
|
||||
import {useNavigate, useParams} from "react-router-dom";
|
||||
import {useLoadingSwitcher} from "../../hooks/useLoading.jsx";
|
||||
import {useFetch, useFetchPut} from "../../hooks/useFetch.js";
|
||||
import {AxiosError} from "../../components/AxiosError.jsx";
|
||||
import {ClubSelect} from "../../components/ClubSelect.jsx";
|
||||
import {useEffect, useState} from "react";
|
||||
import {getCategoryFormBirthDate, getSaison} from "../../utils/Tools.js";
|
||||
import axios from "axios";
|
||||
import imageCompression from "browser-image-compression";
|
||||
|
||||
const api_url = import.meta.env.VITE_API_URL;
|
||||
|
||||
export function MemberPage() {
|
||||
const {id} = useParams()
|
||||
const navigate = useNavigate();
|
||||
|
||||
const setLoading = useLoadingSwitcher()
|
||||
const {data, error} = useFetch(`${api_url}/member/${id}`, setLoading, 1)
|
||||
|
||||
const handleSubmit = (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
setLoading(1)
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("id", data.id);
|
||||
formData.append("lname", event.target.lname?.value);
|
||||
formData.append("fname", event.target.fname?.value);
|
||||
formData.append("categorie", event.target.category?.value);
|
||||
formData.append("club", event.target.club?.value);
|
||||
formData.append("genre", event.target.genre?.value);
|
||||
formData.append("country", event.target.country?.value);
|
||||
formData.append("birth_date", new Date(event.target.birth_date?.value).toUTCString());
|
||||
formData.append("email", event.target.email?.value);
|
||||
formData.append("role", event.target.role?.value);
|
||||
formData.append("grade_arbitrage", event.target.grade_arbitrage?.value);
|
||||
|
||||
const send = (formData_) => {
|
||||
axios.post(`${api_url}/member/${id}`, formData_, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${localStorage.getItem("access_token")}`,
|
||||
'Accept': '*/*',
|
||||
'Content-Type': 'multipart/form-data',
|
||||
}
|
||||
}).then(data => {
|
||||
console.log(data.data)
|
||||
}).catch(e => {
|
||||
console.log(e.response)
|
||||
}).finally(() => {
|
||||
if (setLoading)
|
||||
setLoading(0)
|
||||
})
|
||||
}
|
||||
|
||||
const imageFile = event.target.url_photo.files[0];
|
||||
if (imageFile) {
|
||||
console.log(`originalFile size ${imageFile.size / 1024 / 1024} MB`);
|
||||
|
||||
const options = {
|
||||
maxSizeMB: 1,
|
||||
maxWidthOrHeight: 1920,
|
||||
useWebWorker: true,
|
||||
}
|
||||
|
||||
imageCompression(imageFile, options).then(compressedFile => {
|
||||
console.log(`compressedFile size ${compressedFile.size / 1024 / 1024} MB`); // smaller than maxSizeMB
|
||||
formData.append("photo_data", compressedFile)
|
||||
send(formData)
|
||||
});
|
||||
} else {
|
||||
send(formData)
|
||||
}
|
||||
}
|
||||
|
||||
return <>
|
||||
<h2>Page membre</h2>
|
||||
<button type="button" className="btn btn-link" onClick={() => navigate("/admin/member")}>
|
||||
<< retour
|
||||
</button>
|
||||
{data
|
||||
? <MemberForm data={data} handleSubmit={handleSubmit}/>
|
||||
: error && <AxiosError error={error}/>
|
||||
}
|
||||
</>
|
||||
}
|
||||
|
||||
|
||||
function MemberForm({data, handleSubmit}) {
|
||||
return <div>
|
||||
<div className="row">
|
||||
<div className="col-lg-4">
|
||||
<div className="card mb-4">
|
||||
<div className="card-header">Licence n°{data.licence}</div>
|
||||
<div className="card-body text-center">
|
||||
<div className="input-group mb-3">
|
||||
<img
|
||||
src={"http://localhost:8080/member/"+data.id+"/photo"}
|
||||
alt="avatar"
|
||||
className="rounded-circle img-fluid" style={{object_fit: 'contain'}}/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-lg-8">
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="card mb-4">
|
||||
<div className="card-header">Information</div>
|
||||
<div className="card-body">
|
||||
<TextField name="lname" text="Nom" value={data.lname}/>
|
||||
<TextField name="fname" text="Prénom" value={data.fname}/>
|
||||
<TextField name="email" text="Email" value={data.email} placeholder="name@example.com"
|
||||
type="email"/>
|
||||
<OptionField name="genre" text="Genre" value={data.genre}
|
||||
values={{NA: 'N/A', H: 'H', F: 'F'}}/>
|
||||
<OptionField name="country" text="Pays" value={data.country}
|
||||
values={{NA: 'Sélectionner...', fr: 'FR', es: 'ES', be: 'BE'}}/>
|
||||
<BirthDayField inti_date={data.birth_date ? data.birth_date.split('T')[0] : ''}
|
||||
inti_category={data.categorie}/>
|
||||
<div className="row">
|
||||
<ClubSelect defaultValue={data?.club?.id} name="club"/>
|
||||
</div>
|
||||
<OptionField name="grade_arbitrage" text="Rôle" value={data.role}
|
||||
values={{NA: 'N/A', ASSESSEUR: 'Assesseur', ARBITRE: 'Arbitre'}}/>
|
||||
<OptionField name="role" text="Grade d'arbitrage" value={data.grade_arbitrage}
|
||||
values={{
|
||||
MEMBRE: 'Membre',
|
||||
PRESIDENT: 'Président',
|
||||
TRESORIER: 'Trésorier',
|
||||
SECRETAIRE: 'Secrétaire'
|
||||
}}/>
|
||||
<div className="row">
|
||||
<div className="input-group mb-3">
|
||||
<label className="input-group-text" htmlFor="url_photo">Photos
|
||||
(optionnelle)</label>
|
||||
<input type="file" className="form-control" id="url_photo" name="url_photo"
|
||||
accept=".jpg,.jpeg,.gif,.png,.svg"/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col-md-12 text-right">
|
||||
<button type="submit" className="btn btn-primary">Enregistrer</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div className="row">
|
||||
<div className="col-md-6">
|
||||
<div className="card mb-4 mb-md-0">
|
||||
<div className="card-header">Licence</div>
|
||||
<div className="card-body">
|
||||
<p className="mb-1">Web Design</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<div className="card mb-4 mb-md-0">
|
||||
<div className="card-header">Sélection en équipe de France</div>
|
||||
<div className="card-body">
|
||||
<p className="mb-1">Web Design</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
function BirthDayField({inti_date, inti_category}) {
|
||||
const [date, setDate] = useState(inti_date)
|
||||
const [category, setCategory] = useState(inti_category)
|
||||
const [canUpdate, setCanUpdate] = useState(false)
|
||||
useEffect(() => {
|
||||
const b = category !== getCategoryFormBirthDate(new Date(date), new Date('2023-09-01'))
|
||||
if (b !== canUpdate)
|
||||
setCanUpdate(b)
|
||||
}, [date, category])
|
||||
|
||||
const updateCat = (e) => {
|
||||
console.log(date)
|
||||
setCategory(getCategoryFormBirthDate(new Date(date), new Date('2023-09-01')))
|
||||
}
|
||||
|
||||
|
||||
return <>
|
||||
<div className="input-group mb-3">
|
||||
<span className="input-group-text" id="birth_date">Date de naissance</span>
|
||||
<input type="date" className="form-control" placeholder="jj/mm/aaaa" aria-label="birth_date"
|
||||
name="birth_date" aria-describedby="birth_date" defaultValue={date} required
|
||||
onChange={(e) => setDate(e.target.value)}/>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="input-group mb-3">
|
||||
<span className="input-group-text" id="category">Catégorie</span>
|
||||
<input type="text" className="form-control" placeholder="" name="category"
|
||||
aria-label="category" value={category} aria-describedby="category"
|
||||
disabled/>
|
||||
{canUpdate && <button className="btn btn-outline-secondary" type="button" id="button-addon1"
|
||||
onClick={updateCat}>Mettre à jours</button>}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
|
||||
function OptionField({name, text, values, value}) {
|
||||
return <div className="row">
|
||||
<div className="input-group mb-3">
|
||||
<label className="input-group-text" id={name}>{text}</label>
|
||||
<select className="form-select" id={name} name={name} defaultValue={value} required>
|
||||
{Object.keys(values).map((key, _) => {
|
||||
return (<option key={key} value={key}>{values[key]}</option>)
|
||||
})}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
function TextField({name, text, value, placeholder, type = "text"}) {
|
||||
return <div className="row">
|
||||
<div className="input-group mb-3">
|
||||
<span className="input-group-text" id={name}>{text}</span>
|
||||
<input type={type} className="form-control" placeholder={placeholder ? placeholder : text} aria-label={name}
|
||||
name={name} aria-describedby={name} defaultValue={value} required/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
37
src/main/webapp/src/utils/Tools.js
Normal file
37
src/main/webapp/src/utils/Tools.js
Normal file
@@ -0,0 +1,37 @@
|
||||
export function getCategoryFormBirthDate(birth_date, currentDate = new Date()) {
|
||||
const currentSaison = getSaison(currentDate)
|
||||
const birthYear = birth_date.getFullYear()
|
||||
|
||||
const diff = currentSaison - birthYear;
|
||||
if (diff < 6) {
|
||||
return "SUPER_MINI";
|
||||
} else if (diff < 8) {
|
||||
return "MINI_POUSSIN";
|
||||
} else if (diff < 10) {
|
||||
return "POUSSIN";
|
||||
} else if (diff < 12) {
|
||||
return "BENJAMIN";
|
||||
} else if (diff < 14) {
|
||||
return "MINIME";
|
||||
} else if (diff < 16) {
|
||||
return "CADET";
|
||||
} else if (diff < 18) {
|
||||
return "JUNIOR";
|
||||
} else if (diff < 24) {
|
||||
return "SENIOR1";
|
||||
} else if (diff < 34) {
|
||||
return "SENIOR2";
|
||||
} else if (diff < 44) {
|
||||
return "VETERAN1";
|
||||
} else {
|
||||
return "VETERAN2";
|
||||
}
|
||||
}
|
||||
|
||||
export function getSaison(currentDate = new Date()) {
|
||||
if (currentDate.getMonth() >= 8) { //septembre et plus
|
||||
return currentDate.getFullYear()
|
||||
} else {
|
||||
return currentDate.getFullYear() - 1
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user