From 4f7d7d002442bb0cb04063734d97f993a40474a7 Mon Sep 17 00:00:00 2001 From: N3WT DE COMPET Date: Sun, 22 Feb 2026 18:34:00 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20Gestion=20de=20l'affichage=20des=20docu?= =?UTF-8?q?ments=20valid=C3=A9s=20et=20non=20valid=C3=A9s=20sur=20la=20pag?= =?UTF-8?q?e=20parent=20[N3WTS-2]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Front-End/src/components/Form/FileUpload.js | 6 + .../Inscription/DynamicFormsList.js | 364 ++++++++++++------ .../Inscription/InscriptionFormShared.js | 2 +- .../Inscription/ValidateSubscription.js | 170 ++++---- 4 files changed, 342 insertions(+), 200 deletions(-) diff --git a/Front-End/src/components/Form/FileUpload.js b/Front-End/src/components/Form/FileUpload.js index 942acbe..0486569 100644 --- a/Front-End/src/components/Form/FileUpload.js +++ b/Front-End/src/components/Form/FileUpload.js @@ -10,10 +10,16 @@ export default function FileUpload({ required, errorMsg, enable = true, // Nouvelle prop pour activer/désactiver le champ + key, }) { const [localFileName, setLocalFileName] = useState(uploadedFileName || ''); const fileInputRef = useRef(null); + // Réinitialise localFileName à chaque changement de key (id template) + React.useEffect(() => { + setLocalFileName(uploadedFileName || ''); + }, [key, uploadedFileName]); + const handleFileChange = (e) => { const file = e.target.files[0]; if (file) { diff --git a/Front-End/src/components/Inscription/DynamicFormsList.js b/Front-End/src/components/Inscription/DynamicFormsList.js index e866b2e..353076c 100644 --- a/Front-End/src/components/Inscription/DynamicFormsList.js +++ b/Front-End/src/components/Inscription/DynamicFormsList.js @@ -2,20 +2,20 @@ import React, { useState, useEffect } from 'react'; import FormRenderer from '@/components/Form/FormRenderer'; import FileUpload from '@/components/Form/FileUpload'; -import { CheckCircle, Hourglass, FileText, Download, Upload } from 'lucide-react'; +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} schoolFileMasters - Liste des formulaires maîtres + * @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({ - schoolFileMasters, + schoolFileTemplates, existingResponses = {}, onFormSubmit, enable = true, @@ -23,47 +23,77 @@ export default function DynamicFormsList({ onFileUpload, // nouvelle prop pour gérer l'upload (à passer depuis le parent) }) { const [currentTemplateIndex, setCurrentTemplateIndex] = useState(0); + // Remet formsValidation à false et formsData à undefined lors de la sélection d'un document refusé + useEffect(() => { + const currentTemplate = schoolFileTemplates[currentTemplateIndex]; + if ( + currentTemplate && + currentTemplate.isValidated === false && + formsValidation[currentTemplate.id] !== true + ) { + setFormsValidation((prev) => { + const newValidation = { ...prev }; + newValidation[currentTemplate.id] = false; + return newValidation; + }); + setFormsData((prev) => { + const newData = { ...prev }; + delete newData[currentTemplate.id]; + return newData; + }); + } + }, [currentTemplateIndex, schoolFileTemplates, formsValidation]); const [formsData, setFormsData] = useState({}); const [formsValidation, setFormsValidation] = useState({}); const fileInputRefs = React.useRef({}); // 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) => { + // Initialisation complète de formsValidation et formsData pour chaque template + if (schoolFileTemplates && schoolFileTemplates.length > 0) { + // Initialiser formsData pour chaque template (avec données existantes ou objet vide) + const dataState = {}; + schoolFileTemplates.forEach((tpl) => { if ( - existingResponses[formId] && - Object.keys(existingResponses[formId]).length > 0 + existingResponses && + existingResponses[tpl.id] && + Object.keys(existingResponses[tpl.id]).length > 0 ) { - validationState[formId] = true; + dataState[tpl.id] = existingResponses[tpl.id]; + } else { + dataState[tpl.id] = {}; + } + }); + setFormsData(dataState); + + // Initialiser formsValidation pour chaque template + const validationState = {}; + schoolFileTemplates.forEach((tpl) => { + if ( + existingResponses && + existingResponses[tpl.id] && + Object.keys(existingResponses[tpl.id]).length > 0 + ) { + validationState[tpl.id] = true; + } else { + validationState[tpl.id] = false; } }); 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 + // 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) ); - if (onValidationChange) { - onValidationChange(allFormsValid); - } - }, [formsValidation, schoolFileMasters, onValidationChange]); + onValidationChange(allFormsValid); + }, [formsData, formsValidation, existingResponses, schoolFileTemplates, onValidationChange]); /** * Gère la soumission d'un formulaire individuel @@ -90,7 +120,7 @@ export default function DynamicFormsList({ } // Passer au formulaire suivant si disponible - if (currentTemplateIndex < schoolFileMasters.length - 1) { + if (currentTemplateIndex < schoolFileTemplates.length - 1) { setCurrentTemplateIndex(currentTemplateIndex + 1); } @@ -100,16 +130,6 @@ export default function DynamicFormsList({ } }; - /** - * 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é */ @@ -140,31 +160,42 @@ export default function DynamicFormsList({ * Obtient le formulaire actuel à afficher */ const getCurrentTemplate = () => { - return schoolFileMasters[currentTemplateIndex]; + return schoolFileTemplates[currentTemplateIndex]; }; // 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); - setFormsValidation((prev) => ({ - ...prev, - [selectedFile.id]: true, - })); + 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 (!schoolFileMasters || schoolFileMasters.length === 0) { + if (!schoolFileTemplates || schoolFileTemplates.length === 0) { return (
@@ -173,8 +204,6 @@ export default function DynamicFormsList({ ); } - const currentTemplate = getCurrentTemplate(); - return (
{/* Liste des formulaires */} @@ -183,85 +212,172 @@ export default function DynamicFormsList({ Formulaires à compléter
+ {/* Compteur x/y : inclut les documents validés */} { - Object.keys(formsValidation).filter((id) => formsValidation[id]) - .length - }{' '} - / {schoolFileMasters.length} complété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
-
    - {schoolFileMasters.map((master, index) => { - const isActive = index === currentTemplateIndex; - const isCompleted = isFormCompleted(master.id); - - return ( -
  • setCurrentTemplateIndex(index)} - > - - {getFormStatusIcon(master.id, isActive)} - -
    -
    - {master.formMasterData?.title || - master.title || - master.name || - 'Formulaire sans nom'} -
    - {isCompleted ? ( -
    - Complété -{' '} - { - Object.keys( - formsData[master.id] || - existingResponses[master.id] || - {} - ).length - }{' '} - réponse(s) -
    - ) : ( -
    - {master.formMasterData?.fields || master.fields - ? `${(master.formMasterData?.fields || master.fields).length} champ(s)` - : 'À compléter'} -
    - )} -
    -
  • + {/* 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'} +
    +
    +
  • + ); + })} +
+ ); + })()}
- {/* Affichage du formulaire actuel */}
{currentTemplate && (
-

- {currentTemplate.formTemplateData?.title || - currentTemplate.title || - currentTemplate.name || - 'Formulaire sans nom'} -

+
+

+ {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 || - 'Veuillez compléter ce formulaire pour continuer votre inscription.'} + currentTemplate.description || ''}

- Formulaire {currentTemplateIndex + 1} sur{' '} - {schoolFileMasters.length} + 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}
@@ -286,16 +402,21 @@ export default function DynamicFormsList({ onFormSubmit={(formData) => handleFormSubmit(formData, currentTemplate.id) } + // Désactive le bouton suivant si le template est validé + enable={currentTemplate.isValidated !== true} /> ) : ( // Formulaire existant (PDF, image, etc.)
- -
- {currentTemplate.name} -
- {currentTemplate.file && ( + {currentTemplate.file && currentTemplate.isValidated === true ? ( +