mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-01-29 07:53:23 +00:00
chore: Amélioration du rendu de l'appel
This commit is contained in:
@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
|
||||
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 Popup from '@/components/Popup';
|
||||
import { fetchClasse } from '@/app/actions/schoolAction';
|
||||
@ -20,9 +20,11 @@ import {
|
||||
|
||||
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();
|
||||
@ -114,8 +116,37 @@ export default function Page() {
|
||||
|
||||
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 };
|
||||
initialFormAbsences[student.id] = {
|
||||
...existingAbsence,
|
||||
type,
|
||||
justified,
|
||||
};
|
||||
} else {
|
||||
// Sinon, cocher la case par défaut
|
||||
initialAttendance[student.id] = true;
|
||||
@ -141,21 +172,70 @@ export default function Page() {
|
||||
};
|
||||
|
||||
const handleValidateAttendance = () => {
|
||||
// Filtrer les absences modifiées uniquement pour les étudiants décochés (absents)
|
||||
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
|
||||
);
|
||||
let hasError = false;
|
||||
|
||||
// Envoyer les absences modifiées à une API
|
||||
absencesToUpdate.forEach(([studentId, absenceData]) => {
|
||||
logger.debug('Modification absence élève : ', studentId);
|
||||
saveAbsence(studentId, absenceData);
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
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) => {
|
||||
@ -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) => {
|
||||
if (!absenceData.reason || !studentId || !absenceData.moment) {
|
||||
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: absenceData.reason,
|
||||
reason: reason,
|
||||
moment: absenceData.moment,
|
||||
establishment: selectedEstablishmentId,
|
||||
};
|
||||
@ -254,6 +354,11 @@ export default function Page() {
|
||||
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,
|
||||
@ -275,6 +380,11 @@ export default function Page() {
|
||||
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,
|
||||
@ -419,62 +529,117 @@ export default function Page() {
|
||||
name: "Gestion de l'appel",
|
||||
transform: (row) => (
|
||||
<div className="flex flex-col gap-2 items-center">
|
||||
{/* Case à cocher pour la présence */}
|
||||
<CheckBox
|
||||
item={{ id: row.id }}
|
||||
formData={{
|
||||
attendance: attendance[row.id] ? [row.id] : [],
|
||||
}}
|
||||
handleChange={() => handleAttendanceChange(row.id)}
|
||||
fieldName="attendance"
|
||||
/>
|
||||
{/* 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>
|
||||
|
||||
{/* Champs pour le motif et le moment */}
|
||||
{/* Détails absence/retard */}
|
||||
{!attendance[row.id] && (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-2 mt-2">
|
||||
<SelectChoice
|
||||
name={`reason-${row.id}`}
|
||||
label=""
|
||||
placeHolder="Motif"
|
||||
selected={formAbsences[row.id]?.reason || ''}
|
||||
callback={(e) =>
|
||||
setFormAbsences((prev) => ({
|
||||
...prev,
|
||||
[row.id]: {
|
||||
...prev[row.id],
|
||||
reason: parseInt(e.target.value, 10),
|
||||
},
|
||||
}))
|
||||
}
|
||||
choices={Object.values(AbsenceReason).map(
|
||||
(reason) => ({
|
||||
value: reason.value,
|
||||
label: reason.label,
|
||||
})
|
||||
)}
|
||||
/>
|
||||
<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-3 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' },
|
||||
]}
|
||||
/>
|
||||
|
||||
<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,
|
||||
})
|
||||
)}
|
||||
/>
|
||||
{/* 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,
|
||||
})
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* 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>
|
||||
|
||||
Reference in New Issue
Block a user