);
}
export default function FilesGroupsManagement({
csrfToken,
selectedEstablishmentId,
profileRole,
}) {
const [schoolFileMasters, setSchoolFileMasters] = useState([]);
const [parentFiles, setParentFileMasters] = useState([]);
const [groups, setGroups] = useState([]);
const router = useRouter();
const [isEditing, setIsEditing] = useState(false);
const [fileToEdit, setFileToEdit] = useState(null);
const [isGroupModalOpen, setIsGroupModalOpen] = useState(false);
const [groupToEdit, setGroupToEdit] = useState(null);
const [removePopupVisible, setRemovePopupVisible] = useState(false);
const [removePopupMessage, setRemovePopupMessage] = useState('');
const [removePopupOnConfirm, setRemovePopupOnConfirm] = useState(() => {});
const [isLoading, setIsLoading] = useState(false);
const [selectedGroupId, setSelectedGroupId] = useState(null);
const [showHelp, setShowHelp] = useState(false);
const { showNotification } = useNotification();
const [isParentFileModalOpen, setIsParentFileModalOpen] = useState(false);
const [editingParentFile, setEditingParentFile] = useState(null);
// 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') {
const groupParam = selectedGroupId ? `?groupId=${selectedGroupId}` : '';
router.push(`${FE_ADMIN_STRUCTURE_FORM_BUILDER_URL}${groupParam}`);
} 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}
const groupInfos = (file.groups || []).map((group) => {
if (typeof group === 'object' && group !== null && 'id' in group) {
// Déjà un objet groupe
const found = groups.find((g) => g.id === group.id);
return found || group;
} else {
// C'est un ID
return (
groups.find((g) => g.id === group) || {
id: group,
name: 'Groupe inconnu',
}
);
}
});
return {
...file,
groups: groupInfos,
};
};
// Ne pas transformer ici, stocker la donnée brute
useEffect(() => {
if (selectedEstablishmentId) {
Promise.all([
fetchRegistrationSchoolFileMasters(selectedEstablishmentId),
fetchRegistrationFileGroups(selectedEstablishmentId),
fetchRegistrationParentFileMasters(selectedEstablishmentId),
])
.then(([dataSchoolFileMasters, groupsData, dataParentFileMasters]) => {
setGroups(groupsData);
setParentFileMasters(dataParentFileMasters);
setSchoolFileMasters(dataSchoolFileMasters); // donnée brute
})
.catch((err) => {
logger.debug(err.message);
});
}
}, [selectedEstablishmentId]);
const deleteTemplateMaster = (templateMaster) => {
setRemovePopupVisible(true);
setRemovePopupMessage(
`Attention ! \nVous êtes sur le point de supprimer le formulaire "${templateMaster.name}".\nÊtes-vous sûr(e) de vouloir poursuivre l'opération ?`
);
setRemovePopupOnConfirm(() => () => {
setIsLoading(true);
// Supprimer le formulaire de la base de données
deleteRegistrationSchoolFileMaster(templateMaster.id, csrfToken)
.then((response) => {
if (response.ok) {
setSchoolFileMasters(
schoolFileMasters.filter(
(fichier) => fichier.id !== templateMaster.id
)
);
showNotification(
`Le formulaire "${templateMaster.name}" a été correctement supprimé.`,
'success',
'Succès'
);
setRemovePopupVisible(false);
} else {
showNotification(
`Erreur lors de la suppression du formulaire "${templateMaster.name}".`,
'error',
'Erreur'
);
setRemovePopupVisible(false);
}
})
.catch((error) => {
logger.error('Error deleting file from database:', error);
showNotification(
`Erreur lors de la suppression du formulaire "${templateMaster.name}".`,
'error',
'Erreur'
);
setRemovePopupVisible(false);
})
.finally(() => {
setIsLoading(false);
});
});
};
const editTemplateMaster = (file) => {
const isDynamic =
file.formMasterData &&
Array.isArray(file.formMasterData.fields) &&
file.formMasterData.fields.length > 0;
if (isDynamic) {
router.push(`${FE_ADMIN_STRUCTURE_FORM_BUILDER_URL}?id=${file.id}`);
} else {
setFileToEdit(file);
setIsEditing(true);
setIsFileUploadPopupOpen(true);
}
};
const handleCreateSchoolFileMaster = (
{
name,
group_ids,
formMasterData,
file,
requires_electronic_signature,
},
onCreated
) => {
// Toujours envoyer en FormData, même sans fichier
const dataToSend = new FormData();
const jsonData = {
name,
groups: group_ids,
formMasterData,
establishment: selectedEstablishmentId,
requires_electronic_signature: requires_electronic_signature || false,
};
dataToSend.append('data', JSON.stringify(jsonData));
if (file) {
// Récupérer l'extension du fichier d'origine
let extension = '';
if (file.name && file.name.lastIndexOf('.') !== -1) {
extension = file.name.substring(file.name.lastIndexOf('.'));
}
// Nettoyer le nom saisi pour le fichier (éviter les caractères spéciaux)
const cleanName = (name || 'document')
.replace(/[^a-zA-Z0-9_\-]/g, '_')
.replace(/_+/g, '_')
.replace(/^_+|_+$/g, '');
// Générer le nom de fichier final
const finalFileName = `${cleanName}${extension}`;
dataToSend.append('file', file, finalFileName);
}
createRegistrationSchoolFileMaster(dataToSend, csrfToken)
.then((data) => {
setSchoolFileMasters((prevFiles) => [...prevFiles, data]);
showNotification(
`Le formulaire "${name}" a été créé avec succès.`,
'success',
'Succès'
);
if (onCreated) onCreated(data);
})
.catch((error) => {
logger.error('Error creating form:', error);
showNotification(
'Erreur lors de la création du formulaire',
'error',
'Erreur'
);
});
};
const handleEditSchoolFileMaster = ({
name,
group_ids,
formMasterData,
id,
file,
requires_electronic_signature,
}) => {
// Correction : normaliser group_ids pour ne garder que les IDs (number/string)
let normalizedGroupIds = [];
if (Array.isArray(group_ids)) {
normalizedGroupIds = group_ids.map((g) =>
typeof g === 'object' && g !== null && 'id' in g ? g.id : g
);
}
const dataToSend = new FormData();
const jsonData = {
name: name,
groups: normalizedGroupIds,
formMasterData: formMasterData,
establishment: selectedEstablishmentId,
requires_electronic_signature: requires_electronic_signature || false,
};
dataToSend.append('data', JSON.stringify(jsonData));
// Cas 1 : Nouveau fichier sélectionné (File/Blob)
if (file instanceof File || file instanceof Blob) {
let extension = '';
if (file.name && file.name.lastIndexOf('.') !== -1) {
extension = file.name.substring(file.name.lastIndexOf('.'));
}
const cleanName = (name || 'document')
.replace(/[^a-zA-Z0-9_\-]/g, '_')
.replace(/_+/g, '_')
.replace(/^_+|_+$/g, '');
const finalFileName = `${cleanName}${extension}`;
dataToSend.append('file', file, finalFileName);
}
// Cas 2 : Pas de nouveau fichier, mais le nom a changé → renvoyer le fichier existant avec le nouveau nom
else if (typeof file === 'string' && file) {
// Extraire l'extension du path existant
let extension = '';
const lastDot = file.lastIndexOf('.');
if (lastDot !== -1) {
extension = file.substring(lastDot);
}
const cleanName = (name || 'document')
.replace(/[^a-zA-Z0-9_\-]/g, '_')
.replace(/_+/g, '_')
.replace(/^_+|_+$/g, '');
const finalFileName = `${cleanName}${extension}`;
// Correction : il faut récupérer le fichier à l'URL d'origine, pas à la nouvelle URL renommée
// On utilise le path original (file) pour le fetch, pas le chemin avec le nouveau nom
fetch(getSecureFileUrl(file))
.then((response) => {
if (!response.ok) throw new Error('Fichier distant introuvable');
return response.blob();
})
.then((blob) => {
dataToSend.append('file', blob, finalFileName);
editRegistrationSchoolFileMaster(id, dataToSend, csrfToken)
.then((data) => {
setSchoolFileMasters((prevFichiers) =>
prevFichiers.map((f) => (f.id === id ? data : f))
);
showNotification(
`Le formulaire "${name}" a été modifié avec succès.`,
'success',
'Succès'
);
})
.catch((error) => {
logger.error('Error editing form:', error);
showNotification(
'Erreur lors de la modification du formulaire',
'error',
'Erreur'
);
});
})
.catch((error) => {
logger.error(
'Erreur lors de la récupération du fichier existant pour renommage:',
error
);
showNotification(
'Erreur lors de la récupération du fichier existant pour renommage',
'error',
'Erreur'
);
});
return; // On sort ici car l'appel API est fait dans le fetch
}
// Cas standard (nouveau fichier ou pas de renommage)
editRegistrationSchoolFileMaster(id, dataToSend, csrfToken)
.then((data) => {
setSchoolFileMasters((prevFichiers) =>
prevFichiers.map((f) => (f.id === id ? data : f))
);
showNotification(
`Le formulaire "${name}" a été modifié avec succès.`,
'success',
'Succès'
);
})
.catch((error) => {
logger.error('Error editing form:', error);
showNotification(
'Erreur lors de la modification du formulaire',
'error',
'Erreur'
);
});
};
const handleGroupSubmit = (groupData) => {
if (groupToEdit) {
editRegistrationFileGroup(groupToEdit.id, groupData, csrfToken)
.then((updatedGroup) => {
setGroups(
groups.map((group) =>
group.id === groupToEdit.id ? updatedGroup : group
)
);
setGroupToEdit(null);
setIsGroupModalOpen(false);
})
.catch((error) => {
logger.error('Error handling group:', error);
showNotification(
"Erreur lors de l'opération sur le groupe",
'error',
'Erreur'
);
});
} else {
// Ajouter l'établissement sélectionné lors de la création d'un nouveau groupe
const newGroupData = {
...groupData,
establishment: selectedEstablishmentId,
};
createRegistrationFileGroup(newGroupData, csrfToken)
.then((newGroup) => {
setGroups([...groups, newGroup]);
setIsGroupModalOpen(false);
})
.catch((error) => {
logger.error('Error handling group:', error);
showNotification(
"Erreur lors de l'opération sur le groupe",
'error',
'Erreur'
);
});
}
};
const handleGroupEdit = (group) => {
setGroupToEdit(group);
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(
(file) => file.group && file.group.id === groupId
);
if (filesInGroup.length > 0) {
showNotification(
"Impossible de supprimer ce groupe car il contient déjà des formulaires. Veuillez d'abord retirer tous les formules de ce groupe.",
'error',
'Erreur'
);
return;
}
setRemovePopupVisible(true);
setRemovePopupMessage(
'Attentions ! \nÊtes-vous sûr de vouloir supprimer ce groupe ?'
);
setRemovePopupOnConfirm(() => () => {
setIsLoading(true);
deleteRegistrationFileGroup(groupId, csrfToken)
.then((response) => {
if (response.status === 409) {
throw new Error('Ce groupe est lié à des inscriptions existantes.');
}
if (!response.ok) {
throw new Error('Erreur lors de la suppression du groupe.');
}
setGroups(groups.filter((group) => group.id !== groupId));
// 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');
})
.catch((error) => {
logger.error('Error deleting group:', error);
setRemovePopupVisible(false);
setIsLoading(false);
showNotification(
error.message ||
"Erreur lors de la suppression du groupe. Vérifiez qu'aucune inscription n'utilise ce groupe.",
'error',
'Erreur'
);
});
});
};
const handleCreate = (newParentFile) => {
return createRegistrationParentFileMaster(newParentFile, csrfToken)
.then((response) => {
const createdFile = response;
// Ajouter le nouveau fichier parent à la liste existante
setParentFileMasters((prevFiles) => [...prevFiles, createdFile]);
logger.debug('Document parent créé avec succès:', createdFile);
return createdFile;
})
.catch((error) => {
logger.error('Erreur lors de la création du document parent:', error);
showNotification(
'Une erreur est survenue lors de la création du document parent.',
'error',
'Erreur'
);
throw error;
});
};
// 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
);
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'));
}
logger.debug(
'[FilesGroupsManagement] handleEdit payload:',
JSON.stringify(updatedFile)
);
return editRegistrationParentFileMaster(id, updatedFile, csrfToken)
.then((response) => {
logger.debug(
'[FilesGroupsManagement] editRegistrationParentFileMaster response:',
response
);
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;
})
.catch((error) => {
logger.error(
'Erreur lors de la modification du document parent:',
error
);
showNotification(
'Une erreur est survenue lors de la modification du document parent.',
'error',
'Erreur'
);
throw error;
});
};
const handleDelete = (id) => {
// Vérification avant suppression : afficher une popup de confirmation
setRemovePopupMessage(
"Attention !\nVous êtes sur le point de supprimer la pièce à fournir.\nÊtes-vous sûr(e) de vouloir poursuivre l'opération ?"
);
setRemovePopupOnConfirm(() => () => {
deleteRegistrationParentFileMaster(id, csrfToken)
.then(() => {
setParentFileMasters((prevFiles) =>
prevFiles.filter((file) => file.id !== id)
);
logger.debug('Document parent supprimé avec succès:', id);
showNotification(
'La pièce à fournir a été supprimée avec succès.',
'success',
'Succès'
);
setRemovePopupVisible(false);
})
.catch((error) => {
logger.error(
'Erreur lors de la suppression du fichier parent:',
error
);
showNotification(
'Erreur lors de la suppression de la pièce à fournir.',
'error',
'Erreur'
);
setRemovePopupVisible(false);
});
});
setRemovePopupVisible(true);
};
// Ouvre la modale de création d'une pièce à fournir
const openParentFileModal = () => {
setEditingParentFile(null);
setIsParentFileModalOpen(true);
};
// Ouvre la modale d'édition d'une pièce à fournir
const openEditParentFileModal = (file) => {
setEditingParentFile(file);
setIsParentFileModalOpen(true);
setIsEditing(true);
};
// Ferme la modale de pièce à fournir
const closeParentFileModal = () => {
setEditingParentFile(null);
setIsParentFileModalOpen(false);
};
// Nouvelle aide adaptée
const renderExplanation = () => (
{showHelp && (
Gestion des dossiers et documents d'inscription
Organisation de la page :
Colonne de gauche
{' '}
: liste des dossiers d'inscription (groupes/classes).
Colonne de droite
{' '}
: liste des documents à fournir pour l'inscription.
Ajout de dossiers :
Cliquez sur le bouton{' '}
+
{' '}
à droite de la liste pour créer un nouveau dossier
d'inscription.
Ajout de documents :
Cliquez sur le bouton{' '}
+
{' '}
à droite de la liste des documents pour ajouter :
Formulaire existant
{' '}
: importez un PDF ou autre document à faire remplir. Vous pouvez
activer la signature électronique.
Pièce à fournir
{' '}
: document à déposer par la famille (ex : RIB, justificatif de
domicile).
Astuce : Créez d'abord
vos dossiers d'inscription avant d'ajouter des documents
à fournir.
)}
);
// 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;
};
return (
{/* Aide optionnelle */}
{renderExplanation()}
{/* 2 colonnes : groupes à gauche, documents à droite */}
{/* Popup pour création/édition d'un groupe de documents */}
{
setIsGroupModalOpen(isOpen);
if (!isOpen) {
setGroupToEdit(null);
}
}}
modalClassName="max-w-md sm:max-w-md sm:min-w-0"
title={
groupToEdit ? 'Modifier le dossier' : "Création d'un nouveau dossier"
}
>
{/* Popup pour création/édition d'un formulaire d'école déjà existant */}
{fileToEdit && fileToEdit.id ? (
) : (
)}
{/* Popup pour création/édition d'un document parent */}
{
setIsParentFileModalOpen(open);
if (!open) setEditingParentFile(null);
}}
modalClassName="max-w-md sm:max-w-md sm:min-w-0"
title={
editingParentFile && editingParentFile.id
? 'Modifier la pièce à fournir'
: 'Créer une pièce à fournir'
}
>