diff --git a/Back-End/Auth/serializers.py b/Back-End/Auth/serializers.py index 88cc2ee..5085eb3 100644 --- a/Back-End/Auth/serializers.py +++ b/Back-End/Auth/serializers.py @@ -68,6 +68,7 @@ class ProfileSerializer(serializers.ModelSerializer): return ret class ProfileRoleSerializer(serializers.ModelSerializer): + id = serializers.IntegerField(required=False) profile = serializers.PrimaryKeyRelatedField(queryset=Profile.objects.all(), required=False) profile_data = ProfileSerializer(write_only=True, required=False) associated_profile_email = serializers.SerializerMethodField() diff --git a/Back-End/School/models.py b/Back-End/School/models.py index 71d6078..16e4f04 100644 --- a/Back-End/School/models.py +++ b/Back-End/School/models.py @@ -32,7 +32,7 @@ class Teacher(models.Model): last_name = models.CharField(max_length=100) first_name = models.CharField(max_length=100) specialities = models.ManyToManyField(Speciality, blank=True) - profile_role = models.OneToOneField(ProfileRole, on_delete=models.CASCADE, related_name='teacher_profile', null=True, blank=True) + profile_role = models.OneToOneField(ProfileRole, on_delete=models.CASCADE, related_name='teacher_profile', blank=True) updated_date = models.DateTimeField(auto_now=True) def __str__(self): diff --git a/Back-End/School/serializers.py b/Back-End/School/serializers.py index e706560..ee25db1 100644 --- a/Back-End/School/serializers.py +++ b/Back-End/School/serializers.py @@ -33,72 +33,85 @@ class TeacherSerializer(serializers.ModelSerializer): specialities = serializers.PrimaryKeyRelatedField(queryset=Speciality.objects.all(), many=True, required=False) specialities_details = serializers.SerializerMethodField() updated_date_formatted = serializers.SerializerMethodField() - role_type_display = serializers.SerializerMethodField() - role_type = serializers.IntegerField(write_only=True) - associated_profile_email = serializers.EmailField(write_only=True) + role_type = serializers.SerializerMethodField() profile_role = serializers.PrimaryKeyRelatedField(queryset=ProfileRole.objects.all(), required=False) - associated_profile_email_display = serializers.SerializerMethodField() + profile_role_data = ProfileRoleSerializer(write_only=True, required=False) + associated_profile_email = serializers.SerializerMethodField() class Meta: model = Teacher fields = '__all__' - def create_or_update_profile_role(self, profile, associated_profile_email, establishment_id, role_type): - # Mettre à jour l'email du profil si nécessaire - if profile.email != associated_profile_email: - profile.email = associated_profile_email - profile.username = associated_profile_email - profile.save() + def create_or_update_teacher(self, teacher_data): + profile_role_data = teacher_data.pop('profile_role_data', None) + profile_role = teacher_data.pop('profile_role', None) - profile_role, created = ProfileRole.objects.update_or_create( - profile=profile, - establishment_id=establishment_id, - defaults={'role_type': role_type, 'is_active': True} - ) + # Gestion du ProfileRole + 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 + profile_role_data['establishment'] = establishment_id - if not created: - profile_role.role_type = role_type - profile_role.establishment_id = establishment_id - profile_role.save() + # Vérifiez si un ID est fourni pour une mise à jour + profile_role_id = profile_role_data.get('id') + if profile_role_id: + # Mettre à jour un ProfileRole existant + try: + profile_role_instance = ProfileRole.objects.get(id=profile_role_id) + profile_role_serializer = ProfileRoleSerializer(profile_role_instance, data=profile_role_data) + profile_role_serializer.is_valid(raise_exception=True) + profile_role = profile_role_serializer.save() + except ProfileRole.DoesNotExist: + raise serializers.ValidationError({"profile_role_data": f"ProfileRole with id {profile_role_id} does not exist."}) + else: + # Créer un nouveau ProfileRole + profile_role_serializer = ProfileRoleSerializer(data=profile_role_data) + profile_role_serializer.is_valid(raise_exception=True) + profile_role = profile_role_serializer.save() + elif profile_role: + # Récupérer un ProfileRole existant + profile_role = ProfileRole.objects.get(id=profile_role.id) - return profile_role + if profile_role: + teacher_data['profile_role'] = profile_role + + # Vérifier si un Teacher existe déjà pour ce profile_role + teacher_instance = Teacher.objects.filter(profile_role=profile_role).first() + + if teacher_instance: + # Mettre à jour les champs existants + for key, value in teacher_data.items(): + setattr(teacher_instance, key, value) + teacher_instance.save() + created = False + else: + # Créer un nouveau Teacher + teacher_instance = Teacher.objects.create(**teacher_data) + created = True + + return teacher_instance 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.get('establishment') - role_type = validated_data.pop('role_type') + specialities_data = validated_data.pop('specialities', []) + teacher_instance = self.create_or_update_teacher(validated_data) - profile, created = Profile.objects.get_or_create( - email=associated_profile_email, - defaults={'username': associated_profile_email} - ) - - profile_role = self.create_or_update_profile_role(profile, associated_profile_email, establishment_id, role_type) - - teacher = Teacher.objects.create(profile_role=profile_role, **validated_data) + # Associer les spécialités if specialities_data: - teacher.specialities.set(specialities_data) - teacher.save() - return teacher + teacher_instance.specialities.set(specialities_data) + + return teacher_instance def update(self, instance, validated_data): specialities_data = validated_data.pop('specialities', []) - associated_profile_email = validated_data.pop('associated_profile_email', instance.profile_role.profile.email) - establishment_id = validated_data.get('establishment', instance.profile_role.establishment.id) - role_type = validated_data.get('role_type', instance.profile_role.role_type) + teacher_instance = self.create_or_update_teacher(validated_data) - profile = instance.profile_role.profile - - profile_role = self.create_or_update_profile_role(profile, associated_profile_email, establishment_id, role_type) - instance.profile_role = profile_role - - instance.last_name = validated_data.get('last_name', instance.last_name) - instance.first_name = validated_data.get('first_name', instance.first_name) - instance.save() + # Mettre à jour les spécialités if specialities_data: - instance.specialities.set(specialities_data) - return instance + teacher_instance.specialities.set(specialities_data) + + return teacher_instance def get_updated_date_formatted(self, obj): utc_time = timezone.localtime(obj.updated_date) # Convert to local time @@ -114,14 +127,11 @@ class TeacherSerializer(serializers.ModelSerializer): return obj.profile_role.profile.email return None - def get_role_type_display(self, obj): + def get_role_type(self, obj): if obj.profile_role: return obj.profile_role.role_type return None - def get_associated_profile_email_display(self, obj): - return self.get_associated_profile_email(obj) - class PlanningSerializer(serializers.ModelSerializer): class Meta: model = Planning diff --git a/Back-End/School/views.py b/Back-End/School/views.py index f211640..a4cb382 100644 --- a/Back-End/School/views.py +++ b/Back-End/School/views.py @@ -116,7 +116,7 @@ class TeacherDetailView(APIView): return JsonResponse(teacher_serializer.errors, safe=False) def delete(self, request, id): - return delete_object(Teacher, id, related_field='profile') + return delete_object(Teacher, id, related_field='profile_role') @method_decorator(csrf_protect, name='dispatch') @method_decorator(ensure_csrf_cookie, name='dispatch') diff --git a/Front-End/src/app/[locale]/admin/structure/page.js b/Front-End/src/app/[locale]/admin/structure/page.js index 28663e3..4943f6b 100644 --- a/Front-End/src/app/[locale]/admin/structure/page.js +++ b/Front-End/src/app/[locale]/admin/structure/page.js @@ -22,7 +22,7 @@ import { fetchTuitionPaymentPlans, fetchRegistrationPaymentModes, fetchTuitionPaymentModes } from '@/app/actions/schoolAction'; -import { fetchProfileRoles } from '@/app/actions/authAction'; +import { fetchProfileRoles, fetchProfiles } from '@/app/actions/authAction'; import SidebarTabs from '@/components/SidebarTabs'; import FilesGroupsManagement from '@/components/Structure/Files/FilesGroupsManagement'; import { fetchRegistrationTemplateMaster } from "@/app/actions/registerFileGroupAction"; @@ -43,6 +43,7 @@ export default function Page() { const [tuitionPaymentPlans, setTuitionPaymentPlans] = useState([]); const [registrationPaymentModes, setRegistrationPaymentModes] = useState([]); const [tuitionPaymentModes, setTuitionPaymentModes] = useState([]); + const [profiles, setProfiles] = useState([]); const csrfToken = useCsrfToken(); const { selectedEstablishmentId } = useEstablishment(); @@ -91,6 +92,14 @@ export default function Page() { // Fetch data for tuition payment modes handleTuitionPaymentModes(); + + fetchProfiles() + .then(data => { + setProfiles(data); + }) + .catch(error => { + logger.error('Error fetching profileRoles:', error); + }) } }, [selectedEstablishmentId]); @@ -258,6 +267,7 @@ export default function Page() { setTeachers={setTeachers} classes={classes} setClasses={setClasses} + profiles={profiles} handleCreate={handleCreate} handleEdit={handleEdit} handleDelete={handleDelete} diff --git a/Front-End/src/components/Inscription/InscriptionForm.js b/Front-End/src/components/Inscription/InscriptionForm.js index 6dd799b..6dc841b 100644 --- a/Front-End/src/components/Inscription/InscriptionForm.js +++ b/Front-End/src/components/Inscription/InscriptionForm.js @@ -1,4 +1,4 @@ -import { useState, useEffect } from 'react'; +import { useState, useEffect, useRef } from 'react'; import { User, Mail, Phone, UserCheck, DollarSign, Percent } from 'lucide-react'; import InputTextIcon from '@/components/InputTextIcon'; import ToggleSwitch from '@/components/ToggleSwitch'; @@ -51,6 +51,8 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r const [popupVisible, setPopupVisible] = useState(false); const [popupMessage, setPopupMessage] = useState(""); + const formDataRef = useRef(formData); + const stepTitles = { 1: 'Nouvel élève', 2: 'Nouveau Responsable', @@ -103,6 +105,10 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r }, [registrationDiscounts, registrationFees]); + useEffect(() => { + formDataRef.current = formData; // Mettre à jour la référence à chaque changement de formData + }, [formData]); + useEffect(() => { setStep(currentStep || 1); }, [currentStep]); @@ -119,24 +125,39 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r })); }; - const nextStep = () => { - if (step === 2) { - // 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); - 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 - })); - } + const validateAndSubmit = async () => { + const existingProfile = profiles.find(profile => profile.email === formData.guardianEmail); + + if (existingProfile) { + console.log('existingProfile : ', existingProfile); + await 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, + })); } - + + // Utiliser la dernière version de formData via formDataRef + logger.debug('Submitting form data:', formDataRef.current); + onSubmit(formDataRef.current); + }; + + const nextStep = async () => { + if (step === 2) { + const existingProfile = profiles.find(profile => profile.email === formData.guardianEmail); + + if (existingProfile) { + console.log('existingProfile : ', existingProfile); + await 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, + })); + } + } + if (!showOnlyStep2 && step < steps.length) { setStep(step + 1); } @@ -182,9 +203,11 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r }; const submit = () => { + setTimeout(() => { logger.debug('Submitting form data:', formData); onSubmit(formData); - } + }, 0); + }; const handleRegistrationFeeSelection = (feeId) => { setFormData((prevData) => { @@ -683,7 +706,7 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r <> - + {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 => ( + + ))} +
+
+
+ )} + )} ) diff --git a/Front-End/src/components/Structure/Configuration/StructureManagement.js b/Front-End/src/components/Structure/Configuration/StructureManagement.js index dd2378e..126761a 100644 --- a/Front-End/src/components/Structure/Configuration/StructureManagement.js +++ b/Front-End/src/components/Structure/Configuration/StructureManagement.js @@ -5,7 +5,7 @@ import ClassesSection from '@/components/Structure/Configuration/ClassesSection' import { ClassesProvider } from '@/context/ClassesContext'; import { BE_SCHOOL_SPECIALITIES_URL, BE_SCHOOL_TEACHERS_URL, BE_SCHOOL_SCHOOLCLASSES_URL } from '@/utils/Url'; -const StructureManagement = ({ specialities, setSpecialities, teachers, setTeachers, classes, setClasses, handleCreate, handleEdit, handleDelete }) => { +const StructureManagement = ({ specialities, setSpecialities, teachers, setTeachers, classes, setClasses, profiles, handleCreate, handleEdit, handleDelete }) => { return (
@@ -23,6 +23,7 @@ const StructureManagement = ({ specialities, setSpecialities, teachers, setTeach teachers={teachers} setTeachers={setTeachers} specialities={specialities} + profiles={profiles} handleCreate={(newData) => handleCreate(`${BE_SCHOOL_TEACHERS_URL}`, newData, setTeachers)} handleEdit={(id, updatedData) => handleEdit(`${BE_SCHOOL_TEACHERS_URL}`, id, updatedData, setTeachers)} handleDelete={(id) => handleDelete(`${BE_SCHOOL_TEACHERS_URL}`, id, setTeachers)} diff --git a/Front-End/src/components/Structure/Configuration/TeachersSection.js b/Front-End/src/components/Structure/Configuration/TeachersSection.js index eded255..36ba179 100644 --- a/Front-End/src/components/Structure/Configuration/TeachersSection.js +++ b/Front-End/src/components/Structure/Configuration/TeachersSection.js @@ -92,7 +92,7 @@ const SpecialitiesDropZone = ({ teacher, handleSpecialitiesChange, specialities, ); }; -const TeachersSection = ({ teachers, setTeachers, specialities, handleCreate, handleEdit, handleDelete }) => { +const TeachersSection = ({ teachers, setTeachers, specialities, profiles, handleCreate, handleEdit, handleDelete }) => { const csrfToken = useCsrfToken(); const [editingTeacher, setEditingTeacher] = useState(null); const [newTeacher, setNewTeacher] = useState(null); @@ -111,6 +111,27 @@ const TeachersSection = ({ teachers, setTeachers, specialities, handleCreate, ha const { selectedEstablishmentId } = useEstablishment(); + const handleEmailChange = (e) => { + const email = e.target.value; + + // Vérifier si l'email correspond à un profil existant + const existingProfile = profiles.find((profile) => profile.email === email); + + setFormData((prevData) => ({ + ...prevData, + associated_profile_email: email, + existingProfileId: existingProfile ? existingProfile.id : null, + })); + + if (newTeacher) { + setNewTeacher((prevData) => ({ + ...prevData, + associated_profile_email: email, + existingProfileId: existingProfile ? existingProfile.id : null, + })); + } + }; + const handleCancelConfirmation = () => { setConfirmPopupVisible(false); }; @@ -140,12 +161,23 @@ const TeachersSection = ({ teachers, setTeachers, specialities, handleCreate, ha const data = { last_name: formData.last_name, first_name: formData.first_name, - associated_profile_email: formData.associated_profile_email, - establishment: selectedEstablishmentId, - role_type: formData.role_type, - specialities: formData.specialities + profile_role_data: { + establishment: selectedEstablishmentId, + role_type: formData.role_type || 0, + is_active: true, + ...(formData.existingProfileId + ? { profile: formData.existingProfileId } + : { + profile_data: { + email: formData.associated_profile_email, + username: formData.associated_profile_email, + password: 'Provisoire01!', + }, + }), + }, + specialities: formData.specialities || [], }; - + handleCreate(data) .then((createdTeacher) => { setTeachers([createdTeacher, ...teachers]); @@ -166,30 +198,59 @@ const TeachersSection = ({ teachers, setTeachers, specialities, handleCreate, ha }; const handleUpdateTeacher = (id, updatedData) => { - if (updatedData.last_name && updatedData.first_name && updatedData.associated_profile_email_display) { + // Récupérer l'enseignant actuel à partir de la liste des enseignants + const currentTeacher = teachers.find((teacher) => teacher.id === id); + + // Vérifier si l'email correspond à un profil existant + const existingProfile = profiles.find((profile) => profile.email === currentTeacher.associated_profile_email); + + // Vérifier si l'email a été modifié + const isEmailModified = currentTeacher + ? currentTeacher.associated_profile_email !== updatedData.associated_profile_email + : true; + + // Mettre à jour existingProfileId en fonction de l'email + updatedData.existingProfileId = existingProfile ? existingProfile.id : null; + + if (updatedData.last_name && updatedData.first_name && updatedData.associated_profile_email) { const data = { - last_name: formData.last_name, - first_name: formData.first_name, - associated_profile_email: formData.associated_profile_email || formData.associated_profile_email_display, - establishment: selectedEstablishmentId, - role_type: formData.role_type, - specialities: formData.specialities + last_name: updatedData.last_name, + first_name: updatedData.first_name, + profile_role_data: { + id: updatedData.profile_role, + establishment: selectedEstablishmentId, + role_type: updatedData.role_type || 0, + is_active: true, + ...(isEmailModified + ? { + profile_data: { + id: updatedData.existingProfileId, + email: updatedData.associated_profile_email, + username: updatedData.associated_profile_email, + password: 'Provisoire01!', + }, + } + : { profile: updatedData.existingProfileId }), + }, + specialities: updatedData.specialities || [], }; + handleEdit(id, data) - .then((updatedTeacher) => { - setTeachers(prevTeachers => prevTeachers.map(teacher => teacher.id === id ? { ...teacher, ...updatedTeacher } : teacher)); - setEditingTeacher(null); - setFormData({}); - }) - .catch((error) => { - logger.error('Error:', error.message); - if (error.details) { + .then((updatedTeacher) => { + setTeachers((prevTeachers) => + prevTeachers.map((teacher) => (teacher.id === id ? { ...teacher, ...updatedTeacher } : teacher)) + ); + setEditingTeacher(null); + setFormData({}); + }) + .catch((error) => { + logger.error('Error:', error.message); + if (error.details) { logger.error('Form errors:', error.details); setLocalErrors(error.details); - } - }); - } - else { + } + }); + } else { setPopupMessage("Tous les champs doivent être remplis et valides"); setPopupVisible(true); } @@ -242,8 +303,8 @@ const TeachersSection = ({ teachers, setTeachers, specialities, handleCreate, ha setEditingTeacher(teacher.id); setFormData({ ...teacher, - associated_profile_email: teacher.associated_profile_email_display, - role_type: teacher.role_type_display, + associated_profile_email: teacher.associated_profile_email, + role_type: teacher.role_type, }); }; @@ -278,8 +339,8 @@ const TeachersSection = ({ teachers, setTeachers, specialities, handleCreate, ha @@ -327,7 +388,7 @@ const TeachersSection = ({ teachers, setTeachers, specialities, handleCreate, ha ); case 'EMAIL': - return teacher.associated_profile_email_display; + return teacher.associated_profile_email; case 'SPECIALITES': return (
@@ -337,9 +398,9 @@ const TeachersSection = ({ teachers, setTeachers, specialities, handleCreate, ha
); case 'ADMINISTRATEUR': - if (teacher.associated_profile_email_display) { - const badgeClass = teacher.role_type_display === 1 ? 'bg-red-100 text-red-600' : 'bg-blue-100 text-blue-600'; - const label = teacher.role_type_display === 1 ? 'OUI' : 'NON'; + if (teacher.associated_profile_email) { + const badgeClass = teacher.role_type === 1 ? 'bg-red-100 text-red-600' : 'bg-blue-100 text-blue-600'; + const label = teacher.role_type === 1 ? 'OUI' : 'NON'; return (