feat: Gestion des documents signés durant l'inscription / possibilité de

visualiser un document signer
This commit is contained in:
N3WT DE COMPET
2025-04-26 18:05:00 +02:00
parent daad12cf40
commit 905b95f3a3
2 changed files with 151 additions and 75 deletions

View File

@ -23,6 +23,7 @@ import StudentInfoForm from '@/components/Inscription/StudentInfoForm';
import ResponsableInputFields from '@/components/Inscription/ResponsableInputFields'; import ResponsableInputFields from '@/components/Inscription/ResponsableInputFields';
import PaymentMethodSelector from '@/components/Inscription/PaymentMethodSelector'; import PaymentMethodSelector from '@/components/Inscription/PaymentMethodSelector';
import ProgressStep from '@/components/ProgressStep'; import ProgressStep from '@/components/ProgressStep';
import { CheckCircle, Loader2 } from 'lucide-react';
/** /**
* Composant de formulaire d'inscription partagé * Composant de formulaire d'inscription partagé
@ -70,9 +71,81 @@ export default function InscriptionFormShared({
const [isPage1Valid, setIsPage1Valid] = useState(false); const [isPage1Valid, setIsPage1Valid] = useState(false);
const [isPage2Valid, setIsPage2Valid] = useState(false); const [isPage2Valid, setIsPage2Valid] = useState(false);
const [isPage3Valid, setIsPage3Valid] = useState(false); const [isPage3Valid, setIsPage3Valid] = useState(false);
const [isPage4Valid, setIsPage4Valid] = useState(false);
const [hasInteracted, setHasInteracted] = useState(false); const [hasInteracted, setHasInteracted] = useState(false);
// État pour suivre l'index du fichier en cours
const [currentTemplateIndex, setCurrentTemplateIndex] = useState(0);
useEffect(() => {
// Trouver le premier template non signé
const firstUnsignedIndex = schoolFileTemplates.findIndex(
(template) => template.file === null
);
// Mettre à jour l'index du template actuel
if (firstUnsignedIndex !== -1) {
setCurrentTemplateIndex(firstUnsignedIndex);
} else {
// Si tous les templates sont signés, définir un index hors limites
setCurrentTemplateIndex(schoolFileTemplates.length);
}
}, [schoolFileTemplates]);
useEffect(() => {
// Vérifier si tous les templates ont leur champ "file" différent de null
const allSigned = schoolFileTemplates.every((template) => template.file !== null);
// Mettre à jour isPage4Valid en fonction de cette condition
setIsPage4Valid(allSigned);
if (allSigned) {
setCurrentTemplateIndex(0);
}
}, [schoolFileTemplates]);
const handleTemplateSigned = (index) => {
const template = schoolFileTemplates[index];
if (!template) {
logger.error('Template introuvable pour l\'index donné.');
return;
}
downloadTemplate(template.slug)
.then((data) => fetch(data))
.then((response) => response.blob())
.then((blob) => {
const file = new File(
[blob],
`${template.name}.pdf`,
{ type: blob.type }
);
const updateData = new FormData();
updateData.append('file', file);
return editRegistrationSchoolFileTemplates(
template.id,
updateData,
csrfToken
);
})
.then((data) => {
logger.debug('Template mis à jour avec succès :', data);
// Mettre à jour l'état local de schoolFileTemplates
setSchoolFileTemplates((prevTemplates) =>
prevTemplates.map((t, i) =>
i === index ? { ...t, file: data.file } : t
)
);
})
.catch((error) => {
logger.error('Erreur lors de la mise à jour du template :', error);
});
};
useEffect(() => { useEffect(() => {
fetchSchoolFileTemplatesFromRegistrationFiles(studentId).then((data) => { fetchSchoolFileTemplatesFromRegistrationFiles(studentId).then((data) => {
setSchoolFileTemplates(data); setSchoolFileTemplates(data);
@ -277,7 +350,7 @@ export default function InscriptionFormShared({
setStep={setCurrentPage} setStep={setCurrentPage}
isStepValid={isStepValid} isStepValid={isStepValid}
/> />
<div className="flex-1 overflow-y-auto mt-12 " style={{ maxHeight: 'calc(100vh - 300px)' }}> <div className="flex-1 overflow-y-auto mt-12 " style={{ maxHeight: 'calc(100vh - 400px)' }}>
{/* Page 1 : Informations sur l'élève */} {/* Page 1 : Informations sur l'élève */}
{currentPage === 1 && ( {currentPage === 1 && (
<StudentInfoForm <StudentInfoForm
@ -319,75 +392,79 @@ export default function InscriptionFormShared({
{/* Pages suivantes : Section Fichiers d'inscription */} {/* Pages suivantes : Section Fichiers d'inscription */}
{currentPage === 4 && ( {currentPage === 4 && (
<div className="mt-8 mb-4 w-3/5 mx-auto"> <div className="mt-8 mb-4 w-4/5 mx-auto flex gap-8">
{schoolFileTemplates.length > 0 && ( {/* Liste des états de signature */}
<div className="mt-8 mb-4 w-3/5 mx-auto"> <div className="w-1/4 bg-gray-50 p-4 rounded-lg shadow-sm border border-gray-200">
{/* Titre du document */} <h3 className="text-lg font-semibold text-gray-800 mb-4">Documents</h3>
<div className="mb-4"> <ul className="space-y-2">
<h2 className="text-lg font-semibold text-gray-800"> {schoolFileTemplates.map((template, index) => (
{schoolFileTemplates[currentPage - 2].name || <li
'Document sans nom'} key={template.id}
</h2> className={`flex items-center cursor-pointer ${
<p className="text-sm text-gray-500"> index === currentTemplateIndex
{schoolFileTemplates[currentPage - 2].description || ? 'text-blue-600 font-bold'
'Aucune description disponible pour ce document.'} : template.file !== null
</p> ? 'text-green-600'
</div> : 'text-gray-600'
}`}
onClick={() => setCurrentTemplateIndex(index)} // Mettre à jour l'index du template actuel
>
<span className="mr-2">
{template.file !== null ? (
<CheckCircle className="w-5 h-5 text-green-600" />
) : (
<Loader2 className="w-5 h-5 text-gray-600" />
)}
</span>
{template.name || 'Document sans nom'}
</li>
))}
</ul>
</div>
{/* Affichage du formulaire ou du document */} {/* Affichage du fichier actuel */}
{schoolFileTemplates[currentPage - 2].file === null ? ( <div className="w-3/4">
<DocusealForm {currentTemplateIndex < schoolFileTemplates.length && (
key={schoolFileTemplates[currentPage - 2].slug} // Clé unique basée sur le slug du document <div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
id="docusealForm" <h3 className="text-lg font-semibold text-gray-800 mb-4">
src={ {schoolFileTemplates[currentTemplateIndex].name || 'Document sans nom'}
'https://docuseal.com/s/' + </h3>
schoolFileTemplates[currentPage - 2].slug <p className="text-sm text-gray-500 mb-4">
} {schoolFileTemplates[currentTemplateIndex].description ||
withDownloadButton={false} 'Aucune description disponible pour ce document.'}
withTitle={false} </p>
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[currentTemplateIndex].file === null ? (
schoolFileTemplates[currentPage - 2].id, <DocusealForm
updateData, key={schoolFileTemplates[currentTemplateIndex].slug}
csrfToken id="docusealForm"
); src={`https://docuseal.com/s/${schoolFileTemplates[currentTemplateIndex].slug}`}
}) withDownloadButton={false}
.then((data) => { withTitle={false}
logger.debug('EDIT TEMPLATE : ', data); onComplete={() => handleTemplateSigned(currentTemplateIndex)}
setIsSignatureComplete(true); />
}) ) : (
.catch((error) => { <iframe
logger.error('error editing template : ', error); src={`${BASE_URL}/${schoolFileTemplates[currentTemplateIndex].file}`}
setIsSignatureComplete(false); title="Document Viewer"
}); className="w-full"
}} style={{
/> height: '75vh',
) : ( border: 'none',
<iframe }}
src={`${BASE_URL}/${schoolFileTemplates[currentPage - 2].file}`} />
title="Document Viewer" )}
className="w-full" </div>
style={{ )}
height: '75vh', // Ajuster la hauteur à 75% de la fenêtre
border: 'none', {/* Message de fin */}
}} {currentTemplateIndex >= schoolFileTemplates.length && (
/> <div className="text-center text-green-600 font-semibold">
)} Tous les formulaires ont été signés avec succès !
</div> </div>
)} )}
</div> </div>
</div>
)} )}
{/* Dernière page : Section Fichiers parents */} {/* Dernière page : Section Fichiers parents */}
@ -423,14 +500,16 @@ export default function InscriptionFormShared({
className={`px-4 py-2 rounded-md shadow-sm focus:outline-none ${ className={`px-4 py-2 rounded-md shadow-sm focus:outline-none ${
(currentPage === 1 && !isPage1Valid) || (currentPage === 1 && !isPage1Valid) ||
(currentPage === 2 && !isPage2Valid) || (currentPage === 2 && !isPage2Valid) ||
(currentPage === 3 && !isPage3Valid) (currentPage === 3 && !isPage3Valid) ||
(currentPage === 4 && !isPage4Valid)
? 'bg-gray-300 text-gray-700 cursor-not-allowed' ? 'bg-gray-300 text-gray-700 cursor-not-allowed'
: 'bg-emerald-500 text-white hover:bg-emerald-600' : 'bg-emerald-500 text-white hover:bg-emerald-600'
}`} }`}
disabled={ disabled={
(currentPage === 1 && !isPage1Valid) || (currentPage === 1 && !isPage1Valid) ||
(currentPage === 2 && !isPage2Valid) || (currentPage === 2 && !isPage2Valid) ||
(currentPage === 3 && !isPage3Valid) (currentPage === 3 && !isPage3Valid) ||
(currentPage === 4 && !isPage4Valid)
} }
primary primary
name="Next" name="Next"

View File

@ -75,9 +75,6 @@ export default function StudentInfoForm({
}; };
const getLocalError = (field) => { const getLocalError = (field) => {
if (!hasInteracted) {
return ''; // Ne pas afficher les erreurs locales au premier chargement
}
if ( if (
// Student Form // Student Form
( field === 'last_name' && (!formData.last_name || formData.last_name.trim() === '') ) || ( field === 'last_name' && (!formData.last_name || formData.last_name.trim() === '') ) ||