'use client'; import React, { useState, useEffect } from 'react'; import FormRenderer from '@/components/Form/FormRenderer'; import FileUpload from '@/components/Form/FileUpload'; import { CheckCircle, Hourglass, FileText, Download, Upload, XCircle } from 'lucide-react'; import logger from '@/utils/logger'; import { BASE_URL } from '@/utils/Url'; /** * Composant pour afficher et gérer les formulaires dynamiques d'inscription * @param {Array} schoolFileTemplates - Liste des templates de formulaires * @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 * @param {Function} onFileUpload - Callback appelé quand un fichier est sélectionné */ export default function DynamicFormsList({ schoolFileTemplates, existingResponses = {}, onFormSubmit, enable = true, onValidationChange, onFileUpload, // nouvelle prop pour gérer l'upload (à passer depuis le parent) }) { const [currentTemplateIndex, setCurrentTemplateIndex] = useState(0); const [formsData, setFormsData] = useState({}); const [formsValidation, setFormsValidation] = useState({}); const fileInputRefs = React.useRef({}); // Initialiser les données avec les réponses existantes useEffect(() => { // Initialisation complète de formsValidation et formsData pour chaque template if (schoolFileTemplates && schoolFileTemplates.length > 0) { // Fusionner avec l'état existant pour préserver les données locales setFormsData((prevData) => { const dataState = { ...prevData }; schoolFileTemplates.forEach((tpl) => { // Ne mettre à jour que si on n'a pas encore de données locales ou si les données du serveur ont changé const hasLocalData = prevData[tpl.id] && Object.keys(prevData[tpl.id]).length > 0; const hasServerData = existingResponses && existingResponses[tpl.id] && Object.keys(existingResponses[tpl.id]).length > 0; if (!hasLocalData && hasServerData) { // Pas de données locales mais données serveur : utiliser les données serveur dataState[tpl.id] = existingResponses[tpl.id]; } else if (!hasLocalData && !hasServerData) { // Pas de données du tout : initialiser à vide dataState[tpl.id] = {}; } // Si hasLocalData : on garde les données locales existantes }); return dataState; }); // Fusionner avec l'état de validation existant setFormsValidation((prevValidation) => { const validationState = { ...prevValidation }; schoolFileTemplates.forEach((tpl) => { const hasLocalValidation = prevValidation[tpl.id] === true; const hasServerData = existingResponses && existingResponses[tpl.id] && Object.keys(existingResponses[tpl.id]).length > 0; if (!hasLocalValidation && hasServerData) { // Pas validé localement mais données serveur : marquer comme validé validationState[tpl.id] = true; } else if (validationState[tpl.id] === undefined) { // Pas encore initialisé : initialiser à false validationState[tpl.id] = false; } // Si hasLocalValidation : on garde l'état local existant }); return validationState; }); } }, [existingResponses, schoolFileTemplates]); // Mettre à jour la validation globale quand la validation des formulaires change useEffect(() => { // Un document est considéré comme "validé" s'il est validé par l'école OU complété localement OU déjà existant const allFormsValid = schoolFileTemplates.every( tpl => tpl.isValidated === true || (formsData[tpl.id] && Object.keys(formsData[tpl.id]).length > 0) || (existingResponses[tpl.id] && Object.keys(existingResponses[tpl.id]).length > 0) ); onValidationChange(allFormsValid); }, [formsData, formsValidation, existingResponses, schoolFileTemplates, 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 < schoolFileTemplates.length - 1) { setCurrentTemplateIndex(currentTemplateIndex + 1); } logger.debug('Formulaire soumis avec succès'); } catch (error) { logger.error('Erreur lors de la soumission du formulaire:', error); } }; /** * 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 ; } if (isActive) { return ; } return ; }; /** * Obtient le formulaire actuel à afficher */ const getCurrentTemplate = () => { return schoolFileTemplates[currentTemplateIndex]; }; const currentTemplate = getCurrentTemplate(); // Handler d'upload pour formulaire existant const handleUpload = async (file, selectedFile) => { if (!file || !selectedFile) return; try { const templateId = currentTemplate.id; if (onFileUpload) { await onFileUpload(file, selectedFile); setFormsData((prev) => { const newData = { ...prev, [templateId]: { uploaded: true, fileName: file.name }, }; return newData; }); setFormsValidation((prev) => { const newValidation = { ...prev, [templateId]: true, }; return newValidation; }); } } catch (error) { logger.error('Erreur lors de l\'upload du fichier :', error); } }; const isDynamicForm = (template) => template.formTemplateData && Array.isArray(template.formTemplateData.fields) && template.formTemplateData.fields.length > 0; if (!schoolFileTemplates || schoolFileTemplates.length === 0) { return (

Aucun formulaire à compléter

); } return (
{/* Liste des formulaires */}

Formulaires à compléter

{/* Compteur x/y : inclut les documents validés */} { schoolFileTemplates.filter(tpl => { // Validé ou complété localement return tpl.isValidated === true || (formsData[tpl.id] && Object.keys(formsData[tpl.id]).length > 0) || (existingResponses[tpl.id] && Object.keys(existingResponses[tpl.id]).length > 0); }).length } {' / '} {schoolFileTemplates.length} complétés
{/* Tri des templates par état */} {(() => { // Helper pour état const getState = tpl => { if (tpl.isValidated === true) return 0; // validé const isCompletedLocally = !!( (formsData[tpl.id] && Object.keys(formsData[tpl.id]).length > 0) || (existingResponses[tpl.id] && Object.keys(existingResponses[tpl.id]).length > 0) ); if (isCompletedLocally) return 1; // complété/en attente return 2; // à compléter/refusé }; const sortedTemplates = [...schoolFileTemplates].sort((a, b) => { return getState(a) - getState(b); }); return (
    {sortedTemplates.map((tpl, index) => { const isActive = schoolFileTemplates[currentTemplateIndex]?.id === tpl.id; const isValidated = typeof tpl.isValidated === 'boolean' ? tpl.isValidated : undefined; const isCompletedLocally = !!( (formsData[tpl.id] && Object.keys(formsData[tpl.id]).length > 0) || (existingResponses[tpl.id] && Object.keys(existingResponses[tpl.id]).length > 0) ); // Statut d'affichage let statusLabel = ''; let statusColor = ''; let icon = null; let bgClass = ''; let borderClass = ''; let textClass = ''; let canEdit = true; if (isValidated === true) { statusLabel = 'Validé'; statusColor = 'emerald'; icon = ; bgClass = 'bg-emerald-50'; borderClass = 'border border-emerald-200'; textClass = 'text-emerald-700'; bgClass = isActive ? 'bg-emerald-200' : bgClass; borderClass = isActive ? 'border border-emerald-300' : borderClass; textClass = isActive ? 'text-emerald-900 font-semibold' : textClass; canEdit = false; } else if (isValidated === false) { if (isCompletedLocally) { statusLabel = 'Complété'; statusColor = 'orange'; icon = ; bgClass = isActive ? 'bg-orange-200' : 'bg-orange-50'; borderClass = isActive ? 'border border-orange-300' : 'border border-orange-200'; textClass = isActive ? 'text-orange-900 font-semibold' : 'text-orange-700'; canEdit = true; } else { statusLabel = 'Refusé'; statusColor = 'red'; icon = ; bgClass = isActive ? 'bg-red-200' : 'bg-red-50'; borderClass = isActive ? 'border border-red-300' : 'border border-red-200'; textClass = isActive ? 'text-red-900 font-semibold' : 'text-red-700'; canEdit = true; } } else { if (isCompletedLocally) { statusLabel = 'Complété'; statusColor = 'orange'; icon = ; bgClass = isActive ? 'bg-orange-200' : 'bg-orange-50'; borderClass = isActive ? 'border border-orange-300' : 'border border-orange-200'; textClass = isActive ? 'text-orange-900 font-semibold' : 'text-orange-700'; canEdit = true; } else { statusLabel = 'À compléter'; statusColor = 'gray'; icon = ; bgClass = isActive ? 'bg-gray-200' : ''; borderClass = isActive ? 'border border-gray-300' : ''; textClass = isActive ? 'text-gray-900 font-semibold' : 'text-gray-600'; canEdit = true; } } return (
  • setCurrentTemplateIndex(schoolFileTemplates.findIndex(t => t.id === tpl.id))} > {icon}
    {tpl.formMasterData?.title || tpl.title || tpl.name || 'Formulaire sans nom'} {statusLabel}
    {tpl.formMasterData?.fields || tpl.fields ? `${(tpl.formMasterData?.fields || tpl.fields).length} champ(s)` : 'À compléter'}
  • ); })}
); })()}
{currentTemplate && (

{currentTemplate.name}

{/* Label d'état */} {currentTemplate.isValidated === true ? ( Validé ) : ((formsData[currentTemplate.id] && Object.keys(formsData[currentTemplate.id]).length > 0) || (existingResponses[currentTemplate.id] && Object.keys(existingResponses[currentTemplate.id]).length > 0)) ? ( Complété ) : ( Refusé )}

{currentTemplate.formTemplateData?.description || currentTemplate.description || ''}

Formulaire {(() => { // Trouver l'index du template courant dans la liste triée const getState = tpl => { if (tpl.isValidated === true) return 0; const isCompletedLocally = !!( (formsData[tpl.id] && Object.keys(formsData[tpl.id]).length > 0) || (existingResponses[tpl.id] && Object.keys(existingResponses[tpl.id]).length > 0) ); if (isCompletedLocally) return 1; return 2; }; const sortedTemplates = [...schoolFileTemplates].sort((a, b) => getState(a) - getState(b)); const idx = sortedTemplates.findIndex(tpl => tpl.id === currentTemplate.id); return idx + 1; })()} sur {schoolFileTemplates.length}
{/* Affichage dynamique ou existant */} {isDynamicForm(currentTemplate) ? ( handleFormSubmit(formData, currentTemplate.id) } // Désactive le bouton suivant si le template est validé enable={currentTemplate.isValidated !== true} /> ) : ( // Formulaire existant (PDF, image, etc.)
{/* Cas validé : affichage en iframe */} {currentTemplate.isValidated === true && currentTemplate.file && (