mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-01-28 23:43:22 +00:00
fix: Mise à jour des upcomming events
This commit is contained in:
@ -1,9 +1,17 @@
|
|||||||
'use client';
|
'use client';
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { useTranslations } from 'next-intl';
|
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 Loader from '@/components/Loader';
|
||||||
import StatCard from '@/components/StatCard';
|
import StatCard from '@/components/StatCard';
|
||||||
|
import EventCard from '@/components/EventCard';
|
||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
import {
|
import {
|
||||||
fetchRegisterForms,
|
fetchRegisterForms,
|
||||||
@ -15,20 +23,6 @@ import Attendance from '@/components/Grades/Attendance';
|
|||||||
import LineChart from '@/components/Charts/LineChart';
|
import LineChart from '@/components/Charts/LineChart';
|
||||||
import PieChart from '@/components/Charts/PieChart';
|
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
|
const mockCompletionRate = 72; // en pourcentage
|
||||||
|
|
||||||
export default function DashboardPage() {
|
export default function DashboardPage() {
|
||||||
@ -39,8 +33,11 @@ export default function DashboardPage() {
|
|||||||
const [upcomingEvents, setUpcomingEvents] = useState([]);
|
const [upcomingEvents, setUpcomingEvents] = useState([]);
|
||||||
|
|
||||||
const [absencesToday, setAbsencesToday] = useState([]);
|
const [absencesToday, setAbsencesToday] = useState([]);
|
||||||
const { selectedEstablishmentId, selectedEstablishmentTotalCapacity, apiDocuseal } =
|
const {
|
||||||
useEstablishment();
|
selectedEstablishmentId,
|
||||||
|
selectedEstablishmentTotalCapacity,
|
||||||
|
apiDocuseal,
|
||||||
|
} = useEstablishment();
|
||||||
|
|
||||||
const [statusDistribution, setStatusDistribution] = useState([
|
const [statusDistribution, setStatusDistribution] = useState([
|
||||||
{ label: 'Non envoyé', value: 0 },
|
{ label: 'Non envoyé', value: 0 },
|
||||||
|
|||||||
@ -2,7 +2,14 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import Table from '@/components/Table';
|
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 StatusLabel from '@/components/StatusLabel';
|
||||||
import FileUpload from '@/components/FileUpload';
|
import FileUpload from '@/components/FileUpload';
|
||||||
import { FE_PARENTS_EDIT_SUBSCRIPTION_URL } from '@/utils/Url';
|
import { FE_PARENTS_EDIT_SUBSCRIPTION_URL } from '@/utils/Url';
|
||||||
@ -10,17 +17,16 @@ import {
|
|||||||
fetchChildren,
|
fetchChildren,
|
||||||
editRegisterForm,
|
editRegisterForm,
|
||||||
} from '@/app/actions/subscriptionAction';
|
} from '@/app/actions/subscriptionAction';
|
||||||
|
import { fetchUpcomingEvents } from '@/app/actions/planningAction';
|
||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
import { BASE_URL } from '@/utils/Url';
|
import { BASE_URL } from '@/utils/Url';
|
||||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||||
import { useCsrfToken } from '@/context/CsrfContext';
|
import { useCsrfToken } from '@/context/CsrfContext';
|
||||||
import { useClasses } from '@/context/ClassesContext';
|
import { useClasses } from '@/context/ClassesContext';
|
||||||
import {
|
import { PlanningProvider, PlanningModes } from '@/context/PlanningContext';
|
||||||
PlanningProvider,
|
|
||||||
PlanningModes
|
|
||||||
} from '@/context/PlanningContext';
|
|
||||||
import SectionHeader from '@/components/SectionHeader';
|
import SectionHeader from '@/components/SectionHeader';
|
||||||
import ParentPlanningSection from '@/components/ParentPlanningSection';
|
import ParentPlanningSection from '@/components/ParentPlanningSection';
|
||||||
|
import EventCard from '@/components/EventCard';
|
||||||
|
|
||||||
export default function ParentHomePage() {
|
export default function ParentHomePage() {
|
||||||
const [children, setChildren] = useState([]);
|
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 [uploadState, setUploadState] = useState('off'); // État "on" ou "off" pour l'affichage du composant
|
||||||
const [showPlanning, setShowPlanning] = useState(false);
|
const [showPlanning, setShowPlanning] = useState(false);
|
||||||
const [planningClassName, setPlanningClassName] = useState(null);
|
const [planningClassName, setPlanningClassName] = useState(null);
|
||||||
|
const [upcomingEvents, setUpcomingEvents] = useState([]);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const csrfToken = useCsrfToken();
|
const csrfToken = useCsrfToken();
|
||||||
const [reloadFetch, setReloadFetch] = useState(false);
|
const [reloadFetch, setReloadFetch] = useState(false);
|
||||||
@ -43,7 +50,20 @@ export default function ParentHomePage() {
|
|||||||
});
|
});
|
||||||
setReloadFetch(false);
|
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) {
|
function handleView(eleveId) {
|
||||||
logger.debug(`View dossier for student id: ${eleveId}`);
|
logger.debug(`View dossier for student id: ${eleveId}`);
|
||||||
@ -106,7 +126,9 @@ export default function ParentHomePage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const showClassPlanning = (student) => {
|
const showClassPlanning = (student) => {
|
||||||
setPlanningClassName(`${student.associated_class_name} - ${getNiveauLabel(student.level)}`);
|
setPlanningClassName(
|
||||||
|
`${student.associated_class_name} - ${getNiveauLabel(student.level)}`
|
||||||
|
);
|
||||||
setShowPlanning(true);
|
setShowPlanning(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -143,7 +165,7 @@ export default function ParentHomePage() {
|
|||||||
{
|
{
|
||||||
name: 'Classe',
|
name: 'Classe',
|
||||||
transform: (row) => (
|
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}
|
modeSet={PlanningModes.CLASS_SCHEDULE}
|
||||||
readOnly={true}
|
readOnly={true}
|
||||||
>
|
>
|
||||||
<ParentPlanningSection
|
<ParentPlanningSection planningClassName={planningClassName} />
|
||||||
planningClassName={planningClassName}
|
|
||||||
/>
|
|
||||||
</PlanningProvider>
|
</PlanningProvider>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
// Affichage classique avec le tableau des enfants
|
// Affichage classique avec le tableau des enfants
|
||||||
<div>
|
<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
|
<SectionHeader
|
||||||
icon={Users}
|
icon={Users}
|
||||||
title="Vos enfants"
|
title="Vos enfants"
|
||||||
description="Suivez le parcours de vos enfants"
|
description="Suivez le parcours de vos enfants"
|
||||||
/>
|
/>
|
||||||
<div className="overflow-x-auto">
|
<div className="overflow-x-auto">
|
||||||
<Table
|
<Table data={children} columns={childrenColumns} />
|
||||||
data={children}
|
|
||||||
columns={childrenColumns}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
{/* Composant FileUpload et bouton Valider en dessous du tableau */}
|
{/* Composant FileUpload et bouton Valider en dessous du tableau */}
|
||||||
{uploadState === 'on' && uploadingStudentId && (
|
{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