diff --git a/Back-End/Auth/serializers.py b/Back-End/Auth/serializers.py index bee22ac..c4c4d7e 100644 --- a/Back-End/Auth/serializers.py +++ b/Back-End/Auth/serializers.py @@ -120,6 +120,7 @@ class ProfileRoleSerializer(serializers.ModelSerializer): registration_form = RegistrationForm.objects.filter(student=student).first() registration_status = registration_form.status if registration_form else None students_list.append({ + "id": f"{student.id}", "student_name": f"{student.last_name} {student.first_name}", "registration_status": registration_status }) diff --git a/Back-End/School/serializers.py b/Back-End/School/serializers.py index 42bb6bb..04231a2 100644 --- a/Back-End/School/serializers.py +++ b/Back-End/School/serializers.py @@ -66,7 +66,8 @@ class TeacherSerializer(serializers.ModelSerializer): def create(self, validated_data): specialities_data = validated_data.pop('specialities', None) associated_profile_email = validated_data.pop('associated_profile_email') - establishment_id = validated_data.pop('establishment') + establishment_id = validated_data.get('establishment') + print(f'debug : {validated_data}') role_type = validated_data.pop('role_type') profile, created = Profile.objects.get_or_create( diff --git a/Back-End/Subscriptions/urls.py b/Back-End/Subscriptions/urls.py index e8e705b..b652644 100644 --- a/Back-End/Subscriptions/urls.py +++ b/Back-End/Subscriptions/urls.py @@ -5,7 +5,7 @@ from . import views # RF from .views import RegisterFormView, RegisterFormWithIdView, send, resend, archive # SubClasses -from .views import StudentView, GuardianView, ChildrenListView, StudentListView +from .views import StudentView, GuardianView, ChildrenListView, StudentListView, DissociateGuardianView # Files from .views import RegistrationTemplateMasterView, RegistrationTemplateMasterSimpleView, RegistrationTemplateView, RegistrationTemplateSimpleView from .views import RegistrationFileGroupView, RegistrationFileGroupSimpleView, get_registration_files_by_group @@ -38,4 +38,7 @@ urlpatterns = [ re_path(r'^registrationTemplates/(?P[0-9]+)$', RegistrationTemplateSimpleView.as_view(), name='registrationTemplates'), re_path(r'^registrationTemplates$', RegistrationTemplateView.as_view(), name="registrationTemplates"), + + re_path(r'^students/(?P[0-9]+)/guardians/(?P[0-9]+)/dissociate', DissociateGuardianView.as_view(), name='dissociate-guardian'), + ] \ No newline at end of file diff --git a/Back-End/Subscriptions/views/__init__.py b/Back-End/Subscriptions/views/__init__.py index 7fbf22e..6ae84c4 100644 --- a/Back-End/Subscriptions/views/__init__.py +++ b/Back-End/Subscriptions/views/__init__.py @@ -2,7 +2,7 @@ from .register_form_views import RegisterFormView, RegisterFormWithIdView, send, from .registration_file_views import RegistrationTemplateMasterView, RegistrationTemplateMasterSimpleView, RegistrationTemplateView, RegistrationTemplateSimpleView from .registration_file_group_views import RegistrationFileGroupView, RegistrationFileGroupSimpleView, get_registration_files_by_group from .student_views import StudentView, StudentListView, ChildrenListView -from .guardian_views import GuardianView +from .guardian_views import GuardianView, DissociateGuardianView __all__ = [ 'RegisterFormView', @@ -22,4 +22,5 @@ __all__ = [ 'StudentListView', 'ChildrenListView', 'GuardianView', + 'DissociateGuardianView' ] diff --git a/Back-End/Subscriptions/views/guardian_views.py b/Back-End/Subscriptions/views/guardian_views.py index 13e0912..4d0244b 100644 --- a/Back-End/Subscriptions/views/guardian_views.py +++ b/Back-End/Subscriptions/views/guardian_views.py @@ -1,9 +1,10 @@ from django.http.response import JsonResponse +from rest_framework import status from rest_framework.views import APIView from drf_yasg.utils import swagger_auto_schema from drf_yasg import openapi -from Subscriptions.models import Guardian +from Subscriptions.models import Guardian, Student from N3wtSchool import bdd class GuardianView(APIView): @@ -32,3 +33,43 @@ class GuardianView(APIView): def get(self, request): lastGuardian = bdd.getLastId(Guardian) return JsonResponse({"lastid":lastGuardian}, safe=False) + +class DissociateGuardianView(APIView): + """ + Vue pour dissocier un Guardian d'un Student. + """ + + def put(self, request, student_id, guardian_id): + try: + # Récupérer l'étudiant + student = Student.objects.get(id=student_id) + + # Récupérer le guardian + guardian = Guardian.objects.get(id=guardian_id) + + # Supprimer la relation entre le student et le guardian + student.guardians.remove(guardian) + + # Vérifier si le guardian n'est plus associé à aucun élève + if guardian.student_set.count() == 0: # Utilise la relation ManyToMany inverse + guardian.delete() + + return JsonResponse( + {"message": f"Le guardian {guardian.last_name} {guardian.first_name} a été dissocié de l'étudiant {student.last_name} {student.first_name}."}, + status=status.HTTP_200_OK + ) + except Student.DoesNotExist: + return JsonResponse( + {"error": "Étudiant non trouvé."}, + status=status.HTTP_404_NOT_FOUND + ) + except Guardian.DoesNotExist: + return JsonResponse( + {"error": "Guardian non trouvé."}, + status=status.HTTP_404_NOT_FOUND + ) + except Exception as e: + return JsonResponse( + {"error": f"Une erreur est survenue : {str(e)}"}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR + ) diff --git a/Front-End/src/app/[locale]/admin/directory/page.js b/Front-End/src/app/[locale]/admin/directory/page.js index 9b3b51d..ebbea53 100644 --- a/Front-End/src/app/[locale]/admin/directory/page.js +++ b/Front-End/src/app/[locale]/admin/directory/page.js @@ -1,12 +1,12 @@ 'use client' import React, { useState, useEffect } from 'react'; import { fetchProfileRoles, updateProfileRoles, deleteProfileRoles } from '@/app/actions/authAction'; +import { dissociateGuardian } from '@/app/actions/subscriptionAction'; import logger from '@/utils/logger'; import { useEstablishment } from '@/context/EstablishmentContext'; import DjangoCSRFToken from '@/components/DjangoCSRFToken'; import { useCsrfToken } from '@/context/CsrfContext'; import ProfileDirectory from '@/components/ProfileDirectory'; -import { BE_AUTH_PROFILES_ROLES_URL } from '@/utils/Url'; export default function Page() { const [profileRoles, setProfileRoles] = useState([]); @@ -54,12 +54,23 @@ export default function Page() { }); }; + const handleDissociate = (studentId, guardianId) => { + return dissociateGuardian(studentId, guardianId) + .then(() => { + logger.debug("Guardian dissociated successfully:", guardianId); + }) + .catch(error => { + logger.error('Error dissociating guardian:', error); + throw error; + }); + }; + return (
- +
); diff --git a/Front-End/src/app/actions/subscriptionAction.js b/Front-End/src/app/actions/subscriptionAction.js index da4c4d5..9024fa5 100644 --- a/Front-End/src/app/actions/subscriptionAction.js +++ b/Front-End/src/app/actions/subscriptionAction.js @@ -151,4 +151,18 @@ export const fetchTemplatesFromRegistrationFiles = async (id) => { throw new Error('Erreur lors de la récupération des fichiers associés au groupe'); } return response.json(); -} \ No newline at end of file +} + +export const dissociateGuardian = async (studentId, guardianId) => { + const response = await fetch(`${BE_SUBSCRIPTION_STUDENTS_URL}/${studentId}/guardians/${guardianId}/dissociate`, { + credentials: 'include', + method: 'PUT', + headers: { + 'Accept': 'application/json', + }, + }); + if (!response.ok) { + throw new Error('Erreur lors de la dissociation.'); + } + return response.json(); +}; \ No newline at end of file diff --git a/Front-End/src/components/Inscription/InscriptionForm.js b/Front-End/src/components/Inscription/InscriptionForm.js index 846f983..721dbbe 100644 --- a/Front-End/src/components/Inscription/InscriptionForm.js +++ b/Front-End/src/components/Inscription/InscriptionForm.js @@ -113,22 +113,13 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r if (existingProfile) { // Vérifier si le profil a un rôle de type PARENT const parentRole = existingProfile.roles.find(role => role.role_type === 2); - - if (parentRole) { - console.log('Profil parent associé trouvé !', existingProfile); - setFormData((prevData) => ({ + console.log('Profil associé trouvé !', existingProfile); + setFormData((prevData) => ({ ...prevData, guardianEmail: existingProfile.email, // Mettre à jour le champ guardianEmail avec l'email du profil isExistingParentProfile: true, // Indiquer que le profil est un parent existant existingProfileId: existingProfile.id })); - } else { - console.log('Le profil existe mais n\'est pas de type PARENT.'); - setFormData((prevData) => ({ - ...prevData, - isExistingParentProfile: false, // Réinitialiser si le profil n'est pas de type PARENT - })); - } } } diff --git a/Front-End/src/components/ProfileDirectory.js b/Front-End/src/components/ProfileDirectory.js index 2c6533b..690e144 100644 --- a/Front-End/src/components/ProfileDirectory.js +++ b/Front-End/src/components/ProfileDirectory.js @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import { Trash2, Eye, EyeOff, ToggleLeft, ToggleRight, Info } from 'lucide-react'; +import { Trash2, Eye, EyeOff, ToggleLeft, ToggleRight, Info, XCircle } from 'lucide-react'; import Table from '@/components/Table'; import Popup from '@/components/Popup'; import StatusLabel from '@/components/StatusLabel'; @@ -32,7 +32,7 @@ const roleTypeToBadgeClass = (roleType) => { } }; -const ProfileDirectory = ({ profileRoles, handleActivateProfile, handleDeleteProfile }) => { +const ProfileDirectory = ({ profileRoles, handleActivateProfile, handleDeleteProfile, handleDissociateGuardian }) => { const parentProfiles = profileRoles.filter(profileRole => profileRole.role_type === 2); const schoolAdminProfiles = profileRoles.filter(profileRole => profileRole.role_type !== 2); @@ -41,6 +41,15 @@ const ProfileDirectory = ({ profileRoles, handleActivateProfile, handleDeletePro const [confirmPopupVisible, setConfirmPopupVisible] = useState(false); const [confirmPopupMessage, setConfirmPopupMessage] = useState(""); const [confirmPopupOnConfirm, setConfirmPopupOnConfirm] = useState(() => {}); + const [visibleTooltipId, setVisibleTooltipId] = useState(null); + + 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 ?`); @@ -76,6 +85,26 @@ const ProfileDirectory = ({ profileRoles, handleActivateProfile, handleDeletePro 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(() => () => { + handleDissociateGuardian(student.id, profileRole.associated_person?.id) + .then(() => { + setPopupMessage("Le responsable a été dissocié avec succès."); + setPopupVisible(true); + }) + .catch(error => { + setPopupMessage("Erreur lors de la dissociation du responsable."); + setPopupVisible(true); + }); + setConfirmPopupVisible(false); + }); + setConfirmPopupVisible(true); + }; + const parentColumns = [ { name: 'Identifiant', transform: (row) => row.associated_profile_email }, { name: 'Rôle', transform: (row) => ( @@ -84,31 +113,47 @@ const ProfileDirectory = ({ profileRoles, handleActivateProfile, handleDeletePro ) }, - { name: 'Utilisateur', transform: (row) => ( -
+ { + name: 'Utilisateur', + transform: (row) => ( +
{row.associated_person?.guardian_name} {row.associated_person && ( - -
- Elève(s) associé(s): -
- {row.associated_person?.students?.map(student => ( -
- - {student.student_name} - - -
- ))} -
-
-
- }> +
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} + +
+ + +
+
+ ))} +
+
+
+ )} +
)}
)