feat: Formulaire de création RF sur une seule pag

This commit is contained in:
N3WT DE COMPET
2025-05-05 20:57:51 +02:00
parent 2a6b3bdf63
commit 76f9a7dd14
19 changed files with 1299 additions and 422 deletions

View File

@ -44,6 +44,8 @@ from Auth.serializers import ProfileSerializer, ProfileRoleSerializer
from Establishment.serializers import EstablishmentSerializer from Establishment.serializers import EstablishmentSerializer
from Subscriptions.serializers import RegistrationFormSerializer, StudentSerializer from Subscriptions.serializers import RegistrationFormSerializer, StudentSerializer
from Subscriptions.util import getCurrentSchoolYear, getNextSchoolYear # Import des fonctions nécessaires
# Définir le chemin vers le dossier mock_datas # Définir le chemin vers le dossier mock_datas
MOCK_DATAS_PATH = os.path.join(settings.BASE_DIR, 'School', 'management', 'mock_datas') MOCK_DATAS_PATH = os.path.join(settings.BASE_DIR, 'School', 'management', 'mock_datas')
@ -371,7 +373,7 @@ class Command(BaseCommand):
"first_name": fake.first_name(), "first_name": fake.first_name(),
"birth_date": fake.date_of_birth().strftime('%Y-%m-%d'), "birth_date": fake.date_of_birth().strftime('%Y-%m-%d'),
"address": fake.address(), "address": fake.address(),
"phone":"+33122334455", "phone": "+33122334455",
"profession": fake.job() "profession": fake.job()
} }
@ -403,11 +405,15 @@ class Command(BaseCommand):
fees = Fee.objects.filter(id__in=[1, 2, 3, 4]) fees = Fee.objects.filter(id__in=[1, 2, 3, 4])
discounts = Discount.objects.filter(id__in=[1]) discounts = Discount.objects.filter(id__in=[1])
# Déterminer l'année scolaire (soit l'année en cours, soit l'année prochaine)
school_year = random.choice([getCurrentSchoolYear(), getNextSchoolYear()])
# Créer les données du formulaire d'inscription # Créer les données du formulaire d'inscription
register_form_data = { register_form_data = {
"fileGroup": RegistrationFileGroup.objects.get(id=fake.random_int(min=1, max=file_group_count)), "fileGroup": RegistrationFileGroup.objects.get(id=fake.random_int(min=1, max=file_group_count)),
"establishment": profile_role.establishment, "establishment": profile_role.establishment,
"status": fake.random_int(min=1, max=3) "status": fake.random_int(min=1, max=3),
"school_year": school_year # Ajouter l'année scolaire
} }
# Créer ou mettre à jour le formulaire d'inscription # Créer ou mettre à jour le formulaire d'inscription
@ -420,7 +426,7 @@ class Command(BaseCommand):
if created: if created:
register_form.fees.set(fees) register_form.fees.set(fees)
register_form.discounts.set(discounts) register_form.discounts.set(discounts)
self.stdout.write(self.style.SUCCESS(f'RegistrationForm for student {student.last_name} created successfully')) self.stdout.write(self.style.SUCCESS(f'RegistrationForm for student {student.last_name} created successfully with school year {school_year}'))
else: else:
self.stdout.write(self.style.SUCCESS(f'RegistrationForm for student {student.last_name} already exists')) self.stdout.write(self.style.SUCCESS(f'RegistrationForm for student {student.last_name} already exists'))

View File

