mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-01-29 07:53:23 +00:00
feat: Gestion des documents signés durant l'inscription / possibilité de
visualiser un document signer
This commit is contained in:
@ -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,74 +392,78 @@ 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'
|
||||||
|
: '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>
|
</div>
|
||||||
|
|
||||||
{/* Affichage du formulaire ou du document */}
|
{/* Affichage du fichier actuel */}
|
||||||
{schoolFileTemplates[currentPage - 2].file === null ? (
|
<div className="w-3/4">
|
||||||
|
{currentTemplateIndex < schoolFileTemplates.length && (
|
||||||
|
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
|
||||||
|
<h3 className="text-lg font-semibold text-gray-800 mb-4">
|
||||||
|
{schoolFileTemplates[currentTemplateIndex].name || 'Document sans nom'}
|
||||||
|
</h3>
|
||||||
|
<p className="text-sm text-gray-500 mb-4">
|
||||||
|
{schoolFileTemplates[currentTemplateIndex].description ||
|
||||||
|
'Aucune description disponible pour ce document.'}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{schoolFileTemplates[currentTemplateIndex].file === null ? (
|
||||||
<DocusealForm
|
<DocusealForm
|
||||||
key={schoolFileTemplates[currentPage - 2].slug} // Clé unique basée sur le slug du document
|
key={schoolFileTemplates[currentTemplateIndex].slug}
|
||||||
id="docusealForm"
|
id="docusealForm"
|
||||||
src={
|
src={`https://docuseal.com/s/${schoolFileTemplates[currentTemplateIndex].slug}`}
|
||||||
'https://docuseal.com/s/' +
|
|
||||||
schoolFileTemplates[currentPage - 2].slug
|
|
||||||
}
|
|
||||||
withDownloadButton={false}
|
withDownloadButton={false}
|
||||||
withTitle={false}
|
withTitle={false}
|
||||||
onComplete={() => {
|
onComplete={() => handleTemplateSigned(currentTemplateIndex)}
|
||||||
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);
|
|
||||||
setIsSignatureComplete(true);
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
logger.error('error editing template : ', error);
|
|
||||||
setIsSignatureComplete(false);
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<iframe
|
<iframe
|
||||||
src={`${BASE_URL}/${schoolFileTemplates[currentPage - 2].file}`}
|
src={`${BASE_URL}/${schoolFileTemplates[currentTemplateIndex].file}`}
|
||||||
title="Document Viewer"
|
title="Document Viewer"
|
||||||
className="w-full"
|
className="w-full"
|
||||||
style={{
|
style={{
|
||||||
height: '75vh', // Ajuster la hauteur à 75% de la fenêtre
|
height: '75vh',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* 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>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -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"
|
||||||
|
|||||||
@ -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() === '') ) ||
|
||||||
|
|||||||
Reference in New Issue
Block a user