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)
|
||||
establishment = models.ForeignKey(Establishment, on_delete=models.CASCADE, related_name='profile_roles')
|
||||
is_active = models.BooleanField(default=False)
|
||||
updated_date = models.DateTimeField(auto_now=True)
|
||||
|
||||
def __str__(self):
|
||||
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 Subscriptions.models import Guardian, RegistrationForm
|
||||
from School.models import Teacher
|
||||
from N3wtSchool import settings
|
||||
from django.utils import timezone
|
||||
import pytz
|
||||
|
||||
class ProfileSerializer(serializers.ModelSerializer):
|
||||
id = serializers.IntegerField(required=False)
|
||||
@ -69,10 +72,11 @@ class ProfileRoleSerializer(serializers.ModelSerializer):
|
||||
profile_data = ProfileSerializer(write_only=True, required=False)
|
||||
associated_profile_email = serializers.SerializerMethodField()
|
||||
associated_person = serializers.SerializerMethodField()
|
||||
updated_date_formatted = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
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):
|
||||
profile_data = validated_data.pop('profile_data', None)
|
||||
@ -143,3 +147,10 @@ class ProfileRoleSerializer(serializers.ModelSerializer):
|
||||
"specialities": specialities_list
|
||||
}
|
||||
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)
|
||||
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)
|
||||
return JsonResponse(profile_roles_serializer.data, safe=False)
|
||||
|
||||
|
||||
@ -67,7 +67,6 @@ class TeacherSerializer(serializers.ModelSerializer):
|
||||
specialities_data = validated_data.pop('specialities', None)
|
||||
associated_profile_email = validated_data.pop('associated_profile_email')
|
||||
establishment_id = validated_data.get('establishment')
|
||||
print(f'debug : {validated_data}')
|
||||
role_type = validated_data.pop('role_type')
|
||||
|
||||
profile, created = Profile.objects.get_or_create(
|
||||
|
||||
@ -26,7 +26,6 @@ def getStateMachineObjectState(etat):
|
||||
def updateStateMachine(rf, transition) :
|
||||
automateModel = load_config('Subscriptions/Configuration/automate.json')
|
||||
state_machine = getStateMachineObject(rf.status)
|
||||
print(f'etat DI : {state_machine.state}')
|
||||
if state_machine.trigger(transition, automateModel):
|
||||
rf.status = state_machine.state
|
||||
rf.save()
|
||||
|
||||
@ -29,7 +29,7 @@ class Guardian(models.Model):
|
||||
address = 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)
|
||||
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):
|
||||
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 Subscriptions.models import Guardian, Student
|
||||
from Auth.models import ProfileRole
|
||||
from N3wtSchool import bdd
|
||||
|
||||
class GuardianView(APIView):
|
||||
@ -50,12 +51,34 @@ class DissociateGuardianView(APIView):
|
||||
# Supprimer la relation entre le student et le 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
|
||||
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()
|
||||
|
||||
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
|
||||
)
|
||||
except Student.DoesNotExist:
|
||||
|
||||
@ -99,8 +99,7 @@ class RegisterFormView(APIView):
|
||||
registerForms_List = None
|
||||
|
||||
if registerForms_List:
|
||||
print(f'filtrate sur lestablishment : {establishment_id}')
|
||||
registerForms_List = registerForms_List.filter(establishment=establishment_id)
|
||||
registerForms_List = registerForms_List.filter(establishment=establishment_id).order_by('-last_update')
|
||||
|
||||
if not registerForms_List:
|
||||
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() {
|
||||
const [profileRoles, setProfileRoles] = useState([]);
|
||||
const [reloadFetch, setReloadFetch] = useState(false);
|
||||
|
||||
const csrfToken = useCsrfToken();
|
||||
const { selectedEstablishmentId } = useEstablishment();
|
||||
@ -19,7 +20,7 @@ export default function Page() {
|
||||
// Fetch data for profileRoles
|
||||
handleProfiles();
|
||||
}
|
||||
}, [selectedEstablishmentId]);
|
||||
}, [selectedEstablishmentId, reloadFetch]);
|
||||
|
||||
const handleProfiles = () => {
|
||||
fetchProfileRoles(selectedEstablishmentId)
|
||||
@ -27,6 +28,7 @@ export default function Page() {
|
||||
setProfileRoles(data);
|
||||
})
|
||||
.catch(error => logger.error('Error fetching profileRoles:', error));
|
||||
setReloadFetch(false);
|
||||
};
|
||||
|
||||
const handleEdit = (profileRole) => {
|
||||
@ -56,8 +58,37 @@ export default function Page() {
|
||||
|
||||
const handleDissociate = (studentId, guardianId) => {
|
||||
return dissociateGuardian(studentId, guardianId)
|
||||
.then(() => {
|
||||
.then((response) => {
|
||||
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 => {
|
||||
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 InscriptionForm from '@/components/Inscription/InscriptionForm'
|
||||
import AffectationClasseForm from '@/components/AffectationClasseForm'
|
||||
import { getSession } from 'next-auth/react';
|
||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||
|
||||
import {
|
||||
@ -87,6 +86,7 @@ export default function Page({ params: { locale } }) {
|
||||
const [tuitionFees, setTuitionFees] = useState([]);
|
||||
const [groups, setGroups] = useState([]);
|
||||
const [profiles, setProfiles] = useState([]);
|
||||
const [isOpenAddGuardian, setIsOpenAddGuardian] = useState(false);
|
||||
|
||||
const csrfToken = useCsrfToken();
|
||||
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) => {
|
||||
setIsOpenAffectationClasse(true);
|
||||
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 = [
|
||||
{ name: t('studentName'), transform: (row) => row.student.last_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('lastUpdateDate'), transform: (row) => row.formatted_last_update},
|
||||
{ name: t('registrationFileStatus'), transform: (row) => (
|
||||
@ -742,13 +835,29 @@ const columnsSubscribed = [
|
||||
title="Affectation à une classe"
|
||||
ContentComponent={() => (
|
||||
<AffectationClasseForm
|
||||
student={student}
|
||||
students={students}
|
||||
onSubmit={affectationClassFormSubmitHandler}
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
@ -11,8 +11,19 @@ import ProgressStep from '@/components/ProgressStep';
|
||||
import logger from '@/utils/logger';
|
||||
import Popup from '@/components/Popup';
|
||||
|
||||
const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, registrationFees, tuitionFees, profiles, onSubmit, currentStep, groups }) => {
|
||||
const [formData, setFormData] = useState({
|
||||
const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, registrationFees, tuitionFees, profiles, onSubmit, currentStep, groups, showOnlyStep2 = false }) => {
|
||||
const [formData, setFormData] = useState(() => {
|
||||
if (showOnlyStep2) {
|
||||
return {
|
||||
guardianLastName: '',
|
||||
guardianFirstName: '',
|
||||
guardianEmail: '',
|
||||
guardianPhone: '',
|
||||
selectedGuardians: [],
|
||||
responsableType: 'new',
|
||||
};
|
||||
}
|
||||
return {
|
||||
studentLastName: '',
|
||||
studentFirstName: '',
|
||||
guardianLastName: '',
|
||||
@ -27,6 +38,7 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r
|
||||
selectedTuitionDiscounts: [],
|
||||
selectedTuitionFees: [],
|
||||
selectedFileGroup: null // Ajout du groupe de fichiers sélectionné
|
||||
};
|
||||
});
|
||||
|
||||
const [step, setStep] = useState(currentStep || 1);
|
||||
@ -55,8 +67,8 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r
|
||||
formData.selectedGuardians.length > 0 ||
|
||||
(!formData.emailError && formData.guardianEmail.length > 0 && filteredStudents.length === 0)
|
||||
);
|
||||
const isStep3Valid = formData.selectedRegistrationFees.length > 0;
|
||||
const isStep4Valid = formData.selectedTuitionFees.length > 0;
|
||||
const isStep3Valid = formData.selectedRegistrationFees?.length > 0;
|
||||
const isStep4Valid = formData.selectedTuitionFees?.length > 0;
|
||||
const isStep5Valid = formData.selectedFileGroup !== null;
|
||||
const isStep6Valid = isStep1Valid && isStep2Valid && isStep3Valid && isStep4Valid && isStep5Valid;
|
||||
|
||||
@ -80,12 +92,14 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!showOnlyStep2) {
|
||||
// Calcul du montant total des frais d'inscription lors de l'initialisation
|
||||
const initialTotalRegistrationAmount = calculateFinalRegistrationAmount(
|
||||
registrationFees.map(fee => fee.id),
|
||||
[]
|
||||
);
|
||||
setTotalRegistrationAmount(initialTotalRegistrationAmount);
|
||||
}
|
||||
|
||||
}, [registrationDiscounts, registrationFees]);
|
||||
|
||||
@ -123,13 +137,13 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r
|
||||
}
|
||||
}
|
||||
|
||||
if (step < steps.length) {
|
||||
if (!showOnlyStep2 && step < steps.length) {
|
||||
setStep(step + 1);
|
||||
}
|
||||
};
|
||||
|
||||
const prevStep = () => {
|
||||
if (step > 1) {
|
||||
if (!showOnlyStep2 && step > 1) {
|
||||
setStep(step - 1);
|
||||
}
|
||||
};
|
||||
@ -270,6 +284,7 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r
|
||||
|
||||
return (
|
||||
<div className="space-y-4 mt-6">
|
||||
{!showOnlyStep2 && (
|
||||
<ProgressStep
|
||||
steps={steps}
|
||||
stepTitles={stepTitles}
|
||||
@ -277,6 +292,7 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r
|
||||
setStep={setStep}
|
||||
isStepValid={isStepValid}
|
||||
/>
|
||||
)}
|
||||
|
||||
{step === 1 && (
|
||||
<div className="mt-6">
|
||||
@ -308,7 +324,7 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r
|
||||
name="guardianLastName"
|
||||
type="text"
|
||||
IconItem={User}
|
||||
placeholder="Nom du responsable (optionnel)"
|
||||
placeholder="Nom du responsable"
|
||||
value={formData.guardianLastName}
|
||||
onChange={handleChange}
|
||||
className="w-full mt-4"
|
||||
@ -317,7 +333,7 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r
|
||||
name="guardianFirstName"
|
||||
type="text"
|
||||
IconItem={User}
|
||||
placeholder="Prénom du responsable (optionnel)"
|
||||
placeholder="Prénom du responsable"
|
||||
value={formData.guardianFirstName}
|
||||
onChange={handleChange}
|
||||
className="w-full mt-4"
|
||||
@ -663,15 +679,49 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r
|
||||
)}
|
||||
|
||||
<div className="flex justify-end mt-4 space-x-4">
|
||||
{showOnlyStep2 ? (
|
||||
<>
|
||||
<Button
|
||||
text="Valider"
|
||||
onClick={submit}
|
||||
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="Validate"
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{step > 1 && (
|
||||
<Button text="Précédent"
|
||||
<Button
|
||||
text="Précédent"
|
||||
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" />
|
||||
name="Previous"
|
||||
/>
|
||||
)}
|
||||
{step < steps.length ? (
|
||||
<Button text="Suivant"
|
||||
<Button
|
||||
text="Suivant"
|
||||
onClick={nextStep}
|
||||
className={`px-4 py-2 rounded-md shadow-sm focus:outline-none ${
|
||||
(
|
||||
@ -694,13 +744,18 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r
|
||||
)
|
||||
}
|
||||
primary
|
||||
name="Next" />
|
||||
name="Next"
|
||||
/>
|
||||
) : (
|
||||
<Button text="Valider"
|
||||
<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" />
|
||||
name="Create"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
@ -107,6 +107,7 @@ const ProfileDirectory = ({ profileRoles, handleActivateProfile, handleDeletePro
|
||||
|
||||
const parentColumns = [
|
||||
{ name: 'Identifiant', transform: (row) => row.associated_profile_email },
|
||||
{ name: 'Mise à jour', transform: (row) => row.updated_date_formatted },
|
||||
{ name: 'Rôle', transform: (row) => (
|
||||
<span className={`px-2 py-1 rounded-full font-bold ${roleTypeToBadgeClass(row.role_type)}`}>
|
||||
{roleTypeToLabel(row.role_type)}
|
||||
@ -124,11 +125,15 @@ const ProfileDirectory = ({ profileRoles, handleActivateProfile, handleDeletePro
|
||||
onMouseEnter={() => handleTooltipVisibility(row.id)} // Afficher la tooltip pour cette ligne
|
||||
onMouseLeave={handleTooltipHide} // Cacher la tooltip
|
||||
>
|
||||
<button className="text-blue-500 hover:text-blue-700">
|
||||
<Info className="w-5 h-5" />
|
||||
<button className="relative text-blue-500 hover:text-blue-700 flex items-center justify-center">
|
||||
<div className="w-6 h-6 bg-blue-100 text-blue-700 rounded-full flex items-center justify-center font-bold">
|
||||
{row.associated_person?.students?.length || 0}
|
||||
</div>
|
||||
</button>
|
||||
{visibleTooltipId === row.id && ( // Afficher uniquement si l'ID correspond
|
||||
<div className="absolute z-50 w-96 p-4 bg-white border border-gray-200 rounded shadow-lg left-0 -translate-x-1/2 top-full">
|
||||
<div
|
||||
className="fixed z-50 w-96 p-4 bg-white border border-gray-200 rounded shadow-lg -translate-x-1/2"
|
||||
>
|
||||
<div className="mb-2">
|
||||
<strong>Elève(s) associé(s):</strong>
|
||||
<div className="flex flex-col justify-center space-y-2 mt-4">
|
||||
@ -183,6 +188,7 @@ const ProfileDirectory = ({ profileRoles, handleActivateProfile, handleDeletePro
|
||||
|
||||
const schoolAdminColumns = [
|
||||
{ name: 'Identifiant', transform: (row) => row.associated_profile_email },
|
||||
{ name: 'Mise à jour', transform: (row) => row.updated_date_formatted },
|
||||
{ name: 'Rôle', transform: (row) => (
|
||||
<span className={`px-2 py-1 rounded-full font-bold ${roleTypeToBadgeClass(row.role_type)}`}>
|
||||
{roleTypeToLabel(row.role_type)}
|
||||
|
||||
Reference in New Issue
Block a user