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:
@@ -73,7 +73,7 @@ function Event({event}){
|
|||||||
<div className={styles.eventContainerBottom}>
|
<div className={styles.eventContainerBottom}>
|
||||||
{(windowSize.width <= 768 || eventMenu) ? (
|
{(windowSize.width <= 768 || eventMenu) ? (
|
||||||
|
|
||||||
<Button onClick={() => navigate(`/event/` + event.id)}>Voir plus</Button>
|
<Button onClick={() => navigate(`/events/` + event.id)}>Voir plus</Button>
|
||||||
|
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -14,9 +14,9 @@ export default function SettingsModal() {
|
|||||||
|
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
const [name, setName] = useState(user.name);
|
const [name, setName] = useState(user?.name);
|
||||||
const [lastname, setLastName] = useState(user.lastname);
|
const [lastname, setLastName] = useState(user?.lastname);
|
||||||
const [phone, setPhone] = useState(user.phone);
|
const [phone, setPhone] = useState(user?.phone);
|
||||||
|
|
||||||
const handleLogout = () => {
|
const handleLogout = () => {
|
||||||
logout();
|
logout();
|
||||||
|
|||||||
@@ -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()}> S’inscrire </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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ import SettingsModal from "../../components/SettingsModal/SettingsModal.jsx";
|
|||||||
function ProfilePage() {
|
function ProfilePage() {
|
||||||
|
|
||||||
const { user } = useContext(AuthContext);
|
const { user } = useContext(AuthContext);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<div className={`${styles.profileboard} glassCard`}>
|
<div className={`${styles.profileboard} glassCard`}>
|
||||||
@@ -21,22 +22,22 @@ function ProfilePage() {
|
|||||||
</div>
|
</div>
|
||||||
<div className={styles.description}>
|
<div className={styles.description}>
|
||||||
<div className={styles.headerProfile}>
|
<div className={styles.headerProfile}>
|
||||||
<h2> {user.name} {user.lastname} </h2>
|
<h2> {user?.name} {user?.lastname} </h2>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.topDescription}>
|
<div className={styles.topDescription}>
|
||||||
<div className={`${styles.infoBlock} glassBorder`}>
|
<div className={`${styles.infoBlock} glassBorder`}>
|
||||||
<p><strong>Role :</strong> {user.role}</p>
|
<p><strong>Role :</strong> {user?.role}</p>
|
||||||
<p><strong>Membre depuis :</strong> {formatDate(user.created_at)}</p>
|
<p><strong>Membre depuis :</strong> {user && formatDate(user.created_at)}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className={`${styles.infoBlock} glassBorder`}>
|
<div className={`${styles.infoBlock} glassBorder`}>
|
||||||
<p><strong>Mail :</strong> {user.email} </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>
|
<p><strong>Téléphone :</strong> {user?.phone === null ? "Pas de numéro enregistré" : user?.phone}</p>
|
||||||
</div>
|
</div>
|
||||||
<SettingsModal />
|
<SettingsModal />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2>Tâches :</h2>
|
<h2>Tâches :</h2>
|
||||||
{user.tasks && user.tasks.length > 0 ? (
|
{user && user.tasks.length > 0 ? (
|
||||||
user.tasks.map((task, index) => (
|
user.tasks.map((task, index) => (
|
||||||
<Task key={index} task={task} />
|
<Task key={index} task={task} />
|
||||||
))
|
))
|
||||||
|
|||||||
@@ -1,70 +1,46 @@
|
|||||||
import {useState} from "react";
|
import { useState, useEffect } from "react";
|
||||||
import styles from "./eventDetail.module.css";
|
import styles from "./eventDetail.module.css";
|
||||||
import { useParams } from "react-router";
|
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 params = useParams();
|
||||||
const id = params.id;
|
const id = params.id;
|
||||||
|
|
||||||
const [tasks, setTasks] = useState([
|
const [event, setEvent] = useState(null);
|
||||||
{ 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 = {
|
useEffect(() => {
|
||||||
title: "Conférence React 2025",
|
(async () => {
|
||||||
date: "27 Décembre 2025",
|
const e = await getEventById(id);
|
||||||
location: "Paris, France",
|
setEvent(e);
|
||||||
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
|
|
||||||
)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.allContent}>
|
<div className={styles.allContent}>
|
||||||
<div className={`glassCard`}>
|
<div className={`glassCard`}>
|
||||||
<div className={styles.textImage}>
|
<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}>
|
<div className={styles.eventInfo}>
|
||||||
<h2>{event.title}</h2>
|
<h2>{event?.name}</h2>
|
||||||
<p>Date: {event.date}</p>
|
<p>{event && formatDateLetter(event.start)}</p>
|
||||||
<p>Lieu: {event.location}</p>
|
<p>{event?.description}</p>
|
||||||
<p>Description: {event.description}</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2>Tâches</h2>
|
<h2>Tâches</h2>
|
||||||
<ul className={styles.tasks}>
|
<div className={styles.tasks}>
|
||||||
{tasks.map((task, index) => (
|
{event?.tasks?.map((task, index) => (
|
||||||
<li style={{"--i": index}} key={index}>
|
<EventTask task={task} key={index} />
|
||||||
<div className={styles.tasksButton}>
|
|
||||||
<span><strong>{task.text}</strong></span>
|
|
||||||
<button
|
|
||||||
className={styles.registerButton}
|
|
||||||
onClick={() => toggleRegister(index)}
|
|
||||||
>
|
|
||||||
{task.registered ? "Se désinscrire" : "S’inscrire"}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
))}
|
))}
|
||||||
</ul>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default eventDetail;
|
export default EventDetail;
|
||||||
@@ -38,55 +38,11 @@
|
|||||||
flex-shrink: 0;
|
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 {
|
.registerButton:hover {
|
||||||
opacity: 0.85;
|
opacity: 0.85;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tasksButton {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tasks {
|
.tasks {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
padding: 0;
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user