diff --git a/Front-End/src/components/Structure/Files/FilesGroupsManagement.js b/Front-End/src/components/Structure/Files/FilesGroupsManagement.js
index aee1e66..bc75419 100644
--- a/Front-End/src/components/Structure/Files/FilesGroupsManagement.js
+++ b/Front-End/src/components/Structure/Files/FilesGroupsManagement.js
@@ -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 (
{title && (
- {title}
+ {headerContent ? headerContent : (
+ {title}
+ )}
)}
-
+
{items.length === 0 ? (
- Aucun élément
) : (
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 (
- {
if (!selectable || !onSelect) return;
if (selected) {
@@ -119,8 +153,31 @@ function SimpleList({
aria-selected={selected}
role="option"
>
- {item.name}
- {actionButtons && actionButtons(item)}
+
+ {item.name}
+
+ {description}
+
+
+
+ {docCount !== null && (
+ {docCount} document{docCount > 1 ? 's' : ''}
+ )}
+ {showCustomForm && (
+
+ Formulaire personnalisé
+
+ )}
+ {showRequired && (
+
+ Obligatoire
+
+ )}
+ {showGroups && groupsLabel && (
+ {groupsLabel}
+ )}
+ {actionButtons && actionButtons(item)}
+
);
})
@@ -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 = () => (
@@ -598,42 +640,86 @@ export default function FilesGroupsManagement({
);
- // 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 (
- {/* Aide optionnelle + bouton global de création */}
-
- {renderExplanation()}
-
-
-
-
+ {/* Aide optionnelle */}
+
{renderExplanation()}
-
- {/* Colonne gauche : Dossiers d'inscription */}
-
+ {/* 2 colonnes : groupes à gauche, documents à droite */}
+
+ {/* Colonne groupes (1/3) */}
+
+
+
+
+
+
'blue'}
- title="Dossiers d'inscription"
- minHeight="min-h-[240px]"
+ minHeight="min-h-[60px]"
selectable={true}
forceTheme={false}
+ groupDocCount={getGroupDocCount}
actionButtons={(row) => (
- {/* Colonne droite : Documents (fusion des formulaires et pièces à fournir) */}
-
-
-
- Documents
-
- {selectedGroupId === null ? (
-
- Sélectionnez un dossier pour voir les documents associés.
-
- ) : (
-
item._type}
- // title="Documents" // Supprimé ici, header déjà affiché au-dessus
- minHeight="min-h-[240px]"
- selectable={false}
- forceTheme={true}
- actionButtons={(row) => (
-
- {row._type === 'emerald' ? (
- <>
-
-
- >
- ) : (
- <>
-
-
- >
- )}
-
- )}
- />
- )}
+
+ {/* Colonne documents (2/3) */}
+
+
+
+
+
+
+
+
+ }
+ items={[
+ {
+ type: 'item',
+ label: (
+
+
+ Formulaire personnalisé
+
+ ),
+ onClick: () => handleDocDropdownSelect('formulaire'),
+ },
+ {
+ type: 'item',
+ label: (
+
+
+ Formulaire existant
+
+ ),
+ onClick: () => handleDocDropdownSelect('formulaire_existant'),
+ },
+ {
+ type: 'item',
+ label: (
+
+
+ Pièce à fournir
+
+ ),
+ 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}
+ />
+ {!selectedGroupId ? (
+
+ Sélectionner un dossier d'inscription
+
+ ) : (
+
item._type}
+ minHeight="min-h-[240px]"
+ selectable={false}
+ forceTheme={true}
+ listClassName=""
+ itemClassName="text-gray-800 bg-white"
+ title=""
+ headerContent={null}
+ showGroups={false}
+ actionButtons={(row) => (
+
+ {row._type === 'emerald' ? (
+ <>
+
+
+ >
+ ) : (
+ <>
+
+
+ >
+ )}
+
+ )}
+ />
+ )}
- {/* Modal de création centralisée */}
-
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 */}
- {
- // 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
+ {
+ handleCreateSchoolFileMaster(data);
+ setIsFormBuilderOpen(false);
}}
- onSubmit={handleSubmitFileUpload}
- required
- enable
+ groups={groups}
+ isEditing={false}
/>
- {/* Modal création/édition pièce à fournir */}
+ {/* Popup pour téléchargement d'un document existant */}
+
+
+
+
{
- 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)}
/>
- {/* Modals et popups */}
+ {/* Modals et popups pour édition */}
{
@@ -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({
{
(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({
{
handleGroupSubmit(data);
+ setIsGroupModalOpen(false);
}}
initialData={groupToEdit}
/>
diff --git a/Front-End/src/components/Structure/Files/ParentFiles.js b/Front-End/src/components/Structure/Files/ParentFiles.js
index 8092fab..dc102b7 100644
--- a/Front-End/src/components/Structure/Files/ParentFiles.js
+++ b/Front-End/src/components/Structure/Files/ParentFiles.js
@@ -76,7 +76,7 @@ function ParentFileForm({ initialData, groups, onSubmit, onCancel }) {