feat: Ajout de la fratrie / Gestion des index de fratrie / Gestion des

required
This commit is contained in:
N3WT DE COMPET
2025-05-03 14:45:10 +02:00
parent 9374b001c9
commit 2ab1684791
9 changed files with 141 additions and 106 deletions

View File

@ -40,7 +40,6 @@ class Sibling(models.Model):
""" """
Représente un frère ou une sœur dun élève. Représente un frère ou une sœur dun élève.
""" """
id = models.AutoField(primary_key=True)
last_name = models.CharField(max_length=200, default="") last_name = models.CharField(max_length=200, default="")
first_name = models.CharField(max_length=200, default="") first_name = models.CharField(max_length=200, default="")
birth_date = models.DateField(null=True, blank=True) birth_date = models.DateField(null=True, blank=True)

View File

@ -231,7 +231,6 @@ class RegisterFormWithIdView(APIView):
""" """
studentForm_data = request.data.get('data', '{}') studentForm_data = request.data.get('data', '{}')
print(f'studentForm_data : {studentForm_data}')
try: try:
data = json.loads(studentForm_data) data = json.loads(studentForm_data)
except json.JSONDecodeError: except json.JSONDecodeError:

View File

@ -5,6 +5,7 @@ export default function InputText({
value, value,
onChange, onChange,
errorMsg, errorMsg,
errorLocalMsg,
placeholder, placeholder,
className, className,
required, required,
@ -20,7 +21,11 @@ export default function InputText({
{required && <span className="text-red-500 ml-1">*</span>} {required && <span className="text-red-500 ml-1">*</span>}
</label> </label>
<div <div
className={`mt-1 flex items-center border border-gray-200 rounded-md ${errorMsg ? 'border-red-500' : ''} hover:border-gray-400 focus-within:border-gray-500`} className={`mt-1 flex items-center border rounded-md ${
errorMsg || errorLocalMsg
? 'border-red-500 hover:border-red-700'
: 'border-gray-200 hover:border-gray-400'
} ${!errorMsg && !errorLocalMsg ? 'focus-within:border-gray-500' : ''}`}
> >
<input <input
type={type} type={type}

View File

@ -49,6 +49,7 @@ export default function InscriptionFormShared({
photo: null, photo: null,
last_name: '', last_name: '',
first_name: '', first_name: '',
gender: '',
address: '', address: '',
birth_date: '', birth_date: '',
birth_place: '', birth_place: '',
@ -370,14 +371,18 @@ export default function InscriptionFormShared({
// Vérifier si le mode de paiement sélectionné est un prélèvement SEPA // Vérifier si le mode de paiement sélectionné est un prélèvement SEPA
const isSepaPayment = const isSepaPayment =
formData.registration_payment === '1' || formData.tuition_payment === '1'; formData.registration_payment === 1 || formData.tuition_payment === 1;
// Préparer les données JSON // Préparer les données JSON
const jsonData = { const jsonData = {
student: { student: {
...formData, ...formData,
guardians: guardians, guardians: guardians,
siblings: siblings, siblings: siblings.map(({ id, ...rest }) =>
id && typeof id === 'string' && id.startsWith('temp-')
? rest
: { id, ...rest }
), // Supprimer les IDs temporaires
}, },
establishment: selectedEstablishmentId, establishment: selectedEstablishmentId,
status: isSepaPayment ? 8 : 3, status: isSepaPayment ? 8 : 3,
@ -398,6 +403,8 @@ export default function InscriptionFormShared({
formDataToSend.append('photo', formData.photo); formDataToSend.append('photo', formData.photo);
} }
console.log('submit : ', jsonData);
// Appeler la fonction onSubmit avec les données FormData // Appeler la fonction onSubmit avec les données FormData
onSubmit(formDataToSend); onSubmit(formDataToSend);
}; };

View File

@ -82,18 +82,18 @@ export default function PaymentMethodSelector({
label="Mode de Paiement" label="Mode de Paiement"
placeHolder="Sélectionner un mode de paiement" placeHolder="Sélectionner un mode de paiement"
selected={formData.registration_payment} selected={formData.registration_payment}
callback={(e) => onChange('registration_payment', e.target.value)} callback={(e) =>
onChange('registration_payment', parseInt(e.target.value, 10))
}
choices={registrationPaymentModes.map((mode) => ({ choices={registrationPaymentModes.map((mode) => ({
value: mode.mode, value: mode.mode,
label: label:
paymentModesOptions.find((option) => option.id === mode.mode) paymentModesOptions.find((option) => option.id === mode.mode)
?.name || 'Mode inconnu', ?.name || 'Mode inconnu',
}))} }))}
errorMsg={getError('registration_payment')}
errorLocalMsg={getLocalError('registration_payment')}
required required
errorMsg={
getError('registration_payment') ||
getLocalError('registration_payment')
}
/> />
<RadioList <RadioList
@ -143,17 +143,18 @@ export default function PaymentMethodSelector({
label="Mode de Paiement" label="Mode de Paiement"
placeHolder="Sélectionner un mode de paiement" placeHolder="Sélectionner un mode de paiement"
selected={formData.tuition_payment} selected={formData.tuition_payment}
callback={(e) => onChange('tuition_payment', e.target.value)} callback={(e) =>
onChange('tuition_payment', parseInt(e.target.value, 10))
}
choices={tuitionPaymentModes.map((mode) => ({ choices={tuitionPaymentModes.map((mode) => ({
value: mode.mode, value: mode.mode,
label: label:
paymentModesOptions.find((option) => option.id === mode.mode) paymentModesOptions.find((option) => option.id === mode.mode)
?.name || 'Mode inconnu', ?.name || 'Mode inconnu',
}))} }))}
errorMsg={getError('tuition_payment')}
errorLocalMsg={getLocalError('tuition_payment')}
required required
errorMsg={
getError('tuition_payment') || getLocalError('tuition_payment')
}
/> />
<RadioList <RadioList
@ -167,7 +168,10 @@ export default function PaymentMethodSelector({
label: option.name, label: option.name,
}))} }))}
formData={formData} formData={formData}
handleChange={(e) => onChange('tuition_payment_plan', e.target.value)} handleChange={(e) => {
const value = parseInt(e.target.value, 10);
onChange('tuition_payment_plan', value); // Convertir la valeur en entier
}}
fieldName="tuition_payment_plan" fieldName="tuition_payment_plan"
className="mt-4" className="mt-4"
errorMsg={ errorMsg={

View File

@ -33,6 +33,7 @@ export default function ResponsableInputFields({
}; };
const getLocalError = (index, field) => { const getLocalError = (index, field) => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if ( if (
// Student Form // Student Form
(field === 'last_name' && (field === 'last_name' &&
@ -43,7 +44,8 @@ export default function ResponsableInputFields({
guardians[index].first_name.trim() === '')) || guardians[index].first_name.trim() === '')) ||
(field === 'associated_profile_email' && (field === 'associated_profile_email' &&
(!guardians[index].associated_profile_email || (!guardians[index].associated_profile_email ||
guardians[index].associated_profile_email.trim() === '')) || guardians[index].associated_profile_email.trim() === '' ||
!emailRegex.test(guardians[index].associated_profile_email))) ||
(field === 'birth_date' && (field === 'birth_date' &&
(!guardians[index].birth_date || (!guardians[index].birth_date ||
guardians[index].birth_date.trim() === '')) || guardians[index].birth_date.trim() === '')) ||
@ -138,10 +140,8 @@ export default function ResponsableInputFields({
onChange={(event) => { onChange={(event) => {
onGuardiansChange(item.id, 'last_name', event.target.value); onGuardiansChange(item.id, 'last_name', event.target.value);
}} }}
errorMsg={ errorMsg={getError('last_name')}
getError(index, 'last_name') || errorLocalMsg={getLocalError(index, 'last_name')}
getLocalError(index, 'last_name')
}
required required
/> />
<InputText <InputText
@ -152,10 +152,8 @@ export default function ResponsableInputFields({
onChange={(event) => { onChange={(event) => {
onGuardiansChange(item.id, 'first_name', event.target.value); onGuardiansChange(item.id, 'first_name', event.target.value);
}} }}
errorMsg={ errorMsg={getError('first_name')}
getError(index, 'first_name') || errorLocalMsg={getLocalError(index, 'first_name')}
getLocalError(index, 'first_name')
}
required required
/> />
</div> </div>
@ -173,11 +171,9 @@ export default function ResponsableInputFields({
event.target.value event.target.value
); );
}} }}
errorMsg={getError('associated_profile_email')}
errorLocalMsg={getLocalError(index, 'associated_profile_email')}
required required
errorMsg={
getError(index, 'associated_profile_email') ||
getLocalError(index, 'associated_profile_email')
}
/> />
<InputPhone <InputPhone
name="telephoneResponsable" name="telephoneResponsable"
@ -186,8 +182,9 @@ export default function ResponsableInputFields({
onChange={(event) => { onChange={(event) => {
onGuardiansChange(item.id, 'phone', event.target.value); onGuardiansChange(item.id, 'phone', event.target.value);
}} }}
errorMsg={getError('phone')}
errorLocalMsg={getLocalError(index, 'phone')}
required required
errorMsg={getError(index, 'phone')}
/> />
</div> </div>
@ -200,11 +197,9 @@ export default function ResponsableInputFields({
onChange={(event) => { onChange={(event) => {
onGuardiansChange(item.id, 'birth_date', event.target.value); onGuardiansChange(item.id, 'birth_date', event.target.value);
}} }}
errorMsg={getError('birth_date')}
errorLocalMsg={getLocalError(index, 'birth_date')}
required required
errorMsg={
getError(index, 'birth_date') ||
getLocalError(index, 'birth_date')
}
/> />
<InputText <InputText
name="professionResponsable" name="professionResponsable"
@ -214,11 +209,9 @@ export default function ResponsableInputFields({
onChange={(event) => { onChange={(event) => {
onGuardiansChange(item.id, 'profession', event.target.value); onGuardiansChange(item.id, 'profession', event.target.value);
}} }}
errorMsg={getError('profession')}
errorLocalMsg={getLocalError(index, 'profession')}
required required
errorMsg={
getError(index, 'profession') ||
getLocalError(index, 'profession')
}
/> />
</div> </div>
@ -231,10 +224,9 @@ export default function ResponsableInputFields({
onChange={(event) => { onChange={(event) => {
onGuardiansChange(item.id, 'address', event.target.value); onGuardiansChange(item.id, 'address', event.target.value);
}} }}
errorMsg={getError('address')}
errorLocalMsg={getLocalError(index, 'address')}
required required
errorMsg={
getError(index, 'address') || getLocalError(index, 'address')
}
/> />
</div> </div>
</div> </div>

View File

@ -2,6 +2,7 @@ import InputText from '@/components/InputText';
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import { Trash2, Plus, Users } from 'lucide-react'; import { Trash2, Plus, Users } from 'lucide-react';
import SectionHeader from '@/components/SectionHeader'; import SectionHeader from '@/components/SectionHeader';
import { v4 as uuidv4 } from 'uuid';
export default function SiblingInputFields({ export default function SiblingInputFields({
siblings, siblings,
@ -67,6 +68,7 @@ export default function SiblingInputFields({
setSiblings([ setSiblings([
...siblings, ...siblings,
{ {
id: `temp-${uuidv4()}`,
last_name: '', last_name: '',
first_name: '', first_name: '',
birth_date: '', birth_date: '',
@ -105,10 +107,8 @@ export default function SiblingInputFields({
onChange={(event) => { onChange={(event) => {
onSiblingsChange(item.id, 'last_name', event.target.value); onSiblingsChange(item.id, 'last_name', event.target.value);
}} }}
errorMsg={ errorMsg={getError('last_name')}
getError(index, 'last_name') || errorLocalMsg={getLocalError(index, 'last_name')}
getLocalError(index, 'last_name')
}
required required
/> />
<InputText <InputText
@ -119,10 +119,8 @@ export default function SiblingInputFields({
onChange={(event) => { onChange={(event) => {
onSiblingsChange(item.id, 'first_name', event.target.value); onSiblingsChange(item.id, 'first_name', event.target.value);
}} }}
errorMsg={ errorMsg={getError('first_name')}
getError(index, 'first_name') || errorLocalMsg={getLocalError(index, 'first_name')}
getLocalError(index, 'first_name')
}
required required
/> />
</div> </div>
@ -136,10 +134,8 @@ export default function SiblingInputFields({
onChange={(event) => { onChange={(event) => {
onSiblingsChange(item.id, 'birth_date', event.target.value); onSiblingsChange(item.id, 'birth_date', event.target.value);
}} }}
errorMsg={ errorMsg={getError('birth_date')}
getError(index, 'birth_date') || errorLocalMsg={getLocalError(index, 'birth_date')}
getLocalError(index, 'birth_date')
}
required required
/> />
</div> </div>

View File

@ -21,6 +21,11 @@ const levels = [
{ value: '9', label: 'CM2' }, { value: '9', label: 'CM2' },
]; ];
const genders = [
{ value: '1', label: 'Garçon' },
{ value: '2', label: 'Fille' },
];
export default function StudentInfoForm({ export default function StudentInfoForm({
studentId, studentId,
formData, formData,
@ -46,6 +51,7 @@ export default function StudentInfoForm({
photo: photoPath, photo: photoPath,
last_name: data?.student?.last_name || '', last_name: data?.student?.last_name || '',
first_name: data?.student?.first_name || '', first_name: data?.student?.first_name || '',
gender: data?.student?.gender || '',
address: data?.student?.address || '', address: data?.student?.address || '',
birth_date: data?.student?.birth_date || '', birth_date: data?.student?.birth_date || '',
birth_place: data?.student?.birth_place || '', birth_place: data?.student?.birth_place || '',
@ -109,6 +115,8 @@ export default function StudentInfoForm({
(!formData.last_name || formData.last_name.trim() === '')) || (!formData.last_name || formData.last_name.trim() === '')) ||
(field === 'first_name' && (field === 'first_name' &&
(!formData.first_name || formData.first_name.trim() === '')) || (!formData.first_name || formData.first_name.trim() === '')) ||
(field === 'gender' &&
(!formData.gender || String(formData.gender).trim() === '')) ||
(field === 'nationality' && (field === 'nationality' &&
(!formData.nationality || formData.nationality.trim() === '')) || (!formData.nationality || formData.nationality.trim() === '')) ||
(field === 'birth_date' && (field === 'birth_date' &&
@ -124,8 +132,7 @@ export default function StudentInfoForm({
(!formData.attending_physician || (!formData.attending_physician ||
formData.attending_physician.trim() === '')) || formData.attending_physician.trim() === '')) ||
(field === 'level' && (field === 'level' &&
(!formData.level || String(formData.level).trim() === '')) || (!formData.level || String(formData.level).trim() === ''))
(field === 'photo' && !formData.photo)
) { ) {
return 'Champs requis'; return 'Champs requis';
} }
@ -156,37 +163,44 @@ export default function StudentInfoForm({
return ( return (
<> <>
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200"> <div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200 space-y-8">
<SectionHeader <SectionHeader
icon={User} icon={User}
title={`Informations de l'élève`} title={`Informations de l'élève`}
description={`Remplissez les champs requis`} description={`Remplissez les champs requis`}
/> />
<div className="grid grid-cols-1 md:grid-cols-2 gap-4"> <div className="grid grid-cols-1 md:grid-cols-3 gap-8">
<InputText <InputText
name="last_name" name="last_name"
label="Nom" label="Nom"
value={formData.last_name} value={formData.last_name}
onChange={(e) => onChange('last_name', e.target.value)} onChange={(e) => onChange('last_name', e.target.value)}
required required
errorMsg={getError('last_name') || getLocalError('last_name')} errorMsg={getError('last_name')}
errorLocalMsg={getLocalError('last_name')}
/> />
<InputText <InputText
name="first_name" name="first_name"
label="Prénom" label="Prénom"
value={formData.first_name} value={formData.first_name}
onChange={(e) => onChange('first_name', e.target.value)} onChange={(e) => onChange('first_name', e.target.value)}
errorMsg={getError('first_name') || getLocalError('first_name')} errorMsg={getError('first_name')}
errorLocalMsg={getLocalError('first_name')}
required required
/> />
<InputText <SelectChoice
name="nationality" name="gender"
label="Nationalité" label="Genre"
value={formData.nationality} placeHolder="Sélectionner un genre"
selected={formData.gender}
callback={(e) => onChange('gender', e.target.value)}
choices={genders}
required required
onChange={(e) => onChange('nationality', e.target.value)} errorMsg={getError('gender')}
errorMsg={getError('nationality') || getLocalError('nationality')} errorLocalMsg={getLocalError('gender')}
/> />
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
<InputText <InputText
name="birth_date" name="birth_date"
type="date" type="date"
@ -194,7 +208,8 @@ export default function StudentInfoForm({
value={formData.birth_date} value={formData.birth_date}
onChange={(e) => onChange('birth_date', e.target.value)} onChange={(e) => onChange('birth_date', e.target.value)}
required required
errorMsg={getError('birth_date') || getLocalError('birth_date')} errorMsg={getError('birth_date')}
errorLocalMsg={getLocalError('birth_date')}
/> />
<InputText <InputText
name="birth_place" name="birth_place"
@ -202,7 +217,8 @@ export default function StudentInfoForm({
value={formData.birth_place} value={formData.birth_place}
onChange={(e) => onChange('birth_place', e.target.value)} onChange={(e) => onChange('birth_place', e.target.value)}
required required
errorMsg={getError('birth_place') || getLocalError('birth_place')} errorMsg={getError('birth_place')}
errorLocalMsg={getLocalError('birth_place')}
/> />
<InputText <InputText
name="birth_postal_code" name="birth_postal_code"
@ -210,31 +226,39 @@ export default function StudentInfoForm({
value={formData.birth_postal_code} value={formData.birth_postal_code}
onChange={(e) => onChange('birth_postal_code', e.target.value)} onChange={(e) => onChange('birth_postal_code', e.target.value)}
required required
errorMsg={ errorMsg={getError('birth_postal_code')}
getError('birth_postal_code') || errorLocalMsg={getLocalError('birth_postal_code')}
getLocalError('birth_postal_code')
}
/> />
<div className="md:col-span-2"> </div>
<InputText <div className="grid grid-cols-1 md:grid-cols-2 gap-8">
name="address" <InputText
label="Adresse" name="nationality"
value={formData.address} label="Nationalité"
onChange={(e) => onChange('address', e.target.value)} value={formData.nationality}
required required
errorMsg={getError('address') || getLocalError('address')} onChange={(e) => onChange('nationality', e.target.value)}
/> errorMsg={getError('nationality')}
</div> errorLocalMsg={getLocalError('nationality')}
/>
<InputText
name="address"
label="Adresse"
value={formData.address}
onChange={(e) => onChange('address', e.target.value)}
required
errorMsg={getError('address')}
errorLocalMsg={getLocalError('address')}
/>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
<InputText <InputText
name="attending_physician" name="attending_physician"
label="Médecin Traitant" label="Médecin Traitant"
value={formData.attending_physician} value={formData.attending_physician}
onChange={(e) => onChange('attending_physician', e.target.value)} onChange={(e) => onChange('attending_physician', e.target.value)}
required required
errorMsg={ errorMsg={getError('attending_physician')}
getError('attending_physician') || errorLocalMsg={getLocalError('attending_physician')}
getLocalError('attending_physician')
}
/> />
<SelectChoice <SelectChoice
name="level" name="level"
@ -244,25 +268,18 @@ export default function StudentInfoForm({
callback={(e) => onChange('level', e.target.value)} callback={(e) => onChange('level', e.target.value)}
choices={levels} choices={levels}
required required
errorMsg={getError('level') || getLocalError('level')} errorMsg={getError('level')}
errorLocalMsg={getLocalError('level')}
/>
</div>
<div className="grid grid-cols-1 md:grid-cols-1 gap-8">
<FileUpload
selectionMessage="Sélectionnez une photo"
onFileSelect={(file) => handlePhotoUpload(file)}
existingFile={formData.photo}
errorMsg={getError('photo')}
/> />
</div> </div>
</div>
{/* Section pour l'upload des fichiers */}
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200 mt-6">
<SectionHeader
icon={User}
title={`Photo de l'élève`}
description={`Ajoutez une photo de votre enfant`}
/>
<FileUpload
selectionMessage="Sélectionnez une photo à uploader"
onFileSelect={(file) => handlePhotoUpload(file)}
existingFile={formData.photo}
required
errorMsg={getError('photo') || getLocalError('photo')}
/>
</div> </div>
</> </>
); );

View File

@ -8,9 +8,11 @@ export default function SelectChoice({
callback, callback,
selected, selected,
errorMsg, errorMsg,
errorLocalMsg,
IconItem, IconItem,
disabled = false, disabled = false,
}) { }) {
const isPlaceholderSelected = selected === ''; // Vérifie si le placeholder est sélectionné
return ( return (
<> <>
<div> <div>
@ -22,7 +24,11 @@ export default function SelectChoice({
{required && <span className="text-red-500 ml-1">*</span>} {required && <span className="text-red-500 ml-1">*</span>}
</label> </label>
<div <div
className={`mt-1 flex items-center border border-gray-200 rounded-md ${errorMsg ? 'border-red-500' : ''} ${disabled ? '' : 'hover:border-gray-400 focus-within:border-gray-500'}`} className={`mt-1 flex items-center border rounded-md ${
errorMsg || errorLocalMsg
? 'border-red-500 hover:border-red-700'
: 'border-gray-200 hover:border-gray-400'
} ${disabled ? '' : 'focus-within:border-gray-500'}`}
> >
{IconItem && ( {IconItem && (
<span className="inline-flex items-center px-3 text-gray-500 text-sm"> <span className="inline-flex items-center px-3 text-gray-500 text-sm">
@ -30,7 +36,9 @@ export default function SelectChoice({
</span> </span>
)} )}
<select <select
className={`flex-1 px-3 py-2 block w-full sm:text-sm border-none focus:ring-0 outline-none rounded-md ${disabled ? 'bg-gray-100' : ''}`} className={`flex-1 px-3 py-2 block w-full sm:text-sm border-none focus:ring-0 outline-none rounded-md ${
disabled ? 'bg-gray-100' : ''
} ${isPlaceholderSelected ? 'italic text-gray-500' : 'not-italic text-gray-800'}`} // Applique le style classique si une option autre que le placeholder est sélectionnée
type={type} type={type}
id={name} id={name}
name={name} name={name}
@ -38,9 +46,17 @@ export default function SelectChoice({
onChange={callback} onChange={callback}
disabled={disabled} disabled={disabled}
> >
<option value="">{placeHolder?.toLowerCase()}</option> {/* Placeholder en italique */}
{choices.map(({ value, label }, index) => ( <option value="" className="italic text-gray-500">
<option key={value} value={value}> {placeHolder?.toLowerCase()}
</option>
{/* Autres options sans italique */}
{choices.map(({ value, label }) => (
<option
key={value}
value={value}
className="not-italic text-gray-800"
>
{label} {label}
</option> </option>
))} ))}