feat: Gestion de la création d'un nouveau guardian, de l'association

avec un guardian dumême établissement, et de l'association avec un
guardian d'un autre établissement
This commit is contained in:
N3WT DE COMPET
2025-03-18 21:06:44 +01:00
parent 173ac47fb2
commit fb73f9e9a8
11 changed files with 277 additions and 181 deletions

View File

@ -16,7 +16,24 @@ class ProfileSerializer(serializers.ModelSerializer):
def get_roles(self, obj): def get_roles(self, obj):
roles = ProfileRole.objects.filter(profile=obj) roles = ProfileRole.objects.filter(profile=obj)
return [{'role_type': role.role_type, 'establishment': role.establishment.id, 'establishment_name': role.establishment.name, 'is_active': role.is_active} for role in roles] roles_data = []
for role in roles:
# Récupérer l'ID de l'associated_person en fonction du type de rôle
if role.role_type == ProfileRole.RoleType.PROFIL_PARENT:
guardian = Guardian.objects.filter(profile_role=role).first()
id_associated_person = guardian.id if guardian else None
else:
teacher = Teacher.objects.filter(profile_role=role).first()
id_associated_person = teacher.id if teacher else None
roles_data.append({
'id_associated_person': id_associated_person,
'role_type': role.role_type,
'establishment': role.establishment.id,
'establishment_name': role.establishment.name,
'is_active': role.is_active,
})
return roles_data
def create(self, validated_data): def create(self, validated_data):
user = Profile( user = Profile(
@ -107,6 +124,7 @@ class ProfileRoleSerializer(serializers.ModelSerializer):
"registration_status": registration_status "registration_status": registration_status
}) })
return { return {
"id": guardian.id,
"guardian_name": f"{guardian.last_name} {guardian.first_name}", "guardian_name": f"{guardian.last_name} {guardian.first_name}",
"students": students_list "students": students_list
} }
@ -118,6 +136,7 @@ class ProfileRoleSerializer(serializers.ModelSerializer):
specialities = teacher.specialities.all() specialities = teacher.specialities.all()
specialities_list = [{"name": speciality.name, "color_code": speciality.color_code} for speciality in specialities] specialities_list = [{"name": speciality.name, "color_code": speciality.color_code} for speciality in specialities]
return { return {
"id": teacher.id,
"teacher_name": f"{teacher.last_name} {teacher.first_name}", "teacher_name": f"{teacher.last_name} {teacher.first_name}",
"classes": classes_list, "classes": classes_list,
"specialities": specialities_list "specialities": specialities_list

View File

@ -4,7 +4,6 @@ from django.http.response import JsonResponse
from django.views.decorators.csrf import ensure_csrf_cookie, csrf_exempt, csrf_protect from django.views.decorators.csrf import ensure_csrf_cookie, csrf_exempt, csrf_protect
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.core.cache import cache
from django.middleware.csrf import get_token from django.middleware.csrf import get_token
from rest_framework.views import APIView from rest_framework.views import APIView
from rest_framework.parsers import JSONParser from rest_framework.parsers import JSONParser
@ -24,7 +23,6 @@ from rest_framework.decorators import action, api_view
from Auth.serializers import ProfileSerializer, ProfileRoleSerializer from Auth.serializers import ProfileSerializer, ProfileRoleSerializer
from Subscriptions.models import RegistrationForm from Subscriptions.models import RegistrationForm
from Subscriptions.signals import clear_cache
import Subscriptions.mailManager as mailer import Subscriptions.mailManager as mailer
import Subscriptions.util as util import Subscriptions.util as util
import logging import logging
@ -204,7 +202,6 @@ class LoginView(APIView):
login(request, user) login(request, user)
user.save() user.save()
clear_cache()
retour = '' retour = ''
# Récupérer tous les rôles de l'utilisateur avec le type spécifié # Récupérer tous les rôles de l'utilisateur avec le type spécifié
@ -423,7 +420,6 @@ class SubscribeView(APIView):
else: else:
return JsonResponse(role_serializer.errors, safe=False, status=status.HTTP_400_BAD_REQUEST) return JsonResponse(role_serializer.errors, safe=False, status=status.HTTP_400_BAD_REQUEST)
clear_cache()
retour = error.returnMessage[error.MESSAGE_ACTIVATION_PROFILE] retour = error.returnMessage[error.MESSAGE_ACTIVATION_PROFILE]
retourErreur = '' retourErreur = ''
return JsonResponse({'message': retour, 'errorMessage': retourErreur, "errorFields": errorFields, "id": profil.id}, safe=False) return JsonResponse({'message': retour, 'errorMessage': retourErreur, "errorFields": errorFields, "id": profil.id}, safe=False)
@ -473,7 +469,6 @@ class NewPasswordView(APIView):
profil.code = util.genereRandomCode(12) profil.code = util.genereRandomCode(12)
profil.datePeremption = util.calculeDatePeremption(util._now(), settings.EXPIRATION_URL_NB_DAYS) profil.datePeremption = util.calculeDatePeremption(util._now(), settings.EXPIRATION_URL_NB_DAYS)
profil.save() profil.save()
clear_cache()
retourErreur = '' retourErreur = ''
retour = error.returnMessage[error.MESSAGE_REINIT_PASSWORD] % (newProfilConnection.get('email')) retour = error.returnMessage[error.MESSAGE_REINIT_PASSWORD] % (newProfilConnection.get('email'))
mailer.envoieReinitMotDePasse(newProfilConnection.get('email'), profil.code) mailer.envoieReinitMotDePasse(newProfilConnection.get('email'), profil.code)
@ -527,7 +522,6 @@ class ResetPasswordView(APIView):
profil.code = '' profil.code = ''
profil.datePeremption = '' profil.datePeremption = ''
profil.save() profil.save()
clear_cache()
retourErreur = '' retourErreur = ''
return JsonResponse({'message': retour, "errorMessage": retourErreur, "errorFields": errorFields}, safe=False) return JsonResponse({'message': retour, "errorMessage": retourErreur, "errorFields": errorFields}, safe=False)

View File

@ -5,6 +5,3 @@ class GestioninscriptionsConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField' default_auto_field = 'django.db.models.BigAutoField'
name = 'Subscriptions' name = 'Subscriptions'
def ready(self):
from Subscriptions.signals import clear_cache
clear_cache()

View File

@ -1,7 +1,6 @@
# state_machine.py # state_machine.py
import json import json
from Subscriptions.models import RegistrationForm from Subscriptions.models import RegistrationForm
from Subscriptions.signals import clear_cache
state_mapping = { state_mapping = {
"ABSENT": RegistrationForm.RegistrationFormStatus.RF_ABSENT, "ABSENT": RegistrationForm.RegistrationFormStatus.RF_ABSENT,
@ -31,7 +30,6 @@ def updateStateMachine(rf, transition) :
if state_machine.trigger(transition, automateModel): if state_machine.trigger(transition, automateModel):
rf.status = state_machine.state rf.status = state_machine.state
rf.save() rf.save()
clear_cache()
class Automate_RF_Register: class Automate_RF_Register:
def __init__(self, initial_state): def __init__(self, initial_state):

View File

@ -110,6 +110,9 @@ class StudentSerializer(serializers.ModelSerializer):
profile_role = guardian_data.pop('profile_role', None) profile_role = guardian_data.pop('profile_role', None)
if profile_role_data: if profile_role_data:
# Vérifiez si 'profile' est un objet ou une clé primaire
if isinstance(profile_role_data.get('profile'), Profile):
profile_role_data['profile'] = profile_role_data['profile'].id
establishment_id = profile_role_data.pop('establishment').id establishment_id = profile_role_data.pop('establishment').id
profile_role_data['establishment'] = establishment_id profile_role_data['establishment'] = establishment_id

View File

@ -1,6 +1,5 @@
from django.db.models.signals import post_save, post_delete, m2m_changed from django.db.models.signals import post_save, post_delete, m2m_changed
from django.dispatch import receiver from django.dispatch import receiver
from django.core.cache import cache
from .models import RegistrationForm, Student, Guardian from .models import RegistrationForm, Student, Guardian
from Auth.models import Profile from Auth.models import Profile
from N3wtSchool import settings from N3wtSchool import settings
@ -8,23 +7,6 @@ from N3wtSchool.redis_client import redis_client
import logging import logging
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def clear_cache():
# Préfixes des clés à supprimer
prefixes = ['N3WT_']
for prefix in prefixes:
# Utiliser le motif pour obtenir les clés correspondant au préfixe
pattern = f'*{prefix}*'
logger.debug(f'pattern : {pattern}')
for key in redis_client.scan_iter(pattern):
redis_client.delete(key)
logger.debug(f'deleting : {key}')
@receiver(post_save, sender=RegistrationForm)
@receiver(post_delete, sender=RegistrationForm)
def clear_cache_after_change(sender, instance, **kwargs):
clear_cache()
@receiver(m2m_changed, sender=Student.guardians.through) @receiver(m2m_changed, sender=Student.guardians.through)
def check_orphan_reponsables(sender, **kwargs): def check_orphan_reponsables(sender, **kwargs):
action = kwargs.pop('action', None) action = kwargs.pop('action', None)

View File

@ -1,7 +1,6 @@
from django.http.response import JsonResponse from django.http.response import JsonResponse
from django.views.decorators.csrf import ensure_csrf_cookie, csrf_protect from django.views.decorators.csrf import ensure_csrf_cookie, csrf_protect
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.core.cache import cache
from rest_framework.parsers import JSONParser from rest_framework.parsers import JSONParser
from rest_framework.views import APIView from rest_framework.views import APIView
from rest_framework.decorators import action, api_view from rest_framework.decorators import action, api_view
@ -18,7 +17,6 @@ import Subscriptions.util as util
from Subscriptions.serializers import RegistrationFormSerializer from Subscriptions.serializers import RegistrationFormSerializer
from Subscriptions.pagination import CustomPagination from Subscriptions.pagination import CustomPagination
from Subscriptions.signals import clear_cache
from Subscriptions.models import Student, Guardian, RegistrationForm, RegistrationTemplate, RegistrationFileGroup from Subscriptions.models import Student, Guardian, RegistrationForm, RegistrationTemplate, RegistrationFileGroup
from Subscriptions.automate import updateStateMachine from Subscriptions.automate import updateStateMachine
@ -88,13 +86,6 @@ class RegisterFormView(APIView):
except ValueError: except ValueError:
page_size = settings.NB_RESULT_PER_PAGE page_size = settings.NB_RESULT_PER_PAGE
# Définir le cache_key en fonction du filtre
page_number = request.GET.get('page', 1)
cache_key = f'N3WT_ficheInscriptions_{establishment_id}_{filter}_page_{page_number}_search_{search if filter == "pending" else ""}'
cached_page = cache.get(cache_key)
if cached_page:
return JsonResponse(cached_page, safe=False)
# Récupérer les dossier d'inscriptions en fonction du filtre # Récupérer les dossier d'inscriptions en fonction du filtre
registerForms_List = None registerForms_List = None
if filter == 'pending': if filter == 'pending':
@ -120,7 +111,6 @@ class RegisterFormView(APIView):
if page is not None: if page is not None:
registerForms_serializer = RegistrationFormSerializer(page, many=True) registerForms_serializer = RegistrationFormSerializer(page, many=True)
response_data = paginator.get_paginated_response(registerForms_serializer.data) response_data = paginator.get_paginated_response(registerForms_serializer.data)
cache.set(cache_key, response_data, timeout=60 * 15)
return JsonResponse(response_data, safe=False) return JsonResponse(response_data, safe=False)
return JsonResponse({'error': 'aucune donnée trouvée', 'count': 0}, safe=False) return JsonResponse({'error': 'aucune donnée trouvée', 'count': 0}, safe=False)
@ -308,7 +298,6 @@ class RegisterFormWithIdView(APIView):
student.profiles.clear() student.profiles.clear()
student.registration_files.clear() student.registration_files.clear()
student.delete() student.delete()
clear_cache()
return JsonResponse("La suppression du dossier a été effectuée avec succès", safe=False) return JsonResponse("La suppression du dossier a été effectuée avec succès", safe=False)

View File

@ -43,7 +43,7 @@ import {
fetchRegistrationFees, fetchRegistrationFees,
fetchTuitionFees } from '@/app/actions/schoolAction'; fetchTuitionFees } from '@/app/actions/schoolAction';
import { createProfile, deleteProfile } from '@/app/actions/authAction'; import { createProfile, deleteProfile, fetchProfiles } from '@/app/actions/authAction';
import { import {
BASE_URL, BASE_URL,
@ -86,6 +86,7 @@ export default function Page({ params: { locale } }) {
const [registrationFees, setRegistrationFees] = useState([]); const [registrationFees, setRegistrationFees] = useState([]);
const [tuitionFees, setTuitionFees] = useState([]); const [tuitionFees, setTuitionFees] = useState([]);
const [groups, setGroups] = useState([]); const [groups, setGroups] = useState([]);
const [profiles, setProfiles] = useState([]);
const csrfToken = useCsrfToken(); const csrfToken = useCsrfToken();
const { selectedEstablishmentId } = useEstablishment(); const { selectedEstablishmentId } = useEstablishment();
@ -235,7 +236,14 @@ useEffect(() => {
}) })
.catch(error => { .catch(error => {
logger.error('Error fetching file groups:', error); logger.error('Error fetching file groups:', error);
}),
fetchProfiles()
.then(data => {
setProfiles(data);
}) })
.catch(error => {
logger.error('Error fetching profileRoles:', error);
}),
]) ])
.then(() => { .then(() => {
setIsLoading(false); setIsLoading(false);
@ -373,24 +381,46 @@ useEffect(()=>{
student: { student: {
last_name: updatedData.studentLastName, last_name: updatedData.studentLastName,
first_name: updatedData.studentFirstName, first_name: updatedData.studentFirstName,
guardians: updatedData.selectedGuardians.length !== 0 ? updatedData.selectedGuardians.map(guardianId => ({ id: guardianId })) : [{ guardians: updatedData.selectedGuardians.length !== 0
profile_role_data: { ? updatedData.selectedGuardians.map(guardianId => ({ id: guardianId }))
establishment: selectedEstablishmentId, : (() => {
role_type: 2, if (updatedData.isExistingParentProfile) {
is_active: false, return [{
profile_data: { profile_role_data: {
email: updatedData.guardianEmail, establishment: selectedEstablishmentId,
password: 'Provisoire01!', role_type: 2,
username: updatedData.guardianEmail, is_active: false,
} profile: updatedData.existingProfileId, // Associer au profil existant
}, },
last_name: updatedData.guardianLastName, last_name: updatedData.guardianLastName,
first_name: updatedData.guardianFirstName, first_name: updatedData.guardianFirstName,
birth_date: updatedData.guardianBirthDate, birth_date: updatedData.guardianBirthDate,
address: updatedData.guardianAddress, address: updatedData.guardianAddress,
phone: updatedData.guardianPhone, phone: updatedData.guardianPhone,
profession: updatedData.guardianProfession profession: updatedData.guardianProfession
}], }];
}
// Si aucun profil existant n'est trouvé, créer un nouveau profil
return [{
profile_role_data: {
establishment: selectedEstablishmentId,
role_type: 2,
is_active: false,
profile_data: {
email: updatedData.guardianEmail,
password: 'Provisoire01!',
username: updatedData.guardianEmail,
}
},
last_name: updatedData.guardianLastName,
first_name: updatedData.guardianFirstName,
birth_date: updatedData.guardianBirthDate,
address: updatedData.guardianAddress,
phone: updatedData.guardianPhone,
profession: updatedData.guardianProfession
}];
})(),
sibling: [] sibling: []
}, },
fees: allFeesIds, fees: allFeesIds,
@ -699,6 +729,7 @@ const columnsSubscribed = [
registrationFees={registrationFees.filter(fee => fee.is_active)} registrationFees={registrationFees.filter(fee => fee.is_active)}
tuitionFees={tuitionFees.filter(fee => fee.is_active)} tuitionFees={tuitionFees.filter(fee => fee.is_active)}
groups={groups} groups={groups}
profiles={profiles}
onSubmit={createRF} onSubmit={createRF}
/> />
)} )}

View File

@ -113,6 +113,11 @@ export const deleteProfileRoles = (id, csrfToken) => {
return fetch(request).then(requestResponseHandler); return fetch(request).then(requestResponseHandler);
}; };
export const fetchProfiles = () => {
return fetch(`${BE_AUTH_PROFILES_URL}`)
.then(requestResponseHandler)
};
export const createProfile = (data, csrfToken) => { export const createProfile = (data, csrfToken) => {
const request = new Request( const request = new Request(
`${BE_AUTH_PROFILES_URL}`, `${BE_AUTH_PROFILES_URL}`,

View File

@ -9,11 +9,14 @@ import DiscountsSection from '@/components/Structure/Tarification/DiscountsSecti
import SectionTitle from '@/components/SectionTitle'; import SectionTitle from '@/components/SectionTitle';
import ProgressStep from '@/components/ProgressStep'; import ProgressStep from '@/components/ProgressStep';
import logger from '@/utils/logger'; import logger from '@/utils/logger';
import Popup from '@/components/Popup';
const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, registrationFees, tuitionFees, onSubmit, currentStep, groups }) => { const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, registrationFees, tuitionFees, profiles, onSubmit, currentStep, groups }) => {
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({
studentLastName: '', studentLastName: '',
studentFirstName: '', studentFirstName: '',
guardianLastName: '',
guardianFirstName: '',
guardianEmail: '', guardianEmail: '',
guardianPhone: '', guardianPhone: '',
selectedGuardians: [], selectedGuardians: [],
@ -31,6 +34,10 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r
const [existingGuardians, setExistingGuardians] = useState([]); const [existingGuardians, setExistingGuardians] = useState([]);
const [totalRegistrationAmount, setTotalRegistrationAmount] = useState(0); const [totalRegistrationAmount, setTotalRegistrationAmount] = useState(0);
const [totalTuitionAmount, setTotalTuitionAmount] = useState(0); const [totalTuitionAmount, setTotalTuitionAmount] = useState(0);
const [filteredStudents, setFilteredStudents] = useState(students);
const [popupVisible, setPopupVisible] = useState(false);
const [popupMessage, setPopupMessage] = useState("");
const stepTitles = { const stepTitles = {
1: 'Nouvel élève', 1: 'Nouvel élève',
@ -45,8 +52,8 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r
const isStep1Valid = formData.studentLastName && formData.studentFirstName; const isStep1Valid = formData.studentLastName && formData.studentFirstName;
const isStep2Valid = ( const isStep2Valid = (
(formData.responsableType === "new" && formData.guardianEmail.length > 0) || formData.selectedGuardians.length > 0 ||
(formData.responsableType === "existing" && formData.selectedGuardians.length > 0) (!formData.emailError && formData.guardianEmail.length > 0 && filteredStudents.length === 0)
); );
const isStep3Valid = formData.selectedRegistrationFees.length > 0; const isStep3Valid = formData.selectedRegistrationFees.length > 0;
const isStep4Valid = formData.selectedTuitionFees.length > 0; const isStep4Valid = formData.selectedTuitionFees.length > 0;
@ -99,9 +106,35 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r
}; };
const nextStep = () => { const nextStep = () => {
if (step < steps.length) { if (step === 2) {
setStep(step + 1); // Vérifier si l'adresse email saisie est rattachée à un profil existant
} const existingProfile = profiles.find(profile => profile.email === formData.guardianEmail);
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) => ({
...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
}));
}
}
}
if (step < steps.length) {
setStep(step + 1);
}
}; };
const prevStep = () => { const prevStep = () => {
@ -120,13 +153,27 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r
}; };
const handleResponsableSelection = (guardianId, guardianEmail) => { const handleResponsableSelection = (guardianId, guardianEmail) => {
setFormData((prevData) => { setFormData((prevData) => {
const isSelected = prevData.selectedGuardians.includes(guardianId); const isSelected = prevData.selectedGuardians.includes(guardianId);
const selectedGuardians = isSelected const selectedGuardians = isSelected
? prevData.selectedGuardians.filter(id => id !== guardianId) ? prevData.selectedGuardians.filter(id => id !== guardianId) // Retirer le guardian si déjà sélectionné
: [...prevData.selectedGuardians, guardianId]; : [...prevData.selectedGuardians, guardianId]; // Ajouter le guardian s'il n'est pas sélectionné
return { ...prevData, selectedGuardians, guardianEmail };
}); // Mettre à jour l'email uniquement si un guardian est sélectionné
const updatedGuardianEmail = isSelected ? '' : guardianEmail;
// Si aucun guardian n'est sélectionné, réinitialiser le tableau des élèves
if (selectedGuardians.length === 0) {
setFilteredStudents(students); // Réafficher tous les élèves
setSelectedEleve(null);
}
return {
...prevData,
selectedGuardians,
guardianEmail: updatedGuardianEmail,
};
});
}; };
const submit = () => { const submit = () => {
@ -265,117 +312,141 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r
{step === 2 && ( {step === 2 && (
<div className="mt-6"> <div className="mt-6">
<div className="flex flex-col space-y-4 mt-6"> {/* Nom, Prénom et Téléphone */}
<label className="flex items-center space-x-3"> <InputTextIcon
<input name="guardianLastName"
type="radio" type="text"
name="responsableType" IconItem={User}
value="new" placeholder="Nom du responsable (optionnel)"
checked={formData.responsableType === 'new'} value={formData.guardianLastName}
onChange={handleChange} onChange={handleChange}
className="form-radio h-3 w-3 text-emerald-600 focus:ring-emerald-500 hover:ring-emerald-400 checked:bg-emerald-600 checked:h-3 checked:w-3" className="w-full mt-4"
/> />
<span className="text-gray-900">Nouveau Responsable</span> <InputTextIcon
</label> name="guardianFirstName"
<label className="flex items-center space-x-3"> type="text"
<input IconItem={User}
type="radio" placeholder="Prénom du responsable (optionnel)"
name="responsableType" value={formData.guardianFirstName}
value="existing" onChange={handleChange}
checked={formData.responsableType === 'existing'} className="w-full mt-4"
onChange={handleChange} />
className="form-radio h-3 w-3 text-emerald-600 focus:ring-emerald-500 hover:ring-emerald-400 checked:bg-emerald-600 checked:h-3 checked:w-3" <InputTextIcon
/> name="guardianPhone"
<span className="text-gray-900">Responsable Existant</span> type="tel"
</label> IconItem={Phone}
</div> placeholder="Numéro de téléphone (optionnel)"
{formData.responsableType === 'new' && ( value={formData.guardianPhone}
<> onChange={handleChange}
<InputTextIcon className="w-full mt-4"
name="guardianLastName"
type="text"
IconItem={User}
placeholder="Nom du responsable (optionnel)"
value={formData.guardianLastName}
onChange={handleChange}
className="w-full mt-4"
/>
<InputTextIcon
name="guardianFirstName"
type="text"
IconItem={User}
placeholder="Prénom du responsable (optionnel)"
value={formData.guardianFirstName}
onChange={handleChange}
className="w-full mt-4"
/>
<InputTextIcon
name="guardianEmail"
type="email"
IconItem={Mail}
placeholder="Email du responsable"
value={formData.guardianEmail}
onChange={handleChange}
className="w-full mt-4"
/>
<InputTextIcon
name="guardianPhone"
type="tel"
IconItem={Phone}
placeholder="Numéro de téléphone (optionnel)"
value={formData.guardianPhone}
onChange={handleChange}
className="w-full mt-4"
/> />
</>
)}
{formData.responsableType === 'existing' && ( {/* Email du responsable */}
<div className="mt-4"> <InputTextIcon
<div className="mt-4" style={{ maxHeight: '300px', overflowY: 'auto' }}> name="guardianEmail"
<table className="min-w-full bg-white border"> type="email"
<thead> IconItem={Mail}
<tr> placeholder="Email du responsable"
<th className="px-4 py-2 border">Nom</th> value={formData.guardianEmail}
<th className="px-4 py-2 border">Prénom</th> onChange={(e) => {
const email = e.target.value;
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
setFormData((prevData) => ({
...prevData,
guardianEmail: email,
emailError:
email.length > 0 && // Le champ de mail est non null
(!emailRegex.test(email) && filteredStudents.length === 0) // Format invalide ou aucun résultat
? "Format d'email invalide ou aucun élève trouvé"
: "",
}));
// Filtrer les responsables affichés dans le tableau
const filteredGuardians = students
.flatMap((student) => student.guardians) // Récupérer tous les guardians
.filter((guardian) =>
guardian.associated_profile_email.toLowerCase().includes(email.toLowerCase())
);
// Filtrer les élèves en fonction des responsables
const filteredStudents = students.filter((student) =>
student.guardians.some((guardian) =>
filteredGuardians.some((filteredGuardian) => filteredGuardian.id === guardian.id)
)
);
setFilteredStudents(filteredStudents); // Mettre à jour l'état des élèves filtrés
// Réinitialiser existingGuardians et selectedStudent si l'élève sélectionné n'est plus dans le tableau
if (!filteredStudents.some((student) => student.id === selectedStudent?.id)) {
setSelectedEleve(null); // Réinitialiser l'élève sélectionné
setExistingGuardians([]); // Réinitialiser les responsables associés
setFormData((prevData) => ({
...prevData,
selectedGuardians: [] // Réinitialiser les responsables sélectionnés
}));
}
}}
errorMsg={formData.emailError}
className="w-full mt-4"
/>
{/* Tableau des élèves */}
<div className="mt-4">
<div className="mt-4" style={{ maxHeight: '300px', overflowY: 'auto' }}>
<table className="min-w-full bg-white border">
<thead>
<tr>
<th className="px-4 py-2 border">Nom</th>
<th className="px-4 py-2 border">Prénom</th>
</tr>
</thead>
<tbody>
{filteredStudents.map((student, index) => ( // Utiliser les élèves filtrés
<tr
key={student.id}
className={`cursor-pointer ${
selectedStudent && selectedStudent.id === student.id
? 'bg-emerald-600 text-white'
: index % 2 === 0
? 'bg-emerald-100'
: ''
}`}
onClick={() => handleEleveSelection(student)}
>
<td className="px-4 py-2 border">{student.last_name}</td>
<td className="px-4 py-2 border">{student.first_name}</td>
</tr> </tr>
</thead>
<tbody>
{students.map((student, index) => (
<tr
key={student.id}
className={`cursor-pointer ${selectedStudent && selectedStudent.id === student.id ? 'bg-emerald-600 text-white' : index % 2 === 0 ? 'bg-emerald-100' : ''}`}
onClick={() => handleEleveSelection(student)}
>
<td className="px-4 py-2 border">{student.last_name}</td>
<td className="px-4 py-2 border">{student.first_name}</td>
</tr>
))}
</tbody>
</table>
</div>
{selectedStudent && (
<div className="mt-4">
<h3 className="font-bold">Responsables associés à {selectedStudent.last_name} {selectedStudent.first_name} :</h3>
{existingGuardians.map((guardian) => (
<div key={guardian.id}>
<label className="flex items-center space-x-3 mt-2">
<input
type="checkbox"
checked={formData.selectedGuardians.includes(guardian.id)}
className="form-checkbox h-5 w-5 text-emerald-600"
onChange={() => handleResponsableSelection(guardian.id, guardian.associated_profile_email)}
/>
<span className="text-gray-900">
{guardian.last_name && guardian.first_name ? `${guardian.last_name} ${guardian.first_name}` : `${guardian.associated_profile_email}`}
</span>
</label>
</div>
))} ))}
</div> </tbody>
)} </table>
</div> </div>
)} {selectedStudent && (
<div className="mt-4">
<h3 className="font-bold">
Responsables associés à {selectedStudent.last_name} {selectedStudent.first_name} :
</h3>
{existingGuardians.map((guardian) => (
<div key={guardian.id}>
<label className="flex items-center space-x-3 mt-2">
<input
type="checkbox"
checked={formData.selectedGuardians.includes(guardian.id)}
className="form-checkbox h-5 w-5 text-emerald-600"
onChange={() => handleResponsableSelection(guardian.id, guardian.associated_profile_email)}
/>
<span className="text-gray-900">
{guardian.last_name && guardian.first_name
? `${guardian.last_name} ${guardian.first_name} - ${guardian.associated_profile_email}`
: `${guardian.associated_profile_email}`}
</span>
</label>
</div>
))}
</div>
)}
</div>
</div> </div>
)} )}
@ -641,6 +712,13 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r
name="Create" /> name="Create" />
)} )}
</div> </div>
<Popup
visible={popupVisible}
message={popupMessage}
onConfirm={() => setPopupVisible(false)}
onCancel={() => setPopupVisible(false)}
/>
</div> </div>
); );
} }

View File

@ -28,14 +28,14 @@ const Popup = ({ visible, message, onConfirm, onCancel, uniqueConfirmButton = fa
<div className="flex justify-end space-x-2"> <div className="flex justify-end space-x-2">
{!uniqueConfirmButton && ( {!uniqueConfirmButton && (
<button <button
className="px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600" className="px-4 py-2 bg-gray-300 text-white rounded hover:bg-gray-700 hover:bg-gray-400"
onClick={onCancel} onClick={onCancel}
> >
Annuler Annuler
</button> </button>
)} )}
<button <button
className="px-4 py-2 bg-emerald-500 text-white rounded hover:bg-emerald-600" className="px-4 py-2 bg-emerald-500 text-white hover:bg-emerald-600 rounded hover:bg-emerald-600"
onClick={onConfirm} onClick={onConfirm}
> >
{uniqueConfirmButton ? 'Fermer' : 'Confirmer'} {uniqueConfirmButton ? 'Fermer' : 'Confirmer'}