From f93c42825964d91d11af26541eecb9ba5f01e801 Mon Sep 17 00:00:00 2001 From: Luc SORIGNET Date: Sat, 31 May 2025 14:37:32 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20Mise=20=C3=A0=20jour=20des=20upcomming?= =?UTF-8?q?=20events?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Front-End/src/app/[locale]/admin/page.js | 31 +++-- Front-End/src/app/[locale]/parents/page.js | 63 +++++++--- Front-End/src/components/EventCard.js | 131 +++++++++++++++++++++ 3 files changed, 193 insertions(+), 32 deletions(-) create mode 100644 Front-End/src/components/EventCard.js diff --git a/Front-End/src/app/[locale]/admin/page.js b/Front-End/src/app/[locale]/admin/page.js index aadba19..07c4783 100644 --- a/Front-End/src/app/[locale]/admin/page.js +++ b/Front-End/src/app/[locale]/admin/page.js @@ -1,9 +1,17 @@ 'use client'; import React, { useState, useEffect } from 'react'; import { useTranslations } from 'next-intl'; -import { Users, Clock, CalendarCheck, School, AlertTriangle, CheckCircle2 } from 'lucide-react'; +import { + Users, + Clock, + CalendarCheck, + School, + AlertTriangle, + CheckCircle2, +} from 'lucide-react'; import Loader from '@/components/Loader'; import StatCard from '@/components/StatCard'; +import EventCard from '@/components/EventCard'; import logger from '@/utils/logger'; import { fetchRegisterForms, @@ -15,20 +23,6 @@ import Attendance from '@/components/Grades/Attendance'; import LineChart from '@/components/Charts/LineChart'; import PieChart from '@/components/Charts/PieChart'; -// Composant EventCard pour afficher les événements -const EventCard = ({ title, date, description, type }) => ( -
-
- -
-

{title}

-

{date}

-

{description}

-
-
-
-); - const mockCompletionRate = 72; // en pourcentage export default function DashboardPage() { @@ -39,8 +33,11 @@ export default function DashboardPage() { const [upcomingEvents, setUpcomingEvents] = useState([]); const [absencesToday, setAbsencesToday] = useState([]); - const { selectedEstablishmentId, selectedEstablishmentTotalCapacity, apiDocuseal } = - useEstablishment(); + const { + selectedEstablishmentId, + selectedEstablishmentTotalCapacity, + apiDocuseal, + } = useEstablishment(); const [statusDistribution, setStatusDistribution] = useState([ { label: 'Non envoyé', value: 0 }, diff --git a/Front-End/src/app/[locale]/parents/page.js b/Front-End/src/app/[locale]/parents/page.js index ac61d05..55a160a 100644 --- a/Front-End/src/app/[locale]/parents/page.js +++ b/Front-End/src/app/[locale]/parents/page.js @@ -2,7 +2,14 @@ import React, { useEffect, useState } from 'react'; import { useRouter } from 'next/navigation'; import Table from '@/components/Table'; -import { Edit3, Users, Download, Eye, Upload, CalendarDays } from 'lucide-react'; +import { + Edit3, + Users, + Download, + Eye, + Upload, + CalendarDays, +} from 'lucide-react'; import StatusLabel from '@/components/StatusLabel'; import FileUpload from '@/components/FileUpload'; import { FE_PARENTS_EDIT_SUBSCRIPTION_URL } from '@/utils/Url'; @@ -10,17 +17,16 @@ import { fetchChildren, editRegisterForm, } from '@/app/actions/subscriptionAction'; +import { fetchUpcomingEvents } from '@/app/actions/planningAction'; import logger from '@/utils/logger'; import { BASE_URL } from '@/utils/Url'; import { useEstablishment } from '@/context/EstablishmentContext'; import { useCsrfToken } from '@/context/CsrfContext'; import { useClasses } from '@/context/ClassesContext'; -import { - PlanningProvider, - PlanningModes -} from '@/context/PlanningContext'; +import { PlanningProvider, PlanningModes } from '@/context/PlanningContext'; import SectionHeader from '@/components/SectionHeader'; import ParentPlanningSection from '@/components/ParentPlanningSection'; +import EventCard from '@/components/EventCard'; export default function ParentHomePage() { const [children, setChildren] = useState([]); @@ -30,6 +36,7 @@ export default function ParentHomePage() { const [uploadState, setUploadState] = useState('off'); // État "on" ou "off" pour l'affichage du composant const [showPlanning, setShowPlanning] = useState(false); const [planningClassName, setPlanningClassName] = useState(null); + const [upcomingEvents, setUpcomingEvents] = useState([]); const router = useRouter(); const csrfToken = useCsrfToken(); const [reloadFetch, setReloadFetch] = useState(false); @@ -43,7 +50,20 @@ export default function ParentHomePage() { }); setReloadFetch(false); } - }, [selectedEstablishmentId, reloadFetch]); + }, [selectedEstablishmentId, reloadFetch, user]); + + useEffect(() => { + if (selectedEstablishmentId) { + // Fetch des événements à venir + fetchUpcomingEvents(selectedEstablishmentId) + .then((data) => { + setUpcomingEvents(data); + }) + .catch((error) => { + logger.error('Error fetching upcoming events:', error); + }); + } + }, [selectedEstablishmentId]); function handleView(eleveId) { logger.debug(`View dossier for student id: ${eleveId}`); @@ -106,7 +126,9 @@ export default function ParentHomePage() { }; const showClassPlanning = (student) => { - setPlanningClassName(`${student.associated_class_name} - ${getNiveauLabel(student.level)}`); + setPlanningClassName( + `${student.associated_class_name} - ${getNiveauLabel(student.level)}` + ); setShowPlanning(true); }; @@ -143,7 +165,7 @@ export default function ParentHomePage() { { name: 'Classe', transform: (row) => ( -
{(row.student.associated_class_name)}
+
{row.student.associated_class_name}
), }, { @@ -276,25 +298,36 @@ export default function ParentHomePage() { modeSet={PlanningModes.CLASS_SCHEDULE} readOnly={true} > - + ) : ( // Affichage classique avec le tableau des enfants
+ {/* Section des événements à venir */} + {upcomingEvents.length > 0 && ( +
+ +
+ {upcomingEvents.slice(0, 3).map((event, index) => ( + + ))} +
+
+ )} +
- +
{/* Composant FileUpload et bouton Valider en dessous du tableau */} {uploadState === 'on' && uploadingStudentId && ( diff --git a/Front-End/src/components/EventCard.js b/Front-End/src/components/EventCard.js new file mode 100644 index 0000000..ccc2639 --- /dev/null +++ b/Front-End/src/components/EventCard.js @@ -0,0 +1,131 @@ +import React from 'react'; +import { CalendarCheck } from 'lucide-react'; + +/** + * Formate une date en format français avec jour de la semaine + * @param {string} startDateString - Date de début au format ISO + * @param {string} endDateString - Date de fin au format ISO (optionnel) + * @returns {object} Objet contenant la date formatée et le jour + */ +const formatEventDate = (startDateString, endDateString) => { + if (!startDateString) + return { formattedDate: '', dayName: '', timeIndicator: '', timeRange: '' }; + + try { + const startDate = new Date(startDateString); + const endDate = endDateString ? new Date(endDateString) : null; + const now = new Date(); + const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()); + const eventDate = new Date( + startDate.getFullYear(), + startDate.getMonth(), + startDate.getDate() + ); + + // Options pour le formatage de la date + const dateOptions = { + day: 'numeric', + month: 'long', + year: 'numeric', + }; + + const dayOptions = { + weekday: 'long', + }; + + const timeOptions = { + hour: '2-digit', + minute: '2-digit', + }; + + const formattedDate = startDate.toLocaleDateString('fr-FR', dateOptions); + const dayName = startDate.toLocaleDateString('fr-FR', dayOptions); + + // Formatage de l'heure + let timeRange = ''; + if (endDate) { + const startTime = startDate.toLocaleTimeString('fr-FR', timeOptions); + const endTime = endDate.toLocaleTimeString('fr-FR', timeOptions); + timeRange = `${startTime} - ${endTime}`; + } else { + timeRange = startDate.toLocaleTimeString('fr-FR', timeOptions); + } + + // Calcul des jours restants + const diffTime = eventDate - today; + const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); + + let timeIndicator = ''; + if (diffDays === 0) { + timeIndicator = "Aujourd'hui"; + } else if (diffDays === 1) { + timeIndicator = 'Demain'; + } else if (diffDays > 0 && diffDays <= 7) { + timeIndicator = `Dans ${diffDays} jours`; + } + + return { + formattedDate, + dayName: dayName.charAt(0).toUpperCase() + dayName.slice(1), + timeIndicator, + timeRange, + }; + } catch (error) { + // logger.error('Erreur lors du formatage de la date:', error); + return { + formattedDate: startDateString, + dayName: '', + timeIndicator: '', + timeRange: '', + }; + } +}; + +/** + * Composant EventCard pour afficher les événements à venir + * @param {string} title - Titre de l'événement + * @param {string} start - Date de début de l'événement (format ISO) + * @param {string} end - Date de fin de l'événement (format ISO) + * @param {string} date - Date de l'événement (pour compatibilité) + * @param {string} description - Description de l'événement + * @param {string} type - Type d'événement (optionnel) + * @returns {JSX.Element} Carte d'événement + */ +const EventCard = ({ title, start, end, date, description, type }) => { + // Utiliser start si disponible, sinon date pour compatibilité + const eventDate = start || date; + const { formattedDate, dayName, timeIndicator, timeRange } = formatEventDate( + eventDate, + end + ); + + return ( +
+
+
+ +
+
+

{title}

+
+ {dayName} + {formattedDate} + {timeRange && ( + {timeRange} + )} + {timeIndicator && ( + + {timeIndicator} + + )} +
+ {description && ( +

{description}

+ )} +
+
+
+ ); +}; + +export default EventCard;