mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-01-28 23:43:22 +00:00
feat: Ajout de la possibilité de supprimer une association
guardian/student + ajout de la possibilité de créer un guardian pour un student + tri chrologique
This commit is contained in:
@ -27,6 +27,7 @@ class ProfileRole(models.Model):
|
|||||||
role_type = models.IntegerField(choices=RoleType.choices, default=RoleType.PROFIL_UNDEFINED)
|
role_type = models.IntegerField(choices=RoleType.choices, default=RoleType.PROFIL_UNDEFINED)
|
||||||
establishment = models.ForeignKey(Establishment, on_delete=models.CASCADE, related_name='profile_roles')
|
establishment = models.ForeignKey(Establishment, on_delete=models.CASCADE, related_name='profile_roles')
|
||||||
is_active = models.BooleanField(default=False)
|
is_active = models.BooleanField(default=False)
|
||||||
|
updated_date = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.profile.email} - {self.get_role_type_display()} - {self.establishment.name}"
|
return f"{self.profile.email} - {self.get_role_type_display()} - {self.establishment.name}"
|
||||||
@ -3,6 +3,9 @@ from Auth.models import Profile, ProfileRole
|
|||||||
from Establishment.models import Establishment
|
from Establishment.models import Establishment
|
||||||
from Subscriptions.models import Guardian, RegistrationForm
|
from Subscriptions.models import Guardian, RegistrationForm
|
||||||
from School.models import Teacher
|
from School.models import Teacher
|
||||||
|
from N3wtSchool import settings
|
||||||
|
from django.utils import timezone
|
||||||
|
import pytz
|
||||||
|
|
||||||
class ProfileSerializer(serializers.ModelSerializer):
|
class ProfileSerializer(serializers.ModelSerializer):
|
||||||
id = serializers.IntegerField(required=False)
|
id = serializers.IntegerField(required=False)
|
||||||
@ -69,10 +72,11 @@ class ProfileRoleSerializer(serializers.ModelSerializer):
|
|||||||
profile_data = ProfileSerializer(write_only=True, required=False)
|
profile_data = ProfileSerializer(write_only=True, required=False)
|
||||||
associated_profile_email = serializers.SerializerMethodField()
|
associated_profile_email = serializers.SerializerMethodField()
|
||||||
associated_person = serializers.SerializerMethodField()
|
associated_person = serializers.SerializerMethodField()
|
||||||
|
updated_date_formatted = serializers.SerializerMethodField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ProfileRole
|
model = ProfileRole
|
||||||
fields = ['id', 'role_type', 'establishment', 'is_active', 'profile', 'profile_data', 'associated_profile_email', 'associated_person']
|
fields = ['id', 'role_type', 'establishment', 'is_active', 'profile', 'profile_data', 'associated_profile_email', 'associated_person', 'updated_date_formatted']
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
profile_data = validated_data.pop('profile_data', None)
|
profile_data = validated_data.pop('profile_data', None)
|
||||||
@ -143,3 +147,10 @@ class ProfileRoleSerializer(serializers.ModelSerializer):
|
|||||||
"specialities": specialities_list
|
"specialities": specialities_list
|
||||||
}
|
}
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def get_updated_date_formatted(self, obj):
|
||||||
|
utc_time = timezone.localtime(obj.updated_date)
|
||||||
|
local_tz = pytz.timezone(settings.TZ_APPLI)
|
||||||
|
local_time = utc_time.astimezone(local_tz)
|
||||||
|
|
||||||
|
return local_time.strftime("%d-%m-%Y %H:%M")
|
||||||
@ -538,7 +538,7 @@ class ProfileRoleView(APIView):
|
|||||||
|
|
||||||
profiles_roles_List = bdd.getAllObjects(_objectName=ProfileRole)
|
profiles_roles_List = bdd.getAllObjects(_objectName=ProfileRole)
|
||||||
if profiles_roles_List:
|
if profiles_roles_List:
|
||||||
profiles_roles_List = profiles_roles_List.filter(establishment=establishment_id).distinct()
|
profiles_roles_List = profiles_roles_List.filter(establishment=establishment_id).distinct().order_by('-updated_date')
|
||||||
profile_roles_serializer = ProfileRoleSerializer(profiles_roles_List, many=True)
|
profile_roles_serializer = ProfileRoleSerializer(profiles_roles_List, many=True)
|
||||||
return JsonResponse(profile_roles_serializer.data, safe=False)
|
return JsonResponse(profile_roles_serializer.data, safe=False)
|
||||||
|
|
||||||
|
|||||||
@ -67,7 +67,6 @@ class TeacherSerializer(serializers.ModelSerializer):
|
|||||||
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.get('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(
|
||||||
|
|||||||
@ -26,7 +26,6 @@ def getStateMachineObjectState(etat):
|
|||||||
def updateStateMachine(rf, transition) :
|
def updateStateMachine(rf, transition) :
|
||||||
automateModel = load_config('Subscriptions/Configuration/automate.json')
|
automateModel = load_config('Subscriptions/Configuration/automate.json')
|
||||||
state_machine = getStateMachineObject(rf.status)
|
state_machine = getStateMachineObject(rf.status)
|
||||||
print(f'etat DI : {state_machine.state}')
|
|
||||||
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()
|
||||||
|
|||||||
@ -29,7 +29,7 @@ class Guardian(models.Model):
|
|||||||
address = models.CharField(max_length=200, default="", blank=True)
|
address = models.CharField(max_length=200, default="", blank=True)
|
||||||
phone = models.CharField(max_length=200, default="", blank=True)
|
phone = models.CharField(max_length=200, default="", blank=True)
|
||||||
profession = models.CharField(max_length=200, default="", blank=True)
|
profession = models.CharField(max_length=200, default="", blank=True)
|
||||||
profile_role = models.OneToOneField(ProfileRole, on_delete=models.CASCADE, related_name='guardian_profile', null=True, blank=True)
|
profile_role = models.OneToOneField(ProfileRole, on_delete=models.CASCADE, related_name='guardian_profile', blank=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.last_name + "_" + self.first_name
|
return self.last_name + "_" + self.first_name
|
||||||
|
|||||||
@ -5,6 +5,7 @@ from drf_yasg.utils import swagger_auto_schema
|
|||||||
from drf_yasg import openapi
|
from drf_yasg import openapi
|
||||||
|
|
||||||
from Subscriptions.models import Guardian, Student
|
from Subscriptions.models import Guardian, Student
|
||||||
|
from Auth.models import ProfileRole
|
||||||
from N3wtSchool import bdd
|
from N3wtSchool import bdd
|
||||||
|
|
||||||
class GuardianView(APIView):
|
class GuardianView(APIView):
|
||||||
@ -50,12 +51,34 @@ class DissociateGuardianView(APIView):
|
|||||||
# Supprimer la relation entre le student et le guardian
|
# Supprimer la relation entre le student et le guardian
|
||||||
student.guardians.remove(guardian)
|
student.guardians.remove(guardian)
|
||||||
|
|
||||||
|
if guardian.profile_role:
|
||||||
|
guardian.profile_role.save()
|
||||||
|
|
||||||
|
isGuardianDeleted = False
|
||||||
# Vérifier si le guardian n'est plus associé à aucun élève
|
# Vérifier si le guardian n'est plus associé à aucun élève
|
||||||
if guardian.student_set.count() == 0: # Utilise la relation ManyToMany inverse
|
if guardian.student_set.count() == 0: # Utilise la relation ManyToMany inverse
|
||||||
|
print(f'Le guardian {guardian} n\'est plus rattaché à aucun élève : on le supprime')
|
||||||
|
isGuardianDeleted = True
|
||||||
|
|
||||||
|
# Vérifier si le guardian a un ProfileRole associé
|
||||||
|
if guardian.profile_role:
|
||||||
|
print(f'Suppression du ProfileRole associé au guardian {guardian}')
|
||||||
|
guardian.profile_role.delete()
|
||||||
|
|
||||||
|
# Vérifier si le Profile n'a plus de ProfileRole associés
|
||||||
|
profile = guardian.profile_role.profile
|
||||||
|
if not ProfileRole.objects.filter(profile=profile).exists():
|
||||||
|
print(f'Le profile {profile} n\'a plus de rôle associé : on le supprime')
|
||||||
|
profile.delete()
|
||||||
|
|
||||||
|
# Supprimer le guardian
|
||||||
guardian.delete()
|
guardian.delete()
|
||||||
|
|
||||||
return JsonResponse(
|
return JsonResponse(
|
||||||
{"message": f"Le guardian {guardian.last_name} {guardian.first_name} a été dissocié de l'étudiant {student.last_name} {student.first_name}."},
|
{
|
||||||
|
"message": f"Le guardian {guardian.last_name} {guardian.first_name} a été dissocié de l'étudiant {student.last_name} {student.first_name}.",
|
||||||
|
"isGuardianDeleted": isGuardianDeleted
|
||||||
|
},
|
||||||
status=status.HTTP_200_OK
|
status=status.HTTP_200_OK
|
||||||
)
|
)
|
||||||
except Student.DoesNotExist:
|
except Student.DoesNotExist:
|
||||||
|
|||||||
@ -99,8 +99,7 @@ class RegisterFormView(APIView):
|
|||||||
registerForms_List = None
|
registerForms_List = None
|
||||||
|
|
||||||
if registerForms_List:
|
if registerForms_List:
|
||||||
print(f'filtrate sur lestablishment : {establishment_id}')
|
registerForms_List = registerForms_List.filter(establishment=establishment_id).order_by('-last_update')
|
||||||
registerForms_List = registerForms_List.filter(establishment=establishment_id)
|
|
||||||
|
|
||||||
if not registerForms_List:
|
if not registerForms_List:
|
||||||
return JsonResponse({'error': 'aucune donnée trouvée', 'count': 0}, safe=False)
|
return JsonResponse({'error': 'aucune donnée trouvée', 'count': 0}, safe=False)
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import ProfileDirectory from '@/components/ProfileDirectory';
|
|||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const [profileRoles, setProfileRoles] = useState([]);
|
const [profileRoles, setProfileRoles] = useState([]);
|
||||||
|
const [reloadFetch, setReloadFetch] = useState(false);
|
||||||
|
|
||||||
const csrfToken = useCsrfToken();
|
const csrfToken = useCsrfToken();
|
||||||
const { selectedEstablishmentId } = useEstablishment();
|
const { selectedEstablishmentId } = useEstablishment();
|
||||||
@ -19,7 +20,7 @@ export default function Page() {
|
|||||||
// Fetch data for profileRoles
|
// Fetch data for profileRoles
|
||||||
handleProfiles();
|
handleProfiles();
|
||||||
}
|
}
|
||||||
}, [selectedEstablishmentId]);
|
}, [selectedEstablishmentId, reloadFetch]);
|
||||||
|
|
||||||
const handleProfiles = () => {
|
const handleProfiles = () => {
|
||||||
fetchProfileRoles(selectedEstablishmentId)
|
fetchProfileRoles(selectedEstablishmentId)
|
||||||
@ -27,6 +28,7 @@ export default function Page() {
|
|||||||
setProfileRoles(data);
|
setProfileRoles(data);
|
||||||
})
|
})
|
||||||
.catch(error => logger.error('Error fetching profileRoles:', error));
|
.catch(error => logger.error('Error fetching profileRoles:', error));
|
||||||
|
setReloadFetch(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEdit = (profileRole) => {
|
const handleEdit = (profileRole) => {
|
||||||
@ -56,8 +58,37 @@ export default function Page() {
|
|||||||
|
|
||||||
const handleDissociate = (studentId, guardianId) => {
|
const handleDissociate = (studentId, guardianId) => {
|
||||||
return dissociateGuardian(studentId, guardianId)
|
return dissociateGuardian(studentId, guardianId)
|
||||||
.then(() => {
|
.then((response) => {
|
||||||
logger.debug("Guardian dissociated successfully:", guardianId);
|
logger.debug("Guardian dissociated successfully:", guardianId);
|
||||||
|
|
||||||
|
// Vérifier si le Guardian a été supprimé
|
||||||
|
const isGuardianDeleted = response?.isGuardianDeleted;
|
||||||
|
|
||||||
|
// Mettre à jour le modèle profileRoles
|
||||||
|
setProfileRoles(prevState =>
|
||||||
|
prevState.map(profileRole => {
|
||||||
|
if (profileRole.associated_person?.id === guardianId) {
|
||||||
|
if (isGuardianDeleted) {
|
||||||
|
// Si le Guardian est supprimé, retirer le profileRole
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
// Si le Guardian n'est pas supprimé, mettre à jour les élèves associés
|
||||||
|
const updatedStudents = profileRole.associated_person.students.filter(
|
||||||
|
student => student.id !== studentId
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
...profileRole,
|
||||||
|
associated_person: {
|
||||||
|
...profileRole.associated_person,
|
||||||
|
students: updatedStudents, // Mettre à jour les élèves associés
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setReloadFetch(true);
|
||||||
|
return profileRole; // Conserver les autres profileRoles
|
||||||
|
}).filter(Boolean) // Supprimer les entrées nulles
|
||||||
|
);
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
logger.error('Error dissociating guardian:', error);
|
logger.error('Error dissociating guardian:', error);
|
||||||
|
|||||||
@ -15,7 +15,6 @@ import { MoreVertical, Send, Edit, Trash2, FileText, CheckCircle, Plus } from '
|
|||||||
import Modal from '@/components/Modal';
|
import Modal from '@/components/Modal';
|
||||||
import InscriptionForm from '@/components/Inscription/InscriptionForm'
|
import InscriptionForm from '@/components/Inscription/InscriptionForm'
|
||||||
import AffectationClasseForm from '@/components/AffectationClasseForm'
|
import AffectationClasseForm from '@/components/AffectationClasseForm'
|
||||||
import { getSession } from 'next-auth/react';
|
|
||||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -87,6 +86,7 @@ export default function Page({ params: { locale } }) {
|
|||||||
const [tuitionFees, setTuitionFees] = useState([]);
|
const [tuitionFees, setTuitionFees] = useState([]);
|
||||||
const [groups, setGroups] = useState([]);
|
const [groups, setGroups] = useState([]);
|
||||||
const [profiles, setProfiles] = useState([]);
|
const [profiles, setProfiles] = useState([]);
|
||||||
|
const [isOpenAddGuardian, setIsOpenAddGuardian] = useState(false);
|
||||||
|
|
||||||
const csrfToken = useCsrfToken();
|
const csrfToken = useCsrfToken();
|
||||||
const { selectedEstablishmentId } = useEstablishment();
|
const { selectedEstablishmentId } = useEstablishment();
|
||||||
@ -100,6 +100,15 @@ export default function Page({ params: { locale } }) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleOpenAddGuardian = (eleveSelected) => {
|
||||||
|
setIsOpenAddGuardian(true);
|
||||||
|
setStudent(eleveSelected);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCloseAddGuardian = () => {
|
||||||
|
setIsOpenAddGuardian(false);
|
||||||
|
};
|
||||||
|
|
||||||
const openModalAssociationEleve = (eleveSelected) => {
|
const openModalAssociationEleve = (eleveSelected) => {
|
||||||
setIsOpenAffectationClasse(true);
|
setIsOpenAffectationClasse(true);
|
||||||
setStudent(eleveSelected);
|
setStudent(eleveSelected);
|
||||||
@ -480,10 +489,94 @@ useEffect(()=>{
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const updateRF = (updatedData) => {
|
||||||
|
logger.debug('updateRF updatedData:', updatedData);
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
student: {
|
||||||
|
guardians: updatedData.selectedGuardians.length !== 0
|
||||||
|
? updatedData.selectedGuardians.map(guardianId => ({ id: guardianId }))
|
||||||
|
: (() => {
|
||||||
|
if (updatedData.isExistingParentProfile) {
|
||||||
|
return [{
|
||||||
|
profile_role_data: {
|
||||||
|
establishment: selectedEstablishmentId,
|
||||||
|
role_type: 2,
|
||||||
|
is_active: false,
|
||||||
|
profile: updatedData.existingProfileId, // Associer au profil existant
|
||||||
|
},
|
||||||
|
last_name: updatedData.guardianLastName,
|
||||||
|
first_name: updatedData.guardianFirstName,
|
||||||
|
birth_date: updatedData.guardianBirthDate,
|
||||||
|
address: updatedData.guardianAddress,
|
||||||
|
phone: updatedData.guardianPhone,
|
||||||
|
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
|
||||||
|
}];
|
||||||
|
})(),
|
||||||
|
},
|
||||||
|
establishment: selectedEstablishmentId
|
||||||
|
};
|
||||||
|
|
||||||
|
editRegisterForm(student.id, data, csrfToken)
|
||||||
|
.then(data => {
|
||||||
|
// Mise à jour immédiate des données
|
||||||
|
setRegistrationFormsDataPending(prevState => [...(prevState || []), data]);
|
||||||
|
setTotalPending(prev => prev + 1);
|
||||||
|
if (updatedData.autoMail) {
|
||||||
|
sendConfirmRegisterForm(data.student.id, updatedData.studentLastName, updatedData.studentFirstName);
|
||||||
|
}
|
||||||
|
handleCloseAddGuardian();
|
||||||
|
// Forcer le rechargement complet des données
|
||||||
|
setReloadFetch(true);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
logger.error('Error during updating registration form:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{ name: t('studentName'), transform: (row) => row.student.last_name },
|
{ name: t('studentName'), transform: (row) => row.student.last_name },
|
||||||
{ name: t('studentFistName'), transform: (row) => row.student.first_name },
|
{ name: t('studentFistName'), transform: (row) => row.student.first_name },
|
||||||
{ name: t('mainContactMail'), transform: (row) => (row.student.guardians && row.student.guardians.length > 0) ? row.student.guardians[0].associated_profile_email : '' },
|
{
|
||||||
|
name: t('mainContactMail'),
|
||||||
|
transform: (row) => (
|
||||||
|
row.student.guardians && row.student.guardians.length > 0
|
||||||
|
? row.student.guardians[0].associated_profile_email
|
||||||
|
: (
|
||||||
|
<div className="flex justify-center h-full">
|
||||||
|
<button
|
||||||
|
className="flex items-center gap-2 text-blue-600 font-semibold hover:text-blue-800 transition duration-200 underline decoration-blue-600 hover:decoration-blue-800"
|
||||||
|
onClick={() => handleOpenAddGuardian(row.student)}
|
||||||
|
>
|
||||||
|
<span className="px-3 py-1 bg-blue-100 rounded-full hover:bg-blue-200 transition duration-200">
|
||||||
|
Ajouter un responsable
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
{ name: t('phone'), transform: (row) => formatPhoneNumber(row.student.guardians[0]?.phone) },
|
{ name: t('phone'), transform: (row) => formatPhoneNumber(row.student.guardians[0]?.phone) },
|
||||||
{ name: t('lastUpdateDate'), transform: (row) => row.formatted_last_update},
|
{ name: t('lastUpdateDate'), transform: (row) => row.formatted_last_update},
|
||||||
{ name: t('registrationFileStatus'), transform: (row) => (
|
{ name: t('registrationFileStatus'), transform: (row) => (
|
||||||
@ -742,13 +835,29 @@ const columnsSubscribed = [
|
|||||||
title="Affectation à une classe"
|
title="Affectation à une classe"
|
||||||
ContentComponent={() => (
|
ContentComponent={() => (
|
||||||
<AffectationClasseForm
|
<AffectationClasseForm
|
||||||
student={student}
|
students={students}
|
||||||
onSubmit={affectationClassFormSubmitHandler}
|
onSubmit={affectationClassFormSubmitHandler}
|
||||||
classes={classes}
|
classes={classes}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{isOpenAddGuardian && (
|
||||||
|
<Modal
|
||||||
|
isOpen={isOpenAddGuardian}
|
||||||
|
setIsOpen={setIsOpenAddGuardian}
|
||||||
|
title="Ajouter un responsable"
|
||||||
|
ContentComponent={() => (
|
||||||
|
<InscriptionForm
|
||||||
|
students={students}
|
||||||
|
profiles={profiles}
|
||||||
|
onSubmit={updateRF}
|
||||||
|
currentStep={2}
|
||||||
|
showOnlyStep2={true}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,8 +11,19 @@ import ProgressStep from '@/components/ProgressStep';
|
|||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
import Popup from '@/components/Popup';
|
import Popup from '@/components/Popup';
|
||||||
|
|
||||||
const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, registrationFees, tuitionFees, profiles, onSubmit, currentStep, groups }) => {
|
const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, registrationFees, tuitionFees, profiles, onSubmit, currentStep, groups, showOnlyStep2 = false }) => {
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState(() => {
|
||||||
|
if (showOnlyStep2) {
|
||||||
|
return {
|
||||||
|
guardianLastName: '',
|
||||||
|
guardianFirstName: '',
|
||||||
|
guardianEmail: '',
|
||||||
|
guardianPhone: '',
|
||||||
|
selectedGuardians: [],
|
||||||
|
responsableType: 'new',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
studentLastName: '',
|
studentLastName: '',
|
||||||
studentFirstName: '',
|
studentFirstName: '',
|
||||||
guardianLastName: '',
|
guardianLastName: '',
|
||||||
@ -27,6 +38,7 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r
|
|||||||
selectedTuitionDiscounts: [],
|
selectedTuitionDiscounts: [],
|
||||||
selectedTuitionFees: [],
|
selectedTuitionFees: [],
|
||||||
selectedFileGroup: null // Ajout du groupe de fichiers sélectionné
|
selectedFileGroup: null // Ajout du groupe de fichiers sélectionné
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const [step, setStep] = useState(currentStep || 1);
|
const [step, setStep] = useState(currentStep || 1);
|
||||||
@ -55,8 +67,8 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r
|
|||||||
formData.selectedGuardians.length > 0 ||
|
formData.selectedGuardians.length > 0 ||
|
||||||
(!formData.emailError && formData.guardianEmail.length > 0 && filteredStudents.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;
|
||||||
const isStep5Valid = formData.selectedFileGroup !== null;
|
const isStep5Valid = formData.selectedFileGroup !== null;
|
||||||
const isStep6Valid = isStep1Valid && isStep2Valid && isStep3Valid && isStep4Valid && isStep5Valid;
|
const isStep6Valid = isStep1Valid && isStep2Valid && isStep3Valid && isStep4Valid && isStep5Valid;
|
||||||
|
|
||||||
@ -80,12 +92,14 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!showOnlyStep2) {
|
||||||
// Calcul du montant total des frais d'inscription lors de l'initialisation
|
// Calcul du montant total des frais d'inscription lors de l'initialisation
|
||||||
const initialTotalRegistrationAmount = calculateFinalRegistrationAmount(
|
const initialTotalRegistrationAmount = calculateFinalRegistrationAmount(
|
||||||
registrationFees.map(fee => fee.id),
|
registrationFees.map(fee => fee.id),
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
setTotalRegistrationAmount(initialTotalRegistrationAmount);
|
setTotalRegistrationAmount(initialTotalRegistrationAmount);
|
||||||
|
}
|
||||||
|
|
||||||
}, [registrationDiscounts, registrationFees]);
|
}, [registrationDiscounts, registrationFees]);
|
||||||
|
|
||||||
@ -123,15 +137,15 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (step < steps.length) {
|
if (!showOnlyStep2 && step < steps.length) {
|
||||||
setStep(step + 1);
|
setStep(step + 1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const prevStep = () => {
|
const prevStep = () => {
|
||||||
if (step > 1) {
|
if (!showOnlyStep2 && step > 1) {
|
||||||
setStep(step - 1);
|
setStep(step - 1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEleveSelection = (student) => {
|
const handleEleveSelection = (student) => {
|
||||||
@ -270,6 +284,7 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4 mt-6">
|
<div className="space-y-4 mt-6">
|
||||||
|
{!showOnlyStep2 && (
|
||||||
<ProgressStep
|
<ProgressStep
|
||||||
steps={steps}
|
steps={steps}
|
||||||
stepTitles={stepTitles}
|
stepTitles={stepTitles}
|
||||||
@ -277,6 +292,7 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r
|
|||||||
setStep={setStep}
|
setStep={setStep}
|
||||||
isStepValid={isStepValid}
|
isStepValid={isStepValid}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{step === 1 && (
|
{step === 1 && (
|
||||||
<div className="mt-6">
|
<div className="mt-6">
|
||||||
@ -308,7 +324,7 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r
|
|||||||
name="guardianLastName"
|
name="guardianLastName"
|
||||||
type="text"
|
type="text"
|
||||||
IconItem={User}
|
IconItem={User}
|
||||||
placeholder="Nom du responsable (optionnel)"
|
placeholder="Nom du responsable"
|
||||||
value={formData.guardianLastName}
|
value={formData.guardianLastName}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
className="w-full mt-4"
|
className="w-full mt-4"
|
||||||
@ -317,7 +333,7 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r
|
|||||||
name="guardianFirstName"
|
name="guardianFirstName"
|
||||||
type="text"
|
type="text"
|
||||||
IconItem={User}
|
IconItem={User}
|
||||||
placeholder="Prénom du responsable (optionnel)"
|
placeholder="Prénom du responsable"
|
||||||
value={formData.guardianFirstName}
|
value={formData.guardianFirstName}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
className="w-full mt-4"
|
className="w-full mt-4"
|
||||||
@ -663,44 +679,83 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex justify-end mt-4 space-x-4">
|
<div className="flex justify-end mt-4 space-x-4">
|
||||||
{step > 1 && (
|
{showOnlyStep2 ? (
|
||||||
<Button text="Précédent"
|
<>
|
||||||
onClick={prevStep}
|
<Button
|
||||||
className="px-4 py-2 bg-gray-300 text-gray-700 rounded-md shadow-sm hover:bg-gray-400 focus:outline-none"
|
text="Valider"
|
||||||
secondary
|
onClick={submit}
|
||||||
name="Previous" />
|
className={`px-4 py-2 rounded-md shadow-sm focus:outline-none ${
|
||||||
)}
|
(
|
||||||
{step < steps.length ? (
|
(step === 1 && !isStep1Valid) ||
|
||||||
<Button text="Suivant"
|
(step === 2 && !isStep2Valid) ||
|
||||||
onClick={nextStep}
|
(step === 3 && !isStep3Valid) ||
|
||||||
className={`px-4 py-2 rounded-md shadow-sm focus:outline-none ${
|
(step === 4 && !isStep4Valid) ||
|
||||||
(
|
(step === 5 && !isStep5Valid)
|
||||||
(step === 1 && !isStep1Valid) ||
|
)
|
||||||
(step === 2 && !isStep2Valid) ||
|
? "bg-gray-300 text-gray-700 cursor-not-allowed"
|
||||||
(step === 3 && !isStep3Valid) ||
|
: "bg-emerald-500 text-white hover:bg-emerald-600"
|
||||||
(step === 4 && !isStep4Valid) ||
|
}`}
|
||||||
(step === 5 && !isStep5Valid)
|
disabled={
|
||||||
)
|
(
|
||||||
? "bg-gray-300 text-gray-700 cursor-not-allowed"
|
(step === 1 && !isStep1Valid) ||
|
||||||
: "bg-emerald-500 text-white hover:bg-emerald-600"
|
(step === 2 && !isStep2Valid) ||
|
||||||
}`}
|
(step === 3 && !isStep3Valid) ||
|
||||||
disabled={
|
(step === 4 && !isStep4Valid) ||
|
||||||
(
|
(step === 5 && !isStep5Valid)
|
||||||
(step === 1 && !isStep1Valid) ||
|
)
|
||||||
(step === 2 && !isStep2Valid) ||
|
}
|
||||||
(step === 3 && !isStep3Valid) ||
|
primary
|
||||||
(step === 4 && !isStep4Valid) ||
|
name="Validate"
|
||||||
(step === 5 && !isStep5Valid)
|
/>
|
||||||
)
|
</>
|
||||||
}
|
|
||||||
primary
|
|
||||||
name="Next" />
|
|
||||||
) : (
|
) : (
|
||||||
<Button text="Valider"
|
<>
|
||||||
onClick={submit}
|
{step > 1 && (
|
||||||
className="px-4 py-2 bg-emerald-500 text-white rounded-md shadow-sm hover:bg-emerald-600 focus:outline-none"
|
<Button
|
||||||
primary
|
text="Précédent"
|
||||||
name="Create" />
|
onClick={prevStep}
|
||||||
|
className="px-4 py-2 bg-gray-300 text-gray-700 rounded-md shadow-sm hover:bg-gray-400 focus:outline-none"
|
||||||
|
secondary
|
||||||
|
name="Previous"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{step < steps.length ? (
|
||||||
|
<Button
|
||||||
|
text="Suivant"
|
||||||
|
onClick={nextStep}
|
||||||
|
className={`px-4 py-2 rounded-md shadow-sm focus:outline-none ${
|
||||||
|
(
|
||||||
|
(step === 1 && !isStep1Valid) ||
|
||||||
|
(step === 2 && !isStep2Valid) ||
|
||||||
|
(step === 3 && !isStep3Valid) ||
|
||||||
|
(step === 4 && !isStep4Valid) ||
|
||||||
|
(step === 5 && !isStep5Valid)
|
||||||
|
)
|
||||||
|
? "bg-gray-300 text-gray-700 cursor-not-allowed"
|
||||||
|
: "bg-emerald-500 text-white hover:bg-emerald-600"
|
||||||
|
}`}
|
||||||
|
disabled={
|
||||||
|
(
|
||||||
|
(step === 1 && !isStep1Valid) ||
|
||||||
|
(step === 2 && !isStep2Valid) ||
|
||||||
|
(step === 3 && !isStep3Valid) ||
|
||||||
|
(step === 4 && !isStep4Valid) ||
|
||||||
|
(step === 5 && !isStep5Valid)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
primary
|
||||||
|
name="Next"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
text="Valider"
|
||||||
|
onClick={submit}
|
||||||
|
className="px-4 py-2 bg-emerald-500 text-white rounded-md shadow-sm hover:bg-emerald-600 focus:outline-none"
|
||||||
|
primary
|
||||||
|
name="Create"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -107,6 +107,7 @@ const ProfileDirectory = ({ profileRoles, handleActivateProfile, handleDeletePro
|
|||||||
|
|
||||||
const parentColumns = [
|
const parentColumns = [
|
||||||
{ name: 'Identifiant', transform: (row) => row.associated_profile_email },
|
{ name: 'Identifiant', transform: (row) => row.associated_profile_email },
|
||||||
|
{ name: 'Mise à jour', transform: (row) => row.updated_date_formatted },
|
||||||
{ name: 'Rôle', transform: (row) => (
|
{ name: 'Rôle', transform: (row) => (
|
||||||
<span className={`px-2 py-1 rounded-full font-bold ${roleTypeToBadgeClass(row.role_type)}`}>
|
<span className={`px-2 py-1 rounded-full font-bold ${roleTypeToBadgeClass(row.role_type)}`}>
|
||||||
{roleTypeToLabel(row.role_type)}
|
{roleTypeToLabel(row.role_type)}
|
||||||
@ -120,40 +121,44 @@ const ProfileDirectory = ({ profileRoles, handleActivateProfile, handleDeletePro
|
|||||||
<span>{row.associated_person?.guardian_name}</span>
|
<span>{row.associated_person?.guardian_name}</span>
|
||||||
{row.associated_person && (
|
{row.associated_person && (
|
||||||
<div
|
<div
|
||||||
className="relative group"
|
className="relative group"
|
||||||
onMouseEnter={() => handleTooltipVisibility(row.id)} // Afficher la tooltip pour cette ligne
|
onMouseEnter={() => handleTooltipVisibility(row.id)} // Afficher la tooltip pour cette ligne
|
||||||
onMouseLeave={handleTooltipHide} // Cacher la tooltip
|
onMouseLeave={handleTooltipHide} // Cacher la tooltip
|
||||||
>
|
>
|
||||||
<button className="text-blue-500 hover:text-blue-700">
|
<button className="relative text-blue-500 hover:text-blue-700 flex items-center justify-center">
|
||||||
<Info className="w-5 h-5" />
|
<div className="w-6 h-6 bg-blue-100 text-blue-700 rounded-full flex items-center justify-center font-bold">
|
||||||
</button>
|
{row.associated_person?.students?.length || 0}
|
||||||
{visibleTooltipId === row.id && ( // Afficher uniquement si l'ID correspond
|
</div>
|
||||||
<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">
|
</button>
|
||||||
<div className="mb-2">
|
{visibleTooltipId === row.id && ( // Afficher uniquement si l'ID correspond
|
||||||
<strong>Elève(s) associé(s):</strong>
|
<div
|
||||||
<div className="flex flex-col justify-center space-y-2 mt-4">
|
className="fixed z-50 w-96 p-4 bg-white border border-gray-200 rounded shadow-lg -translate-x-1/2"
|
||||||
{row.associated_person?.students?.map(student => (
|
>
|
||||||
<div key={student.student_name} className="flex justify-between items-center">
|
<div className="mb-2">
|
||||||
<span className="px-2 py-1 rounded-full text-gray-800 whitespace-nowrap inline-block min-w-0 max-w-fit">
|
<strong>Elève(s) associé(s):</strong>
|
||||||
{student.student_name}
|
<div className="flex flex-col justify-center space-y-2 mt-4">
|
||||||
</span>
|
{row.associated_person?.students?.map(student => (
|
||||||
<div className="flex items-center space-x-2">
|
<div key={student.student_name} className="flex justify-between items-center">
|
||||||
<StatusLabel status={student.registration_status} showDropdown={false} />
|
<span className="px-2 py-1 rounded-full text-gray-800 whitespace-nowrap inline-block min-w-0 max-w-fit">
|
||||||
<button
|
{student.student_name}
|
||||||
className="text-red-500 hover:text-red-700 flex items-center space-x-1"
|
</span>
|
||||||
onClick={() => handleConfirmDissociateGuardian(row, student)}
|
<div className="flex items-center space-x-2">
|
||||||
>
|
<StatusLabel status={student.registration_status} showDropdown={false} />
|
||||||
<XCircle className="w-5 h-5" />
|
<button
|
||||||
<span className="text-sm">Dissocier</span>
|
className="text-red-500 hover:text-red-700 flex items-center space-x-1"
|
||||||
</button>
|
onClick={() => handleConfirmDissociateGuardian(row, student)}
|
||||||
</div>
|
>
|
||||||
|
<XCircle className="w-5 h-5" />
|
||||||
|
<span className="text-sm">Dissocier</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
))}
|
</div>
|
||||||
</div>
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
</div>
|
)}
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@ -183,6 +188,7 @@ const ProfileDirectory = ({ profileRoles, handleActivateProfile, handleDeletePro
|
|||||||
|
|
||||||
const schoolAdminColumns = [
|
const schoolAdminColumns = [
|
||||||
{ name: 'Identifiant', transform: (row) => row.associated_profile_email },
|
{ name: 'Identifiant', transform: (row) => row.associated_profile_email },
|
||||||
|
{ name: 'Mise à jour', transform: (row) => row.updated_date_formatted },
|
||||||
{ name: 'Rôle', transform: (row) => (
|
{ name: 'Rôle', transform: (row) => (
|
||||||
<span className={`px-2 py-1 rounded-full font-bold ${roleTypeToBadgeClass(row.role_type)}`}>
|
<span className={`px-2 py-1 rounded-full font-bold ${roleTypeToBadgeClass(row.role_type)}`}>
|
||||||
{roleTypeToLabel(row.role_type)}
|
{roleTypeToLabel(row.role_type)}
|
||||||
|
|||||||
Reference in New Issue
Block a user