From d1a0067f7b7125e453ff6fc75efead881a7af37d Mon Sep 17 00:00:00 2001 From: N3WT DE COMPET Date: Sat, 1 Mar 2025 17:50:54 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20Cr=C3=A9ation=20de=20clones=20lors=20de?= =?UTF-8?q?=20la=20cr=C3=A9ation=20de=20RF=20[#22]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/[locale]/admin/subscriptions/page.js | 129 ++++++++++++++---- Front-End/src/app/actions/authAction.js | 14 ++ .../app/actions/registerFileGroupAction.js | 17 ++- .../components/Inscription/InscriptionForm.js | 13 +- .../components/Structure/Files/FileUpload.js | 30 +--- .../Structure/Files/FilesGroupsManagement.js | 1 - Front-End/src/utils/Url.js | 5 +- 7 files changed, 145 insertions(+), 64 deletions(-) diff --git a/Front-End/src/app/[locale]/admin/subscriptions/page.js b/Front-End/src/app/[locale]/admin/subscriptions/page.js index e5d610c..796b3e0 100644 --- a/Front-End/src/app/[locale]/admin/subscriptions/page.js +++ b/Front-End/src/app/[locale]/admin/subscriptions/page.js @@ -28,7 +28,10 @@ import { editRegisterForm } from "@/app/actions/subscriptionAction" import { - fetchRegistrationTemplateMaster + fetchRegistrationTemplateMaster, + createRegistrationTemplates, + fetchRegistrationFileGroups, + cloneTemplate } from "@/app/actions/registerFileGroupAction"; import { @@ -38,7 +41,7 @@ import { fetchRegistrationFees, fetchTuitionFees } from '@/app/actions/schoolAction'; -import { createProfile } from '@/app/actions/authAction'; +import { createProfile, deleteProfile } from '@/app/actions/authAction'; import { BASE_URL, @@ -46,7 +49,6 @@ import { import DjangoCSRFToken from '@/components/DjangoCSRFToken' import { useCsrfToken } from '@/context/CsrfContext'; -import { fetchRegistrationFileGroups } from '@/app/actions/registerFileGroupAction'; import { ESTABLISHMENT_ID } from '@/utils/Url'; import logger from '@/utils/logger'; @@ -71,7 +73,7 @@ export default function Page({ params: { locale } }) { const [totalArchives, setTotalArchives] = useState(0); const [itemsPerPage, setItemsPerPage] = useState(5); // Définir le nombre d'éléments par page - const [fichiers, setFichiers] = useState([]); + const [templateMasters, setTemplateMasters] = useState([]); const [isOpen, setIsOpen] = useState(false); const [isOpenAffectationClasse, setIsOpenAffectationClasse] = useState(false); const [student, setStudent] = useState(''); @@ -202,7 +204,7 @@ useEffect(() => { .then((data)=> { logger.debug(data); - setFichiers(data) + setTemplateMasters(data) }) .catch((err)=>{ err = err.message; logger.debug(err);}) fetchRegistrationDiscounts() @@ -258,7 +260,7 @@ useEffect(() => { .then(registerFormArchivedDataHandler) .catch(requestErrorHandler) fetchRegistrationTemplateMaster() - .then((data)=> {setFichiers(data)}) + .then((data)=> {setTemplateMasters(data)}) .catch((err)=>{ err = err.message; logger.debug(err);}); } else { setTimeout(() => { @@ -365,6 +367,8 @@ useEffect(()=>{ if (updatedData.selectedGuardians.length !== 0) { const selectedGuardiansIds = updatedData.selectedGuardians.map(guardianId => guardianId) + const guardianEmail = updatedData.guardianEmail + const data = { student: { last_name: updatedData.studentLastName, @@ -379,15 +383,48 @@ useEffect(()=>{ createRegisterForm(data, csrfToken) .then(data => { - // Mise à jour immédiate des données - setRegistrationFormsDataPending(prevState => [...(prevState || []), data]); - setTotalPending(prev => prev + 1); - if (updatedData.autoMail) { - sendConfirmRegisterForm(data.student.id, updatedData.studentLastName, updatedData.studentFirstName); - } - closeModal(); - // Forcer le rechargement complet des données - setReloadFetch(true); + // Cloner les templates pour chaque templateMaster du fileGroup + const masters = templateMasters.filter(file => file.groups.includes(selectedFileGroup)); + const clonePromises = masters.map((templateMaster, index) => { + return cloneTemplate(templateMaster.template_id, guardianEmail) + .then(clonedDocument => { + // Sauvegarde des templates clonés dans la base de données + const cloneData = { + name: `clone_${clonedDocument.id}`, + template_id: clonedDocument.id, + master: templateMaster.template_id, + registration_form: data.student.id + }; + + return createRegistrationTemplates(cloneData, csrfToken) + .then(response => { + logger.debug('Template enregistré avec succès:', response); + }) + .catch(error => { + logger.error('Erreur lors de l\'enregistrement du template:', error); + }); + }) + .catch(error => { + logger.error('Error during cloning or sending:', error); + }); + }); + + // Attendre que tous les clones soient créés + Promise.all(clonePromises) + .then(() => { + // Mise à jour immédiate des données + setRegistrationFormsDataPending(prevState => [...(prevState || []), data]); + setTotalPending(prev => prev + 1); + if (updatedData.autoMail) { + sendConfirmRegisterForm(data.student.id, updatedData.studentLastName, updatedData.studentFirstName); + } + closeModal(); + // Forcer le rechargement complet des données + setReloadFetch(true); + }) + .catch(error => { + logger.error('Error during cloning or sending:', error); + }); }) .catch((error) => { logger.error('Error:', error); @@ -423,20 +460,60 @@ useEffect(()=>{ createRegisterForm(data, csrfToken) .then(data => { - // Mise à jour immédiate des données - setRegistrationFormsDataPending(prevState => [...(prevState || []), data]); - setTotalPending(prev => prev + 1); - if (updatedData.autoMail) { - sendConfirmRegisterForm(data.student.id, updatedData.studentLastName, updatedData.studentFirstName); - } - closeModal(); - logger.debug('Success:', data); - // Forcer le rechargement complet des données - setReloadFetch(true); + // Cloner les templates pour chaque templateMaster du fileGroup + const masters = templateMasters.filter(file => file.groups.includes(selectedFileGroup)); + const clonePromises = masters.map((templateMaster, index) => { + return cloneTemplate(templateMaster.template_id, updatedData.guardianEmail) + .then(clonedDocument => { + // Sauvegarde des templates clonés dans la base de données + const cloneData = { + name: `clone_${clonedDocument.id}`, + template_id: clonedDocument.id, + master: templateMaster.template_id, + registration_form: data.student.id + }; + + return createRegistrationTemplates(cloneData, csrfToken) + .then(response => { + logger.debug('Template enregistré avec succès:', response); + }) + .catch(error => { + logger.error('Erreur lors de l\'enregistrement du template:', error); + }); + }) + .catch(error => { + logger.error('Error during cloning or sending:', error); + }); + }); + + // Attendre que tous les clones soient créés + Promise.all(clonePromises) + .then(() => { + // Mise à jour immédiate des données + setRegistrationFormsDataPending(prevState => [...(prevState || []), data]); + setTotalPending(prev => prev + 1); + + if (updatedData.autoMail) { + sendConfirmRegisterForm(data.student.id, updatedData.studentLastName, updatedData.studentFirstName); + } + closeModal(); + logger.debug('Success:', data); + // Forcer le rechargement complet des données + setReloadFetch(true); + }) + .catch(error => { + logger.error('Error during cloning or sending:', error); + }); }) .catch((error) => { logger.error('Error:', error); - //deleteProfile(response.id, csrfToken); + deleteProfile(response.id, csrfToken) + .then(() => { + logger.debug('Profile deleted due to RF creation failure'); + }) + .catch(deleteError => { + logger.error('Error deleting profile:', deleteError); + }); }); } }) diff --git a/Front-End/src/app/actions/authAction.js b/Front-End/src/app/actions/authAction.js index 1c7cd91..c406336 100644 --- a/Front-End/src/app/actions/authAction.js +++ b/Front-End/src/app/actions/authAction.js @@ -89,6 +89,20 @@ export const createProfile = (data, csrfToken) => { return fetch(request).then(requestResponseHandler); }; +export const deleteProfile = (id, csrfToken) => { + const request = new Request( + `${BE_AUTH_PROFILES_URL}/${id}`, + { + method: 'DELETE', + headers: { + 'X-CSRFToken': csrfToken + }, + credentials: 'include' + } + ); + return fetch(request).then(requestResponseHandler); +}; + export const updateProfile = (id, data, csrfToken) => { const request = new Request( `${BE_AUTH_PROFILES_URL}/${id}`, diff --git a/Front-End/src/app/actions/registerFileGroupAction.js b/Front-End/src/app/actions/registerFileGroupAction.js index 5a2a056..6de0050 100644 --- a/Front-End/src/app/actions/registerFileGroupAction.js +++ b/Front-End/src/app/actions/registerFileGroupAction.js @@ -1,6 +1,7 @@ import { BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL, BE_SUBSCRIPTION_REGISTRATION_TEMPLATES_URL, - BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL + BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL, + FE_API_DOCUSEAL_CLONE_URL } from '@/utils/Url'; const requestResponseHandler = async (response) => { @@ -193,4 +194,18 @@ export const editRegistrationTemplateMaster = (fileId, data, csrfToken) => { credentials: 'include', }) .then(requestResponseHandler) +} + +export const cloneTemplate = (templateId, email) => { + return fetch(`${FE_API_DOCUSEAL_CLONE_URL}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + templateId, + email + }) + }) + .then(requestResponseHandler) } \ No newline at end of file diff --git a/Front-End/src/components/Inscription/InscriptionForm.js b/Front-End/src/components/Inscription/InscriptionForm.js index a9a703b..92899f1 100644 --- a/Front-End/src/components/Inscription/InscriptionForm.js +++ b/Front-End/src/components/Inscription/InscriptionForm.js @@ -119,12 +119,13 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r setExistingGuardians(student.guardians); }; - const handleResponsableSelection = (guardianId) => { + const handleResponsableSelection = (guardianId, guardianEmail) => { setFormData((prevData) => { - const selectedGuardians = prevData.selectedGuardians.includes(guardianId) - ? prevData.selectedGuardians.filter(id => id !== guardianId) - : [...prevData.selectedGuardians, guardianId]; - return { ...prevData, selectedGuardians }; + const isSelected = prevData.selectedGuardians.includes(guardianId); + const selectedGuardians = isSelected + ? prevData.selectedGuardians.filter(id => id !== guardianId) + : [...prevData.selectedGuardians, guardianId]; + return { ...prevData, selectedGuardians, guardianEmail }; }); }; @@ -345,7 +346,7 @@ const InscriptionForm = ( { students, registrationDiscounts, tuitionDiscounts, r type="checkbox" checked={formData.selectedGuardians.includes(guardian.id)} className="form-checkbox h-5 w-5 text-emerald-600" - onChange={() => handleResponsableSelection(guardian.id)} + onChange={() => handleResponsableSelection(guardian.id, guardian.email)} /> {guardian.last_name && guardian.first_name ? `${guardian.last_name} ${guardian.first_name}` : `${guardian.email}`} diff --git a/Front-End/src/components/Structure/Files/FileUpload.js b/Front-End/src/components/Structure/Files/FileUpload.js index 542213d..edff3d6 100644 --- a/Front-End/src/components/Structure/Files/FileUpload.js +++ b/Front-End/src/components/Structure/Files/FileUpload.js @@ -1,12 +1,11 @@ import React, { useState, useEffect } from 'react'; import ToggleSwitch from '@/components/ToggleSwitch'; // Import du composant ToggleSwitch -import { fetchRegistrationFileGroups } from '@/app/actions/registerFileGroupAction'; +import { fetchRegistrationFileGroups, createRegistrationTemplates, cloneTemplate } from '@/app/actions/registerFileGroupAction'; import { DocusealBuilder } from '@docuseal/react'; 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/registerFileGroupAction'; import { useCsrfToken } from '@/context/CsrfContext'; export default function FileUpload({ handleCreateTemplateMaster, handleEditTemplateMaster, fileToEdit = null, onSuccess }) { @@ -138,33 +137,6 @@ export default function FileUpload({ handleCreateTemplateMaster, handleEditTempl }; - 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 (
diff --git a/Front-End/src/components/Structure/Files/FilesGroupsManagement.js b/Front-End/src/components/Structure/Files/FilesGroupsManagement.js index 10ccf74..7977445 100644 --- a/Front-End/src/components/Structure/Files/FilesGroupsManagement.js +++ b/Front-End/src/components/Structure/Files/FilesGroupsManagement.js @@ -66,7 +66,6 @@ export default function FilesGroupsManagement({ csrfToken }) { const deleteTemplateMaster = (templateMaster) => { // Supprimer les clones associés via l'API DocuSeal - console.log(templates) const removeClonesPromises = templates .filter(template => template.master === templateMaster.template_id) .map(template => removeTemplate(template.template_id)); diff --git a/Front-End/src/utils/Url.js b/Front-End/src/utils/Url.js index b8cb343..e79f7dc 100644 --- a/Front-End/src/utils/Url.js +++ b/Front-End/src/utils/Url.js @@ -86,4 +86,7 @@ export const FE_ADMIN_SETTINGS_URL = `/admin/settings` export const FE_PARENTS_HOME_URL = `/parents` export const FE_PARENTS_MESSAGERIE_URL = `/parents/messagerie` export const FE_PARENTS_SETTINGS_URL = `/parents/settings` -export const FE_PARENTS_EDIT_INSCRIPTION_URL = `/parents/editInscription` \ No newline at end of file +export const FE_PARENTS_EDIT_INSCRIPTION_URL = `/parents/editInscription` + +// API DOCUSEAL +export const FE_API_DOCUSEAL_CLONE_URL = `/api/docuseal/cloneTemplate` \ No newline at end of file