Merge remote-tracking branch 'origin/WIP_Inscriptions' into develop

This commit is contained in:
Luc SORIGNET
2025-04-25 10:55:54 +02:00
39 changed files with 2400 additions and 1610 deletions

View File

@ -1,13 +1,4 @@
import {
Trash2,
Edit3,
Plus,
ZoomIn,
Users,
Check,
X,
Hand,
} from 'lucide-react';
import { Trash2, Edit3, ZoomIn, Users, Check, X, Hand } from 'lucide-react';
import React, { useState, useEffect } from 'react';
import Table from '@/components/Table';
import Popup from '@/components/Popup';
@ -21,6 +12,7 @@ import { HTML5Backend } from 'react-dnd-html5-backend';
import { ESTABLISHMENT_ID } from '@/utils/Url';
import logger from '@/utils/logger';
import ClasseDetails from '@/components/ClasseDetails';
import SectionHeader from '@/components/SectionHeader';
const ItemTypes = {
TEACHER: 'teacher',
@ -553,19 +545,13 @@ const ClassesSection = ({
return (
<DndProvider backend={HTML5Backend}>
<div className="space-y-4">
<div className="flex justify-between items-center">
<div className="flex items-center mb-4">
<Users className="w-6 h-6 text-emerald-500 mr-2" />
<h2 className="text-xl font-semibold">Classes</h2>
</div>
<button
type="button"
onClick={handleAddClass}
className="text-emerald-500 hover:text-emerald-700"
>
<Plus className="w-5 h-5" />
</button>
</div>
<SectionHeader
icon={Users}
title="Liste des classes"
description="Gérez les classes de votre école"
button={true}
onClick={handleAddClass}
/>
<Table
data={newClass ? [newClass, ...classes] : classes}
columns={columns}

View File

@ -1,4 +1,4 @@
import { Plus, Trash2, Edit3, Check, X, BookOpen } from 'lucide-react';
import { Trash2, Edit3, Check, X, BookOpen } from 'lucide-react';
import { useState } from 'react';
import Table from '@/components/Table';
import Popup from '@/components/Popup';
@ -6,7 +6,9 @@ import InputTextWithColorIcon from '@/components/InputTextWithColorIcon';
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import SpecialityItem from '@/components/Structure/Configuration/SpecialityItem';
import { useEstablishment } from '@/context/EstablishmentContext';
import logger from '@/utils/logger';
import SectionHeader from '@/components/SectionHeader';
const SpecialitiesSection = ({
specialities,
@ -26,6 +28,8 @@ const SpecialitiesSection = ({
const [removePopupMessage, setRemovePopupMessage] = useState('');
const [removePopupOnConfirm, setRemovePopupOnConfirm] = useState(() => {});
const { selectedEstablishmentId } = useEstablishment();
// Récupération des messages d'erreur
const getError = (field) => {
return localErrors?.[field]?.[0];
@ -49,7 +53,13 @@ const SpecialitiesSection = ({
const handleSaveNewSpeciality = () => {
if (newSpeciality.name) {
handleCreate(newSpeciality)
// Ajouter l'ID de l'établissement à la nouvelle spécialité
const specialityData = {
...newSpeciality,
establishment: selectedEstablishmentId, // Inclure l'ID de l'établissement
};
handleCreate(specialityData)
.then((createdSpeciality) => {
setSpecialities([createdSpeciality, ...specialities]);
setNewSpeciality(null);
@ -234,19 +244,13 @@ const SpecialitiesSection = ({
return (
<DndProvider backend={HTML5Backend}>
<div className="space-y-4">
<div className="flex justify-between items-center">
<div className="flex items-center mb-4">
<BookOpen className="w-6 h-6 text-emerald-500 mr-2" />
<h2 className="text-xl font-semibold">Spécialités</h2>
</div>
<button
type="button"
onClick={handleAddSpeciality}
className="text-emerald-500 hover:text-emerald-700"
>
<Plus className="w-5 h-5" />
</button>
</div>
<SectionHeader
icon={BookOpen}
title="Liste des spécialités"
description="Gérez les spécialités de votre école"
button={true}
onClick={handleAddSpeciality}
/>
<Table
data={newSpeciality ? [newSpeciality, ...specialities] : specialities}
columns={columns}

View File

@ -22,9 +22,9 @@ const StructureManagement = ({
handleDelete,
}) => {
return (
<div className="max-w-8xl mx-auto p-4 mt-6 space-y-8">
<div className="w-full mx-auto mt-6">
<ClassesProvider>
<div className="w-2/5 p-4 bg-white rounded-lg shadow-md">
<div className="mt-8 w-2/5">
<SpecialitiesSection
specialities={specialities}
setSpecialities={setSpecialities}
@ -48,7 +48,7 @@ const StructureManagement = ({
}
/>
</div>
<div className="w-4/5 p-4 bg-white rounded-lg shadow-md">
<div className="w-4/5 mt-12">
<TeachersSection
teachers={teachers}
setTeachers={setTeachers}
@ -70,7 +70,7 @@ const StructureManagement = ({
}
/>
</div>
<div className="w-full p-4 bg-white rounded-lg shadow-md">
<div className="w-full mt-12">
<ClassesSection
classes={classes}
setClasses={setClasses}

View File

@ -1,18 +1,8 @@
import React, { useState, useEffect } from 'react';
import {
Plus,
Edit3,
Trash2,
GraduationCap,
Check,
X,
Hand,
Search,
} from 'lucide-react';
import { Edit3, Trash2, GraduationCap, Check, X, Hand } from 'lucide-react';
import Table from '@/components/Table';
import Popup from '@/components/Popup';
import ToggleSwitch from '@/components/ToggleSwitch';
import { createProfile, updateProfile } from '@/app/actions/authAction';
import { useCsrfToken } from '@/context/CsrfContext';
import { DndProvider, useDrag, useDrop } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
@ -20,8 +10,8 @@ import InputText from '@/components/InputText';
import SpecialityItem from '@/components/Structure/Configuration/SpecialityItem';
import TeacherItem from './TeacherItem';
import logger from '@/utils/logger';
import { fetchProfiles } from '@/app/actions/authAction';
import { useEstablishment } from '@/context/EstablishmentContext';
import SectionHeader from '@/components/SectionHeader';
const ItemTypes = {
SPECIALITY: 'speciality',
@ -578,19 +568,13 @@ const TeachersSection = ({
return (
<DndProvider backend={HTML5Backend}>
<div className="space-y-4">
<div className="flex justify-between items-center">
<div className="flex items-center mb-4">
<GraduationCap className="w-6 h-6 text-emerald-500 mr-2" />
<h2 className="text-xl font-semibold">Enseignants</h2>
</div>
<button
type="button"
onClick={handleAddTeacher}
className="text-emerald-500 hover:text-emerald-700"
>
<Plus className="w-5 h-5" />
</button>
</div>
<SectionHeader
icon={GraduationCap}
title="Liste des enseignants.es"
description="Gérez les enseignants.es de votre école"
button={true}
onClick={handleAddTeacher}
/>
<Table
data={newTeacher ? [newTeacher, ...teachers] : teachers}
columns={columns}

View File

@ -1,31 +1,22 @@
import React, { useState, useEffect } from 'react';
import ToggleSwitch from '@/components/ToggleSwitch'; // Import du composant ToggleSwitch
import {
fetchRegistrationFileGroups,
createRegistrationTemplates,
createRegistrationSchoolFileTemplate,
cloneTemplate,
generateToken,
} from '@/app/actions/registerFileGroupAction';
import { DocusealBuilder } from '@docuseal/react';
import logger from '@/utils/logger';
import {
BE_DOCUSEAL_GET_JWT,
BASE_URL,
FE_API_DOCUSEAL_GENERATE_TOKEN,
} from '@/utils/Url';
import Button from '@/components/Button'; // Import du composant Button
import MultiSelect from '@/components/MultiSelect'; // Import du composant MultiSelect
import { useCsrfToken } from '@/context/CsrfContext';
import { useEstablishment } from '@/context/EstablishmentContext';
export default function FileUpload({
export default function FileUploadDocuSeal({
handleCreateTemplateMaster,
handleEditTemplateMaster,
fileToEdit = null,
onSuccess,
}) {
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);
@ -61,10 +52,6 @@ export default function FileUpload({
);
}, [fileToEdit]);
const handleFileNameChange = (event) => {
setUploadedFileName(event.target.value);
};
const handleGroupChange = (selectedGroups) => {
setSelectedGroups(selectedGroups);
@ -120,7 +107,7 @@ export default function FileUpload({
logger.debug('creation du clone avec required : ', is_required);
cloneTemplate(templateMaster?.id, guardian.email, is_required)
.then((clonedDocument) => {
// Sauvegarde des templates clonés dans la base de données
// Sauvegarde des schoolFileTemplates clonés dans la base de données
const data = {
name: `${uploadedFileName}_${guardian.first_name}_${guardian.last_name}`,
slug: clonedDocument.slug,
@ -128,7 +115,7 @@ export default function FileUpload({
master: templateMaster?.id,
registration_form: guardian.registration_form,
};
createRegistrationTemplates(data, csrfToken)
createRegistrationSchoolFileTemplate(data, csrfToken)
.then((response) => {
logger.debug('Template enregistré avec succès:', response);
onSuccess();

View File

@ -1,36 +1,40 @@
import React, { useState, useEffect } from 'react';
import {
Plus,
Download,
Edit3,
Trash2,
FolderPlus,
Signature,
} from 'lucide-react';
import { Download, Edit3, Trash2, FolderPlus, Signature } from 'lucide-react';
import Modal from '@/components/Modal';
import Table from '@/components/Table';
import FileUpload from '@/components/Structure/Files/FileUpload';
import FileUploadDocuSeal from '@/components/Structure/Files/FileUploadDocuSeal';
import { BASE_URL } from '@/utils/Url';
import {
// GET
fetchRegistrationFileGroups,
fetchRegistrationSchoolFileMasters,
fetchRegistrationSchoolFileTemplates,
fetchRegistrationParentFileMasters,
// POST
createRegistrationFileGroup,
deleteRegistrationFileGroup,
createRegistrationSchoolFileMaster,
createRegistrationParentFileMaster,
// PUT
editRegistrationFileGroup,
fetchRegistrationTemplateMaster,
createRegistrationTemplateMaster,
editRegistrationTemplateMaster,
deleteRegistrationTemplateMaster,
fetchRegistrationTemplates,
editRegistrationSchoolFileMaster,
editRegistrationParentFileMaster,
// DELETE
deleteRegistrationFileGroup,
deleteRegistrationSchoolFileMaster,
deleteRegistrationParentFileMaster,
} from '@/app/actions/registerFileGroupAction';
import RegistrationFileGroupForm from '@/components/Structure/Files/RegistrationFileGroupForm';
import logger from '@/utils/logger';
import ParentFilesSection from '@/components/Structure/Files/ParentFilesSection';
import SectionHeader from '@/components/SectionHeader';
export default function FilesGroupsManagement({
csrfToken,
selectedEstablishmentId,
}) {
const [templateMasters, setTemplateMasters] = useState([]);
const [templates, setTemplates] = useState([]);
const [schoolFileMasters, setSchoolFileMasters] = useState([]);
const [schoolFileTemplates, setSchoolFileTemplates] = useState([]);
const [parentFiles, setParentFileMasters] = useState([]);
const [groups, setGroups] = useState([]);
const [selectedGroup, setSelectedGroup] = useState(null);
const [isModalOpen, setIsModalOpen] = useState(false);
@ -39,6 +43,10 @@ export default function FilesGroupsManagement({
const [isGroupModalOpen, setIsGroupModalOpen] = useState(false);
const [groupToEdit, setGroupToEdit] = useState(null);
const [reloadTemplates, setReloadTemplates] = useState(false);
const [editingDocumentId, setEditingDocumentId] = useState(null);
const [formData, setFormData] = useState({});
const [uploadedFileName, setUploadedFileName] = useState('');
const handleReloadTemplates = () => {
setReloadTemplates(true);
@ -61,19 +69,28 @@ export default function FilesGroupsManagement({
useEffect(() => {
if (selectedEstablishmentId) {
Promise.all([
fetchRegistrationTemplateMaster(),
fetchRegistrationSchoolFileMasters(),
fetchRegistrationFileGroups(selectedEstablishmentId),
fetchRegistrationTemplates(),
fetchRegistrationSchoolFileTemplates(),
fetchRegistrationParentFileMasters(),
])
.then(([filesTemplateMasters, groupsData, filesTemplates]) => {
setGroups(groupsData);
setTemplates(filesTemplates);
// Transformer chaque fichier pour inclure les informations complètes du groupe
const transformedFiles = filesTemplateMasters.map((file) =>
transformFileData(file, groupsData)
);
setTemplateMasters(transformedFiles);
})
.then(
([
dataSchoolFileMasters,
groupsData,
dataSchoolFileTemplates,
dataParentFileMasters,
]) => {
setGroups(groupsData);
setSchoolFileTemplates(dataSchoolFileTemplates);
setParentFileMasters(dataParentFileMasters);
// Transformer chaque fichier pour inclure les informations complètes du groupe
const transformedFiles = dataSchoolFileMasters.map((file) =>
transformFileData(file, groupsData)
);
setSchoolFileMasters(transformedFiles);
}
)
.catch((err) => {
console.log(err.message);
})
@ -85,7 +102,7 @@ export default function FilesGroupsManagement({
const deleteTemplateMaster = (templateMaster) => {
// Supprimer les clones associés via l'API DocuSeal
const removeClonesPromises = templates
const removeClonesPromises = schoolFileTemplates
.filter((template) => template.master === templateMaster.id)
.map((template) => removeTemplate(template.id));
@ -100,11 +117,11 @@ export default function FilesGroupsManagement({
logger.debug('Master et clones supprimés avec succès de DocuSeal.');
// Supprimer le template master de la base de données
deleteRegistrationTemplateMaster(templateMaster.id, csrfToken)
deleteRegistrationSchoolFileMaster(templateMaster.id, csrfToken)
.then((response) => {
if (response.ok) {
setTemplateMasters(
templateMasters.filter(
setSchoolFileMasters(
schoolFileMasters.filter(
(fichier) => fichier.id !== templateMaster.id
)
);
@ -175,11 +192,11 @@ export default function FilesGroupsManagement({
};
logger.debug(data);
createRegistrationTemplateMaster(data, csrfToken)
createRegistrationSchoolFileMaster(data, csrfToken)
.then((data) => {
// Transformer le nouveau fichier avec les informations du groupe
const transformedFile = transformFileData(data, groups);
setTemplateMasters((prevFiles) => [...prevFiles, transformedFile]);
setSchoolFileMasters((prevFiles) => [...prevFiles, transformedFile]);
setIsModalOpen(false);
})
.catch((error) => {
@ -196,11 +213,11 @@ export default function FilesGroupsManagement({
};
logger.debug(data);
editRegistrationTemplateMaster(id, data, csrfToken)
editRegistrationSchoolFileMaster(id, data, csrfToken)
.then((data) => {
// Transformer le fichier mis à jour avec les informations du groupe
const transformedFile = transformFileData(data, groups);
setTemplateMasters((prevFichiers) =>
setSchoolFileMasters((prevFichiers) =>
prevFichiers.map((f) => (f.id === id ? transformedFile : f))
);
setIsModalOpen(false);
@ -228,7 +245,13 @@ export default function FilesGroupsManagement({
alert("Erreur lors de l'opération sur le groupe");
});
} else {
createRegistrationFileGroup(groupData, csrfToken)
// Ajouter l'établissement sélectionné lors de la création d'un nouveau groupe
const newGroupData = {
...groupData,
establishment: selectedEstablishmentId,
};
createRegistrationFileGroup(newGroupData, csrfToken)
.then((newGroup) => {
setGroups([...groups, newGroup]);
setIsGroupModalOpen(false);
@ -246,13 +269,13 @@ export default function FilesGroupsManagement({
};
const handleGroupDelete = (groupId) => {
// Vérifier si des templateMasters utilisent ce groupe
const filesInGroup = templateMasters.filter(
// Vérifier si des schoolFileMasters utilisent ce groupe
const filesInGroup = schoolFileMasters.filter(
(file) => file.group && file.group.id === groupId
);
if (filesInGroup.length > 0) {
alert(
"Impossible de supprimer ce groupe car il contient des templateMasters. Veuillez d'abord retirer tous les templateMasters de ce groupe."
"Impossible de supprimer ce groupe car il contient des schoolFileMasters. Veuillez d'abord retirer tous les schoolFileMasters de ce groupe."
);
return;
}
@ -279,7 +302,62 @@ export default function FilesGroupsManagement({
}
};
const filteredFiles = templateMasters.filter((file) => {
const handleCreate = (newParentFile) => {
return createRegistrationParentFileMaster(newParentFile, csrfToken)
.then((response) => {
const createdFile = response;
// Ajouter le nouveau fichier parent à la liste existante
setParentFileMasters((prevFiles) => [...prevFiles, createdFile]);
logger.debug('Document parent créé avec succès:', createdFile);
return createdFile;
})
.catch((error) => {
logger.error('Erreur lors de la création du document parent:', error);
alert(
'Une erreur est survenue lors de la création du document parent.'
);
throw error;
});
};
const handleEdit = (id, updatedFile) => {
return editRegistrationParentFileMaster(id, updatedFile, csrfToken)
.then((response) => {
const modifiedFile = response.data; // Extraire les données mises à jour
// Mettre à jour la liste des fichiers parents
setParentFileMasters((prevFiles) =>
prevFiles.map((file) => (file.id === id ? modifiedFile : file))
);
logger.debug('Document parent mis à jour avec succès:', modifiedFile);
return modifiedFile; // Retourner le fichier mis à jour
})
.catch((error) => {
logger.error(
'Erreur lors de la modification du document parent:',
error
);
alert(
'Une erreur est survenue lors de la modification du document parent.'
);
throw error;
});
};
const handleDelete = (id) => {
return deleteRegistrationParentFileMaster(id, csrfToken)
.then(() => {
// Mettre à jour la liste des fichiers parents en supprimant l'élément correspondant
setParentFileMasters((prevFiles) =>
prevFiles.filter((file) => file.id !== id)
);
logger.debug('Document parent supprimé avec succès:', id);
})
.catch((error) => {
logger.error('Erreur lors de la suppression du fichier parent:', error);
});
};
const filteredFiles = schoolFileMasters.filter((file) => {
if (!selectedGroup) return true;
return (
file.groups &&
@ -288,9 +366,9 @@ export default function FilesGroupsManagement({
});
const columnsFiles = [
{ name: 'Nom du fichier', transform: (row) => row.name },
{ name: 'Nom du formulaire', transform: (row) => row.name },
{
name: 'Groupes',
name: "Dossiers d'inscription",
transform: (row) =>
row.groups && row.groups.length > 0
? row.groups.map((group) => group.name).join(', ')
@ -327,7 +405,7 @@ export default function FilesGroupsManagement({
];
const columnsGroups = [
{ name: 'Nom du groupe', transform: (row) => row.name },
{ name: 'Nom du dossier', transform: (row) => row.name },
{ name: 'Description', transform: (row) => row.description },
{
name: 'Actions',
@ -351,7 +429,8 @@ export default function FilesGroupsManagement({
];
return (
<div>
<div className="w-full mx-auto mt-6">
{/* Modal pour les fichiers */}
<Modal
isOpen={isModalOpen}
setIsOpen={(isOpen) => {
@ -362,7 +441,7 @@ export default function FilesGroupsManagement({
}}
title={isEditing ? 'Modification du document' : 'Ajouter un document'}
ContentComponent={() => (
<FileUpload
<FileUploadDocuSeal
handleCreateTemplateMaster={handleCreateTemplateMaster}
handleEditTemplateMaster={handleEditTemplateMaster}
fileToEdit={fileToEdit}
@ -371,13 +450,15 @@ export default function FilesGroupsManagement({
)}
modalClassName="w-4/5 h-4/5"
/>
{/* Modal pour les groupes */}
<Modal
isOpen={isGroupModalOpen}
setIsOpen={setIsGroupModalOpen}
title={
groupToEdit
? 'Modifier le groupe'
: 'Ajouter un groupe de templateMasters'
: 'Ajouter un groupe de schoolFileMasters'
}
ContentComponent={() => (
<RegistrationFileGroupForm
@ -386,61 +467,45 @@ export default function FilesGroupsManagement({
/>
)}
/>
<div className="mt-8 mb-4">
<div className="flex justify-between items-center mb-4">
<h2 className="text-xl font-bold">Groupes de fichiers</h2>
<button
onClick={() => setIsGroupModalOpen(true)}
className="flex items-center bg-blue-600 text-white p-2 rounded-full shadow hover:bg-blue-900 transition duration-200"
>
<FolderPlus className="w-5 h-5" />
</button>
</div>
<Table
data={groups}
columns={columnsGroups}
itemsPerPage={5}
currentPage={1}
totalPages={Math.ceil(groups.length / 5)}
{/* Section Groupes de fichiers */}
<div className="mt-8 w-3/5">
<SectionHeader
icon={FolderPlus}
title="Dossiers d'inscriptions"
description="Gérez les dossiers d'inscription pour organiser vos documents."
button={true}
buttonOpeningModal={true}
onClick={() => setIsGroupModalOpen(true)}
/>
<Table data={groups} columns={columnsGroups} />
</div>
{groups.length > 0 && (
<div className="mt-8">
<div className="flex justify-between items-center mb-4">
<h2 className="text-xl font-bold">Fichiers</h2>
<div className="flex items-center gap-4">
<select
className="border rounded p-2"
value={selectedGroup || ''}
onChange={(e) => setSelectedGroup(e.target.value)}
>
<option value="">Tous les groupes</option>
{groups.map((group) => (
<option key={group.id} value={group.id}>
{group.name}
</option>
))}
</select>
<button
onClick={() => {
setIsModalOpen(true);
setIsEditing(false);
}}
className="flex items-center bg-emerald-600 text-white p-2 rounded-full shadow hover:bg-emerald-900 transition duration-200"
>
<Plus className="w-5 h-5" />
</button>
</div>
</div>
<Table
data={filteredFiles}
columns={columnsFiles}
itemsPerPage={10}
currentPage={1}
totalPages={Math.ceil(filteredFiles.length / 10)}
/>
</div>
)}
{/* Section Fichiers */}
<div className="mt-12 mb-4 w-3/5">
<SectionHeader
icon={Signature}
title="Formulaires à remplir"
description="Gérez les formulaires nécessitant une signature électronique."
button={true}
buttonOpeningModal={true}
onClick={() => {
setIsModalOpen(true);
setIsEditing(false);
}}
/>
<Table data={filteredFiles} columns={columnsFiles} />
</div>
{/* Section Pièces à fournir */}
<ParentFilesSection
parentFiles={parentFiles}
setParentFileMasters={setParentFileMasters}
groups={groups}
handleCreate={handleCreate}
handleEdit={handleEdit}
handleDelete={handleDelete}
/>
</div>
);
}

View File

@ -1,343 +0,0 @@
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 { formatDate } from '@/utils/Date';
import { BASE_URL } from '@/utils/Url';
import {
fetchRegisterFormFileTemplate,
createRegistrationFormFileTemplate,
editRegistrationFormFileTemplate,
deleteRegisterFormFileTemplate,
getRegisterFormFileTemplate
} from '@/app/actions/subscriptionAction';
import {
fetchRegistrationFileGroups,
createRegistrationFileGroup,
deleteRegistrationFileGroup,
editRegistrationFileGroup
} from '@/app/actions/registerFileGroupAction';
import RegistrationFileGroupForm from '@/components/RegistrationFileGroupForm';
import { useEstablishment } from '@/context/EstablishmentContext';
export default function FilesManagement({ csrfToken }) {
const [fichiers, setFichiers] = useState([]);
const [groups, setGroups] = useState([]);
const [selectedGroup, setSelectedGroup] = useState(null);
const [isModalOpen, setIsModalOpen] = useState(false);
const [isEditing, setIsEditing] = useState(false);
const [fileToEdit, setFileToEdit] = useState(null);
const [isGroupModalOpen, setIsGroupModalOpen] = useState(false);
const [groupToEdit, setGroupToEdit] = useState(null);
const { selectedEstablishmentId } = useEstablishment();
// 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);
return {
...file,
group: groupInfo || { id: file.group, name: 'Groupe inconnu' }
};
};
useEffect(() => {
Promise.all([
fetchRegisterFormFileTemplate(),
fetchRegistrationFileGroups(selectedEstablishmentId)
]).then(([filesData, groupsData]) => {
setGroups(groupsData);
// Sélectionner automatiquement le premier groupe s'il existe
if (groupsData.length > 0) {
setSelectedGroup(groupsData[0].id.toString());
}
// Transformer chaque fichier pour inclure les informations complètes du groupe
const transformedFiles = filesData.map(file => transformFileData(file, groupsData));
setFichiers(transformedFiles);
}).catch(err => {
console.log(err.message);
});
}, []);
const handleFileDelete = (fileId) => {
deleteRegisterFormFileTemplate(fileId, csrfToken)
.then(response => {
if (response.ok) {
setFichiers(fichiers.filter(fichier => fichier.id !== fileId));
alert('Fichier supprimé avec succès.');
} else {
alert('Erreur lors de la suppression du fichier.');
}
})
.catch(error => {
console.error('Error deleting file:', error);
alert('Erreur lors de la suppression du fichier.');
});
};
const handleFileEdit = (file) => {
setIsEditing(true);
setFileToEdit(file);
setIsModalOpen(true);
};
const handleFileUpload = ({file, name, is_required, order, groupId}) => {
if (!name) {
alert('Veuillez entrer un nom de fichier.');
return;
}
const formData = new FormData();
if(file) {
formData.append('file', file);
}
formData.append('name', name);
formData.append('is_required', is_required);
formData.append('order', order);
// 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)
.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 {
createRegistrationFormFileTemplate(formData, 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 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');
});
}
};
const handleGroupEdit = (group) => {
setGroupToEdit(group);
setIsGroupModalOpen(true);
};
const handleGroupDelete = (groupId) => {
// Vérifier si des fichiers utilisent ce groupe
const filesInGroup = fichiers.filter(file => file.group && file.group.id === groupId);
if (filesInGroup.length > 0) {
alert('Impossible de supprimer ce groupe car il contient des fichiers. Veuillez d\'abord retirer tous les fichiers de ce groupe.');
return;
}
if (window.confirm('Êtes-vous sûr de vouloir supprimer ce groupe ?')) {
deleteRegistrationFileGroup(groupId, csrfToken)
.then((response) => {
if (response.status === 409) {
throw new Error('Ce groupe est lié à des inscriptions existantes.');
}
if (!response.ok) {
throw new Error('Erreur lors de la suppression du groupe.');
}
setGroups(groups.filter(group => group.id !== groupId));
alert('Groupe supprimé avec succès.');
})
.catch(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);
});
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: 'Actions', transform: (row) => (
<div className="flex items-center justify-center gap-2">
{row.file && (
<a href={`${BASE_URL}${row.file}`} target='_blank' className="text-blue-500 hover:text-blue-700">
<Download size={16} />
</a>
)}
<button onClick={() => handleFileEdit(row)} className="text-blue-500 hover:text-blue-700">
<Edit size={16} />
</button>
<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>
)}
];
const columnsGroups = [
{ name: 'Nom du groupe', transform: (row) => row.name },
{ name: 'Description', transform: (row) => row.description },
{ name: 'Actions', transform: (row) => (
<div className="flex items-center justify-center gap-2">
<button onClick={() => handleGroupEdit(row)} className="text-blue-500 hover:text-blue-700">
<Edit size={16} />
</button>
<button onClick={() => handleGroupDelete(row.id)} className="text-red-500 hover:text-red-700">
<Trash2 size={16} />
</button>
</div>
)}
];
// Fonction pour gérer la demande de signature
const handleSignatureRequest = (file) => {
const formData = new FormData();
formData.append('file', file);
console.log('Demande de signature pour le fichier :', file);
fetch('http://localhost:8080:/DocuSeal/generateToken', {
method: 'POST',
headers: {
'Authorization': 'Bearer NFPZy6BBGvYs1BwTuXMQ3XAu5N1kLFiXWftGQhkiz2A',
},
body: formData,
})
.then((response) => {
if (!response.ok) {
throw new Error('Erreur lors du téléversement du document : ' + response.statusText);
}
return response.json();
})
.then((data) => {
const documentId = data.documentId;
console.log('Document téléversé avec succès, ID :', documentId);
onUpload(documentId);
});
.catch((error) => console.error(error));
};
return (
<div>
<Modal
isOpen={isModalOpen}
setIsOpen={setIsModalOpen}
title={isEditing ? 'Modifier un fichier' : 'Ajouter un fichier'}
ContentComponent={() => (
<FileUpload
onFileUpload={handleFileUpload}
fileToEdit={fileToEdit}
/>
)}
/>
<Modal
isOpen={isGroupModalOpen}
setIsOpen={setIsGroupModalOpen}
title={groupToEdit ? "Modifier le groupe" : "Ajouter un groupe de fichiers"}
ContentComponent={() => (
<RegistrationFileGroupForm
onSubmit={handleGroupSubmit}
initialData={groupToEdit}
/>
)}
/>
<div className="mt-8 mb-4">
<div className="flex justify-between items-center mb-4">
<h2 className="text-xl font-bold">Groupes de fichiers</h2>
<button
onClick={() => setIsGroupModalOpen(true)}
className="flex items-center bg-blue-600 text-white p-2 rounded-full shadow hover:bg-blue-900 transition duration-200"
>
<FolderPlus className="w-5 h-5" />
</button>
</div>
<Table
data={groups}
columns={columnsGroups}
itemsPerPage={5}
currentPage={1}
totalPages={Math.ceil(groups.length / 5)}
/>
</div>
{groups.length > 0 && (
<div className="mt-8">
<div className="flex justify-between items-center mb-4">
<h2 className="text-xl font-bold">Fichiers</h2>
<div className="flex items-center gap-4">
<select
className="border rounded p-2"
value={selectedGroup || ''}
onChange={(e) => setSelectedGroup(e.target.value)}
>
<option value="">Tous les groupes</option>
{groups.map(group => (
<option key={group.id} value={group.id}>{group.name}</option>
))}
</select>
<button
onClick={() => { setIsModalOpen(true); setIsEditing(false); }}
className="flex items-center bg-emerald-600 text-white p-2 rounded-full shadow hover:bg-emerald-900 transition duration-200"
>
<Plus className="w-5 h-5" />
</button>
</div>
</div>
<Table
data={filteredFiles}
columns={columnsFiles}
itemsPerPage={10}
currentPage={1}
totalPages={Math.ceil(filteredFiles.length / 10)}
/>
</div>
)}
</div>
);
}

View File

@ -0,0 +1,273 @@
import React, { useState } from 'react';
import { Plus, Edit3, Trash2, Check, X, FileText } from 'lucide-react';
import Table from '@/components/Table';
import InputText from '@/components/InputText';
import MultiSelect from '@/components/MultiSelect';
import Popup from '@/components/Popup';
import logger from '@/utils/logger';
import { createRegistrationParentFileTemplate } from '@/app/actions/registerFileGroupAction';
import { useCsrfToken } from '@/context/CsrfContext';
import SectionHeader from '@/components/SectionHeader';
export default function ParentFilesSection({ parentFiles, groups, handleCreate, handleEdit, handleDelete }) {
const [editingDocumentId, setEditingDocumentId] = useState(null);
const [formData, setFormData] = useState(null);
const [selectedGroups, setSelectedGroups] = useState([]); // Gestion des groupes sélectionnés
const [guardianDetails, setGuardianDetails] = useState([]);
const [popupVisible, setPopupVisible] = useState(false);
const [popupMessage, setPopupMessage] = useState("");
const [removePopupVisible, setRemovePopupVisible] = useState(false);
const [removePopupMessage, setRemovePopupMessage] = useState("");
const [removePopupOnConfirm, setRemovePopupOnConfirm] = useState(() => {});
const csrfToken = useCsrfToken();
const handleAddEmptyRequiredDocument = () => {
setEditingDocumentId('new');
setFormData({ name: '', description: '', groups: [] });
setSelectedGroups([]); // Réinitialiser les groupes sélectionnés
};
const handleEditDocument = (document) => {
setEditingDocumentId(document.id);
setFormData(document);
const initialSelectedGroups = document.groups.map((groupId) =>
groups.find((group) => group.id === groupId)
);
setSelectedGroups(initialSelectedGroups);
};
const handleSaveDocument = () => {
if (!formData.name) {
alert('Le nom de la pièce est requis.');
return;
}
const updatedFormData = {
...formData,
groups: selectedGroups.map((group) => group.id),
};
if (editingDocumentId === 'new') {
handleCreate(updatedFormData).then((createdDocument) => {
setEditingDocumentId(null);
setFormData(null);
setSelectedGroups([]);
guardianDetails.forEach((guardian, index) => {
// Création des templates
const data = {
master: createdDocument?.id,
registration_form: guardian.registration_form
};
console.log(guardian)
createRegistrationParentFileTemplate(data, csrfToken)
.then(response => {
logger.debug('Template enregistré avec succès:', response);
})
.catch(error => {
logger.error('Erreur lors de l\'enregistrement du template:', error);
});
});
});
} else {
handleEdit(editingDocumentId, updatedFormData).then(() => {
setEditingDocumentId(null);
setFormData(null);
setSelectedGroups([]);
});
}
};
const handleRemoveDocument = (id) => {
return handleDelete(id)
.then(() => {
setEditingDocumentId(null);
setFormData(null);
setSelectedGroups([]);
})
.catch((error) => {
logger.error(error);
});
};
const handleCancelEdit = () => {
setEditingDocumentId(null);
setFormData(null);
setSelectedGroups([]);
};
const handleGroupChange = (selected) => {
setSelectedGroups(selected);
console.log('selected : ', selected)
// Extraire les guardians associés aux register_forms des groupes sélectionnés
const details = selected.flatMap(group =>
group.registration_forms.flatMap(form =>
form.guardians.map(guardian => ({
email: guardian.associated_profile_email,
last_name: form.last_name, // Extraire depuis form
first_name: form.first_name, // Extraire depuis form
registration_form: form.student_id // Utiliser student_id comme ID du register_form
}))
)
);
console.log("Guardians associés : ", details);
setGuardianDetails(details); // Mettre à jour la variable d'état avec les détails des guardians
};
const renderRequiredDocumentCell = (document, column) => {
const isEditing = editingDocumentId === document.id || (editingDocumentId === 'new' && !document.id);
if (isEditing) {
switch (column) {
case 'Nom de la pièce':
return (
<InputText
name="name"
value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
placeholder="Nom de la pièce"
className="w-full"
/>
);
case 'Description':
return (
<InputText
name="description"
value={formData.description}
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
placeholder="Description"
className="w-full"
/>
);
case 'Dossiers d\'inscription':
return (
<MultiSelect
name="groups"
label="Sélection de groupes de fichiers"
options={groups}
selectedOptions={selectedGroups}
onChange={handleGroupChange}
errorMsg={null}
/>
);
case 'Actions':
return (
<div className="flex justify-center space-x-2">
<button
type="button"
onClick={handleSaveDocument}
className="text-green-500 hover:text-green-700"
>
<Check className="w-5 h-5" />
</button>
<button
type="button"
onClick={handleCancelEdit}
className="text-red-500 hover:text-red-700"
>
<X className="w-5 h-5" />
</button>
</div>
);
default:
return null;
}
} else {
switch (column) {
case 'Nom de la pièce':
return <span>{document.name}</span>;
case 'Description':
return <span>{document.description}</span>;
case 'Dossiers d\'inscription':
return (
<span>
{document.groups
.map((groupId) => groups.find((group) => group.id === groupId)?.name || 'Dossiers d\'inscription inconnu')
.join(', ')}
</span>
);
case 'Actions':
return (
<div className="flex justify-center space-x-2">
<button
type="button"
onClick={() => handleEditDocument(document)}
className="text-blue-500 hover:text-blue-700"
>
<Edit3 className="w-5 h-5" />
</button>
<button
type="button"
onClick={() => {
setRemovePopupVisible(true);
setRemovePopupMessage(
`Attentions ! \nVous êtes sur le point de supprimer le document "${document.name}".\nÊtes-vous sûr(e) de vouloir poursuivre l'opération ?`
);
setRemovePopupOnConfirm(() => () => {
handleRemoveDocument(document.id)
.then(() => {
setPopupMessage(`Le document "${document.name}" a été correctement supprimé.`);
setPopupVisible(true);
setRemovePopupVisible(false);
})
.catch((error) => {
logger.error('Erreur lors de la suppression du document:', error);
setPopupMessage(`Erreur lors de la suppression du document "${document.name}".`);
setPopupVisible(true);
setRemovePopupVisible(false);
});
});
}}
className="text-red-500 hover:text-red-700"
>
<Trash2 className="w-5 h-5" />
</button>
</div>
);
default:
return null;
}
}
};
const columnsRequiredDocuments = [
{ name: 'Nom de la pièce', transform: (row) => renderRequiredDocumentCell(row, 'Nom de la pièce') },
{ name: 'Description', transform: (row) => renderRequiredDocumentCell(row, 'Description') },
{ name: 'Dossiers d\'inscription', transform: (row) => renderRequiredDocumentCell(row, 'Dossiers d\'inscription') },
{ name: 'Actions', transform: (row) => renderRequiredDocumentCell(row, 'Actions') },
];
return (
<div className="mt-12 w-4/5">
<SectionHeader
icon={FileText}
title="Pièces à fournir"
description="Configurez la liste des documents que les parents doivent fournir."
button={true}
onClick={handleAddEmptyRequiredDocument}
/>
<Table
data={editingDocumentId === 'new' ? [formData, ...parentFiles] : parentFiles}
columns={columnsRequiredDocuments}
/>
<Popup
visible={popupVisible}
message={popupMessage}
onConfirm={() => setPopupVisible(false)}
onCancel={() => setPopupVisible(false)}
uniqueConfirmButton={true}
/>
<Popup
visible={removePopupVisible}
message={removePopupMessage}
onConfirm={removePopupOnConfirm}
onCancel={() => setRemovePopupVisible(false)}
/>
</div>
);
}

View File

@ -1,20 +1,12 @@
import React, { useState } from 'react';
import {
Plus,
Trash2,
Edit3,
Check,
X,
Percent,
EuroIcon,
Tag,
} from 'lucide-react';
import { Trash2, Edit3, Check, X, Percent, EuroIcon, Tag } from 'lucide-react';
import Table from '@/components/Table';
import Popup from '@/components/Popup';
import CheckBox from '@/components/CheckBox';
import InputText from '@/components/InputText';
import logger from '@/utils/logger';
import { ESTABLISHMENT_ID } from '@/utils/Url';
import SectionHeader from '@/components/SectionHeader';
const DiscountsSection = ({
discounts,
@ -347,22 +339,15 @@ const DiscountsSection = ({
];
return (
<div className="space-y-4">
{!subscriptionMode && (
<div className="flex justify-between items-center">
<div className="flex items-center mb-4">
<Tag className="w-6 h-6 text-emerald-500 mr-2" />
<h2 className="text-xl font-semibold">Liste des réductions</h2>
</div>
<button
type="button"
onClick={handleAddDiscount}
className="text-emerald-500 hover:text-emerald-700"
>
<Plus className="w-5 h-5" />
</button>
</div>
)}
<div className="space-y-4 mt-8">
<SectionHeader
icon={Tag}
discountStyle={true}
title={`${type == 0 ? "Liste des réductions sur les frais d'inscription" : 'Liste des réductions sur les frais de scolarité'}`}
description={`${subscriptionMode ? 'Sélectionnez' : 'Gérez'} ${type == 0 ? " vos réductions sur les frais d'inscription" : ' vos réductions sur les frais de scolarité'}`}
button={!subscriptionMode}
onClick={handleAddDiscount}
/>
<Table
data={newDiscount ? [newDiscount, ...discounts] : discounts}
columns={columns}

View File

@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React from 'react';
import FeesSection from '@/components/Structure/Tarification/FeesSection';
import DiscountsSection from '@/components/Structure/Tarification/DiscountsSection';
import PaymentPlanSelector from '@/components/PaymentPlanSelector';
@ -50,185 +50,181 @@ const FeesManagement = ({
};
return (
<div className="w-full mx-auto p-2 mt-6 space-y-6">
<div className="bg-white p-2 rounded-lg shadow-md">
<h2 className="text-2xl font-semibold mb-4">
Frais d&apos;inscription
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="col-span-1 p-6 rounded-lg shadow-inner mt-4">
<FeesSection
fees={registrationFees}
setFees={setRegistrationFees}
discounts={registrationDiscounts}
handleCreate={(newData) =>
handleCreate(
`${BE_SCHOOL_FEES_URL}`,
newData,
setRegistrationFees
)
}
handleEdit={(id, updatedData) =>
handleEdit(
`${BE_SCHOOL_FEES_URL}`,
id,
updatedData,
setRegistrationFees
)
}
handleDelete={(id) =>
handleDelete(`${BE_SCHOOL_FEES_URL}`, id, setRegistrationFees)
}
type={0}
/>
</div>
<div className="col-span-1 p-6 rounded-lg shadow-inner mt-4">
<DiscountsSection
discounts={registrationDiscounts}
setDiscounts={setRegistrationDiscounts}
handleCreate={(newData) =>
handleCreate(
`${BE_SCHOOL_DISCOUNTS_URL}`,
newData,
setRegistrationDiscounts
)
}
handleEdit={(id, updatedData) =>
handleEdit(
`${BE_SCHOOL_DISCOUNTS_URL}`,
id,
updatedData,
setRegistrationDiscounts
)
}
handleDelete={(id) =>
handleDelete(
`${BE_SCHOOL_DISCOUNTS_URL}`,
id,
setRegistrationDiscounts
)
}
onDiscountDelete={(id) => handleDiscountDelete(id, 0)}
type={0}
/>
</div>
<div className="col-span-1 p-6 rounded-lg shadow-inner mt-4">
<PaymentPlanSelector
paymentPlans={registrationPaymentPlans}
setPaymentPlans={setRegistrationPaymentPlans}
handleEdit={(id, updatedData) =>
handleEdit(
`${BE_SCHOOL_PAYMENT_PLANS_URL}`,
id,
updatedData,
setRegistrationPaymentPlans
)
}
type={0}
/>
</div>
<div className="col-span-1 p-6 rounded-lg shadow-inner mt-4">
<PaymentModeSelector
paymentModes={registrationPaymentModes}
setPaymentModes={setRegistrationPaymentModes}
handleEdit={(id, updatedData) =>
handleEdit(
`${BE_SCHOOL_PAYMENT_MODES_URL}`,
id,
updatedData,
setRegistrationPaymentModes
)
}
type={0}
/>
</div>
<div className="w-full mx-auto mt-6">
<div className="w-4/5 mx-auto flex items-center mt-8">
<hr className="flex-grow border-t-2 border-gray-300" />
<span className="mx-4 text-gray-600 font-semibold">
Frais d'inscription
</span>
<hr className="flex-grow border-t-2 border-gray-300" />
</div>
<div className="mt-8 w-4/5">
<FeesSection
fees={registrationFees}
setFees={setRegistrationFees}
discounts={registrationDiscounts}
handleCreate={(newData) =>
handleCreate(`${BE_SCHOOL_FEES_URL}`, newData, setRegistrationFees)
}
handleEdit={(id, updatedData) =>
handleEdit(
`${BE_SCHOOL_FEES_URL}`,
id,
updatedData,
setRegistrationFees
)
}
handleDelete={(id) =>
handleDelete(`${BE_SCHOOL_FEES_URL}`, id, setRegistrationFees)
}
type={0}
/>
</div>
<div className="mt-12 w-4/5">
<DiscountsSection
discounts={registrationDiscounts}
setDiscounts={setRegistrationDiscounts}
handleCreate={(newData) =>
handleCreate(
`${BE_SCHOOL_DISCOUNTS_URL}`,
newData,
setRegistrationDiscounts
)
}
handleEdit={(id, updatedData) =>
handleEdit(
`${BE_SCHOOL_DISCOUNTS_URL}`,
id,
updatedData,
setRegistrationDiscounts
)
}
handleDelete={(id) =>
handleDelete(
`${BE_SCHOOL_DISCOUNTS_URL}`,
id,
setRegistrationDiscounts
)
}
onDiscountDelete={(id) => handleDiscountDelete(id, 0)}
type={0}
/>
</div>
<div className="mt-12 grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="col-span-1 mt-4">
<PaymentPlanSelector
paymentPlans={registrationPaymentPlans}
setPaymentPlans={setRegistrationPaymentPlans}
handleEdit={(id, updatedData) =>
handleEdit(
`${BE_SCHOOL_PAYMENT_PLANS_URL}`,
id,
updatedData,
setRegistrationPaymentPlans
)
}
type={0}
/>
</div>
<div className="col-span-1 mt-4">
<PaymentModeSelector
paymentModes={registrationPaymentModes}
setPaymentModes={setRegistrationPaymentModes}
handleEdit={(id, updatedData) =>
handleEdit(
`${BE_SCHOOL_PAYMENT_MODES_URL}`,
id,
updatedData,
setRegistrationPaymentModes
)
}
type={0}
/>
</div>
</div>
<div className="bg-white p-2 rounded-lg shadow-md">
<h2 className="text-2xl font-semibold mb-4">Frais de scolarité</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="col-span-1 p-6 rounded-lg shadow-inner mt-4">
<FeesSection
fees={tuitionFees}
setFees={setTuitionFees}
discounts={tuitionDiscounts}
handleCreate={(newData) =>
handleCreate(`${BE_SCHOOL_FEES_URL}`, newData, setTuitionFees)
}
handleEdit={(id, updatedData) =>
handleEdit(
`${BE_SCHOOL_FEES_URL}`,
id,
updatedData,
setTuitionFees
)
}
handleDelete={(id) =>
handleDelete(`${BE_SCHOOL_FEES_URL}`, id, setTuitionFees)
}
type={1}
/>
</div>
<div className="col-span-1 p-6 rounded-lg shadow-inner mt-4">
<DiscountsSection
discounts={tuitionDiscounts}
setDiscounts={setTuitionDiscounts}
handleCreate={(newData) =>
handleCreate(
`${BE_SCHOOL_DISCOUNTS_URL}`,
newData,
setTuitionDiscounts
)
}
handleEdit={(id, updatedData) =>
handleEdit(
`${BE_SCHOOL_DISCOUNTS_URL}`,
id,
updatedData,
setTuitionDiscounts
)
}
handleDelete={(id) =>
handleDelete(
`${BE_SCHOOL_DISCOUNTS_URL}`,
id,
setTuitionDiscounts
)
}
onDiscountDelete={(id) => handleDiscountDelete(id, 1)}
type={1}
/>
</div>
<div className="col-span-1 p-6 rounded-lg shadow-inner mt-4">
<PaymentPlanSelector
paymentPlans={tuitionPaymentPlans}
setPaymentPlans={setTuitionPaymentPlans}
handleEdit={(id, updatedData) =>
handleEdit(
`${BE_SCHOOL_PAYMENT_PLANS_URL}`,
id,
updatedData,
setRegistrationPaymentPlans
)
}
type={1}
/>
</div>
<div className="col-span-1 p-6 rounded-lg shadow-inner mt-4">
<PaymentModeSelector
paymentModes={tuitionPaymentModes}
setPaymentModes={setTuitionPaymentModes}
handleEdit={(id, updatedData) =>
handleEdit(
`${BE_SCHOOL_PAYMENT_MODES_URL}`,
id,
updatedData,
setTuitionPaymentModes
)
}
type={1}
/>
</div>
<div className="w-4/5 mx-auto flex items-center mt-16">
<hr className="flex-grow border-t-2 border-gray-300" />
<span className="mx-4 text-gray-600 font-semibold">
Frais de scolarité
</span>
<hr className="flex-grow border-t-2 border-gray-300" />
</div>
<div className="mt-8 w-4/5">
<FeesSection
fees={tuitionFees}
setFees={setTuitionFees}
discounts={tuitionDiscounts}
handleCreate={(newData) =>
handleCreate(`${BE_SCHOOL_FEES_URL}`, newData, setTuitionFees)
}
handleEdit={(id, updatedData) =>
handleEdit(`${BE_SCHOOL_FEES_URL}`, id, updatedData, setTuitionFees)
}
handleDelete={(id) =>
handleDelete(`${BE_SCHOOL_FEES_URL}`, id, setTuitionFees)
}
type={1}
/>
</div>
<div className="mt-12 w-4/5">
<DiscountsSection
discounts={tuitionDiscounts}
setDiscounts={setTuitionDiscounts}
handleCreate={(newData) =>
handleCreate(
`${BE_SCHOOL_DISCOUNTS_URL}`,
newData,
setTuitionDiscounts
)
}
handleEdit={(id, updatedData) =>
handleEdit(
`${BE_SCHOOL_DISCOUNTS_URL}`,
id,
updatedData,
setTuitionDiscounts
)
}
handleDelete={(id) =>
handleDelete(`${BE_SCHOOL_DISCOUNTS_URL}`, id, setTuitionDiscounts)
}
onDiscountDelete={(id) => handleDiscountDelete(id, 1)}
type={1}
/>
</div>
<div className="mt-12 grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="col-span-1 mt-4">
<PaymentPlanSelector
paymentPlans={tuitionPaymentPlans}
setPaymentPlans={setTuitionPaymentPlans}
handleEdit={(id, updatedData) =>
handleEdit(
`${BE_SCHOOL_PAYMENT_PLANS_URL}`,
id,
updatedData,
setRegistrationPaymentPlans
)
}
type={1}
/>
</div>
<div className="col-span-1 mt-4">
<PaymentModeSelector
paymentModes={tuitionPaymentModes}
setPaymentModes={setTuitionPaymentModes}
handleEdit={(id, updatedData) =>
handleEdit(
`${BE_SCHOOL_PAYMENT_MODES_URL}`,
id,
updatedData,
setTuitionPaymentModes
)
}
type={1}
/>
</div>
</div>
</div>

View File

@ -1,20 +1,11 @@
import React, { useState } from 'react';
import {
Plus,
Trash2,
Edit3,
Check,
X,
EyeOff,
Eye,
CreditCard,
BookOpen,
} from 'lucide-react';
import { Trash2, Edit3, Check, X, EyeOff, Eye, CreditCard } from 'lucide-react';
import Table from '@/components/Table';
import Popup from '@/components/Popup';
import CheckBox from '@/components/CheckBox';
import InputText from '@/components/InputText';
import logger from '@/utils/logger';
import SectionHeader from '@/components/SectionHeader';
import { ESTABLISHMENT_ID } from '@/utils/Url';
@ -325,21 +316,13 @@ const FeesSection = ({
return (
<div className="space-y-4">
{!subscriptionMode && (
<div className="flex justify-between items-center">
<div className="flex items-center mb-4">
<CreditCard className="w-6 h-6 text-emerald-500 mr-2" />
<h2 className="text-xl font-semibold">Liste des frais</h2>
</div>
<button
type="button"
onClick={handleAddFee}
className="text-emerald-500 hover:text-emerald-700"
>
<Plus className="w-5 h-5" />
</button>
</div>
)}
<SectionHeader
icon={CreditCard}
title={`${type == 0 ? "Liste des frais d'inscription" : 'Liste des frais de scolarité'}`}
description={`${subscriptionMode ? 'Sélectionnez' : 'Gérez'} ${type == 0 ? " vos frais d'inscription" : ' vos frais de scolarité'}`}
button={!subscriptionMode}
onClick={handleAddFee}
/>
<Table
data={newFee ? [newFee, ...fees] : fees}
columns={columns}