From 980f169c1d1a46f0d47f4b9ff65fa940ac610023 Mon Sep 17 00:00:00 2001 From: N3WT DE COMPET Date: Tue, 6 May 2025 22:57:52 +0200 Subject: [PATCH] fix: pagination annuaire --- Back-End/Subscriptions/serializers.py | 2 +- .../src/app/[locale]/admin/directory/page.js | 530 +++++++++++++++++- .../structure/SchoolClassManagement/page.js | 2 - Front-End/src/app/[locale]/parents/page.js | 35 ++ Front-End/src/app/actions/authAction.js | 2 +- .../Inscription/ResponsableInputFields.js | 2 +- Front-End/src/components/Popup.js | 23 +- Front-End/src/components/ProfileDirectory.js | 523 ----------------- Front-End/src/components/StatusLabel.js | 2 +- .../Structure/Configuration/ClassesSection.js | 7 +- .../Structure/Configuration/TeacherItem.js | 21 +- 11 files changed, 581 insertions(+), 568 deletions(-) delete mode 100644 Front-End/src/components/ProfileDirectory.js diff --git a/Back-End/Subscriptions/serializers.py b/Back-End/Subscriptions/serializers.py index a0f6134..88e32b2 100644 --- a/Back-End/Subscriptions/serializers.py +++ b/Back-End/Subscriptions/serializers.py @@ -363,7 +363,7 @@ class StudentByParentSerializer(serializers.ModelSerializer): class Meta: model = Student - fields = ['id', 'last_name', 'first_name'] + fields = ['id', 'last_name', 'first_name', 'level', 'photo'] def __init__(self, *args, **kwargs): super(StudentByParentSerializer, self).__init__(*args, **kwargs) diff --git a/Front-End/src/app/[locale]/admin/directory/page.js b/Front-End/src/app/[locale]/admin/directory/page.js index ae6947d..49bac3e 100644 --- a/Front-End/src/app/[locale]/admin/directory/page.js +++ b/Front-End/src/app/[locale]/admin/directory/page.js @@ -1,16 +1,74 @@ 'use client'; import React, { useState, useEffect } from 'react'; -import { fetchProfileRoles } from '@/app/actions/authAction'; -import logger from '@/utils/logger'; import { useEstablishment } from '@/context/EstablishmentContext'; -import ProfileDirectory from '@/components/ProfileDirectory'; import { PARENT_FILTER, SCHOOL_FILTER } from '@/utils/constants'; +import { Trash2, ToggleLeft, ToggleRight, Info, XCircle } from 'lucide-react'; +import Table from '@/components/Table'; +import Popup from '@/components/Popup'; +import StatusLabel from '@/components/StatusLabel'; +import SpecialityItem from '@/components/Structure/Configuration/SpecialityItem'; +import SidebarTabs from '@/components/SidebarTabs'; +import { + fetchProfileRoles, + updateProfileRoles, + deleteProfileRoles, +} from '@/app/actions/authAction'; +import { dissociateGuardian } from '@/app/actions/subscriptionAction'; +import { useCsrfToken } from '@/context/CsrfContext'; +import DjangoCSRFToken from '@/components/DjangoCSRFToken'; +import logger from '@/utils/logger'; + +const roleTypeToLabel = (roleType) => { + switch (roleType) { + case 0: + return 'ECOLE'; + case 1: + return 'ADMIN'; + case 2: + return 'PARENT'; + default: + return 'UNKNOWN'; + } +}; + +const roleTypeToBadgeClass = (roleType) => { + switch (roleType) { + case 0: + return 'bg-blue-100 text-blue-600'; + case 1: + return 'bg-red-100 text-red-600'; + case 2: + return 'bg-green-100 text-green-600'; + default: + return 'bg-gray-100 text-gray-600'; + } +}; export default function Page() { const [profileRolesDatasParent, setProfileRolesDatasParent] = useState([]); const [profileRolesDatasSchool, setProfileRolesDatasSchool] = useState([]); const [reloadFetch, setReloadFetch] = useState(false); + const [popupVisible, setPopupVisible] = useState(false); + const [popupMessage, setPopupMessage] = useState(''); + const [confirmPopupVisible, setConfirmPopupVisible] = useState(false); + const [confirmPopupMessage, setConfirmPopupMessage] = useState(''); + const [confirmPopupOnConfirm, setConfirmPopupOnConfirm] = useState(() => {}); + const [visibleTooltipId, setVisibleTooltipId] = useState(null); + const [activeTab, setActiveTab] = useState('parent'); // Onglet actif + const [totalProfilesParentPages, setTotalProfilesParentPages] = useState(1); + const [totalProfilesSchoolPages, setTotalProfilesSchoolPages] = useState(1); + const [currentProfilesParentPage, setCurrentProfilesParentPage] = useState(1); + + const [totalProfilesParent, setTotalProfilesParent] = useState(0); + const [totalProfilesSchool, setTotalProfilesSchool] = useState(0); + const [currentProfilesSchoolPage, setCurrentProfilesSchoolPage] = useState(1); + const [profileRolesParent, setProfileRolesParent] = useState([]); + const [profileRolesSchool, setProfileRolesSchool] = useState([]); + const itemsPerPage = 15; // Nombre d'éléments par page + + const csrfToken = useCsrfToken(); + const { selectedEstablishmentId } = useEstablishment(); const requestErrorHandler = (err) => { @@ -22,16 +80,31 @@ export default function Page() { // Fetch data for profileRolesParent handleProfiles(); } - }, [selectedEstablishmentId, reloadFetch]); + }, [ + selectedEstablishmentId, + reloadFetch, + currentProfilesParentPage, + currentProfilesSchoolPage, + ]); const handleProfiles = () => { - fetchProfileRoles(selectedEstablishmentId, PARENT_FILTER) + fetchProfileRoles( + selectedEstablishmentId, + PARENT_FILTER, + currentProfilesParentPage, + itemsPerPage + ) .then((data) => { setProfileRolesDatasParent(data); }) .catch(requestErrorHandler); - fetchProfileRoles(selectedEstablishmentId, SCHOOL_FILTER) + fetchProfileRoles( + selectedEstablishmentId, + SCHOOL_FILTER, + currentProfilesSchoolPage, + itemsPerPage + ) .then((data) => { setProfileRolesDatasSchool(data); }) @@ -39,12 +112,447 @@ export default function Page() { setReloadFetch(false); }; + const handleEdit = (profileRole) => { + const updatedData = { ...profileRole, is_active: !profileRole.is_active }; + return updateProfileRoles(profileRole.id, updatedData, csrfToken) + .then((data) => { + setProfileRolesParent((prevState) => + prevState.map((item) => (item.id === profileRole.id ? data : item)) + ); + return data; + }) + .catch((error) => { + logger.error('Error editing data:', error); + throw error; + }); + }; + + const handleDelete = (id) => { + return deleteProfileRoles(id, csrfToken) + .then(() => { + setProfileRolesParent((prevState) => + prevState.filter((item) => item.id !== id) + ); + logger.debug('Profile deleted successfully:', id); + }) + .catch((error) => { + logger.error('Error deleting profile:', error); + throw error; + }); + }; + + const handleDissociate = (studentId, guardianId) => { + return dissociateGuardian(studentId, guardianId) + .then((response) => { + logger.debug('Guardian dissociated successfully:', guardianId); + + // Vérifier si le Guardian a été supprimé + const isGuardianDeleted = response?.isGuardianDeleted; + + // Mettre à jour le modèle profileRolesParent + setProfileRolesParent( + (prevState) => + prevState + .map((profileRole) => { + if (profileRole.associated_person?.id === guardianId) { + if (isGuardianDeleted) { + // Si le Guardian est supprimé, retirer le profileRole + return null; + } else { + // Si le Guardian n'est pas supprimé, mettre à jour les élèves associés + const updatedStudents = + profileRole.associated_person.students.filter( + (student) => student.id !== studentId + ); + return { + ...profileRole, + associated_person: { + ...profileRole.associated_person, + students: updatedStudents, // Mettre à jour les élèves associés + }, + }; + } + } + return profileRole; // Conserver les autres profileRolesParent + }) + .filter(Boolean) // Supprimer les entrées nulles + ); + }) + .catch((error) => { + logger.error('Error dissociating guardian:', error); + throw error; + }); + }; + + const profilesRoleParentDataHandler = (data) => { + if (data) { + const { profilesRoles, count, page_size } = data; + if (profilesRoles) { + setProfileRolesParent(profilesRoles); + } + const calculatedTotalPages = + count === 0 ? 1 : Math.ceil(count / page_size); + setTotalProfilesParent(count); + setTotalProfilesParentPages(calculatedTotalPages); + } + }; + + const profilesRoleSchoolDataHandler = (data) => { + if (data) { + const { profilesRoles, count, page_size } = data; + if (profilesRoles) { + setProfileRolesSchool(profilesRoles); + } + const calculatedTotalPages = + count === 0 ? 1 : Math.ceil(count / page_size); + setTotalProfilesSchool(count); + setTotalProfilesSchoolPages(calculatedTotalPages); + } + }; + + useEffect(() => { + profilesRoleParentDataHandler(profileRolesDatasParent); + profilesRoleSchoolDataHandler(profileRolesDatasSchool); + + if (activeTab === 'parent') { + setTotalProfilesParentPages( + Math.ceil(totalProfilesParent / itemsPerPage) + ); + } else if (activeTab === 'school') { + setTotalProfilesSchoolPages( + Math.ceil(totalProfilesSchool / itemsPerPage) + ); + } + }, [profileRolesDatasParent, profileRolesDatasSchool, activeTab]); + + const handlePageChange = (newPage) => { + if (activeTab === 'parent') { + setCurrentProfilesParentPage(newPage); + } else if (activeTab === 'school') { + setCurrentProfilesSchoolPage(newPage); + } + }; + + const handleTooltipVisibility = (id) => { + setVisibleTooltipId(id); // Définir l'ID de la ligne pour laquelle la tooltip est visible + }; + + const handleTooltipHide = () => { + setVisibleTooltipId(null); // Cacher toutes les tooltips + }; + + const handleConfirmActivateProfile = (profileRole) => { + setConfirmPopupMessage( + `Êtes-vous sûr de vouloir ${profileRole.is_active ? 'désactiver' : 'activer'} ce profil ?` + ); + setConfirmPopupOnConfirm(() => () => { + handleEdit(profileRole) + .then(() => { + setPopupMessage( + `Le profil a été ${profileRole.is_active ? 'désactivé' : 'activé'} avec succès.` + ); + setPopupVisible(true); + }) + .catch((error) => { + setPopupMessage( + `Erreur lors de la ${profileRole.is_active ? 'désactivation' : 'activation'} du profil.` + ); + setPopupVisible(true); + }); + setConfirmPopupVisible(false); + }); + setConfirmPopupVisible(true); + }; + + const handleConfirmDeleteProfile = (id) => { + setConfirmPopupMessage('Êtes-vous sûr de vouloir supprimer ce profil ?'); + setConfirmPopupOnConfirm(() => () => { + handleDelete(id) + .then(() => { + setPopupMessage('Le profil a été supprimé avec succès.'); + setPopupVisible(true); + }) + .catch((error) => { + setPopupMessage(error.message); + setPopupVisible(true); + }); + setConfirmPopupVisible(false); + }); + setConfirmPopupVisible(true); + }; + + const handleConfirmDissociateGuardian = (profileRole, student) => { + setVisibleTooltipId(null); + setConfirmPopupMessage( + `Vous êtes sur le point de dissocier le responsable ${profileRole.associated_person?.guardian_name} de l'élève ${student.student_name}. Êtes-vous sûr de vouloir poursuivre cette opération ?` + ); + setConfirmPopupOnConfirm(() => () => { + handleDissociate(student.id, profileRole.associated_person?.id) + .then(() => { + setPopupMessage('Le responsable a été dissocié avec succès.'); + setPopupVisible(true); + }) + .catch((error) => { + setPopupMessage(error.message); + setPopupVisible(true); + }); + setConfirmPopupVisible(false); + }); + setConfirmPopupVisible(true); + }; + + const parentColumns = [ + { name: 'Identifiant', transform: (row) => row.associated_profile_email }, + { name: 'Mise à jour', transform: (row) => row.updated_date_formatted }, + { + name: 'Rôle', + transform: (row) => ( + + {roleTypeToLabel(row.role_type)} + + ), + }, + { + name: 'Utilisateur', + transform: (row) => ( +
+ {row.associated_person?.guardian_name} + {row.associated_person && ( +
handleTooltipVisibility(row.id)} // Afficher la tooltip pour cette ligne + onMouseLeave={handleTooltipHide} // Cacher la tooltip + > + + {visibleTooltipId === row.id && ( // Afficher uniquement si l'ID correspond +
+
+ Elève(s) associé(s): +
+ {row.associated_person?.students?.map((student) => ( +
+ + {student.student_name} + +
+ + +
+
+ ))} +
+
+
+ )} +
+ )} +
+ ), + }, + { + name: 'Actions', + transform: (row) => ( +
+ + +
+ ), + }, + ]; + + const schoolAdminColumns = [ + { name: 'Identifiant', transform: (row) => row.associated_profile_email }, + { name: 'Mise à jour', transform: (row) => row.updated_date_formatted }, + { + name: 'Rôle', + transform: (row) => ( + + {roleTypeToLabel(row.role_type)} + + ), + }, + { + name: 'Utilisateur', + transform: (row) => ( +
+ {row.associated_person?.teacher_name} + {row.associated_person && ( +
handleTooltipVisibility(row.id)} // Afficher la tooltip pour cette ligne + onMouseLeave={handleTooltipHide} // Cacher la tooltip + > + + {visibleTooltipId === row.id && ( // Afficher uniquement si l'ID correspond +
+
+ Classes associées: +
+ {row.associated_person?.classes?.map((classe) => ( + + {classe.name} + + ))} +
+
+
+ Spécialités: +
+ {row.associated_person?.specialities?.map( + (speciality) => ( + + ) + )} +
+
+
+ )} +
+ )} +
+ ), + }, + { + name: 'Actions', + transform: (row) => ( +
+ + +
+ ), + }, + ]; + return ( -
- + + + + + ), + }, + { + id: 'school', + label: 'École', + content: ( +
+
+ + ), + }, + ]} + onTabChange={(newActiveTab) => { + setActiveTab(newActiveTab); + }} /> - + {/* Popups */} + setPopupVisible(false)} + uniqueConfirmButton={true} + /> + setConfirmPopupVisible(false)} + /> + ); } diff --git a/Front-End/src/app/[locale]/admin/structure/SchoolClassManagement/page.js b/Front-End/src/app/[locale]/admin/structure/SchoolClassManagement/page.js index 4c6e409..70dff7a 100644 --- a/Front-End/src/app/[locale]/admin/structure/SchoolClassManagement/page.js +++ b/Front-End/src/app/[locale]/admin/structure/SchoolClassManagement/page.js @@ -8,11 +8,9 @@ import { fetchClasse } from '@/app/actions/schoolAction'; import { useSearchParams } from 'next/navigation'; import logger from '@/utils/logger'; import { useClasses } from '@/context/ClassesContext'; -import { BASE_URL } from '@/utils/Url'; import Button from '@/components/Button'; import SelectChoice from '@/components/SelectChoice'; import CheckBox from '@/components/CheckBox'; -import InputText from '@/components/InputText'; import { fetchAbsences, createAbsences, diff --git a/Front-End/src/app/[locale]/parents/page.js b/Front-End/src/app/[locale]/parents/page.js index 26b095a..428d08f 100644 --- a/Front-End/src/app/[locale]/parents/page.js +++ b/Front-End/src/app/[locale]/parents/page.js @@ -14,6 +14,7 @@ import logger from '@/utils/logger'; import { BASE_URL } from '@/utils/Url'; import { useEstablishment } from '@/context/EstablishmentContext'; import { useCsrfToken } from '@/context/CsrfContext'; +import { useClasses } from '@/context/ClassesContext'; export default function ParentHomePage() { const [children, setChildren] = useState([]); @@ -24,6 +25,7 @@ export default function ParentHomePage() { const router = useRouter(); const csrfToken = useCsrfToken(); const [reloadFetch, setReloadFetch] = useState(false); + const { getNiveauLabel } = useClasses(); useEffect(() => { if (user !== null) { @@ -96,8 +98,41 @@ export default function ParentHomePage() { }; const childrenColumns = [ + { + name: 'photo', + transform: (row) => ( +
+ {row.student.photo ? ( + + {`${row.student.first_name} + + ) : ( +
+ + {row.student.first_name[0]} + {row.student.last_name[0]} + +
+ )} +
+ ), + }, { name: 'Nom', transform: (row) => `${row.student.last_name}` }, { name: 'Prénom', transform: (row) => `${row.student.first_name}` }, + { + name: 'Niveau', + transform: (row) => ( +
{getNiveauLabel(row.student.level)}
+ ), + }, { name: 'Statut', transform: (row) => ( diff --git a/Front-End/src/app/actions/authAction.js b/Front-End/src/app/actions/authAction.js index 1e92c54..727f5b6 100644 --- a/Front-End/src/app/actions/authAction.js +++ b/Front-End/src/app/actions/authAction.js @@ -81,7 +81,7 @@ export const fetchProfileRoles = ( ) => { let url = `${BE_AUTH_PROFILES_ROLES_URL}?filter=${filter}&establishment_id=${establishment}`; if (page !== '' && pageSize !== '') { - url = `${BE_AUTH_PROFILES_ROLES_URL}?filter=${filter}&establishment_id=${establishment}&page=${page}&search=${search}`; + url = `${BE_AUTH_PROFILES_ROLES_URL}?filter=${filter}&establishment_id=${establishment}&page=${page}`; } return fetch(url, { headers: { diff --git a/Front-End/src/components/Inscription/ResponsableInputFields.js b/Front-End/src/components/Inscription/ResponsableInputFields.js index dc5d303..4b441dc 100644 --- a/Front-End/src/components/Inscription/ResponsableInputFields.js +++ b/Front-End/src/components/Inscription/ResponsableInputFields.js @@ -89,7 +89,7 @@ export default function ResponsableInputFields({ profile_role_data: { establishment: selectedEstablishmentId, role_type: 2, - is_active: false, + is_active: true, profile_data: { email: '', password: 'Provisoire01!', diff --git a/Front-End/src/components/Popup.js b/Front-End/src/components/Popup.js index 5db8f05..9e8b888 100644 --- a/Front-End/src/components/Popup.js +++ b/Front-End/src/components/Popup.js @@ -16,30 +16,31 @@ const Popup = ({ const messageLines = isStringMessage ? message.split('\n') : null; return ReactDOM.createPortal( -
-
-
+
+
+ {/* Titre ou message */} +
{isStringMessage - ? // Afficher le message sous forme de lignes si c'est une chaîne - messageLines.map((line, index) => ( -

+ ? messageLines.map((line, index) => ( +

{line}

)) - : // Sinon, afficher directement le contenu React - message} + : message}
-
+ + {/* Boutons d'action */} +
{!uniqueConfirmButton && ( )} - {visibleTooltipId === row.id && ( // Afficher uniquement si l'ID correspond -
-
- Elève(s) associé(s): -
- {row.associated_person?.students?.map((student) => ( -
- - {student.student_name} - -
- - -
-
- ))} -
-
-
- )} -
- )} -
- ), - }, - { - name: 'Actions', - transform: (row) => ( -
- - -
- ), - }, - ]; - - const schoolAdminColumns = [ - { name: 'Identifiant', transform: (row) => row.associated_profile_email }, - { name: 'Mise à jour', transform: (row) => row.updated_date_formatted }, - { - name: 'Rôle', - transform: (row) => ( - - {roleTypeToLabel(row.role_type)} - - ), - }, - { - name: 'Utilisateur', - transform: (row) => ( -
- {row.associated_person?.teacher_name} - {row.associated_person && ( -
handleTooltipVisibility(row.id)} // Afficher la tooltip pour cette ligne - onMouseLeave={handleTooltipHide} // Cacher la tooltip - > - - {visibleTooltipId === row.id && ( // Afficher uniquement si l'ID correspond -
-
- Classes associées: -
- {row.associated_person?.classes?.map((classe) => ( - - {classe.name} - - ))} -
-
-
- Spécialités: -
- {row.associated_person?.specialities?.map( - (speciality) => ( - - ) - )} -
-
-
- )} -
- )} -
- ), - }, - { - name: 'Actions', - transform: (row) => ( -
- - -
- ), - }, - ]; - - return ( - <> - - -
- - ), - }, - { - id: 'school', - label: 'École', - content: ( -
-
- - ), - }, - ]} - onTabChange={(newActiveTab) => { - setActiveTab(newActiveTab); - }} - /> - {/* Popups */} - setPopupVisible(false)} - uniqueConfirmButton={true} - /> - setConfirmPopupVisible(false)} - /> - - ); -}; - -export default ProfileDirectory; diff --git a/Front-End/src/components/StatusLabel.js b/Front-End/src/components/StatusLabel.js index 70ca3d3..ca809ec 100644 --- a/Front-End/src/components/StatusLabel.js +++ b/Front-End/src/components/StatusLabel.js @@ -10,7 +10,7 @@ const StatusLabel = ({ status, onChange, showDropdown = true, parent }) => { ? [ { value: 2, label: 'Nouveau' }, { value: 3, label: 'En validation' }, - { value: 5, label: 'Validé' }, + { value: 5, label: 'Inscrit' }, { value: 7, label: 'SEPA reçu' }, { value: 8, label: 'En validation' }, ] diff --git a/Front-End/src/components/Structure/Configuration/ClassesSection.js b/Front-End/src/components/Structure/Configuration/ClassesSection.js index c5cc851..3675060 100644 --- a/Front-End/src/components/Structure/Configuration/ClassesSection.js +++ b/Front-End/src/components/Structure/Configuration/ClassesSection.js @@ -308,11 +308,6 @@ const ClassesSection = ({ } }; - const openEditModalDetails = (classe) => { - setSelectedClass(classe); - setDetailsModalVisible(true); - }; - const renderClassCell = (classe, column) => { const isEditing = editingClass === classe.id; const isCreating = newClass && newClass.id === classe.id; @@ -427,7 +422,7 @@ const ClassesSection = ({ return classe.age_range; case 'NIVEAUX': const levelLabels = Array.isArray(classe.levels) - ? getNiveauxLabels(classe.levels) + ? getNiveauxLabels(classe.levels.sort((a, b) => a - b)) // Trier les niveaux par ordre croissant : []; return (
diff --git a/Front-End/src/components/Structure/Configuration/TeacherItem.js b/Front-End/src/components/Structure/Configuration/TeacherItem.js index 3355045..18c5309 100644 --- a/Front-End/src/components/Structure/Configuration/TeacherItem.js +++ b/Front-End/src/components/Structure/Configuration/TeacherItem.js @@ -24,18 +24,17 @@ const TeacherItem = ({ teacher, isDraggable = true }) => { return (
{teacher.last_name} {teacher.first_name}