Merge branch 'features/linkEventsPage' into 'dev'

Features/linkeventspage

See merge request sae-but2/2025-26/gestion-benevoles-association/Frontend!59
This commit is contained in:
T'JAMPENS QUENTIN p2406187
2026-01-12 14:55:28 +00:00
10 changed files with 260 additions and 100 deletions
+1 -1
View File
@@ -73,7 +73,7 @@ function Event({event}){
<div className={styles.eventContainerBottom}>
{(windowSize.width <= 768 || eventMenu) ? (
<Button onClick={() => navigate(`/event/` + event.id)}>Voir plus</Button>
<Button onClick={() => navigate(`/events/` + event.id)}>Voir plus</Button>
) : null}
</div>
@@ -14,9 +14,9 @@ export default function SettingsModal() {
const [open, setOpen] = useState(false);
const [name, setName] = useState(user.name);
const [lastname, setLastName] = useState(user.lastname);
const [phone, setPhone] = useState(user.phone);
const [name, setName] = useState(user?.name);
const [lastname, setLastName] = useState(user?.lastname);
const [phone, setPhone] = useState(user?.phone);
const handleLogout = () => {
logout();
+88
View File
@@ -0,0 +1,88 @@
import styles from "./eventTask.module.css";
import { formatDateLetter } from "../../utils/date/formatDateLetter.js";
import Button from "../ui/button/button.jsx";
import assign from "../../utils/tasks/assign.js";
import unAssign from "../../utils/tasks/unAssign.js";
import { useEffect, useState } from "react";
import isAssigned from "../../utils/tasks/isAssigned.js";
import Modal from "../ui/modal/modal.jsx";
export default function EventTask({ task, index }) {
const [assigned, setAssigned] = useState(false);
const [open, setOpen] = useState(false);
const [title, setTitle] = useState("");
const [message, setMessage] = useState("");
async function checkAssign() {
const data = await isAssigned(task.id);
setAssigned(data?.assigned);
}
useEffect(() => {
checkAssign();
}, [])
const handleRegisterBtn = async () => {
const res = await assign(task.id);
switch (res.status) {
case 200:
setTitle("Inscription réussie")
setMessage(`Vous êtes maintenant inscrit à la tâche ${task.name}`);
setOpen(true);
setAssigned(true);
break;
case 409:
setTitle("Erreur lors de l'inscription")
setMessage("Vous êtes déjà inscrit à cette tâche")
setOpen(true);
break;
case 403:
setTitle("Erreur lors de l'inscription")
setMessage("Le nombre maximum d'inscription pour cette tâche à été atteint")
setOpen(true);
break;
}
};
const handleUnassign = async () => {
const res = await unAssign(task.id);
switch (res.status) {
case 200:
setTitle("Désinscription réussie")
setMessage(`Vous n'êtes plus inscrit à la tâche ${task.name}`);
setOpen(true);
setAssigned(false);
break;
case 409:
setTitle("Erreur lors de la désinscription")
setMessage(`Vous n'êtes pas inscrit à cette tâche`);
setOpen(true);
break;
}
};
return <div style={{"--i": index}} key={index} className={styles.content}>
<div className={styles.task}>
<div>
<span><strong>{task.name}</strong></span>
<p className={styles.taskDate}> {formatDateLetter(task.start)} - {formatDateLetter(task.end)} </p>
<p>{task.description}</p>
</div>
{assigned ?
<Button variant={"danger"} onClick={() => handleUnassign()}> Se désinscrire </Button>
:
<Button onClick={() => handleRegisterBtn()}> Sinscrire </Button>
}
</div>
<Modal open={open} onClose={setOpen} title={title}>
<p className={styles.modalContent}>{message}</p>
</Modal>
</div>
}
@@ -0,0 +1,41 @@
.task {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
}
.taskDate {
opacity: 0.5;
}
.content {
margin: 16px 0px 16px 0px;
padding: 10px 20px 10px 20px;
border-radius: 20px;
border: none;
background: transparent;
box-shadow:
inset 1.25px 1.25px 1px rgba(255, 255, 255, 1),
inset -1.25px -1.25px 1px rgba(255, 255, 255, 1),
inset -0.25px 0.25px 1px rgba(0, 0, 0, 0.2),
inset 0.25px -0.25px 1px rgba(0, 0, 0, 0.2);
opacity: 0;
animation: fadeInUp 0.6s ease-out forwards;
animation-delay: calc(var(--i) * 0.1s);
}
.modalContent {
text-align: center;
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
+7 -6
View File
@@ -8,6 +8,7 @@ import SettingsModal from "../../components/SettingsModal/SettingsModal.jsx";
function ProfilePage() {
const { user } = useContext(AuthContext);
return (
<div className={styles.container}>
<div className={`${styles.profileboard} glassCard`}>
@@ -21,22 +22,22 @@ function ProfilePage() {
</div>
<div className={styles.description}>
<div className={styles.headerProfile}>
<h2> {user.name} {user.lastname} </h2>
<h2> {user?.name} {user?.lastname} </h2>
</div>
<div className={styles.topDescription}>
<div className={`${styles.infoBlock} glassBorder`}>
<p><strong>Role :</strong> {user.role}</p>
<p><strong>Membre depuis :</strong> {formatDate(user.created_at)}</p>
<p><strong>Role :</strong> {user?.role}</p>
<p><strong>Membre depuis :</strong> {user && formatDate(user.created_at)}</p>
</div>
<div className={`${styles.infoBlock} glassBorder`}>
<p><strong>Mail :</strong> {user.email} </p>
<p><strong>Téléphone :</strong> {user.phone === null ? "Pas de numéro enregistré" : user.phone}</p>
<p><strong>Mail :</strong> {user?.email} </p>
<p><strong>Téléphone :</strong> {user?.phone === null ? "Pas de numéro enregistré" : user?.phone}</p>
</div>
<SettingsModal />
</div>
<h2>Tâches :</h2>
{user.tasks && user.tasks.length > 0 ? (
{user && user.tasks.length > 0 ? (
user.tasks.map((task, index) => (
<Task key={index} task={task} />
))
+21 -45
View File
@@ -1,70 +1,46 @@
import {useState} from "react";
import { useState, useEffect } from "react";
import styles from "./eventDetail.module.css";
import { useParams } from "react-router";
import getEventById from "../../utils/event/getEventById.js";
import { formatDateLetter } from "../../utils/date/formatDateLetter.js";
import EventTask from "../../components/eventTask/EventTask.jsx";
function eventDetail() {
function EventDetail() {
const params = useParams();
const id = params.id;
const [tasks, setTasks] = useState([
{ text: "Préparer les slides", registered: false },
{ text: "Réserver la salle", registered: false },
{ text: "Envoyer les invitations", registered: false },
{ text: "Tester la configuration technique", registered: false },
{ text: "Organiser le catering", registered: false },
]);
const [event, setEvent] = useState(null);
const event = {
title: "Conférence React 2025",
date: "27 Décembre 2025",
location: "Paris, France",
description: "azertyuiop azertyuiop azertyuiop azertyuiop azertyuiop azertyuiop azertyuiop azertyuiop azertyuiop azertyuiop azertyuiop azertyuiop"
};
const toggleRegister = (index) => {
setTasks((prevTasks) =>
prevTasks.map((task, i) =>
i === index
? { ...task, registered: !task.registered }
: task
)
);
};
useEffect(() => {
(async () => {
const e = await getEventById(id);
setEvent(e);
}) ()
}, [])
return (
<div className={styles.allContent}>
<div className={`glassCard`}>
<div className={styles.textImage}>
<img src="/public/icons/theme/dark.svg" alt="Illustration événement" className={styles.eventImage}/>
<img src="/icons/theme/dark.svg" alt="Illustration événement" className={styles.eventImage}/>
<div className={styles.eventInfo}>
<h2>{event.title}</h2>
<p>Date: {event.date}</p>
<p>Lieu: {event.location}</p>
<p>Description: {event.description}</p>
<h2>{event?.name}</h2>
<p>{event && formatDateLetter(event.start)}</p>
<p>{event?.description}</p>
</div>
</div>
<h2>Tâches</h2>
<ul className={styles.tasks}>
{tasks.map((task, index) => (
<li style={{"--i": index}} key={index}>
<div className={styles.tasksButton}>
<span><strong>{task.text}</strong></span>
<button
className={styles.registerButton}
onClick={() => toggleRegister(index)}
>
{task.registered ? "Se désinscrire" : "Sinscrire"}
</button>
</div>
</li>
<div className={styles.tasks}>
{event?.tasks?.map((task, index) => (
<EventTask task={task} key={index} />
))}
</ul>
</div>
</div>
</div>
);
}
export default eventDetail;
export default EventDetail;
+1 -45
View File
@@ -38,55 +38,11 @@
flex-shrink: 0;
}
.registerButton {
margin-left: auto;
padding: 5px 10px;
border-radius: 12px;
border: none;
cursor: pointer;
background: #019AFF;
color: white;
font-weight: 600;
}
.registerButton:hover {
opacity: 0.85;
}
.tasksButton {
display: flex;
align-items: center;
gap: 16px;
}
.tasks {
list-style: none;
padding: 0;
}
.tasks li {
margin: 16px 0px 16px 0px;
padding: 10px 20px 10px 20px;
border-radius: 20px;
border: none;
background: transparent;
box-shadow:
inset 1.25px 1.25px 1px rgba(255, 255, 255, 1),
inset -1.25px -1.25px 1px rgba(255, 255, 255, 1),
inset -0.25px 0.25px 1px rgba(0, 0, 0, 0.2),
inset 0.25px -0.25px 1px rgba(0, 0, 0, 0.2);
opacity: 0;
animation: fadeInUp 0.6s ease-out forwards;
animation-delay: calc(var(--i) * 0.1s);
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
}
+37
View File
@@ -0,0 +1,37 @@
import getXSRFToken from "../getXSRF.js";
export default async function assign(taskId) {
const csrfToken = await getXSRFToken();
try {
const res = await fetch(
`http://${import.meta.env.VITE_API_URL}/api/events/task/assign`,
{
method: 'POST',
credentials: 'include',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'X-XSRF-TOKEN': csrfToken,
},
body: JSON.stringify({
task_id: taskId
}),
}
);
const data = await res.json();
return {
status: res.status,
message: data?.message ?? null,
};
} catch (err) {
console.error("❌ Erreur réseau :", err);
return {
status: 0,
message: "Network error",
};
}
}
+24
View File
@@ -0,0 +1,24 @@
export default async function isAssigned(taskId) {
try {
const res = await fetch(
`http://${import.meta.env.VITE_API_URL}/api/events/tasks/${taskId}/assigned`,
{
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;
}
}
+37
View File
@@ -0,0 +1,37 @@
import getXSRFToken from "../getXSRF.js";
export default async function unAssign(taskId) {
const csrfToken = await getXSRFToken();
try {
const res = await fetch(
`http://${import.meta.env.VITE_API_URL}/api/events/task/unassign`,
{
method: 'POST',
credentials: 'include',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'X-XSRF-TOKEN': csrfToken,
},
body: JSON.stringify({
task_id: taskId
}),
}
);
const data = await res.json();
return {
status: res.status,
message: data?.message ?? null,
};
} catch (err) {
console.error("❌ Erreur réseau :", err);
return {
status: 0,
message: "Network error",
};
}
}