'use client'; import React, { useState, useEffect } from 'react'; import { Users, Layers, CheckCircle, Clock, XCircle } from 'lucide-react'; import Table from '@/components/Table'; import Popup from '@/components/Popup'; import { fetchClasse } from '@/app/actions/schoolAction'; import { useSearchParams } from 'next/navigation'; import logger from '@/utils/logger'; import { useClasses } from '@/context/ClassesContext'; import Button from '@/components/Button'; import SelectChoice from '@/components/SelectChoice'; import CheckBox from '@/components/CheckBox'; import { fetchAbsences, createAbsences, editAbsences, deleteAbsences, } from '@/app/actions/subscriptionAction'; import { useCsrfToken } from '@/context/CsrfContext'; import { useEstablishment } from '@/context/EstablishmentContext'; import { useNotification } from '@/context/NotificationContext'; export default function Page() { const searchParams = useSearchParams(); const { showNotification } = useNotification(); const schoolClassId = searchParams.get('schoolClassId'); const [classe, setClasse] = useState([]); const { getNiveauxLabels, getNiveauLabel } = useClasses(); const [popupVisible, setPopupVisible] = useState(false); const [popupMessage, setPopupMessage] = useState(''); const [selectedLevels, setSelectedLevels] = useState([]); // Par défaut, tous les niveaux sont sélectionnés const [filteredStudents, setFilteredStudents] = useState([]); const [isEditingAttendance, setIsEditingAttendance] = useState(false); // État pour le mode édition const [attendance, setAttendance] = useState({}); // État pour les cases cochées const [fetchedAbsences, setFetchedAbsences] = useState({}); // Absences récupérées depuis le backend const [formAbsences, setFormAbsences] = useState({}); // Absences modifiées localement const csrfToken = useCsrfToken(); const { selectedEstablishmentId } = useEstablishment(); // AbsenceMoment constants const AbsenceMoment = { MORNING: { value: 1, label: 'Matinée' }, AFTERNOON: { value: 2, label: 'Après-midi' }, TOTAL: { value: 3, label: 'Journée' }, }; // AbsenceReason constants const AbsenceReason = { JUSTIFIED_ABSENCE: { value: 1, label: 'Absence justifiée' }, UNJUSTIFIED_ABSENCE: { value: 2, label: 'Absence non justifiée' }, JUSTIFIED_LATE: { value: 3, label: 'Retard justifié' }, UNJUSTIFIED_LATE: { value: 4, label: 'Retard non justifié' }, }; useEffect(() => { // Récupérer les données de la classe et initialiser les élèves filtrés if (schoolClassId) { fetchClasse(schoolClassId) .then((classeData) => { logger.debug('Classes récupérées :', classeData); setClasse(classeData); setFilteredStudents(classeData.students); // Initialiser les élèves filtrés setSelectedLevels(getNiveauxLabels(classeData.levels)); // Initialiser les niveaux sélectionnés }) .catch(requestErrorHandler); } }, [schoolClassId]); useEffect(() => { // Récupérer les absences pour l'établissement sélectionné if (selectedEstablishmentId) { fetchAbsences(selectedEstablishmentId) .then((data) => { const absencesById = data.reduce((acc, absence) => { acc[absence.student] = absence; return acc; }, {}); setFetchedAbsences(absencesById); }) .catch((error) => logger.error('Erreur lors de la récupération des absences :', error) ); } }, [selectedEstablishmentId]); useEffect(() => { // Filtrer les élèves en fonction des niveaux sélectionnés if (classe && selectedLevels.length > 0) { const filtered = classe.students.filter((student) => selectedLevels.includes(getNiveauLabel(student.level)) ); setFilteredStudents(filtered); } else { setFilteredStudents([]); // Aucun élève si aucun niveau n'est sélectionné } }, [selectedLevels, classe]); useEffect(() => { // Initialiser `attendance` et `formAbsences` en fonction des élèves filtrés et des absences if (filteredStudents.length > 0 && fetchedAbsences) { const today = new Date().toISOString().split('T')[0]; const initialAttendance = {}; const initialFormAbsences = {}; filteredStudents.forEach((student) => { const existingAbsence = fetchedAbsences[student.id] && fetchedAbsences[student.id].day === today ? fetchedAbsences[student.id] : null; if (existingAbsence) { // Si une absence existe pour aujourd'hui, décocher la case et pré-remplir les champs // Conversion reason -> type/justified let type = ''; let justified = false; switch (existingAbsence.reason) { case AbsenceReason.JUSTIFIED_ABSENCE.value: type = 'absence'; justified = true; break; case AbsenceReason.UNJUSTIFIED_ABSENCE.value: type = 'absence'; justified = false; break; case AbsenceReason.JUSTIFIED_LATE.value: type = 'retard'; justified = true; break; case AbsenceReason.UNJUSTIFIED_LATE.value: type = 'retard'; justified = false; break; default: type = ''; justified = false; } initialAttendance[student.id] = false; initialFormAbsences[student.id] = { ...existingAbsence, type, justified, }; } else { // Sinon, cocher la case par défaut initialAttendance[student.id] = true; } }); setAttendance(initialAttendance); setFormAbsences(initialFormAbsences); } }, [filteredStudents, fetchedAbsences]); const handleLevelClick = (label) => { setSelectedLevels( (prev) => prev.includes(label) ? prev.filter((level) => level !== label) // Retirer le niveau si déjà sélectionné : [...prev, label] // Ajouter le niveau si non sélectionné ); }; const handleToggleAttendanceMode = () => { setIsEditingAttendance((prev) => !prev); // Basculer entre mode édition et visualisation }; const handleValidateAttendance = () => { let hasError = false; // Pour chaque élève filtré (présents et absents) filteredStudents.forEach((student) => { const studentId = student.id; const isPresent = attendance[studentId]; const existingAbsence = fetchedAbsences[studentId]; if (isPresent) { // Si l'élève est présent et qu'une absence existe, la supprimer if (existingAbsence) { deleteAbsences(existingAbsence.id, csrfToken) .then(() => { logger.debug( `Absence pour l'élève ${studentId} supprimée (présent).` ); setFetchedAbsences((prev) => { const updatedAbsences = { ...prev }; delete updatedAbsences[studentId]; return updatedAbsences; }); setFormAbsences((prev) => { const updatedAbsences = { ...prev }; delete updatedAbsences[studentId]; return updatedAbsences; }); }) .catch((error) => { logger.error( `Erreur lors de la suppression de l'absence pour l'élève ${studentId}:`, error ); showNotification( `Erreur lors de la suppression de l'absence pour l'élève ${studentId}`, 'error', 'Erreur' ); }); } // Si tu veux garder une trace de la présence, tu peux ici appeler une API ou enregistrer un "Présent" } else { // Si l'élève est absent, créer ou modifier l'absence const absenceData = formAbsences[studentId]; if (!absenceData || !absenceData.type || !absenceData.moment) { logger.error( `Tous les champs requis doivent être fournis pour l'élève ${studentId}.` ); showNotification( `Tous les champs requis doivent être fournis pour l'élève ${studentId}.`, 'error', 'Erreur' ); hasError = true; // On ne fait pas de return ici, on continue la boucle pour les autres élèves } else { saveAbsence(studentId, absenceData); } } }); // On ne quitte le mode édition que s'il n'y a pas d'erreur if (!hasError) { setIsEditingAttendance(false); } }; const handleAttendanceChange = (studentId) => { const today = new Date().toISOString().split('T')[0]; // Obtenir la date actuelle au format YYYY-MM-DD setAttendance((prev) => { const updatedAttendance = { ...prev, [studentId]: !prev[studentId], // Inverser l'état de présence }; // Si l'élève est décoché (absent) if (!updatedAttendance[studentId]) { // Vérifier s'il existe une absence pour le jour actuel const existingAbsence = Object.values(fetchedAbsences).find( (absence) => absence.student === studentId && absence.day === today ); if (existingAbsence) { // Afficher l'absence existante pour le jour actuel setFormAbsences((prev) => ({ ...prev, [studentId]: { ...existingAbsence, }, })); } else { // Initialiser des champs vides pour créer une nouvelle absence setFormAbsences((prev) => ({ ...prev, [studentId]: { day: today, reason: null, moment: null, }, })); } } else { // Si l'élève est recoché (présent), supprimer l'absence existante const existingAbsence = Object.values(fetchedAbsences).find( (absence) => absence.student === studentId && absence.day === today ); if (existingAbsence) { // Appeler la fonction pour supprimer l'absence deleteAbsences(existingAbsence.id, csrfToken) .then(() => { logger.debug( `Absence pour l'élève ${studentId} supprimée avec succès.` ); // Mettre à jour les absences récupérées setFetchedAbsences((prev) => { const updatedAbsences = { ...prev }; delete updatedAbsences[studentId]; return updatedAbsences; }); }) .catch((error) => { logger.error( `Erreur lors de la suppression de l'absence pour l'élève ${studentId}:`, error ); }); } // Supprimer les données d'absence dans `formAbsences` setFormAbsences((prev) => { const updatedAbsences = { ...prev }; delete updatedAbsences[studentId]; return updatedAbsences; }); } return updatedAttendance; }); }; const getAbsenceReason = (type, justified) => { if (type === 'absence') { return justified ? AbsenceReason.JUSTIFIED_ABSENCE.value : AbsenceReason.UNJUSTIFIED_ABSENCE.value; } else if (type === 'retard') { return justified ? AbsenceReason.JUSTIFIED_LATE.value : AbsenceReason.UNJUSTIFIED_LATE.value; } return null; }; const saveAbsence = (studentId, absenceData) => { if (!absenceData.type || !studentId || !absenceData.moment) { logger.error('Tous les champs requis doivent être fournis.'); showNotification( 'Tous les champs requis doivent être fournis.', 'error', 'Erreur' ); return; } const reason = getAbsenceReason(absenceData.type, absenceData.justified); const payload = { student: studentId, day: absenceData.day, reason: reason, moment: absenceData.moment, establishment: selectedEstablishmentId, commentaire: absenceData.commentaire, }; if (absenceData.id) { // Modifier une absence existante editAbsences(absenceData.id, payload, csrfToken) .then(() => { logger.debug( `Absence pour l'élève ${studentId} modifiée avec succès.` ); showNotification( 'Opération effectuée avec succès.', 'success', 'Succès' ); // Mettre à jour fetchedAbsences et formAbsences localement setFetchedAbsences((prev) => ({ ...prev, [studentId]: { ...prev[studentId], ...payload }, })); setFormAbsences((prev) => ({ ...prev, [studentId]: { ...prev[studentId], ...payload }, })); }) .catch((error) => { logger.error( `Erreur lors de la modification de l'absence pour l'élève ${studentId}:`, error ); }); } else { // Créer une nouvelle absence createAbsences(payload, csrfToken) .then((response) => { logger.debug(`Absence pour l'élève ${studentId} créée avec succès.`); showNotification( 'Opération effectuée avec succès.', 'success', 'Succès' ); // Mettre à jour fetchedAbsences et formAbsences localement setFetchedAbsences((prev) => ({ ...prev, [studentId]: { id: response.id, ...payload }, })); setFormAbsences((prev) => ({ ...prev, [studentId]: { id: response.id, ...payload }, })); }) .catch((error) => { logger.error( `Erreur lors de la création de l'absence pour l'élève ${studentId}:`, error ); }); } }; const requestErrorHandler = (err) => { logger.error('Error fetching data:', err); }; const today = new Date().toISOString().split('T')[0]; // Obtenez la date actuelle au format YYYY-MM-DD return (

{classe?.atmosphere_name}

{/* Section Niveaux et Enseignants */}
{/* Section Niveaux */}

Niveaux

Filtrer les élèves par niveau

{classe?.levels?.length > 0 ? ( getNiveauxLabels(classe.levels).map((label, index) => ( handleLevelClick(label)} // Gérer le clic sur un niveau className={`px-4 py-2 rounded-full cursor-pointer border transition-all duration-200 ${ selectedLevels.includes(label) ? 'bg-emerald-200 text-emerald-800 border-emerald-300 shadow-md' : 'bg-gray-200 text-gray-800 border-gray-300 hover:bg-gray-300' }`} > {selectedLevels.includes(label) ? ( {label} ) : ( label )} )) ) : ( Aucun niveau associé )}
{/* Section Enseignants */}

Enseignants

Liste des enseignants

{classe?.teachers_details?.map((teacher) => ( {teacher.last_name} {teacher.first_name} ))}
{/* Affichage de la date du jour */}

Appel du jour :{' '} {today}

{!isEditingAttendance ? (
(
{row.last_name}
), }, { name: 'Prénom', transform: (row) => (
{row.first_name}
), }, { name: 'Niveau', transform: (row) => (
{getNiveauLabel(row.level)}
), }, ...(isEditingAttendance ? [ { name: "Gestion de l'appel", transform: (row) => (
{/* Présence */}
{attendance[row.id] ? ( <> handleAttendanceChange(row.id) } fieldName="attendance" /> Présent ) : ( <> {/* Icône croix pour remettre l'élève en présent */} Effacer l'absence )}
{/* Détails absence/retard */} {!attendance[row.id] && (
Motif d'absence
{/* Select Absence/Retard */} setFormAbsences((prev) => ({ ...prev, [row.id]: { ...prev[row.id], type: e.target.value, }, })) } choices={[ { value: 'absence', label: 'Absence' }, { value: 'retard', label: 'Retard' }, ]} /> {/* Select Moment */} setFormAbsences((prev) => ({ ...prev, [row.id]: { ...prev[row.id], moment: parseInt(e.target.value, 10), }, })) } choices={Object.values(AbsenceMoment).map( (moment) => ({ value: moment.value, label: moment.label, }) )} /> {/* Nouveau champ commentaire */} setFormAbsences((prev) => ({ ...prev, [row.id]: { ...prev[row.id], commentaire: e.target.value, }, })) } /> {/* Checkbox Justifié */}
setFormAbsences((prev) => ({ ...prev, [row.id]: { ...prev[row.id], justified: !prev[row.id]?.justified, }, })) } fieldName="justified" itemLabelFunc={() => 'Justifié'} />
)}
), }, ] : [ { name: 'Statut', transform: (row) => { const today = new Date().toISOString().split('T')[0]; const absence = formAbsences[row.id] || Object.values(fetchedAbsences).find( (absence) => absence.student === row.id && absence.day === today ); if (!absence) { return (
Présent
); } switch (absence.reason) { case AbsenceReason.JUSTIFIED_LATE.value: return (
Retard justifié
); case AbsenceReason.UNJUSTIFIED_LATE.value: return (
Retard non justifié
); case AbsenceReason.JUSTIFIED_ABSENCE.value: return (
Absence justifiée
); case AbsenceReason.UNJUSTIFIED_ABSENCE.value: return (
Absence non justifiée
); default: return (
Statut inconnu
); } }, }, ]), ]} data={filteredStudents} // Utiliser les élèves filtrés /> {/* Popup */} setPopupVisible(false)} onCancel={() => setPopupVisible(false)} uniqueConfirmButton={true} /> ); }