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 ? (
+
+ ) : currentTemplate.file && (
)}
- {enable && (
+ {/* Upload désactivé si validé par l'école */}
+ {enable && currentTemplate.isValidated !== true && (
handleUpload(file, currentTemplate)}
required
- enable
+ enable={true}
/>
)}
+ {/* Le label d'état est maintenant dans l'en-tête */}
)}
)}
{/* Message de fin */}
- {currentTemplateIndex >= schoolFileMasters.length && (
+ {currentTemplateIndex >= schoolFileTemplates.length && (
diff --git a/Front-End/src/components/Inscription/InscriptionFormShared.js b/Front-End/src/components/Inscription/InscriptionFormShared.js
index a72be3e..041d567 100644
--- a/Front-End/src/components/Inscription/InscriptionFormShared.js
+++ b/Front-End/src/components/Inscription/InscriptionFormShared.js
@@ -799,7 +799,7 @@ export default function InscriptionFormShared({
{/* Page 5 : Formulaires dynamiques d'inscription */}
{currentPage === 5 && (
{
+ // On construit la map index -> status à partir des templates
+ const newStatuses = {};
+ // Fiche élève (pas de validation individuelle)
+ newStatuses[0] = undefined;
+ // School templates
+ schoolFileTemplates.forEach((tpl, i) => {
+ if (typeof tpl.isValidated === 'boolean') {
+ newStatuses[1 + i] = tpl.isValidated ? 'accepted' : 'refused';
+ }
+ });
+ // Parent templates
+ parentFileTemplates.forEach((tpl, i) => {
+ if (typeof tpl.isValidated === 'boolean') {
+ newStatuses[1 + schoolFileTemplates.length + i] = tpl.isValidated ? 'accepted' : 'refused';
+ }
+ });
+ setDocStatuses(s => ({ ...s, ...newStatuses }));
+ }, [schoolFileTemplates, parentFileTemplates]);
const [showRefusedPopup, setShowRefusedPopup] = useState(false);
// Affiche la popup de confirmation finale (tous docs validés et classe sélectionnée)
@@ -155,8 +176,8 @@ export default function ValidateSubscription({
.map((doc, idx) => ({ ...doc, idx }))
.filter((doc, idx) => docStatuses[idx] === 'refused');
- // Récupère la liste des documents à cocher (inclut la fiche élève)
- const docIndexes = allTemplates.map((_, idx) => idx);
+ // Récupère la liste des documents à cocher (hors fiche élève)
+ const docIndexes = allTemplates.map((_, idx) => idx).filter(idx => idx !== 0);
const allChecked = docIndexes.length > 0 && docIndexes.every(idx => docStatuses[idx] === 'accepted' || docStatuses[idx] === 'refused');
const allValidated = docIndexes.length > 0 && docIndexes.every(idx => docStatuses[idx] === 'accepted');
const hasRefused = docIndexes.some(idx => docStatuses[idx] === 'refused');
@@ -223,84 +244,75 @@ export default function ValidateSubscription({
)}
{template.name}
- {/* 3 boutons côte à côte : À traiter / Validé / Refusé (pour tous les documents, y compris fiche élève) */}
-
-
-
+
+ )}
))}
@@ -377,7 +389,7 @@ export default function ValidateSubscription({
{`Le dossier d'inscription de ${firstName} ${lastName} va être refusé. Un email sera envoyé au responsable à l'adresse : `}
{email}
- {` avec la liste des documents non validés :`}
+ {' avec la liste des documents non validés :'}
{refusedDocs.map(doc => (
- {doc.name}