validate + delete user + last event + prototype chart
This commit is contained in:
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -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" };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user