@ -25,8 +25,8 @@ class Guardian(models.Model):
""" """
Représente un responsable légal (parent/tuteur) dun élève. Représente un responsable légal (parent/tuteur) dun élève.
""" """
last_name = models.CharField(max_length=200, default="") last_name = models.CharField(max_length=200, null=True, blank=True)
first_name = models.CharField(max_length=200, default="") first_name = models.CharField(max_length=200, null=True, blank=True)
birth_date = models.DateField(null=True, blank=True) birth_date = models.DateField(null=True, blank=True)
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)
@ -49,8 +49,8 @@ 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.
""" """
last_name = models.CharField(max_length=200, default="") last_name = models.CharField(max_length=200, null=True, blank=True)
first_name = models.CharField(max_length=200, default="") first_name = models.CharField(max_length=200, null=True, blank=True)
birth_date = models.DateField(null=True, blank=True) birth_date = models.DateField(null=True, blank=True)
def __str__(self): def __str__(self):
@ -218,6 +218,7 @@ class RegistrationForm(models.Model):
student = models.OneToOneField(Student, on_delete=models.CASCADE, primary_key=True) student = models.OneToOneField(Student, on_delete=models.CASCADE, primary_key=True)
status = models.IntegerField(choices=RegistrationFormStatus, default=RegistrationFormStatus.RF_IDLE) status = models.IntegerField(choices=RegistrationFormStatus, default=RegistrationFormStatus.RF_IDLE)
last_update = models.DateTimeField(auto_now=True) last_update = models.DateTimeField(auto_now=True)
school_year = models.CharField(max_length=9, default="", blank=True)
notes = models.CharField(max_length=200, blank=True) notes = models.CharField(max_length=200, blank=True)
registration_link_code = models.CharField(max_length=200, default="", blank=True) registration_link_code = models.CharField(max_length=200, default="", blank=True)
registration_file = models.FileField( registration_file = models.FileField(

View File

@ -343,7 +343,7 @@ class GuardianByDICreationSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = Guardian model = Guardian
fields = ['id', 'last_name', 'first_name', 'associated_profile_email'] fields = ['id', 'last_name', 'first_name', 'associated_profile_email', 'phone']
def get_associated_profile_email(self, obj): def get_associated_profile_email(self, obj):
if obj.profile_role and obj.profile_role.profile: if obj.profile_role and obj.profile_role.profile:
@ -356,7 +356,7 @@ class StudentByRFCreationSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = Student model = Student
fields = ['id', 'last_name', 'first_name', 'guardians'] fields = ['id', 'last_name', 'first_name', 'guardians', 'level']
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(StudentByRFCreationSerializer, self).__init__(*args, **kwargs) super(StudentByRFCreationSerializer, self).__init__(*args, **kwargs)

View File

@ -174,4 +174,45 @@ def delete_registration_files(registerForm):
registerForm.registration_file.delete(save=False) registerForm.registration_file.delete(save=False)
if os.path.exists(base_dir): if os.path.exists(base_dir):
shutil.rmtree(base_dir) shutil.rmtree(base_dir)
from datetime import datetime
def getCurrentSchoolYear():
"""
Retourne l'année scolaire en cours au format "YYYY-YYYY".
Exemple : Si nous sommes en octobre 2023, retourne "2023-2024".
"""
now = datetime.now()
current_year = now.year
current_month = now.month
# Si nous sommes avant septembre, l'année scolaire a commencé l'année précédente
start_year = current_year if current_month >= 9 else current_year - 1
return f"{start_year}-{start_year + 1}"
def getNextSchoolYear():
"""
Retourne l'année scolaire suivante au format "YYYY-YYYY".
Exemple : Si nous sommes en octobre 2023, retourne "2024-2025".
"""
current_school_year = getCurrentSchoolYear()
start_year, end_year = map(int, current_school_year.split('-'))
return f"{start_year + 1}-{end_year + 1}"
def getHistoricalYears(count=5):
"""
Retourne un tableau des années scolaires passées au format "YYYY-YYYY".
Exemple : ["2022-2023", "2021-2022", "2020-2021"].
:param count: Le nombre d'années scolaires passées à inclure.
"""
current_school_year = getCurrentSchoolYear()
start_year = int(current_school_year.split('-')[0])
historical_years = []
for i in range(1, count + 1):
historical_start_year = start_year - i
historical_years.append(f"{historical_start_year}-{historical_start_year + 1}")
return historical_years

View File

@ -33,7 +33,7 @@ class RegisterFormView(APIView):
@swagger_auto_schema( @swagger_auto_schema(
manual_parameters=[ manual_parameters=[
openapi.Parameter('filter', openapi.IN_QUERY, description="filtre", type=openapi.TYPE_STRING, enum=['pending', 'archived', 'subscribed'], required=True), openapi.Parameter('filter', openapi.IN_QUERY, description="filtre", type=openapi.TYPE_STRING, enum=['current_year', 'next_year', 'historical'], required=True),
openapi.Parameter('search', openapi.IN_QUERY, description="search", type=openapi.TYPE_STRING, required=False), openapi.Parameter('search', openapi.IN_QUERY, description="search", type=openapi.TYPE_STRING, required=False),
openapi.Parameter('page_size', openapi.IN_QUERY, description="limite de page lors de la pagination", type=openapi.TYPE_INTEGER, required=False), openapi.Parameter('page_size', openapi.IN_QUERY, description="limite de page lors de la pagination", type=openapi.TYPE_INTEGER, required=False),
openapi.Parameter('establishment_id', openapi.IN_QUERY, description="ID de l'établissement", type=openapi.TYPE_INTEGER, required=True), openapi.Parameter('establishment_id', openapi.IN_QUERY, description="ID de l'établissement", type=openapi.TYPE_INTEGER, required=True),
@ -51,7 +51,7 @@ class RegisterFormView(APIView):
"last_name": "Doe", "last_name": "Doe",
"date_of_birth": "2010-01-01" "date_of_birth": "2010-01-01"
}, },
"status": "pending", "status": "current_year",
"last_update": "10-02-2025 10:00" "last_update": "10-02-2025 10:00"
}, },
{ {
@ -62,7 +62,7 @@ class RegisterFormView(APIView):
"last_name": "Doe", "last_name": "Doe",
"date_of_birth": "2011-02-02" "date_of_birth": "2011-02-02"
}, },
"status": "archived", "status": "historical",
"last_update": "09-02-2025 09:00" "last_update": "09-02-2025 09:00"
} }
] ]
@ -85,14 +85,19 @@ class RegisterFormView(APIView):
except ValueError: except ValueError:
page_size = settings.NB_RESULT_PER_PAGE page_size = settings.NB_RESULT_PER_PAGE
# Récupérer les dossier d'inscriptions en fonction du filtre # Récupérer les années scolaires
current_year = util.getCurrentSchoolYear()
next_year = util.getNextSchoolYear()
historical_years = util.getHistoricalYears()
# Récupérer les dossiers d'inscriptions en fonction du filtre
registerForms_List = None registerForms_List = None
if filter == 'pending': if filter == 'current_year':
exclude_states = [RegistrationForm.RegistrationFormStatus.RF_VALIDATED, RegistrationForm.RegistrationFormStatus.RF_ARCHIVED] registerForms_List = RegistrationForm.objects.filter(school_year=current_year)
registerForms_List = bdd.searchObjects(RegistrationForm, search, _excludeStates=exclude_states) elif filter == 'next_year':
elif filter == 'archived': registerForms_List = RegistrationForm.objects.filter(school_year=next_year)
registerForms_List = bdd.getObjects(RegistrationForm, 'status', RegistrationForm.RegistrationFormStatus.RF_ARCHIVED) elif filter == 'historical':
elif filter == 'subscribed': registerForms_List = RegistrationForm.objects.filter(school_year__in=historical_years)
registerForms_List = bdd.getObjects(RegistrationForm, 'status', RegistrationForm.RegistrationFormStatus.RF_VALIDATED) registerForms_List = bdd.getObjects(RegistrationForm, 'status', RegistrationForm.RegistrationFormStatus.RF_VALIDATED)
else: else:
registerForms_List = None registerForms_List = None
@ -126,7 +131,7 @@ class RegisterFormView(APIView):
"last_name": "Doe", "last_name": "Doe",
"date_of_birth": "2010-01-01" "date_of_birth": "2010-01-01"
}, },
"status": "pending", "status": "current_year",
"last_update": "10-02-2025 10:00", "last_update": "10-02-2025 10:00",
"codeLienInscription": "ABC123XYZ456" "codeLienInscription": "ABC123XYZ456"
} }
@ -514,4 +519,5 @@ def get_parent_file_templates_by_rf(request, id):
# Retourner les données sérialisées # Retourner les données sérialisées
return JsonResponse(serializer.data, safe=False) return JsonResponse(serializer.data, safe=False)
except RegistrationParentFileTemplate.DoesNotExist: except RegistrationParentFileTemplate.DoesNotExist:
return JsonResponse({'error': 'Aucune pièce à fournir trouvée pour ce dossier d\'inscription'}, status=status.HTTP_404_NOT_FOUND) return JsonResponse({'error': 'Aucune pièce à fournir trouvée pour ce dossier d\'inscription'}, status=status.HTTP_404_NOT_FOUND)

