feat: Gestion de l'affichage des documents validés et non validés sur la page parent [N3WTS-2]

This commit is contained in:
N3WT DE COMPET
2026-02-22 18:34:00 +01:00
parent 8fd1b62ec0
commit 4f7d7d0024
4 changed files with 342 additions and 200 deletions

View File

@ -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) {

View File

@ -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 (
<div className="text-center py-8">
<FileText className="w-16 h-16 text-gray-300 mx-auto mb-4" />
@ -173,8 +204,6 @@ export default function DynamicFormsList({
);
}
const currentTemplate = getCurrentTemplate();
return (
<div className="mt-8 mb-4 w-full mx-auto flex gap-8">
{/* Liste des formulaires */}
@ -183,85 +212,172 @@ export default function DynamicFormsList({
Formulaires à compléter
</h3>
<div className="text-sm text-gray-600 mb-4">
{/* 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
</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>
{/* 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)
);
})}
</ul>
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 (
<ul className="space-y-2">
{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 = <CheckCircle className="w-5 h-5 text-emerald-600" />;
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 = <Hourglass className="w-5 h-5 text-orange-400" />;
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 = <XCircle className="w-5 h-5 text-red-500" />;
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 = <Hourglass className="w-5 h-5 text-orange-400" />;
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 = <Hourglass className="w-5 h-5 text-gray-400" />;
bgClass = isActive ? 'bg-gray-200' : '';
borderClass = isActive ? 'border border-gray-300' : '';
textClass = isActive ? 'text-gray-900 font-semibold' : 'text-gray-600';
canEdit = true;
}
}
return (
<li
key={tpl.id}
className={`flex items-center cursor-pointer p-2 rounded-md transition-colors ${
isActive
? `${bgClass.replace('50', '200')} ${borderClass.replace('200', '300')} ${textClass.replace('700', '900')} font-semibold`
: `${bgClass} ${borderClass} ${textClass}`
}`}
onClick={() => setCurrentTemplateIndex(schoolFileTemplates.findIndex(t => t.id === tpl.id))}
>
<span className="mr-3">{icon}</span>
<div className="flex-1 min-w-0">
<div className="text-sm truncate flex items-center gap-2">
{tpl.formMasterData?.title || tpl.title || tpl.name || 'Formulaire sans nom'}
<span className={`ml-2 px-2 py-0.5 rounded bg-${statusColor}-100 text-${statusColor}-700 text-xs font-semibold`}>
{statusLabel}
</span>
</div>
<div className="text-xs text-gray-500">
{tpl.formMasterData?.fields || tpl.fields
? `${(tpl.formMasterData?.fields || tpl.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.formTemplateData?.title ||
currentTemplate.title ||
currentTemplate.name ||
'Formulaire sans nom'}
</h3>
<div className="flex items-center gap-3 mb-2">
<h3 className="text-xl font-semibold text-gray-800">
{currentTemplate.name}
</h3>
{/* Label d'état */}
{currentTemplate.isValidated === true ? (
<span className="px-2 py-0.5 rounded bg-emerald-100 text-emerald-700 text-sm font-semibold">Validé</span>
) : ((formsData[currentTemplate.id] && Object.keys(formsData[currentTemplate.id]).length > 0) ||
(existingResponses[currentTemplate.id] && Object.keys(existingResponses[currentTemplate.id]).length > 0)) ? (
<span className="px-2 py-0.5 rounded bg-orange-100 text-orange-700 text-sm font-semibold">Complété</span>
) : (
<span className="px-2 py-0.5 rounded bg-red-100 text-red-700 text-sm font-semibold">Refusé</span>
)}
</div>
<p className="text-sm text-gray-600">
{currentTemplate.formTemplateData?.description ||
currentTemplate.description ||
'Veuillez compléter ce formulaire pour continuer votre inscription.'}
currentTemplate.description || ''}
</p>
<div className="text-xs text-gray-500 mt-1">
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}
</div>
</div>
@ -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.)
<div className="flex flex-col items-center gap-6">
<div className="flex flex-col items-center gap-2">
<FileText className="w-16 h-16 text-gray-400" />
<div className="text-lg font-semibold text-gray-700">
{currentTemplate.name}
</div>
{currentTemplate.file && (
{currentTemplate.file && currentTemplate.isValidated === true ? (
<iframe
src={`${BASE_URL}${currentTemplate.file}`}
title={currentTemplate.name}
className="w-full"
style={{ height: '600px', border: 'none' }}
/>
) : currentTemplate.file && (
<a
href={`${BASE_URL}${currentTemplate.file}`}
target="_blank"
@ -308,21 +429,24 @@ export default function DynamicFormsList({
</a>
)}
</div>
{enable && (
{/* Upload désactivé si validé par l'école */}
{enable && currentTemplate.isValidated !== true && (
<FileUpload
selectionMessage="Sélectionnez le fichier du document"
key={currentTemplate.id}
selectionMessage={'Sélectionnez le fichier du document'}
onFileSelect={(file) => handleUpload(file, currentTemplate)}
required
enable
enable={true}
/>
)}
{/* Le label d'état est maintenant dans l'en-tête */}
</div>
)}
</div>
)}
{/* Message de fin */}
{currentTemplateIndex >= schoolFileMasters.length && (
{currentTemplateIndex >= schoolFileTemplates.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">

View File

@ -799,7 +799,7 @@ export default function InscriptionFormShared({
{/* Page 5 : Formulaires dynamiques d'inscription */}
{currentPage === 5 && (
<DynamicFormsList
schoolFileMasters={schoolFileTemplates}
schoolFileTemplates={schoolFileTemplates}
existingResponses={formResponses}
onFormSubmit={handleDynamicFormSubmit}
onValidationChange={handleDynamicFormsValidationChange}

View File

@ -33,6 +33,27 @@ export default function ValidateSubscription({
const [isPageValid, setIsPageValid] = useState(false);
// Pour la validation/refus des documents
const [docStatuses, setDocStatuses] = useState({}); // {index: 'accepted'|'refused'}
// Met à jour docStatuses selon isValidated des templates récupérés
useEffect(() => {
// 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({
)}
</span>
<span className="flex-1">{template.name}</span>
{/* 3 boutons côte à côte : À traiter / Validé / Refusé (pour tous les documents, y compris fiche élève) */}
<span className="ml-2 flex gap-1">
<button
type="button"
className={`px-2 py-1 rounded-full text-xs font-medium border transition-colors focus:outline-none focus:ring-2 focus:ring-blue-400
${docStatuses[index] === undefined ? 'bg-gray-300 text-gray-700 border-gray-400' : 'bg-white text-gray-500 border-gray-300'}`}
aria-pressed={docStatuses[index] === undefined}
onClick={e => {
e.stopPropagation();
setDocStatuses(s => ({ ...s, [index]: undefined }));
}}
>À traiter</button>
<button
type="button"
className={`px-2 py-1 rounded-full text-xs font-medium border transition-colors focus:outline-none focus:ring-2 focus:ring-blue-400 flex items-center gap-1
${docStatuses[index] === 'accepted' ? 'bg-emerald-500 text-white border-emerald-500' : 'bg-white text-emerald-600 border-emerald-300'}`}
aria-pressed={docStatuses[index] === 'accepted'}
onClick={e => {
e.stopPropagation();
setDocStatuses(s => ({ ...s, [index]: 'accepted' }));
// Appel API pour valider le document (hors fiche élève)
if (index > 0 && handleValidateOrRefuseDoc) {
// index 0 = fiche élève, ensuite school puis parent puis SEPA
let template = null;
let type = null;
if (index > 0 && index <= schoolFileTemplates.length) {
template = schoolFileTemplates[index - 1];
type = 'school';
} else if (index > schoolFileTemplates.length && index <= schoolFileTemplates.length + parentFileTemplates.length) {
template = parentFileTemplates[index - 1 - schoolFileTemplates.length];
type = 'parent';
{/* 2 boutons : Validé / Refusé (sauf fiche élève) */}
{index !== 0 && (
<span className="ml-2 flex gap-1">
<button
type="button"
className={`px-2 py-1 rounded-full text-xs font-medium border transition-colors focus:outline-none focus:ring-2 focus:ring-blue-400 flex items-center gap-1
${docStatuses[index] === 'accepted' ? 'bg-emerald-500 text-white border-emerald-500' : 'bg-white text-emerald-600 border-emerald-300'}`}
aria-pressed={docStatuses[index] === 'accepted'}
onClick={e => {
e.stopPropagation();
setDocStatuses(s => ({ ...s, [index]: 'accepted' }));
// Appel API pour valider le document
if (handleValidateOrRefuseDoc) {
let template = null;
let type = null;
if (index > 0 && index <= schoolFileTemplates.length) {
template = schoolFileTemplates[index - 1];
type = 'school';
} else if (index > schoolFileTemplates.length && index <= schoolFileTemplates.length + parentFileTemplates.length) {
template = parentFileTemplates[index - 1 - schoolFileTemplates.length];
type = 'parent';
}
if (template && template.id) {
handleValidateOrRefuseDoc({
templateId: template.id,
type,
validated: true,
csrfToken,
});
}
}
if (template && template.id) {
handleValidateOrRefuseDoc({
templateId: template.id,
type,
validated: true,
csrfToken,
});
}}
>
<span className="text-lg"></span> Validé
</button>
<button
type="button"
className={`px-2 py-1 rounded-full text-xs font-medium border transition-colors focus:outline-none focus:ring-2 focus:ring-blue-400 flex items-center gap-1
${docStatuses[index] === 'refused' ? 'bg-red-500 text-white border-red-500' : 'bg-white text-red-600 border-red-300'}`}
aria-pressed={docStatuses[index] === 'refused'}
onClick={e => {
e.stopPropagation();
setDocStatuses(s => ({ ...s, [index]: 'refused' }));
// Appel API pour refuser le document
if (handleValidateOrRefuseDoc) {
let template = null;
let type = null;
if (index > 0 && index <= schoolFileTemplates.length) {
template = schoolFileTemplates[index - 1];
type = 'school';
} else if (index > schoolFileTemplates.length && index <= schoolFileTemplates.length + parentFileTemplates.length) {
template = parentFileTemplates[index - 1 - schoolFileTemplates.length];
type = 'parent';
}
if (template && template.id) {
handleValidateOrRefuseDoc({
templateId: template.id,
type,
validated: false,
csrfToken,
});
}
}
}
}}
>
<span className="text-lg"></span> Validé
</button>
<button
type="button"
className={`px-2 py-1 rounded-full text-xs font-medium border transition-colors focus:outline-none focus:ring-2 focus:ring-blue-400 flex items-center gap-1
${docStatuses[index] === 'refused' ? 'bg-red-500 text-white border-red-500' : 'bg-white text-red-600 border-red-300'}`}
aria-pressed={docStatuses[index] === 'refused'}
onClick={e => {
e.stopPropagation();
setDocStatuses(s => ({ ...s, [index]: 'refused' }));
// Appel API pour refuser le document (hors fiche élève)
if (index > 0 && handleValidateOrRefuseDoc) {
let template = null;
let type = null;
if (index > 0 && index <= schoolFileTemplates.length) {
template = schoolFileTemplates[index - 1];
type = 'school';
} else if (index > schoolFileTemplates.length && index <= schoolFileTemplates.length + parentFileTemplates.length) {
template = parentFileTemplates[index - 1 - schoolFileTemplates.length];
type = 'parent';
}
if (template && template.id) {
handleValidateOrRefuseDoc({
templateId: template.id,
type,
validated: false,
csrfToken,
});
}
}
}}
>
<span className="text-lg"></span> Refusé
</button>
</span>
}}
>
<span className="text-lg"></span> Refusé
</button>
</span>
)}
</li>
))}
</ul>
@ -377,7 +389,7 @@ export default function ValidateSubscription({
<span>
{`Le dossier d'inscription de ${firstName} ${lastName} va être refusé. Un email sera envoyé au responsable à l'adresse : `}
<span className="font-semibold text-blue-700">{email}</span>
{` avec la liste des documents non validés :`}
{' avec la liste des documents non validés :'}
<ul className="list-disc ml-6 mt-2">
{refusedDocs.map(doc => (
<li key={doc.idx}>{doc.name}</li>