mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-04-06 05:01:25 +00:00
1489 lines
53 KiB
JavaScript
1489 lines
53 KiB
JavaScript
import React, { useState, useEffect } from 'react';
|
||
import { useRouter } from 'next/navigation';
|
||
import { Edit, Trash2, FileText, Star, ChevronDown, Plus } from 'lucide-react';
|
||
import Modal from '@/components/Modal';
|
||
import {
|
||
// GET
|
||
fetchRegistrationFileGroups,
|
||
fetchRegistrationSchoolFileMasters,
|
||
fetchRegistrationParentFileMasters,
|
||
// POST
|
||
createRegistrationFileGroup,
|
||
createRegistrationSchoolFileMaster,
|
||
createRegistrationParentFileMaster,
|
||
// PUT
|
||
editRegistrationFileGroup,
|
||
editRegistrationSchoolFileMaster,
|
||
editRegistrationParentFileMaster,
|
||
// DELETE
|
||
deleteRegistrationFileGroup,
|
||
deleteRegistrationSchoolFileMaster,
|
||
deleteRegistrationParentFileMaster,
|
||
} from '@/app/actions/registerFileGroupAction';
|
||
import RegistrationFileGroupForm from '@/components/Structure/Files/RegistrationFileGroupForm';
|
||
import logger from '@/utils/logger';
|
||
import Popup from '@/components/Popup';
|
||
import Loader from '@/components/Loader';
|
||
import { useNotification } from '@/context/NotificationContext';
|
||
import FileUpload from '@/components/Form/FileUpload';
|
||
import SectionTitle from '@/components/SectionTitle';
|
||
import DropdownMenu from '@/components/DropdownMenu';
|
||
import CheckBox from '@/components/Form/CheckBox';
|
||
import Button from '@/components/Form/Button';
|
||
import InputText from '@/components/Form/InputText';
|
||
import { getSecureFileUrl } from '@/utils/fileUrl';
|
||
import { FE_ADMIN_STRUCTURE_FORM_BUILDER_URL } from '@/utils/Url';
|
||
|
||
function getItemBgColor(type, selected, forceTheme = false) {
|
||
// Colonne gauche : blanc si rien n'est sélectionné, emerald si sélectionné
|
||
if (type === 'blue') {
|
||
if (selected) return 'bg-primary/10';
|
||
return 'bg-white';
|
||
}
|
||
// Colonne droite : thème selon type, jamais sélectionné
|
||
if (forceTheme) {
|
||
if (type === 'emerald') return 'bg-primary/5';
|
||
if (type === 'orange') return 'bg-orange-50';
|
||
return 'bg-gray-50';
|
||
}
|
||
return 'bg-white';
|
||
}
|
||
|
||
function SimpleList({
|
||
items,
|
||
onSelect,
|
||
selectedId,
|
||
actionButtons,
|
||
getItemType,
|
||
title,
|
||
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-2
|
||
border-b border-gray-200
|
||
bg-white
|
||
rounded-t
|
||
flex items-center
|
||
${headerClassName}
|
||
`}
|
||
>
|
||
{headerContent ? (
|
||
headerContent
|
||
) : (
|
||
<span className="text-base text-gray-700">{title}</span>
|
||
)}
|
||
</div>
|
||
)}
|
||
<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) => {
|
||
const key = `${item.id}-${idx}`;
|
||
const selected = selectedId === item.id;
|
||
const itemType = getItemType ? getItemType(item) : 'gray';
|
||
const bgColor = getItemBgColor(itemType, selected, forceTheme);
|
||
const zIndex =
|
||
selectable && selected
|
||
? 'z-10 relative'
|
||
: selectable
|
||
? 'z-0 relative'
|
||
: '';
|
||
const marginFix =
|
||
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={`w-full flex items-center justify-between px-4 py-3 transition ${bgColor} ${selected && selectable ? 'ring-2 ring-tertiary' : ''} ${selectable ? 'cursor-pointer' : ''} ${zIndex} ${marginFix} ${extraZ} ${typeof itemClassName === 'function' ? itemClassName(item) : itemClassName}`}
|
||
onClick={() => {
|
||
if (!selectable || !onSelect) return;
|
||
if (selected) {
|
||
onSelect(null);
|
||
} else {
|
||
onSelect(item.id);
|
||
}
|
||
}}
|
||
tabIndex={0}
|
||
aria-selected={selected}
|
||
role="option"
|
||
>
|
||
<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>
|
||
);
|
||
})
|
||
)}
|
||
</ul>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
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 = () => (
|
||
<div className="mb-4">
|
||
<button
|
||
className="flex items-center gap-2 text-secondary hover:text-secondary font-medium mb-2"
|
||
onClick={() => setShowHelp((v) => !v)}
|
||
aria-expanded={showHelp}
|
||
aria-controls="aide-inscription"
|
||
>
|
||
<span className="underline">
|
||
{showHelp ? 'Masquer' : 'Afficher'} l’aide
|
||
</span>
|
||
<svg
|
||
className={`w-4 h-4 transition-transform ${showHelp ? 'rotate-180' : ''}`}
|
||
fill="none"
|
||
stroke="currentColor"
|
||
strokeWidth="2"
|
||
viewBox="0 0 24 24"
|
||
>
|
||
<path
|
||
strokeLinecap="round"
|
||
strokeLinejoin="round"
|
||
d="M19 9l-7 7-7-7"
|
||
/>
|
||
</svg>
|
||
</button>
|
||
{showHelp && (
|
||
<div
|
||
id="aide-inscription"
|
||
className="p-4 bg-blue-50 border border-blue-200 rounded mb-4"
|
||
>
|
||
<h2 className="font-headline text-lg font-bold mb-2">
|
||
Gestion des dossiers et documents d'inscription
|
||
</h2>
|
||
<div className="text-gray-700 space-y-2">
|
||
<p>
|
||
<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'inscription (groupes/classes).
|
||
<br />
|
||
<span className="text-secondary font-semibold">
|
||
Colonne de droite
|
||
</span>{' '}
|
||
: liste des documents à fournir pour l'inscription.
|
||
</p>
|
||
<p>
|
||
<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-primary text-white border border-primary">
|
||
+
|
||
</span>{' '}
|
||
à droite de la liste pour créer un nouveau dossier
|
||
d'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-primary text-white border border-primary">
|
||
+
|
||
</span>{' '}
|
||
à droite de la liste des documents pour ajouter :
|
||
</p>
|
||
<ul className="list-disc list-inside ml-6">
|
||
<li>
|
||
<span className="text-black font-semibold">
|
||
Formulaire existant
|
||
</span>{' '}
|
||
: importez un PDF ou autre document à faire remplir. Vous pouvez
|
||
activer la signature électronique.
|
||
</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> Créez d'abord
|
||
vos dossiers d'inscription avant d'ajouter des documents
|
||
à fournir.
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
|
||
// 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 (
|
||
<div className="w-full">
|
||
{/* Aide optionnelle */}
|
||
<div className="mb-8">{renderExplanation()}</div>
|
||
|
||
{/* 2 colonnes : groupes à gauche, documents à droite */}
|
||
<div className="flex flex-col xl:flex-row gap-8">
|
||
{/* Colonne groupes (plein écran mobile/tablette, 1/3 desktop) */}
|
||
<div className="flex flex-col w-full xl:w-1/3 xl:min-w-[320px] xl:max-w-md">
|
||
<div className="flex items-center mb-4">
|
||
<SectionTitle title="Liste des dossiers d'inscriptions" />
|
||
<div className="flex-1" />
|
||
{profileRole !== 0 && (
|
||
<button
|
||
className="flex items-center justify-center bg-primary hover:bg-primary 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={handleGroupSelect}
|
||
getItemType={() => 'blue'}
|
||
minHeight="min-h-[60px]"
|
||
selectable={true}
|
||
forceTheme={false}
|
||
groupDocCount={getGroupDocCount}
|
||
actionButtons={(row) => (
|
||
<div className="flex items-center gap-2">
|
||
<button
|
||
onClick={(e) => {
|
||
e.stopPropagation();
|
||
handleGroupEdit(row);
|
||
}}
|
||
className="p-2 rounded-full hover:bg-gray-100 transition"
|
||
title="Modifier"
|
||
>
|
||
<span title="Editer le dossier">
|
||
<Edit className="w-5 h-5 text-blue-500 hover:text-blue-700" />
|
||
</span>
|
||
</button>
|
||
<button
|
||
onClick={(e) => {
|
||
e.stopPropagation();
|
||
handleGroupDelete(row.id);
|
||
}}
|
||
className="p-2 rounded-full hover:bg-gray-100 transition"
|
||
title="Supprimer"
|
||
>
|
||
<span title="Supprimer le dossier">
|
||
<Trash2 className="w-5 h-5 text-red-500 hover:text-red-700" />
|
||
</span>
|
||
</button>
|
||
</div>
|
||
)}
|
||
/>
|
||
</div>
|
||
|
||
{/* Colonne documents (plein écran mobile/tablette, 2/3 desktop) */}
|
||
<div className="flex flex-col w-full xl:flex-1">
|
||
<div className="flex items-center mb-4">
|
||
<SectionTitle title="Liste des documents" />
|
||
<div className="flex-1" />
|
||
{profileRole !== 0 && (
|
||
<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">
|
||
<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-primary hover:bg-primary 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'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">
|
||
<button
|
||
onClick={(e) => {
|
||
e.stopPropagation();
|
||
if (row._type === 'emerald') {
|
||
editTemplateMaster(row);
|
||
} else {
|
||
openEditParentFileModal(row);
|
||
}
|
||
}}
|
||
className="p-2 rounded-full hover:bg-gray-100 transition"
|
||
title="Modifier"
|
||
>
|
||
<span title="Editer le document">
|
||
<Edit className="w-5 h-5 text-blue-500 hover:text-blue-700" />
|
||
</span>
|
||
</button>
|
||
<button
|
||
onClick={(e) => {
|
||
e.stopPropagation();
|
||
if (row._type === 'emerald') {
|
||
deleteTemplateMaster(row);
|
||
} else {
|
||
handleDelete(row.id);
|
||
}
|
||
}}
|
||
className="p-2 rounded-full hover:bg-gray-100 transition"
|
||
title="Supprimer"
|
||
>
|
||
<span title="Supprimer le document">
|
||
<Trash2 className="w-5 h-5 text-red-500 hover:text-red-700" />
|
||
</span>
|
||
</button>
|
||
</div>
|
||
)}
|
||
/>
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Popup pour création/édition d'un groupe de documents */}
|
||
<Modal
|
||
isOpen={isGroupModalOpen}
|
||
setIsOpen={(isOpen) => {
|
||
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"
|
||
}
|
||
>
|
||
<div className="w-full max-w-md max-h-[90vh] overflow-y-auto">
|
||
<RegistrationFileGroupForm
|
||
onSubmit={(data) => {
|
||
handleGroupSubmit(data);
|
||
setIsGroupModalOpen(false);
|
||
}}
|
||
initialData={groupToEdit}
|
||
/>
|
||
</div>
|
||
</Modal>
|
||
|
||
{/* Popup pour création/édition d'un formulaire d'école déjà existant */}
|
||
<Modal
|
||
isOpen={isFileUploadPopupOpen}
|
||
setIsOpen={setIsFileUploadPopupOpen}
|
||
modalClassName="max-w-md sm:max-w-md sm:min-w-0"
|
||
title={
|
||
fileToEdit && fileToEdit.id
|
||
? 'Modifier le document existant'
|
||
: 'Télécharger un document existant'
|
||
}
|
||
>
|
||
<div className="w-full max-h-[90vh] overflow-y-auto">
|
||
{fileToEdit && fileToEdit.id ? (
|
||
<form
|
||
className="flex flex-col gap-4 w-full"
|
||
onSubmit={(e) => {
|
||
e.preventDefault();
|
||
if (
|
||
!fileToEdit?.name ||
|
||
!fileToEdit?.groups ||
|
||
fileToEdit.groups.length === 0 ||
|
||
!fileToEdit?.file
|
||
)
|
||
return;
|
||
if (isEditing) {
|
||
handleEditSchoolFileMaster({
|
||
id: fileToEdit.id,
|
||
name: fileToEdit.name,
|
||
group_ids: fileToEdit.groups,
|
||
file: fileToEdit.file,
|
||
formMasterData: fileToEdit.formMasterData,
|
||
requires_electronic_signature:
|
||
fileToEdit.requires_electronic_signature || false,
|
||
});
|
||
} else {
|
||
handleCreateSchoolFileMaster({
|
||
name: fileToEdit.name,
|
||
group_ids: fileToEdit.groups,
|
||
file: fileToEdit.file,
|
||
requires_electronic_signature:
|
||
fileToEdit.requires_electronic_signature || false,
|
||
});
|
||
}
|
||
setIsFileUploadPopupOpen(false);
|
||
setFileToEdit(null);
|
||
}}
|
||
>
|
||
<InputText
|
||
label="Nom du document"
|
||
name="name"
|
||
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'inscription{' '}
|
||
<span className="text-red-500">*</span>
|
||
</label>
|
||
<div className="flex flex-wrap gap-4 max-h-32 overflow-y-auto border rounded-md p-2">
|
||
{groups && groups.length > 0 ? (
|
||
groups.map((group) => {
|
||
const selectedGroupIds = (fileToEdit?.groups || []).map(
|
||
(g) =>
|
||
typeof g === 'object' && g !== null && 'id' in g
|
||
? g.id
|
||
: g
|
||
);
|
||
return (
|
||
<CheckBox
|
||
key={group.id}
|
||
item={{ id: group.id }}
|
||
formData={{
|
||
groups: selectedGroupIds,
|
||
}}
|
||
handleChange={() => {
|
||
let group_ids = selectedGroupIds;
|
||
if (group_ids.includes(group.id)) {
|
||
group_ids = group_ids.filter(
|
||
(id) => id !== group.id
|
||
);
|
||
} else {
|
||
group_ids = [...group_ids, group.id];
|
||
}
|
||
setFileToEdit({ ...fileToEdit, groups: group_ids });
|
||
}}
|
||
fieldName="groups"
|
||
itemLabelFunc={() => group.name}
|
||
/>
|
||
);
|
||
})
|
||
) : (
|
||
<p className="text-gray-500 text-sm">
|
||
Aucun groupe disponible
|
||
</p>
|
||
)}
|
||
</div>
|
||
</div>
|
||
{/* Label document sélectionné sans icône œil */}
|
||
{fileToEdit?.file && (
|
||
<div className="flex items-center gap-2 mb-2">
|
||
<FileText className="w-5 h-5 text-gray-600" />
|
||
<span className="text-sm truncate">
|
||
{fileToEdit.file.name ||
|
||
fileToEdit.file.path ||
|
||
'Document sélectionné'}
|
||
</span>
|
||
</div>
|
||
)}
|
||
<FileUpload
|
||
selectionMessage="Sélectionnez le fichier du document"
|
||
onFileSelect={(file) => setFileToEdit({ ...fileToEdit, file })}
|
||
required
|
||
enable
|
||
/>
|
||
<CheckBox
|
||
item={{ id: 'signature' }}
|
||
formData={{
|
||
requires_electronic_signature:
|
||
fileToEdit?.requires_electronic_signature || false,
|
||
}}
|
||
handleChange={() =>
|
||
setFileToEdit({
|
||
...fileToEdit,
|
||
requires_electronic_signature:
|
||
!fileToEdit?.requires_electronic_signature,
|
||
})
|
||
}
|
||
fieldName="requires_electronic_signature"
|
||
itemLabelFunc={() => 'À signer électroniquement'}
|
||
/>
|
||
<Button
|
||
primary
|
||
type="submit"
|
||
text="Enregistrer"
|
||
className="mt-2"
|
||
disabled={
|
||
!fileToEdit?.name ||
|
||
!fileToEdit?.groups ||
|
||
fileToEdit.groups.length === 0 ||
|
||
!fileToEdit?.file
|
||
}
|
||
/>
|
||
</form>
|
||
) : (
|
||
<form
|
||
className="flex flex-col gap-4 w-full"
|
||
onSubmit={(e) => {
|
||
e.preventDefault();
|
||
if (
|
||
!fileToEdit?.name ||
|
||
!fileToEdit?.groups ||
|
||
fileToEdit.groups.length === 0 ||
|
||
!fileToEdit?.file
|
||
)
|
||
return;
|
||
handleCreateSchoolFileMaster({
|
||
name: fileToEdit.name,
|
||
group_ids: fileToEdit.groups,
|
||
file: fileToEdit.file,
|
||
requires_electronic_signature:
|
||
fileToEdit.requires_electronic_signature || false,
|
||
});
|
||
setIsFileUploadPopupOpen(false);
|
||
setFileToEdit(null);
|
||
}}
|
||
>
|
||
<InputText
|
||
label="Nom du document"
|
||
name="name"
|
||
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'inscription{' '}
|
||
<span className="text-red-500">*</span>
|
||
</label>
|
||
<div className="flex flex-wrap gap-4 max-h-32 overflow-y-auto border rounded-md p-2">
|
||
{groups && groups.length > 0 ? (
|
||
groups.map((group) => {
|
||
const selectedGroupIds = (fileToEdit?.groups || []).map(
|
||
(g) =>
|
||
typeof g === 'object' && g !== null && 'id' in g
|
||
? g.id
|
||
: g
|
||
);
|
||
return (
|
||
<CheckBox
|
||
key={group.id}
|
||
item={{ id: group.id }}
|
||
formData={{
|
||
groups: selectedGroupIds,
|
||
}}
|
||
handleChange={() => {
|
||
let group_ids = selectedGroupIds;
|
||
if (group_ids.includes(group.id)) {
|
||
group_ids = group_ids.filter(
|
||
(id) => id !== group.id
|
||
);
|
||
} else {
|
||
group_ids = [...group_ids, group.id];
|
||
}
|
||
setFileToEdit({ ...fileToEdit, groups: group_ids });
|
||
}}
|
||
fieldName="groups"
|
||
itemLabelFunc={() => group.name}
|
||
/>
|
||
);
|
||
})
|
||
) : (
|
||
<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
|
||
/>
|
||
<CheckBox
|
||
item={{ id: 'signature' }}
|
||
formData={{
|
||
requires_electronic_signature:
|
||
fileToEdit?.requires_electronic_signature || false,
|
||
}}
|
||
handleChange={() =>
|
||
setFileToEdit({
|
||
...fileToEdit,
|
||
requires_electronic_signature:
|
||
!fileToEdit?.requires_electronic_signature,
|
||
})
|
||
}
|
||
fieldName="requires_electronic_signature"
|
||
itemLabelFunc={() => 'À signer électroniquement'}
|
||
/>
|
||
<Button
|
||
primary
|
||
type="submit"
|
||
text="Enregistrer"
|
||
className="mt-2"
|
||
disabled={
|
||
!fileToEdit?.name ||
|
||
!fileToEdit?.groups ||
|
||
fileToEdit.groups.length === 0 ||
|
||
!fileToEdit?.file
|
||
}
|
||
/>
|
||
</form>
|
||
)}
|
||
</div>
|
||
</Modal>
|
||
|
||
{/* Popup pour création/édition d'un document parent */}
|
||
<Modal
|
||
isOpen={isParentFileModalOpen}
|
||
setIsOpen={(open) => {
|
||
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'
|
||
}
|
||
>
|
||
<div className="w-full max-w-md max-h-[90vh] overflow-y-auto">
|
||
<form
|
||
className="flex flex-col gap-4"
|
||
onSubmit={(e) => {
|
||
e.preventDefault();
|
||
if (
|
||
!editingParentFile?.name ||
|
||
!editingParentFile?.groups ||
|
||
editingParentFile.groups.length === 0
|
||
)
|
||
return;
|
||
const payload = {
|
||
name: editingParentFile.name,
|
||
description: editingParentFile.description || '',
|
||
groups: editingParentFile.groups,
|
||
is_required: !!editingParentFile.is_required,
|
||
};
|
||
if (editingParentFile?.id) {
|
||
handleEdit(editingParentFile.id, payload);
|
||
} else {
|
||
handleCreate(payload);
|
||
}
|
||
setIsParentFileModalOpen(false);
|
||
setEditingParentFile(null);
|
||
}}
|
||
>
|
||
<InputText
|
||
label="Nom de la pièce à fournir"
|
||
name="name"
|
||
value={editingParentFile?.name || ''}
|
||
onChange={(e) =>
|
||
setEditingParentFile({
|
||
...editingParentFile,
|
||
name: e.target.value,
|
||
})
|
||
}
|
||
required
|
||
/>
|
||
<InputText
|
||
label="Description"
|
||
name="description"
|
||
value={editingParentFile?.description || ''}
|
||
onChange={(e) =>
|
||
setEditingParentFile({
|
||
...editingParentFile,
|
||
description: e.target.value,
|
||
})
|
||
}
|
||
required={false}
|
||
/>
|
||
<div className="space-y-2">
|
||
<label className="block text-sm font-medium text-gray-700">
|
||
Groupes d'inscription{' '}
|
||
<span className="text-red-500">*</span>
|
||
</label>
|
||
<div className="flex flex-wrap gap-4 max-h-32 overflow-y-auto border rounded-md p-2">
|
||
{groups && groups.length > 0 ? (
|
||
groups.map((group) => {
|
||
const selectedGroupIds = (
|
||
editingParentFile?.groups || []
|
||
).map((g) =>
|
||
typeof g === 'object' && g !== null && 'id' in g
|
||
? g.id
|
||
: g
|
||
);
|
||
return (
|
||
<CheckBox
|
||
key={group.id}
|
||
item={{ id: group.id }}
|
||
formData={{
|
||
groups: selectedGroupIds,
|
||
}}
|
||
handleChange={() => {
|
||
let group_ids = selectedGroupIds;
|
||
if (group_ids.includes(group.id)) {
|
||
group_ids = group_ids.filter(
|
||
(id) => id !== group.id
|
||
);
|
||
} else {
|
||
group_ids = [...group_ids, group.id];
|
||
}
|
||
setEditingParentFile({
|
||
...editingParentFile,
|
||
groups: group_ids,
|
||
});
|
||
}}
|
||
fieldName="groups"
|
||
itemLabelFunc={() => group.name}
|
||
/>
|
||
);
|
||
})
|
||
) : (
|
||
<p className="text-gray-500 text-sm">
|
||
Aucun groupe disponible
|
||
</p>
|
||
)}
|
||
</div>
|
||
</div>
|
||
<div className="flex items-center gap-2 mt-2">
|
||
<CheckBox
|
||
item={{ id: 'is_required' }}
|
||
formData={{ is_required: !!editingParentFile?.is_required }}
|
||
handleChange={() =>
|
||
setEditingParentFile({
|
||
...editingParentFile,
|
||
is_required: !editingParentFile?.is_required,
|
||
})
|
||
}
|
||
fieldName="is_required"
|
||
itemLabelFunc={() => 'Requis'}
|
||
/>
|
||
</div>
|
||
<Button
|
||
primary
|
||
type="submit"
|
||
text="Enregistrer"
|
||
className="mt-2"
|
||
disabled={
|
||
!editingParentFile?.name ||
|
||
!editingParentFile?.groups ||
|
||
editingParentFile.groups.length === 0
|
||
}
|
||
/>
|
||
</form>
|
||
</div>
|
||
</Modal>
|
||
|
||
<Popup
|
||
isOpen={removePopupVisible}
|
||
message={removePopupMessage}
|
||
onConfirm={removePopupOnConfirm}
|
||
onCancel={() => setRemovePopupVisible(false)}
|
||
/>
|
||
{isLoading && <Loader />}
|
||
</div>
|
||
);
|
||
}
|