mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-01-28 23:43:22 +00:00
feat: Signatures électroniques docuseal [#22]
This commit is contained in:
@ -1,4 +1,5 @@
|
||||
NEXT_PUBLIC_API_URL=http://localhost:8080
|
||||
NEXT_PUBLIC_USE_FAKE_DATA='false'
|
||||
AUTH_SECRET='false'
|
||||
NEXTAUTH_URL=http://localhost:3000
|
||||
NEXTAUTH_URL=http://localhost:3000
|
||||
DOCUSEAL_API_KEY="LRvUTQCbMSSpManYKshdQk9Do6rBQgjHyPrbGfxU3Jg"
|
||||
6
Front-End/.vscode/settings.json
vendored
6
Front-End/.vscode/settings.json
vendored
@ -1,6 +0,0 @@
|
||||
{
|
||||
"i18n-ally.localesPaths": [
|
||||
"messages"
|
||||
],
|
||||
"i18n-ally.keystyle": "nested"
|
||||
}
|
||||
@ -22,8 +22,8 @@ import { createDatas,
|
||||
fetchRegistrationPaymentModes,
|
||||
fetchTuitionPaymentModes } from '@/app/actions/schoolAction';
|
||||
import SidebarTabs from '@/components/SidebarTabs';
|
||||
import FilesManagement from '@/components/Structure/Files/FilesManagement';
|
||||
import { fetchRegisterFormFileTemplate } from '@/app/actions/subscriptionAction';
|
||||
import FilesGroupsManagement from '@/components/Structure/Files/FilesGroupsManagement';
|
||||
import { fetchRegistrationTemplateMaster } from '@/app/actions/subscriptionAction';
|
||||
import logger from '@/utils/logger';
|
||||
|
||||
|
||||
@ -70,7 +70,7 @@ export default function Page() {
|
||||
handleTuitionFees();
|
||||
|
||||
// Fetch data for registration file templates
|
||||
fetchRegisterFormFileTemplate()
|
||||
fetchRegistrationTemplateMaster()
|
||||
.then((data)=> {
|
||||
setFichiers(data)
|
||||
})
|
||||
@ -301,7 +301,7 @@ export default function Page() {
|
||||
{
|
||||
id: 'Files',
|
||||
label: 'Documents d\'inscription',
|
||||
content: <FilesManagement csrfToken={csrfToken} />
|
||||
content: <FilesGroupsManagement csrfToken={csrfToken} />
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@ -24,7 +24,7 @@ import {
|
||||
createRegisterForm,
|
||||
sendRegisterForm,
|
||||
archiveRegisterForm,
|
||||
fetchRegisterFormFileTemplate,
|
||||
fetchRegistrationTemplateMaster,
|
||||
fetchStudents,
|
||||
editRegisterForm } from "@/app/actions/subscriptionAction"
|
||||
|
||||
@ -195,7 +195,7 @@ useEffect(() => {
|
||||
fetchRegisterForms(ARCHIVED)
|
||||
.then(registerFormArchivedDataHandler)
|
||||
.catch(requestErrorHandler)
|
||||
fetchRegisterFormFileTemplate()
|
||||
fetchRegistrationTemplateMaster()
|
||||
.then((data)=> {
|
||||
logger.debug(data);
|
||||
|
||||
@ -254,7 +254,7 @@ useEffect(() => {
|
||||
fetchRegisterForms(ARCHIVED)
|
||||
.then(registerFormArchivedDataHandler)
|
||||
.catch(requestErrorHandler)
|
||||
fetchRegisterFormFileTemplate()
|
||||
fetchRegistrationTemplateMaster()
|
||||
.then((data)=> {setFichiers(data)})
|
||||
.catch((err)=>{ err = err.message; logger.debug(err);});
|
||||
} else {
|
||||
|
||||
@ -6,12 +6,13 @@ import { useRouter } from 'next/navigation'; // Ajout de l'importation
|
||||
import { User, MessageSquare, LogOut, Settings, Home } from 'lucide-react'; // Ajout de l'importation de l'icône Home
|
||||
import Logo from '@/components/Logo'; // Ajout de l'importation du composant Logo
|
||||
import { FE_PARENTS_HOME_URL,FE_PARENTS_MESSAGERIE_URL,FE_PARENTS_SETTINGS_URL } from '@/utils/Url'; // Ajout de l'importation de l'URL de la page d'accueil parent
|
||||
import useLocalStorage from '@/hooks/useLocalStorage';
|
||||
import { fetchMessages } from '@/app/actions/messagerieAction';
|
||||
import ProtectedRoute from '@/components/ProtectedRoute';
|
||||
import { disconnect } from '@/app/actions/authAction';
|
||||
import Popup from '@/components/Popup';
|
||||
import logger from '@/utils/logger';
|
||||
import { useSession } from 'next-auth/react';
|
||||
import { FE_USERS_LOGIN_URL } from '@/utils/Url';
|
||||
|
||||
export default function Layout({
|
||||
children,
|
||||
@ -19,7 +20,8 @@ export default function Layout({
|
||||
|
||||
const router = useRouter(); // Définition de router
|
||||
const [messages, setMessages] = useState([]);
|
||||
const [userId, setUserId] = useLocalStorage("userId", '') ;
|
||||
const { data: session, status } = useSession();
|
||||
const [userId, setUserId] = useState(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [isPopupVisible, setIsPopupVisible] = useState(false);
|
||||
|
||||
@ -32,27 +34,33 @@ export default function Layout({
|
||||
disconnect();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setIsLoading(true);
|
||||
setUserId(userId)
|
||||
fetchMessages(userId)
|
||||
.then(data => {
|
||||
if (data) {
|
||||
setMessages(data);
|
||||
}
|
||||
logger.debug('Success :', data);
|
||||
})
|
||||
.catch(error => {
|
||||
logger.error('Error fetching data:', error);
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLoading(false);
|
||||
});
|
||||
}, [userId]);
|
||||
// useEffect(() => {
|
||||
// if (status === 'loading') return;
|
||||
// if (!session) {
|
||||
// router.push(`${FE_USERS_LOGIN_URL}`);
|
||||
// }
|
||||
|
||||
if (isLoading) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
// const userIdFromSession = session.user.id;
|
||||
// setUserId(userIdFromSession);
|
||||
// setIsLoading(true);
|
||||
// fetchMessages(userId)
|
||||
// .then(data => {
|
||||
// if (data) {
|
||||
// setMessages(data);
|
||||
// }
|
||||
// logger.debug('Success :', data);
|
||||
// })
|
||||
// .catch(error => {
|
||||
// logger.error('Error fetching data:', error);
|
||||
// })
|
||||
// .finally(() => {
|
||||
// setIsLoading(false);
|
||||
// });
|
||||
// }, [userId]);
|
||||
|
||||
// if (isLoading) {
|
||||
// return <div>Loading...</div>;
|
||||
// }
|
||||
|
||||
return (
|
||||
<ProtectedRoute>
|
||||
|
||||
@ -4,22 +4,31 @@ import { useRouter } from 'next/navigation';
|
||||
import Table from '@/components/Table';
|
||||
import { Edit } from 'lucide-react';
|
||||
import StatusLabel from '@/components/StatusLabel';
|
||||
import useLocalStorage from '@/hooks/useLocalStorage';
|
||||
import { FE_PARENTS_EDIT_INSCRIPTION_URL } from '@/utils/Url';
|
||||
import { fetchChildren } from '@/app/actions/subscriptionAction';
|
||||
import logger from '@/utils/logger';
|
||||
import { useSession } from 'next-auth/react';
|
||||
import { FE_USERS_LOGIN_URL } from '@/utils/Url';
|
||||
|
||||
export default function ParentHomePage() {
|
||||
const [actions, setActions] = useState([]);
|
||||
const [children, setChildren] = useState([]);
|
||||
const [userId, setUserId] = useLocalStorage("userId", '') ;
|
||||
const { data: session, status } = useSession();
|
||||
const [userId, setUserId] = useState(null);
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
if (!userId) return;
|
||||
if (status === 'loading') return;
|
||||
|
||||
fetchChildren(userId).then(data => {
|
||||
if (!session) {
|
||||
router.push(`${FE_USERS_LOGIN_URL}`);
|
||||
}
|
||||
console.log(session);
|
||||
const userIdFromSession = session.user.user_id;
|
||||
setUserId(userIdFromSession);
|
||||
|
||||
fetchChildren(userIdFromSession).then(data => {
|
||||
setChildren(data);
|
||||
});
|
||||
}, [userId]);
|
||||
|
||||
@ -13,10 +13,10 @@ import {
|
||||
FE_PARENTS_HOME_URL
|
||||
} from '@/utils/Url';
|
||||
import { login } from '@/app/actions/authAction';
|
||||
import useLocalStorage from '@/hooks/useLocalStorage';
|
||||
import { getSession } from 'next-auth/react';
|
||||
import { useCsrfToken } from '@/context/CsrfContext'; // Importez le hook useCsrfToken
|
||||
import logger from '@/utils/logger';
|
||||
import { useSession } from 'next-auth/react';
|
||||
|
||||
const useFakeData = process.env.NEXT_PUBLIC_USE_FAKE_DATA === 'true';
|
||||
|
||||
@ -28,8 +28,6 @@ export default function Page() {
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const [userId, setUserId] = useLocalStorage("userId", '') ;
|
||||
|
||||
const router = useRouter();
|
||||
const csrfToken = useCsrfToken(); // Utilisez le hook useCsrfToken
|
||||
|
||||
@ -55,7 +53,6 @@ export default function Page() {
|
||||
}
|
||||
const user = session.user;
|
||||
logger.debug('User Session:', user);
|
||||
localStorage.setItem('userId', user.id); // Stocker l'identifiant de l'utilisateur
|
||||
if (user.droit === 0) {
|
||||
// Vue ECOLE
|
||||
} else if (user.droit === 1) {
|
||||
|
||||
@ -2,9 +2,9 @@ import {
|
||||
BE_SUBSCRIPTION_STUDENTS_URL,
|
||||
BE_SUBSCRIPTION_CHILDRENS_URL,
|
||||
BE_SUBSCRIPTION_REGISTERFORMS_URL,
|
||||
BE_SUBSCRIPTION_REGISTRATIONFORMFILE_TEMPLATE_URL,
|
||||
BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL,
|
||||
BE_SUBSCRIPTION_LAST_GUARDIAN_ID_URL,
|
||||
BE_SUBSCRIPTION_REGISTRATIONFORMFILE_URL
|
||||
BE_SUBSCRIPTION_REGISTRATION_TEMPLATES_URL
|
||||
} from '@/utils/Url';
|
||||
|
||||
export const PENDING = 'pending';
|
||||
@ -101,10 +101,10 @@ export const archiveRegisterForm = (id) => {
|
||||
}).then(requestResponseHandler)
|
||||
}
|
||||
|
||||
export const fetchRegisterFormFile = (id = null) => {
|
||||
let url = `${BE_SUBSCRIPTION_REGISTRATIONFORMFILE_URL}`
|
||||
export const fetchRegistrationTemplates = (id = null) => {
|
||||
let url = `${BE_SUBSCRIPTION_REGISTRATION_TEMPLATES_URL}`
|
||||
if (id) {
|
||||
url = `${BE_SUBSCRIPTION_REGISTRATIONFORMFILE_URL}/${id}`;
|
||||
url = `${BE_SUBSCRIPTION_REGISTRATION_TEMPLATES_URL}/${id}`;
|
||||
}
|
||||
const request = new Request(
|
||||
`${url}`,
|
||||
@ -118,8 +118,8 @@ export const fetchRegisterFormFile = (id = null) => {
|
||||
return fetch(request).then(requestResponseHandler)
|
||||
};
|
||||
|
||||
export const editRegistrationFormFile= (fileId, data, csrfToken) => {
|
||||
return fetch(`${BE_SUBSCRIPTION_REGISTRATIONFORMFILE_URL}/${fileId}`, {
|
||||
export const editRegistrationTemplates= (fileId, data, csrfToken) => {
|
||||
return fetch(`${BE_SUBSCRIPTION_REGISTRATION_TEMPLATES_URL}/${fileId}`, {
|
||||
method: 'PUT',
|
||||
body: data,
|
||||
headers: {
|
||||
@ -130,21 +130,22 @@ export const editRegistrationFormFile= (fileId, data, csrfToken) => {
|
||||
.then(requestResponseHandler)
|
||||
}
|
||||
|
||||
export const createRegistrationFormFile = (data,csrfToken) => {
|
||||
export const createRegistrationTemplates = (data,csrfToken) => {
|
||||
|
||||
return fetch(`${BE_SUBSCRIPTION_REGISTRATIONFORMFILE_URL}`, {
|
||||
return fetch(`${BE_SUBSCRIPTION_REGISTRATION_TEMPLATES_URL}`, {
|
||||
method: 'POST',
|
||||
body: data,
|
||||
body: JSON.stringify(data),
|
||||
headers: {
|
||||
'X-CSRFToken': csrfToken,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
credentials: 'include',
|
||||
})
|
||||
.then(requestResponseHandler)
|
||||
}
|
||||
|
||||
export const deleteRegisterFormFile= (fileId,csrfToken) => {
|
||||
return fetch(`${BE_SUBSCRIPTION_REGISTRATIONFORMFILE_URL}/${fileId}`, {
|
||||
export const deleteRegistrationTemplates= (fileId,csrfToken) => {
|
||||
return fetch(`${BE_SUBSCRIPTION_REGISTRATION_TEMPLATES_URL}/${fileId}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'X-CSRFToken': csrfToken,
|
||||
@ -153,10 +154,10 @@ export const deleteRegisterFormFile= (fileId,csrfToken) => {
|
||||
})
|
||||
}
|
||||
|
||||
export const fetchRegisterFormFileTemplate = (id = null) => {
|
||||
let url = `${BE_SUBSCRIPTION_REGISTRATIONFORMFILE_TEMPLATE_URL}`;
|
||||
export const fetchRegistrationTemplateMaster = (id = null) => {
|
||||
let url = `${BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL}`;
|
||||
if(id){
|
||||
url = `${BE_SUBSCRIPTION_REGISTRATIONFORMFILE_TEMPLATE_URL}/${id}`;
|
||||
url = `${BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL}/${id}`;
|
||||
}
|
||||
const request = new Request(
|
||||
`${url}`,
|
||||
@ -170,21 +171,22 @@ export const fetchRegisterFormFileTemplate = (id = null) => {
|
||||
return fetch(request).then(requestResponseHandler)
|
||||
};
|
||||
|
||||
export const createRegistrationFormFileTemplate = (data,csrfToken) => {
|
||||
export const createRegistrationTemplateMaster = (data,csrfToken) => {
|
||||
|
||||
return fetch(`${BE_SUBSCRIPTION_REGISTRATIONFORMFILE_TEMPLATE_URL}`, {
|
||||
return fetch(`${BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL}`, {
|
||||
method: 'POST',
|
||||
body: data,
|
||||
body: JSON.stringify(data),
|
||||
headers: {
|
||||
'X-CSRFToken': csrfToken,
|
||||
'Content-Type':'application/json'
|
||||
},
|
||||
credentials: 'include',
|
||||
})
|
||||
.then(requestResponseHandler)
|
||||
}
|
||||
|
||||
export const deleteRegisterFormFileTemplate = (fileId,csrfToken) => {
|
||||
return fetch(`${BE_SUBSCRIPTION_REGISTRATIONFORMFILE_TEMPLATE_URL}/${fileId}`, {
|
||||
export const deleteRegistrationTemplateMaster = (fileId,csrfToken) => {
|
||||
return fetch(`${BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL}/${fileId}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'X-CSRFToken': csrfToken,
|
||||
@ -193,8 +195,8 @@ export const deleteRegisterFormFileTemplate = (fileId,csrfToken) => {
|
||||
})
|
||||
}
|
||||
|
||||
export const editRegistrationFormFileTemplate = (fileId, data, csrfToken) => {
|
||||
return fetch(`${BE_SUBSCRIPTION_REGISTRATIONFORMFILE_TEMPLATE_URL}/${fileId}`, {
|
||||
export const editRegistrationTemplateMaster = (fileId, data, csrfToken) => {
|
||||
return fetch(`${BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL}/${fileId}`, {
|
||||
method: 'PUT',
|
||||
body: data,
|
||||
headers: {
|
||||
|
||||
17
Front-End/src/components/DocusealBuilder.js
Normal file
17
Front-End/src/components/DocusealBuilder.js
Normal 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;
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
18
Front-End/src/components/Inscription/FilesToSign.js
Normal file
18
Front-End/src/components/Inscription/FilesToSign.js
Normal 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>
|
||||
);
|
||||
}
|
||||
18
Front-End/src/components/Inscription/FilesToUpload.js
Normal file
18
Front-End/src/components/Inscription/FilesToUpload.js
Normal 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>
|
||||
);
|
||||
}
|
||||
@ -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
|
||||
|
||||
126
Front-End/src/components/Inscription/StudentInfoForm.js
Normal file
126
Front-End/src/components/Inscription/StudentInfoForm.js
Normal 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'é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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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}
|
||||
|
||||
200
Front-End/src/components/Structure/Files/FileUpload.js
Normal file
200
Front-End/src/components/Structure/Files/FileUpload.js
Normal 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>
|
||||
);
|
||||
}
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
@ -1,48 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
const useLocalStorage = (key, initialValue) => {
|
||||
const [storedValue, setStoredValue] = useState(() => {
|
||||
try {
|
||||
if (typeof window !== 'undefined') {
|
||||
const item = window.localStorage.getItem(key);
|
||||
// Vérifier si l'item existe et n'est pas undefined
|
||||
return item !== null && item !== 'undefined'
|
||||
? JSON.parse(item)
|
||||
: initialValue;
|
||||
}
|
||||
return initialValue;
|
||||
} catch (error) {
|
||||
console.error('Error reading from localStorage:', error);
|
||||
return initialValue;
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
// Vérifier si la valeur n'est pas undefined avant de la stocker
|
||||
if (typeof storedValue !== 'undefined') {
|
||||
window.localStorage.setItem(key, JSON.stringify(storedValue));
|
||||
} else {
|
||||
window.localStorage.removeItem(key);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error writing to localStorage:', error);
|
||||
}
|
||||
}, [key, storedValue]);
|
||||
|
||||
const setValue = (value) => {
|
||||
try {
|
||||
// Permettre à la valeur d'être une fonction
|
||||
const valueToStore = value instanceof Function ? value(storedValue) : value;
|
||||
setStoredValue(valueToStore);
|
||||
} catch (error) {
|
||||
console.error('Error updating localStorage value:', error);
|
||||
}
|
||||
};
|
||||
|
||||
return [storedValue, setValue];
|
||||
};
|
||||
|
||||
export default useLocalStorage;
|
||||
36
Front-End/src/pages/api/docuseal/cloneTemplate.js
Normal file
36
Front-End/src/pages/api/docuseal/cloneTemplate.js
Normal file
@ -0,0 +1,36 @@
|
||||
import { BE_DOCUSEAL_CLONE_TEMPLATE } from '@/utils/Url';
|
||||
|
||||
export default function handler(req, res) {
|
||||
if (req.method === 'POST') {
|
||||
const { templateId, email } = req.body;
|
||||
|
||||
fetch(BE_DOCUSEAL_CLONE_TEMPLATE, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Auth-Token': process.env.DOCUSEAL_API_KEY
|
||||
},
|
||||
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 => {
|
||||
console.log('Template cloned successfully:', data);
|
||||
res.status(200).json(data);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error cloning template:', error);
|
||||
res.status(500).json({ error: 'Internal Server Error' });
|
||||
});
|
||||
} else {
|
||||
res.setHeader('Allow', ['POST']);
|
||||
res.status(405).end(`Method ${req.method} Not Allowed`);
|
||||
}
|
||||
}
|
||||
31
Front-End/src/pages/api/docuseal/generateToken.js
Normal file
31
Front-End/src/pages/api/docuseal/generateToken.js
Normal file
@ -0,0 +1,31 @@
|
||||
import { BE_DOCUSEAL_GET_JWT } from '@/utils/Url';
|
||||
|
||||
export default function handler(req, res) {
|
||||
if (req.method === 'POST') {
|
||||
console.log('DOCUSEAL_API_KEY:', process.env.DOCUSEAL_API_KEY);
|
||||
|
||||
fetch(BE_DOCUSEAL_GET_JWT, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Auth-Token': process.env.DOCUSEAL_API_KEY
|
||||
},
|
||||
body: JSON.stringify(req.body),
|
||||
})
|
||||
.then(response => {
|
||||
console.log('Response status:', response.status);
|
||||
return response.json().then(data => ({ status: response.status, data }));
|
||||
})
|
||||
.then(({ status, data }) => {
|
||||
console.log('Response data:', data);
|
||||
res.status(status).json(data);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
res.status(500).json({ error: 'Internal Server Error' });
|
||||
});
|
||||
} else {
|
||||
res.setHeader('Allow', ['POST']);
|
||||
res.status(405).end(`Method ${req.method} Not Allowed`);
|
||||
}
|
||||
}
|
||||
@ -4,6 +4,9 @@ export const BASE_URL = process.env.NEXT_PUBLIC_API_URL;
|
||||
|
||||
//URL-Back-End
|
||||
|
||||
// GESTION DocuSeal
|
||||
export const BE_DOCUSEAL_GET_JWT = `${BASE_URL}/DocuSeal/generateToken`
|
||||
export const BE_DOCUSEAL_CLONE_TEMPLATE = `${BASE_URL}/DocuSeal/cloneTemplate`
|
||||
|
||||
// GESTION LOGIN
|
||||
export const BE_AUTH_NEW_PASSWORD_URL = `${BASE_URL}/Auth/newPassword`
|
||||
@ -20,8 +23,8 @@ export const BE_AUTH_INFO_SESSION = `${BASE_URL}/Auth/infoSession`
|
||||
export const BE_SUBSCRIPTION_STUDENTS_URL = `${BASE_URL}/Subscriptions/students` // Récupère la liste des élèves inscrits ou en cours d'inscriptions
|
||||
export const BE_SUBSCRIPTION_CHILDRENS_URL = `${BASE_URL}/Subscriptions/children` // Récupère la liste des élèves d'un profil
|
||||
export const BE_SUBSCRIPTION_REGISTERFORMS_URL = `${BASE_URL}/Subscriptions/registerForms`
|
||||
export const BE_SUBSCRIPTION_REGISTRATIONFORMFILE_TEMPLATE_URL = `${BASE_URL}/Subscriptions/registrationFileTemplates`
|
||||
export const BE_SUBSCRIPTION_REGISTRATIONFORMFILE_URL = `${BASE_URL}/Subscriptions/registrationFiles`
|
||||
export const BE_SUBSCRIPTION_REGISTRATION_TEMPLATE_MASTER_URL = `${BASE_URL}/Subscriptions/registrationTemplateMasters`
|
||||
export const BE_SUBSCRIPTION_REGISTRATION_TEMPLATES_URL = `${BASE_URL}/Subscriptions/registrationTemplates`
|
||||
export const BE_SUBSCRIPTION_REGISTRATIONFILE_GROUPS_URL = `${BASE_URL}/Subscriptions/registrationFileGroups`
|
||||
export const BE_SUBSCRIPTION_LAST_GUARDIAN_ID_URL = `${BASE_URL}/Subscriptions/lastGuardianId`
|
||||
|
||||
|
||||
Reference in New Issue
Block a user