mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-01-29 07:53:23 +00:00
feat: Ordonnancement de l'inscription sur plusieurs pages + contrôle des
champs remplis dans le formulaire
This commit is contained in:
@ -1,10 +1,8 @@
|
||||
// Import des dépendances nécessaires
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import Loader from '@/components/Loader';
|
||||
import Button from '@/components/Button';
|
||||
import DjangoCSRFToken from '@/components/DjangoCSRFToken';
|
||||
import {
|
||||
fetchRegisterForm,
|
||||
fetchSchoolFileTemplatesFromRegistrationFiles,
|
||||
fetchParentFileTemplatesFromRegistrationFiles,
|
||||
} from '@/app/actions/subscriptionAction';
|
||||
@ -19,11 +17,12 @@ import {
|
||||
} from '@/app/actions/schoolAction';
|
||||
import { BASE_URL } from '@/utils/Url';
|
||||
import logger from '@/utils/logger';
|
||||
import StudentInfoForm, {
|
||||
validateStudentInfo,
|
||||
} from '@/components/Inscription/StudentInfoForm';
|
||||
import FilesToUpload from '@/components/Inscription/FilesToUpload';
|
||||
import { DocusealForm } from '@docuseal/react';
|
||||
import StudentInfoForm from '@/components/Inscription/StudentInfoForm';
|
||||
import ResponsableInputFields from '@/components/Inscription/ResponsableInputFields';
|
||||
import PaymentMethodSelector from '@/components/Inscription/PaymentMethodSelector';
|
||||
import ProgressStep from '@/components/ProgressStep';
|
||||
|
||||
/**
|
||||
* Composant de formulaire d'inscription partagé
|
||||
@ -41,7 +40,6 @@ export default function InscriptionFormShared({
|
||||
errors = {}, // Nouvelle prop pour les erreurs
|
||||
}) {
|
||||
// États pour gérer les données du formulaire
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [formData, setFormData] = useState({
|
||||
id: '',
|
||||
last_name: '',
|
||||
@ -67,44 +65,13 @@ export default function InscriptionFormShared({
|
||||
const [schoolFileTemplates, setSchoolFileTemplates] = useState([]);
|
||||
const [parentFileTemplates, setParentFileTemplates] = useState([]);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [isSignatureComplete, setIsSignatureComplete] = useState(false);
|
||||
|
||||
const isCurrentPageValid = () => {
|
||||
if (currentPage === 1) {
|
||||
const isValid = validateStudentInfo(formData);
|
||||
return isValid;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
const [isPage1Valid, setIsPage1Valid] = useState(false);
|
||||
const [isPage2Valid, setIsPage2Valid] = useState(false);
|
||||
const [isPage3Valid, setIsPage3Valid] = useState(false);
|
||||
|
||||
// Chargement initial des données
|
||||
// Mettre à jour les données quand initialData change
|
||||
useEffect(() => {
|
||||
if (studentId) {
|
||||
fetchRegisterForm(studentId).then((data) => {
|
||||
logger.debug(data);
|
||||
|
||||
setFormData({
|
||||
id: data?.student?.id || '',
|
||||
last_name: data?.student?.last_name || '',
|
||||
first_name: data?.student?.first_name || '',
|
||||
address: data?.student?.address || '',
|
||||
birth_date: data?.student?.birth_date || '',
|
||||
birth_place: data?.student?.birth_place || '',
|
||||
birth_postal_code: data?.student?.birth_postal_code || '',
|
||||
nationality: data?.student?.nationality || '',
|
||||
attending_physician: data?.student?.attending_physician || '',
|
||||
level: data?.student?.level || '',
|
||||
registration_payment: data?.registration_payment || '',
|
||||
tuition_payment: data?.tuition_payment || '',
|
||||
totalRegistrationFees: data?.totalRegistrationFees,
|
||||
totalTuitionFees: data?.totalTuitionFees,
|
||||
});
|
||||
setGuardians(data?.student?.guardians || []);
|
||||
});
|
||||
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [studentId]);
|
||||
const [hasInteracted, setHasInteracted] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
fetchSchoolFileTemplatesFromRegistrationFiles(studentId).then((data) => {
|
||||
@ -159,11 +126,6 @@ export default function InscriptionFormShared({
|
||||
);
|
||||
};
|
||||
|
||||
// Fonctions de gestion du formulaire et des fichiers
|
||||
const updateFormField = (field, value) => {
|
||||
setFormData((prev) => ({ ...prev, [field]: value }));
|
||||
};
|
||||
|
||||
const handleFileUpload = (file, selectedFile) => {
|
||||
if (!file || !selectedFile) {
|
||||
logger.error('Données manquantes pour le téléversement.');
|
||||
@ -265,19 +227,6 @@ export default function InscriptionFormShared({
|
||||
onSubmit(data);
|
||||
};
|
||||
|
||||
// Soumission du formulaire
|
||||
const handleSave = (e) => {
|
||||
e.preventDefault();
|
||||
const data = {
|
||||
student: {
|
||||
...formData,
|
||||
guardians,
|
||||
},
|
||||
establishment: selectedEstablishmentId,
|
||||
};
|
||||
onSubmit(data);
|
||||
};
|
||||
|
||||
const handleNextPage = () => {
|
||||
setCurrentPage(currentPage + 1);
|
||||
};
|
||||
@ -286,96 +235,163 @@ export default function InscriptionFormShared({
|
||||
setCurrentPage(currentPage - 1);
|
||||
};
|
||||
|
||||
// Affichage du loader pendant le chargement
|
||||
if (isLoading) return <Loader />;
|
||||
const stepTitles = {
|
||||
1: 'Elève',
|
||||
2: 'Responsables légaux',
|
||||
3: "Modalités de paiement",
|
||||
4: "Formulaires à signer",
|
||||
5: "Pièces à fournir",
|
||||
6: "Validation"
|
||||
};
|
||||
|
||||
const steps = [
|
||||
'Élève',
|
||||
'Responsable',
|
||||
'Paiement',
|
||||
'Formulaires',
|
||||
'Documents parent',
|
||||
'Validation'
|
||||
];
|
||||
|
||||
const isStepValid = (stepNumber) => {
|
||||
switch (stepNumber) {
|
||||
case 1:
|
||||
return isPage1Valid;
|
||||
case 2:
|
||||
return isPage2Valid;
|
||||
case 3:
|
||||
return isPage3Valid;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Rendu du composant
|
||||
return (
|
||||
<div className="mx-auto p-6">
|
||||
<form onSubmit={handleSubmit} className="space-y-8">
|
||||
<DjangoCSRFToken csrfToken={csrfToken} />
|
||||
{/* Page 1 : Informations de l'élève et Responsables */}
|
||||
<DjangoCSRFToken csrfToken={csrfToken} />
|
||||
<ProgressStep
|
||||
steps={steps}
|
||||
stepTitles={stepTitles}
|
||||
currentStep={currentPage}
|
||||
setStep={setCurrentPage}
|
||||
isStepValid={isStepValid}
|
||||
/>
|
||||
<div className="flex-1 overflow-y-auto mt-12 " style={{ maxHeight: 'calc(100vh - 300px)' }}>
|
||||
{/* Page 1 : Informations sur l'élève */}
|
||||
{currentPage === 1 && (
|
||||
<StudentInfoForm
|
||||
studentId={studentId}
|
||||
formData={formData}
|
||||
updateFormField={updateFormField}
|
||||
setFormData={setFormData}
|
||||
guardians={guardians}
|
||||
setGuardians={setGuardians}
|
||||
registrationPaymentModes={registrationPaymentModes}
|
||||
tuitionPaymentModes={tuitionPaymentModes}
|
||||
errors={errors}
|
||||
setIsPageValid={setIsPage1Valid}
|
||||
hasInteracted={hasInteracted}
|
||||
setHasInteracted={setHasInteracted}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Page 2 : Informations sur les responsables légaux */}
|
||||
{currentPage === 2 && (
|
||||
<ResponsableInputFields
|
||||
guardians={guardians}
|
||||
setGuardians={setGuardians}
|
||||
errors={errors}
|
||||
setIsPageValid={setIsPage2Valid}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Page 3 : Informations sur les modalités de paiement */}
|
||||
{currentPage === 3 && (
|
||||
<>
|
||||
<PaymentMethodSelector
|
||||
formData={formData}
|
||||
setFormData={setFormData}
|
||||
registrationPaymentModes={registrationPaymentModes}
|
||||
tuitionPaymentModes={tuitionPaymentModes}
|
||||
errors={errors}
|
||||
setIsPageValid={setIsPage3Valid}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Pages suivantes : Section Fichiers d'inscription */}
|
||||
{currentPage > 1 && currentPage <= schoolFileTemplates.length + 1 && (
|
||||
<div className="mt-8 mb-4 w-3/5">
|
||||
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
|
||||
{/* Titre du document */}
|
||||
<div className="mb-4">
|
||||
<h2 className="text-lg font-semibold text-gray-800">
|
||||
{schoolFileTemplates[currentPage - 2].name ||
|
||||
'Document sans nom'}
|
||||
</h2>
|
||||
<p className="text-sm text-gray-500">
|
||||
{schoolFileTemplates[currentPage - 2].description ||
|
||||
'Aucune description disponible pour ce document.'}
|
||||
</p>
|
||||
</div>
|
||||
{currentPage === 4 && (
|
||||
<div className="mt-8 mb-4 w-3/5 mx-auto">
|
||||
{schoolFileTemplates.length > 0 && (
|
||||
<div className="mt-8 mb-4 w-3/5 mx-auto">
|
||||
{/* Titre du document */}
|
||||
<div className="mb-4">
|
||||
<h2 className="text-lg font-semibold text-gray-800">
|
||||
{schoolFileTemplates[currentPage - 2].name ||
|
||||
'Document sans nom'}
|
||||
</h2>
|
||||
<p className="text-sm text-gray-500">
|
||||
{schoolFileTemplates[currentPage - 2].description ||
|
||||
'Aucune description disponible pour ce document.'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Affichage du formulaire ou du document */}
|
||||
{schoolFileTemplates[currentPage - 2].file === null ? (
|
||||
<DocusealForm
|
||||
id="docusealForm"
|
||||
src={
|
||||
'https://docuseal.com/s/' +
|
||||
schoolFileTemplates[currentPage - 2].slug
|
||||
}
|
||||
withDownloadButton={false}
|
||||
onComplete={() => {
|
||||
downloadTemplate(schoolFileTemplates[currentPage - 2].slug)
|
||||
.then((data) => fetch(data))
|
||||
.then((response) => response.blob())
|
||||
.then((blob) => {
|
||||
const file = new File(
|
||||
[blob],
|
||||
`${schoolFileTemplates[currentPage - 2].name}.pdf`,
|
||||
{ type: blob.type }
|
||||
);
|
||||
const updateData = new FormData();
|
||||
updateData.append('file', file);
|
||||
{/* Affichage du formulaire ou du document */}
|
||||
{schoolFileTemplates[currentPage - 2].file === null ? (
|
||||
<DocusealForm
|
||||
key={schoolFileTemplates[currentPage - 2].slug} // Clé unique basée sur le slug du document
|
||||
id="docusealForm"
|
||||
src={
|
||||
'https://docuseal.com/s/' +
|
||||
schoolFileTemplates[currentPage - 2].slug
|
||||
}
|
||||
withDownloadButton={false}
|
||||
withTitle={false}
|
||||
onComplete={() => {
|
||||
downloadTemplate(schoolFileTemplates[currentPage - 2].slug)
|
||||
.then((data) => fetch(data))
|
||||
.then((response) => response.blob())
|
||||
.then((blob) => {
|
||||
const file = new File(
|
||||
[blob],
|
||||
`${schoolFileTemplates[currentPage - 2].name}.pdf`,
|
||||
{ type: blob.type }
|
||||
);
|
||||
const updateData = new FormData();
|
||||
updateData.append('file', file);
|
||||
|
||||
return editRegistrationSchoolFileTemplates(
|
||||
schoolFileTemplates[currentPage - 2].id,
|
||||
updateData,
|
||||
csrfToken
|
||||
);
|
||||
})
|
||||
.then((data) => {
|
||||
logger.debug('EDIT TEMPLATE : ', data);
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.error('error editing template : ', error);
|
||||
});
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<iframe
|
||||
src={`${BASE_URL}/${schoolFileTemplates[currentPage - 2].file}`}
|
||||
title="Document Viewer"
|
||||
className="w-full"
|
||||
style={{
|
||||
height: '75vh', // Ajuster la hauteur à 75% de la fenêtre
|
||||
border: 'none',
|
||||
}}
|
||||
/>
|
||||
return editRegistrationSchoolFileTemplates(
|
||||
schoolFileTemplates[currentPage - 2].id,
|
||||
updateData,
|
||||
csrfToken
|
||||
);
|
||||
})
|
||||
.then((data) => {
|
||||
logger.debug('EDIT TEMPLATE : ', data);
|
||||
setIsSignatureComplete(true);
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.error('error editing template : ', error);
|
||||
setIsSignatureComplete(false);
|
||||
});
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<iframe
|
||||
src={`${BASE_URL}/${schoolFileTemplates[currentPage - 2].file}`}
|
||||
title="Document Viewer"
|
||||
className="w-full"
|
||||
style={{
|
||||
height: '75vh', // Ajuster la hauteur à 75% de la fenêtre
|
||||
border: 'none',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Dernière page : Section Fichiers parents */}
|
||||
{currentPage === schoolFileTemplates.length + 2 && (
|
||||
{currentPage === 5 && (
|
||||
<FilesToUpload
|
||||
parentFileTemplates={parentFileTemplates}
|
||||
uploadedFiles={uploadedFiles}
|
||||
@ -383,16 +399,10 @@ export default function InscriptionFormShared({
|
||||
onFileDelete={handleDeleteFile}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Boutons de contrôle */}
|
||||
<div className="flex justify-center space-x-4">
|
||||
<Button
|
||||
text="Sauvegarder"
|
||||
onClick={handleSave}
|
||||
className="px-4 py-2 rounded-md shadow-sm focus:outline-none bg-orange-500 text-white hover:bg-orange-600"
|
||||
primary
|
||||
name="Save"
|
||||
/>
|
||||
<div className="flex justify-center space-x-4 mt-12">
|
||||
{currentPage > 1 && (
|
||||
<Button
|
||||
text="Précédent"
|
||||
@ -400,9 +410,10 @@ export default function InscriptionFormShared({
|
||||
e.preventDefault();
|
||||
handlePreviousPage();
|
||||
}}
|
||||
primary
|
||||
/>
|
||||
)}
|
||||
{currentPage < schoolFileTemplates.length + 2 && (
|
||||
{currentPage < steps.length && (
|
||||
<Button
|
||||
text="Suivant"
|
||||
onClick={(e) => {
|
||||
@ -410,20 +421,25 @@ export default function InscriptionFormShared({
|
||||
handleNextPage();
|
||||
}}
|
||||
className={`px-4 py-2 rounded-md shadow-sm focus:outline-none ${
|
||||
!isCurrentPageValid()
|
||||
(currentPage === 1 && !isPage1Valid) ||
|
||||
(currentPage === 2 && !isPage2Valid) ||
|
||||
(currentPage === 3 && !isPage3Valid)
|
||||
? 'bg-gray-300 text-gray-700 cursor-not-allowed'
|
||||
: 'bg-emerald-500 text-white hover:bg-emerald-600'
|
||||
}`}
|
||||
disabled={!isCurrentPageValid()}
|
||||
disabled={
|
||||
(currentPage === 1 && !isPage1Valid) ||
|
||||
(currentPage === 2 && !isPage2Valid) ||
|
||||
(currentPage === 3 && !isPage3Valid)
|
||||
}
|
||||
primary
|
||||
name="Next"
|
||||
/>
|
||||
)}
|
||||
{currentPage === schoolFileTemplates.length + 2 && (
|
||||
<Button type="submit" text="Valider" primary />
|
||||
{currentPage === steps.length && (
|
||||
<Button onClick={handleSubmit} text="Valider" primary />
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user