feat: Réorganisation items dans la page [N3WTS-17]

This commit is contained in:
N3WT DE COMPET
2026-01-05 14:56:36 +01:00
parent a034149eae
commit 8549699dec
2 changed files with 418 additions and 237 deletions

View File

@ -2,6 +2,10 @@ import React, { useState, useEffect, useRef } from 'react';
import {
Edit3,
Trash2,
FileText,
Star,
ChevronDown,
Plus
} from 'lucide-react';
import Modal from '@/components/Modal';
import FormTemplateBuilder from '@/components/Form/FormTemplateBuilder';
@ -31,12 +35,14 @@ import Loader from '@/components/Loader';
import { useNotification } from '@/context/NotificationContext';
import CreateDocumentModal from '@/components/Structure/Files/CreateDocumentModal';
import FileUpload from '@/components/Form/FileUpload';
import SectionTitle from '@/components/SectionTitle';
import DropdownMenu from '@/components/DropdownMenu';
function getItemBgColor(type, selected, forceTheme = false) {
// Colonne gauche : bleu, sélectionné plus soutenu
// Colonne gauche : blanc si rien n'est sélectionné, emerald si sélectionné
if (type === 'blue') {
if (selected) return 'bg-blue-200';
return 'bg-blue-50';
if (selected) return 'bg-emerald-100';
return 'bg-white';
}
// Colonne droite : thème selon type, jamais sélectionné
if (forceTheme) {
@ -57,38 +63,36 @@ function SimpleList({
minHeight = 'min-h-[200px]',
selectable = true,
forceTheme = false,
headerClassName = '',
listClassName = '',
itemClassName = '',
headerContent = null,
showGroups = false,
groupDocCount = null,
}) {
return (
<div className={`rounded border border-gray-200 bg-white ${minHeight} flex flex-col`}>
{title && (
<div
className={`
px-4 py-3
font-bold text-base
border-b border-gray-300
bg-gradient-to-r
${title === "Dossiers d'inscription"
? 'from-blue-100 to-blue-50 text-blue-800'
: title === 'Documents'
? 'from-emerald-100 to-orange-50 text-emerald-800'
: 'from-gray-100 to-white text-gray-800'}
px-4 py-2
border-b border-gray-200
bg-white
rounded-t
shadow-sm
tracking-wide
uppercase
flex items-center
${headerClassName}
`}
>
{title}
{headerContent ? headerContent : (
<span className="text-base text-gray-700">{title}</span>
)}
</div>
)}
<ul className="flex-1">
<ul className={`flex-1 ${listClassName}`}>
{items.length === 0 ? (
<li className="py-4 text-center text-gray-400">Aucun élément</li>
) : (
items.map((item, idx) => {
// Correction : clé unique et stable même si plusieurs items ont le même id
// On concatène l'id et l'index pour garantir l'unicité
const key = `${item.id}-${idx}`;
const selected = selectedId === item.id;
const itemType = getItemType ? getItemType(item) : 'gray';
@ -103,10 +107,40 @@ function SimpleList({
selectable && idx !== items.length - 1
? '-mb-[1px]'
: '';
let description = '';
if (typeof item.description === 'string' && item.description.trim()) {
description = item.description;
} else if (
item._type === 'emerald' &&
item.formMasterData &&
typeof item.formMasterData.description === 'string' &&
item.formMasterData.description.trim()
) {
description = item.formMasterData.description;
} else {
description = 'aucune description fournie';
}
const groupsLabel =
showGroups && Array.isArray(item.groups) && item.groups.length > 0
? item.groups.map(g => g.name).join(', ')
: null;
const docCount = groupDocCount && typeof groupDocCount === 'function'
? groupDocCount(item)
: null;
const showCustomForm =
item._type === 'emerald' &&
Array.isArray(item.formMasterData?.fields) &&
item.formMasterData.fields.length > 0;
const showRequired =
item._type === 'orange' && item.is_required;
// Correction du bug liseré : appliquer un z-index élevé au premier item sélectionné
const extraZ = selected && idx === 0 ? 'z-20 relative' : '';
return (
<li
key={key}
className={`flex items-center justify-between px-4 py-3 transition ${bgColor} ${selected && selectable ? 'ring-2 ring-blue-400' : ''} ${selectable ? 'cursor-pointer' : ''} ${zIndex} ${marginFix}`}
className={`flex items-center justify-between px-4 py-3 transition ${bgColor} ${selected && selectable ? 'ring-2 ring-emerald-400' : ''} ${selectable ? 'cursor-pointer' : ''} ${zIndex} ${marginFix} ${extraZ} ${typeof itemClassName === 'function' ? itemClassName(item) : itemClassName}`}
onClick={() => {
if (!selectable || !onSelect) return;
if (selected) {
@ -119,8 +153,31 @@ function SimpleList({
aria-selected={selected}
role="option"
>
<span className="font-medium text-gray-800">{item.name}</span>
{actionButtons && actionButtons(item)}
<div className="flex flex-col">
<span className="text-gray-800">{item.name}</span>
<span className="text-xs text-gray-400 mt-1 italic">
{description}
</span>
</div>
<div className="flex items-center gap-2">
{docCount !== null && (
<span className="text-xs text-blue-700 font-semibold mr-2">{docCount} document{docCount > 1 ? 's' : ''}</span>
)}
{showCustomForm && (
<span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-bold border border-yellow-600 bg-yellow-400 text-yellow-900 mr-1">
Formulaire personnalisé
</span>
)}
{showRequired && (
<span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-bold border border-orange-500 bg-orange-100 text-orange-700 mr-1">
Obligatoire
</span>
)}
{showGroups && groupsLabel && (
<span className="text-xs text-gray-500 mr-2">{groupsLabel}</span>
)}
{actionButtons && actionButtons(item)}
</div>
</li>
);
})
@ -137,7 +194,7 @@ export default function FilesGroupsManagement({
const [schoolFileMasters, setSchoolFileMasters] = useState([]);
const [parentFiles, setParentFileMasters] = useState([]);
const [groups, setGroups] = useState([]);
const [selectedGroup, setSelectedGroup] = useState(null);
const [isModalOpen, setIsModalOpen] = useState(false);
const [isEditing, setIsEditing] = useState(false);
const [fileToEdit, setFileToEdit] = useState(null);
@ -148,14 +205,35 @@ export default function FilesGroupsManagement({
const [removePopupMessage, setRemovePopupMessage] = useState('');
const [removePopupOnConfirm, setRemovePopupOnConfirm] = useState(() => {});
const [isLoading, setIsLoading] = useState(false);
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
const [createModalKey, setCreateModalKey] = useState(0);
const [selectedGroupId, setSelectedGroupId] = useState(null);
const [showHelp, setShowHelp] = useState(false);
const { showNotification } = useNotification();
const [isFormBuilderOpen, setIsFormBuilderOpen] = useState(false);
const [isFileUploadOpen, setIsFileUploadOpen] = useState(false);
const [isParentFileModalOpen, setIsParentFileModalOpen] = useState(false);
const [editingParentFile, setEditingParentFile] = useState(null);
const [isFileUploadModalOpen, setIsFileUploadModalOpen] = useState(false);
// Pour la popup "Télécharger un document existant"
const [isFileUploadPopupOpen, setIsFileUploadPopupOpen] = useState(false);
// Dropdown pour création de document
const [isDocDropdownOpen, setIsDocDropdownOpen] = useState(false);
// Handler pour le dropdown document (ouvre la bonne modale)
const handleDocDropdownSelect = (type) => {
setIsDocDropdownOpen(false);
if (type === 'formulaire') {
setIsFormBuilderOpen(true);
setIsEditing(false);
setFileToEdit(null);
} else if (type === 'formulaire_existant') {
setIsFileUploadPopupOpen(true);
setFileToEdit({});
} else if (type === 'parent') {
setEditingParentFile(null);
setIsParentFileModalOpen(true);
}
};
const transformFileData = (file, groups) => {
// file.groups peut contenir des IDs (number/string) ou des objets {id, name}
@ -363,6 +441,14 @@ export default function FilesGroupsManagement({
setIsGroupModalOpen(true);
};
// Handler pour sélectionner/désélectionner un groupe (simple)
const handleGroupSelect = (groupId) => {
setSelectedGroupId((prev) => (prev === groupId ? null : groupId));
};
// Handler pour tout désélectionner
const clearGroupSelection = () => setSelectedGroupId(null);
const handleGroupDelete = (groupId) => {
// Vérifier si des schoolFileMasters utilisent ce groupe
const filesInGroup = schoolFileMasters.filter(
@ -392,10 +478,8 @@ export default function FilesGroupsManagement({
throw new Error('Erreur lors de la suppression du groupe.');
}
setGroups(groups.filter((group) => group.id !== groupId));
// Si le groupe supprimé était sélectionné, on désélectionne
if (String(selectedGroupId) === String(groupId)) {
setSelectedGroupId(null);
}
// Purger la sélection si le groupe supprimé était sélectionné
setSelectedGroupId((prev) => (prev === groupId ? null : prev));
setRemovePopupVisible(false);
setIsLoading(false);
showNotification('Groupe supprimé avec succès.', 'success', 'Succès');
@ -434,9 +518,9 @@ export default function FilesGroupsManagement({
});
};
// Correction du bug : ne pas supprimer l'élément lors de l'édition d'un doc parent
const handleEdit = (id, updatedFile) => {
logger.debug('[FilesGroupsManagement] handleEdit called with:', id, updatedFile);
// Correction : vérifier si updatedFile est bien un objet (et pas juste un id)
if (typeof updatedFile !== 'object' || updatedFile === null) {
logger.error('[FilesGroupsManagement] handleEdit: updatedFile is not an object', updatedFile);
return Promise.reject(new Error('updatedFile is not an object'));
@ -445,10 +529,11 @@ export default function FilesGroupsManagement({
return editRegistrationParentFileMaster(id, updatedFile, csrfToken)
.then((response) => {
logger.debug('[FilesGroupsManagement] editRegistrationParentFileMaster response:', response);
const modifiedFile = response.data; // Extraire les données mises à jour
const modifiedFile = response.data || response;
setParentFileMasters((prevFiles) =>
prevFiles.map((file) => (file.id === id ? modifiedFile : file))
);
// Correction : ne pas filtrer/supprimer l'élément, juste le remplacer
logger.debug('Document parent mis à jour avec succès:', modifiedFile);
return modifiedFile;
})
@ -498,63 +583,7 @@ export default function FilesGroupsManagement({
setIsParentFileModalOpen(false);
};
// Ouvre le menu de choix pour formulaire d'école
const handleCreateFormMenu = () => {
setIsCreateModalOpen(false);
setTimeout(() => setIsCreateModalOpen(true), 0); // force le reset du menu
};
// Ouvre la modale FormTemplateBuilder
const handleOpenFormTemplateBuilder = () => {
setIsModalOpen(true);
setIsEditing(false);
setFileToEdit(null);
};
// Ouvre la modale FileUpload
const handleOpenFileUpload = () => {
setIsFileUploadModalOpen(true);
};
// Ferme la modale FileUpload
const handleCloseFileUpload = () => {
setIsFileUploadModalOpen(false);
};
// Soumission du formulaire existant (upload)
const handleSubmitFileUpload = ({ name, group_ids, file }) => {
// On centralise la logique dans handleCreateSchoolFileMaster
handleCreateSchoolFileMaster({ name, group_ids, file });
setIsFileUploadModalOpen(false);
};
// Filtrage des formulaires et pièces selon le dossier sélectionné
// Appliquer la transformation à la volée ici et comparer en string
const filteredFiles = schoolFileMasters
.map((file) => transformFileData(file, groups))
.filter((file) => {
if (!selectedGroupId) return false;
return (
file.groups &&
file.groups.some((group) => String(group.id) === String(selectedGroupId))
);
});
const filteredParentFiles = parentFiles.filter((file) => {
if (!selectedGroupId) return false;
return (
file.groups &&
file.groups.map(String).includes(String(selectedGroupId))
);
});
// Fusion des deux types de documents pour la colonne de droite
const mergedDocuments = [
...filteredFiles.map((doc) => ({ ...doc, _type: 'emerald' })),
...filteredParentFiles.map((doc) => ({ ...doc, _type: 'orange' })),
];
// Nouvelle explication: adaptée au contexte "dossier lié à une classe/groupe de classes"
// Nouvelle aide adaptée
const renderExplanation = () => (
<div className="mb-4">
<button
@ -575,22 +604,35 @@ export default function FilesGroupsManagement({
</h2>
<div className="text-gray-700 space-y-2">
<p>
<span className="font-semibold">1. Créez un ou plusieurs <span className="text-blue-700">dossiers d&apos;inscription</span></span> :<br />
Chaque dossier correspond à une classe ou un groupe de classes (ex : <span className="italic">Dossier Maternelles</span>, <span className="italic">Dossier Élémentaires</span>). Lors de la création d&apos;une inscription élève, un seul dossier d&apos;inscription sera rattaché à l&apos;élève.
<span className="font-semibold">Organisation de la page :</span>
<br />
<span className="text-blue-700 font-semibold">Colonne de gauche</span> : liste des dossiers d&apos;inscription (groupes/classes).
<br />
<span className="text-emerald-700 font-semibold">Colonne de droite</span> : liste des documents à fournir pour l&apos;inscription.
</p>
<p>
<span className="font-semibold">2. Pour chaque dossier, ajoutez des documents à fournir :</span>
<span className="font-semibold">Ajout de dossiers :</span>
<br />
Cliquez sur le bouton <span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-bold bg-emerald-500 text-white border border-emerald-600">+</span> à droite de la liste pour créer un nouveau dossier d&apos;inscription.
</p>
<p>
<span className="font-semibold">Ajout de documents :</span>
<br />
Cliquez sur le bouton <span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-bold bg-emerald-500 text-white border border-emerald-600">+</span> à droite de la liste des documents pour ajouter :
</p>
<ul className="list-disc list-inside ml-6">
<li>
<span className="text-emerald-700 font-semibold">Formulaires personnalisés</span> : créés dynamiquement par l&apos;école, à remplir et/ou signer électroniquement par la famille (ex : autorisation de sortie, fiche sanitaire, etc.).
<span className="text-yellow-700 font-semibold">Formulaire personnalisé</span> : créé dynamiquement par l&apos;école, à remplir et/ou signer électroniquement par la famille.
</li>
<li>
<span className="text-orange-700 font-semibold">Pièces à fournir</span> : documents à déposer par la famille (ex : RIB, justificatif de domicile, ou formulaire PDF à télécharger, remplir puis ré-uploader).
<span className="text-black font-semibold">Formulaire existant</span> : importez un PDF ou autre document à faire remplir.
</li>
<li>
<span className="text-orange-700 font-semibold">Pièce à fournir</span> : document à déposer par la famille (ex : RIB, justificatif de domicile).
</li>
</ul>
<div className="mt-2 text-sm text-gray-600">
<span className="font-semibold">Astuce :</span> Commencez toujours par créer vos dossiers d&apos;inscription (liés à vos classes) avant d&apos;ajouter des documents à fournir.
<span className="font-semibold">Astuce :</span> Créez d&apos;abord vos dossiers d&apos;inscription avant d&apos;ajouter des documents à fournir.
</div>
</div>
</div>
@ -598,42 +640,86 @@ export default function FilesGroupsManagement({
</div>
);
// Correction : définition de handleBackToCreateMenu
const handleBackToCreateMenu = () => {
setCreateModalKey((k) => k + 1); // force le reset du composant modal
setIsCreateModalOpen(true);
// Filtrage des formulaires et pièces selon le dossier sélectionné
// Si aucun groupe sélectionné, la colonne de droite est vide
let filteredFiles = [];
let filteredParentFiles = [];
if (selectedGroupId) {
filteredFiles = schoolFileMasters
.map((file) => transformFileData(file, groups))
.filter(
(file) =>
file.groups &&
file.groups.some((group) => group.id === selectedGroupId)
);
filteredParentFiles = parentFiles.filter(
(file) =>
file.groups &&
file.groups.some((gid) =>
(typeof gid === 'object' ? gid.id : gid) === selectedGroupId
)
);
}
const mergedDocuments =
selectedGroupId
? [
...filteredFiles.map((doc) => ({ ...doc, _type: 'emerald' })),
...filteredParentFiles.map((doc) => ({ ...doc, _type: 'orange' })),
]
: [];
// Calcul du nombre de documents par groupe
const getGroupDocCount = (group) => {
const groupId = group.id;
let count = 0;
// Documents école
count += schoolFileMasters.filter(
(file) =>
Array.isArray(file.groups)
? file.groups.some((g) => (typeof g === 'object' ? g.id : g) === groupId)
: false
).length;
// Pièces à fournir
count += parentFiles.filter(
(file) =>
Array.isArray(file.groups)
? file.groups.some((g) => (typeof g === 'object' ? g.id : g) === groupId)
: false
).length;
return count;
};
// Nouvelle disposition: sections côte à côte alignées
return (
<div className="w-full">
{/* Aide optionnelle + bouton global de création */}
<div className="mb-8">
{renderExplanation()}
<div className="flex justify-center">
<button
className="flex items-center justify-center gap-3 bg-emerald-500 hover:bg-emerald-600 text-white px-8 py-4 rounded-xl shadow transition text-lg font-bold"
style={{ minWidth: '320px', maxWidth: '400px' }}
onClick={() => setIsCreateModalOpen(true)}
>
<span className="text-2xl font-bold">+</span>
<span>Créer un nouvel élément</span>
</button>
</div>
</div>
{/* Aide optionnelle */}
<div className="mb-8">{renderExplanation()}</div>
<div className="flex gap-8">
{/* Colonne gauche : Dossiers d'inscription */}
<div className="w-[30%] min-w-[260px]">
{/* 2 colonnes : groupes à gauche, documents à droite */}
<div className="flex flex-row gap-8">
{/* Colonne groupes (1/3) */}
<div className="flex flex-col w-1/3 min-w-[320px] max-w-md">
<div className="flex items-center mb-4">
<SectionTitle title="Liste des dossiers d'inscriptions" />
<div className="flex-1" />
<button
className="flex items-center justify-center bg-emerald-500 hover:bg-emerald-600 text-white px-3 py-2 rounded-lg shadow transition text-base font-semibold"
onClick={() => setIsGroupModalOpen(true)}
title="Créer un nouveau dossier"
>
<Plus className="w-5 h-5" />
</button>
</div>
<SimpleList
items={groups}
selectedId={selectedGroupId}
onSelect={setSelectedGroupId}
onSelect={handleGroupSelect}
getItemType={() => 'blue'}
title="Dossiers d'inscription"
minHeight="min-h-[240px]"
minHeight="min-h-[60px]"
selectable={true}
forceTheme={false}
groupDocCount={getGroupDocCount}
actionButtons={(row) => (
<div className="flex items-center gap-2">
<button
@ -654,130 +740,226 @@ export default function FilesGroupsManagement({
)}
/>
</div>
{/* Colonne droite : Documents (fusion des formulaires et pièces à fournir) */}
<div className="w-[70%] min-w-[320px]">
<div className="rounded border border-gray-200 bg-white min-h-[240px] flex flex-col">
<div
className={`
px-4 py-3
font-bold text-base
border-b border-gray-300
bg-gradient-to-r
from-emerald-100 to-orange-50 text-emerald-800
rounded-t
shadow-sm
tracking-wide
uppercase
flex items-center
`}
>
Documents
</div>
{selectedGroupId === null ? (
<div className="flex items-center justify-center flex-1 bg-white text-gray-400 text-center text-base">
Sélectionnez un dossier pour voir les documents associés.
</div>
) : (
<SimpleList
key={selectedGroupId}
items={mergedDocuments}
selectedId={null}
onSelect={null}
getItemType={(item) => item._type}
// title="Documents" // Supprimé ici, header déjà affiché au-dessus
minHeight="min-h-[240px]"
selectable={false}
forceTheme={true}
actionButtons={(row) => (
<div className="flex items-center gap-2">
{row._type === 'emerald' ? (
<>
<button
onClick={(e) => { e.stopPropagation(); editTemplateMaster(row); }}
className="text-blue-500 hover:text-blue-700"
title="Modifier"
>
<Edit3 className="w-5 h-5" />
</button>
<button
onClick={(e) => { e.stopPropagation(); deleteTemplateMaster(row); }}
className="text-red-500 hover:text-red-700"
title="Supprimer"
>
<Trash2 className="w-5 h-5" />
</button>
</>
) : (
<>
<button
onClick={(e) => { e.stopPropagation(); openEditParentFileModal(row); }}
className="text-blue-500 hover:text-blue-700"
title="Modifier"
>
<Edit3 className="w-5 h-5" />
</button>
<button
onClick={(e) => { e.stopPropagation(); handleDelete(row.id); }}
className="text-red-500 hover:text-red-700"
title="Supprimer"
>
<Trash2 className="w-5 h-5" />
</button>
</>
)}
</div>
)}
/>
)}
{/* Colonne documents (2/3) */}
<div className="flex flex-col w-2/3">
<div className="flex items-center mb-4">
<SectionTitle title="Liste des documents" />
<div className="flex-1" />
<DropdownMenu
buttonContent={
<span className="flex items-center">
<Plus className="w-5 h-5" />
<ChevronDown className="w-4 h-4 ml-1" />
</span>
}
items={[
{
type: 'item',
label: (
<span className="flex items-center">
<Star className="w-5 h-5 mr-2 text-yellow-600" />
Formulaire personnalisé
</span>
),
onClick: () => handleDocDropdownSelect('formulaire'),
},
{
type: 'item',
label: (
<span className="flex items-center">
<FileText className="w-5 h-5 mr-2 text-gray-600" />
Formulaire existant
</span>
),
onClick: () => handleDocDropdownSelect('formulaire_existant'),
},
{
type: 'item',
label: (
<span className="flex items-center">
<Plus className="w-5 h-5 mr-2 text-orange-500" />
Pièce à fournir
</span>
),
onClick: () => handleDocDropdownSelect('parent'),
},
]}
buttonClassName="flex items-center justify-center bg-emerald-500 hover:bg-emerald-600 text-white px-3 py-2 rounded-lg shadow transition text-base font-semibold"
menuClassName="absolute right-0 mt-2 w-56 bg-white border border-gray-200 rounded shadow-lg z-20"
dropdownOpen={isDocDropdownOpen}
setDropdownOpen={setIsDocDropdownOpen}
/>
</div>
{!selectedGroupId ? (
<div className="flex items-center justify-center h-40 text-gray-400 text-lg italic border border-gray-200 rounded bg-white">
Sélectionner un dossier d&apos;inscription
</div>
) : (
<SimpleList
key={selectedGroupId || 'all'}
items={mergedDocuments}
selectedId={null}
onSelect={null}
getItemType={(item) => item._type}
minHeight="min-h-[240px]"
selectable={false}
forceTheme={true}
listClassName=""
itemClassName="text-gray-800 bg-white"
title=""
headerContent={null}
showGroups={false}
actionButtons={(row) => (
<div className="flex items-center gap-2">
{row._type === 'emerald' ? (
<>
<button
onClick={(e) => { e.stopPropagation(); editTemplateMaster(row); }}
className="text-blue-500 hover:text-blue-700"
title="Modifier"
>
<Edit3 className="w-5 h-5" />
</button>
<button
onClick={(e) => { e.stopPropagation(); deleteTemplateMaster(row); }}
className="text-red-500 hover:text-red-700"
title="Supprimer"
>
<Trash2 className="w-5 h-5" />
</button>
</>
) : (
<>
<button
onClick={(e) => { e.stopPropagation(); openEditParentFileModal(row); }}
className="text-blue-500 hover:text-blue-700"
title="Modifier"
>
<Edit3 className="w-5 h-5" />
</button>
<button
onClick={(e) => { e.stopPropagation(); handleDelete(row.id); }}
className="text-red-500 hover:text-red-700"
title="Supprimer"
>
<Trash2 className="w-5 h-5" />
</button>
</>
)}
</div>
)}
/>
)}
</div>
</div>
{/* Modal de création centralisée */}
<CreateDocumentModal
key={createModalKey}
isOpen={isCreateModalOpen}
onClose={() => setIsCreateModalOpen(false)}
onCreateGroup={() => {
setIsGroupModalOpen(true);
setIsCreateModalOpen(false);
}}
onCreateForm={() => {
setIsCreateModalOpen(false);
setTimeout(() => setIsModalOpen(true), 0);
}}
onCreateParentFile={() => {
openParentFileModal();
setIsCreateModalOpen(false);
}}
onBack={handleBackToCreateMenu}
onCreateSchoolFileMaster={handleCreateSchoolFileMaster}
groups={groups}
/>
{/* Modal pour upload de formulaire existant */}
{/* Modals pour création/édition */}
<Modal
isOpen={isFileUploadModalOpen}
setIsOpen={handleCloseFileUpload}
title="Importer un formulaire existant"
modalClassName="w-full max-w-md"
isOpen={isFormBuilderOpen}
setIsOpen={setIsFormBuilderOpen}
title="Créer un formulaire personnalisé"
modalClassName="w-11/12 h-5/6"
>
<FileUpload
selectionMessage="Sélectionnez le fichier du formulaire"
onFileSelect={(file) => {
// Ici, il faut gérer le nom et les groupes (à adapter selon vos besoins UI)
// Par exemple, ouvrir un petit formulaire pour compléter les infos puis submit
<FormTemplateBuilder
onSave={(data) => {
handleCreateSchoolFileMaster(data);
setIsFormBuilderOpen(false);
}}
onSubmit={handleSubmitFileUpload}
required
enable
groups={groups}
isEditing={false}
/>
</Modal>
{/* Modal création/édition pièce à fournir */}
{/* Popup pour téléchargement d'un document existant */}
<Modal
isOpen={isFileUploadPopupOpen}
setIsOpen={setIsFileUploadPopupOpen}
title="Télécharger un document existant"
modalClassName="w-full max-w-md"
>
<form
className="flex flex-col gap-4"
onSubmit={e => {
e.preventDefault();
if (!fileToEdit?.name || !fileToEdit?.group_ids || !fileToEdit?.file) return;
handleCreateSchoolFileMaster({
name: fileToEdit.name,
group_ids: fileToEdit.group_ids,
file: fileToEdit.file,
});
setIsFileUploadPopupOpen(false);
setFileToEdit(null);
}}
>
<input
type="text"
className="border rounded px-3 py-2"
placeholder="Nom du document"
value={fileToEdit?.name || ''}
onChange={e => setFileToEdit({ ...fileToEdit, name: e.target.value })}
required
/>
<div className="space-y-2">
<label className="block text-sm font-medium text-gray-700">
Groupes d&apos;inscription <span className="text-red-500">*</span>
</label>
<div className="space-y-2 max-h-32 overflow-y-auto border rounded-md p-2">
{groups && groups.length > 0 ? (
groups.map((group) => (
<label key={group.id} className="flex items-center">
<input
type="checkbox"
checked={fileToEdit?.group_ids?.includes(group.id) || false}
onChange={(e) => {
let group_ids = fileToEdit?.group_ids || [];
if (e.target.checked) {
group_ids = [...group_ids, group.id];
} else {
group_ids = group_ids.filter((id) => id !== group.id);
}
setFileToEdit({ ...fileToEdit, group_ids });
}}
className="mr-2 text-blue-600"
/>
<span className="text-sm">{group.name}</span>
</label>
))
) : (
<p className="text-gray-500 text-sm">
Aucun groupe disponible
</p>
)}
</div>
</div>
<FileUpload
selectionMessage="Sélectionnez le fichier du document"
onFileSelect={file =>
setFileToEdit({ ...fileToEdit, file })
}
required
enable
/>
<button
type="submit"
className="bg-emerald-600 text-white px-4 py-2 rounded font-bold mt-2"
disabled={
!fileToEdit?.name ||
!fileToEdit?.group_ids ||
fileToEdit.group_ids.length === 0 ||
!fileToEdit?.file
}
>
Créer le document
</button>
</form>
</Modal>
<Modal
isOpen={isParentFileModalOpen}
setIsOpen={(open) => {
if (!open) closeParentFileModal();
setIsParentFileModalOpen(open);
if (!open) setEditingParentFile(null);
}}
title={editingParentFile ? 'Modifier la pièce à fournir' : 'Créer une pièce à fournir'}
modalClassName="w-full max-w-md"
@ -790,11 +972,11 @@ export default function FilesGroupsManagement({
handleDelete={handleDelete}
singleForm // affiche uniquement le formulaire, pas la liste
initialData={editingParentFile}
onCancel={closeParentFileModal}
onCancel={() => setIsParentFileModalOpen(false)}
/>
</Modal>
{/* Modals et popups */}
{/* Modals et popups pour édition */}
<Modal
isOpen={isModalOpen}
setIsOpen={(isOpen) => {
@ -802,8 +984,6 @@ export default function FilesGroupsManagement({
if (!isOpen) {
setFileToEdit(null);
setIsEditing(false);
// Retour au menu principal de création si fermeture
handleBackToCreateMenu();
}
}}
title={isEditing ? 'Modification du formulaire' : 'Créer un formulaire'}
@ -812,6 +992,7 @@ export default function FilesGroupsManagement({
<FormTemplateBuilder
onSave={(data) => {
(isEditing ? handleEditSchoolFileMaster : handleCreateSchoolFileMaster)(data);
setIsModalOpen(false);
}}
initialData={fileToEdit}
groups={groups}
@ -824,8 +1005,7 @@ export default function FilesGroupsManagement({
setIsOpen={(isOpen) => {
setIsGroupModalOpen(isOpen);
if (!isOpen) {
// Retour au menu principal de création si fermeture
handleBackToCreateMenu();
setGroupToEdit(null);
}
}}
title={
@ -835,6 +1015,7 @@ export default function FilesGroupsManagement({
<RegistrationFileGroupForm
onSubmit={(data) => {
handleGroupSubmit(data);
setIsGroupModalOpen(false);
}}
initialData={groupToEdit}
/>

View File

@ -76,7 +76,7 @@ function ParentFileForm({ initialData, groups, onSubmit, onCancel }) {
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Dossiers d&aposinscription <span className="text-red-500">*</span>
Dossiers d&apos;inscription <span className="text-red-500">*</span>
</label>
<select
multiple