validate + delete user + last event + prototype chart

This commit is contained in:
p2405951
2026-01-13 11:49:11 +01:00
parent 263e7fea46
commit b0f0aec16e
7 changed files with 194 additions and 233 deletions
@@ -5,25 +5,13 @@ import getAllEvents from "../../utils/event/getAllEvents";
function IncompleteEvent() { function IncompleteEvent() {
const [events, setEvents] = useState([]); const [events, setEvents] = useState([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => { useEffect(() => {
const fetchEvents = async () => { const fetchEvents = async () => {
try { const data = await getAllEvents();
const data = await getAllEvents(); setEvents(data?.data || data || []);
if (data) { setLoading(false);
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(); fetchEvents();
}, []); }, []);
@@ -40,48 +28,29 @@ function IncompleteEvent() {
); );
} }
if (error) {
return (
<div className={`${styles.rightBlock} glassCard`}>
<h2>Derniers événements</h2>
<div className={styles.eventsContainer}>
<div className={`${styles.emptyEvent} glassCard`}>
<p>{error}</p>
</div>
</div>
</div>
);
}
return ( return (
<div className={`${styles.rightBlock} glassCard`}> <div className={`${styles.rightBlock} glassCard`}>
<h2>Derniers événements</h2> <h2>Derniers événements</h2>
<div className={styles.eventsContainer}> <div className={styles.eventsContainer}>
{events.length > 0 ? ( {events.map((event) => (
events.map((event, index) => ( <div key={event.id} className={`glassCard ${styles.eventCard}`}>
<div key={event.id} className={`glassCard ${styles.eventCard}`}> <h3>{event.name}</h3>
<h3>{event.name}</h3> <p className={styles.eventDate}>
<p className={styles.eventDate}> {new Date(event.start).toLocaleDateString("fr-FR")} - {new Date(event.end).toLocaleDateString("fr-FR")}
{new Date(event.start).toLocaleDateString("fr-FR")} - {new Date(event.end).toLocaleDateString("fr-FR")} </p>
</p> <p className={styles.eventDescription}>{event.description}</p>
<p className={styles.eventDescription}>{event.description}</p> <h4>Tâches associées :</h4>
<h4>Tâches associées :</h4> <ul className={styles.tasksList}>
<ul className={styles.tasksList}> {event.tasks?.length ? (
{event.tasks && event.tasks.length > 0 ? ( event.tasks.map((task, taskIndex) => (
event.tasks.map((task, taskIndex) => ( <li key={taskIndex}>{task.name}</li>
<li key={taskIndex}>{task.name}</li> ))
)) ) : (
) : ( <li>Aucune tâche associée.</li>
<li>Aucune tâche associée.</li> )}
)} </ul>
</ul>
</div>
))
) : (
<div className={`${styles.emptyEvent} glassCard`}>
<p>Aucun événement disponible.</p>
</div> </div>
)} ))}
</div> </div>
</div> </div>
); );
+47 -123
View File
@@ -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 <div>Chargement des membres en attente...</div>;
}
return (
<div className={`${styles.rightBlock} glassCard`}>
<h2>Membres en attente de validation</h2>
<div className={styles.membersContainer}>
{pendingMembers.map((member) => (
<div key={member.id} className={`glassCard ${styles.memberCard}`}>
<p>{member.name} {member.lastname}</p>
<div className={styles.buttonGroup}>
<Button onClick={() => handleValidate(member.id)} variant="primary">
Valider
</Button>
<Button onClick={() => handleReject(member.id)} variant="danger">
Refuser
</Button>
</div>
</div>
))}
</div>
</div>
);
}
export default pendingMembers;*/
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import Button from "../ui/button/button"; import Button from "../ui/button/button";
import styles from "./PendingMembers.module.css"; 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 getUserById from "../../utils/users/getUserById";
import validateUser from "../../utils/users/validateUser"; import validateUser from "../../utils/users/validateUser";
import deleteUser from "../../utils/users/deleteUser"; import deleteUser from "../../utils/users/deleteUser";
@@ -96,46 +10,62 @@ import deleteUser from "../../utils/users/deleteUser";
function PendingMembers() { function PendingMembers() {
const [pendingMembers, setPendingMembers] = useState([]); const [pendingMembers, setPendingMembers] = useState([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [actionLoading, setActionLoading] = useState(null);
const [error, setError] = useState(null);
useEffect(() => { useEffect(() => {
const fetchPendingMembers = async () => { const fetchPendingMembers = async () => {
setLoading(true); setLoading(true);
const userIds = await getUserToValidate();
const userIds = await getInvalidUsers();
if (userIds && userIds.length > 0) { if (userIds && userIds.length > 0) {
const users = await Promise.all( const users = await Promise.all(userIds.map((id) => getUserById(id)));
userIds.map((id) => getUserById(id))
);
setPendingMembers(users.filter(Boolean)); setPendingMembers(users.filter(Boolean));
} else { } else {
setPendingMembers([]); setPendingMembers([]);
} }
setLoading(false); setLoading(false);
}; };
fetchPendingMembers(); fetchPendingMembers();
}, []); }, []);
const handleValidate = async (id) => { const removeMember = (id) => {
const ok = await validateUser(id); setPendingMembers(prevMembers =>
prevMembers.filter(member => member.id !== id)
if (!ok) return;
setPendingMembers((prev) =>
prev.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 handleReject = async (id) => {
const ok = await deleteUser(id); setActionLoading(id);
setError(null);
if (!ok) return; try {
const result = await deleteUser(id);
setPendingMembers((prev) => if (result.success) {
prev.filter((member) => member.id !== id) removeMember(id);
); } else {
setError(result.message || "Échec de la suppression.");
}
} catch (err) {
setError("Erreur lors de la suppression.");
} finally {
setActionLoading(null);
}
}; };
if (loading) { if (loading) {
@@ -145,34 +75,28 @@ function PendingMembers() {
return ( return (
<div className={`${styles.rightBlock} glassCard`}> <div className={`${styles.rightBlock} glassCard`}>
<h2>Membres en attente de validation</h2> <h2>Membres en attente de validation</h2>
{error && <p style={{ color: "red" }}>{error}</p>}
<div className={styles.membersContainer}> <div className={styles.membersContainer}>
{pendingMembers.length === 0 && ( {pendingMembers.length === 0 && <p>Aucun membre en attente</p>}
<p>Aucun membre en attente</p>
)}
{pendingMembers.map((member) => ( {pendingMembers.map((member) => (
<div <div key={member.id} className={`glassCard ${styles.memberCard}`}>
key={member.id} <p>{member.name} {member.lastname}</p>
className={`glassCard ${styles.memberCard}`}
>
<p>
{member.name} {member.lastname}
</p>
<div className={styles.buttonGroup}> <div className={styles.buttonGroup}>
<Button <Button
onClick={() => handleValidate(member.id)} onClick={() => handleValidate(member.id)}
variant="primary" variant="primary"
disabled={actionLoading === member.id}
> >
Valider {actionLoading === member.id ? "En cours..." : "Valider"}
</Button> </Button>
<Button <Button
onClick={() => handleReject(member.id)} onClick={() => handleReject(member.id)}
variant="danger" variant="danger"
disabled={actionLoading === member.id}
> >
Refuser {actionLoading === member.id ? "En cours..." : "Refuser"}
</Button> </Button>
</div> </div>
</div> </div>
+38 -32
View File
@@ -1,14 +1,32 @@
import React, { useState } from "react"; import React, { useState, useEffect } from "react";
import { Doughnut } from "react-chartjs-2"; import { Doughnut } from "react-chartjs-2";
import { Chart, ArcElement } from "chart.js"; import { Chart, ArcElement } from "chart.js";
import { MenuItem, Select, FormControl } from "@mui/material"; import { MenuItem, Select, FormControl } from "@mui/material";
import styles from "./ParticipationChart.module.css"; import styles from "./ParticipationChart.module.css";
import getAllEvents from "../../utils/event/getAllEvents";
Chart.register(ArcElement); Chart.register(ArcElement);
function ParticipationChart() { function ParticipationChart() {
const [selected, setSelected] = useState("3"); 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 = { const chartData = {
labels: ["Progress", "Background"], labels: ["Progress", "Background"],
@@ -17,9 +35,7 @@ function ParticipationChart() {
data: [participationRate, 100 - participationRate], data: [participationRate, 100 - participationRate],
backgroundColor: ["#1e60f9ff", "#ffffffff"], backgroundColor: ["#1e60f9ff", "#ffffffff"],
borderWidth: 0, borderWidth: 0,
weight: 10,
cutout: "93%", cutout: "93%",
circumference: 360,
rotation: -90, rotation: -90,
}, },
], ],
@@ -27,48 +43,39 @@ function ParticipationChart() {
const chartOptions = { const chartOptions = {
responsive: true, responsive: true,
maintainAspectRatio: true,
plugins: { plugins: {
legend: { display: false }, legend: { display: false },
tooltip: { enabled: false }, tooltip: { enabled: false },
}, },
animation: {
animateScale: true,
animateRotate: true,
},
}; };
return ( return (
<div className={`${styles.leftBlock} glassCard`}> <div className={`${styles.leftBlock} glassCard`}>
<div className={styles.participationHeader}> <div className={styles.participationHeader}>
<div className={`${styles.partiText} glassCard`}> <div className={`${styles.partiText} glassCard`}>
<label htmlFor="months">Taux de participation depuis :</label> <label>Taux de participation depuis :</label>
<div className={styles.formControl}>
<FormControl className={styles.formDesign}> <FormControl className={styles.formDesign}>
<Select <Select
value={selected} value={selected}
onChange={(e) => setSelected(e.target.value)} onChange={(e) => setSelected(e.target.value)}
className={styles.selectRoot} className={styles.selectRoot}
MenuProps={{ >
classes: {paper: styles.selectMenuPaper,}, <MenuItem value="1">1 mois</MenuItem>
MenuListProps: { <MenuItem value="3">3 mois</MenuItem>
className: styles.selectMenuList,}, <MenuItem value="6">6 mois</MenuItem>
}} <MenuItem value="12">12 mois</MenuItem>
displayEmpty </Select>
> </FormControl>
<MenuItem value="1">1 mois</MenuItem>
<MenuItem value="3">3 mois</MenuItem>
<MenuItem value="6">6 mois</MenuItem>
<MenuItem value="12">12 mois</MenuItem>
</Select>
</FormControl>
</div>
</div> </div>
</div> </div>
<div className={styles.chartContainer}> <div className={styles.chartContainer}>
<div className={styles.chartWrapper}> <div className={styles.chartWrapper}>
<Doughnut data={chartData} options={chartOptions} /> <Doughnut data={chartData} options={chartOptions} />
<div className={styles.chartPercentage}>{participationRate}%</div> <div className={styles.chartPercentage}>
{eventsCount} events
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -76,4 +83,3 @@ function ParticipationChart() {
} }
export default ParticipationChart; export default ParticipationChart;
+28
View File
@@ -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 <p>Chargement...</p>;
return <p>Nombre dévénements : {eventsCount}</p>;
}
export default EventsCounter;
+33 -12
View File
@@ -1,14 +1,35 @@
export default async function deleteUser(userId) { import getXSRFToken from "../getXSRF";
const res = await fetch(
`http://${import.meta.env.VITE_API_URL}/api/users/${userId}`,
{
method: "DELETE",
credentials: "include",
headers: {
Accept: "application/json",
},
}
);
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" };
}
} }
-14
View File
@@ -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;
}
+27
View File
@@ -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;
}
}