View File

@ -30,5 +30,6 @@
"classe": "Class", "classe": "Class",
"registrationFileStatus": "Registration file status", "registrationFileStatus": "Registration file status",
"files": "Files", "files": "Files",
"subscribeFiles": "Subscribe files" "subscribeFiles": "Subscribe files",
"historical": "Historical"
} }

View File

@ -30,5 +30,6 @@
"classe": "Classe", "classe": "Classe",
"registrationFileStatus": "État du dossier d'inscription", "registrationFileStatus": "État du dossier d'inscription",
"files": "Fichiers", "files": "Fichiers",
"subscribeFiles": "Fichiers d'inscription" "subscribeFiles": "Fichiers d'inscription",
"historical": "Historique"
} }

File diff suppressed because it is too large Load Diff

View File

@ -25,40 +25,24 @@ import InscriptionForm from '@/components/Inscription/InscriptionForm';
import { useEstablishment } from '@/context/EstablishmentContext'; import { useEstablishment } from '@/context/EstablishmentContext';
import { import {
PENDING, CURRENT_YEAR,
SUBSCRIBED, NEXT_YEAR,
ARCHIVED, HISTORICAL,
fetchRegisterForms, fetchRegisterForms,
createRegisterForm,
sendRegisterForm, sendRegisterForm,
archiveRegisterForm, archiveRegisterForm,
fetchStudents,
editRegisterForm, editRegisterForm,
editRegisterFormWithBinaryFile, editRegisterFormWithBinaryFile,
} from '@/app/actions/subscriptionAction'; } from '@/app/actions/subscriptionAction';
import { import { fetchClasses, updateDatas } from '@/app/actions/schoolAction';
fetchRegistrationSchoolFileMasters,
fetchRegistrationParentFileMasters,
createRegistrationSchoolFileTemplate,
createRegistrationParentFileTemplate,
fetchRegistrationFileGroups,
cloneTemplate,
} from '@/app/actions/registerFileGroupAction';
import {
fetchClasses,
fetchRegistrationDiscounts,
fetchTuitionDiscounts,
fetchRegistrationFees,
fetchTuitionFees,
} from '@/app/actions/schoolAction';
import { fetchProfiles } from '@/app/actions/authAction'; import { fetchProfiles } from '@/app/actions/authAction';
import { import {
FE_ADMIN_SUBSCRIPTIONS_EDIT_URL, FE_ADMIN_SUBSCRIPTIONS_EDIT_URL,
FE_ADMIN_SUBSCRIPTIONS_VALIDATE_URL, FE_ADMIN_SUBSCRIPTIONS_VALIDATE_URL,
FE_ADMIN_SUBSCRIPTIONS_CREATE_URL,
BASE_URL, BASE_URL,
} from '@/utils/Url'; } from '@/utils/Url';
@ -68,41 +52,41 @@ import logger from '@/utils/logger';
import { PhoneLabel } from '@/components/PhoneLabel'; import { PhoneLabel } from '@/components/PhoneLabel';
import FileUpload from '@/components/FileUpload'; import FileUpload from '@/components/FileUpload';
import FilesModal from '@/components/Inscription/FilesModal'; import FilesModal from '@/components/Inscription/FilesModal';
import {
getCurrentSchoolYear,
getNextSchoolYear,
getHistoricalYears,
} from '@/utils/Date';
export default function Page({ params: { locale } }) { export default function Page({ params: { locale } }) {
const t = useTranslations('subscriptions'); const t = useTranslations('subscriptions');
const [registrationForms, setRegistrationForms] = useState([]); const [registrationForms, setRegistrationForms] = useState([]);
const [registrationFormsDataPending, setRegistrationFormsDataPending] = const [
registrationFormsDataCurrentYear,
setRegistrationFormsDataCurrentYear,
] = useState([]);
const [registrationFormsDataNextYear, setRegistrationFormsDataNextYear] =
useState([]); useState([]);
const [registrationFormsDataSubscribed, setRegistrationFormsDataSubscribed] = const [registrationFormsDataHistorical, setRegistrationFormsDataHistorical] =
useState([]); useState([]);
const [registrationFormsDataArchived, setRegistrationFormsDataArchived] = const currentSchoolYear = getCurrentSchoolYear(); // Exemple : "2024-2025"
useState([]); const nextSchoolYear = getNextSchoolYear(); // Exemple : "2025-2026"
// const [filter, setFilter] = useState('*'); const historicalYears = getHistoricalYears();
const [searchTerm, setSearchTerm] = useState(''); const [searchTerm, setSearchTerm] = useState('');
const [alertPage, setAlertPage] = useState(false); const [alertPage, setAlertPage] = useState(false);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [activeTab, setActiveTab] = useState('pending'); const [activeTab, setActiveTab] = useState('currentYear');
const [currentPage, setCurrentPage] = useState(1); const [currentPage, setCurrentPage] = useState(1);
const [totalPages, setTotalPages] = useState(1); const [totalPages, setTotalPages] = useState(1);
const [totalPending, setTotalPending] = useState(0); const [totalCurrentYear, setTotalCurrentYear] = useState(0);
const [totalSubscribed, setTotalSubscribed] = useState(0); const [totalSubscribed, setTotalNextYear] = useState(0);
const [totalArchives, setTotalArchives] = useState(0); const [totalHistorical, setTotalHistorical] = useState(0);
const [itemsPerPage, setItemsPerPage] = useState(10); // Définir le nombre d'éléments par page const [itemsPerPage, setItemsPerPage] = useState(10); // Définir le nombre d'éléments par page
const [schoolFileMasters, setSchoolFileMasters] = useState([]);
const [parentFileMasters, setParentFileMasters] = useState([]);
const [isOpen, setIsOpen] = useState(false);
const [student, setStudent] = useState(''); const [student, setStudent] = useState('');
const [classes, setClasses] = useState([]); const [classes, setClasses] = useState([]);
const [students, setEleves] = useState([]);
const [reloadFetch, setReloadFetch] = useState(false); const [reloadFetch, setReloadFetch] = useState(false);
const [registrationDiscounts, setRegistrationDiscounts] = useState([]);
const [tuitionDiscounts, setTuitionDiscounts] = useState([]);
const [registrationFees, setRegistrationFees] = useState([]);
const [tuitionFees, setTuitionFees] = useState([]);
const [groups, setGroups] = useState([]);
const [profiles, setProfiles] = useState([]); const [profiles, setProfiles] = useState([]);
const [isOpenAddGuardian, setIsOpenAddGuardian] = useState(false); const [isOpenAddGuardian, setIsOpenAddGuardian] = useState(false);
@ -132,14 +116,6 @@ export default function Page({ params: { locale } }) {
setIsSepaUploadModalOpen(false); setIsSepaUploadModalOpen(false);
}; };
const openModal = () => {
setIsOpen(true);
};
const closeModal = () => {
setIsOpen(false);
};
const openFilesModal = (row) => { const openFilesModal = (row) => {
setSelectedRegisterForm(row || []); setSelectedRegisterForm(row || []);
setIsFilesModalOpen(true); setIsFilesModalOpen(true);
@ -160,11 +136,11 @@ export default function Page({ params: { locale } }) {
if (data) { if (data) {
const { registerForms, count, page_size } = data; const { registerForms, count, page_size } = data;
if (registerForms) { if (registerForms) {
setRegistrationFormsDataPending(registerForms); setRegistrationFormsDataCurrentYear(registerForms);
} }
const calculatedTotalPages = const calculatedTotalPages =
count === 0 ? 1 : Math.ceil(count / page_size); count === 0 ? 1 : Math.ceil(count / page_size);
setTotalPending(count); setTotalCurrentYear(count);
setTotalPages(calculatedTotalPages); setTotalPages(calculatedTotalPages);
} }
}; };
@ -179,9 +155,9 @@ export default function Page({ params: { locale } }) {
const registerFormSubscribedDataHandler = (data) => { const registerFormSubscribedDataHandler = (data) => {
if (data) { if (data) {
const { registerForms, count, page_size } = data; const { registerForms, count, page_size } = data;
setTotalSubscribed(count); setTotalNextYear(count);
if (registerForms) { if (registerForms) {
setRegistrationFormsDataSubscribed(registerForms); setRegistrationFormsDataNextYear(registerForms);
} }
} }
}; };
@ -197,9 +173,9 @@ export default function Page({ params: { locale } }) {
if (data) { if (data) {
const { registerForms, count, page_size } = data; const { registerForms, count, page_size } = data;
setTotalArchives(count); setTotalHistorical(count);
if (registerForms) { if (registerForms) {
setRegistrationFormsDataArchived(registerForms); setRegistrationFormsDataHistorical(registerForms);
} }
} }
}; };
@ -211,7 +187,7 @@ export default function Page({ params: { locale } }) {
Promise.all([ Promise.all([
fetchRegisterForms( fetchRegisterForms(
selectedEstablishmentId, selectedEstablishmentId,
PENDING, CURRENT_YEAR,
currentPage, currentPage,
itemsPerPage, itemsPerPage,
searchTerm searchTerm
@ -225,59 +201,12 @@ export default function Page({ params: { locale } }) {
}) })
.catch(requestErrorHandler), .catch(requestErrorHandler),
fetchStudents(selectedEstablishmentId) fetchRegisterForms(selectedEstablishmentId, NEXT_YEAR)
.then((studentsData) => {
setEleves(studentsData);
})
.catch(requestErrorHandler),
fetchRegisterForms(selectedEstablishmentId, SUBSCRIBED)
.then(registerFormSubscribedDataHandler) .then(registerFormSubscribedDataHandler)
.catch(requestErrorHandler), .catch(requestErrorHandler),
fetchRegisterForms(selectedEstablishmentId, ARCHIVED) fetchRegisterForms(selectedEstablishmentId, HISTORICAL)
.then(registerFormArchivedDataHandler) .then(registerFormArchivedDataHandler)
.catch(requestErrorHandler), .catch(requestErrorHandler),
fetchRegistrationSchoolFileMasters()
.then((data) => {
setSchoolFileMasters(data);
})
.catch((err) => {
logger.debug(err.message);
}),
fetchRegistrationParentFileMasters()
.then((data) => {
setParentFileMasters(data);
})
.catch((err) => {
logger.debug(err.message);
}),
fetchRegistrationDiscounts(selectedEstablishmentId)
.then((data) => {
setRegistrationDiscounts(data);
})
.catch(requestErrorHandler),
fetchTuitionDiscounts(selectedEstablishmentId)
.then((data) => {
setTuitionDiscounts(data);
})
.catch(requestErrorHandler),
fetchRegistrationFees(selectedEstablishmentId)
.then((data) => {
setRegistrationFees(data);
})
.catch(requestErrorHandler),
fetchTuitionFees(selectedEstablishmentId)
.then((data) => {
setTuitionFees(data);
})
.catch(requestErrorHandler),
fetchRegistrationFileGroups(selectedEstablishmentId)
.then((data) => {
setGroups(data);
})
.catch((error) => {
logger.error('Error fetching file groups:', error);
}),
fetchProfiles() fetchProfiles()
.then((data) => { .then((data) => {
setProfiles(data); setProfiles(data);
@ -307,27 +236,19 @@ export default function Page({ params: { locale } }) {
setIsLoading(true); setIsLoading(true);
fetchRegisterForms( fetchRegisterForms(
selectedEstablishmentId, selectedEstablishmentId,
PENDING, CURRENT_YEAR,
currentPage, currentPage,
itemsPerPage, itemsPerPage,
searchTerm searchTerm
) )
.then(registerFormPendingDataHandler) .then(registerFormPendingDataHandler)
.catch(requestErrorHandler); .catch(requestErrorHandler);
fetchRegisterForms(selectedEstablishmentId, SUBSCRIBED) fetchRegisterForms(selectedEstablishmentId, NEXT_YEAR)
.then(registerFormSubscribedDataHandler) .then(registerFormSubscribedDataHandler)
.catch(requestErrorHandler); .catch(requestErrorHandler);
fetchRegisterForms(selectedEstablishmentId, ARCHIVED) fetchRegisterForms(selectedEstablishmentId, HISTORICAL)
.then(registerFormArchivedDataHandler) .then(registerFormArchivedDataHandler)
.catch(requestErrorHandler); .catch(requestErrorHandler);
fetchRegistrationSchoolFileMasters()
.then((data) => {
setSchoolFileMasters(data);
})
.catch((err) => {
err = err.message;
logger.debug(err);
});
setIsLoading(false); setIsLoading(false);
setReloadFetch(false); setReloadFetch(false);
@ -344,12 +265,12 @@ export default function Page({ params: { locale } }) {
* UseEffect to update page count of tab * UseEffect to update page count of tab
*/ */
useEffect(() => { useEffect(() => {
if (activeTab === 'pending') { if (activeTab === 'currentYear') {
setTotalPages(Math.ceil(totalPending / itemsPerPage)); setTotalPages(Math.ceil(totalCurrentYear / itemsPerPage));
} else if (activeTab === 'subscribed') { } else if (activeTab === 'nextYear') {
setTotalPages(Math.ceil(totalSubscribed / itemsPerPage)); setTotalPages(Math.ceil(totalSubscribed / itemsPerPage));
} else if (activeTab === 'archived') { } else if (activeTab === 'historical') {
setTotalPages(Math.ceil(totalArchives / itemsPerPage)); setTotalPages(Math.ceil(totalHistorical / itemsPerPage));
} }
}, [currentPage]); }, [currentPage]);
@ -460,187 +381,6 @@ export default function Page({ params: { locale } }) {
setCurrentPage(newPage); setCurrentPage(newPage);
}; };
const createRF = (updatedData) => {
logger.debug('createRF updatedData:', updatedData);
const selectedRegistrationFeesIds =
updatedData.selectedRegistrationFees.map((feeId) => feeId);
const selectedRegistrationDiscountsIds =
updatedData.selectedRegistrationDiscounts.map((discountId) => discountId);
const selectedTuitionFeesIds = updatedData.selectedTuitionFees.map(
(feeId) => feeId
);
const selectedTuitionDiscountsIds =
updatedData.selectedTuitionDiscounts.map((discountId) => discountId);
const selectedFileGroup = updatedData.selectedFileGroup;
const allFeesIds = [
...selectedRegistrationFeesIds,
...selectedTuitionFeesIds,
];
const allDiscountsds = [
...selectedRegistrationDiscountsIds,
...selectedTuitionDiscountsIds,
];
const data = {
student: {
last_name: updatedData.studentLastName,
first_name: updatedData.studentFirstName,
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,
},
];
})(),
sibling: [],
},
fees: allFeesIds,
discounts: allDiscountsds,
fileGroup: selectedFileGroup,
establishment: selectedEstablishmentId,
};
setIsLoading(true);
createRegisterForm(data, csrfToken)
.then((data) => {
// Cloner les schoolFileTemplates pour chaque templateMaster du fileGroup
const masters = schoolFileMasters.filter((file) =>
file.groups.includes(selectedFileGroup)
);
const parent_masters = parentFileMasters.filter((file) =>
file.groups.includes(selectedFileGroup)
);
const clonePromises = masters.map((templateMaster) => {
return cloneTemplate(
templateMaster.id,
updatedData.guardianEmail,
templateMaster.is_required
)
.then((clonedDocument) => {
// Sauvegarde des schoolFileTemplates clonés dans la base de données
const cloneData = {
name: `${templateMaster.name}_${updatedData.studentFirstName}_${updatedData.studentLastName}`,
slug: clonedDocument.slug,
id: clonedDocument.id,
master: templateMaster.id,
registration_form: data.student.id,
};
return createRegistrationSchoolFileTemplate(cloneData, csrfToken)
.then((response) => {
logger.debug('Template enregistré avec succès:', response);
})
.catch((error) => {
setIsLoading(false);
logger.error(
"Erreur lors de l'enregistrement du template:",
error
);
});
})
.catch((error) => {
setIsLoading(false);
logger.error('Error during cloning or sending:', error);
});
});
// Créer les parentFileTemplates pour chaque parentMaster
const parentClonePromises = parent_masters.map((parentMaster) => {
const parentTemplateData = {
master: parentMaster.id,
registration_form: data.student.id,
};
return createRegistrationParentFileTemplate(
parentTemplateData,
csrfToken
)
.then((response) => {
logger.debug('Parent template enregistré avec succès:', response);
})
.catch((error) => {
setIsLoading(false);
logger.error(
"Erreur lors de l'enregistrement du parent template:",
error
);
});
});
// Attendre que tous les clones (school et parent) soient créés
Promise.all([...clonePromises, ...parentClonePromises])
.then(() => {
// 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
);
}
closeModal(); // Appeler closeModal ici après que tout soit terminé
// Forcer le rechargement complet des données
setReloadFetch(true);
setIsLoading(false);
})
.catch((error) => {
setIsLoading(false);
logger.error('Error during cloning or sending:', error);
});
})
.catch((error) => {
setIsLoading(false);
logger.error('Error:', error);
});
};
const updateRF = (updatedData) => { const updateRF = (updatedData) => {
logger.debug('updateRF updatedData:', updatedData); logger.debug('updateRF updatedData:', updatedData);
@ -700,11 +440,11 @@ export default function Page({ params: { locale } }) {
editRegisterForm(student.id, data, csrfToken) editRegisterForm(student.id, data, csrfToken)
.then((data) => { .then((data) => {
// Mise à jour immédiate des données // Mise à jour immédiate des données
setRegistrationFormsDataPending((prevState) => [ setRegistrationFormsDataCurrentYear((prevState) => [
...(prevState || []), ...(prevState || []),
data, data,
]); ]);
setTotalPending((prev) => prev + 1); setTotalCurrentYear((prev) => prev + 1);
if (updatedData.autoMail) { if (updatedData.autoMail) {
sendConfirmRegisterForm( sendConfirmRegisterForm(
data.student.id, data.student.id,
@ -980,7 +720,7 @@ export default function Page({ params: { locale } }) {
} else { } else {
if ( if (
registrationForms.length === 0 && registrationForms.length === 0 &&
registrationFormsDataArchived.length === 0 && registrationFormsDataHistorical.length === 0 &&
alertPage alertPage
) { ) {
return ( return (
@ -997,49 +737,54 @@ export default function Page({ params: { locale } }) {
<div className="p-8"> <div className="p-8">
<div className="border-b border-gray-200 mb-6"> <div className="border-b border-gray-200 mb-6">
<div className="flex items-center gap-8"> <div className="flex items-center gap-8">
{/* Tab pour l'année scolaire en cours */}
<Tab <Tab
text={ text={
<> <>
{t('pending')} {currentSchoolYear}
<span className="ml-2 text-sm text-gray-400"> <span className="ml-2 text-sm text-gray-400">
({totalPending}) ({totalCurrentYear})
</span> </span>
</> </>
} }
active={activeTab === 'pending'} active={activeTab === 'currentYear'}
onClick={() => setActiveTab('pending')} onClick={() => setActiveTab('currentYear')}
/> />
{/* Tab pour l'année scolaire prochaine */}
<Tab <Tab
text={ text={
<> <>
{t('subscribed')} {nextSchoolYear}
<span className="ml-2 text-sm text-gray-400"> <span className="ml-2 text-sm text-gray-400">
({totalSubscribed}) ({totalSubscribed})
</span> </span>
</> </>
} }
active={activeTab === 'subscribed'} active={activeTab === 'nextYear'}
onClick={() => setActiveTab('subscribed')} onClick={() => setActiveTab('nextYear')}
/> />
{/* Tab pour l'historique */}
<Tab <Tab
text={ text={
<> <>
{t('archived')} {t('historical')}
<span className="ml-2 text-sm text-gray-400"> <span className="ml-2 text-sm text-gray-400">
({totalArchives}) ({totalHistorical})
</span> </span>
</> </>
} }
active={activeTab === 'archived'} active={activeTab === 'historical'}
onClick={() => setActiveTab('archived')} onClick={() => setActiveTab('historical')}
/> />
</div> </div>
</div> </div>
<div className="border-b border-gray-200 mb-6 w-full"> <div className="border-b border-gray-200 mb-6 w-full">
{/*SI STATE == pending || subscribe || archived */} {activeTab === 'currentYear' ||
{activeTab === 'pending' || activeTab === 'nextYear' ||
activeTab === 'subscribed' || activeTab === 'historical' ? (
activeTab === 'archived' ? (
<React.Fragment> <React.Fragment>
<div className="flex justify-between items-center mb-4 w-full"> <div className="flex justify-between items-center mb-4 w-full">
<div className="relative flex-grow"> <div className="relative flex-grow">
@ -1056,7 +801,10 @@ export default function Page({ params: { locale } }) {
/> />
</div> </div>
<button <button
onClick={openModal} onClick={() => {
const url = `${FE_ADMIN_SUBSCRIPTIONS_CREATE_URL}`;
router.push(url);
}}
className="flex items-center bg-emerald-600 text-white p-2 rounded-full shadow hover:bg-emerald-900 transition duration-200 ml-4" className="flex items-center bg-emerald-600 text-white p-2 rounded-full shadow hover:bg-emerald-900 transition duration-200 ml-4"
> >
<Plus className="w-5 h-5" /> <Plus className="w-5 h-5" />
@ -1068,18 +816,13 @@ export default function Page({ params: { locale } }) {
<Table <Table
key={`${currentPage}-${searchTerm}`} key={`${currentPage}-${searchTerm}`}
data={ data={
activeTab === 'pending' activeTab === 'currentYear'
? [ ? registrationFormsDataCurrentYear
...registrationFormsDataPending, : activeTab === 'nextYear'
...registrationFormsDataSubscribed, ? registrationFormsDataNextYear
] : registrationFormsDataHistorical
: activeTab === 'subscribed'
? registrationFormsDataSubscribed
: registrationFormsDataArchived
}
columns={
activeTab === 'subscribed' ? columnsSubscribed : columns
} }
columns={columns}
itemsPerPage={itemsPerPage} itemsPerPage={itemsPerPage}
currentPage={currentPage} currentPage={currentPage}
totalPages={totalPages} totalPages={totalPages}
@ -1102,27 +845,6 @@ export default function Page({ params: { locale } }) {
onCancel={() => setConfirmPopupVisible(false)} onCancel={() => setConfirmPopupVisible(false)}
/> />
{isOpen && (
<Modal
isOpen={isOpen}
setIsOpen={setIsOpen}
title={"Nouveau dossier d'inscription"}
ContentComponent={() => (
<InscriptionForm
students={students}
registrationDiscounts={registrationDiscounts}
tuitionDiscounts={tuitionDiscounts}
registrationFees={registrationFees.filter(
(fee) => fee.is_active
)}
tuitionFees={tuitionFees.filter((fee) => fee.is_active)}
groups={groups}
profiles={profiles}
onSubmit={createRF}
/>
)}
/>
)}
{isSepaUploadModalOpen && ( {isSepaUploadModalOpen && (
<Modal <Modal
isOpen={isSepaUploadModalOpen} isOpen={isSepaUploadModalOpen}

View File

@ -6,9 +6,9 @@ import {
BE_SUBSCRIPTION_ABSENCES_URL, BE_SUBSCRIPTION_ABSENCES_URL,
} from '@/utils/Url'; } from '@/utils/Url';
export const PENDING = 'pending'; export const CURRENT_YEAR = 'current_year';
export const SUBSCRIBED = 'subscribed'; export const NEXT_YEAR = 'next_year';
export const ARCHIVED = 'archived'; export const HISTORICAL = 'historical';
const requestResponseHandler = async (response) => { const requestResponseHandler = async (response) => {
const body = await response.json(); const body = await response.json();
@ -23,7 +23,7 @@ const requestResponseHandler = async (response) => {
export const fetchRegisterForms = ( export const fetchRegisterForms = (
establishment, establishment,
filter = PENDING, filter = CURRENT_YEAR,
page = '', page = '',
pageSize = '', pageSize = '',
search = '' search = ''

View File

@ -6,8 +6,11 @@ export default function InputTextIcon({
value, value,
onChange, onChange,
errorMsg, errorMsg,
errorLocalMsg,
placeholder, placeholder,
className, className,
required,
enable = true, // Nouvelle prop pour activer/désactiver le champ
}) { }) {
return ( return (
<> <>
@ -17,9 +20,16 @@ export default function InputTextIcon({
className="block text-sm font-medium text-gray-700" className="block text-sm font-medium text-gray-700"
> >
{label} {label}
{required && <span className="text-red-500 ml-1">*</span>}
</label> </label>
<div <div
className={`mt-1 flex items-stretch 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' : ''} ${
!enable ? 'bg-gray-100 cursor-not-allowed' : ''
}`}
> >
<span className="inline-flex items-center px-3 rounded-l-md bg-gray-50 text-gray-500 text-sm"> <span className="inline-flex items-center px-3 rounded-l-md bg-gray-50 text-gray-500 text-sm">
{IconItem && <IconItem />} {IconItem && <IconItem />}
@ -30,8 +40,12 @@ export default function InputTextIcon({
placeholder={placeholder} placeholder={placeholder}
name={name} name={name}
value={value} value={value}
onChange={onChange} onChange={enable ? onChange : undefined}
className="flex-1 px-3 py-2 block w-full rounded-r-md sm:text-sm border-none focus:ring-0 outline-none" className={`flex-1 px-3 py-2 block w-full sm:text-sm border-none focus:ring-0 outline-none rounded-md ${
!enable ? 'bg-gray-100 cursor-not-allowed' : ''
}`}
required={required}
readOnly={!enable ? 'readOnly' : ''}
/> />
</div> </div>
{errorMsg && <p className="mt-2 text-sm text-red-600">{errorMsg}</p>} {errorMsg && <p className="mt-2 text-sm text-red-600">{errorMsg}</p>}

View File

@ -14,6 +14,8 @@ import InputPhone from '../InputPhone';
import { PhoneLabel } from '../PhoneLabel'; import { PhoneLabel } from '../PhoneLabel';
import CheckBox from '@/components/CheckBox'; import CheckBox from '@/components/CheckBox';
import RadioList from '@/components/RadioList'; import RadioList from '@/components/RadioList';
import SelectChoice from '@/components/SelectChoice';
import { getCurrentSchoolYear, getNextSchoolYear } from '@/utils/Date';
const InscriptionForm = ({ const InscriptionForm = ({
students, students,
@ -52,6 +54,7 @@ const InscriptionForm = ({
selectedTuitionDiscounts: [], selectedTuitionDiscounts: [],
selectedTuitionFees: [], selectedTuitionFees: [],
selectedFileGroup: null, // Ajout du groupe de fichiers sélectionné selectedFileGroup: null, // Ajout du groupe de fichiers sélectionné
schoolYear: getCurrentSchoolYear(),
}; };
}); });
@ -390,6 +393,24 @@ const InscriptionForm = ({
{step === 1 && ( {step === 1 && (
<div className="mt-6"> <div className="mt-6">
{/* Sélection de l'année scolaire */}
<SelectChoice
name="schoolYear"
label="Année scolaire"
placeHolder="Sélectionnez une année scolaire"
choices={[
{ value: getCurrentSchoolYear(), label: getCurrentSchoolYear() },
{ value: getNextSchoolYear(), label: getNextSchoolYear() },
]}
selected={formData.schoolYear || getCurrentSchoolYear()}
callback={(e) =>
setFormData((prevData) => ({
...prevData,
schoolYear: e.target.value,
}))
}
className="w-full mt-4"
/>
<InputTextIcon <InputTextIcon
name="studentLastName" name="studentLastName"
type="text" type="text"

View File

@ -8,23 +8,7 @@ import SectionHeader from '@/components/SectionHeader';
import { User } from 'lucide-react'; import { User } from 'lucide-react';
import FileUpload from '@/components/FileUpload'; import FileUpload from '@/components/FileUpload';
import { BASE_URL } from '@/utils/Url'; import { BASE_URL } from '@/utils/Url';
import { levels, genders } from '@/utils/constants';
const levels = [
{ value: '1', label: 'TPS - Très Petite Section' },
{ value: '2', label: 'PS - Petite Section' },
{ value: '3', label: 'MS - Moyenne Section' },
{ value: '4', label: 'GS - Grande Section' },
{ value: '5', label: 'CP' },
{ value: '6', label: 'CE1' },
{ value: '7', label: 'CE2' },
{ value: '8', label: 'CM1' },
{ 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,

View File

@ -1,13 +1,20 @@
import React from 'react'; import React from 'react';
const SectionTitle = ({ title }) => { const SectionTitle = ({ title, children }) => {
return ( return (
<div className="relative mb-4"> <div className="relative flex">
<div className="absolute inset-0 flex items-center"> {/* Liseré vertical */}
<div className="w-full border-t border-gray-300"></div> <div className="w-1 bg-emerald-400"></div>
</div>
<div className="relative flex justify-center"> {/* Contenu de la section */}
<div className="px-4 bg-white text-gray-500">{title}</div> <div className="flex-1 pl-6">
{/* Titre avec liseré horizontal */}
<div className="flex items-center mb-4">
<h2 className="text-emerald-700 font-bold text-xl">{title}</h2>
<div className="flex-1 h-0.5 bg-emerald-200 ml-4"></div>
</div>
{/* Contenu passé en children */}
{children}
</div> </div>
</div> </div>
); );

View File

@ -345,15 +345,17 @@ const DiscountsSection = ({
]; ];
return ( return (
<div className="space-y-4 mt-8"> <div className="space-y-4">
<SectionHeader {!subscriptionMode && (
icon={Tag} <SectionHeader
discountStyle={true} icon={Tag}
title={`${type == 0 ? "Liste des réductions sur les frais d'inscription" : 'Liste des réductions sur les frais de scolarité'}`} discountStyle={true}
description={`${subscriptionMode ? 'Sélectionnez' : 'Gérez'} ${type == 0 ? " vos réductions sur les frais d'inscription" : ' vos réductions sur les frais de scolarité'}`} title={`${type == 0 ? "Liste des réductions sur les frais d'inscription" : 'Liste des réductions sur les frais de scolarité'}`}
button={!subscriptionMode} description={`'Gérez' ${type == 0 ? " vos réductions sur les frais d'inscription" : ' vos réductions sur les frais de scolarité'}`}
onClick={handleAddDiscount} button={!subscriptionMode}
/> onClick={handleAddDiscount}
/>
)}
<Table <Table
data={newDiscount ? [newDiscount, ...discounts] : discounts} data={newDiscount ? [newDiscount, ...discounts] : discounts}
columns={columns} columns={columns}

View File

@ -321,13 +321,15 @@ const FeesSection = ({
return ( return (
<div className="space-y-4"> <div className="space-y-4">
<SectionHeader {!subscriptionMode && (
icon={CreditCard} <SectionHeader
title={`${type == 0 ? "Liste des frais d'inscription" : 'Liste des frais de scolarité'}`} icon={CreditCard}
description={`${subscriptionMode ? 'Sélectionnez' : 'Gérez'} ${type == 0 ? " vos frais d'inscription" : ' vos frais de scolarité'}`} title={`${type == 0 ? "Liste des frais d'inscription" : 'Liste des frais de scolarité'}`}
button={!subscriptionMode} description={`'Gérez'${type == 0 ? " vos frais d'inscription" : ' vos frais de scolarité'}`}
onClick={handleAddFee} button={!subscriptionMode}
/> onClick={handleAddFee}
/>
)}
<Table <Table
data={newFee ? [newFee, ...fees] : fees} data={newFee ? [newFee, ...fees] : fees}
columns={columns} columns={columns}

View File

@ -99,3 +99,45 @@ export const isWeekend = (date) => {
const day = date.getDay(); const day = date.getDay();
return day === 0 || day === 6; return day === 0 || day === 6;
}; };
/**
* Retourne l'année scolaire en cours au format "YYYY-YYYY".
* Exemple : Si nous sommes en octobre 2023, retourne "2023-2024".
*/
export function getCurrentSchoolYear() {
const now = new Date();
const currentYear = now.getFullYear();
const currentMonth = now.getMonth() + 1; // Les mois sont indexés à partir de 0
// Si nous sommes avant septembre, l'année scolaire a commencé l'année précédente
const startYear = currentMonth >= 9 ? currentYear : currentYear - 1;
return `${startYear}-${startYear + 1}`;
}
/**
* Retourne l'année scolaire suivante au format "YYYY-YYYY".
* Exemple : Si nous sommes en octobre 2023, retourne "2024-2025".
*/
export function getNextSchoolYear() {
const currentSchoolYear = getCurrentSchoolYear();
const [startYear, endYear] = currentSchoolYear.split('-').map(Number);
return `${startYear + 1}-${endYear + 1}`;
}
/**
* Retourne un tableau des années scolaires passées au format "YYYY-YYYY".
* Exemple : ["2022-2023", "2021-2022", "2020-2021"].
* @param {number} count - Le nombre d'années scolaires passées à inclure.
*/
export function getHistoricalYears(count = 5) {
const currentSchoolYear = getCurrentSchoolYear();
const [startYear] = currentSchoolYear.split('-').map(Number);
const historicalYears = [];
for (let i = 1; i <= count; i++) {
const historicalStartYear = startYear - i;
historicalYears.push(`${historicalStartYear}-${historicalStartYear + 1}`);
}
return historicalYears;
}

View File

@ -68,6 +68,7 @@ export const FE_ADMIN_HOME_URL = `/admin`;
// ADMIN/SUBSCRIPTIONS URL // ADMIN/SUBSCRIPTIONS URL
export const FE_ADMIN_SUBSCRIPTIONS_URL = `/admin/subscriptions`; export const FE_ADMIN_SUBSCRIPTIONS_URL = `/admin/subscriptions`;
export const FE_ADMIN_SUBSCRIPTIONS_CREATE_URL = `/admin/subscriptions/createSubscription`;
export const FE_ADMIN_SUBSCRIPTIONS_EDIT_URL = `/admin/subscriptions/editInscription`; export const FE_ADMIN_SUBSCRIPTIONS_EDIT_URL = `/admin/subscriptions/editInscription`;
export const FE_ADMIN_SUBSCRIPTIONS_VALIDATE_URL = `/admin/subscriptions/validateSubscription`; export const FE_ADMIN_SUBSCRIPTIONS_VALIDATE_URL = `/admin/subscriptions/validateSubscription`;

View File

@ -0,0 +1,16 @@
export const levels = [
{ value: '1', label: 'TPS - Très Petite Section' },
{ value: '2', label: 'PS - Petite Section' },
{ value: '3', label: 'MS - Moyenne Section' },
{ value: '4', label: 'GS - Grande Section' },
{ value: '5', label: 'CP' },
{ value: '6', label: 'CE1' },
{ value: '7', label: 'CE2' },
{ value: '8', label: 'CM1' },
{ value: '9', label: 'CM2' },
];
export const genders = [
{ value: '1', label: 'Garçon' },
{ value: '2', label: 'Fille' },
];