chore: Amélioration du rendu de l'appel

This commit is contained in:
N3WT DE COMPET
2025-05-25 18:32:46 +02:00
parent 3e5cebef44
commit fd6348fd6b

View File

@ -1,7 +1,7 @@
'use client'; 'use client';
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { Users, Layers, CheckCircle, Clock } from 'lucide-react'; import { Users, Layers, CheckCircle, Clock, XCircle } from 'lucide-react';
import Table from '@/components/Table'; import Table from '@/components/Table';
import Popup from '@/components/Popup'; import Popup from '@/components/Popup';
import { fetchClasse } from '@/app/actions/schoolAction'; import { fetchClasse } from '@/app/actions/schoolAction';
@ -20,9 +20,11 @@ import {
import { useCsrfToken } from '@/context/CsrfContext'; import { useCsrfToken } from '@/context/CsrfContext';
import { useEstablishment } from '@/context/EstablishmentContext'; import { useEstablishment } from '@/context/EstablishmentContext';
import { useNotification } from '@/context/NotificationContext';
export default function Page() { export default function Page() {
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const { showNotification } = useNotification();
const schoolClassId = searchParams.get('schoolClassId'); const schoolClassId = searchParams.get('schoolClassId');
const [classe, setClasse] = useState([]); const [classe, setClasse] = useState([]);
const { getNiveauxLabels, getNiveauLabel } = useClasses(); const { getNiveauxLabels, getNiveauLabel } = useClasses();
@ -114,8 +116,37 @@ export default function Page() {
if (existingAbsence) { if (existingAbsence) {
// Si une absence existe pour aujourd'hui, décocher la case et pré-remplir les champs // 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; initialAttendance[student.id] = false;
initialFormAbsences[student.id] = { ...existingAbsence }; initialFormAbsences[student.id] = {
...existingAbsence,
type,
justified,
};
} else { } else {
// Sinon, cocher la case par défaut // Sinon, cocher la case par défaut
initialAttendance[student.id] = true; initialAttendance[student.id] = true;
@ -141,21 +172,70 @@ export default function Page() {
}; };
const handleValidateAttendance = () => { const handleValidateAttendance = () => {
// Filtrer les absences modifiées uniquement pour les étudiants décochés (absents) let hasError = false;
const absencesToUpdate = Object.entries(formAbsences).filter(
([studentId, absenceData]) =>
!attendance[studentId] && // L'étudiant est décoché (absent)
JSON.stringify(absenceData) !==
JSON.stringify(fetchedAbsences[studentId]) // Les données ont été modifiées
);
// Envoyer les absences modifiées à une API // Pour chaque élève filtré (présents et absents)
absencesToUpdate.forEach(([studentId, absenceData]) => { filteredStudents.forEach((student) => {
logger.debug('Modification absence élève : ', studentId); 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); saveAbsence(studentId, absenceData);
}
}
}); });
setIsEditingAttendance(false); // Revenir au mode visualisation // On ne quitte le mode édition que s'il n'y a pas d'erreur
if (!hasError) {
setIsEditingAttendance(false);
}
}; };
const handleAttendanceChange = (studentId) => { const handleAttendanceChange = (studentId) => {
@ -233,16 +313,36 @@ export default function Page() {
}); });
}; };
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) => { const saveAbsence = (studentId, absenceData) => {
if (!absenceData.reason || !studentId || !absenceData.moment) { if (!absenceData.type || !studentId || !absenceData.moment) {
logger.error('Tous les champs requis doivent être fournis.'); logger.error('Tous les champs requis doivent être fournis.');
showNotification(
'Tous les champs requis doivent être fournis.',
'error',
'Erreur'
);
return; return;
} }
const reason = getAbsenceReason(absenceData.type, absenceData.justified);
const payload = { const payload = {
student: studentId, student: studentId,
day: absenceData.day, day: absenceData.day,
reason: absenceData.reason, reason: reason,
moment: absenceData.moment, moment: absenceData.moment,
establishment: selectedEstablishmentId, establishment: selectedEstablishmentId,
}; };
@ -254,6 +354,11 @@ export default function Page() {
logger.debug( logger.debug(
`Absence pour l'élève ${studentId} modifiée avec succès.` `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 // Mettre à jour fetchedAbsences et formAbsences localement
setFetchedAbsences((prev) => ({ setFetchedAbsences((prev) => ({
...prev, ...prev,
@ -275,6 +380,11 @@ export default function Page() {
createAbsences(payload, csrfToken) createAbsences(payload, csrfToken)
.then((response) => { .then((response) => {
logger.debug(`Absence pour l'élève ${studentId} créée avec succès.`); 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 // Mettre à jour fetchedAbsences et formAbsences localement
setFetchedAbsences((prev) => ({ setFetchedAbsences((prev) => ({
...prev, ...prev,
@ -419,41 +529,74 @@ export default function Page() {
name: "Gestion de l'appel", name: "Gestion de l'appel",
transform: (row) => ( transform: (row) => (
<div className="flex flex-col gap-2 items-center"> <div className="flex flex-col gap-2 items-center">
{/* Case à cocher pour la présence */} {/* Présence */}
<div className="flex items-center gap-2">
{attendance[row.id] ? (
<>
<CheckBox <CheckBox
item={{ id: row.id }} item={{ id: row.id }}
formData={{ formData={{
attendance: attendance[row.id] ? [row.id] : [], attendance: attendance[row.id] ? [row.id] : [],
}} }}
handleChange={() => handleAttendanceChange(row.id)} handleChange={() =>
handleAttendanceChange(row.id)
}
fieldName="attendance" 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&apos;absence
</span>
</>
)}
</div>
{/* Champs pour le motif et le moment */} {/* Détails absence/retard */}
{!attendance[row.id] && ( {!attendance[row.id] && (
<div className="grid grid-cols-1 md:grid-cols-2 gap-2 mt-2"> <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&apos;absence
</span>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-2 items-center">
{/* Select Absence/Retard */}
<SelectChoice <SelectChoice
name={`reason-${row.id}`} name={`type-${row.id}`}
label="" label=""
placeHolder="Motif" placeHolder="Type"
selected={formAbsences[row.id]?.reason || ''} selected={formAbsences[row.id]?.type || ''}
callback={(e) => callback={(e) =>
setFormAbsences((prev) => ({ setFormAbsences((prev) => ({
...prev, ...prev,
[row.id]: { [row.id]: {
...prev[row.id], ...prev[row.id],
reason: parseInt(e.target.value, 10), type: e.target.value,
}, },
})) }))
} }
choices={Object.values(AbsenceReason).map( choices={[
(reason) => ({ { value: 'absence', label: 'Absence' },
value: reason.value, { value: 'retard', label: 'Retard' },
label: reason.label, ]}
})
)}
/> />
{/* Select Moment */}
<SelectChoice <SelectChoice
name={`moment-${row.id}`} name={`moment-${row.id}`}
label="" label=""
@ -475,6 +618,28 @@ export default function Page() {
}) })
)} )}
/> />
{/* 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>
)} )}
</div> </div>