mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-01-28 23:43:22 +00:00
Merge pull request 'Merge suite à un push sur la mauvaise branche' (#47) from refactoring into develop
Reviewed-on: https://git.v0id.ovh/n3wt-innov/n3wt-school/pulls/47
This commit is contained in:
@ -120,6 +120,7 @@ class ProfileRoleSerializer(serializers.ModelSerializer):
|
|||||||
registration_form = RegistrationForm.objects.filter(student=student).first()
|
registration_form = RegistrationForm.objects.filter(student=student).first()
|
||||||
registration_status = registration_form.status if registration_form else None
|
registration_status = registration_form.status if registration_form else None
|
||||||
students_list.append({
|
students_list.append({
|
||||||
|
"id": f"{student.id}",
|
||||||
"student_name": f"{student.last_name} {student.first_name}",
|
"student_name": f"{student.last_name} {student.first_name}",
|
||||||
"registration_status": registration_status
|
"registration_status": registration_status
|
||||||
})
|
})
|
||||||
|
|||||||
@ -66,7 +66,8 @@ class TeacherSerializer(serializers.ModelSerializer):
|
|||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
specialities_data = validated_data.pop('specialities', None)
|
specialities_data = validated_data.pop('specialities', None)
|
||||||
associated_profile_email = validated_data.pop('associated_profile_email')
|
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')
|
role_type = validated_data.pop('role_type')
|
||||||
|
|
||||||
profile, created = Profile.objects.get_or_create(
|
profile, created = Profile.objects.get_or_create(
|
||||||
|
|||||||
@ -5,7 +5,7 @@ from . import views
|
|||||||
# RF
|
# RF
|
||||||
from .views import RegisterFormView, RegisterFormWithIdView, send, resend, archive
|
from .views import RegisterFormView, RegisterFormWithIdView, send, resend, archive
|
||||||
# SubClasses
|
# SubClasses
|
||||||
from .views import StudentView, GuardianView, ChildrenListView, StudentListView
|
from .views import StudentView, GuardianView, ChildrenListView, StudentListView, DissociateGuardianView
|
||||||
# Files
|
# Files
|
||||||
from .views import RegistrationTemplateMasterView, RegistrationTemplateMasterSimpleView, RegistrationTemplateView, RegistrationTemplateSimpleView
|
from .views import RegistrationTemplateMasterView, RegistrationTemplateMasterSimpleView, RegistrationTemplateView, RegistrationTemplateSimpleView
|
||||||
from .views import RegistrationFileGroupView, RegistrationFileGroupSimpleView, get_registration_files_by_group
|
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/(?P<id>[0-9]+)$', RegistrationTemplateSimpleView.as_view(), name='registrationTemplates'),
|
||||||
re_path(r'^registrationTemplates$', RegistrationTemplateView.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_views import RegistrationTemplateMasterView, RegistrationTemplateMasterSimpleView, RegistrationTemplateView, RegistrationTemplateSimpleView
|
||||||
from .registration_file_group_views import RegistrationFileGroupView, RegistrationFileGroupSimpleView, get_registration_files_by_group
|
from .registration_file_group_views import RegistrationFileGroupView, RegistrationFileGroupSimpleView, get_registration_files_by_group
|
||||||
from .student_views import StudentView, StudentListView, ChildrenListView
|
from .student_views import StudentView, StudentListView, ChildrenListView
|
||||||
from .guardian_views import GuardianView
|
from .guardian_views import GuardianView, DissociateGuardianView
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'RegisterFormView',
|
'RegisterFormView',
|
||||||
@ -22,4 +22,5 @@ __all__ = [
|
|||||||
'StudentListView',
|
'StudentListView',
|
||||||
'ChildrenListView',
|
'ChildrenListView',
|
||||||
'GuardianView',
|
'GuardianView',
|
||||||
|
'DissociateGuardianView'
|
||||||
]
|
]
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
from django.http.response import JsonResponse
|
from django.http.response import JsonResponse
|
||||||
|
from rest_framework import status
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
from drf_yasg.utils import swagger_auto_schema
|
from drf_yasg.utils import swagger_auto_schema
|
||||||
from drf_yasg import openapi
|
from drf_yasg import openapi
|
||||||
|
|
||||||
from Subscriptions.models import Guardian
|
from Subscriptions.models import Guardian, Student
|
||||||
from N3wtSchool import bdd
|
from N3wtSchool import bdd
|
||||||
|
|
||||||
class GuardianView(APIView):
|
class GuardianView(APIView):
|
||||||
@ -32,3 +33,43 @@ class GuardianView(APIView):
|
|||||||
def get(self, request):
|
def get(self, request):
|
||||||
lastGuardian = bdd.getLastId(Guardian)
|
lastGuardian = bdd.getLastId(Guardian)
|
||||||
return JsonResponse({"lastid":lastGuardian}, safe=False)
|
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'
|
'use client'
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { fetchProfileRoles, updateProfileRoles, deleteProfileRoles } from '@/app/actions/authAction';
|
import { fetchProfileRoles, updateProfileRoles, deleteProfileRoles } from '@/app/actions/authAction';
|
||||||
|
import { dissociateGuardian } from '@/app/actions/subscriptionAction';
|
||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||||
import DjangoCSRFToken from '@/components/DjangoCSRFToken';
|
import DjangoCSRFToken from '@/components/DjangoCSRFToken';
|
||||||
import { useCsrfToken } from '@/context/CsrfContext';
|
import { useCsrfToken } from '@/context/CsrfContext';
|
||||||
import ProfileDirectory from '@/components/ProfileDirectory';
|
import ProfileDirectory from '@/components/ProfileDirectory';
|
||||||
import { BE_AUTH_PROFILES_ROLES_URL } from '@/utils/Url';
|
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const [profileRoles, setProfileRoles] = useState([]);
|
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 (
|
return (
|
||||||
<div className='p-8'>
|
<div className='p-8'>
|
||||||
<DjangoCSRFToken csrfToken={csrfToken} />
|
<DjangoCSRFToken csrfToken={csrfToken} />
|
||||||
|
|
||||||
<div className="w-full p-4">
|
<div className="w-full p-4">
|
||||||
<ProfileDirectory profileRoles={profileRoles} handleActivateProfile={handleEdit} handleDeleteProfile={handleDelete} />
|
<ProfileDirectory profileRoles={profileRoles} handleActivateProfile={handleEdit} handleDeleteProfile={handleDelete} handleDissociateGuardian={handleDissociate} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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');
|
throw new Error('Erreur lors de la récupération des fichiers associés au groupe');
|
||||||
}
|
}
|
||||||
return response.json();
|
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) {
|
if (existingProfile) {
|
||||||
// Vérifier si le profil a un rôle de type PARENT
|
// Vérifier si le profil a un rôle de type PARENT
|
||||||
const parentRole = existingProfile.roles.find(role => role.role_type === 2);
|
const parentRole = existingProfile.roles.find(role => role.role_type === 2);
|
||||||
|
console.log('Profil associé trouvé !', existingProfile);
|
||||||
if (parentRole) {
|
setFormData((prevData) => ({
|
||||||
console.log('Profil parent associé trouvé !', existingProfile);
|
|
||||||
setFormData((prevData) => ({
|
|
||||||
...prevData,
|
...prevData,
|
||||||
guardianEmail: existingProfile.email, // Mettre à jour le champ guardianEmail avec l'email du profil
|
guardianEmail: existingProfile.email, // Mettre à jour le champ guardianEmail avec l'email du profil
|
||||||
isExistingParentProfile: true, // Indiquer que le profil est un parent existant
|
isExistingParentProfile: true, // Indiquer que le profil est un parent existant
|
||||||
existingProfileId: existingProfile.id
|
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 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 Table from '@/components/Table';
|
||||||
import Popup from '@/components/Popup';
|
import Popup from '@/components/Popup';
|
||||||
import StatusLabel from '@/components/StatusLabel';
|
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 parentProfiles = profileRoles.filter(profileRole => profileRole.role_type === 2);
|
||||||
const schoolAdminProfiles = 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 [confirmPopupVisible, setConfirmPopupVisible] = useState(false);
|
||||||
const [confirmPopupMessage, setConfirmPopupMessage] = useState("");
|
const [confirmPopupMessage, setConfirmPopupMessage] = useState("");
|
||||||
const [confirmPopupOnConfirm, setConfirmPopupOnConfirm] = 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) => {
|
const handleConfirmActivateProfile = (profileRole) => {
|
||||||
setConfirmPopupMessage(`Êtes-vous sûr de vouloir ${profileRole.is_active ? 'désactiver' : 'activer'} ce profil ?`);
|
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);
|
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 = [
|
const parentColumns = [
|
||||||
{ name: 'Identifiant', transform: (row) => row.associated_profile_email },
|
{ name: 'Identifiant', transform: (row) => row.associated_profile_email },
|
||||||
{ name: 'Rôle', transform: (row) => (
|
{ name: 'Rôle', transform: (row) => (
|
||||||
@ -84,31 +113,47 @@ const ProfileDirectory = ({ profileRoles, handleActivateProfile, handleDeletePro
|
|||||||
</span>
|
</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>
|
<span>{row.associated_person?.guardian_name}</span>
|
||||||
{row.associated_person && (
|
{row.associated_person && (
|
||||||
<Tooltip content={
|
<div
|
||||||
<div>
|
className="relative group"
|
||||||
<div className="mb-2">
|
onMouseEnter={() => handleTooltipVisibility(row.id)} // Afficher la tooltip pour cette ligne
|
||||||
<strong>Elève(s) associé(s):</strong>
|
onMouseLeave={handleTooltipHide} // Cacher la tooltip
|
||||||
<div className="flex flex-col justify-center space-y-2">
|
>
|
||||||
{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">
|
|
||||||
{student.student_name}
|
|
||||||
</span>
|
|
||||||
<StatusLabel status={student.registration_status} showDropdown={false} />
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}>
|
|
||||||
<button className="text-blue-500 hover:text-blue-700">
|
<button className="text-blue-500 hover:text-blue-700">
|
||||||
<Info className="w-5 h-5" />
|
<Info className="w-5 h-5" />
|
||||||
</button>
|
</button>
|
||||||
</Tooltip>
|
{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 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 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>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user