feat: Précablage du formulaire dynamique [N3WTS-17]

This commit is contained in:
Luc SORIGNET
2025-11-30 17:24:25 +01:00
parent 7486f6c5ce
commit dd00cba385
41 changed files with 2637 additions and 606 deletions

View File

@ -0,0 +1,295 @@
'use client';
import React, { useState, useEffect } from 'react';
import FormRenderer from '@/components/Form/FormRenderer';
import { CheckCircle, Hourglass, FileText } from 'lucide-react';
import logger from '@/utils/logger';
/**
* Composant pour afficher et gérer les formulaires dynamiques d'inscription
* @param {Array} schoolFileMasters - Liste des formulaires maîtres
* @param {Object} existingResponses - Réponses déjà sauvegardées
* @param {Function} onFormSubmit - Callback appelé quand un formulaire est soumis
* @param {Boolean} enable - Si les formulaires sont modifiables
*/
export default function DynamicFormsList({
schoolFileMasters,
existingResponses = {},
onFormSubmit,
enable = true,
onValidationChange,
}) {
const [currentTemplateIndex, setCurrentTemplateIndex] = useState(0);
const [formsData, setFormsData] = useState({});
const [formsValidation, setFormsValidation] = useState({});
// Initialiser les données avec les réponses existantes
useEffect(() => {
if (existingResponses && Object.keys(existingResponses).length > 0) {
setFormsData(existingResponses);
// Marquer les formulaires avec réponses comme valides
const validationState = {};
Object.keys(existingResponses).forEach((formId) => {
if (
existingResponses[formId] &&
Object.keys(existingResponses[formId]).length > 0
) {
validationState[formId] = true;
}
});
setFormsValidation(validationState);
}
}, [existingResponses]);
// Debug: Log des formulaires maîtres reçus
useEffect(() => {
logger.debug(
'DynamicFormsList - Formulaires maîtres reçus:',
schoolFileMasters
);
}, [schoolFileMasters]);
// Mettre à jour la validation globale quand la validation des formulaires change
useEffect(() => {
const allFormsValid = schoolFileMasters.every(
(master, index) => formsValidation[master.id] === true
);
if (onValidationChange) {
onValidationChange(allFormsValid);
}
}, [formsValidation, schoolFileMasters, onValidationChange]);
/**
* Gère la soumission d'un formulaire individuel
*/
const handleFormSubmit = async (formData, templateId) => {
try {
logger.debug('Soumission du formulaire:', { templateId, formData });
// Sauvegarder les données du formulaire
setFormsData((prev) => ({
...prev,
[templateId]: formData,
}));
// Marquer le formulaire comme complété
setFormsValidation((prev) => ({
...prev,
[templateId]: true,
}));
// Appeler le callback parent
if (onFormSubmit) {
await onFormSubmit(formData, templateId);
}
// Passer au formulaire suivant si disponible
if (currentTemplateIndex < schoolFileMasters.length - 1) {
setCurrentTemplateIndex(currentTemplateIndex + 1);
}
logger.debug('Formulaire soumis avec succès');
} catch (error) {
logger.error('Erreur lors de la soumission du formulaire:', error);
}
};
/**
* Gère les changements de validation d'un formulaire
*/
const handleFormValidationChange = (isValid, templateId) => {
setFormsValidation((prev) => ({
...prev,
[templateId]: isValid,
}));
};
/**
* Vérifie si un formulaire est complété
*/
const isFormCompleted = (templateId) => {
return (
formsValidation[templateId] === true ||
(formsData[templateId] &&
Object.keys(formsData[templateId]).length > 0) ||
(existingResponses[templateId] &&
Object.keys(existingResponses[templateId]).length > 0)
);
};
/**
* Obtient l'icône de statut d'un formulaire
*/
const getFormStatusIcon = (templateId, isActive) => {
if (isFormCompleted(templateId)) {
return <CheckCircle className="w-5 h-5 text-green-600" />;
}
if (isActive) {
return <FileText className="w-5 h-5 text-blue-600" />;
}
return <Hourglass className="w-5 h-5 text-gray-400" />;
};
/**
* Obtient le formulaire actuel à afficher
*/
const getCurrentTemplate = () => {
return schoolFileMasters[currentTemplateIndex];
};
if (!schoolFileMasters || schoolFileMasters.length === 0) {
return (
<div className="text-center py-8">
<FileText className="w-16 h-16 text-gray-300 mx-auto mb-4" />
<p className="text-gray-600 mb-4">Aucun formulaire à compléter</p>
</div>
);
}
const currentTemplate = getCurrentTemplate();
return (
<div className="mt-8 mb-4 w-full mx-auto flex gap-8">
{/* Liste des formulaires */}
<div className="w-1/4 bg-gray-50 p-4 rounded-lg shadow-sm border border-gray-200">
<h3 className="text-lg font-semibold text-gray-800 mb-4">
Formulaires à compléter
</h3>
<div className="text-sm text-gray-600 mb-4">
{
Object.keys(formsValidation).filter((id) => formsValidation[id])
.length
}{' '}
/ {schoolFileMasters.length} complétés
</div>
<ul className="space-y-2">
{schoolFileMasters.map((master, index) => {
const isActive = index === currentTemplateIndex;
const isCompleted = isFormCompleted(master.id);
return (
<li
key={master.id}
className={`flex items-center cursor-pointer p-2 rounded-md transition-colors ${
isActive
? 'bg-blue-100 text-blue-700 font-semibold'
: isCompleted
? 'text-green-600 hover:bg-green-50'
: 'text-gray-600 hover:bg-gray-100'
}`}
onClick={() => setCurrentTemplateIndex(index)}
>
<span className="mr-3">
{getFormStatusIcon(master.id, isActive)}
</span>
<div className="flex-1 min-w-0">
<div className="text-sm truncate">
{master.formMasterData?.title ||
master.title ||
master.name ||
'Formulaire sans nom'}
</div>
{isCompleted ? (
<div className="text-xs text-green-600">
Complété -{' '}
{
Object.keys(
formsData[master.id] ||
existingResponses[master.id] ||
{}
).length
}{' '}
réponse(s)
</div>
) : (
<div className="text-xs text-gray-500">
{master.formMasterData?.fields || master.fields
? `${(master.formMasterData?.fields || master.fields).length} champ(s)`
: 'À compléter'}
</div>
)}
</div>
</li>
);
})}
</ul>
</div>
{/* Affichage du formulaire actuel */}
<div className="w-3/4">
{currentTemplate && (
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
<div className="mb-6">
<h3 className="text-xl font-semibold text-gray-800 mb-2">
{currentTemplate.formMasterData?.title ||
currentTemplate.title ||
currentTemplate.name ||
'Formulaire sans nom'}
</h3>
<p className="text-sm text-gray-600">
{currentTemplate.formMasterData?.description ||
currentTemplate.description ||
'Veuillez compléter ce formulaire pour continuer votre inscription.'}
</p>
<div className="text-xs text-gray-500 mt-1">
Formulaire {currentTemplateIndex + 1} sur{' '}
{schoolFileMasters.length}
</div>
</div>
{/* Vérifier si le formulaire maître a des données de configuration */}
{(currentTemplate.formMasterData?.fields &&
currentTemplate.formMasterData.fields.length > 0) ||
(currentTemplate.fields && currentTemplate.fields.length > 0) ? (
<FormRenderer
key={currentTemplate.id}
formConfig={{
id: currentTemplate.id,
title:
currentTemplate.formMasterData?.title ||
currentTemplate.title ||
currentTemplate.name ||
'Formulaire',
fields:
currentTemplate.formMasterData?.fields ||
currentTemplate.fields ||
[],
submitLabel:
currentTemplate.formMasterData?.submitLabel || 'Valider',
}}
onFormSubmit={(formData) =>
handleFormSubmit(formData, currentTemplate.id)
}
/>
) : (
<div className="text-center py-8">
<FileText className="w-16 h-16 text-gray-300 mx-auto mb-4" />
<p className="text-gray-600">
Ce formulaire n'est pas encore configuré.
</p>
<p className="text-sm text-gray-500 mt-2">
Contactez l'administration pour plus d'informations.
</p>
</div>
)}
</div>
)}
{/* Message de fin */}
{currentTemplateIndex >= schoolFileMasters.length && (
<div className="text-center py-8">
<CheckCircle className="w-16 h-16 text-green-600 mx-auto mb-4" />
<h3 className="text-lg font-semibold text-green-600 mb-2">
Tous les formulaires ont été complétés !
</h3>
<p className="text-gray-600">
Vous pouvez maintenant passer à l'étape suivante.
</p>
</div>
)}
</div>
</div>
);
}