@@ -31,6 +32,24 @@ export function Nav() {
}
+function ClubMenu() {
+ const {is_authenticated, userinfo} = useAuth()
+
+ if (!is_authenticated || !(userinfo?.roles?.includes("club_president")
+ || userinfo?.roles?.includes("club_secretaire") || userinfo?.roles?.includes("club_tresorier")))
+ return <>>
+
+ return
+
+ Club
+
+
+
+}
+
function AdminMenu() {
const {is_authenticated, userinfo} = useAuth()
diff --git a/src/main/webapp/src/pages/admin/member/InformationForm.jsx b/src/main/webapp/src/pages/admin/member/InformationForm.jsx
index e88630a..265decc 100644
--- a/src/main/webapp/src/pages/admin/member/InformationForm.jsx
+++ b/src/main/webapp/src/pages/admin/member/InformationForm.jsx
@@ -2,7 +2,7 @@ import {useLoadingSwitcher} from "../../../hooks/useLoading.jsx";
import {apiAxios} from "../../../utils/Tools.js";
import {toast} from "react-toastify";
import imageCompression from "browser-image-compression";
-import {BirthDayField, OptionField, TextField} from "./MemberCustomFiels.jsx";
+import {BirthDayField, OptionField, TextField} from "../../../components/MemberCustomFiels.jsx";
import {ClubSelect} from "../../../components/ClubSelect.jsx";
export function InformationForm({data}) {
diff --git a/src/main/webapp/src/pages/admin/member/LicenceCard.jsx b/src/main/webapp/src/pages/admin/member/LicenceCard.jsx
index 1346079..fd11ec3 100644
--- a/src/main/webapp/src/pages/admin/member/LicenceCard.jsx
+++ b/src/main/webapp/src/pages/admin/member/LicenceCard.jsx
@@ -4,7 +4,7 @@ import {useEffect, useReducer, useState} from "react";
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import {faPen} from "@fortawesome/free-solid-svg-icons";
import {AxiosError} from "../../../components/AxiosError.jsx";
-import {CheckField, TextField} from "./MemberCustomFiels.jsx";
+import {CheckField, TextField} from "../../../components/MemberCustomFiels.jsx";
import {apiAxios, getSaison} from "../../../utils/Tools.js";
import {Input} from "../../../components/Input.jsx";
import {toast} from "react-toastify";
@@ -115,10 +115,9 @@ function removeLicence(id, dispatch) {
success: "Licence supprimée avec succès 🎉",
error: "Échec de la suppression de la licence 😕"
}
- ).then(data => {
+ ).then(_ => {
dispatch({type: 'REMOVE', payload: id})
})
- console.log(id)
}
function ModalContent({licence, dispatch}) {
diff --git a/src/main/webapp/src/pages/admin/member/PremForm.jsx b/src/main/webapp/src/pages/admin/member/PremForm.jsx
index 8f5ab0d..59525e9 100644
--- a/src/main/webapp/src/pages/admin/member/PremForm.jsx
+++ b/src/main/webapp/src/pages/admin/member/PremForm.jsx
@@ -2,7 +2,7 @@ import {useLoadingSwitcher} from "../../../hooks/useLoading.jsx";
import {apiAxios} from "../../../utils/Tools.js";
import {toast} from "react-toastify";
import {useFetch} from "../../../hooks/useFetch.js";
-import {CheckField} from "./MemberCustomFiels.jsx";
+import {CheckField} from "../../../components/MemberCustomFiels.jsx";
import {AxiosError} from "../../../components/AxiosError.jsx";
export function PremForm({userData}) {
@@ -66,7 +66,7 @@ function PremFormContent({userData}) {
FFSAF intra
{data
? <>
-
>
: error && }
diff --git a/src/main/webapp/src/pages/club/ClubRoot.jsx b/src/main/webapp/src/pages/club/ClubRoot.jsx
new file mode 100644
index 0000000..5c92a4a
--- /dev/null
+++ b/src/main/webapp/src/pages/club/ClubRoot.jsx
@@ -0,0 +1,43 @@
+import {Outlet} from "react-router-dom";
+import {LoadingProvider} from "../../hooks/useLoading.jsx";
+import {MemberList} from "./MemberList.jsx";
+import {MemberPage} from "./member/MemberPage.jsx";
+import {useAuth} from "../../hooks/useAuth.jsx";
+
+export function ClubRoot() {
+ const {userinfo} = useAuth()
+ let club = ""
+ if (userinfo?.groups) {
+ for (let group of userinfo.groups) {
+ if (group.startsWith("/club/")) {
+ club = group.slice(group.indexOf("-") + 1)
+ break
+ }
+ }
+ }
+
+ return <>
+
+
Espace club
{club}
+
+
+
+ >
+}
+
+export function getClubChildren() {
+ return [
+ {
+ path: 'member',
+ element:
+ },
+ {
+ path: 'member/:id',
+ element:
+ },
+ {
+ path: 'b',
+ element: Club B
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/main/webapp/src/pages/club/MemberList.jsx b/src/main/webapp/src/pages/club/MemberList.jsx
new file mode 100644
index 0000000..a486f6b
--- /dev/null
+++ b/src/main/webapp/src/pages/club/MemberList.jsx
@@ -0,0 +1,60 @@
+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 removeDiacritics = str => {
+ return str
+ .normalize('NFD')
+ .replace(/[\u0300-\u036f]/g, '')
+}
+
+export function MemberList() {
+ const setLoading = useLoadingSwitcher()
+ const {data, error} = useFetch(`/member/club`, 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 <>
+
+ {data
+ ?
+ {visibleMember.map(member => (
+ navigate("/club/member/" + member.id)}
+ className="list-group-item list-group-item-action">{member.fname} {member.lname}))}
+
+ : error
+ ?
+ :
+ }
+ >
+}
+
+function SearchBar({searchInput, onSearchInputChange}) {
+ return
+}
+
+function Def() {
+ return
+
+
+
+
+
+
+}
\ No newline at end of file
diff --git a/src/main/webapp/src/pages/club/member/CompteInfo.jsx b/src/main/webapp/src/pages/club/member/CompteInfo.jsx
new file mode 100644
index 0000000..e9128c9
--- /dev/null
+++ b/src/main/webapp/src/pages/club/member/CompteInfo.jsx
@@ -0,0 +1,55 @@
+import {toast} from "react-toastify";
+import {apiAxios} from "../../../utils/Tools.js";
+import {useLoadingSwitcher} from "../../../hooks/useLoading.jsx";
+import {useFetch} from "../../../hooks/useFetch.js";
+import {ColoredCircle} from "../../../components/ColoredCircle.jsx";
+import {AxiosError} from "../../../components/AxiosError.jsx";
+
+export function CompteInfo({userData}) {
+
+ return
+
Compte
+
+ {userData.userId
+ ?
+ :
+ <>
+
+
+
Ce membre ne dispose pas de compte...
+ Un compte sera créé par la fédération lors de la validation de sa première licence
+
+
+
+ >
+ }
+
+
+}
+
+function CompteInfoContent({userData}) {
+ const setLoading = useLoadingSwitcher()
+ const {data, error} = useFetch(`/compte/${userData.userId}`, setLoading, 1)
+
+ return <>
+ {data
+ ? <>
+
+
+
Identifiant: {data.login}
+
+
+
+
+ >
+ : error &&
+ } >
+}
diff --git a/src/main/webapp/src/pages/club/member/InformationForm.jsx b/src/main/webapp/src/pages/club/member/InformationForm.jsx
new file mode 100644
index 0000000..4fd541c
--- /dev/null
+++ b/src/main/webapp/src/pages/club/member/InformationForm.jsx
@@ -0,0 +1,101 @@
+// noinspection DuplicatedCode
+
+import {useLoadingSwitcher} from "../../../hooks/useLoading.jsx";
+import {apiAxios} from "../../../utils/Tools.js";
+import {toast} from "react-toastify";
+import imageCompression from "browser-image-compression";
+import {BirthDayField, OptionField, TextField} from "../../../components/MemberCustomFiels.jsx";
+import {ClubSelect} from "../../../components/ClubSelect.jsx";
+
+export function InformationForm({data}) {
+ const setLoading = useLoadingSwitcher()
+ 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("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);
+
+ const send = (formData_) => {
+ apiAxios.post(`/member/club/${data.id}`, formData_, {
+ headers: {
+ 'Accept': '*/*',
+ 'Content-Type': 'multipart/form-data',
+ }
+ }).then(_ => {
+ toast.success('Profile mis à jours avec succès 🎉');
+ }).catch(e => {
+ console.log(e.response)
+ toast.error('Échec de la mise à jours du profile 😕 (code: ' + e.response.status + ')');
+ }).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 ;
+}
\ No newline at end of file
diff --git a/src/main/webapp/src/pages/club/member/LicenceCard.jsx b/src/main/webapp/src/pages/club/member/LicenceCard.jsx
new file mode 100644
index 0000000..48023ea
--- /dev/null
+++ b/src/main/webapp/src/pages/club/member/LicenceCard.jsx
@@ -0,0 +1,176 @@
+import {useLoadingSwitcher} from "../../../hooks/useLoading.jsx";
+import {useFetch} from "../../../hooks/useFetch.js";
+import {useEffect, useReducer, useState} from "react";
+import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
+import {faInfo, faPen} from "@fortawesome/free-solid-svg-icons";
+import {AxiosError} from "../../../components/AxiosError.jsx";
+import {apiAxios, getSaison} from "../../../utils/Tools.js";
+import {toast} from "react-toastify";
+import {ColoredText} from "../../../components/ColoredCircle.jsx";
+
+function licenceReducer(licences, action) {
+ switch (action.type) {
+ case 'REMOVE':
+ return licences.filter(licence => licence.id !== action.payload)
+ case 'UPDATE_OR_ADD':
+ const index = licences.findIndex(licence => licence.id === action.payload.id)
+ if (index === -1) {
+ return [
+ ...licences,
+ action.payload
+ ]
+ } else {
+ licences[index] = action.payload
+ return [...licences]
+ }
+ case 'SORT':
+ return licences.sort((a, b) => b.saison - a.saison)
+ default:
+ throw new Error()
+ }
+}
+
+export function LicenceCard({userData}) {
+ const defaultLicence = {id: -1, membre: userData.id, validate: false, saison: getSaison(), certificate: false}
+
+ const setLoading = useLoadingSwitcher()
+ const {data, error} = useFetch(`/licence/${userData.id}`, setLoading, 1)
+ const [modalLicence, setModal] = useState(defaultLicence)
+ const [licences, dispatch] = useReducer(licenceReducer, [])
+
+ useEffect(() => {
+ if (!data) return
+ for (const dataKey of data) {
+ dispatch({type: 'UPDATE_OR_ADD', payload: dataKey})
+ }
+ dispatch({type: 'SORT'})
+ }, [data]);
+
+ return
+
+
+
Licence
+
+
+
+
+
+
+
+
+
;
+}
+
+function sendLicence(event, dispatch) {
+ event.preventDefault();
+
+ const formData = new FormData(event.target);
+ toast.promise(
+ apiAxios.post(`/licence/club/${formData.get('membre')}`, formData),
+ {
+ pending: "Enregistrement de la demande de licence en cours",
+ success: "Demande de licence enregistrée avec succès 🎉",
+ error: "Échec de la demande de licence 😕"
+ }
+ ).then(data => {
+ dispatch({type: 'UPDATE_OR_ADD', payload: data.data})
+ dispatch({type: 'SORT'})
+ })
+
+}
+
+function removeLicence(id, dispatch) {
+ toast.promise(
+ apiAxios.delete(`/licence/club/${id}`),
+ {
+ pending: "Suppression de la demande en cours",
+ success: "Demande supprimée avec succès 🎉",
+ error: "Échec de la suppression de la demande de licence 😕"
+ }
+ ).then(_ => {
+ dispatch({type: 'REMOVE', payload: id})
+ })
+}
+
+function ModalContent({licence, dispatch}) {
+ const [certificate, setCertificate] = useState(false)
+ const [isNew, setNew] = useState(true)
+
+ const handleCertificateChange = (event) => {
+ setCertificate(event.target.value === 'true');
+ }
+
+ useEffect(() => {
+ if (licence.id !== -1) {
+ setNew(false)
+ setCertificate(licence.certificate)
+ } else {
+ setNew(true)
+ setCertificate(false)
+ }
+ }, [licence]);
+
+ const currentSaison = licence.saison === getSaison();
+
+ return
+}
diff --git a/src/main/webapp/src/pages/club/member/MemberPage.jsx b/src/main/webapp/src/pages/club/member/MemberPage.jsx
new file mode 100644
index 0000000..3f34d2f
--- /dev/null
+++ b/src/main/webapp/src/pages/club/member/MemberPage.jsx
@@ -0,0 +1,70 @@
+import {useNavigate, useParams} from "react-router-dom";
+import {LoadingProvider, useLoadingSwitcher} from "../../../hooks/useLoading.jsx";
+import {useFetch} from "../../../hooks/useFetch.js";
+import {AxiosError} from "../../../components/AxiosError.jsx";
+import {CompteInfo} from "./CompteInfo.jsx";
+import {InformationForm} from "./InformationForm.jsx";
+import {LicenceCard} from "./LicenceCard.jsx";
+
+const vite_url = import.meta.env.VITE_URL;
+
+export function MemberPage() {
+ const {id} = useParams()
+ const navigate = useNavigate();
+
+ const setLoading = useLoadingSwitcher()
+ const {data, error} = useFetch(`/member/${id}`, setLoading, 1)
+
+ return <>
+ Page membre
+
+ {data
+ ?
+ : error &&
+ }
+ >
+}
+
+function PhotoCard({data}) {
+ return
+
Licence n°{data.licence}
+
+
+

+
+
+
;
+}
+
+function SelectCard() {
+ return
+
Sélection en équipe de France
+
+
;
+}