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() {
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 (
<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 (
<div className={`${styles.rightBlock} glassCard`}>
<h2>Derniers événements</h2>
<div className={styles.eventsContainer}>
{events.length > 0 ? (
events.map((event, index) => (
<div key={event.id} className={`glassCard ${styles.eventCard}`}>
<h3>{event.name}</h3>
<p className={styles.eventDate}>
{new Date(event.start).toLocaleDateString("fr-FR")} - {new Date(event.end).toLocaleDateString("fr-FR")}
</p>
<p className={styles.eventDescription}>{event.description}</p>
<h4>Tâches associées :</h4>
<ul className={styles.tasksList}>
{event.tasks && event.tasks.length > 0 ? (
event.tasks.map((task, taskIndex) => (
<li key={taskIndex}>{task.name}</li>
))
) : (
<li>Aucune tâche associée.</li>
)}
</ul>
</div>
))
) : (
<div className={`${styles.emptyEvent} glassCard`}>
<p>Aucun événement disponible.</p>
{events.map((event) => (
<div key={event.id} className={`glassCard ${styles.eventCard}`}>
<h3>{event.name}</h3>
<p className={styles.eventDate}>
{new Date(event.start).toLocaleDateString("fr-FR")} - {new Date(event.end).toLocaleDateString("fr-FR")}
</p>
<p className={styles.eventDescription}>{event.description}</p>
<h4>Tâches associées :</h4>
<ul className={styles.tasksList}>
{event.tasks?.length ? (
event.tasks.map((task, taskIndex) => (
<li key={taskIndex}>{task.name}</li>
))
) : (
<li>Aucune tâche associée.</li>
)}
</ul>
</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 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 (
<div className={`${styles.rightBlock} glassCard`}>
<h2>Membres en attente de validation</h2>
{error && <p style={{ color: "red" }}>{error}</p>}
<div className={styles.membersContainer}>
{pendingMembers.length === 0 && (
<p>Aucun membre en attente</p>
)}
{pendingMembers.length === 0 && <p>Aucun membre en attente</p>}
{pendingMembers.map((member) => (
<div
key={member.id}
className={`glassCard ${styles.memberCard}`}
>
<p>
{member.name} {member.lastname}
</p>
<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"
disabled={actionLoading === member.id}
>
Valider
{actionLoading === member.id ? "En cours..." : "Valider"}
</Button>
<Button
onClick={() => handleReject(member.id)}
variant="danger"
disabled={actionLoading === member.id}
>
Refuser
{actionLoading === member.id ? "En cours..." : "Refuser"}
</Button>
</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 { 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 (
<div className={`${styles.leftBlock} glassCard`}>
<div className={styles.participationHeader}>
<div className={`${styles.partiText} glassCard`}>
<label htmlFor="months">Taux de participation depuis :</label>
<div className={styles.formControl}>
<FormControl className={styles.formDesign}>
<Select
value={selected}
onChange={(e) => setSelected(e.target.value)}
className={styles.selectRoot}
MenuProps={{
classes: {paper: styles.selectMenuPaper,},
MenuListProps: {
className: styles.selectMenuList,},
}}
displayEmpty
>
<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>
<label>Taux de participation depuis :</label>
<FormControl className={styles.formDesign}>
<Select
value={selected}
onChange={(e) => setSelected(e.target.value)}
className={styles.selectRoot}
>
<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 className={styles.chartContainer}>
<div className={styles.chartWrapper}>
<Doughnut data={chartData} options={chartOptions} />
<div className={styles.chartPercentage}>{participationRate}%</div>
<div className={styles.chartPercentage}>
{eventsCount} events
</div>
</div>
</div>
</div>
@@ -76,4 +83,3 @@ function 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) {
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" };
}
}
-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;
}
}