Merge branch 'features/linkVolunteersPage' into 'dev'

Features/linkvolunteerspage

See merge request sae-but2/2025-26/gestion-benevoles-association/Frontend!26
This commit is contained in:
T'JAMPENS QUENTIN p2406187
2025-12-05 14:00:13 +00:00
5 changed files with 216 additions and 187 deletions
+83
View File
@@ -0,0 +1,83 @@
import React, {useEffect, useState} from "react";
import styles from "./VolunteersPage.module.css";
import getUserById from "../../utils/users/getUserById.js";
import formatDate from "../../utils/date/formatDate.js";
import { useNavigate } from "react-router";
import Button from "../../components/ui/button/button.jsx";
export default function VolunteerCard({ id }) {
const [isExpanded, setIsExpanded] = useState(false);
const [user, setUser] = useState({});
const toggleExpand = () => {
setIsExpanded(!isExpanded);
};
useEffect(() => {
(async () => {
const data = await getUserById(id);
setUser(data);
}) ()
}, []);
const navigate = useNavigate();
return (
<div className={`${styles.profileboard} glassCard`}>
{/* Header : titre + bouton expand */}
<div className={styles.header}>
<h2>{user?.name}</h2>
<button
className={`${styles.expandButton} glassCard`}
onClick={toggleExpand}
>
{isExpanded ? "▼" : "▲"}
</button>
</div>
<div className={styles.mainContent}>
{!isExpanded ? (
<div className={styles.basicInfo}>
<p><strong>Rôle :</strong> {user?.role}</p>
<p><strong>Membre depuis :</strong> {user?.created_at && formatDate(user.created_at)}</p>
</div>
) : (
<div className={styles.expandedContent}>
<div className={styles.profilePictureContainer}>
<img src={"/react.svg"} alt="profile" className={styles.profilePicture} />
</div>
<div className={styles.textInfo}>
<div className={styles.basicInfo}>
<p><strong>Rôle :</strong> {user?.role}</p>
<p><strong>Membre depuis :</strong> {user?.created_at && formatDate(user.created_at)}</p>
</div>
<div className={styles.contactInfo}>
<p><strong>Email :</strong> {user?.email}</p>
{user?.phone && <p><strong>Téléphone :</strong> {user.phone}</p>}
</div>
</div>
</div>
)}
</div>
{isExpanded && (
<div className={styles.bottomBar}>
<Button
onClick={() => navigate(`/profile/${id}`)}
variant="transparent"
>
Voir plus
</Button>
</div>
)}
</div>
);
}
+13 -96
View File
@@ -1,106 +1,23 @@
import { useState, useEffect } from "react";
import React, { useState } from "react";
import styles from "./VolunteersPage.module.css"; import styles from "./VolunteersPage.module.css";
import VolunteerCard from "./VolunteerCard.jsx";
import getAllUser from "../../utils/users/getAllUsers.js";
function Volunteer({ name, role, since, email, phone, photo }) {
const [isExpanded, setIsExpanded] = useState(false);
const toggleExpand = () => {
setIsExpanded(!isExpanded);
};
return (
<div className={`${styles.profileboard} glassCard`}>
<div className={styles.mainContent}>
{!isExpanded ? (
<div className={styles.basicInfo}>
<h2>{name}</h2>
<p><strong>Rôle :</strong> {role}</p>
<p><strong>Membre depuis :</strong> {since}</p>
</div>
) : (
<div className={styles.expandedContent}>
<div className={styles.profilePictureContainer}>
<img src={photo} alt={`${name}'s profile`} className={styles.profilePicture} />
</div>
<div className={styles.textInfo}>
<div className={styles.basicInfo}>
<h2>{name}</h2>
<p><strong>Rôle :</strong> {role}</p>
<p><strong>Membre depuis :</strong> {since}</p>
</div>
<div className={styles.contactInfo}>
<p><strong>Email :</strong> {email}</p>
<p><strong>Téléphone :</strong> {phone}</p>
</div>
</div>
</div>
)}
<button
className={`${styles.expandButton} glassCard`}
onClick={toggleExpand}
>
{isExpanded ? "▲" : "▼"}
</button>
{isExpanded && (
<div className="voirPlus">
<a href="#" className={styles.voirPlus}>VOIR PLUS</a>
</div>
)}
</div>
</div>
);
}
function VolunteerPage() { function VolunteerPage() {
const volunteers = [
{ const [users, setUsers] = useState([]);
name: "LEBLOND Malo",
role: "Président", useEffect(() => {
since: "2019", (async () => {
email: "malo.leblond@example.com", const data = await getAllUser();
phone: "08 67 87 32 21", setUsers(data);
photo: "../src/assets/react.svg", }) ()
}, }, [])
{
name: "DUPONT Jean",
role: "Bénévole",
since: "2020",
email: "jean.dupont@example.com",
phone: "06 12 34 56 78",
photo: "../src/assets/react.svg",
},
{
name: "MARTIN Sophie",
role: "Bénévole",
since: "2021",
email: "sophie.martin@example.com",
phone: "06 87 65 43 21",
photo: "../src/assets/react.svg",
},
{
name: "MARTIN Sophie",
role: "Bénévole",
since: "2021",
email: "sophie.martin@example.com",
phone: "06 87 65 43 21",
photo: "../src/assets/react.svg",
},
];
return ( return (
<div className={styles.container}> <div className={styles.container}>
{volunteers.map((volunteer, index) => ( {users.map((user, index) => <VolunteerCard key={index} id={user} /> )}
<Volunteer
key={index}
name={volunteer.name}
role={volunteer.role}
since={volunteer.since}
email={volunteer.email}
phone={volunteer.phone}
photo={volunteer.photo}
/>
))}
</div> </div>
); );
} }
+37 -49
View File
@@ -1,25 +1,37 @@
.container { .container {
width: 100%; width: 100%;
padding: 10px; padding: 10px;
box-sizing: border-box;
margin-top: 50px; margin-top: 50px;
box-sizing: border-box;
} }
.profileboard { .profileboard {
width: 100%; width: 100%;
padding: 15px; padding: 15px;
border-radius: 10px;
box-sizing: border-box;
margin-top: 3%; margin-top: 3%;
position: relative; display: flex;
flex-direction: column;
gap: 10px;
box-sizing: border-box;
} }
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.header h2 {
margin: 0;
}
.mainContent { .mainContent {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: space-between;
align-items: flex-start;
width: 100%; width: 100%;
flex: 1;
} }
.basicInfo { .basicInfo {
@@ -29,23 +41,9 @@
.expandedContent { .expandedContent {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: flex-start;
width: 100%; width: 100%;
} }
.profilePictureContainer {
width: 120px;
height: 120px;
margin-right: 20px;
}
.profilePicture {
width: 100%;
height: 100%;
border-radius: 50%;
object-fit: cover;
}
.textInfo { .textInfo {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -60,6 +58,24 @@
margin: 5px 0; margin: 5px 0;
} }
.bottomBar {
display: flex;
justify-content: flex-end;
}
.profilePictureContainer {
width: 120px;
height: 120px;
margin-right: 20px;
}
.profilePicture {
width: 100%;
height: 100%;
border-radius: 50%;
object-fit: cover;
}
.expandButton { .expandButton {
width: 30px; width: 30px;
height: 30px; height: 30px;
@@ -70,24 +86,6 @@
background: transparent; background: transparent;
border: none; border: none;
font-size: 16px; font-size: 16px;
margin-left: 10px;
position: absolute;
top: 10px;
right: 10px;
}
.voirPlus {
position: absolute;
bottom: 10px;
right: 10px;
color: #007bff;
text-decoration: none;
font-weight: bold;
cursor: pointer;
}
.voirPlus:hover {
text-decoration: underline;
} }
@media screen and (min-width: 768px) { @media screen and (min-width: 768px) {
@@ -95,18 +93,13 @@
width: 150px; width: 150px;
height: 150px; height: 150px;
} }
.voirPlus{
text-align: right;
}
} }
@media screen and (min-width: 1024px) { @media screen and (min-width: 1024px) {
.container { .container {
max-width: 1200px; max-width: 1200px;
margin: 0 auto; margin: 50px auto;
padding: 20px; padding: 20px;
margin-top:50px;
margin-bottom: 50px;
} }
.profileboard { .profileboard {
@@ -118,8 +111,3 @@
height: auto; height: auto;
} }
} }
+21
View File
@@ -0,0 +1,21 @@
export default async function getAllUser() {
try {
const res = await fetch(`http://${import.meta.env.VITE_API_URL}/api/users`, {
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;
}
}
+20
View File
@@ -0,0 +1,20 @@
export default async function getUserById (id) {
try {
const res = await fetch(`http://${import.meta.env.VITE_API_URL}/api/users/${id}`, {
method: 'GET',
credentials: 'include',
headers: {
'Accept': 'application/json'
}
});
if (!res.ok) {
throw new Error(`Erreur serveur : ${res.status}`);
}
return await res.json();
} catch (err) {
console.error('Erreur :', err);
return null;
}
}