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

@ -5,6 +5,7 @@ export default function InputText({
value,
onChange,
errorMsg,
errorLocalMsg,
placeholder,
className,
required,
@ -20,7 +21,11 @@ export default function InputText({
{required && <span className="text-red-500 ml-1">*</span>}
</label>
<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
type={type}

View File

@ -49,6 +49,7 @@ export default function InscriptionFormShared({
photo: null,
last_name: '',
first_name: '',
gender: '',
address: '',
birth_date: '',
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
const isSepaPayment =
formData.registration_payment === '1' || formData.tuition_payment === '1';
formData.registration_payment === 1 || formData.tuition_payment === 1;
// Préparer les données JSON
const jsonData = {
student: {
...formData,
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,
status: isSepaPayment ? 8 : 3,
@ -398,6 +403,8 @@ export default function InscriptionFormShared({
formDataToSend.append('photo', formData.photo);
}
console.log('submit : ', jsonData);
// Appeler la fonction onSubmit avec les données FormData
onSubmit(formDataToSend);
};

View File

@ -82,18 +82,18 @@ export default function PaymentMethodSelector({
label="Mode de Paiement"
placeHolder="Sélectionner un mode de paiement"
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) => ({
value: mode.mode,
label:
paymentModesOptions.find((option) => option.id === mode.mode)
?.name || 'Mode inconnu',
}))}
errorMsg={getError('registration_payment')}
errorLocalMsg={getLocalError('registration_payment')}
required
errorMsg={
getError('registration_payment') ||
getLocalError('registration_payment')
}
/>
<RadioList
@ -143,17 +143,18 @@ export default function PaymentMethodSelector({
label="Mode de Paiement"
placeHolder="Sélectionner un mode de paiement"
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) => ({
value: mode.mode,
label:
paymentModesOptions.find((option) => option.id === mode.mode)
?.name || 'Mode inconnu',
}))}
errorMsg={getError('tuition_payment')}
errorLocalMsg={getLocalError('tuition_payment')}
required
errorMsg={
getError('tuition_payment') || getLocalError('tuition_payment')
}
/>
<RadioList
@ -167,7 +168,10 @@ export default function PaymentMethodSelector({
label: option.name,
}))}
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"
className="mt-4"
errorMsg={

View File

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

View File

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

View File

@ -21,6 +21,11 @@ const levels = [
{ value: '9', label: 'CM2' },
];
const genders = [
{ value: '1', label: 'Garçon' },
{ value: '2', label: 'Fille' },
];
export default function StudentInfoForm({
studentId,
formData,
@ -46,6 +51,7 @@ export default function StudentInfoForm({
photo: photoPath,
last_name: data?.student?.last_name || '',
first_name: data?.student?.first_name || '',
gender: data?.student?.gender || '',
address: data?.student?.address || '',
birth_date: data?.student?.birth_date || '',
birth_place: data?.student?.birth_place || '',
@ -109,6 +115,8 @@ export default function StudentInfoForm({
(!formData.last_name || formData.last_name.trim() === '')) ||
(field === 'first_name' &&
(!formData.first_name || formData.first_name.trim() === '')) ||
(field === 'gender' &&
(!formData.gender || String(formData.gender).trim() === '')) ||
(field === 'nationality' &&
(!formData.nationality || formData.nationality.trim() === '')) ||
(field === 'birth_date' &&
@ -124,8 +132,7 @@ export default function StudentInfoForm({
(!formData.attending_physician ||
formData.attending_physician.trim() === '')) ||
(field === 'level' &&
(!formData.level || String(formData.level).trim() === '')) ||
(field === 'photo' && !formData.photo)
(!formData.level || String(formData.level).trim() === ''))
) {
return 'Champs requis';
}
@ -156,37 +163,44 @@ export default function StudentInfoForm({
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
icon={User}
title={`Informations de l'élève`}
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
name="last_name"
label="Nom"
value={formData.last_name}
onChange={(e) => onChange('last_name', e.target.value)}
required
errorMsg={getError('last_name') || getLocalError('last_name')}
errorMsg={getError('last_name')}
errorLocalMsg={getLocalError('last_name')}
/>
<InputText
name="first_name"
label="Prénom"
value={formData.first_name}
onChange={(e) => onChange('first_name', e.target.value)}
errorMsg={getError('first_name') || getLocalError('first_name')}
errorMsg={getError('first_name')}
errorLocalMsg={getLocalError('first_name')}
required
/>
<InputText
name="nationality"
label="Nationalité"
value={formData.nationality}
<SelectChoice
name="gender"
label="Genre"
placeHolder="Sélectionner un genre"
selected={formData.gender}
callback={(e) => onChange('gender', e.target.value)}
choices={genders}
required
onChange={(e) => onChange('nationality', e.target.value)}
errorMsg={getError('nationality') || getLocalError('nationality')}
errorMsg={getError('gender')}
errorLocalMsg={getLocalError('gender')}
/>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
<InputText
name="birth_date"
type="date"
@ -194,7 +208,8 @@ export default function StudentInfoForm({
value={formData.birth_date}
onChange={(e) => onChange('birth_date', e.target.value)}
required
errorMsg={getError('birth_date') || getLocalError('birth_date')}
errorMsg={getError('birth_date')}
errorLocalMsg={getLocalError('birth_date')}
/>
<InputText
name="birth_place"
@ -202,7 +217,8 @@ export default function StudentInfoForm({
value={formData.birth_place}
onChange={(e) => onChange('birth_place', e.target.value)}
required
errorMsg={getError('birth_place') || getLocalError('birth_place')}
errorMsg={getError('birth_place')}
errorLocalMsg={getLocalError('birth_place')}
/>
<InputText
name="birth_postal_code"
@ -210,31 +226,39 @@ export default function StudentInfoForm({
value={formData.birth_postal_code}
onChange={(e) => onChange('birth_postal_code', e.target.value)}
required
errorMsg={
getError('birth_postal_code') ||
getLocalError('birth_postal_code')
}
errorMsg={getError('birth_postal_code')}
errorLocalMsg={getLocalError('birth_postal_code')}
/>
<div className="md:col-span-2">
<InputText
name="address"
label="Adresse"
value={formData.address}
onChange={(e) => onChange('address', e.target.value)}
required
errorMsg={getError('address') || getLocalError('address')}
/>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
<InputText
name="nationality"
label="Nationalité"
value={formData.nationality}
required
onChange={(e) => onChange('nationality', e.target.value)}
errorMsg={getError('nationality')}
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
name="attending_physician"
label="Médecin Traitant"
value={formData.attending_physician}
onChange={(e) => onChange('attending_physician', e.target.value)}
required
errorMsg={
getError('attending_physician') ||
getLocalError('attending_physician')
}
errorMsg={getError('attending_physician')}
errorLocalMsg={getLocalError('attending_physician')}
/>
<SelectChoice
name="level"
@ -244,25 +268,18 @@ export default function StudentInfoForm({
callback={(e) => onChange('level', e.target.value)}
choices={levels}
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>
{/* 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>
</>
);

View File

@ -8,9 +8,11 @@ export default function SelectChoice({
callback,
selected,
errorMsg,
errorLocalMsg,
IconItem,
disabled = false,
}) {
const isPlaceholderSelected = selected === ''; // Vérifie si le placeholder est sélectionné
return (
<>
<div>
@ -22,7 +24,11 @@ export default function SelectChoice({
{required && <span className="text-red-500 ml-1">*</span>}
</label>
<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 && (
<span className="inline-flex items-center px-3 text-gray-500 text-sm">
@ -30,7 +36,9 @@ export default function SelectChoice({
</span>
)}
<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}
id={name}
name={name}
@ -38,9 +46,17 @@ export default function SelectChoice({
onChange={callback}
disabled={disabled}
>
<option value="">{placeHolder?.toLowerCase()}</option>
{choices.map(({ value, label }, index) => (
<option key={value} value={value}>
{/* Placeholder en italique */}
<option value="" className="italic text-gray-500">
{placeHolder?.toLowerCase()}
</option>
{/* Autres options sans italique */}
{choices.map(({ value, label }) => (
<option
key={value}
value={value}
className="not-italic text-gray-800"
>
{label}
</option>
))}