mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-01-28 23:43:22 +00:00
743 lines
28 KiB
JavaScript
743 lines
28 KiB
JavaScript
'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 (
|
|
<div className="p-6 space-y-6">
|
|
<h1 className="text-2xl font-bold">{classe?.atmosphere_name}</h1>
|
|
|
|
{/* Section Niveaux et Enseignants */}
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
{/* Section Niveaux */}
|
|
<div className="bg-white p-4 rounded-lg shadow-md">
|
|
<h2 className="text-xl font-semibold mb-4 flex items-center">
|
|
<Layers className="w-6 h-6 mr-2" />
|
|
Niveaux
|
|
</h2>
|
|
<p className="text-sm text-gray-500 mb-4">
|
|
Filtrer les élèves par niveau
|
|
</p>
|
|
<div className="flex flex-wrap gap-2">
|
|
{classe?.levels?.length > 0 ? (
|
|
getNiveauxLabels(classe.levels).map((label, index) => (
|
|
<span
|
|
key={index}
|
|
onClick={() => 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) ? (
|
|
<span className="flex items-center gap-2">
|
|
<CheckCircle className="w-4 h-4 text-emerald-600" />
|
|
{label}
|
|
</span>
|
|
) : (
|
|
label
|
|
)}
|
|
</span>
|
|
))
|
|
) : (
|
|
<span className="text-gray-500">Aucun niveau associé</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Section Enseignants */}
|
|
<div className="bg-white p-4 rounded-lg shadow-md">
|
|
<h2 className="text-xl font-semibold mb-4 flex items-center">
|
|
<Users className="w-6 h-6 mr-2" />
|
|
Enseignants
|
|
</h2>
|
|
<p className="text-sm text-gray-500 mb-4">Liste des enseignants</p>
|
|
<div className="flex flex-wrap gap-2">
|
|
{classe?.teachers_details?.map((teacher) => (
|
|
<span
|
|
key={teacher.id}
|
|
className="px-3 py-1 bg-emerald-200 rounded-full text-emerald-800"
|
|
>
|
|
{teacher.last_name} {teacher.first_name}
|
|
</span>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Affichage de la date du jour */}
|
|
<div className="flex justify-between items-center mb-4 bg-white p-4 rounded-lg shadow-md">
|
|
<div className="flex items-center space-x-3">
|
|
<div className="flex items-center justify-center w-10 h-10 bg-emerald-100 text-emerald-600 rounded-full">
|
|
<Clock className="w-6 h-6" />
|
|
</div>
|
|
<h2 className="text-lg font-semibold text-gray-800">
|
|
Appel du jour :{' '}
|
|
<span className="ml-2 text-emerald-600">{today}</span>
|
|
</h2>
|
|
</div>
|
|
<div className="flex items-center">
|
|
{!isEditingAttendance ? (
|
|
<Button
|
|
text="Faire l'appel"
|
|
onClick={handleToggleAttendanceMode}
|
|
primary
|
|
className="px-4 py-2 bg-emerald-500 text-white rounded-lg shadow hover:bg-emerald-600 transition-all"
|
|
/>
|
|
) : (
|
|
<Button
|
|
text="Valider l'appel"
|
|
onClick={handleValidateAttendance}
|
|
primary
|
|
className="px-4 py-2 bg-emerald-500 text-white rounded-lg shadow hover:bg-emerald-600 transition-all"
|
|
/>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<Table
|
|
columns={[
|
|
{
|
|
name: 'Nom',
|
|
transform: (row) => (
|
|
<div className="text-center">{row.last_name}</div>
|
|
),
|
|
},
|
|
{
|
|
name: 'Prénom',
|
|
transform: (row) => (
|
|
<div className="text-center">{row.first_name}</div>
|
|
),
|
|
},
|
|
{
|
|
name: 'Niveau',
|
|
transform: (row) => (
|
|
<div className="text-center">{getNiveauLabel(row.level)}</div>
|
|
),
|
|
},
|
|
...(isEditingAttendance
|
|
? [
|
|
{
|
|
name: "Gestion de l'appel",
|
|
transform: (row) => (
|
|
<div className="flex flex-col gap-2 items-center">
|
|
{/* Présence */}
|
|
<div className="flex items-center gap-2">
|
|
{attendance[row.id] ? (
|
|
<>
|
|
<CheckBox
|
|
item={{ id: row.id }}
|
|
formData={{
|
|
attendance: attendance[row.id] ? [row.id] : [],
|
|
}}
|
|
handleChange={() =>
|
|
handleAttendanceChange(row.id)
|
|
}
|
|
fieldName="attendance"
|
|
/>
|
|
<span className="text-sm font-medium text-gray-700">
|
|
Présent
|
|
</span>
|
|
</>
|
|
) : (
|
|
<>
|
|
{/* Icône croix pour remettre l'élève en présent */}
|
|
<button
|
|
type="button"
|
|
onClick={() => handleAttendanceChange(row.id)}
|
|
className="text-red-500 hover:text-red-700 transition"
|
|
title="Annuler l'absence"
|
|
>
|
|
<XCircle className="w-6 h-6" />
|
|
</button>
|
|
<span className="text-sm font-medium text-red-600">
|
|
Effacer l'absence
|
|
</span>
|
|
</>
|
|
)}
|
|
</div>
|
|
|
|
{/* Détails absence/retard */}
|
|
{!attendance[row.id] && (
|
|
<div className="w-full bg-emerald-50 border border-emerald-100 rounded-lg p-3 mt-2 shadow-sm">
|
|
<div className="flex items-center gap-2 mb-2">
|
|
<Clock className="w-4 h-4 text-emerald-500" />
|
|
<span className="font-semibold text-emerald-700 text-sm">
|
|
Motif d'absence
|
|
</span>
|
|
</div>
|
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-2 items-center">
|
|
{/* Select Absence/Retard */}
|
|
<SelectChoice
|
|
name={`type-${row.id}`}
|
|
label=""
|
|
placeHolder="Type"
|
|
selected={formAbsences[row.id]?.type || ''}
|
|
callback={(e) =>
|
|
setFormAbsences((prev) => ({
|
|
...prev,
|
|
[row.id]: {
|
|
...prev[row.id],
|
|
type: e.target.value,
|
|
},
|
|
}))
|
|
}
|
|
choices={[
|
|
{ value: 'absence', label: 'Absence' },
|
|
{ value: 'retard', label: 'Retard' },
|
|
]}
|
|
/>
|
|
|
|
{/* Select Moment */}
|
|
<SelectChoice
|
|
name={`moment-${row.id}`}
|
|
label=""
|
|
placeHolder="Durée"
|
|
selected={formAbsences[row.id]?.moment || ''}
|
|
callback={(e) =>
|
|
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 */}
|
|
<input
|
|
type="text"
|
|
className="border rounded px-2 py-1 text-sm w-full"
|
|
placeholder="Commentaire"
|
|
value={formAbsences[row.id]?.commentaire || ''}
|
|
onChange={(e) =>
|
|
setFormAbsences((prev) => ({
|
|
...prev,
|
|
[row.id]: {
|
|
...prev[row.id],
|
|
commentaire: e.target.value,
|
|
},
|
|
}))
|
|
}
|
|
/>
|
|
|
|
{/* Checkbox Justifié */}
|
|
<div className="flex items-center gap-2 justify-center">
|
|
<CheckBox
|
|
item={{ id: `justified-${row.id}` }}
|
|
formData={{
|
|
justified: !!formAbsences[row.id]?.justified,
|
|
}}
|
|
handleChange={() =>
|
|
setFormAbsences((prev) => ({
|
|
...prev,
|
|
[row.id]: {
|
|
...prev[row.id],
|
|
justified: !prev[row.id]?.justified,
|
|
},
|
|
}))
|
|
}
|
|
fieldName="justified"
|
|
itemLabelFunc={() => 'Justifié'}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
),
|
|
},
|
|
]
|
|
: [
|
|
{
|
|
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 (
|
|
<div className="text-center text-green-500 flex justify-center items-center gap-2">
|
|
<CheckCircle className="w-5 h-5" />
|
|
Présent
|
|
</div>
|
|
);
|
|
}
|
|
|
|
switch (absence.reason) {
|
|
case AbsenceReason.JUSTIFIED_LATE.value:
|
|
return (
|
|
<div className="text-center text-yellow-500 flex justify-center items-center gap-2">
|
|
<Clock className="w-5 h-5" />
|
|
Retard justifié
|
|
</div>
|
|
);
|
|
case AbsenceReason.UNJUSTIFIED_LATE.value:
|
|
return (
|
|
<div className="text-center text-red-500 flex justify-center items-center gap-2">
|
|
<Clock className="w-5 h-5" />
|
|
Retard non justifié
|
|
</div>
|
|
);
|
|
case AbsenceReason.JUSTIFIED_ABSENCE.value:
|
|
return (
|
|
<div className="text-center text-blue-500 flex justify-center items-center gap-2">
|
|
<CheckCircle className="w-5 h-5" />
|
|
Absence justifiée
|
|
</div>
|
|
);
|
|
case AbsenceReason.UNJUSTIFIED_ABSENCE.value:
|
|
return (
|
|
<div className="text-center text-red-500 flex justify-center items-center gap-2">
|
|
<CheckCircle className="w-5 h-5" />
|
|
Absence non justifiée
|
|
</div>
|
|
);
|
|
default:
|
|
return (
|
|
<div className="text-center text-gray-500 flex justify-center items-center gap-2">
|
|
<CheckCircle className="w-5 h-5" />
|
|
Statut inconnu
|
|
</div>
|
|
);
|
|
}
|
|
},
|
|
},
|
|
]),
|
|
]}
|
|
data={filteredStudents} // Utiliser les élèves filtrés
|
|
/>
|
|
|
|
{/* Popup */}
|
|
<Popup
|
|
isOpen={popupVisible}
|
|
message={popupMessage}
|
|
onConfirm={() => setPopupVisible(false)}
|
|
onCancel={() => setPopupVisible(false)}
|
|
uniqueConfirmButton={true}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|