mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-01-28 23:43:22 +00:00
chore: Amélioration du rendu de l'appel
This commit is contained in:
@ -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;
|
||||||
saveAbsence(studentId, absenceData);
|
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) => {
|
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,62 +529,117 @@ 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 */}
|
||||||
<CheckBox
|
<div className="flex items-center gap-2">
|
||||||
item={{ id: row.id }}
|
{attendance[row.id] ? (
|
||||||
formData={{
|
<>
|
||||||
attendance: attendance[row.id] ? [row.id] : [],
|
<CheckBox
|
||||||
}}
|
item={{ id: row.id }}
|
||||||
handleChange={() => handleAttendanceChange(row.id)}
|
formData={{
|
||||||
fieldName="attendance"
|
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] && (
|
{!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">
|
||||||
<SelectChoice
|
<div className="flex items-center gap-2 mb-2">
|
||||||
name={`reason-${row.id}`}
|
<Clock className="w-4 h-4 text-emerald-500" />
|
||||||
label=""
|
<span className="font-semibold text-emerald-700 text-sm">
|
||||||
placeHolder="Motif"
|
Motif d'absence
|
||||||
selected={formAbsences[row.id]?.reason || ''}
|
</span>
|
||||||
callback={(e) =>
|
</div>
|
||||||
setFormAbsences((prev) => ({
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-2 items-center">
|
||||||
...prev,
|
{/* Select Absence/Retard */}
|
||||||
[row.id]: {
|
<SelectChoice
|
||||||
...prev[row.id],
|
name={`type-${row.id}`}
|
||||||
reason: parseInt(e.target.value, 10),
|
label=""
|
||||||
},
|
placeHolder="Type"
|
||||||
}))
|
selected={formAbsences[row.id]?.type || ''}
|
||||||
}
|
callback={(e) =>
|
||||||
choices={Object.values(AbsenceReason).map(
|
setFormAbsences((prev) => ({
|
||||||
(reason) => ({
|
...prev,
|
||||||
value: reason.value,
|
[row.id]: {
|
||||||
label: reason.label,
|
...prev[row.id],
|
||||||
})
|
type: e.target.value,
|
||||||
)}
|
},
|
||||||
/>
|
}))
|
||||||
|
}
|
||||||
|
choices={[
|
||||||
|
{ value: 'absence', label: 'Absence' },
|
||||||
|
{ value: 'retard', label: 'Retard' },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
|
||||||
<SelectChoice
|
{/* Select Moment */}
|
||||||
name={`moment-${row.id}`}
|
<SelectChoice
|
||||||
label=""
|
name={`moment-${row.id}`}
|
||||||
placeHolder="Durée"
|
label=""
|
||||||
selected={formAbsences[row.id]?.moment || ''}
|
placeHolder="Durée"
|
||||||
callback={(e) =>
|
selected={formAbsences[row.id]?.moment || ''}
|
||||||
setFormAbsences((prev) => ({
|
callback={(e) =>
|
||||||
...prev,
|
setFormAbsences((prev) => ({
|
||||||
[row.id]: {
|
...prev,
|
||||||
...prev[row.id],
|
[row.id]: {
|
||||||
moment: parseInt(e.target.value, 10),
|
...prev[row.id],
|
||||||
},
|
moment: parseInt(e.target.value, 10),
|
||||||
}))
|
},
|
||||||
}
|
}))
|
||||||
choices={Object.values(AbsenceMoment).map(
|
}
|
||||||
(moment) => ({
|
choices={Object.values(AbsenceMoment).map(
|
||||||
value: moment.value,
|
(moment) => ({
|
||||||
label: moment.label,
|
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>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user