feat: Signatures électroniques docuseal [#22]

This commit is contained in:
N3WT DE COMPET
2025-02-28 18:30:18 +01:00
parent 8897d523dc
commit c8c8941ec8
41 changed files with 984 additions and 549 deletions

View File

@ -0,0 +1,17 @@
import React, { useEffect } from 'react';
import { DocusealBuilder as OriginalDocusealBuilder } from '@docuseal/react';
const DocusealBuilder = ({ onSave, onSend, ...props }) => {
useEffect(() => {
if (onSave) {
props.save = onSave;
}
if (onSend) {
props.send = onSend;
}
}, [onSave, onSend, props]);
return <OriginalDocusealBuilder {...props} />;
};
export default DocusealBuilder;

View File

@ -1,50 +0,0 @@
import React, { useState } from 'react';
import { Upload } from 'lucide-react';
export default function DraggableFileUpload({ fileName, onFileSelect }) {
const [dragActive, setDragActive] = useState(false);
const handleDragOver = (event) => {
event.preventDefault();
setDragActive(true);
};
const handleDragLeave = () => {
setDragActive(false);
};
const handleFileChosen = (selectedFile) => {
onFileSelect && onFileSelect(selectedFile);
};
const handleDrop = (event) => {
event.preventDefault();
setDragActive(false);
const droppedFile = event.dataTransfer.files[0];
handleFileChosen(droppedFile);
};
const handleFileChange = (event) => {
const selectedFile = event.target.files[0];
handleFileChosen(selectedFile);
};
return (
<div>
<div
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
className={`border-2 border-dashed p-8 rounded-md ${dragActive ? 'border-blue-500' : 'border-gray-300'} flex flex-col items-center justify-center`}
style={{ height: '200px' }}
>
<input type="file" onChange={handleFileChange} className="hidden" id="fileInput" />
<label htmlFor="fileInput" className="cursor-pointer flex flex-col items-center">
<Upload size={48} className="text-gray-400 mb-2" />
<p className="text-center">{fileName || 'Glissez et déposez un fichier ici ou cliquez ici pour sélectionner un fichier'}</p>
</label>
</div>
</div>
);
}

View File

@ -0,0 +1,18 @@
import React from 'react';
import Table from '@/components/Table';
export default function FilesToSign({ fileTemplates, columns }) {
return (
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
<h2 className="text-xl font-bold mb-4 text-gray-800">Fichiers à remplir</h2>
<Table
data={fileTemplates}
columns={columns}
itemsPerPage={5}
currentPage={1}
totalPages={1}
onPageChange={() => {}}
/>
</div>
);
}

View File

@ -0,0 +1,18 @@
import React from 'react';
import Table from '@/components/Table';
export default function FilesToUpload({ fileTemplates, columns }) {
return (
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
<h2 className="text-xl font-bold mb-4 text-gray-800">Fichiers à uploader</h2>
<Table
data={fileTemplates}
columns={columns}
itemsPerPage={5}
currentPage={1}
totalPages={1}
onPageChange={() => {}}
/>
</div>
);
}

View File

@ -7,7 +7,7 @@ import Loader from '@/components/Loader';
import Button from '@/components/Button';
import DjangoCSRFToken from '@/components/DjangoCSRFToken';
import Table from '@/components/Table';
import { fetchRegisterFormFileTemplate, createRegistrationFormFile, fetchRegisterForm, deleteRegisterFormFile } from '@/app/actions/subscriptionAction';
import { fetchRegistrationTemplateMaster, createRegistrationTemplates, fetchRegisterForm, deleteRegistrationTemplates } from '@/app/actions/subscriptionAction';
import { fetchRegistrationFileFromGroup } from '@/app/actions/registerFileGroupAction';
import { Download, Upload, Trash2, Eye } from 'lucide-react';
import { BASE_URL } from '@/utils/Url';
@ -117,7 +117,7 @@ export default function InscriptionFormShared({
data.append('register_form', formData.id);
try {
const response = await createRegistrationFormFile(data, csrfToken);
const response = await createRegistrationTemplates(data, csrfToken);
if (response) {
setUploadedFiles(prev => {
const newFiles = prev.filter(f => parseInt(f.template) !== currentTemplateId);
@ -158,7 +158,7 @@ export default function InscriptionFormShared({
if (!fileToDelete) return;
try {
await deleteRegisterFormFile(fileToDelete.id, csrfToken);
await deleteRegistrationTemplates(fileToDelete.id, csrfToken);
setUploadedFiles(prev => prev.filter(f => parseInt(f.template) !== templateId));
} catch (error) {
logger.error('Error deleting file:', error);
@ -276,7 +276,7 @@ export default function InscriptionFormShared({
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
<h2 className="text-xl font-bold mb-4 text-gray-800">{requiredFileTemplates[currentPage - 2].name}</h2>
<iframe
src={`${BASE_URL}/data/${requiredFileTemplates[currentPage - 2].file}?signature=true`}
src={`${BASE_URL}/data/${requiredFileTemplates[currentPage - 2].file}`}
width="100%"
height="800px"
className="w-full" // Utiliser la classe CSS pour la largeur

View File

@ -0,0 +1,126 @@
import React from 'react';
import InputText from '@/components/InputText';
import SelectChoice from '@/components/SelectChoice';
import ResponsableInputFields from '@/components/Inscription/ResponsableInputFields';
const levels = [
{ value:'1', label: 'TPS - Très Petite Section'},
{ value:'2', label: 'PS - Petite Section'},
{ value:'3', label: 'MS - Moyenne Section'},
{ value:'4', label: 'GS - Grande Section'},
];
export default function StudentInfoForm({ formData, updateFormField, guardians, setGuardians, errors }) {
const getError = (field) => {
return errors?.student?.[field]?.[0];
};
return (
<>
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
<h2 className="text-xl font-bold mb-4 text-gray-800">Informations de l&apos;élève</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<InputText
name="last_name"
label="Nom"
value={formData.last_name}
onChange={(e) => updateFormField('last_name', e.target.value)}
required
errorMsg={getError('last_name')}
/>
<InputText
name="first_name"
label="Prénom"
value={formData.first_name}
onChange={(e) => updateFormField('first_name', e.target.value)}
errorMsg={getError('first_name')}
required
/>
<InputText
name="nationality"
label="Nationalité"
value={formData.nationality}
required
onChange={(e) => updateFormField('nationality', e.target.value)}
/>
<InputText
name="birth_date"
type="date"
label="Date de Naissance"
value={formData.birth_date}
onChange={(e) => updateFormField('birth_date', e.target.value)}
required
errorMsg={getError('birth_date')}
/>
<InputText
name="birth_place"
label="Lieu de Naissance"
value={formData.birth_place}
onChange={(e) => updateFormField('birth_place', e.target.value)}
required
errorMsg={getError('birth_place')}
/>
<InputText
name="birth_postal_code"
label="Code Postal de Naissance"
value={formData.birth_postal_code}
onChange={(e) => updateFormField('birth_postal_code', e.target.value)}
required
errorMsg={getError('birth_postal_code')}
/>
<div className="md:col-span-2">
<InputText
name="address"
label="Adresse"
value={formData.address}
onChange={(e) => updateFormField('address', e.target.value)}
required
errorMsg={getError('address')}
/>
</div>
<InputText
name="attending_physician"
label="Médecin Traitant"
value={formData.attending_physician}
onChange={(e) => updateFormField('attending_physician', e.target.value)}
required
errorMsg={getError('attending_physician')}
/>
<SelectChoice
name="level"
label="Niveau"
placeHolder="Sélectionner un niveau"
selected={formData.level}
callback={(e) => updateFormField('level', e.target.value)}
choices={levels}
required
errorMsg={getError('level')}
/>
</div>
</div>
<div className="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
<h2 className="text-xl font-bold mb-4 text-gray-800">Responsables</h2>
<ResponsableInputFields
guardians={guardians}
onGuardiansChange={(id, field, value) => {
const updatedGuardians = guardians.map(resp =>
resp.id === id ? { ...resp, [field]: value } : resp
);
setGuardians(updatedGuardians);
}}
addGuardian={(e) => {
e.preventDefault();
setGuardians([...guardians, { id: Date.now() }]);
}}
deleteGuardian={(index) => {
const newArray = [...guardians];
newArray.splice(index, 1);
setGuardians(newArray);
}}
errors={errors?.student?.guardians || []}
/>
</div>
</>
);
}

View File

@ -1,12 +1,12 @@
import * as Dialog from '@radix-ui/react-dialog';
const Modal = ({ isOpen, setIsOpen, title, ContentComponent }) => {
const Modal = ({ isOpen, setIsOpen, title, ContentComponent, modalClassName }) => {
return (
<Dialog.Root open={isOpen} onOpenChange={setIsOpen}>
<Dialog.Portal>
<Dialog.Overlay className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
<Dialog.Content className="fixed inset-0 flex items-center justify-center p-4">
<div className="inline-block bg-white rounded-lg px-6 py-5 text-left shadow-xl transform transition-all sm:my-8 min-w-[500px] w-max m-12 h-max">
<div className={`inline-block bg-white rounded-lg px-6 py-5 text-left shadow-xl transform transition-all sm:my-8 ${modalClassName ? modalClassName : 'min-w-[500px] m-12 w-max max-h-[80vh] h-full'}`}>
<div className="flex justify-between items-start mb-4">
<Dialog.Title className="text-xl font-medium text-gray-900">
{title}
@ -23,7 +23,7 @@ const Modal = ({ isOpen, setIsOpen, title, ContentComponent }) => {
</button>
</Dialog.Close>
</div>
<div className="w-full">
<div className="w-full h-full">
<ContentComponent />
</div>
</div>

View File

@ -31,13 +31,10 @@ const MultiSelect = ({ name, label, options, selectedOptions, onChange, errorMsg
return (
<div ref={containerRef}>
<label htmlFor={name} className="block text-sm font-medium text-gray-700">
{label}
</label>
<div className="relative mt-1">
<button
type="button"
className="w-full bg-white border border-gray-300 rounded-md shadow-sm pl-3 pr-10 py-2 text-left cursor-pointer focus:outline-none sm:text-sm"
className="w-full bg-white border border-gray-300 rounded-md shadow-sm pl-3 pr-10 py-2 text-left cursor-pointer focus:outline-none sm:text-sm hover:border-emerald-500 focus:border-emerald-500"
onClick={() => setIsOpen(!isOpen)}
>
{selectedOptions.length > 0 ? (
@ -49,7 +46,7 @@ const MultiSelect = ({ name, label, options, selectedOptions, onChange, errorMsg
))}
</div>
) : (
'Sélectionnez les niveaux'
<span>{label}</span>
)}
<ChevronDown className="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none h-full w-5" />
</button>

View File

@ -291,6 +291,7 @@ const ClassesSection = ({ classes, setClasses, teachers, handleCreate, handleEdi
return (
<MultiSelect
name="levels"
label="Sélection de niveaux"
options={allNiveaux}
selectedOptions={currentData.levels ? currentData.levels.map(levelId => allNiveaux.find(level => level.id === levelId)) : []}
onChange={handleMultiSelectChange}

View File

@ -0,0 +1,200 @@
import React, { useState, useEffect } from 'react';
import ToggleSwitch from '@/components/ToggleSwitch'; // Import du composant ToggleSwitch
import { fetchRegistrationFileGroups } from '@/app/actions/registerFileGroupAction';
import DocusealBuilder from '@/components/DocusealBuilder'; // Import du composant wrapper
import logger from '@/utils/logger';
import { BE_DOCUSEAL_GET_JWT, BASE_URL } from '@/utils/Url';
import Button from '@/components/Button'; // Import du composant Button
import MultiSelect from '@/components/MultiSelect'; // Import du composant MultiSelect
import { createRegistrationTemplates } from '@/app/actions/subscriptionAction'; // Import de la fonction createRegistrationTemplates
import { useCsrfToken } from '@/context/CsrfContext';
export default function FileUpload({ handleCreateTemplateMaster, fileToEdit = null }) {
const [isRequired, setIsRequired] = useState(false); // État pour le toggle isRequired
const [order, setOrder] = useState(0);
const [groups, setGroups] = useState([]);
const [token, setToken] = useState(null);
const [templateMaster, setTemplateMaster] = useState(null);
const [uploadedFileName, setUploadedFileName] = useState('');
const [selectedGroups, setSelectedGroups] = useState([]);
const [guardianEmails, setGuardianEmails] = useState([]);
const [registrationFormIds, setRegistrationFormIds] = useState([]);
const csrfToken = useCsrfToken();
useEffect(() => {
fetchRegistrationFileGroups().then(data => setGroups(data));
if (fileToEdit) {
setUploadedFileName(fileToEdit.name || '');
setSelectedGroups(fileToEdit.groups || []);
}
}, [fileToEdit]);
useEffect(() => {
const body = fileToEdit
? JSON.stringify({
user_email: 'n3wt.school@gmail.com',
template_id: fileToEdit.template_id
})
: JSON.stringify({
user_email: 'n3wt.school@gmail.com'
});
fetch('/api/docuseal/generateToken', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: body,
})
.then((response) => response.json())
.then((data) => {
setToken(data.token);
})
.catch((error) => console.error(error));
}, [fileToEdit]);
const handleFileNameChange = (event) => {
setUploadedFileName(event.target.value);
};
const handleGroupChange = (selectedGroups) => {
setSelectedGroups(selectedGroups);
const emails = selectedGroups.flatMap(group => group.registration_forms.flatMap(form => form.guardians.map(guardian => guardian.email)));
setGuardianEmails(emails); // Mettre à jour la variable d'état avec les emails des guardians
const registrationFormIds = selectedGroups.flatMap(group => group.registration_forms.map(form => form.student_id));
setRegistrationFormIds(registrationFormIds); // Mettre à jour la variable d'état avec les IDs des dossiers d'inscription
logger.debug('Emails des Guardians associés aux groupes sélectionnés:', emails);
logger.debug('IDs des dossiers d\'inscription associés aux groupes sélectionnés:', registrationFormIds);
};
const handleLoad = (detail) => {
const templateId = detail?.id;
setTemplateMaster(detail);
logger.debug('Master template created with ID:', templateId);
}
const handleUpload = (detail) => {
logger.debug('Uploaded file detail:', detail);
setUploadedFileName(detail.name);
};
const handleSubmit = () => {
logger.debug('Création du template master:', templateMaster?.id);
handleCreateTemplateMaster({
name: uploadedFileName,
group_ids: selectedGroups.map(group => group.id),
template_id: templateMaster?.id
});
guardianEmails.forEach((email, index) => {
cloneTemplate(templateMaster?.id, email)
.then(clonedDocument => {
// Sauvegarde des templates clonés dans la base de données
const data = {
name: `clone_${clonedDocument.id}`,
template_id: clonedDocument.id,
master: templateMaster?.id,
registration_form: registrationFormIds[index]
};
createRegistrationTemplates(data, csrfToken)
.then(response => {
logger.debug('Template enregistré avec succès:', response);
})
.catch(error => {
logger.error('Erreur lors de l\'enregistrement du template:', error);
});
// Logique pour envoyer chaque template au submitter
logger.debug('Sending template to:', email);
})
.catch(error => {
logger.error('Error during cloning or sending:', error);
});
});
};
const cloneTemplate = (templateId, email) => {
return fetch('/api/docuseal/cloneTemplate', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
templateId,
email
})
})
.then(response => {
if (!response.ok) {
return response.json().then(err => { throw new Error(err.message); });
}
return response.json();
})
.then(data => {
logger.debug('Template cloned successfully:', data);
return data;
})
.catch(error => {
console.error('Error cloning template:', error);
throw error;
});
};
return (
<div className="h-full flex flex-col mt-4 space-y-4">
<div className="grid grid-cols-10 gap-4 items-start">
<div className="col-span-2">
<MultiSelect
name="groups"
label="Sélection de groupes de fichiers"
options={groups}
selectedOptions={selectedGroups}
onChange={handleGroupChange}
errorMsg={null}
/>
</div>
<div className="col-span-7">
{token && (
<DocusealBuilder
token={token}
headers={{
'Authorization': `Bearer ${token}`
}}
withSendButton={false}
withSignYourselfButton={false}
autosave={true}
language={'fr'}
onLoad={handleLoad}
onUpload={handleUpload}
className="h-full overflow-auto" // Ajouter overflow-auto pour permettre le défilement
style={{ maxHeight: '70vh' }} // Limiter la hauteur maximale du composant
// Il faut auter l'host correspondant (une fois passé en HTTPS)
//host="docuseal:3001"
/>
)}
</div>
<div className="col-span-1 flex justify-end">
<Button
text="Valider"
onClick={handleSubmit}
className={`px-4 py-2 rounded-md shadow-sm focus:outline-none ${
(uploadedFileName === '' || selectedGroups.length === 0)
? "bg-gray-300 text-gray-700 cursor-not-allowed"
: "bg-emerald-500 text-white hover:bg-emerald-600"
}`}
primary
disabled={uploadedFileName === '' || selectedGroups.length === 0}
/>
</div>
</div>
</div>
);
}

View File

@ -2,14 +2,15 @@ import React, { useState, useEffect } from 'react';
import { Plus, Download, Edit, Trash2, FolderPlus, Signature } from 'lucide-react';
import Modal from '@/components/Modal';
import Table from '@/components/Table';
import FileUpload from '@/components/FileUpload';
import FileUpload from '@/components/Structure/Files/FileUpload';
import { formatDate } from '@/utils/Date';
import { BASE_URL } from '@/utils/Url';
import {
fetchRegisterFormFileTemplate,
createRegistrationFormFileTemplate,
editRegistrationFormFileTemplate,
deleteRegisterFormFileTemplate
fetchRegistrationTemplateMaster,
createRegistrationTemplateMaster,
editRegistrationTemplateMaster,
deleteRegistrationTemplateMaster,
getRegisterFormFileTemplate
} from '@/app/actions/subscriptionAction';
import {
fetchRegistrationFileGroups,
@ -17,10 +18,9 @@ import {
deleteRegistrationFileGroup,
editRegistrationFileGroup
} from '@/app/actions/registerFileGroupAction';
import RegistrationFileGroupForm from '@/components/RegistrationFileGroupForm';
import logger from '@/utils/logger';
import RegistrationFileGroupForm from '@/components/Structure/Files/RegistrationFileGroupForm';
export default function FilesManagement({ csrfToken }) {
export default function FilesGroupsManagement({ csrfToken }) {
const [fichiers, setFichiers] = useState([]);
const [groups, setGroups] = useState([]);
const [selectedGroup, setSelectedGroup] = useState(null);
@ -29,23 +29,18 @@ export default function FilesManagement({ csrfToken }) {
const [fileToEdit, setFileToEdit] = useState(null);
const [isGroupModalOpen, setIsGroupModalOpen] = useState(false);
const [groupToEdit, setGroupToEdit] = useState(null);
const [token, setToken] = useState(null);
const [selectedFile, setSelectedFile] = useState(null);
// Fonction pour transformer les données des fichiers avec les informations complètes du groupe
const transformFileData = (file, groups) => {
if (!file.group) return file;
const groupInfo = groups.find(g => g.id === file.group);
const groupInfos = file.groups.map(groupId => groups.find(g => g.id === groupId) || { id: groupId, name: 'Groupe inconnu' });
return {
...file,
group: groupInfo || { id: file.group, name: 'Groupe inconnu' }
groups: groupInfos
};
};
useEffect(() => {
Promise.all([
fetchRegisterFormFileTemplate(),
fetchRegistrationTemplateMaster(),
fetchRegistrationFileGroups()
]).then(([filesData, groupsData]) => {
setGroups(groupsData);
@ -57,12 +52,12 @@ export default function FilesManagement({ csrfToken }) {
const transformedFiles = filesData.map(file => transformFileData(file, groupsData));
setFichiers(transformedFiles);
}).catch(err => {
logger.debug(err.message);
console.log(err.message);
});
}, []);
const handleFileDelete = (fileId) => {
deleteRegisterFormFileTemplate(fileId, csrfToken)
deleteRegistrationTemplateMaster(fileId, csrfToken)
.then(response => {
if (response.ok) {
setFichiers(fichiers.filter(fichier => fichier.id !== fileId));
@ -72,7 +67,7 @@ export default function FilesManagement({ csrfToken }) {
}
})
.catch(error => {
logger.error('Error deleting file:', error);
console.error('Error deleting file:', error);
alert('Erreur lors de la suppression du fichier.');
});
};
@ -83,7 +78,45 @@ export default function FilesManagement({ csrfToken }) {
setIsModalOpen(true);
};
const handleFileUpload = ({file, name, is_required, order, groupId}) => {
const handleCreateTemplateMaster = ({name, group_ids, template_id}) => {
const data = {
name: name,
template_id: template_id,
groups: group_ids
};
console.log(data);
if (isEditing && fileToEdit) {
editRegistrationTemplateMaster(fileToEdit.id, data, csrfToken)
.then(data => {
// Transformer le fichier mis à jour avec les informations du groupe
const transformedFile = transformFileData(data, groups);
setFichiers(prevFichiers =>
prevFichiers.map(f => f.id === fileToEdit.id ? transformedFile : f)
);
setIsModalOpen(false);
setFileToEdit(null);
setIsEditing(false);
})
.catch(error => {
console.error('Error editing file:', error);
alert('Erreur lors de la modification du fichier');
});
} else {
createRegistrationTemplateMaster(data, csrfToken)
.then(data => {
// Transformer le nouveau fichier avec les informations du groupe
const transformedFile = transformFileData(data, groups);
setFichiers(prevFiles => [...prevFiles, transformedFile]);
setIsModalOpen(false);
})
.catch(error => {
console.error('Error uploading file:', error);
});
}
};
const handleTemplate = ({name, is_required, order, groupId, document_id}) => {
if (!name) {
alert('Veuillez entrer un nom de fichier.');
return;
@ -96,14 +129,16 @@ export default function FilesManagement({ csrfToken }) {
formData.append('name', name);
formData.append('is_required', is_required);
formData.append('order', order);
formData.append('document_id', document_id);
// Modification ici : vérifier si groupId existe et n'est pas vide
if (groupId && groupId !== '') {
formData.append('group', groupId); // Notez que le nom du champ est 'group' et non 'group_id'
}
if (isEditing && fileToEdit) {
editRegistrationFormFileTemplate(fileToEdit.id, formData, csrfToken)
editRegistrationTemplateMaster(fileToEdit.id, formData, csrfToken)
.then(data => {
// Transformer le fichier mis à jour avec les informations du groupe
const transformedFile = transformFileData(data, groups);
@ -115,11 +150,11 @@ export default function FilesManagement({ csrfToken }) {
setIsEditing(false);
})
.catch(error => {
logger.error('Error editing file:', error);
console.error('Error editing file:', error);
alert('Erreur lors de la modification du fichier');
});
} else {
createRegistrationFormFileTemplate(formData, csrfToken)
createRegistrationTemplateMaster(formData, csrfToken)
.then(data => {
// Transformer le nouveau fichier avec les informations du groupe
const transformedFile = transformFileData(data, groups);
@ -127,25 +162,33 @@ export default function FilesManagement({ csrfToken }) {
setIsModalOpen(false);
})
.catch(error => {
logger.error('Error uploading file:', error);
console.error('Error uploading file:', error);
});
}
};
const handleGroupSubmit = async (groupData) => {
try {
if (groupToEdit) {
const updatedGroup = await editRegistrationFileGroup(groupToEdit.id, groupData, csrfToken);
setGroups(groups.map(group => group.id === groupToEdit.id ? updatedGroup : group));
setGroupToEdit(null);
} else {
const newGroup = await createRegistrationFileGroup(groupData, csrfToken);
setGroups([...groups, newGroup]);
}
setIsGroupModalOpen(false);
} catch (error) {
logger.error('Error handling group:', error);
alert('Erreur lors de l\'opération sur le groupe');
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 => {
console.error('Error handling group:', error);
alert('Erreur lors de l\'opération sur le groupe');
});
} else {
createRegistrationFileGroup(groupData, csrfToken)
.then(newGroup => {
setGroups([...groups, newGroup]);
setIsGroupModalOpen(false);
})
.catch(error => {
console.error('Error handling group:', error);
alert('Erreur lors de l\'opération sur le groupe');
});
}
};
@ -175,24 +218,20 @@ export default function FilesManagement({ csrfToken }) {
alert('Groupe supprimé avec succès.');
})
.catch(error => {
logger.error('Error deleting group:', error);
console.error('Error deleting group:', error);
alert(error.message || 'Erreur lors de la suppression du groupe. Vérifiez qu\'aucune inscription n\'utilise ce groupe.');
});
}
};
// Ajouter cette fonction de filtrage
const filteredFiles = fichiers.filter(file => {
if (!selectedGroup) return true;
return file.group && file.group.id === parseInt(selectedGroup);
return file.groups && file.groups.some(group => group.id === parseInt(selectedGroup));
});
const columnsFiles = [
{ name: 'Nom du fichier', transform: (row) => row.name },
{ name: 'Groupe', transform: (row) => row.group ? row.group.name : 'Aucun' },
{ name: 'Date de création', transform: (row) => formatDate(new Date (row.date_added),"DD/MM/YYYY hh:mm:ss") },
{ name: 'Fichier Obligatoire', transform: (row) => row.is_required ? 'Oui' : 'Non' },
{ name: 'Ordre de fusion', transform: (row) => row.order },
{ name: 'Groupes', transform: (row) => row.groups && row.groups.length > 0 ? row.groups.map(group => group.name).join(', ') : 'Aucun' },
{ name: 'Actions', transform: (row) => (
<div className="flex items-center justify-center gap-2">
{row.file && (
@ -206,9 +245,6 @@ export default function FilesManagement({ csrfToken }) {
<button onClick={() => handleFileDelete(row.id)} className="text-red-500 hover:text-red-700">
<Trash2 size={16} />
</button>
<button onClick={() => handleSignatureRequest(row)} className="text-green-500 hover:text-green-700">
<Signature size={16} />
</button>
</div>
)}
];
@ -228,40 +264,24 @@ export default function FilesManagement({ csrfToken }) {
)}
];
// Fonction pour gérer la demande de signature
const handleSignatureRequest = (file) => {
fetch('http://localhost:8080/DocuSeal/generateToken', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
document_id: file.id,
user_email: 'anthony.casini.30@gmail.com',
url: file.file
}),
})
.then((response) => response.json())
.then((data) => {
console.log("token received : ", data.token);
setToken(data.token);
setSelectedFile(file);
})
.catch((error) => console.error(error));
};
return (
<div>
<Modal
isOpen={isModalOpen}
setIsOpen={setIsModalOpen}
title={isEditing ? 'Modifier un fichier' : 'Ajouter un fichier'}
setIsOpen={(isOpen) => {
setIsModalOpen(isOpen);
if (!isOpen) {
setFileToEdit(null);
}
}}
title={isEditing ? 'Modification du document' : 'Ajouter un document'}
ContentComponent={() => (
<FileUpload
onFileUpload={handleFileUpload}
handleCreateTemplateMaster={handleCreateTemplateMaster}
fileToEdit={fileToEdit}
/>
)}
modalClassName='w-4/5 h-4/5'
/>
<Modal
isOpen={isGroupModalOpen}
@ -324,14 +344,6 @@ export default function FilesManagement({ csrfToken }) {
/>
</div>
)}
{token && selectedFile && (
<DocusealBuilder
token={token}
headers={{
'Authorization': `Bearer Rh2CC75ZMZqirmtBGA5NRjUzj8hr9eDYTBeZxv3jgzb`
}}
/>
)}
</div>
);
}