mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-01-28 15:33:22 +00:00
fix: Mise à jour des upcomming events
This commit is contained in:
@ -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 }) => (
|
||||
<div className="bg-stone-50 p-4 rounded-lg shadow-sm border border-gray-100 mb-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<CalendarCheck className="text-blue-500" size={20} />
|
||||
<div>
|
||||
<h4 className="font-medium">{title}</h4>
|
||||
<p className="text-sm text-gray-500">{date}</p>
|
||||
<p className="text-sm mt-1">{description}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
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 },
|
||||
|
||||
@ -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) => (
|
||||
<div className="text-center">{(row.student.associated_class_name)}</div>
|
||||
<div className="text-center">{row.student.associated_class_name}</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
@ -276,25 +298,36 @@ export default function ParentHomePage() {
|
||||
modeSet={PlanningModes.CLASS_SCHEDULE}
|
||||
readOnly={true}
|
||||
>
|
||||
<ParentPlanningSection
|
||||
planningClassName={planningClassName}
|
||||
/>
|
||||
<ParentPlanningSection planningClassName={planningClassName} />
|
||||
</PlanningProvider>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
// Affichage classique avec le tableau des enfants
|
||||
<div>
|
||||
{/* Section des événements à venir */}
|
||||
{upcomingEvents.length > 0 && (
|
||||
<div className="mb-6">
|
||||
<SectionHeader
|
||||
icon={CalendarDays}
|
||||
title="Événements à venir"
|
||||
description="Prochains événements de l'établissement"
|
||||
/>
|
||||
<div className="bg-stone-50 p-4 rounded-lg shadow-sm border border-gray-100">
|
||||
{upcomingEvents.slice(0, 3).map((event, index) => (
|
||||
<EventCard key={index} {...event} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<SectionHeader
|
||||
icon={Users}
|
||||
title="Vos enfants"
|
||||
description="Suivez le parcours de vos enfants"
|
||||
/>
|
||||
<div className="overflow-x-auto">
|
||||
<Table
|
||||
data={children}
|
||||
columns={childrenColumns}
|
||||
/>
|
||||
<Table data={children} columns={childrenColumns} />
|
||||
</div>
|
||||
{/* Composant FileUpload et bouton Valider en dessous du tableau */}
|
||||
{uploadState === 'on' && uploadingStudentId && (
|
||||
|
||||
131
Front-End/src/components/EventCard.js
Normal file
131
Front-End/src/components/EventCard.js
Normal file
@ -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 (
|
||||
<div className="bg-white p-4 rounded-lg shadow-sm border border-gray-200 mb-3 hover:shadow-md transition-shadow">
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="flex-shrink-0 mt-1">
|
||||
<CalendarCheck className="text-emerald-500" size={20} />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h4 className="font-medium text-gray-900 mb-1">{title}</h4>
|
||||
<div className="flex flex-col text-sm text-gray-500">
|
||||
<span className="font-medium text-emerald-600">{dayName}</span>
|
||||
<span>{formattedDate}</span>
|
||||
{timeRange && (
|
||||
<span className="text-xs text-gray-600 mt-1">{timeRange}</span>
|
||||
)}
|
||||
{timeIndicator && (
|
||||
<span className="text-xs text-blue-600 mt-1 font-medium">
|
||||
{timeIndicator}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{description && (
|
||||
<p className="text-sm mt-2 text-gray-700">{description}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default EventCard;
|
||||
Reference in New Issue
Block a user