mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-01-28 23:43:22 +00:00
feat: Ajout d'une fonction de dissociation entre un responsable et un
élève
This commit is contained in:
@ -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
|
||||
})
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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<id>[0-9]+)$', RegistrationTemplateSimpleView.as_view(), name='registrationTemplates'),
|
||||
re_path(r'^registrationTemplates$', RegistrationTemplateView.as_view(), name="registrationTemplates"),
|
||||
|
||||
re_path(r'^students/(?P<student_id>[0-9]+)/guardians/(?P<guardian_id>[0-9]+)/dissociate', DissociateGuardianView.as_view(), name='dissociate-guardian'),
|
||||
|
||||
]
|
||||
@ -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'
|
||||
]
|
||||
|
||||
@ -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
|
||||
)
|
||||
|
||||
@ -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 (
|
||||
<div className='p-8'>
|
||||
<DjangoCSRFToken csrfToken={csrfToken} />
|
||||
|
||||
<div className="w-full p-4">
|
||||
<ProfileDirectory profileRoles={profileRoles} handleActivateProfile={handleEdit} handleDeleteProfile={handleDelete} />
|
||||
<ProfileDirectory profileRoles={profileRoles} handleActivateProfile={handleEdit} handleDeleteProfile={handleDelete} handleDissociateGuardian={handleDissociate} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -152,3 +152,17 @@ export const fetchTemplatesFromRegistrationFiles = async (id) => {
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
|
||||
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();
|
||||
};
|
||||
@ -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);
|
||||
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
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
</span>
|
||||
)
|
||||
},
|
||||
{ name: 'Utilisateur', transform: (row) => (
|
||||
<div className="flex items-center justify-center space-x-2">
|
||||
{
|
||||
name: 'Utilisateur',
|
||||
transform: (row) => (
|
||||
<div className="flex items-center justify-center space-x-2 relative">
|
||||
<span>{row.associated_person?.guardian_name}</span>
|
||||
{row.associated_person && (
|
||||
<Tooltip content={
|
||||
<div>
|
||||
<div
|
||||
className="relative group"
|
||||
onMouseEnter={() => handleTooltipVisibility(row.id)} // Afficher la tooltip pour cette ligne
|
||||
onMouseLeave={handleTooltipHide} // Cacher la tooltip
|
||||
>
|
||||
<button className="text-blue-500 hover:text-blue-700">
|
||||
<Info className="w-5 h-5" />
|
||||
</button>
|
||||
{visibleTooltipId === row.id && ( // Afficher uniquement si l'ID correspond
|
||||
<div className="absolute z-50 w-96 p-4 bg-white border border-gray-200 rounded shadow-lg left-0 -translate-x-1/2 top-full">
|
||||
<div className="mb-2">
|
||||
<strong>Elève(s) associé(s):</strong>
|
||||
<div className="flex flex-col justify-center space-y-2">
|
||||
<div className="flex flex-col justify-center space-y-2 mt-4">
|
||||
{row.associated_person?.students?.map(student => (
|
||||
<div key={student.student_name} className="flex justify-between items-center">
|
||||
<span className="px-2 py-1 rounded-full text-gray-800">
|
||||
<span className="px-2 py-1 rounded-full text-gray-800 whitespace-nowrap inline-block min-w-0 max-w-fit">
|
||||
{student.student_name}
|
||||
</span>
|
||||
<div className="flex items-center space-x-2">
|
||||
<StatusLabel status={student.registration_status} showDropdown={false} />
|
||||
<button
|
||||
className="text-red-500 hover:text-red-700 flex items-center space-x-1"
|
||||
onClick={() => handleConfirmDissociateGuardian(row, student)}
|
||||
>
|
||||
<XCircle className="w-5 h-5" />
|
||||
<span className="text-sm">Dissocier</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}>
|
||||
<button className="text-blue-500 hover:text-blue-700">
|
||||
<Info className="w-5 h-5" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user