From a90d52b44bc533ee0c27a1fae66724d2a9ecafd6 Mon Sep 17 00:00:00 2001 From: p2405951 Date: Tue, 6 Jan 2026 09:32:20 +0100 Subject: [PATCH 1/6] =?UTF-8?q?r=C3=A9cup=C3=A9ration=20modifs=20dev=20sur?= =?UTF-8?q?=20new=20branch?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.exemple | 1 - .../PendingMembers/PendingMembers.jsx | 62 +++++++++++++------ src/utils/users/deleteUser.js | 22 +++++++ src/utils/users/getUserToValidate.js | 21 +++++++ src/utils/users/validateUser | 22 +++++++ 5 files changed, 107 insertions(+), 21 deletions(-) delete mode 100644 .env.exemple create mode 100644 src/utils/users/deleteUser.js create mode 100644 src/utils/users/getUserToValidate.js create mode 100644 src/utils/users/validateUser diff --git a/.env.exemple b/.env.exemple deleted file mode 100644 index 3452b5c..0000000 --- a/.env.exemple +++ /dev/null @@ -1 +0,0 @@ -VITE_API_URL= \ No newline at end of file diff --git a/src/components/PendingMembers/PendingMembers.jsx b/src/components/PendingMembers/PendingMembers.jsx index 27dace2..8b43ca1 100644 --- a/src/components/PendingMembers/PendingMembers.jsx +++ b/src/components/PendingMembers/PendingMembers.jsx @@ -1,38 +1,61 @@ -import React from "react"; +import React, { useState, useEffect } from "react"; import Button from "../ui/button/button"; import styles from "./PendingMembers.module.css"; +import getInvalidUsers from "../../utils/users/getUserToValidate"; +import getUserById from "../../utils/users/getUserById"; function pendingMembers() { - const pendingMembers = [ - { name: "Antoine Ordonneau" }, - { name: "Giovanni Josserand" }, - { name: "Quentin T'Jampens" }, - { name: "Quentin T'Jampens" }, - { name: "Quentin T'Jampens" }, - { name: "Quentin T'Jampens" }, - { name: "Quentin T'Jampens" } - ]; + const [pendingMembers, setPendingMembers] = useState([]); + const [loading, setLoading] = useState(true); - const handleValidate = (name) => { - console.log(`Valider ${name}`); + useEffect(() => { + const fetchPendingMembers = async () => { + const userIds = await getInvalidUsers(); + console.log("IDs reçus :", userIds); + + if (userIds && userIds.length > 0) { + const usersWithDetails = await Promise.all( + userIds.map(async (id) => { + const user = await getUserById(id); + console.log("Utilisateur récupéré :", user); + return user; + }) +); + + console.log("Utilisateurs avec détails :", usersWithDetails); + setPendingMembers(usersWithDetails.filter(user => user !== null)); + } + setLoading(false); + }; + fetchPendingMembers(); + }, []); + + const handleValidate = async (id) => { + console.log(`Valider l'utilisateur ID: ${id}`); + setPendingMembers(pendingMembers.filter(member => member.id !== id)); }; - const handleReject = (name) => { - console.log(`Refuser ${name}`); + const handleReject = async (id) => { + console.log(`Refuser l'utilisateur ID: ${id}`); + setPendingMembers(pendingMembers.filter(member => member.id !== id)); }; + if (loading) { + return
Chargement des membres en attente...
; + } + return (

Membres en attente de validation

- {pendingMembers.map((member, index) => ( -
-

{member.name}

+ {pendingMembers.map((member) => ( +
+

{member.name} {member.lastname}

- -
@@ -42,5 +65,4 @@ function pendingMembers() {
); } - export default pendingMembers; diff --git a/src/utils/users/deleteUser.js b/src/utils/users/deleteUser.js new file mode 100644 index 0000000..ff88d96 --- /dev/null +++ b/src/utils/users/deleteUser.js @@ -0,0 +1,22 @@ +export default async function deleteUser(userId) { + try { + const res = await fetch(`http://${import.meta.env.VITE_API_URL}/api/users/${userId}`, { + method: 'DELETE', + credentials: 'include', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + }, + }); + + if (!res.ok) { + throw new Error(`Erreur serveur : ${res.status}`); + } + + const data = await res.json(); + return data; + } catch (err) { + console.error('Erreur lors de la suppression :', err); + return null; + } +} diff --git a/src/utils/users/getUserToValidate.js b/src/utils/users/getUserToValidate.js new file mode 100644 index 0000000..f619c0b --- /dev/null +++ b/src/utils/users/getUserToValidate.js @@ -0,0 +1,21 @@ +export default async function getUserToValidate() { + try { + const res = await fetch(`http://${import.meta.env.VITE_API_URL}/api/users/invalid`, { + method: 'GET', + credentials: 'include', + headers: { + 'Accept': 'application/json' + } + }); + + if (!res.ok) { + throw new Error(`Erreur serveur : ${res.status}`); + } + + const data = await res.json(); + return data; + } catch (err) { + console.error('Erreur :', err); + return null; + } +} \ No newline at end of file diff --git a/src/utils/users/validateUser b/src/utils/users/validateUser new file mode 100644 index 0000000..f366a2b --- /dev/null +++ b/src/utils/users/validateUser @@ -0,0 +1,22 @@ +export default async function validateUser(userId) { + try { + const res = await fetch(`http://${import.meta.env.VITE_API_URL}/api/users/${userId}/validate`, { + method: 'POST', + credentials: 'include', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + }, + }); + + if (!res.ok) { + throw new Error(`Erreur serveur : ${res.status}`); + } + + const data = await res.json(); + return data; + } catch (err) { + console.error('Erreur lors de la validation :', err); + return null; + } +} From e38fdc11ab9210049604f93ec753b8eebc079e4a Mon Sep 17 00:00:00 2001 From: p2405951 Date: Mon, 12 Jan 2026 14:56:01 +0100 Subject: [PATCH 2/6] add responsive+ delete inline style --- .../IncompleteEvent.module.css | 4 +- .../PendingMembers/PendingMembers.jsx | 137 ++++++++++++++++-- .../PendingMembers/PendingMembers.module.css | 4 +- src/components/chart/ParticipationChart.jsx | 83 ++++++++++- .../chart/ParticipationChart.module.css | 123 +++++++++++----- src/utils/users/deleteUser.js | 24 +-- src/utils/users/validateUser | 24 +-- 7 files changed, 314 insertions(+), 85 deletions(-) diff --git a/src/components/IncompleteEvent/IncompleteEvent.module.css b/src/components/IncompleteEvent/IncompleteEvent.module.css index b3f1d70..cce5be6 100644 --- a/src/components/IncompleteEvent/IncompleteEvent.module.css +++ b/src/components/IncompleteEvent/IncompleteEvent.module.css @@ -27,7 +27,7 @@ list-style-type: circle; } -.eventsContainer::-webkit-scrollbar { +/*.eventsContainer::-webkit-scrollbar { height: 8px; } @@ -43,7 +43,7 @@ .eventsContainer::-webkit-scrollbar-thumb:hover { background: rgba(30, 96, 249, 0.7); -} +}*/ .eventCard { min-width: calc((100% - 20px) / 3); diff --git a/src/components/PendingMembers/PendingMembers.jsx b/src/components/PendingMembers/PendingMembers.jsx index 8b43ca1..828a056 100644 --- a/src/components/PendingMembers/PendingMembers.jsx +++ b/src/components/PendingMembers/PendingMembers.jsx @@ -1,8 +1,10 @@ -import React, { useState, useEffect } from "react"; +/*import React, { useState, useEffect } from "react"; import Button from "../ui/button/button"; import styles from "./PendingMembers.module.css"; import getInvalidUsers from "../../utils/users/getUserToValidate"; import getUserById from "../../utils/users/getUserById"; +import validateUser from "../../utils/users/validateUser"; +import deleteUser from "../../utils/users/deleteUser"; function pendingMembers() { const [pendingMembers, setPendingMembers] = useState([]); @@ -30,15 +32,31 @@ function pendingMembers() { fetchPendingMembers(); }, []); - const handleValidate = async (id) => { - console.log(`Valider l'utilisateur ID: ${id}`); - setPendingMembers(pendingMembers.filter(member => member.id !== id)); - }; +const handleReject = async (id) => { + const res = await deleteUser(id); + + if (!res.ok) { + console.error(res.status, res.data); + alert(res.data?.message || "Action refusée"); + return; + } + + setPendingMembers(prev => prev.filter(u => u.id !== id)); +}; + +const handleValidate = async (id) => { + const res = await validateUser(id); + + if (!res.ok) { + alert(res.data?.message || "Validation refusée"); + return; + } + + setPendingMembers(prev => prev.filter(u => u.id !== id)); +}; + + - const handleReject = async (id) => { - console.log(`Refuser l'utilisateur ID: ${id}`); - setPendingMembers(pendingMembers.filter(member => member.id !== id)); - }; if (loading) { return
Chargement des membres en attente...
; @@ -65,4 +83,103 @@ function pendingMembers() {
); } -export default pendingMembers; +export default pendingMembers;*/ +import React, { useState, useEffect } from "react"; +import Button from "../ui/button/button"; +import styles from "./PendingMembers.module.css"; + +import getInvalidUsers from "../../utils/users/getUserToValidate"; +import getUserById from "../../utils/users/getUserById"; +import validateUser from "../../utils/users/validateUser"; +import deleteUser from "../../utils/users/deleteUser"; + +function PendingMembers() { + const [pendingMembers, setPendingMembers] = useState([]); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const fetchPendingMembers = async () => { + setLoading(true); + + const userIds = await getInvalidUsers(); + + if (userIds && userIds.length > 0) { + const users = await Promise.all( + userIds.map((id) => getUserById(id)) + ); + setPendingMembers(users.filter(Boolean)); + } else { + setPendingMembers([]); + } + + setLoading(false); + }; + + fetchPendingMembers(); + }, []); + + const handleValidate = async (id) => { + const ok = await validateUser(id); + + if (!ok) return; + + setPendingMembers((prev) => + prev.filter((member) => member.id !== id) + ); + }; + + const handleReject = async (id) => { + const ok = await deleteUser(id); + + if (!ok) return; + + setPendingMembers((prev) => + prev.filter((member) => member.id !== id) + ); + }; + + if (loading) { + return
Chargement des membres en attente...
; + } + + return ( +
+

Membres en attente de validation

+ +
+ {pendingMembers.length === 0 && ( +

Aucun membre en attente

+ )} + + {pendingMembers.map((member) => ( +
+

+ {member.name} {member.lastname} +

+ +
+ + + +
+
+ ))} +
+
+ ); +} + +export default PendingMembers; diff --git a/src/components/PendingMembers/PendingMembers.module.css b/src/components/PendingMembers/PendingMembers.module.css index 9eaef39..c87d933 100644 --- a/src/components/PendingMembers/PendingMembers.module.css +++ b/src/components/PendingMembers/PendingMembers.module.css @@ -18,7 +18,7 @@ } -.membersContainer::-webkit-scrollbar { +/*.membersContainer::-webkit-scrollbar { width: 8px; } @@ -34,7 +34,7 @@ .membersContainer::-webkit-scrollbar-thumb:hover { background: rgba(125, 249, 30, 0.7); -} +}*/ .memberCard { display: flex; diff --git a/src/components/chart/ParticipationChart.jsx b/src/components/chart/ParticipationChart.jsx index 7ebd731..cfee6da 100644 --- a/src/components/chart/ParticipationChart.jsx +++ b/src/components/chart/ParticipationChart.jsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +/*import React, { useState } from "react"; import { Doughnut } from "react-chartjs-2"; import { Chart, ArcElement } from "chart.js"; import { MenuItem, Select, FormControl } from "@mui/material"; @@ -87,4 +87,83 @@ function participationChart() { ); } -export default participationChart; +export default participationChart;*/ +import React, { useState } from "react"; +import { Doughnut } from "react-chartjs-2"; +import { Chart, ArcElement } from "chart.js"; +import { MenuItem, Select, FormControl } from "@mui/material"; +import styles from "./ParticipationChart.module.css"; + +Chart.register(ArcElement); + +function ParticipationChart() { + const [selected, setSelected] = useState("3"); + const [participationRate, setParticipationRate] = useState(78); + + const chartData = { + labels: ["Progress", "Background"], + datasets: [ + { + data: [participationRate, 100 - participationRate], + backgroundColor: ["#1e60f9ff", "#ffffffff"], + borderWidth: 0, + weight: 10, + cutout: "93%", + circumference: 360, + rotation: -90, + }, + ], + }; + + const chartOptions = { + responsive: true, + maintainAspectRatio: true, + plugins: { + legend: { display: false }, + tooltip: { enabled: false }, + }, + animation: { + animateScale: true, + animateRotate: true, + }, + }; + + return ( +
+
+
+ +
+ + + +
+
+
+
+
+ +
{participationRate}%
+
+
+
+ ); +} + +export default ParticipationChart; + diff --git a/src/components/chart/ParticipationChart.module.css b/src/components/chart/ParticipationChart.module.css index 7fa3515..0da2f5c 100644 --- a/src/components/chart/ParticipationChart.module.css +++ b/src/components/chart/ParticipationChart.module.css @@ -3,15 +3,18 @@ display: flex; flex-direction: column; align-items: center; - height: calc(100vh - 260px); - /* Exemple: calc(100vh - 60px) */ - margin-bottom: auto; + height: 100%; + min-height: calc(100vh - 260px); + box-sizing: border-box; + padding: 20px; + margin: 0; } .participationHeader { display: flex; align-items: center; width: 100%; + margin-bottom: 10px; } .partiText { @@ -27,14 +30,15 @@ .partiText label { font-size: 20px; - text-align: right; + text-align: center; transition: all 0.3s ease; + width: 100%; } - .chartContainer { - max-width: 80%; - width: 400px; + width: 80%; + max-width: 400px; height: auto; + aspect-ratio: 1/1; margin: 0 auto; position: relative; animation: slideEvent2 2s; @@ -59,12 +63,70 @@ color: #333; } +.formControl { + display: flex; + justify-content: center; + width: 100%; + margin-top: 10px; +} + +.formDesign { + margin: 0 auto; + width: fit-content; +} + +.selectRoot { + width: 100px; + height: 35px; + font-size: 19px; + border:none; +} + +.selectRoot .MuiOutlinedInput-notchedOutline { + border: none; +} + +.selectRoot .MuiSelect-select { + padding: 8px; + padding-right: 24px; +} + +.selectRoot .MuiSvgIcon-root { + font-size: 20px; +} + +.selectRoot .MuiMenu-paper { + border-radius: 7px; + width: 100px; + max-width: 100px; +} + +.selectRoot .MuiMenuItem-root { + font-size: 18px; +} + +.selectMenuPaper { + border-radius: 7px; + width: 100px; + max-width: 120px; + max-height: 300px; + overflow-y: auto; + border:none; +} + +.selectMenuItem { + font-size: 18px; + min-height: 40px; + display: flex; + align-items: center; +} + + @keyframes slideEvent2 { from { opacity: 0; transform: translateX(-20px); } - to { opacity: 1; transform: translateX(0); @@ -76,45 +138,32 @@ opacity: 0; transform: translateX(20px); } - to { opacity: 1; transform: translateX(0); } } -.formControl { - display: flex; - justify-content: center; - width: 100%; -} - -.formDesign { - margin: 0 auto; - width: fit-content; -} - @media screen and (max-width: 760px) { - .partiText { - flex-direction: column; - justify-content: center; + .leftBlock { + width: 100%; + height: auto; + min-height: auto; + margin-bottom: 20px; + } + + .chartContainer { + width: 90%; + max-width: 300px; + height: auto; + aspect-ratio: 1/1; } .partiText label { - text-align: center; + font-size: 18px; } - .leftBlock { - width: 100%; - display: flex; - flex-direction: column; - align-items: center; - height: fit-content; - margin-bottom: auto; - margin-top: 30px; + .chartPercentage { + font-size: 48px; } - .chartContainer{ - height:fit-content; - } - -} \ No newline at end of file +} diff --git a/src/utils/users/deleteUser.js b/src/utils/users/deleteUser.js index ff88d96..b8ae5b2 100644 --- a/src/utils/users/deleteUser.js +++ b/src/utils/users/deleteUser.js @@ -1,22 +1,14 @@ export default async function deleteUser(userId) { - try { - const res = await fetch(`http://${import.meta.env.VITE_API_URL}/api/users/${userId}`, { - method: 'DELETE', - credentials: 'include', + const res = await fetch( + `http://${import.meta.env.VITE_API_URL}/api/users/${userId}`, + { + method: "DELETE", + credentials: "include", headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', + Accept: "application/json", }, - }); - - if (!res.ok) { - throw new Error(`Erreur serveur : ${res.status}`); } + ); - const data = await res.json(); - return data; - } catch (err) { - console.error('Erreur lors de la suppression :', err); - return null; - } + return res.ok; } diff --git a/src/utils/users/validateUser b/src/utils/users/validateUser index f366a2b..f5611c8 100644 --- a/src/utils/users/validateUser +++ b/src/utils/users/validateUser @@ -1,22 +1,14 @@ export default async function validateUser(userId) { - try { - const res = await fetch(`http://${import.meta.env.VITE_API_URL}/api/users/${userId}/validate`, { - method: 'POST', - credentials: 'include', + const res = await fetch( + `http://${import.meta.env.VITE_API_URL}/api/users/${userId}/validate`, + { + method: "POST", + credentials: "include", headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', + Accept: "application/json", }, - }); - - if (!res.ok) { - throw new Error(`Erreur serveur : ${res.status}`); } + ); - const data = await res.json(); - return data; - } catch (err) { - console.error('Erreur lors de la validation :', err); - return null; - } + return res.ok; } From 5059e51e3f4c854d9386754b351dbaa991c5db49 Mon Sep 17 00:00:00 2001 From: p2405951 Date: Mon, 12 Jan 2026 15:02:43 +0100 Subject: [PATCH 3/6] fix scrollbar --- .../chart/ParticipationChart.module.css | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/components/chart/ParticipationChart.module.css b/src/components/chart/ParticipationChart.module.css index 0da2f5c..b35dcab 100644 --- a/src/components/chart/ParticipationChart.module.css +++ b/src/components/chart/ParticipationChart.module.css @@ -167,3 +167,23 @@ font-size: 48px; } } +/* scrollbar */ +::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +::-webkit-scrollbar-track { + background: transparent; + border-radius: 10px; +} + +::-webkit-scrollbar-thumb { + background: rgba(100, 100, 100, 0.3); + border-radius: 10px; +} + +::-webkit-scrollbar-thumb:hover { + background: rgba(100, 100, 100, 0.5); +} + From 263e7fea461eff18739005f22aee0a7de6ecf443 Mon Sep 17 00:00:00 2001 From: p2405951 Date: Mon, 12 Jan 2026 15:40:22 +0100 Subject: [PATCH 4/6] implement incomplet events --- .../IncompleteEvent/IncompleteEvent.jsx | 100 +++++++++++---- .../IncompleteEvent.module.css | 114 ++++++++---------- src/components/chart/ParticipationChart.jsx | 90 -------------- 3 files changed, 123 insertions(+), 181 deletions(-) diff --git a/src/components/IncompleteEvent/IncompleteEvent.jsx b/src/components/IncompleteEvent/IncompleteEvent.jsx index b8e3355..0024070 100644 --- a/src/components/IncompleteEvent/IncompleteEvent.jsx +++ b/src/components/IncompleteEvent/IncompleteEvent.jsx @@ -1,35 +1,87 @@ -import React from "react"; +import React, { useState, useEffect } from "react"; import styles from "./IncompleteEvent.module.css"; +import getAllEvents from "../../utils/event/getAllEvents"; function IncompleteEvent() { - const incompleteEvents = [ - { title: "Événement A", tasks: ["Communication sur les réseaux", "Enormément de comm", "Toujours + de comm,Communication sur les réseaux", - "Enormément de comm", "Toujours + de comm","Communication sur les réseaux", "Enormément de comm", "Toujours + de comm","Communication sur les réseaux", "Enormément de comm", "Toujours + de comm]"] }, - { title: "Événement B", tasks: ["Réservation de la salle", "Vérification du matériel"] }, - { title: "Événement C", tasks: ["Communication sur les réseaux", "Enormément de comm", "Toujours + de comm"] }, - { title: "Événement D", tasks: ["Communication sur les réseaux", "Enormément de comm", "Toujours + de comm"] }, - { title: "Événement E", tasks: ["Communication sur les réseaux", "Enormément de comm", "Toujours + de comm,Communication sur les réseaux", - "Enormément de comm", "Toujours + de comm","Communication sur les réseaux", "Enormément de comm", "Toujours + de comm","Communication sur les réseaux", "Enormément de comm", "Toujours + de comm]"] }, - { title: "Événement F", tasks: ["Communication sur les réseaux", "Enormément de comm", "Toujours + de comm,Communication sur les réseaux", - "Enormément de comm", "Toujours + de comm","Communication sur les réseaux", "Enormément de comm", "Toujours + de comm","Communication sur les réseaux", "Enormément de comm", "Toujours + de comm]"] }, - { title: "Événement G", tasks: ["Communication sur les réseaux", "Enormément de comm", "Toujours + de comm,Communication sur les réseaux", - "Enormément de comm", "Toujours + de comm","Communication sur les réseaux", "Enormément de comm", "Toujours + de comm","Communication sur les réseaux", "Enormément de comm", "Toujours + de comm]"] }, -]; + const [events, setEvents] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + const fetchEvents = async () => { + try { + const data = await getAllEvents(); + if (data) { + setEvents(data); + } else { + setError("Aucun événement trouvé."); + } + } catch (err) { + setError("Erreur lors de la récupération des événements."); + console.error(err); + } finally { + setLoading(false); + } + }; + + fetchEvents(); + }, []); + + if (loading) { + return ( +
+

Derniers événements

+
+
+

Chargement des événements...

+
+
+
+ ); + } + + if (error) { + return ( +
+

Derniers événements

+
+
+

{error}

+
+
+
+ ); + } return (
-

Événements incomplets

+

Derniers événements

- {incompleteEvents.map((event, index) => ( -
-

{event.title}

-
    - {event.tasks.map((task, taskIndex) => ( -
  • {task}
  • - ))} -
+ {events.length > 0 ? ( + events.map((event, index) => ( +
+

{event.name}

+

+ {new Date(event.start).toLocaleDateString("fr-FR")} - {new Date(event.end).toLocaleDateString("fr-FR")} +

+

{event.description}

+

Tâches associées :

+
    + {event.tasks && event.tasks.length > 0 ? ( + event.tasks.map((task, taskIndex) => ( +
  • {task.name}
  • + )) + ) : ( +
  • Aucune tâche associée.
  • + )} +
+
+ )) + ) : ( +
+

Aucun événement disponible.

- ))} + )}
); diff --git a/src/components/IncompleteEvent/IncompleteEvent.module.css b/src/components/IncompleteEvent/IncompleteEvent.module.css index cce5be6..836ac34 100644 --- a/src/components/IncompleteEvent/IncompleteEvent.module.css +++ b/src/components/IncompleteEvent/IncompleteEvent.module.css @@ -2,7 +2,7 @@ width: 100%; } -.rightBlock>h2 { +.rightBlock > h2 { font-size: 29px; } @@ -17,34 +17,6 @@ scroll-behavior: smooth; } -.eventCard ul { - align-items: center; - margin-left: 8%; -} - -.eventCard ul li { - text-align: left; - list-style-type: circle; -} - -/*.eventsContainer::-webkit-scrollbar { - height: 8px; -} - -.eventsContainer::-webkit-scrollbar-track { - background: rgba(0, 0, 0, 0.1); - border-radius: 10px; -} - -.eventsContainer::-webkit-scrollbar-thumb { - background: rgba(30, 96, 249, 0.5); - border-radius: 10px; -} - -.eventsContainer::-webkit-scrollbar-thumb:hover { - background: rgba(30, 96, 249, 0.7); -}*/ - .eventCard { min-width: calc((100% - 20px) / 3); max-width: calc((100% - 20px) / 3); @@ -53,44 +25,49 @@ overflow-y: auto; padding: 10px; text-align: center; - padding-bottom: 200px; box-sizing: border-box; opacity: 0; transform: translateX(20px); animation: slideInEvent 1s ease forwards; + flex-shrink: 0; +} - flex-shrink: 1; +.eventCard ul { + align-items: center; + margin-left: 8%; + padding-left: 15px; +} + +.eventCard ul li { + text-align: left; + list-style-type: circle; +} + +.eventCard > h3 { + font-size: 24px; +} + +/* Animation des cartes avec délais */ +.eventCard:nth-child(1) { animation-delay: 0s; } +.eventCard:nth-child(2) { animation-delay: 0.1s; } +.eventCard:nth-child(3) { animation-delay: 0.2s; } +.eventCard:nth-child(4) { animation-delay: 0.3s; } +.eventCard:nth-child(5) { animation-delay: 0.4s; } +.eventCard:nth-child(6) { animation-delay: 0.5s; } + +.emptyEvent { + min-width: calc((100% - 20px) / 3); + max-width: calc((100% - 20px) / 3); + min-height: 250px; + max-height: 250px; padding: 10px; text-align: center; box-sizing: border-box; -} - -.eventCard:nth-child(1) { - animation-delay: 0s; -} - -.eventCard:nth-child(2) { - animation-delay: 0.1s; -} - -.eventCard:nth-child(3) { - animation-delay: 0.2s; -} - -.eventCard:nth-child(4) { - animation-delay: 0.3s; -} - -.eventCard:nth-child(5) { - animation-delay: 0.4s; -} - -.eventCard:nth-child(6) { - animation-delay: 0.5s; -} - -.eventCard>h3 { - font-size: 24px; + display: flex; + justify-content: center; + align-items: center; + opacity: 1; + flex-shrink: 0; } @keyframes slideInEvent { @@ -98,19 +75,19 @@ opacity: 0; transform: translateX(20px); } - to { opacity: 1; transform: translateX(0); } } +/* Adaptation pour mobile */ @media screen and (max-width: 760px) { .eventCard { min-width: 100%; min-height: 200px; max-height: fit-content; - padding-bottom: 0px; + padding-bottom: 0; } .eventCard h3 { @@ -118,18 +95,21 @@ } .eventsContainer { - display: flex; flex-direction: column; - gap: 10px; max-height: 300px; margin-top: 15px; height: fit-content; overflow-y: auto; - padding-bottom: 0px; - scroll-behavior: smooth; + padding-bottom: 0; } - .rightBlock>h2 { + .rightBlock > h2 { font-size: 18px; } -} \ No newline at end of file + + .emptyEvent { + min-width: 100%; + min-height: 200px; + max-height: fit-content; + } +} diff --git a/src/components/chart/ParticipationChart.jsx b/src/components/chart/ParticipationChart.jsx index cfee6da..1d8dbea 100644 --- a/src/components/chart/ParticipationChart.jsx +++ b/src/components/chart/ParticipationChart.jsx @@ -1,93 +1,3 @@ -/*import React, { useState } from "react"; -import { Doughnut } from "react-chartjs-2"; -import { Chart, ArcElement } from "chart.js"; -import { MenuItem, Select, FormControl } from "@mui/material"; -import styles from "./ParticipationChart.module.css"; - -Chart.register(ArcElement); - -function participationChart() { - const [selected, setSelected] = useState("3"); - const [participationRate, setParticipationRate] = useState(78); - - const chartData = { - labels: ["Progress", "Background"], - datasets: [ - { - data: [participationRate, 100 - participationRate], - backgroundColor: ["#1e60f9ff", "#ffffffff"], - borderWidth: 0, - weight: 10, - cutout: "93%", - circumference: 360, - rotation: -90, - }, - ], - }; - - const chartOptions = { - responsive: true, - plugins: { - legend: { display: false }, - tooltip: { enabled: false }, - }, - animation: { - animateScale: true, - animateRotate: true, - }, - }; - - return ( -
-
-
- -
- - - -
-
-
-
-
- -
{participationRate}%
-
-
-
- ); -} - -export default participationChart;*/ import React, { useState } from "react"; import { Doughnut } from "react-chartjs-2"; import { Chart, ArcElement } from "chart.js"; From b0f0aec16e182f75c394fba9b505e6fc59b248c9 Mon Sep 17 00:00:00 2001 From: p2405951 Date: Tue, 13 Jan 2026 11:49:11 +0100 Subject: [PATCH 5/6] validate + delete user + last event + prototype chart --- .../IncompleteEvent/IncompleteEvent.jsx | 73 +++----- .../PendingMembers/PendingMembers.jsx | 170 +++++------------- src/components/chart/ParticipationChart.jsx | 70 ++++---- src/utils/event/eventCounter.js | 28 +++ src/utils/users/deleteUser.js | 45 +++-- src/utils/users/validateUser | 14 -- src/utils/users/validateUser.js | 27 +++ 7 files changed, 194 insertions(+), 233 deletions(-) create mode 100644 src/utils/event/eventCounter.js delete mode 100644 src/utils/users/validateUser create mode 100644 src/utils/users/validateUser.js diff --git a/src/components/IncompleteEvent/IncompleteEvent.jsx b/src/components/IncompleteEvent/IncompleteEvent.jsx index 0024070..b5d83c7 100644 --- a/src/components/IncompleteEvent/IncompleteEvent.jsx +++ b/src/components/IncompleteEvent/IncompleteEvent.jsx @@ -5,25 +5,13 @@ import getAllEvents from "../../utils/event/getAllEvents"; function IncompleteEvent() { const [events, setEvents] = useState([]); const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); useEffect(() => { const fetchEvents = async () => { - try { - const data = await getAllEvents(); - if (data) { - setEvents(data); - } else { - setError("Aucun événement trouvé."); - } - } catch (err) { - setError("Erreur lors de la récupération des événements."); - console.error(err); - } finally { - setLoading(false); - } + const data = await getAllEvents(); + setEvents(data?.data || data || []); + setLoading(false); }; - fetchEvents(); }, []); @@ -40,48 +28,29 @@ function IncompleteEvent() { ); } - if (error) { - return ( -
-

Derniers événements

-
-
-

{error}

-
-
-
- ); - } - return (

Derniers événements

- {events.length > 0 ? ( - events.map((event, index) => ( -
-

{event.name}

-

- {new Date(event.start).toLocaleDateString("fr-FR")} - {new Date(event.end).toLocaleDateString("fr-FR")} -

-

{event.description}

-

Tâches associées :

-
    - {event.tasks && event.tasks.length > 0 ? ( - event.tasks.map((task, taskIndex) => ( -
  • {task.name}
  • - )) - ) : ( -
  • Aucune tâche associée.
  • - )} -
-
- )) - ) : ( -
-

Aucun événement disponible.

+ {events.map((event) => ( +
+

{event.name}

+

+ {new Date(event.start).toLocaleDateString("fr-FR")} - {new Date(event.end).toLocaleDateString("fr-FR")} +

+

{event.description}

+

Tâches associées :

+
    + {event.tasks?.length ? ( + event.tasks.map((task, taskIndex) => ( +
  • {task.name}
  • + )) + ) : ( +
  • Aucune tâche associée.
  • + )} +
- )} + ))}
); diff --git a/src/components/PendingMembers/PendingMembers.jsx b/src/components/PendingMembers/PendingMembers.jsx index 828a056..77fabf7 100644 --- a/src/components/PendingMembers/PendingMembers.jsx +++ b/src/components/PendingMembers/PendingMembers.jsx @@ -1,94 +1,8 @@ -/*import React, { useState, useEffect } from "react"; -import Button from "../ui/button/button"; -import styles from "./PendingMembers.module.css"; -import getInvalidUsers from "../../utils/users/getUserToValidate"; -import getUserById from "../../utils/users/getUserById"; -import validateUser from "../../utils/users/validateUser"; -import deleteUser from "../../utils/users/deleteUser"; - -function pendingMembers() { - const [pendingMembers, setPendingMembers] = useState([]); - const [loading, setLoading] = useState(true); - - useEffect(() => { - const fetchPendingMembers = async () => { - const userIds = await getInvalidUsers(); - console.log("IDs reçus :", userIds); - - if (userIds && userIds.length > 0) { - const usersWithDetails = await Promise.all( - userIds.map(async (id) => { - const user = await getUserById(id); - console.log("Utilisateur récupéré :", user); - return user; - }) -); - - console.log("Utilisateurs avec détails :", usersWithDetails); - setPendingMembers(usersWithDetails.filter(user => user !== null)); - } - setLoading(false); - }; - fetchPendingMembers(); - }, []); - -const handleReject = async (id) => { - const res = await deleteUser(id); - - if (!res.ok) { - console.error(res.status, res.data); - alert(res.data?.message || "Action refusée"); - return; - } - - setPendingMembers(prev => prev.filter(u => u.id !== id)); -}; - -const handleValidate = async (id) => { - const res = await validateUser(id); - - if (!res.ok) { - alert(res.data?.message || "Validation refusée"); - return; - } - - setPendingMembers(prev => prev.filter(u => u.id !== id)); -}; - - - - - if (loading) { - return
Chargement des membres en attente...
; - } - - return ( -
-

Membres en attente de validation

-
- {pendingMembers.map((member) => ( -
-

{member.name} {member.lastname}

-
- - -
-
- ))} -
-
- ); -} -export default pendingMembers;*/ import React, { useState, useEffect } from "react"; import Button from "../ui/button/button"; import styles from "./PendingMembers.module.css"; -import getInvalidUsers from "../../utils/users/getUserToValidate"; +import getUserToValidate from "../../utils/users/getUserToValidate"; import getUserById from "../../utils/users/getUserById"; import validateUser from "../../utils/users/validateUser"; import deleteUser from "../../utils/users/deleteUser"; @@ -96,46 +10,62 @@ import deleteUser from "../../utils/users/deleteUser"; function PendingMembers() { const [pendingMembers, setPendingMembers] = useState([]); const [loading, setLoading] = useState(true); + const [actionLoading, setActionLoading] = useState(null); + const [error, setError] = useState(null); useEffect(() => { const fetchPendingMembers = async () => { setLoading(true); - - const userIds = await getInvalidUsers(); - + const userIds = await getUserToValidate(); if (userIds && userIds.length > 0) { - const users = await Promise.all( - userIds.map((id) => getUserById(id)) - ); + const users = await Promise.all(userIds.map((id) => getUserById(id))); setPendingMembers(users.filter(Boolean)); } else { setPendingMembers([]); } - setLoading(false); }; - fetchPendingMembers(); }, []); - const handleValidate = async (id) => { - const ok = await validateUser(id); - - if (!ok) return; - - setPendingMembers((prev) => - prev.filter((member) => member.id !== id) + const removeMember = (id) => { + setPendingMembers(prevMembers => + prevMembers.filter(member => member.id !== id) ); }; + const handleValidate = async (id) => { + setActionLoading(id); + setError(null); + try { + const result = await validateUser(id); + if (result.success) { + removeMember(id); + } else { + setError(result.message || "Échec de la validation."); + } + } catch (err) { + setError("Erreur lors de la validation."); + } finally { + setActionLoading(null); + } + }; + const handleReject = async (id) => { - const ok = await deleteUser(id); - - if (!ok) return; - - setPendingMembers((prev) => - prev.filter((member) => member.id !== id) - ); + setActionLoading(id); + setError(null); + try { + const result = await deleteUser(id); + if (result.success) { + removeMember(id); + } else { + setError(result.message || "Échec de la suppression."); + } + } catch (err) { + setError("Erreur lors de la suppression."); + } finally { + setActionLoading(null); + } }; if (loading) { @@ -145,34 +75,28 @@ function PendingMembers() { return (

Membres en attente de validation

+ {error &&

{error}

}
- {pendingMembers.length === 0 && ( -

Aucun membre en attente

- )} + {pendingMembers.length === 0 &&

Aucun membre en attente

} {pendingMembers.map((member) => ( -
-

- {member.name} {member.lastname} -

- +
+

{member.name} {member.lastname}

-
diff --git a/src/components/chart/ParticipationChart.jsx b/src/components/chart/ParticipationChart.jsx index 1d8dbea..90581b3 100644 --- a/src/components/chart/ParticipationChart.jsx +++ b/src/components/chart/ParticipationChart.jsx @@ -1,14 +1,32 @@ -import React, { useState } from "react"; +import React, { useState, useEffect } from "react"; import { Doughnut } from "react-chartjs-2"; import { Chart, ArcElement } from "chart.js"; import { MenuItem, Select, FormControl } from "@mui/material"; import styles from "./ParticipationChart.module.css"; +import getAllEvents from "../../utils/event/getAllEvents"; Chart.register(ArcElement); function ParticipationChart() { const [selected, setSelected] = useState("3"); - const [participationRate, setParticipationRate] = useState(78); + + const [eventsCount, setEventsCount] = useState(0); + const [participationRate, setParticipationRate] = useState(0); + + useEffect(() => { + const fetchEvents = async () => { + const res = await getAllEvents(); + + const eventsArray = res?.data ?? []; + + setEventsCount(eventsArray.length); + + const rate = Math.min(eventsArray.length, 100); + setParticipationRate(rate); + }; + + fetchEvents(); + }, []); const chartData = { labels: ["Progress", "Background"], @@ -17,9 +35,7 @@ function ParticipationChart() { data: [participationRate, 100 - participationRate], backgroundColor: ["#1e60f9ff", "#ffffffff"], borderWidth: 0, - weight: 10, cutout: "93%", - circumference: 360, rotation: -90, }, ], @@ -27,48 +43,39 @@ function ParticipationChart() { const chartOptions = { responsive: true, - maintainAspectRatio: true, plugins: { legend: { display: false }, tooltip: { enabled: false }, }, - animation: { - animateScale: true, - animateRotate: true, - }, }; return (
- -
- - - -
+ + + + +
+
-
{participationRate}%
+
+ {eventsCount} events +
@@ -76,4 +83,3 @@ function ParticipationChart() { } export default ParticipationChart; - diff --git a/src/utils/event/eventCounter.js b/src/utils/event/eventCounter.js new file mode 100644 index 0000000..20ee578 --- /dev/null +++ b/src/utils/event/eventCounter.js @@ -0,0 +1,28 @@ +import { useEffect, useState } from "react"; +import getAllEvents from "../../utils/events/getAllEvents"; + +function EventsCounter() { + const [eventsCount, setEventsCount] = useState(0); + const [loading, setLoading] = useState(true); + useEffect(() => { + const fetchEvents = async () => { + try { + const events = await getAllEvents(); + if (Array.isArray(events)) { + setEventsCount(events.length); + } else { + setEventsCount(0); + } + } catch (err) { + console.error(err); + setEventsCount(0); + } finally { + setLoading(false); + } + }; + fetchEvents(); + }, []); + if (loading) return

Chargement...

; + return

Nombre d’événements : {eventsCount}

; +} +export default EventsCounter; diff --git a/src/utils/users/deleteUser.js b/src/utils/users/deleteUser.js index b8ae5b2..5c4a8d3 100644 --- a/src/utils/users/deleteUser.js +++ b/src/utils/users/deleteUser.js @@ -1,14 +1,35 @@ -export default async function deleteUser(userId) { - const res = await fetch( - `http://${import.meta.env.VITE_API_URL}/api/users/${userId}`, - { - method: "DELETE", - credentials: "include", - headers: { - Accept: "application/json", - }, - } - ); +import getXSRFToken from "../getXSRF"; - return res.ok; +export default async function deleteUser(userId) { + + const csrfToken = await getXSRFToken(); + + try { + + + const res = await fetch( + `http://${import.meta.env.VITE_API_URL}/api/users/${userId}`, + { + method: "DELETE", + credentials: "include", + headers: { + Accept: "application/json", + "X-XSRF-TOKEN": csrfToken, + }, + } + ); + + if (!res.ok) { + let message = "Erreur backend"; + try { + const errorData = await res.json(); + message = errorData.message || message; + } catch {} + return { success: false, message }; + } + return { success: true }; + } catch (err) { + console.error(err); + return { success: false, message: "Erreur réseau" }; + } } diff --git a/src/utils/users/validateUser b/src/utils/users/validateUser deleted file mode 100644 index f5611c8..0000000 --- a/src/utils/users/validateUser +++ /dev/null @@ -1,14 +0,0 @@ -export default async function validateUser(userId) { - const res = await fetch( - `http://${import.meta.env.VITE_API_URL}/api/users/${userId}/validate`, - { - method: "POST", - credentials: "include", - headers: { - Accept: "application/json", - }, - } - ); - - return res.ok; -} diff --git a/src/utils/users/validateUser.js b/src/utils/users/validateUser.js new file mode 100644 index 0000000..21f4d1e --- /dev/null +++ b/src/utils/users/validateUser.js @@ -0,0 +1,27 @@ +import getXSRFToken from "../getXSRF.js"; + +export default async function validateUser(userId) { + const csrfToken = await getXSRFToken(); + try { + const res = await fetch( + `http://${import.meta.env.VITE_API_URL}/api/users/${userId}/validate`, + { + method: "POST", + credentials: "include", + headers: { + Accept: "application/json", + 'X-XSRF-TOKEN': csrfToken, + }, + } + ); + if (!res.ok) { + const errorData = await res.json(); + console.error("Erreur backend:", errorData); + return false; + } + return true; + } catch (err) { + console.error("Erreur réseau:", err); + return false; + } +} From 2beb998c50bb0c57d2e301f2e126504da2a33e06 Mon Sep 17 00:00:00 2001 From: p2405951 Date: Tue, 13 Jan 2026 12:19:54 +0100 Subject: [PATCH 6/6] fix merge --- .../IncompleteEvent/IncompleteEvent.jsx | 2 +- src/components/chart/ParticipationChart.jsx | 2 +- src/utils/date/formatDateLetter.js | 20 ++++++++++++++----- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/components/IncompleteEvent/IncompleteEvent.jsx b/src/components/IncompleteEvent/IncompleteEvent.jsx index b5d83c7..ed4ac34 100644 --- a/src/components/IncompleteEvent/IncompleteEvent.jsx +++ b/src/components/IncompleteEvent/IncompleteEvent.jsx @@ -1,6 +1,6 @@ import React, { useState, useEffect } from "react"; import styles from "./IncompleteEvent.module.css"; -import getAllEvents from "../../utils/event/getAllEvents"; +import getAllEvents from "../../utils/events/getAllEvents"; function IncompleteEvent() { const [events, setEvents] = useState([]); diff --git a/src/components/chart/ParticipationChart.jsx b/src/components/chart/ParticipationChart.jsx index 90581b3..18aac36 100644 --- a/src/components/chart/ParticipationChart.jsx +++ b/src/components/chart/ParticipationChart.jsx @@ -3,7 +3,7 @@ import { Doughnut } from "react-chartjs-2"; import { Chart, ArcElement } from "chart.js"; import { MenuItem, Select, FormControl } from "@mui/material"; import styles from "./ParticipationChart.module.css"; -import getAllEvents from "../../utils/event/getAllEvents"; +import getAllEvents from "../../utils/events/getAllEvents"; Chart.register(ArcElement); diff --git a/src/utils/date/formatDateLetter.js b/src/utils/date/formatDateLetter.js index 3480835..883b3c8 100644 --- a/src/utils/date/formatDateLetter.js +++ b/src/utils/date/formatDateLetter.js @@ -1,14 +1,23 @@ + + export function formatDateLetter(d) { + if (!d) return "Date inconnue"; + + const dateObj = new Date(d); + if (isNaN(dateObj.getTime())) return "Date invalide"; + const monthNames = [ "Janvier", "Février", "Mars", "Avril", "Mai", "Juin", "Juillet", "Août", "Septembre", "Octobre", "Novembre", "Décembre" ]; - const [datePart, timePart] = d.split(' '); - const [year, month, day] = datePart.split('-'); - const [hours, minutes] = timePart.split(':'); + const day = dateObj.getDate().toString().padStart(2, "0"); + const month = monthNames[dateObj.getMonth()]; + const year = dateObj.getFullYear(); + const hours = dateObj.getHours().toString().padStart(2, "0"); + const minutes = dateObj.getMinutes().toString().padStart(2, "0"); - return `${day} ${monthNames[parseInt(month, 10) - 1]} ${year} à ${hours}:${minutes}`; + return `${day} ${month} ${year} à ${hours}:${minutes}`; } @@ -19,4 +28,5 @@ export function formatDateLetterJS(d) { ]; const [year, month, day] = d.split('-'); return `${day} ${monthNames[parseInt(month) - 1]} ${year}`; -} \ No newline at end of file +} +