chore: application prettier

This commit is contained in:
Luc SORIGNET
2025-04-15 19:37:47 +02:00
parent dd0884bbce
commit f7666c894b
174 changed files with 10609 additions and 8760 deletions

View File

@ -1,4 +1,13 @@
import { Trash2, Edit3, Plus, ZoomIn, Users, Check, X, Hand } from 'lucide-react';
import {
Trash2,
Edit3,
Plus,
ZoomIn,
Users,
Check,
X,
Hand,
} from 'lucide-react';
import React, { useState, useEffect } from 'react';
import Table from '@/components/Table';
import Popup from '@/components/Popup';
@ -17,30 +26,40 @@ const ItemTypes = {
TEACHER: 'teacher',
};
const TeachersDropZone = ({ classe, handleTeachersChange, teachers, isEditing }) => {
const [localTeachers, setLocalTeachers] = useState(classe.teachers_details || []);
const TeachersDropZone = ({
classe,
handleTeachersChange,
teachers,
isEditing,
}) => {
const [localTeachers, setLocalTeachers] = useState(
classe.teachers_details || []
);
useEffect(() => {
}, [teachers]);
useEffect(() => {}, [teachers]);
useEffect(() => {
setLocalTeachers(classe.teachers_details || []);
}, [classe.teachers_details]);
useEffect(() => {
handleTeachersChange(localTeachers.map(teacher => teacher.id));
handleTeachersChange(localTeachers.map((teacher) => teacher.id));
}, [localTeachers]);
const [{ isOver, canDrop }, drop] = useDrop({
accept: ItemTypes.TEACHER,
drop: (item) => {
const teacherDetails = teachers.find(teacher => teacher.id === item.id);
const exists = localTeachers.some(teacher => teacher.id === item.id);
const teacherDetails = teachers.find((teacher) => teacher.id === item.id);
const exists = localTeachers.some((teacher) => teacher.id === item.id);
if (!exists) {
setLocalTeachers(prevTeachers => {
setLocalTeachers((prevTeachers) => {
const updatedTeachers = [
...prevTeachers,
{ id: item.id, last_name: teacherDetails.last_name, first_name: teacherDetails.first_name }
{
id: item.id,
last_name: teacherDetails.last_name,
first_name: teacherDetails.first_name,
},
];
return updatedTeachers;
});
@ -56,26 +75,33 @@ const TeachersDropZone = ({ classe, handleTeachersChange, teachers, isEditing })
});
const handleRemoveTeacher = (id) => {
setLocalTeachers(prevTeachers => {
const updatedTeachers = prevTeachers.filter(teacher => teacher.id !== id);
setLocalTeachers((prevTeachers) => {
const updatedTeachers = prevTeachers.filter(
(teacher) => teacher.id !== id
);
return updatedTeachers;
});
};
return (
<div ref={drop} className={`p-2 rounded-md flex flex-col items-center ${isEditing ? 'border-2 border-dashed border-blue-500 bg-blue-50' : ''} ${
isOver && canDrop ? 'border-2 border-solid border-blue-300' : ''
}`}
<div
ref={drop}
className={`p-2 rounded-md flex flex-col items-center ${isEditing ? 'border-2 border-dashed border-blue-500 bg-blue-50' : ''} ${
isOver && canDrop ? 'border-2 border-solid border-blue-300' : ''
}`}
>
{isEditing && (
{isEditing && (
<div className="mb-2 text-blue-500 font-semibold flex items-center space-x-2">
<Hand className="w-5 h-5" /> {/* Ajoutez l'icône Hand */}
<span>Déposez un enseignant ici</span>
</div>
)}
{localTeachers.map((teacher, index) => (
<div key={`${teacher.id}-${index}`} className="flex items-center space-x-2 mb-2">
<TeacherItem key={teacher.id} teacher={teacher} isDraggable={false}/>
<div
key={`${teacher.id}-${index}`}
className="flex items-center space-x-2 mb-2"
>
<TeacherItem key={teacher.id} teacher={teacher} isDraggable={false} />
{isEditing && (
<button
type="button"
@ -91,15 +117,22 @@ const TeachersDropZone = ({ classe, handleTeachersChange, teachers, isEditing })
);
};
const ClassesSection = ({ classes, setClasses, teachers, handleCreate, handleEdit, handleDelete }) => {
const ClassesSection = ({
classes,
setClasses,
teachers,
handleCreate,
handleEdit,
handleDelete,
}) => {
const [formData, setFormData] = useState({});
const [editingClass, setEditingClass] = useState(null);
const [newClass, setNewClass] = useState(null);
const [localErrors, setLocalErrors] = useState({});
const [popupVisible, setPopupVisible] = useState(false);
const [popupMessage, setPopupMessage] = useState("");
const [popupMessage, setPopupMessage] = useState('');
const [removePopupVisible, setRemovePopupVisible] = useState(false);
const [removePopupMessage, setRemovePopupMessage] = useState("");
const [removePopupMessage, setRemovePopupMessage] = useState('');
const [removePopupOnConfirm, setRemovePopupOnConfirm] = useState(() => {});
const [detailsModalVisible, setDetailsModalVisible] = useState(false);
const [selectedClass, setSelectedClass] = useState(null);
@ -122,11 +155,15 @@ const ClassesSection = ({ classes, setClasses, teachers, handleCreate, handleEdi
{ id: 9, name: 'CM2', age: 10 },
];
const allNiveaux = [...niveauxPremierCycle, ...niveauxSecondCycle, ...niveauxTroisiemeCycle];
const allNiveaux = [
...niveauxPremierCycle,
...niveauxSecondCycle,
...niveauxTroisiemeCycle,
];
const getNiveauxLabels = (levels) => {
return levels.map(niveauId => {
const niveau = allNiveaux.find(n => n.id === niveauId);
return levels.map((niveauId) => {
const niveau = allNiveaux.find((n) => n.id === niveauId);
return niveau ? niveau.name : niveauId;
});
};
@ -143,7 +180,10 @@ const ClassesSection = ({ classes, setClasses, teachers, handleCreate, handleEdi
const choices = [];
for (let i = 0; i < 3; i++) {
const year = startYear + i;
choices.push({ value: `${year}-${year + 1}`, label: `${year}-${year + 1}` });
choices.push({
value: `${year}-${year + 1}`,
label: `${year}-${year + 1}`,
});
}
return choices;
};
@ -154,8 +194,25 @@ const ClassesSection = ({ classes, setClasses, teachers, handleCreate, handleEdi
};
const handleAddClass = () => {
setNewClass({ id: Date.now(), atmosphere_name: '', age_range: '', levels: [], number_of_students: '', school_year: '', teachers: [], establishment: ESTABLISHMENT_ID });
setFormData({ atmosphere_name: '', age_range: '', levels: [], number_of_students: '', school_year: '', teachers: [], establishment: ESTABLISHMENT_ID });
setNewClass({
id: Date.now(),
atmosphere_name: '',
age_range: '',
levels: [],
number_of_students: '',
school_year: '',
teachers: [],
establishment: ESTABLISHMENT_ID,
});
setFormData({
atmosphere_name: '',
age_range: '',
levels: [],
number_of_students: '',
school_year: '',
teachers: [],
establishment: ESTABLISHMENT_ID,
});
};
const handleChange = (e) => {
@ -175,7 +232,13 @@ const ClassesSection = ({ classes, setClasses, teachers, handleCreate, handleEdi
};
const handleSaveNewClass = () => {
if (newClass.atmosphere_name && newClass.age_range && newClass.levels.length > 0 && newClass.number_of_students && newClass.school_year) {
if (
newClass.atmosphere_name &&
newClass.age_range &&
newClass.levels.length > 0 &&
newClass.number_of_students &&
newClass.school_year
) {
handleCreate(newClass)
.then((createdClass) => {
setClasses((prevClasses) => [createdClass, ...classes]);
@ -185,21 +248,31 @@ const ClassesSection = ({ classes, setClasses, teachers, handleCreate, handleEdi
.catch((error) => {
logger.error('Error:', error.message);
if (error.details) {
logger.error('Form errors:', error.details);
setLocalErrors(error.details);
logger.error('Form errors:', error.details);
setLocalErrors(error.details);
}
});
} else {
setPopupMessage("Tous les champs doivent être remplis et valides");
setPopupVisible(true);
}
} else {
setPopupMessage('Tous les champs doivent être remplis et valides');
setPopupVisible(true);
}
};
const handleUpdateClass = (id, updatedData) => {
if (updatedData.atmosphere_name && updatedData.age_range && updatedData.levels.length > 0 && updatedData.number_of_students && updatedData.school_year) {
if (
updatedData.atmosphere_name &&
updatedData.age_range &&
updatedData.levels.length > 0 &&
updatedData.number_of_students &&
updatedData.school_year
) {
handleEdit(id, updatedData)
.then((updatedClass) => {
setClasses((prevClasses) => prevClasses.map((classe) => (classe.id === id ? updatedClass : classe)));
setClasses((prevClasses) =>
prevClasses.map((classe) =>
classe.id === id ? updatedClass : classe
)
);
setEditingClass(null);
setFormData({});
setLocalErrors({});
@ -207,12 +280,12 @@ const ClassesSection = ({ classes, setClasses, teachers, handleCreate, handleEdi
.catch((error) => {
logger.error('Error:', error.message);
if (error.details) {
logger.error('Form errors:', error.details);
setLocalErrors(error.details);
logger.error('Form errors:', error.details);
setLocalErrors(error.details);
}
});
} else {
setPopupMessage("Tous les champs doivent être remplis et valides");
setPopupMessage('Tous les champs doivent être remplis et valides');
setPopupVisible(true);
}
};
@ -236,7 +309,7 @@ const ClassesSection = ({ classes, setClasses, teachers, handleCreate, handleEdi
};
const handleMultiSelectChange = (selectedOptions) => {
const levels = selectedOptions.map(option => option.id);
const levels = selectedOptions.map((option) => option.id);
if (editingClass) {
setFormData((prevData) => ({
@ -277,7 +350,7 @@ const ClassesSection = ({ classes, setClasses, teachers, handleCreate, handleEdi
errorMsg={getError('atmosphere_name')}
/>
);
case 'TRANCHE D\'AGE':
case "TRANCHE D'AGE":
return (
<InputText
name="age_range"
@ -286,14 +359,20 @@ const ClassesSection = ({ classes, setClasses, teachers, handleCreate, handleEdi
placeholder="Tranche d'âge (ex: 3-6)"
errorMsg={getError('age_range')}
/>
)
);
case 'NIVEAUX':
return (
<MultiSelect
name="levels"
label="Sélection de niveaux"
options={allNiveaux}
selectedOptions={currentData.levels ? currentData.levels.map(levelId => allNiveaux.find(level => level.id === levelId)) : []}
selectedOptions={
currentData.levels
? currentData.levels.map((levelId) =>
allNiveaux.find((level) => level.id === levelId)
)
: []
}
onChange={handleMultiSelectChange}
errorMsg={getError('levels')}
/>
@ -308,8 +387,8 @@ const ClassesSection = ({ classes, setClasses, teachers, handleCreate, handleEdi
placeholder="Capacité"
errorMsg={getError('number_of_students')}
/>
)
case 'ANNÉE SCOLAIRE' :
);
case 'ANNÉE SCOLAIRE':
return (
<SelectChoice
type="select"
@ -322,24 +401,35 @@ const ClassesSection = ({ classes, setClasses, teachers, handleCreate, handleEdi
IconItem={null}
disabled={false}
/>
)
);
case 'ENSEIGNANTS':
return (
<TeachersDropZone classe={currentData} handleTeachersChange={handleTeachersChange} teachers={teachers} isEditing={isEditing || isCreating} />
<TeachersDropZone
classe={currentData}
handleTeachersChange={handleTeachersChange}
teachers={teachers}
isEditing={isEditing || isCreating}
/>
);
case 'ACTIONS':
return (
<div className="flex justify-center space-x-2">
<button
type="button"
onClick={() => (isEditing ? handleUpdateClass(editingClass, formData) : handleSaveNewClass())}
onClick={() =>
isEditing
? handleUpdateClass(editingClass, formData)
: handleSaveNewClass()
}
className="text-green-500 hover:text-green-700"
>
<Check className="w-5 h-5" />
</button>
<button
type="button"
onClick={() => (isEditing ? setEditingClass(null) : setNewClass(null))}
onClick={() =>
isEditing ? setEditingClass(null) : setNewClass(null)
}
className="text-red-500 hover:text-red-700"
>
<X className="w-5 h-5" />
@ -353,28 +443,34 @@ const ClassesSection = ({ classes, setClasses, teachers, handleCreate, handleEdi
switch (column) {
case 'AMBIANCE':
return classe.atmosphere_name;
case 'TRANCHE D\'AGE':
case "TRANCHE D'AGE":
return classe.age_range;
case 'NIVEAUX':
const levelLabels = Array.isArray(classe.levels) ? getNiveauxLabels(classe.levels) : [];
const levelLabels = Array.isArray(classe.levels)
? getNiveauxLabels(classe.levels)
: [];
return (
<div className="flex flex-wrap justify-center items-center space-x-2">
{levelLabels.length > 0
? levelLabels.map((label, index) => (
<LevelLabel key={index} label={label} index={index} />
))
: 'Aucun niveau'}
? levelLabels.map((label, index) => (
<LevelLabel key={index} label={label} index={index} />
))
: 'Aucun niveau'}
</div>
);
case 'CAPACITE':
return classe.number_of_students;
case 'ANNÉE SCOLAIRE' :
case 'ANNÉE SCOLAIRE':
return classe.school_year;
case 'ENSEIGNANTS':
return (
<div className="flex justify-center space-x-2 flex-wrap">
{classe.teachers_details.map((teacher) => (
<TeacherItem key={teacher.id} teacher={teacher} isDraggable={false} />
<TeacherItem
key={teacher.id}
teacher={teacher}
isDraggable={false}
/>
))}
</div>
);
@ -385,7 +481,9 @@ const ClassesSection = ({ classes, setClasses, teachers, handleCreate, handleEdi
<div className="flex justify-center space-x-2">
<button
type="button"
onClick={() => setEditingClass(classe.id) || setFormData(classe)}
onClick={() =>
setEditingClass(classe.id) || setFormData(classe)
}
className="text-blue-500 hover:text-blue-700"
>
<Edit3 className="w-5 h-5" />
@ -394,18 +492,29 @@ const ClassesSection = ({ classes, setClasses, teachers, handleCreate, handleEdi
type="button"
onClick={() => {
setRemovePopupVisible(true);
setRemovePopupMessage("Attentions ! \nVous êtes sur le point de supprimer la classe " + classe.atmosphere_name + ".\nÊtes-vous sûr(e) de vouloir poursuivre l'opération ?");
setRemovePopupMessage(
'Attentions ! \nVous êtes sur le point de supprimer la classe ' +
classe.atmosphere_name +
".\nÊtes-vous sûr(e) de vouloir poursuivre l'opération ?"
);
setRemovePopupOnConfirm(() => () => {
handleDelete(classe.id)
.then(data => {
.then((data) => {
logger.debug('Success:', data);
setPopupMessage("La classe " + classe.atmosphere_name + " a été correctement supprimée");
setPopupMessage(
'La classe ' +
classe.atmosphere_name +
' a été correctement supprimée'
);
setPopupVisible(true);
setRemovePopupVisible(false);
})
.catch(error => {
.catch((error) => {
logger.error('Error archiving data:', error);
setPopupMessage("Erreur lors de la suppression de la classe " + classe.atmosphere_name);
setPopupMessage(
'Erreur lors de la suppression de la classe ' +
classe.atmosphere_name
);
setPopupVisible(true);
setRemovePopupVisible(false);
});
@ -431,14 +540,14 @@ const ClassesSection = ({ classes, setClasses, teachers, handleCreate, handleEdi
};
const columns = [
{ name: 'AMBIANCE', label: 'Nom d\'ambiance' },
{ name: 'TRANCHE D\'AGE', label: 'Tranche d\'âge' },
{ name: 'AMBIANCE', label: "Nom d'ambiance" },
{ name: "TRANCHE D'AGE", label: "Tranche d'âge" },
{ name: 'NIVEAUX', label: 'Niveaux' },
{ name: 'CAPACITE', label: 'Capacité max' },
{ name: 'ANNÉE SCOLAIRE', label: 'Année scolaire' },
{ name: 'ENSEIGNANTS', label: 'Enseignants' },
{ name: 'MISE A JOUR', label: 'Date mise à jour' },
{ name: 'ACTIONS', label: 'Actions' }
{ name: 'ACTIONS', label: 'Actions' },
];
return (
@ -449,7 +558,11 @@ const ClassesSection = ({ classes, setClasses, teachers, handleCreate, handleEdi
<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">
<button
type="button"
onClick={handleAddClass}
className="text-emerald-500 hover:text-emerald-700"
>
<Plus className="w-5 h-5" />
</button>
</div>
@ -460,7 +573,9 @@ const ClassesSection = ({ classes, setClasses, teachers, handleCreate, handleEdi
/>
<Popup
visible={detailsModalVisible}
message={selectedClass ? <ClasseDetails classe={selectedClass} /> : null}
message={
selectedClass ? <ClasseDetails classe={selectedClass} /> : null
}
onConfirm={() => setDetailsModalVisible(false)}
onCancel={() => setDetailsModalVisible(false)}
uniqueConfirmButton={true}
@ -483,4 +598,4 @@ const ClassesSection = ({ classes, setClasses, teachers, handleCreate, handleEdi
);
};
export default ClassesSection;
export default ClassesSection;

View File

@ -1,15 +1,24 @@
import React from 'react';
import { Calendar } from 'lucide-react';
const DateRange = ({ nameStart, nameEnd, valueStart, valueEnd, onChange, label }) => {
const DateRange = ({
nameStart,
nameEnd,
valueStart,
valueEnd,
onChange,
label,
}) => {
return (
<div className="space-y-4 mt-4 p-4 border rounded-md shadow-sm bg-white">
<label className="block text-lg font-medium text-gray-700 mb-2">{label}</label>
<label className="block text-lg font-medium text-gray-700 mb-2">
{label}
</label>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 items-center">
<div className="relative flex items-center">
<span className="mr-2">Du</span>
<Calendar className="w-5 h-5 text-emerald-500 absolute top-3 left-16" />
<input
<input
type="date"
name={nameStart}
value={valueStart}
@ -21,7 +30,7 @@ const DateRange = ({ nameStart, nameEnd, valueStart, valueEnd, onChange, label }
<div className="relative flex items-center">
<span className="mr-2">Au</span>
<Calendar className="w-5 h-5 text-emerald-500 absolute top-3 left-16" />
<input
<input
type="date"
name={nameEnd}
value={valueEnd}

View File

@ -4,7 +4,13 @@ import DateRange from '@/components/Structure/Configuration/DateRange';
import TimeRange from '@/components/Structure/Configuration/TimeRange';
import CheckBoxList from '@/components/CheckBoxList';
const PlanningConfiguration = ({ formData, handleChange, handleTimeChange, handleJoursChange, typeEmploiDuTemps }) => {
const PlanningConfiguration = ({
formData,
handleChange,
handleTimeChange,
handleJoursChange,
typeEmploiDuTemps,
}) => {
const daysOfWeek = [
{ id: 1, name: 'lun' },
{ id: 2, name: 'mar' },
@ -14,13 +20,15 @@ const PlanningConfiguration = ({ formData, handleChange, handleTimeChange, handl
{ id: 6, name: 'sam' },
];
const isLabelAttenuated = (item) => {
return !formData.opening_days.includes(parseInt(item.id));
const isLabelAttenuated = (item) => {
return !formData.opening_days.includes(parseInt(item.id));
};
return (
<div className="space-y-4">
<label className="mt-6 block text-2xl font-medium text-gray-700">Emploi du temps</label>
<label className="mt-6 block text-2xl font-medium text-gray-700">
Emploi du temps
</label>
<div className="flex justify-between space-x-4 items-start">
<div className="w-1/2">
@ -41,7 +49,7 @@ const PlanningConfiguration = ({ formData, handleChange, handleTimeChange, handl
onStartChange={(e) => handleTimeChange(e, 0)}
onEndChange={(e) => handleTimeChange(e, 1)}
/>
{/* CheckBoxList */}
<CheckBoxList
items={daysOfWeek}

View File

@ -8,17 +8,22 @@ import { HTML5Backend } from 'react-dnd-html5-backend';
import SpecialityItem from '@/components/Structure/Configuration/SpecialityItem';
import logger from '@/utils/logger';
const SpecialitiesSection = ({ specialities, setSpecialities, handleCreate, handleEdit, handleDelete }) => {
const SpecialitiesSection = ({
specialities,
setSpecialities,
handleCreate,
handleEdit,
handleDelete,
}) => {
const [newSpeciality, setNewSpeciality] = useState(null);
const [editingSpeciality, setEditingSpeciality] = useState(null);
const [formData, setFormData] = useState({});
const [localErrors, setLocalErrors] = useState({});
const [popupVisible, setPopupVisible] = useState(false);
const [popupMessage, setPopupMessage] = useState("");
const [popupMessage, setPopupMessage] = useState('');
const [removePopupVisible, setRemovePopupVisible] = useState(false);
const [removePopupMessage, setRemovePopupMessage] = useState("");
const [removePopupMessage, setRemovePopupMessage] = useState('');
const [removePopupOnConfirm, setRemovePopupOnConfirm] = useState(() => {});
// Récupération des messages d'erreur
@ -33,16 +38,17 @@ const SpecialitiesSection = ({ specialities, setSpecialities, handleCreate, hand
const handleRemoveSpeciality = (id) => {
return handleDelete(id)
.then(() => {
setSpecialities(prevSpecialities => prevSpecialities.filter(speciality => speciality.id !== id));
setSpecialities((prevSpecialities) =>
prevSpecialities.filter((speciality) => speciality.id !== id)
);
})
.catch(error => {
.catch((error) => {
logger.error(error);
});
};
const handleSaveNewSpeciality = () => {
if (
newSpeciality.name) {
if (newSpeciality.name) {
handleCreate(newSpeciality)
.then((createdSpeciality) => {
setSpecialities([createdSpeciality, ...specialities]);
@ -52,34 +58,37 @@ const SpecialitiesSection = ({ specialities, setSpecialities, handleCreate, hand
.catch((error) => {
logger.error('Error:', error.message);
if (error.details) {
logger.error('Form errors:', error.details);
setLocalErrors(error.details);
logger.error('Form errors:', error.details);
setLocalErrors(error.details);
}
});
} else {
setPopupMessage("Tous les champs doivent être remplis et valides");
setPopupMessage('Tous les champs doivent être remplis et valides');
setPopupVisible(true);
}
};
const handleUpdateSpeciality = (id, updatedSpeciality) => {
if (
updatedSpeciality.name) {
if (updatedSpeciality.name) {
handleEdit(id, updatedSpeciality)
.then((updatedSpeciality) => {
setSpecialities(specialities.map(speciality => speciality.id === id ? updatedSpeciality : speciality));
setSpecialities(
specialities.map((speciality) =>
speciality.id === id ? updatedSpeciality : speciality
)
);
setEditingSpeciality(null);
setLocalErrors({});
})
.catch((error) => {
logger.error('Error:', error.message);
if (error.details) {
logger.error('Form errors:', error.details);
setLocalErrors(error.details);
logger.error('Form errors:', error.details);
setLocalErrors(error.details);
}
});
} else {
setPopupMessage("Tous les champs doivent être remplis et valides");
setPopupMessage('Tous les champs doivent être remplis et valides');
setPopupVisible(true);
}
};
@ -129,14 +138,22 @@ const SpecialitiesSection = ({ specialities, setSpecialities, handleCreate, hand
<div className="flex justify-center space-x-2">
<button
type="button"
onClick={() => (isEditing ? handleUpdateSpeciality(editingSpeciality, formData) : handleSaveNewSpeciality())}
onClick={() =>
isEditing
? handleUpdateSpeciality(editingSpeciality, formData)
: handleSaveNewSpeciality()
}
className="text-green-500 hover:text-green-700"
>
<Check className="w-5 h-5" />
</button>
<button
type="button"
onClick={() => (isEditing ? setEditingSpeciality(null) : setNewSpeciality(null))}
onClick={() =>
isEditing
? setEditingSpeciality(null)
: setNewSpeciality(null)
}
className="text-red-500 hover:text-red-700"
>
<X className="w-5 h-5" />
@ -149,9 +166,7 @@ const SpecialitiesSection = ({ specialities, setSpecialities, handleCreate, hand
} else {
switch (column) {
case 'LIBELLE':
return (
<SpecialityItem key={speciality.id} speciality={speciality} />
);
return <SpecialityItem key={speciality.id} speciality={speciality} />;
case 'MISE A JOUR':
return speciality.updated_date_formatted;
case 'ACTIONS':
@ -159,7 +174,9 @@ const SpecialitiesSection = ({ specialities, setSpecialities, handleCreate, hand
<div className="flex justify-center space-x-2">
<button
type="button"
onClick={() => setEditingSpeciality(speciality.id) || setFormData(speciality)}
onClick={() =>
setEditingSpeciality(speciality.id) || setFormData(speciality)
}
className="text-blue-500 hover:text-blue-700"
>
<Edit3 className="w-5 h-5" />
@ -168,18 +185,29 @@ const SpecialitiesSection = ({ specialities, setSpecialities, handleCreate, hand
type="button"
onClick={() => {
setRemovePopupVisible(true);
setRemovePopupMessage("Attentions ! \nVous êtes sur le point de supprimer la spécialité " + speciality.name + ".\nÊtes-vous sûr(e) de vouloir poursuivre l'opération ?");
setRemovePopupMessage(
'Attentions ! \nVous êtes sur le point de supprimer la spécialité ' +
speciality.name +
".\nÊtes-vous sûr(e) de vouloir poursuivre l'opération ?"
);
setRemovePopupOnConfirm(() => () => {
handleRemoveSpeciality(speciality.id)
.then(data => {
.then((data) => {
logger.debug('Success:', data);
setPopupMessage("La spécialité " + speciality.name + " a été correctement supprimée");
setPopupMessage(
'La spécialité ' +
speciality.name +
' a été correctement supprimée'
);
setPopupVisible(true);
setRemovePopupVisible(false);
})
.catch(error => {
.catch((error) => {
logger.error('Error archiving data:', error);
setPopupMessage("Erreur lors de la suppression de la spécialité " + speciality.name);
setPopupMessage(
'Erreur lors de la suppression de la spécialité ' +
speciality.name
);
setPopupVisible(true);
setRemovePopupVisible(false);
});
@ -198,10 +226,10 @@ const SpecialitiesSection = ({ specialities, setSpecialities, handleCreate, hand
};
const columns = [
{ name: 'LIBELLE', label: 'Libellé' },
{ name: 'MISE A JOUR', label: 'Date mise à jour' },
{ name: 'ACTIONS', label: 'Actions' }
];
{ name: 'LIBELLE', label: 'Libellé' },
{ name: 'MISE A JOUR', label: 'Date mise à jour' },
{ name: 'ACTIONS', label: 'Actions' },
];
return (
<DndProvider backend={HTML5Backend}>
@ -211,7 +239,11 @@ const SpecialitiesSection = ({ specialities, setSpecialities, handleCreate, hand
<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">
<button
type="button"
onClick={handleAddSpeciality}
className="text-emerald-500 hover:text-emerald-700"
>
<Plus className="w-5 h-5" />
</button>
</div>

View File

@ -9,29 +9,32 @@ const lightenColor = (color, percent) => {
const num = parseInt(color.slice(1), 16),
amt = Math.round(2.55 * percent),
R = (num >> 16) + amt,
G = (num >> 8 & 0x00FF) + amt,
B = (num & 0x0000FF) + amt;
return `#${(0x1000000 + (R < 255 ? R < 1 ? 0 : R : 255) * 0x10000 + (G < 255 ? G < 1 ? 0 : G : 255) * 0x100 + (B < 255 ? B < 1 ? 0 : B : 255)).toString(16).slice(1).toUpperCase()}`;
G = ((num >> 8) & 0x00ff) + amt,
B = (num & 0x0000ff) + amt;
return `#${(0x1000000 + (R < 255 ? (R < 1 ? 0 : R) : 255) * 0x10000 + (G < 255 ? (G < 1 ? 0 : G) : 255) * 0x100 + (B < 255 ? (B < 1 ? 0 : B) : 255)).toString(16).slice(1).toUpperCase()}`;
};
const darkenColor = (color, percent) => {
const num = parseInt(color.slice(1), 16),
amt = Math.round(2.55 * percent),
R = (num >> 16) - amt,
G = (num >> 8 & 0x00FF) - amt,
B = (num & 0x0000FF) - amt;
return `#${(0x1000000 + (R < 255 ? R < 1 ? 0 : R : 255) * 0x10000 + (G < 255 ? G < 1 ? 0 : G : 255) * 0x100 + (B < 255 ? B < 1 ? 0 : B : 255)).toString(16).slice(1).toUpperCase()}`;
G = ((num >> 8) & 0x00ff) - amt,
B = (num & 0x0000ff) - amt;
return `#${(0x1000000 + (R < 255 ? (R < 1 ? 0 : R) : 255) * 0x10000 + (G < 255 ? (G < 1 ? 0 : G) : 255) * 0x100 + (B < 255 ? (B < 1 ? 0 : B) : 255)).toString(16).slice(1).toUpperCase()}`;
};
const SpecialityItem = ({ speciality, isDraggable = true }) => {
const [{ isDragging }, drag] = useDrag(() => ({
type: ItemTypes.SPECIALITY,
item: { id: speciality.id, name: speciality.name },
collect: (monitor) => ({
isDragging: !!monitor.isDragging(),
const [{ isDragging }, drag] = useDrag(
() => ({
type: ItemTypes.SPECIALITY,
item: { id: speciality.id, name: speciality.name },
collect: (monitor) => ({
isDragging: !!monitor.isDragging(),
}),
canDrag: () => isDraggable,
}),
canDrag: () => isDraggable,
}), [isDraggable]);
[isDraggable]
);
return (
<div
@ -40,10 +43,12 @@ const SpecialityItem = ({ speciality, isDraggable = true }) => {
isDragging ? 'opacity-30' : 'opacity-100'
} ${isDraggable ? 'cursor-grabbing hover:shadow-lg hover:scale-105' : ''}`}
style={{
backgroundColor: isDragging ? lightenColor(speciality.color_code, 30) : speciality.color_code,
backgroundColor: isDragging
? lightenColor(speciality.color_code, 30)
: speciality.color_code,
border: `1px solid ${darkenColor(speciality.color_code, 20)}`,
color: 'white',
boxShadow: isDraggable ? '0 2px 4px rgba(0, 0, 0, 0.1)' : 'none'
boxShadow: isDraggable ? '0 2px 4px rgba(0, 0, 0, 0.1)' : 'none',
}}
>
{speciality.name}
@ -51,4 +56,4 @@ const SpecialityItem = ({ speciality, isDraggable = true }) => {
);
};
export default SpecialityItem;
export default SpecialityItem;

View File

@ -3,9 +3,24 @@ import SpecialitiesSection from '@/components/Structure/Configuration/Specialiti
import TeachersSection from '@/components/Structure/Configuration/TeachersSection';
import ClassesSection from '@/components/Structure/Configuration/ClassesSection';
import { ClassesProvider } from '@/context/ClassesContext';
import { BE_SCHOOL_SPECIALITIES_URL, BE_SCHOOL_TEACHERS_URL, BE_SCHOOL_SCHOOLCLASSES_URL } from '@/utils/Url';
import {
BE_SCHOOL_SPECIALITIES_URL,
BE_SCHOOL_TEACHERS_URL,
BE_SCHOOL_SCHOOLCLASSES_URL,
} from '@/utils/Url';
const StructureManagement = ({ specialities, setSpecialities, teachers, setTeachers, classes, setClasses, profiles, handleCreate, handleEdit, handleDelete }) => {
const StructureManagement = ({
specialities,
setSpecialities,
teachers,
setTeachers,
classes,
setClasses,
profiles,
handleCreate,
handleEdit,
handleDelete,
}) => {
return (
<div className="max-w-8xl mx-auto p-4 mt-6 space-y-8">
<ClassesProvider>
@ -13,9 +28,24 @@ const StructureManagement = ({ specialities, setSpecialities, teachers, setTeach
<SpecialitiesSection
specialities={specialities}
setSpecialities={setSpecialities}
handleCreate={(newData) => handleCreate(`${BE_SCHOOL_SPECIALITIES_URL}`, newData, setSpecialities)}
handleEdit={(id, updatedData) => handleEdit(`${BE_SCHOOL_SPECIALITIES_URL}`, id, updatedData, setSpecialities)}
handleDelete={(id) => handleDelete(`${BE_SCHOOL_SPECIALITIES_URL}`, id, setSpecialities)}
handleCreate={(newData) =>
handleCreate(
`${BE_SCHOOL_SPECIALITIES_URL}`,
newData,
setSpecialities
)
}
handleEdit={(id, updatedData) =>
handleEdit(
`${BE_SCHOOL_SPECIALITIES_URL}`,
id,
updatedData,
setSpecialities
)
}
handleDelete={(id) =>
handleDelete(`${BE_SCHOOL_SPECIALITIES_URL}`, id, setSpecialities)
}
/>
</div>
<div className="w-4/5 p-4 bg-white rounded-lg shadow-md">
@ -24,9 +54,20 @@ const StructureManagement = ({ specialities, setSpecialities, teachers, setTeach
setTeachers={setTeachers}
specialities={specialities}
profiles={profiles}
handleCreate={(newData) => handleCreate(`${BE_SCHOOL_TEACHERS_URL}`, newData, setTeachers)}
handleEdit={(id, updatedData) => handleEdit(`${BE_SCHOOL_TEACHERS_URL}`, id, updatedData, setTeachers)}
handleDelete={(id) => handleDelete(`${BE_SCHOOL_TEACHERS_URL}`, id, setTeachers)}
handleCreate={(newData) =>
handleCreate(`${BE_SCHOOL_TEACHERS_URL}`, newData, setTeachers)
}
handleEdit={(id, updatedData) =>
handleEdit(
`${BE_SCHOOL_TEACHERS_URL}`,
id,
updatedData,
setTeachers
)
}
handleDelete={(id) =>
handleDelete(`${BE_SCHOOL_TEACHERS_URL}`, id, setTeachers)
}
/>
</div>
<div className="w-full p-4 bg-white rounded-lg shadow-md">
@ -34,9 +75,24 @@ const StructureManagement = ({ specialities, setSpecialities, teachers, setTeach
classes={classes}
setClasses={setClasses}
teachers={teachers}
handleCreate={(newData) => handleCreate(`${BE_SCHOOL_SCHOOLCLASSES_URL}`, newData, setClasses)}
handleEdit={(id, updatedData) => handleEdit(`${BE_SCHOOL_SCHOOLCLASSES_URL}`, id, updatedData, setClasses)}
handleDelete={(id) => handleDelete(`${BE_SCHOOL_SCHOOLCLASSES_URL}`, id, setClasses)}
handleCreate={(newData) =>
handleCreate(
`${BE_SCHOOL_SCHOOLCLASSES_URL}`,
newData,
setClasses
)
}
handleEdit={(id, updatedData) =>
handleEdit(
`${BE_SCHOOL_SCHOOLCLASSES_URL}`,
id,
updatedData,
setClasses
)
}
handleDelete={(id) =>
handleDelete(`${BE_SCHOOL_SCHOOLCLASSES_URL}`, id, setClasses)
}
/>
</div>
</ClassesProvider>

View File

@ -4,9 +4,9 @@ const TabsStructure = ({ activeTab, setActiveTab, tabs }) => {
return (
<div className="flex justify-center items-center w-full">
{tabs.map((tab) => (
<button
<button
key={tab.id}
className={`tab px-4 py-2 mx-2 flex items-center justify-center space-x-2 ${activeTab === tab.id ? 'bg-emerald-600 text-white shadow-lg' : 'bg-emerald-200 text-emerald-600'} rounded-full`}
className={`tab px-4 py-2 mx-2 flex items-center justify-center space-x-2 ${activeTab === tab.id ? 'bg-emerald-600 text-white shadow-lg' : 'bg-emerald-200 text-emerald-600'} rounded-full`}
onClick={() => setActiveTab(tab.id)}
>
<tab.icon className="w-5 h-5" />
@ -18,6 +18,3 @@ const TabsStructure = ({ activeTab, setActiveTab, tabs }) => {
};
export default TabsStructure;

View File

@ -6,14 +6,20 @@ const ItemTypes = {
};
const TeacherItem = ({ teacher, isDraggable = true }) => {
const [{ isDragging }, drag] = useDrag(() => ({
type: ItemTypes.TEACHER,
item: { id: teacher.id, name: `${teacher.last_name} ${teacher.first_name}` },
collect: (monitor) => ({
isDragging: !!monitor.isDragging(),
const [{ isDragging }, drag] = useDrag(
() => ({
type: ItemTypes.TEACHER,
item: {
id: teacher.id,
name: `${teacher.last_name} ${teacher.first_name}`,
},
collect: (monitor) => ({
isDragging: !!monitor.isDragging(),
}),
canDrag: () => isDraggable,
}),
canDrag: () => isDraggable,
}), [isDraggable]);
[isDraggable]
);
return (
<div
@ -22,9 +28,13 @@ const TeacherItem = ({ teacher, isDraggable = true }) => {
isDragging ? 'opacity-30' : 'opacity-100'
} ${isDraggable ? 'cursor-grabbing hover:shadow-lg hover:scale-105' : ''}`}
style={{
backgroundColor: isDragging ? '#d1d5db' : isDraggable ? '#10b981' : '#a7f3d0', // Change background color based on dragging state and draggable state
backgroundColor: isDragging
? '#d1d5db'
: isDraggable
? '#10b981'
: '#a7f3d0', // Change background color based on dragging state and draggable state
border: isDraggable ? '1px solid #10b981' : '1px solid #a7f3d0', // Add a border
boxShadow: isDraggable ? '0 2px 4px rgba(0, 0, 0, 0.1)' : 'none' // Add a shadow if draggable
boxShadow: isDraggable ? '0 2px 4px rgba(0, 0, 0, 0.1)' : 'none', // Add a shadow if draggable
}}
>
{teacher.last_name} {teacher.first_name}
@ -32,4 +42,4 @@ const TeacherItem = ({ teacher, isDraggable = true }) => {
);
};
export default TeacherItem;
export default TeacherItem;

View File

@ -1,5 +1,14 @@
import React, { useState, useEffect } from 'react';
import { Plus, Edit3, Trash2, GraduationCap, Check, X, Hand, Search } from 'lucide-react';
import {
Plus,
Edit3,
Trash2,
GraduationCap,
Check,
X,
Hand,
Search,
} from 'lucide-react';
import Table from '@/components/Table';
import Popup from '@/components/Popup';
import ToggleSwitch from '@/components/ToggleSwitch';
@ -18,30 +27,46 @@ const ItemTypes = {
SPECIALITY: 'speciality',
};
const SpecialitiesDropZone = ({ teacher, handleSpecialitiesChange, specialities, isEditing }) => {
const [localSpecialities, setLocalSpecialities] = useState(teacher.specialities_details || []);
const SpecialitiesDropZone = ({
teacher,
handleSpecialitiesChange,
specialities,
isEditing,
}) => {
const [localSpecialities, setLocalSpecialities] = useState(
teacher.specialities_details || []
);
useEffect(() => {
}, [specialities]);
useEffect(() => {}, [specialities]);
useEffect(() => {
setLocalSpecialities(teacher.specialities_details || []);
}, [teacher.specialities_details]);
useEffect(() => {
handleSpecialitiesChange(localSpecialities.map(speciality => speciality.id));
handleSpecialitiesChange(
localSpecialities.map((speciality) => speciality.id)
);
}, [localSpecialities]);
const [{ isOver, canDrop }, drop] = useDrop({
accept: ItemTypes.SPECIALITY,
drop: (item) => {
const specialityDetails = specialities.find(speciality => speciality.id === item.id);
const exists = localSpecialities.some(speciality => speciality.id === item.id);
const specialityDetails = specialities.find(
(speciality) => speciality.id === item.id
);
const exists = localSpecialities.some(
(speciality) => speciality.id === item.id
);
if (!exists) {
setLocalSpecialities(prevSpecialities => {
setLocalSpecialities((prevSpecialities) => {
const updatedSpecialities = [
...prevSpecialities,
{ id: item.id, name: specialityDetails.name, color_code: specialityDetails.color_code }
{
id: item.id,
name: specialityDetails.name,
color_code: specialityDetails.color_code,
},
];
return updatedSpecialities;
});
@ -57,26 +82,37 @@ const SpecialitiesDropZone = ({ teacher, handleSpecialitiesChange, specialities,
});
const handleRemoveSpeciality = (id) => {
setLocalSpecialities(prevSpecialities => {
const updatedSpecialities = prevSpecialities.filter(speciality => speciality.id !== id);
setLocalSpecialities((prevSpecialities) => {
const updatedSpecialities = prevSpecialities.filter(
(speciality) => speciality.id !== id
);
return updatedSpecialities;
});
};
return (
<div ref={drop} className={`p-2 rounded-md flex flex-col items-center ${isEditing ? 'border-2 border-dashed border-blue-500 bg-blue-50' : ''} ${
isOver && canDrop ? 'border-2 border-solid border-blue-300' : ''
}`}
<div
ref={drop}
className={`p-2 rounded-md flex flex-col items-center ${isEditing ? 'border-2 border-dashed border-blue-500 bg-blue-50' : ''} ${
isOver && canDrop ? 'border-2 border-solid border-blue-300' : ''
}`}
>
{isEditing && (
{isEditing && (
<div className="mb-2 text-blue-500 font-semibold flex items-center space-x-2">
<Hand className="w-5 h-5" /> {/* Ajoutez l'icône Hand */}
<span>Déposez une spécialité ici</span>
</div>
)}
{localSpecialities.map((speciality, index) => (
<div key={`${speciality.id}-${index}`} className="flex items-center space-x-2 mb-2">
<SpecialityItem key={speciality.id} speciality={speciality} isDraggable={false}/>
<div
key={`${speciality.id}-${index}`}
className="flex items-center space-x-2 mb-2"
>
<SpecialityItem
key={speciality.id}
speciality={speciality}
isDraggable={false}
/>
{isEditing && (
<button
type="button"
@ -92,37 +128,45 @@ const SpecialitiesDropZone = ({ teacher, handleSpecialitiesChange, specialities,
);
};
const TeachersSection = ({ teachers, setTeachers, specialities, profiles, handleCreate, handleEdit, handleDelete }) => {
const TeachersSection = ({
teachers,
setTeachers,
specialities,
profiles,
handleCreate,
handleEdit,
handleDelete,
}) => {
const csrfToken = useCsrfToken();
const [editingTeacher, setEditingTeacher] = useState(null);
const [newTeacher, setNewTeacher] = useState(null);
const [formData, setFormData] = useState({});
const [localErrors, setLocalErrors] = useState({});
const [popupVisible, setPopupVisible] = useState(false);
const [popupMessage, setPopupMessage] = useState("");
const [popupMessage, setPopupMessage] = useState('');
const [removePopupVisible, setRemovePopupVisible] = useState(false);
const [removePopupMessage, setRemovePopupMessage] = useState("");
const [removePopupMessage, setRemovePopupMessage] = useState('');
const [removePopupOnConfirm, setRemovePopupOnConfirm] = useState(() => {});
const [confirmPopupVisible, setConfirmPopupVisible] = useState(false);
const [confirmPopupMessage, setConfirmPopupMessage] = useState("");
const [confirmPopupMessage, setConfirmPopupMessage] = useState('');
const [confirmPopupOnConfirm, setConfirmPopupOnConfirm] = useState(() => {});
const { selectedEstablishmentId } = useEstablishment();
const handleEmailChange = (e) => {
const email = e.target.value;
// Vérifier si l'email correspond à un profil existant
const existingProfile = profiles.find((profile) => profile.email === email);
setFormData((prevData) => ({
...prevData,
associated_profile_email: email,
existingProfileId: existingProfile ? existingProfile.id : null,
}));
if (newTeacher) {
setNewTeacher((prevData) => ({
...prevData,
@ -133,7 +177,7 @@ const TeachersSection = ({ teachers, setTeachers, specialities, profiles, handle
};
const handleCancelConfirmation = () => {
setConfirmPopupVisible(false);
setConfirmPopupVisible(false);
};
// Récupération des messages d'erreur
@ -142,22 +186,41 @@ const TeachersSection = ({ teachers, setTeachers, specialities, profiles, handle
};
const handleAddTeacher = () => {
setNewTeacher({ id: Date.now(), last_name: '', first_name: '', associated_profile_email: '', specialities: [], role_type: 0 });
setFormData({ last_name: '', first_name: '', associated_profile_email: '', specialities: [], role_type: 0});
setNewTeacher({
id: Date.now(),
last_name: '',
first_name: '',
associated_profile_email: '',
specialities: [],
role_type: 0,
});
setFormData({
last_name: '',
first_name: '',
associated_profile_email: '',
specialities: [],
role_type: 0,
});
};
const handleRemoveTeacher = (id) => {
return handleDelete(id)
.then(() => {
setTeachers(prevTeachers => prevTeachers.filter(teacher => teacher.id !== id));
setTeachers((prevTeachers) =>
prevTeachers.filter((teacher) => teacher.id !== id)
);
})
.catch(error => {
.catch((error) => {
logger.error(error);
});
};
const handleSaveNewTeacher = () => {
if (formData.last_name && formData.first_name && formData.associated_profile_email) {
if (
formData.last_name &&
formData.first_name &&
formData.associated_profile_email
) {
const data = {
last_name: formData.last_name,
first_name: formData.first_name,
@ -177,7 +240,7 @@ const TeachersSection = ({ teachers, setTeachers, specialities, profiles, handle
},
specialities: formData.specialities || [],
};
handleCreate(data)
.then((createdTeacher) => {
setTeachers([createdTeacher, ...teachers]);
@ -192,7 +255,7 @@ const TeachersSection = ({ teachers, setTeachers, specialities, profiles, handle
}
});
} else {
setPopupMessage("Tous les champs doivent être remplis et valides");
setPopupMessage('Tous les champs doivent être remplis et valides');
setPopupVisible(true);
}
};
@ -202,17 +265,24 @@ const TeachersSection = ({ teachers, setTeachers, specialities, profiles, handle
const currentTeacher = teachers.find((teacher) => teacher.id === id);
// Vérifier si l'email correspond à un profil existant
const existingProfile = profiles.find((profile) => profile.email === currentTeacher.associated_profile_email);
const existingProfile = profiles.find(
(profile) => profile.email === currentTeacher.associated_profile_email
);
// Vérifier si l'email a été modifié
const isEmailModified = currentTeacher
? currentTeacher.associated_profile_email !== updatedData.associated_profile_email
: true;
? currentTeacher.associated_profile_email !==
updatedData.associated_profile_email
: true;
// Mettre à jour existingProfileId en fonction de l'email
updatedData.existingProfileId = existingProfile ? existingProfile.id : null;
if (updatedData.last_name && updatedData.first_name && updatedData.associated_profile_email) {
if (
updatedData.last_name &&
updatedData.first_name &&
updatedData.associated_profile_email
) {
const data = {
last_name: updatedData.last_name,
first_name: updatedData.first_name,
@ -238,7 +308,9 @@ const TeachersSection = ({ teachers, setTeachers, specialities, profiles, handle
handleEdit(id, data)
.then((updatedTeacher) => {
setTeachers((prevTeachers) =>
prevTeachers.map((teacher) => (teacher.id === id ? { ...teacher, ...updatedTeacher } : teacher))
prevTeachers.map((teacher) =>
teacher.id === id ? { ...teacher, ...updatedTeacher } : teacher
)
);
setEditingTeacher(null);
setFormData({});
@ -251,7 +323,7 @@ const TeachersSection = ({ teachers, setTeachers, specialities, profiles, handle
}
});
} else {
setPopupMessage("Tous les champs doivent être remplis et valides");
setPopupMessage('Tous les champs doivent être remplis et valides');
setPopupVisible(true);
}
};
@ -347,7 +419,12 @@ const TeachersSection = ({ teachers, setTeachers, specialities, profiles, handle
);
case 'SPECIALITES':
return (
<SpecialitiesDropZone teacher={currentData} handleSpecialitiesChange={handleSpecialitiesChange} specialities={specialities} isEditing={isEditing || isCreating} />
<SpecialitiesDropZone
teacher={currentData}
handleSpecialitiesChange={handleSpecialitiesChange}
specialities={specialities}
isEditing={isEditing || isCreating}
/>
);
case 'ADMINISTRATEUR':
return (
@ -364,14 +441,20 @@ const TeachersSection = ({ teachers, setTeachers, specialities, profiles, handle
<div className="flex justify-center space-x-2">
<button
type="button"
onClick={() => (isEditing ? handleUpdateTeacher(editingTeacher, formData) : handleSaveNewTeacher())}
onClick={() =>
isEditing
? handleUpdateTeacher(editingTeacher, formData)
: handleSaveNewTeacher()
}
className="text-green-500 hover:text-green-700"
>
<Check className="w-5 h-5" />
</button>
<button
type="button"
onClick={() => (isEditing ? setEditingTeacher(null) : setNewTeacher(null))}
onClick={() =>
isEditing ? setEditingTeacher(null) : setNewTeacher(null)
}
className="text-red-500 hover:text-red-700"
>
<X className="w-5 h-5" />
@ -384,33 +467,43 @@ const TeachersSection = ({ teachers, setTeachers, specialities, profiles, handle
} else {
switch (column) {
case 'NOM - PRENOM':
return (
<TeacherItem key={teacher.id} teacher={teacher} />
);
return <TeacherItem key={teacher.id} teacher={teacher} />;
case 'EMAIL':
return teacher.associated_profile_email;
case 'SPECIALITES':
return (
<div className="flex justify-center space-x-2 flex-wrap">
{teacher.specialities_details.map((speciality) => (
<SpecialityItem key={speciality.id} speciality={speciality} isDraggable={false} />
<SpecialityItem
key={speciality.id}
speciality={speciality}
isDraggable={false}
/>
))}
</div>
);
case 'ADMINISTRATEUR':
if (teacher.associated_profile_email) {
const badgeClass = teacher.role_type === 1 ? 'bg-red-100 text-red-600' : 'bg-blue-100 text-blue-600';
const label = teacher.role_type === 1 ? 'OUI' : 'NON';
return (
<div key={teacher.id} className="flex justify-center items-center space-x-2">
<span className={`px-3 py-1 rounded-full font-bold ${badgeClass}`}>
{label}
</span>
</div>
);
} else {
return <i>Non définie</i>;
};
if (teacher.associated_profile_email) {
const badgeClass =
teacher.role_type === 1
? 'bg-red-100 text-red-600'
: 'bg-blue-100 text-blue-600';
const label = teacher.role_type === 1 ? 'OUI' : 'NON';
return (
<div
key={teacher.id}
className="flex justify-center items-center space-x-2"
>
<span
className={`px-3 py-1 rounded-full font-bold ${badgeClass}`}
>
{label}
</span>
</div>
);
} else {
return <i>Non définie</i>;
}
case 'MISE A JOUR':
return teacher.updated_date_formatted;
case 'ACTIONS':
@ -427,18 +520,35 @@ const TeachersSection = ({ teachers, setTeachers, specialities, profiles, handle
type="button"
onClick={() => {
setRemovePopupVisible(true);
setRemovePopupMessage("Attentions ! \nVous êtes sur le point de supprimer l'enseignant " + teacher.last_name + " " + teacher.first_name + ".\nÊtes-vous sûr(e) de vouloir poursuivre l'opération ?");
setRemovePopupMessage(
"Attentions ! \nVous êtes sur le point de supprimer l'enseignant " +
teacher.last_name +
' ' +
teacher.first_name +
".\nÊtes-vous sûr(e) de vouloir poursuivre l'opération ?"
);
setRemovePopupOnConfirm(() => () => {
handleRemoveTeacher(teacher.id)
.then(data => {
.then((data) => {
logger.debug('Success:', data);
setPopupMessage("L'enseignant " + teacher.last_name + " " + teacher.first_name + " a été correctement supprimé");
setPopupMessage(
"L'enseignant " +
teacher.last_name +
' ' +
teacher.first_name +
' a été correctement supprimé'
);
setPopupVisible(true);
setRemovePopupVisible(false);
})
.catch(error => {
.catch((error) => {
logger.error('Error archiving data:', error);
setPopupMessage("Erreur lors de la suppression de l'enseignant " + teacher.last_name + " " + teacher.first_name);
setPopupMessage(
"Erreur lors de la suppression de l'enseignant " +
teacher.last_name +
' ' +
teacher.first_name
);
setPopupVisible(true);
setRemovePopupVisible(false);
});
@ -462,7 +572,7 @@ const TeachersSection = ({ teachers, setTeachers, specialities, profiles, handle
{ name: 'SPECIALITES', label: 'Spécialités' },
{ name: 'ADMINISTRATEUR', label: 'Profil' },
{ name: 'MISE A JOUR', label: 'Mise à jour' },
{ name: 'ACTIONS', label: 'Actions' }
{ name: 'ACTIONS', label: 'Actions' },
];
return (
@ -473,7 +583,11 @@ const TeachersSection = ({ teachers, setTeachers, specialities, profiles, handle
<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">
<button
type="button"
onClick={handleAddTeacher}
className="text-emerald-500 hover:text-emerald-700"
>
<Plus className="w-5 h-5" />
</button>
</div>

View File

@ -1,23 +1,37 @@
import React from 'react';
import Table from '@/components/Table';
const TeachersSelectionConfiguration = ({ formData, teachers, handleTeacherSelection, selectedTeachers }) => {
const TeachersSelectionConfiguration = ({
formData,
teachers,
handleTeacherSelection,
selectedTeachers,
}) => {
return (
<div className="mt-4" style={{ maxHeight: '300px', overflowY: 'auto' }}>
<label className="mt-6 block text-2xl font-medium text-gray-700 mb-2">Enseignants</label>
<label className={`block text-sm font-medium mb-4`}>Sélection : <span className={`${formData.teachers.length !== 0 ? 'text-emerald-400' : 'text-red-300'}`}>{formData.teachers.length}</span></label>
<label className="mt-6 block text-2xl font-medium text-gray-700 mb-2">
Enseignants
</label>
<label className={`block text-sm font-medium mb-4`}>
Sélection :{' '}
<span
className={`${formData.teachers.length !== 0 ? 'text-emerald-400' : 'text-red-300'}`}
>
{formData.teachers.length}
</span>
</label>
<Table
columns={[
{
name: 'Nom',
{
name: 'Nom',
transform: (row) => row.last_name,
},
{
name: 'Prénom',
{
name: 'Prénom',
transform: (row) => row.first_name,
},
// {
// name: 'Spécialités',
// {
// name: 'Spécialités',
// transform: (row) => (
// <div className="flex flex-wrap items-center">
// {row.specialites.map(specialite => (

View File

@ -5,7 +5,9 @@ const TimeRange = ({ startTime, endTime, onStartChange, onEndChange }) => {
<div className="mb-4">
<div className="flex space-x-4">
<div className="w-1/2">
<label className="block text-sm font-medium text-gray-700 mb-2">Heure de début</label>
<label className="block text-sm font-medium text-gray-700 mb-2">
Heure de début
</label>
<input
type="time"
name="startTime"
@ -15,7 +17,9 @@ const TimeRange = ({ startTime, endTime, onStartChange, onEndChange }) => {
/>
</div>
<div className="w-1/2">
<label className="block text-sm font-medium text-gray-700 mb-2">Heure de fin</label>
<label className="block text-sm font-medium text-gray-700 mb-2">
Heure de fin
</label>
<input
type="time"
name="endTime"

View File

@ -1,15 +1,29 @@
import React, { useState, useEffect } from 'react';
import ToggleSwitch from '@/components/ToggleSwitch'; // Import du composant ToggleSwitch
import { fetchRegistrationFileGroups, createRegistrationTemplates, cloneTemplate, generateToken } from '@/app/actions/registerFileGroupAction';
import {
fetchRegistrationFileGroups,
createRegistrationTemplates,
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 {
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({ handleCreateTemplateMaster, handleEditTemplateMaster, fileToEdit = null, onSuccess }) {
export default function FileUpload({
handleCreateTemplateMaster,
handleEditTemplateMaster,
fileToEdit = null,
onSuccess,
}) {
const [isRequired, setIsRequired] = useState(false); // État pour le toggle isRequired
const [order, setOrder] = useState(0);
const [groups, setGroups] = useState([]);
@ -24,7 +38,9 @@ export default function FileUpload({ handleCreateTemplateMaster, handleEditTempl
const { selectedEstablishmentId } = useEstablishment();
useEffect(() => {
fetchRegistrationFileGroups(selectedEstablishmentId).then(data => setGroups(data));
fetchRegistrationFileGroups(selectedEstablishmentId).then((data) =>
setGroups(data)
);
if (fileToEdit) {
setUploadedFileName(fileToEdit.name || '');
@ -40,7 +56,9 @@ export default function FileUpload({ handleCreateTemplateMaster, handleEditTempl
.then((data) => {
setToken(data.token);
})
.catch((error) => console.error('Erreur lors de la génération du token:', error));
.catch((error) =>
console.error('Erreur lors de la génération du token:', error)
);
}, [fileToEdit]);
const handleFileNameChange = (event) => {
@ -49,14 +67,14 @@ export default function FileUpload({ handleCreateTemplateMaster, handleEditTempl
const handleGroupChange = (selectedGroups) => {
setSelectedGroups(selectedGroups);
const details = selectedGroups.flatMap(group =>
group.registration_forms.flatMap(form =>
form.guardians.map(guardian => ({
const details = selectedGroups.flatMap((group) =>
group.registration_forms.flatMap((form) =>
form.guardians.map((guardian) => ({
email: guardian.associated_profile_email,
last_name: form.last_name,
first_name: form.first_name,
registration_form: form.student_id
registration_form: form.student_id,
}))
)
);
@ -65,9 +83,9 @@ export default function FileUpload({ handleCreateTemplateMaster, handleEditTempl
const handleLoad = (detail) => {
const templateId = detail?.id;
logger.debug('loading template id : ', detail)
logger.debug('loading template id : ', detail);
setTemplateMaster(detail);
}
};
const handleUpload = (detail) => {
logger.debug('Uploaded file detail:', detail);
@ -75,54 +93,57 @@ export default function FileUpload({ handleCreateTemplateMaster, handleEditTempl
};
const handleChange = (detail) => {
logger.debug(detail)
logger.debug(detail);
setUploadedFileName(detail.name);
}
};
const handleSubmit = (data) => {
const is_required = (data.fields.length > 0)
const is_required = data.fields.length > 0;
if (fileToEdit) {
logger.debug('Modification du template master:', templateMaster?.id);
handleEditTemplateMaster({
name: uploadedFileName,
group_ids: selectedGroups.map(group => group.id),
group_ids: selectedGroups.map((group) => group.id),
id: templateMaster?.id,
is_required: is_required
is_required: is_required,
});
} else {
logger.debug('Création du template master:', templateMaster?.id);
handleCreateTemplateMaster({
name: uploadedFileName,
group_ids: selectedGroups.map(group => group.id),
group_ids: selectedGroups.map((group) => group.id),
id: templateMaster?.id,
is_required: is_required
is_required: is_required,
});
guardianDetails.forEach((guardian, index) => {
logger.debug('creation du clone avec required : ', is_required)
logger.debug('creation du clone avec required : ', is_required);
cloneTemplate(templateMaster?.id, guardian.email, is_required)
.then(clonedDocument => {
.then((clonedDocument) => {
// Sauvegarde des templates clonés dans la base de données
const data = {
name: `${uploadedFileName}_${guardian.first_name}_${guardian.last_name}`,
slug: clonedDocument.slug,
id: clonedDocument.id,
master: templateMaster?.id,
registration_form: guardian.registration_form
registration_form: guardian.registration_form,
};
createRegistrationTemplates(data, csrfToken)
.then(response => {
.then((response) => {
logger.debug('Template enregistré avec succès:', response);
onSuccess();
})
.catch(error => {
logger.error('Erreur lors de l\'enregistrement du template:', error);
.catch((error) => {
logger.error(
"Erreur lors de l'enregistrement du template:",
error
);
});
// Logique pour envoyer chaque template au submitter
logger.debug('Sending template to:', guardian.email);
})
.catch(error => {
.catch((error) => {
logger.error('Error during cloning or sending:', error);
});
});
@ -144,10 +165,10 @@ export default function FileUpload({ handleCreateTemplateMaster, handleEditTempl
</div>
<div className="col-span-8">
{token && (
<DocusealBuilder
token={token}
<DocusealBuilder
token={token}
headers={{
'Authorization': `Bearer ${token}`
Authorization: `Bearer ${token}`,
}}
withSendButton={false}
withSaveButton={false}
@ -168,4 +189,4 @@ export default function FileUpload({ handleCreateTemplateMaster, handleEditTempl
</div>
</div>
);
}
}

View File

@ -1,5 +1,12 @@
import React, { useState, useEffect } from 'react';
import { Plus, Download, Edit3, Trash2, FolderPlus, Signature } from 'lucide-react';
import {
Plus,
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';
@ -13,12 +20,15 @@ import {
createRegistrationTemplateMaster,
editRegistrationTemplateMaster,
deleteRegistrationTemplateMaster,
fetchRegistrationTemplates
fetchRegistrationTemplates,
} from '@/app/actions/registerFileGroupAction';
import RegistrationFileGroupForm from '@/components/Structure/Files/RegistrationFileGroupForm';
import logger from '@/utils/logger';
export default function FilesGroupsManagement({ csrfToken, selectedEstablishmentId }) {
export default function FilesGroupsManagement({
csrfToken,
selectedEstablishmentId,
}) {
const [templateMasters, setTemplateMasters] = useState([]);
const [templates, setTemplates] = useState([]);
const [groups, setGroups] = useState([]);
@ -32,13 +42,19 @@ export default function FilesGroupsManagement({ csrfToken, selectedEstablishment
const handleReloadTemplates = () => {
setReloadTemplates(true);
}
};
const transformFileData = (file, groups) => {
const groupInfos = file.groups.map(groupId => groups.find(g => g.id === groupId) || { id: groupId, name: 'Groupe inconnu' });
const groupInfos = file.groups.map(
(groupId) =>
groups.find((g) => g.id === groupId) || {
id: groupId,
name: 'Groupe inconnu',
}
);
return {
...file,
groups: groupInfos
groups: groupInfos,
};
};
@ -47,58 +63,75 @@ export default function FilesGroupsManagement({ csrfToken, selectedEstablishment
Promise.all([
fetchRegistrationTemplateMaster(),
fetchRegistrationFileGroups(selectedEstablishmentId),
fetchRegistrationTemplates()
]).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);
}).catch(err => {
console.log(err.message);
}).finally(() => {
setReloadTemplates(false);
});
fetchRegistrationTemplates(),
])
.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);
})
.catch((err) => {
console.log(err.message);
})
.finally(() => {
setReloadTemplates(false);
});
}
}, [reloadTemplates, selectedEstablishmentId]);
const deleteTemplateMaster = (templateMaster) => {
// Supprimer les clones associés via l'API DocuSeal
const removeClonesPromises = templates
.filter(template => template.master === templateMaster.id)
.map(template => removeTemplate(template.id));
.filter((template) => template.master === templateMaster.id)
.map((template) => removeTemplate(template.id));
// Ajouter la suppression du master à la liste des promesses
removeClonesPromises.push(removeTemplate(templateMaster.id));
// Attendre que toutes les suppressions dans DocuSeal soient terminées
Promise.all(removeClonesPromises)
.then(responses => {
const allSuccessful = responses.every(response => response.ok);
.then((responses) => {
const allSuccessful = responses.every((response) => response.ok);
if (allSuccessful) {
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)
.then(response => {
.then((response) => {
if (response.ok) {
setTemplateMasters(templateMasters.filter(fichier => fichier.id !== templateMaster.id));
setTemplateMasters(
templateMasters.filter(
(fichier) => fichier.id !== templateMaster.id
)
);
alert('Fichier supprimé avec succès.');
} else {
alert('Erreur lors de la suppression du fichier dans la base de données.');
alert(
'Erreur lors de la suppression du fichier dans la base de données.'
);
}
})
.catch(error => {
.catch((error) => {
console.error('Error deleting file from database:', error);
alert('Erreur lors de la suppression du fichier dans la base de données.');
alert(
'Erreur lors de la suppression du fichier dans la base de données.'
);
});
} else {
alert('Erreur lors de la suppression du master ou des clones dans DocuSeal.');
alert(
'Erreur lors de la suppression du master ou des clones dans DocuSeal.'
);
}
})
.catch(error => {
.catch((error) => {
console.error('Error removing template from DocuSeal:', error);
alert('Erreur lors de la suppression du master ou des clones dans DocuSeal.');
alert(
'Erreur lors de la suppression du master ou des clones dans DocuSeal.'
);
});
};
@ -107,22 +140,24 @@ export default function FilesGroupsManagement({ csrfToken, selectedEstablishment
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrfToken
'X-CSRFToken': csrfToken,
},
body: JSON.stringify({
templateId
templateId,
}),
})
.then((response) => {
if (!response.ok) {
return response.json().then((err) => {
throw new Error(err.message);
});
}
return response;
})
})
.then(response => {
if (!response.ok) {
return response.json().then(err => { throw new Error(err.message); });
}
return response;
})
.catch(error => {
console.error('Error removing template:', error);
throw error;
});
.catch((error) => {
console.error('Error removing template:', error);
throw error;
});
};
const editTemplateMaster = (file) => {
@ -131,72 +166,76 @@ export default function FilesGroupsManagement({ csrfToken, selectedEstablishment
setIsModalOpen(true);
};
const handleCreateTemplateMaster = ({name, group_ids, id, is_required}) => {
const handleCreateTemplateMaster = ({ name, group_ids, id, is_required }) => {
const data = {
name: name,
id: id,
groups: group_ids,
is_required: is_required
is_required: is_required,
};
logger.debug(data);
createRegistrationTemplateMaster(data, csrfToken)
.then(data => {
// Transformer le nouveau fichier avec les informations du groupe
const transformedFile = transformFileData(data, groups);
setTemplateMasters(prevFiles => [...prevFiles, transformedFile]);
setIsModalOpen(false);
})
.catch(error => {
console.error('Error uploading file:', error);
});
.then((data) => {
// Transformer le nouveau fichier avec les informations du groupe
const transformedFile = transformFileData(data, groups);
setTemplateMasters((prevFiles) => [...prevFiles, transformedFile]);
setIsModalOpen(false);
})
.catch((error) => {
console.error('Error uploading file:', error);
});
};
const handleEditTemplateMaster = ({name, group_ids, id, is_required}) => {
const handleEditTemplateMaster = ({ name, group_ids, id, is_required }) => {
const data = {
name: name,
id: id,
groups: group_ids,
is_required: is_required
is_required: is_required,
};
logger.debug(data);
editRegistrationTemplateMaster(id, data, csrfToken)
.then(data => {
// Transformer le fichier mis à jour avec les informations du groupe
const transformedFile = transformFileData(data, groups);
setTemplateMasters(prevFichiers =>
prevFichiers.map(f => f.id === id ? transformedFile : f)
);
setIsModalOpen(false);
})
.catch(error => {
console.error('Error editing file:', error);
alert('Erreur lors de la modification du fichier');
});
.then((data) => {
// Transformer le fichier mis à jour avec les informations du groupe
const transformedFile = transformFileData(data, groups);
setTemplateMasters((prevFichiers) =>
prevFichiers.map((f) => (f.id === id ? transformedFile : f))
);
setIsModalOpen(false);
})
.catch((error) => {
console.error('Error editing file:', error);
alert('Erreur lors de la modification du fichier');
});
};
const handleGroupSubmit = (groupData) => {
if (groupToEdit) {
editRegistrationFileGroup(groupToEdit.id, groupData, csrfToken)
.then(updatedGroup => {
setGroups(groups.map(group => group.id === groupToEdit.id ? updatedGroup : group));
.then((updatedGroup) => {
setGroups(
groups.map((group) =>
group.id === groupToEdit.id ? updatedGroup : group
)
);
setGroupToEdit(null);
setIsGroupModalOpen(false);
})
.catch(error => {
.catch((error) => {
console.error('Error handling group:', error);
alert('Erreur lors de l\'opération sur le groupe');
alert("Erreur lors de l'opération sur le groupe");
});
} else {
createRegistrationFileGroup(groupData, csrfToken)
.then(newGroup => {
.then((newGroup) => {
setGroups([...groups, newGroup]);
setIsGroupModalOpen(false);
})
.catch(error => {
.catch((error) => {
console.error('Error handling group:', error);
alert('Erreur lors de l\'opération sur le groupe');
alert("Erreur lors de l'opération sur le groupe");
});
}
};
@ -208,9 +247,13 @@ export default function FilesGroupsManagement({ csrfToken, selectedEstablishment
const handleGroupDelete = (groupId) => {
// Vérifier si des templateMasters utilisent ce groupe
const filesInGroup = templateMasters.filter(file => file.group && file.group.id === groupId);
const filesInGroup = templateMasters.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.');
alert(
"Impossible de supprimer ce groupe car il contient des templateMasters. Veuillez d'abord retirer tous les templateMasters de ce groupe."
);
return;
}
@ -223,54 +266,88 @@ export default function FilesGroupsManagement({ csrfToken, selectedEstablishment
if (!response.ok) {
throw new Error('Erreur lors de la suppression du groupe.');
}
setGroups(groups.filter(group => group.id !== groupId));
setGroups(groups.filter((group) => group.id !== groupId));
alert('Groupe supprimé avec succès.');
})
.catch(error => {
.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.');
alert(
error.message ||
"Erreur lors de la suppression du groupe. Vérifiez qu'aucune inscription n'utilise ce groupe."
);
});
}
};
const filteredFiles = templateMasters.filter(file => {
const filteredFiles = templateMasters.filter((file) => {
if (!selectedGroup) return true;
return file.groups && file.groups.some(group => 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: '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 && (
<a href={`${BASE_URL}${row.file}`} target='_blank' className="text-blue-500 hover:text-blue-700">
<Download className="w-5 h-5" />
</a>
)}
<button onClick={() => editTemplateMaster(row)} className="text-blue-500 hover:text-blue-700">
<Edit3 className="w-5 h-5" />
</button>
<button onClick={() => deleteTemplateMaster(row)} className="text-red-500 hover:text-red-700">
<Trash2 className="w-5 h-5" />
</button>
</div>
)}
{
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 && (
<a
href={`${BASE_URL}${row.file}`}
target="_blank"
className="text-blue-500 hover:text-blue-700"
>
<Download className="w-5 h-5" />
</a>
)}
<button
onClick={() => editTemplateMaster(row)}
className="text-blue-500 hover:text-blue-700"
>
<Edit3 className="w-5 h-5" />
</button>
<button
onClick={() => deleteTemplateMaster(row)}
className="text-red-500 hover:text-red-700"
>
<Trash2 className="w-5 h-5" />
</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">
<Edit3 className="w-5 h-5" />
</button>
<button onClick={() => handleGroupDelete(row.id)} className="text-red-500 hover:text-red-700">
<Trash2 className="w-5 h-5" />
</button>
</div>
)}
{
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"
>
<Edit3 className="w-5 h-5" />
</button>
<button
onClick={() => handleGroupDelete(row.id)}
className="text-red-500 hover:text-red-700"
>
<Trash2 className="w-5 h-5" />
</button>
</div>
),
},
];
return (
@ -292,12 +369,16 @@ export default function FilesGroupsManagement({ csrfToken, selectedEstablishment
onSuccess={handleReloadTemplates}
/>
)}
modalClassName='w-4/5 h-4/5'
modalClassName="w-4/5 h-4/5"
/>
<Modal
isOpen={isGroupModalOpen}
setIsOpen={setIsGroupModalOpen}
title={groupToEdit ? "Modifier le groupe" : "Ajouter un groupe de templateMasters"}
title={
groupToEdit
? 'Modifier le groupe'
: 'Ajouter un groupe de templateMasters'
}
ContentComponent={() => (
<RegistrationFileGroupForm
onSubmit={handleGroupSubmit}
@ -334,12 +415,17 @@ export default function FilesGroupsManagement({ csrfToken, selectedEstablishment
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>
{groups.map((group) => (
<option key={group.id} value={group.id}>
{group.name}
</option>
))}
</select>
<button
onClick={() => { setIsModalOpen(true); setIsEditing(false); }}
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" />

View File

@ -53,4 +53,4 @@ export default function RegistrationFileGroupForm({ onSubmit, initialData }) {
</div>
</form>
);
}
}

View File

@ -8,17 +8,19 @@ export default function RegistrationFileGroupList() {
const { selectedEstablishmentId } = useEstablishment();
useEffect(() => {
fetchRegistrationFileGroups(selectedEstablishmentId).then(data => setGroups(data));
fetchRegistrationFileGroups(selectedEstablishmentId).then((data) =>
setGroups(data)
);
}, []);
return (
<div>
<h2>Groupes de fichiers d&apos;inscription</h2>
<ul>
{groups.map(group => (
{groups.map((group) => (
<li key={group.id}>{group.name}</li>
))}
</ul>
</div>
);
}
}

View File

@ -7,17 +7,27 @@ const ClassesInformation = ({ selectedClass, isPastYear }) => {
}
return (
<div className={`w-full p-6 shadow-lg rounded-full border relative ${isPastYear ? 'bg-gray-200 border-gray-600' : 'bg-emerald-200 border-emerald-500'}`}>
<div className={`border-b pb-4 ${isPastYear ? 'border-gray-600' : 'border-emerald-500'}`}>
<p className="text-gray-700 text-center"><strong>{selectedClass.age_range} ans</strong></p>
<div
className={`w-full p-6 shadow-lg rounded-full border relative ${isPastYear ? 'bg-gray-200 border-gray-600' : 'bg-emerald-200 border-emerald-500'}`}
>
<div
className={`border-b pb-4 ${isPastYear ? 'border-gray-600' : 'border-emerald-500'}`}
>
<p className="text-gray-700 text-center">
<strong>{selectedClass.age_range} ans</strong>
</p>
</div>
<div className={`border-b pb-4 ${isPastYear ? 'border-gray-600' : 'border-emerald-500'}`}>
<div
className={`border-b pb-4 ${isPastYear ? 'border-gray-600' : 'border-emerald-500'}`}
>
<div className="flex flex-wrap justify-center space-x-4">
{selectedClass.teachers.map((teacher) => (
<div key={teacher.id} className="relative group mt-4">
<TeacherLabel nom={teacher.nom} prenom={teacher.prenom} />
<div className="absolute left-1/2 transform -translate-x-1/2 bottom-full mb-2 w-max px-4 py-2 text-white bg-gray-800 rounded-lg opacity-0 group-hover:opacity-100 transition-opacity duration-300">
<p className="text-sm">{teacher.nom} {teacher.prenom}</p>
<p className="text-sm">
{teacher.nom} {teacher.prenom}
</p>
</div>
</div>
))}

View File

@ -5,17 +5,21 @@ import logger from '@/utils/logger';
const ClassesList = ({ classes, onClassSelect, selectedClassId }) => {
const currentYear = new Date().getFullYear();
const currentMonth = new Date().getMonth();
const currentSchoolYearStart = currentMonth >= 8 ? currentYear : currentYear - 1;
const currentSchoolYearStart =
currentMonth >= 8 ? currentYear : currentYear - 1;
const handleClassClick = (classe) => {
logger.debug(`Classe sélectionnée: ${classe.atmosphere_name}, Année scolaire: ${classe.school_year}`);
logger.debug(
`Classe sélectionnée: ${classe.atmosphere_name}, Année scolaire: ${classe.school_year}`
);
onClassSelect(classe);
};
const categorizedClasses = classes.reduce((acc, classe) => {
const { school_year } = classe;
const [startYear] = school_year.split('-').map(Number);
const category = startYear >= currentSchoolYearStart ? 'Actives' : 'Anciennes';
const category =
startYear >= currentSchoolYearStart ? 'Actives' : 'Anciennes';
if (!acc[category]) {
acc[category] = [];
@ -45,8 +49,12 @@ const ClassesList = ({ classes, onClassSelect, selectedClassId }) => {
onClick={() => handleClassClick(classe)}
style={{ maxWidth: '400px' }}
>
<div className="flex-1 text-sm font-medium">{classe.atmosphere_name}</div>
<div className="flex-1 text-sm font-medium">{classe.school_year}</div>
<div className="flex-1 text-sm font-medium">
{classe.atmosphere_name}
</div>
<div className="flex-1 text-sm font-medium">
{classe.school_year}
</div>
</div>
))}
</div>
@ -63,8 +71,12 @@ const ClassesList = ({ classes, onClassSelect, selectedClassId }) => {
onClick={() => handleClassClick(classe)}
style={{ maxWidth: '400px' }}
>
<div className="flex-1 text-sm font-medium">{classe.atmosphere_name}</div>
<div className="flex-1 text-sm font-medium">{classe.school_year}</div>
<div className="flex-1 text-sm font-medium">
{classe.atmosphere_name}
</div>
<div className="flex-1 text-sm font-medium">
{classe.school_year}
</div>
</div>
))}
</div>

View File

@ -5,11 +5,11 @@ import { UserIcon } from 'lucide-react'; // Assure-toi d'importer l'icône que t
const DraggableSpeciality = ({ speciality }) => {
const [{ isDragging }, drag] = useDrag(() => ({
type: 'SPECIALITY',
item: {
id: speciality.id,
name: speciality.nom,
color: speciality.codeCouleur,
teachers: speciality.teachers
item: {
id: speciality.id,
name: speciality.nom,
color: speciality.codeCouleur,
teachers: speciality.teachers,
},
collect: (monitor) => ({
isDragging: !!monitor.isDragging(),
@ -21,7 +21,11 @@ const DraggableSpeciality = ({ speciality }) => {
ref={drag}
key={speciality.id}
className={`relative flex items-center px-4 py-2 rounded-full font-bold text-white text-center shadow-lg cursor-pointer transition-transform duration-200 ease-in-out transform ${isDragging ? 'opacity-50 scale-95' : 'scale-100 hover:scale-105 hover:shadow-xl'}`}
style={{ backgroundColor: speciality.codeCouleur, minWidth: '200px', maxWidth: '400px' }}
style={{
backgroundColor: speciality.codeCouleur,
minWidth: '200px',
maxWidth: '400px',
}}
title={speciality.nom}
>
{speciality.nom}

View File

@ -4,28 +4,33 @@ import PropTypes from 'prop-types';
// Définition du composant DropTargetCell
const DropTargetCell = ({ day, hour, courses, onDrop, onClick }) => {
const [{ isOver, canDrop }, drop] = useDrop(() => ({
accept: 'SPECIALITY',
drop: (item) => onDrop(item, hour, day),
collect: (monitor) => ({
isOver: monitor.isOver(),
canDrop: monitor.canDrop(),
const [{ isOver, canDrop }, drop] = useDrop(
() => ({
accept: 'SPECIALITY',
drop: (item) => onDrop(item, hour, day),
collect: (monitor) => ({
isOver: monitor.isOver(),
canDrop: monitor.canDrop(),
}),
}),
}), [hour, day]);
[hour, day]
);
const isColorDark = (color) => {
if (!color) return false;
const r = parseInt(color.slice(1, 3), 16);
const g = parseInt(color.slice(3, 5), 16);
const b = parseInt(color.slice(5, 7), 16);
return (r * 0.299 + g * 0.587 + b * 0.114) < 150;
return r * 0.299 + g * 0.587 + b * 0.114 < 150;
};
const isToday = (someDate) => {
const today = new Date();
return someDate.getDate() === today.getDate() &&
someDate.getMonth() === today.getMonth() &&
someDate.getFullYear() === today.getFullYear();
return (
someDate.getDate() === today.getDate() &&
someDate.getMonth() === today.getMonth() &&
someDate.getFullYear() === today.getFullYear()
);
};
// Vérifie si c'est une heure pleine
@ -38,19 +43,32 @@ const DropTargetCell = ({ day, hour, courses, onDrop, onClick }) => {
${isToday(new Date(day)) ? 'bg-emerald-100/50 border-x border-emerald-600' : ''}
hover:bg-emerald-100 h-10 border-b
${isFullHour ? 'border-emerald-200' : 'border-gray-300'}
${isOver && canDrop ? 'bg-emerald-200' : ''}`} // Ajouté pour indiquer le drop
style={{ display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'column',
width: '100%' }}
${isOver && canDrop ? 'bg-emerald-200' : ''}`} // Ajouté pour indiquer le drop
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'column',
width: '100%',
}}
>
{courses.map(course => (
<div key={course.matiere}
className="flex flex-row items-center justify-center gap-2"
style={{ backgroundColor: course.color, color: isColorDark(course.color) ? '#E5E5E5' : '#333333', width: '100%', height: '100%' }}>
<div style={{ textAlign: 'center', fontWeight: 'bold' }}>{course.matiere}</div>
<div style={{ fontStyle: 'italic', textAlign: 'center' }}>{course.teachers.join(', ')}</div>
{courses.map((course) => (
<div
key={course.matiere}
className="flex flex-row items-center justify-center gap-2"
style={{
backgroundColor: course.color,
color: isColorDark(course.color) ? '#E5E5E5' : '#333333',
width: '100%',
height: '100%',
}}
>
<div style={{ textAlign: 'center', fontWeight: 'bold' }}>
{course.matiere}
</div>
<div style={{ fontStyle: 'italic', textAlign: 'center' }}>
{course.teachers.join(', ')}
</div>
</div>
))}
</div>

View File

@ -8,11 +8,21 @@ import { useClasses } from '@/context/ClassesContext';
import { Calendar } from 'lucide-react';
import SpecialityEventModal from '@/components/Structure/Planning/SpecialityEventModal'; // Assurez-vous du bon chemin d'importation
const PlanningClassView = ({ schedule, onDrop, selectedLevel, handleUpdatePlanning, classe }) => {
const PlanningClassView = ({
schedule,
onDrop,
selectedLevel,
handleUpdatePlanning,
classe,
}) => {
const { formData } = useClasseForm();
const { determineInitialPeriod } = useClasses();
const [currentPeriod, setCurrentPeriod] = useState(schedule?.emploiDuTemps ? determineInitialPeriod(schedule.emploiDuTemps) : null);
const [currentPeriod, setCurrentPeriod] = useState(
schedule?.emploiDuTemps
? determineInitialPeriod(schedule.emploiDuTemps)
: null
);
const [isModalOpen, setIsModalOpen] = useState(false);
const [selectedCell, setSelectedCell] = useState(null);
const [existingEvent, setExistingEvent] = useState(null);
@ -36,36 +46,53 @@ const PlanningClassView = ({ schedule, onDrop, selectedLevel, handleUpdatePlanni
);
}
const emploiDuTemps = schedule.emploiDuTemps[currentPeriod] || schedule.emploiDuTemps;
const emploiDuTemps =
schedule.emploiDuTemps[currentPeriod] || schedule.emploiDuTemps;
const joursOuverture = Object.keys(emploiDuTemps);
const currentWeekDays = joursOuverture
.map(day => {
switch(day.toLowerCase()) {
case 'lundi': return 1;
case 'mardi': return 2;
case 'mercredi': return 3;
case 'jeudi': return 4;
case 'vendredi': return 5;
case 'samedi': return 6;
case 'dimanche': return 7;
default: return 0;
}
.map((day) => {
switch (day.toLowerCase()) {
case 'lundi':
return 1;
case 'mardi':
return 2;
case 'mercredi':
return 3;
case 'jeudi':
return 4;
case 'vendredi':
return 5;
case 'samedi':
return 6;
case 'dimanche':
return 7;
default:
return 0;
}
})
.sort((a, b) => a - b) // Trier les jours dans l'ordre croissant
.map(day => addDays(startOfWeek(new Date(), { weekStartsOn: 1 }), day - 1)); // Calculer les dates à partir du lundi
.map((day) =>
addDays(startOfWeek(new Date(), { weekStartsOn: 1 }), day - 1)
); // Calculer les dates à partir du lundi
const getFilteredEvents = (day, time, level) => {
const [hour, minute] = time.split(':').map(Number);
const startTime = hour + minute / 60; // Convertir l'heure en fraction d'heure
return emploiDuTemps[day.toLowerCase()]?.filter(event => {
return (
emploiDuTemps[day.toLowerCase()]?.filter((event) => {
const [eventHour, eventMinute] = event.heure.split(':').map(Number);
const eventStartTime = eventHour + eventMinute / 60;
const eventEndTime = eventStartTime + parseFloat(event.duree);
// Filtrer en fonction du selectedLevel
return schedule.niveau === level && startTime >= eventStartTime && startTime < eventEndTime;
}) || [];
return (
schedule.niveau === level &&
startTime >= eventStartTime &&
startTime < eventEndTime
);
}) || []
);
};
const handleCellClick = (hour, day) => {
@ -78,10 +105,14 @@ const PlanningClassView = ({ schedule, onDrop, selectedLevel, handleUpdatePlanni
const renderTimeSlots = () => {
const timeSlots = [];
for (let hour = parseInt(formData.time_range[0], 10); hour <= parseInt(formData.time_range[1], 10); hour++) {
for (
let hour = parseInt(formData.time_range[0], 10);
hour <= parseInt(formData.time_range[1], 10);
hour++
) {
const hourString = hour.toString().padStart(2, '0');
timeSlots.push(
<React.Fragment key={`${hourString}:00-${Math.random()}`}>
<div className="h-20 p-1 text-right text-sm text-gray-500 bg-gray-100 font-medium">
@ -95,14 +126,22 @@ const PlanningClassView = ({ schedule, onDrop, selectedLevel, handleUpdatePlanni
<DropTargetCell
hour={`${hourString}:00`}
day={day}
courses={getFilteredEvents(day, `${hourString}:00`, selectedLevel)}
courses={getFilteredEvents(
day,
`${hourString}:00`,
selectedLevel
)}
onDrop={onDrop}
onClick={(hour, day) => handleCellClick(hour, day)}
/>
<DropTargetCell
hour={`${hourString}:30`}
day={day}
courses={getFilteredEvents(day, `${hourString}:30`, selectedLevel)}
courses={getFilteredEvents(
day,
`${hourString}:30`,
selectedLevel
)}
onDrop={onDrop}
onClick={(hour, day) => handleCellClick(hour, day)}
/>
@ -124,46 +163,77 @@ const PlanningClassView = ({ schedule, onDrop, selectedLevel, handleUpdatePlanni
</h2>
{schedule.emploiDuTemps.S1 && schedule.emploiDuTemps.S2 && (
<div>
<button onClick={() => setCurrentPeriod('S1')} className={`px-4 py-2 ${currentPeriod === 'S1' ? 'bg-emerald-600 text-white' : 'bg-gray-200 text-gray-800'}`}>
<button
onClick={() => setCurrentPeriod('S1')}
className={`px-4 py-2 ${currentPeriod === 'S1' ? 'bg-emerald-600 text-white' : 'bg-gray-200 text-gray-800'}`}
>
Semestre 1
</button>
<button onClick={() => setCurrentPeriod('S2')} className={`px-4 py-2 ${currentPeriod === 'S2' ? 'bg-emerald-600 text-white' : 'bg-gray-200 text-gray-800'}`}>
<button
onClick={() => setCurrentPeriod('S2')}
className={`px-4 py-2 ${currentPeriod === 'S2' ? 'bg-emerald-600 text-white' : 'bg-gray-200 text-gray-800'}`}
>
Semestre 2
</button>
</div>
)}
{schedule.emploiDuTemps.T1 && schedule.emploiDuTemps.T2 && schedule.emploiDuTemps.T3 && (
<div>
<button onClick={() => setCurrentPeriod('T1')} className={`px-4 py-2 ${currentPeriod === 'T1' ? 'bg-emerald-600 text-white' : 'bg-gray-200 text-gray-800'}`}>
Trimestre 1
</button>
<button onClick={() => setCurrentPeriod('T2')} className={`px-4 py-2 ${currentPeriod === 'T2' ? 'bg-emerald-600 text-white' : 'bg-gray-200 text-gray-800'}`}>
Trimestre 2
</button>
<button onClick={() => setCurrentPeriod('T3')} className={`px-4 py-2 ${currentPeriod === 'T3' ? 'bg-emerald-600 text-white' : 'bg-gray-200 text-gray-800'}`}>
Trimestre 3
</button>
</div>
)}
{schedule.emploiDuTemps.T1 &&
schedule.emploiDuTemps.T2 &&
schedule.emploiDuTemps.T3 && (
<div>
<button
onClick={() => setCurrentPeriod('T1')}
className={`px-4 py-2 ${currentPeriod === 'T1' ? 'bg-emerald-600 text-white' : 'bg-gray-200 text-gray-800'}`}
>
Trimestre 1
</button>
<button
onClick={() => setCurrentPeriod('T2')}
className={`px-4 py-2 ${currentPeriod === 'T2' ? 'bg-emerald-600 text-white' : 'bg-gray-200 text-gray-800'}`}
>
Trimestre 2
</button>
<button
onClick={() => setCurrentPeriod('T3')}
className={`px-4 py-2 ${currentPeriod === 'T3' ? 'bg-emerald-600 text-white' : 'bg-gray-200 text-gray-800'}`}
>
Trimestre 3
</button>
</div>
)}
</div>
<div className="flex-1 max-h-[calc(100vh-192px)] overflow-hidden">
{/* En-tête des jours */}
<div className="grid w-full" style={{ gridTemplateColumns: `2.5rem repeat(${currentWeekDays.length}, 1fr)` }}>
<div
className="grid w-full"
style={{
gridTemplateColumns: `2.5rem repeat(${currentWeekDays.length}, 1fr)`,
}}
>
<div className="bg-gray-50 h-14"></div>
{currentWeekDays.map((date, index) => (
<div
key={`${date}-${index}`}
className="p-3 text-center bg-emerald-100 text-emerald-800 border-r border-emerald-200">
className="p-3 text-center bg-emerald-100 text-emerald-800 border-r border-emerald-200"
>
<div className="text font-semibold">
{format(date, 'EEEE', { locale: fr })}
</div>
</div>
))}
</div>
{/* Contenu du planning */}
<div className="flex-1 overflow-y-auto relative" style={{ maxHeight: 'calc(100vh - 300px)' }}>
<div className="grid bg-white relative" style={{ gridTemplateColumns: `2.5rem repeat(${currentWeekDays.length}, 1fr)` }}>
<div
className="flex-1 overflow-y-auto relative"
style={{ maxHeight: 'calc(100vh - 300px)' }}
>
<div
className="grid bg-white relative"
style={{
gridTemplateColumns: `2.5rem repeat(${currentWeekDays.length}, 1fr)`,
}}
>
{renderTimeSlots()}
</div>
</div>

View File

@ -1,4 +1,4 @@
'use client'
'use client';
import React, { useState, useEffect } from 'react';
import { HTML5Backend } from 'react-dnd-html5-backend';
@ -16,14 +16,17 @@ import logger from '@/utils/logger';
const ScheduleManagement = ({ handleUpdatePlanning, classes }) => {
const currentYear = new Date().getFullYear();
const currentMonth = new Date().getMonth();
const currentSchoolYearStart = currentMonth >= 8 ? currentYear : currentYear - 1;
const currentSchoolYearStart =
currentMonth >= 8 ? currentYear : currentYear - 1;
const [selectedClass, setSelectedClass] = useState(null);
const [selectedLevel, setSelectedLevel] = useState('');
const [schedule, setSchedule] = useState(null);
const { getNiveauxTabs } = useClasses();
const niveauxLabels = Array.isArray(selectedClass?.levels) ? getNiveauxTabs(selectedClass.levels) : [];
const niveauxLabels = Array.isArray(selectedClass?.levels)
? getNiveauxTabs(selectedClass.levels)
: [];
const [isModalOpen, setIsModalOpen] = useState(false);
const handleOpenModal = () => setIsModalOpen(true);
@ -36,14 +39,18 @@ const ScheduleManagement = ({ handleUpdatePlanning, classes }) => {
setSelectedLevel(niveau);
const currentPlanning = selectedClass.plannings_read?.find(planning => planning.niveau === niveau);
const currentPlanning = selectedClass.plannings_read?.find(
(planning) => planning.niveau === niveau
);
setSchedule(currentPlanning ? currentPlanning.planning : {});
}
}, [selectedClass, niveauxLabels]);
useEffect(() => {
if (selectedClass && selectedLevel) {
const currentPlanning = selectedClass.plannings_read?.find(planning => planning.niveau === selectedLevel);
const currentPlanning = selectedClass.plannings_read?.find(
(planning) => planning.niveau === selectedLevel
);
setSchedule(currentPlanning ? currentPlanning.planning : {});
}
}, [selectedClass, selectedLevel]);
@ -53,21 +60,28 @@ const ScheduleManagement = ({ handleUpdatePlanning, classes }) => {
};
const handleClassSelect = (classId) => {
const selectedClasse = categorizedClasses['Actives'].find(classe => classe.id === classId);
const selectedClasse = categorizedClasses['Actives'].find(
(classe) => classe.id === classId
);
setSelectedClass(selectedClasse);
setSelectedLevel('');
};
const onDrop = (item, hour, day) => {
const { id, name, color, teachers } = item;
const newSchedule = { ...schedule, emploiDuTemps: schedule.emploiDuTemps || {} };
const newSchedule = {
...schedule,
emploiDuTemps: schedule.emploiDuTemps || {},
};
if (!newSchedule.emploiDuTemps[day]) {
newSchedule.emploiDuTemps[day] = [];
}
const courseTime = `${hour.toString().padStart(2, '0')}:00`;
const existingCourseIndex = newSchedule.emploiDuTemps[day].findIndex(course => course.heure === courseTime);
const existingCourseIndex = newSchedule.emploiDuTemps[day].findIndex(
(course) => course.heure === courseTime
);
const newCourse = {
duree: '1',
@ -84,12 +98,14 @@ const ScheduleManagement = ({ handleUpdatePlanning, classes }) => {
}
// Mettre à jour scheduleRef
setSchedule(newSchedule)
setSchedule(newSchedule);
// Utiliser `handleUpdatePlanning` pour mettre à jour le planning du niveau de la classe
const planningId = selectedClass.plannings_read.find(planning => planning.niveau === selectedLevel)?.planning.id;
const planningId = selectedClass.plannings_read.find(
(planning) => planning.niveau === selectedLevel
)?.planning.id;
if (planningId) {
logger.debug('newSchedule : ', newSchedule)
logger.debug('newSchedule : ', newSchedule);
handleUpdatePlanning(BE_SCHOOL_PLANNINGS_URL, planningId, newSchedule);
}
};
@ -97,7 +113,8 @@ const ScheduleManagement = ({ handleUpdatePlanning, classes }) => {
const categorizedClasses = classes.reduce((acc, classe) => {
const { school_year } = classe;
const [startYear] = school_year.split('-').map(Number);
const category = startYear >= currentSchoolYearStart ? 'Actives' : 'Anciennes';
const category =
startYear >= currentSchoolYearStart ? 'Actives' : 'Anciennes';
if (!acc[category]) {
acc[category] = [];
@ -111,7 +128,6 @@ const ScheduleManagement = ({ handleUpdatePlanning, classes }) => {
<DndProvider backend={HTML5Backend}>
<div className="p-4 bg-gray-100 border-b">
<div className="grid grid-cols-3 gap-4">
{/* Colonne Classes */}
<div className="p-4 bg-gray-50 rounded-lg shadow-inner">
<div className="flex justify-between items-center mb-4">
@ -120,17 +136,17 @@ const ScheduleManagement = ({ handleUpdatePlanning, classes }) => {
Classes
</h2>
</div>
{categorizedClasses['Actives'] &&
{categorizedClasses['Actives'] && (
<TabsStructure
activeTab={selectedClass?.id}
setActiveTab={handleClassSelect}
tabs={categorizedClasses['Actives'].map(classe => ({
tabs={categorizedClasses['Actives'].map((classe) => ({
id: classe.id,
title: classe.atmosphere_name,
icon: Users
icon: Users,
}))}
/>
}
)}
</div>
{/* Colonne Niveaux */}
@ -141,9 +157,13 @@ const ScheduleManagement = ({ handleUpdatePlanning, classes }) => {
Niveaux
</h2>
</div>
{niveauxLabels &&
<TabsStructure activeTab={selectedLevel} setActiveTab={handleLevelSelect} tabs={niveauxLabels} />
}
{niveauxLabels && (
<TabsStructure
activeTab={selectedLevel}
setActiveTab={handleLevelSelect}
tabs={niveauxLabels}
/>
)}
</div>
{/* Colonne Spécialités */}
@ -154,9 +174,10 @@ const ScheduleManagement = ({ handleUpdatePlanning, classes }) => {
Spécialités
</h2>
</div>
<SpecialitiesList teachers={selectedClass ? selectedClass.teachers : []} />
<SpecialitiesList
teachers={selectedClass ? selectedClass.teachers : []}
/>
</div>
</div>
</div>
@ -171,7 +192,13 @@ const ScheduleManagement = ({ handleUpdatePlanning, classes }) => {
className="flex-1 relative"
>
<ClasseFormProvider initialClasse={selectedClass || {}}>
<PlanningClassView schedule={schedule} onDrop={onDrop} selectedLevel={selectedLevel} handleUpdatePlanning={handleUpdatePlanning} classe={selectedClass} />
<PlanningClassView
schedule={schedule}
onDrop={onDrop}
selectedLevel={selectedLevel}
handleUpdatePlanning={handleUpdatePlanning}
classe={selectedClass}
/>
</ClasseFormProvider>
</motion.div>
</AnimatePresence>
@ -179,7 +206,6 @@ const ScheduleManagement = ({ handleUpdatePlanning, classes }) => {
</DndProvider>
</div>
);
};
export default ScheduleManagement;

View File

@ -17,5 +17,3 @@ const SpecialitiesList = ({ teachers }) => {
};
export default SpecialitiesList;

View File

@ -6,7 +6,14 @@ import { BE_SCHOOL_PLANNINGS_URL } from '@/utils/Url';
import { BookOpen, Users } from 'lucide-react';
import logger from '@/utils/logger';
const SpecialityEventModal = ({ isOpen, onClose, selectedCell, existingEvent, handleUpdatePlanning, classe }) => {
const SpecialityEventModal = ({
isOpen,
onClose,
selectedCell,
existingEvent,
handleUpdatePlanning,
classe,
}) => {
const { formData, setFormData } = useClasseForm();
const { groupSpecialitiesBySubject } = useClasses();
const [selectedSpeciality, setSelectedSpeciality] = useState('');
@ -15,7 +22,7 @@ const SpecialityEventModal = ({ isOpen, onClose, selectedCell, existingEvent, ha
specialiteId: '',
teacherId: '',
start: '',
duration: '1h'
duration: '1h',
});
useEffect(() => {
@ -25,7 +32,7 @@ const SpecialityEventModal = ({ isOpen, onClose, selectedCell, existingEvent, ha
specialiteId: '',
teacherId: '',
start: '',
duration: '1h'
duration: '1h',
});
setSelectedSpeciality('');
setSelectedTeacher('');
@ -42,10 +49,10 @@ const SpecialityEventModal = ({ isOpen, onClose, selectedCell, existingEvent, ha
setSelectedTeacher(existingEvent.teacherId);
} else {
// Mode création
setEventData(prev => ({
setEventData((prev) => ({
...prev,
start: selectedCell.hour,
duration: '1h'
duration: '1h',
}));
setSelectedSpeciality('');
setSelectedTeacher('');
@ -69,17 +76,21 @@ const SpecialityEventModal = ({ isOpen, onClose, selectedCell, existingEvent, ha
}
// Transformer eventData pour correspondre au format du planning
const selectedTeacherData = formData.teachers.find(teacher => teacher.id === parseInt(eventData.teacherId, 10));
const selectedTeacherData = formData.teachers.find(
(teacher) => teacher.id === parseInt(eventData.teacherId, 10)
);
const newCourse = {
color: '#FF0000', // Vous pouvez définir la couleur de manière dynamique si nécessaire
teachers: selectedTeacherData ? [`${selectedTeacherData.nom} ${selectedTeacherData.prenom}`] : [],
teachers: selectedTeacherData
? [`${selectedTeacherData.nom} ${selectedTeacherData.prenom}`]
: [],
heure: `${eventData.start}:00`,
duree: eventData.duration.replace('h', ''), // Supposons que '1h' signifie 1
matiere: 'GROUPE'
matiere: 'GROUPE',
};
// Mettre à jour le planning
const updatedPlannings = classe.plannings_read.map(planning => {
const updatedPlannings = classe.plannings_read.map((planning) => {
if (planning.niveau === selectedCell.selectedLevel) {
const newEmploiDuTemps = { ...planning.emploiDuTemps };
@ -88,7 +99,9 @@ const SpecialityEventModal = ({ isOpen, onClose, selectedCell, existingEvent, ha
}
const courseTime = newCourse.heure;
const existingCourseIndex = newEmploiDuTemps[selectedCell.day].findIndex(course => course.heure === courseTime);
const existingCourseIndex = newEmploiDuTemps[
selectedCell.day
].findIndex((course) => course.heure === courseTime);
if (existingCourseIndex !== -1) {
newEmploiDuTemps[selectedCell.day][existingCourseIndex] = newCourse;
@ -98,31 +111,37 @@ const SpecialityEventModal = ({ isOpen, onClose, selectedCell, existingEvent, ha
return {
...planning,
emploiDuTemps: newEmploiDuTemps
emploiDuTemps: newEmploiDuTemps,
};
}
return planning;
});
const updatedPlanning = updatedPlannings.find(planning => planning.niveau === selectedCell.selectedLevel);
const updatedPlanning = updatedPlannings.find(
(planning) => planning.niveau === selectedCell.selectedLevel
);
setFormData(prevFormData => ({
setFormData((prevFormData) => ({
...prevFormData,
plannings: updatedPlannings
plannings: updatedPlannings,
}));
// Appeler handleUpdatePlanning avec les arguments appropriés
const planningId = updatedPlanning ? updatedPlanning.planning.id : null;
logger.debug("id : ", planningId)
logger.debug('id : ', planningId);
if (planningId) {
handleUpdatePlanning(BE_SCHOOL_PLANNINGS_URL, planningId, updatedPlanning.emploiDuTemps);
handleUpdatePlanning(
BE_SCHOOL_PLANNINGS_URL,
planningId,
updatedPlanning.emploiDuTemps
);
}
onClose();
};
const filteredTeachers = selectedSpeciality
? formData.teachers.filter(teacher =>
? formData.teachers.filter((teacher) =>
teacher.specialites.includes(parseInt(selectedSpeciality, 10))
)
: formData.teachers;
@ -132,9 +151,9 @@ const SpecialityEventModal = ({ isOpen, onClose, selectedCell, existingEvent, ha
setSelectedSpeciality(specialityId);
// Mettre à jour eventData
setEventData(prev => ({
setEventData((prev) => ({
...prev,
specialiteId: specialityId
specialiteId: specialityId,
}));
};
@ -143,9 +162,9 @@ const SpecialityEventModal = ({ isOpen, onClose, selectedCell, existingEvent, ha
setSelectedTeacher(teacherId);
// Mettre à jour eventData
setEventData(prev => ({
setEventData((prev) => ({
...prev,
teacherId: teacherId
teacherId: teacherId,
}));
};
@ -153,7 +172,7 @@ const SpecialityEventModal = ({ isOpen, onClose, selectedCell, existingEvent, ha
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white p-6 rounded-lg w-full max-w-md">
<h2 className="text-xl font-semibold mb-4">
{eventData.id ? 'Modifier l\'événement' : 'Nouvel événement'}
{eventData.id ? "Modifier l'événement" : 'Nouvel événement'}
</h2>
<form onSubmit={handleSubmit} className="space-y-4">
@ -165,10 +184,12 @@ const SpecialityEventModal = ({ isOpen, onClose, selectedCell, existingEvent, ha
selected={selectedSpeciality}
choices={[
{ value: '', label: 'Sélectionner une spécialité' },
...groupSpecialitiesBySubject(formData.teachers).map((speciality) => ({
value: speciality.id,
label: speciality.nom
}))
...groupSpecialitiesBySubject(formData.teachers).map(
(speciality) => ({
value: speciality.id,
label: speciality.nom,
})
),
]}
callback={handleSpecialityChange}
IconItem={BookOpen}
@ -182,11 +203,11 @@ const SpecialityEventModal = ({ isOpen, onClose, selectedCell, existingEvent, ha
placeHolder="Enseignants"
selected={selectedTeacher}
choices={[
{ value: '', label: 'Sélectionner un enseignant'},
...filteredTeachers.map(teacher => ({
{ value: '', label: 'Sélectionner un enseignant' },
...filteredTeachers.map((teacher) => ({
value: teacher.id,
label: `${teacher.nom} ${teacher.prenom}`
}))
label: `${teacher.nom} ${teacher.prenom}`,
})),
]}
callback={handleTeacherChange}
IconItem={Users}
@ -202,10 +223,12 @@ const SpecialityEventModal = ({ isOpen, onClose, selectedCell, existingEvent, ha
<input
type="text"
value={eventData.duration}
onChange={(e) => setEventData(prev => ({
...prev,
duration: e.target.value
}))}
onChange={(e) =>
setEventData((prev) => ({
...prev,
duration: e.target.value,
}))
}
className="w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-emerald-500"
/>
</div>

View File

@ -1,5 +1,14 @@
import React, { useState } from 'react';
import { Plus, Trash2, Edit3, Check, X, Percent, EuroIcon, Tag } from 'lucide-react';
import {
Plus,
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';
@ -7,27 +16,47 @@ import InputText from '@/components/InputText';
import logger from '@/utils/logger';
import { ESTABLISHMENT_ID } from '@/utils/Url';
const DiscountsSection = ({ discounts, setDiscounts, handleCreate, handleEdit, handleDelete, type, subscriptionMode = false, selectedDiscounts, handleDiscountSelection }) => {
const DiscountsSection = ({
discounts,
setDiscounts,
handleCreate,
handleEdit,
handleDelete,
type,
subscriptionMode = false,
selectedDiscounts,
handleDiscountSelection,
}) => {
const [editingDiscount, setEditingDiscount] = useState(null);
const [newDiscount, setNewDiscount] = useState(null);
const [formData, setFormData] = useState({});
const [localErrors, setLocalErrors] = useState({});
const [popupVisible, setPopupVisible] = useState(false);
const [popupMessage, setPopupMessage] = useState("");
const [popupMessage, setPopupMessage] = useState('');
const [removePopupVisible, setRemovePopupVisible] = useState(false);
const [removePopupMessage, setRemovePopupMessage] = useState("");
const [removePopupMessage, setRemovePopupMessage] = useState('');
const [removePopupOnConfirm, setRemovePopupOnConfirm] = useState(() => {});
const handleAddDiscount = () => {
setNewDiscount({ id: Date.now(), name: '', amount: '', description: '', discount_type: 0, type: type, establishment: ESTABLISHMENT_ID });
setNewDiscount({
id: Date.now(),
name: '',
amount: '',
description: '',
discount_type: 0,
type: type,
establishment: ESTABLISHMENT_ID,
});
};
const handleRemoveDiscount = (id) => {
return handleDelete(id)
.then(() => {
setDiscounts(prevDiscounts => prevDiscounts.filter(discount => discount.id !== id));
setDiscounts((prevDiscounts) =>
prevDiscounts.filter((discount) => discount.id !== id)
);
})
.catch(error => {
.catch((error) => {
logger.error(error);
});
};
@ -40,7 +69,7 @@ const DiscountsSection = ({ discounts, setDiscounts, handleCreate, handleEdit, h
setNewDiscount(null);
setLocalErrors({});
})
.catch(error => {
.catch((error) => {
if (error && typeof error === 'object') {
setLocalErrors(error);
} else {
@ -48,7 +77,7 @@ const DiscountsSection = ({ discounts, setDiscounts, handleCreate, handleEdit, h
}
});
} else {
setPopupMessage("Tous les champs doivent être remplis");
setPopupMessage('Tous les champs doivent être remplis');
setPopupVisible(true);
}
};
@ -60,7 +89,7 @@ const DiscountsSection = ({ discounts, setDiscounts, handleCreate, handleEdit, h
setEditingDiscount(null);
setLocalErrors({});
})
.catch(error => {
.catch((error) => {
if (error && typeof error === 'object') {
setLocalErrors(error);
} else {
@ -68,25 +97,31 @@ const DiscountsSection = ({ discounts, setDiscounts, handleCreate, handleEdit, h
}
});
} else {
setPopupMessage("Tous les champs doivent être remplis");
setPopupMessage('Tous les champs doivent être remplis');
setPopupVisible(true);
}
};
const handleToggleDiscountType = (id) => {
const discount = discounts.find(discount => discount.id === id);
const discount = discounts.find((discount) => discount.id === id);
if (!discount) return;
const updatedData = {
...discount,
discount_type: discount.discount_type === 0 ? 1 : 0
discount_type: discount.discount_type === 0 ? 1 : 0,
};
handleEdit(id, updatedData)
.then(() => {
setDiscounts(prevDiscounts => prevDiscounts.map(discount => discount.id === id ? { ...discount, discount_type: updatedData.discount_type } : discount));
setDiscounts((prevDiscounts) =>
prevDiscounts.map((discount) =>
discount.id === id
? { ...discount, discount_type: updatedData.discount_type }
: discount
)
);
})
.catch(error => {
.catch((error) => {
logger.error(error);
});
};
@ -129,7 +164,13 @@ const DiscountsSection = ({ discounts, setDiscounts, handleCreate, handleEdit, h
value={value}
onChange={onChange}
placeholder={placeholder}
errorMsg={localErrors && localErrors[field] && Array.isArray(localErrors[field]) ? localErrors[field][0] : ''}
errorMsg={
localErrors &&
localErrors[field] &&
Array.isArray(localErrors[field])
? localErrors[field][0]
: ''
}
/>
</div>
</div>
@ -143,36 +184,61 @@ const DiscountsSection = ({ discounts, setDiscounts, handleCreate, handleEdit, h
if (isEditing || isCreating) {
switch (column) {
case 'LIBELLE':
return renderInputField('name', currentData.name, handleChange, 'Libellé de la réduction');
return renderInputField(
'name',
currentData.name,
handleChange,
'Libellé de la réduction'
);
case 'REMISE':
return (
<div className="flex justify-center space-x-2">
{renderInputField('amount', currentData.amount, handleChange,'Montant')}
{renderInputField(
'amount',
currentData.amount,
handleChange,
'Montant'
)}
<button
type="button"
onClick={() => handleToggleDiscountTypeEdition(discount.id)}
className="flex justify-center items-center text-emerald-500 hover:text-emerald-700 ml-2"
>
{currentData.discount_type === 0 ? <EuroIcon className="w-5 h-5" /> : <Percent className="w-5 h-5" />}
{currentData.discount_type === 0 ? (
<EuroIcon className="w-5 h-5" />
) : (
<Percent className="w-5 h-5" />
)}
</button>
</div>
);
case 'DESCRIPTION':
return renderInputField('description', currentData.description, handleChange, 'Description');
return renderInputField(
'description',
currentData.description,
handleChange,
'Description'
);
case 'ACTIONS':
return (
<div className="flex justify-center space-x-2">
<button
type="button"
onClick={() => (isEditing ? handleUpdateDiscount(editingDiscount, formData) : handleSaveNewDiscount())}
onClick={() =>
isEditing
? handleUpdateDiscount(editingDiscount, formData)
: handleSaveNewDiscount()
}
className="text-green-500 hover:text-green-700"
>
<Check className="w-5 h-5" />
</button>
<button
type="button"
onClick={() => (isEditing ? setEditingDiscount(null) : setNewDiscount(null))}
onClick={() =>
isEditing ? setEditingDiscount(null) : setNewDiscount(null)
}
className="text-red-500 hover:text-red-700"
>
<X className="w-5 h-5" />
@ -187,7 +253,9 @@ const DiscountsSection = ({ discounts, setDiscounts, handleCreate, handleEdit, h
case 'LIBELLE':
return discount.name;
case 'REMISE':
return discount.discount_type === 0 ? `${discount.amount}` : `${discount.amount} %`;
return discount.discount_type === 0
? `${discount.amount}`
: `${discount.amount} %`;
case 'DESCRIPTION':
return discount.description;
case 'MISE A JOUR':
@ -200,11 +268,17 @@ const DiscountsSection = ({ discounts, setDiscounts, handleCreate, handleEdit, h
onClick={() => handleToggleDiscountType(discount.id)}
className="flex justify-center items-center text-emerald-500 hover:text-emerald-700"
>
{discount.discount_type === 0 ? <EuroIcon className="w-5 h-5" /> : <Percent className="w-5 h-5" />}
{discount.discount_type === 0 ? (
<EuroIcon className="w-5 h-5" />
) : (
<Percent className="w-5 h-5" />
)}
</button>
<button
type="button"
onClick={() => setEditingDiscount(discount.id) || setFormData(discount)}
onClick={() =>
setEditingDiscount(discount.id) || setFormData(discount)
}
className="text-blue-500 hover:text-blue-700"
>
<Edit3 className="w-5 h-5" />
@ -213,18 +287,22 @@ const DiscountsSection = ({ discounts, setDiscounts, handleCreate, handleEdit, h
type="button"
onClick={() => {
setRemovePopupVisible(true);
setRemovePopupMessage("Attentions ! \nVous êtes sur le point de supprimer un tarif personnalisé.\nÊtes-vous sûr(e) de vouloir poursuivre l'opération ?");
setRemovePopupMessage(
"Attentions ! \nVous êtes sur le point de supprimer un tarif personnalisé.\nÊtes-vous sûr(e) de vouloir poursuivre l'opération ?"
);
setRemovePopupOnConfirm(() => () => {
handleRemoveDiscount(discount.id)
.then(data => {
.then((data) => {
logger.debug('Success:', data);
setPopupMessage("Réduction correctement supprimé");
setPopupMessage('Réduction correctement supprimé');
setPopupVisible(true);
setRemovePopupVisible(false);
})
.catch(error => {
.catch((error) => {
logger.error('Error archiving data:', error);
setPopupMessage("Erreur lors de la suppression de la réduction");
setPopupMessage(
'Erreur lors de la suppression de la réduction'
);
setPopupVisible(true);
setRemovePopupVisible(false);
});
@ -236,17 +314,17 @@ const DiscountsSection = ({ discounts, setDiscounts, handleCreate, handleEdit, h
</button>
</div>
);
case '':
return (
<div className="flex justify-center">
<CheckBox
item={discount}
formData={{ selectedDiscounts }}
handleChange={() => handleDiscountSelection(discount.id)}
fieldName="selectedDiscounts"
/>
</div>
);
case '':
return (
<div className="flex justify-center">
<CheckBox
item={discount}
formData={{ selectedDiscounts }}
handleChange={() => handleDiscountSelection(discount.id)}
fieldName="selectedDiscounts"
/>
</div>
);
default:
return null;
}
@ -254,19 +332,19 @@ const DiscountsSection = ({ discounts, setDiscounts, handleCreate, handleEdit, h
};
const columns = subscriptionMode
? [
{ name: 'LIBELLE', label: 'Libellé' },
{ name: 'DESCRIPTION', label: 'Description' },
{ name: 'REMISE', label: 'Remise' },
{ name: '', label: 'Sélection' }
]
: [
{ name: 'LIBELLE', label: 'Libellé' },
{ name: 'REMISE', label: 'Remise' },
{ name: 'DESCRIPTION', label: 'Description' },
{ name: 'MISE A JOUR', label: 'Date mise à jour' },
{ name: 'ACTIONS', label: 'Actions' }
];
? [
{ name: 'LIBELLE', label: 'Libellé' },
{ name: 'DESCRIPTION', label: 'Description' },
{ name: 'REMISE', label: 'Remise' },
{ name: '', label: 'Sélection' },
]
: [
{ name: 'LIBELLE', label: 'Libellé' },
{ name: 'REMISE', label: 'Remise' },
{ name: 'DESCRIPTION', label: 'Description' },
{ name: 'MISE A JOUR', label: 'Date mise à jour' },
{ name: 'ACTIONS', label: 'Actions' },
];
return (
<div className="space-y-4">
@ -276,7 +354,11 @@ const DiscountsSection = ({ discounts, setDiscounts, handleCreate, handleEdit, h
<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">
<button
type="button"
onClick={handleAddDiscount}
className="text-emerald-500 hover:text-emerald-700"
>
<Plus className="w-5 h-5" />
</button>
</div>
@ -285,7 +367,7 @@ const DiscountsSection = ({ discounts, setDiscounts, handleCreate, handleEdit, h
data={newDiscount ? [newDiscount, ...discounts] : discounts}
columns={columns}
renderCell={renderDiscountCell}
defaultTheme='bg-yellow-100'
defaultTheme="bg-yellow-100"
/>
<Popup
visible={popupVisible}
@ -304,4 +386,4 @@ const DiscountsSection = ({ discounts, setDiscounts, handleCreate, handleEdit, h
);
};
export default DiscountsSection;
export default DiscountsSection;

View File

@ -3,41 +3,47 @@ import FeesSection from '@/components/Structure/Tarification/FeesSection';
import DiscountsSection from '@/components/Structure/Tarification/DiscountsSection';
import PaymentPlanSelector from '@/components/PaymentPlanSelector';
import PaymentModeSelector from '@/components/PaymentModeSelector';
import { BE_SCHOOL_FEES_URL, BE_SCHOOL_DISCOUNTS_URL, BE_SCHOOL_PAYMENT_PLANS_URL, BE_SCHOOL_PAYMENT_MODES_URL } from '@/utils/Url';
const FeesManagement = ({ registrationDiscounts,
setRegistrationDiscounts,
tuitionDiscounts,
setTuitionDiscounts,
registrationFees,
setRegistrationFees,
tuitionFees,
setTuitionFees,
registrationPaymentPlans,
setRegistrationPaymentPlans,
tuitionPaymentPlans,
setTuitionPaymentPlans,
registrationPaymentModes,
setRegistrationPaymentModes,
tuitionPaymentModes,
setTuitionPaymentModes,
handleCreate,
handleEdit,
handleDelete }) => {
import {
BE_SCHOOL_FEES_URL,
BE_SCHOOL_DISCOUNTS_URL,
BE_SCHOOL_PAYMENT_PLANS_URL,
BE_SCHOOL_PAYMENT_MODES_URL,
} from '@/utils/Url';
const FeesManagement = ({
registrationDiscounts,
setRegistrationDiscounts,
tuitionDiscounts,
setTuitionDiscounts,
registrationFees,
setRegistrationFees,
tuitionFees,
setTuitionFees,
registrationPaymentPlans,
setRegistrationPaymentPlans,
tuitionPaymentPlans,
setTuitionPaymentPlans,
registrationPaymentModes,
setRegistrationPaymentModes,
tuitionPaymentModes,
setTuitionPaymentModes,
handleCreate,
handleEdit,
handleDelete,
}) => {
const handleDiscountDelete = (id, type) => {
if (type === 0) {
setRegistrationFees(prevFees =>
prevFees.map(fee => ({
setRegistrationFees((prevFees) =>
prevFees.map((fee) => ({
...fee,
discounts: fee.discounts.filter(discountId => discountId !== id)
discounts: fee.discounts.filter((discountId) => discountId !== id),
}))
);
} else {
setTuitionFees(prevFees =>
prevFees.map(fee => ({
setTuitionFees((prevFees) =>
prevFees.map((fee) => ({
...fee,
discounts: fee.discounts.filter(discountId => discountId !== id)
discounts: fee.discounts.filter((discountId) => discountId !== id),
}))
);
}
@ -46,16 +52,33 @@ const FeesManagement = ({ registrationDiscounts,
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>
<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)}
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>
@ -63,9 +86,28 @@ const FeesManagement = ({ registrationDiscounts,
<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)}
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}
/>
@ -74,7 +116,14 @@ const FeesManagement = ({ registrationDiscounts,
<PaymentPlanSelector
paymentPlans={registrationPaymentPlans}
setPaymentPlans={setRegistrationPaymentPlans}
handleEdit={(id, updatedData) => handleEdit(`${BE_SCHOOL_PAYMENT_PLANS_URL}`, id, updatedData, setRegistrationPaymentPlans)}
handleEdit={(id, updatedData) =>
handleEdit(
`${BE_SCHOOL_PAYMENT_PLANS_URL}`,
id,
updatedData,
setRegistrationPaymentPlans
)
}
type={0}
/>
</div>
@ -82,23 +131,41 @@ const FeesManagement = ({ registrationDiscounts,
<PaymentModeSelector
paymentModes={registrationPaymentModes}
setPaymentModes={setRegistrationPaymentModes}
handleEdit={(id, updatedData) => handleEdit(`${BE_SCHOOL_PAYMENT_MODES_URL}`, id, updatedData, setRegistrationPaymentModes)}
handleEdit={(id, updatedData) =>
handleEdit(
`${BE_SCHOOL_PAYMENT_MODES_URL}`,
id,
updatedData,
setRegistrationPaymentModes
)
}
type={0}
/>
</div>
</div>
</div>
<div className="bg-white p-2 rounded-lg shadow-md">
<h2 className="text-2xl font-semibold mb-4">Frais de scolarité</h2>
<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)}
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>
@ -106,9 +173,28 @@ const FeesManagement = ({ registrationDiscounts,
<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)}
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}
/>
@ -117,7 +203,14 @@ const FeesManagement = ({ registrationDiscounts,
<PaymentPlanSelector
paymentPlans={tuitionPaymentPlans}
setPaymentPlans={setTuitionPaymentPlans}
handleEdit={(id, updatedData) => handleEdit(`${BE_SCHOOL_PAYMENT_PLANS_URL}`, id, updatedData, setRegistrationPaymentPlans)}
handleEdit={(id, updatedData) =>
handleEdit(
`${BE_SCHOOL_PAYMENT_PLANS_URL}`,
id,
updatedData,
setRegistrationPaymentPlans
)
}
type={1}
/>
</div>
@ -125,7 +218,14 @@ const FeesManagement = ({ registrationDiscounts,
<PaymentModeSelector
paymentModes={tuitionPaymentModes}
setPaymentModes={setTuitionPaymentModes}
handleEdit={(id, updatedData) => handleEdit(`${BE_SCHOOL_PAYMENT_MODES_URL}`, id, updatedData, setTuitionPaymentModes)}
handleEdit={(id, updatedData) =>
handleEdit(
`${BE_SCHOOL_PAYMENT_MODES_URL}`,
id,
updatedData,
setTuitionPaymentModes
)
}
type={1}
/>
</div>
@ -135,4 +235,4 @@ const FeesManagement = ({ registrationDiscounts,
);
};
export default FeesManagement;
export default FeesManagement;

View File

@ -1,5 +1,15 @@
import React, { useState } from 'react';
import { Plus, Trash2, Edit3, Check, X, EyeOff, Eye, CreditCard, BookOpen } from 'lucide-react';
import {
Plus,
Trash2,
Edit3,
Check,
X,
EyeOff,
Eye,
CreditCard,
BookOpen,
} from 'lucide-react';
import Table from '@/components/Table';
import Popup from '@/components/Popup';
import CheckBox from '@/components/CheckBox';
@ -8,17 +18,29 @@ import logger from '@/utils/logger';
import { ESTABLISHMENT_ID } from '@/utils/Url';
const FeesSection = ({ fees, setFees, discounts, handleCreate, handleEdit, handleDelete, type, subscriptionMode = false, selectedFees, handleFeeSelection }) => {
const FeesSection = ({
fees,
setFees,
discounts,
handleCreate,
handleEdit,
handleDelete,
type,
subscriptionMode = false,
selectedFees,
handleFeeSelection,
}) => {
const [editingFee, setEditingFee] = useState(null);
const [newFee, setNewFee] = useState(null);
const [formData, setFormData] = useState({});
const [localErrors, setLocalErrors] = useState({});
const [popupVisible, setPopupVisible] = useState(false);
const [popupMessage, setPopupMessage] = useState("");
const [popupMessage, setPopupMessage] = useState('');
const [removePopupVisible, setRemovePopupVisible] = useState(false);
const [removePopupMessage, setRemovePopupMessage] = useState("");
const [removePopupMessage, setRemovePopupMessage] = useState('');
const [removePopupOnConfirm, setRemovePopupOnConfirm] = useState(() => {});
const labelTypeFrais = (type === 0 ? 'Frais d\'inscription' : 'Frais de scolarité');
const labelTypeFrais =
type === 0 ? "Frais d'inscription" : 'Frais de scolarité';
// Récupération des messages d'erreur
const getError = (field) => {
@ -26,23 +48,31 @@ const FeesSection = ({ fees, setFees, discounts, handleCreate, handleEdit, handl
};
const handleAddFee = () => {
setNewFee({ id: Date.now(), name: '', base_amount: '', description: '', validity_start_date: '', validity_end_date: '', discounts: [], type: type, establishment: ESTABLISHMENT_ID });
setNewFee({
id: Date.now(),
name: '',
base_amount: '',
description: '',
validity_start_date: '',
validity_end_date: '',
discounts: [],
type: type,
establishment: ESTABLISHMENT_ID,
});
};
const handleRemoveFee = (id) => {
return handleDelete(id)
.then(() => {
setFees(prevFees => prevFees.filter(fee => fee.id !== id));
setFees((prevFees) => prevFees.filter((fee) => fee.id !== id));
})
.catch(error => {
.catch((error) => {
logger.error(error);
});
};
const handleSaveNewFee = () => {
if (
newFee.name &&
newFee.base_amount) {
if (newFee.name && newFee.base_amount) {
handleCreate(newFee)
.then((createdFee) => {
setFees([createdFee, ...fees]);
@ -52,53 +82,55 @@ const FeesSection = ({ fees, setFees, discounts, handleCreate, handleEdit, handl
.catch((error) => {
logger.error('Error:', error.message);
if (error.details) {
logger.error('Form errors:', error.details);
setLocalErrors(error.details);
logger.error('Form errors:', error.details);
setLocalErrors(error.details);
}
});
} else {
setPopupMessage("Tous les champs doivent être remplis et valides");
setPopupMessage('Tous les champs doivent être remplis et valides');
setPopupVisible(true);
}
};
const handleUpdateFee = (id, updatedFee) => {
if (
updatedFee.name &&
updatedFee.base_amount) {
if (updatedFee.name && updatedFee.base_amount) {
handleEdit(id, updatedFee)
.then((updatedFee) => {
setFees(fees.map(fee => fee.id === id ? updatedFee : fee));
setFees(fees.map((fee) => (fee.id === id ? updatedFee : fee)));
setEditingFee(null);
setLocalErrors({});
})
.catch((error) => {
logger.error('Error:', error.message);
if (error.details) {
logger.error('Form errors:', error.details);
setLocalErrors(error.details);
logger.error('Form errors:', error.details);
setLocalErrors(error.details);
}
});
} else {
setPopupMessage("Tous les champs doivent être remplis et valides");
setPopupMessage('Tous les champs doivent être remplis et valides');
setPopupVisible(true);
}
};
const handleToggleActive = (id, isActive) => {
const fee = fees.find(fee => fee.id === id);
const fee = fees.find((fee) => fee.id === id);
if (!fee) return;
const updatedData = {
is_active: !isActive,
discounts: fee.discounts
discounts: fee.discounts,
};
handleEdit(id, updatedData)
.then(() => {
setFees(prevFees => prevFees.map(fee => fee.id === id ? { ...fee, is_active: !isActive } : fee));
setFees((prevFees) =>
prevFees.map((fee) =>
fee.id === id ? { ...fee, is_active: !isActive } : fee
)
);
})
.catch(error => {
.catch((error) => {
logger.error(error);
});
};
@ -107,7 +139,7 @@ const FeesSection = ({ fees, setFees, discounts, handleCreate, handleEdit, handl
const { name, value } = e.target;
let parsedValue = value;
if (name === 'discounts') {
parsedValue = value.split(',').map(v => parseInt(v, 10));
parsedValue = value.split(',').map((v) => parseInt(v, 10));
}
if (editingFee) {
setFormData((prevData) => ({
@ -145,24 +177,45 @@ const FeesSection = ({ fees, setFees, discounts, handleCreate, handleEdit, handl
if (isEditing || isCreating) {
switch (column) {
case 'NOM':
return renderInputField('name', currentData.name, handleChange, 'Nom des frais');
return renderInputField(
'name',
currentData.name,
handleChange,
'Nom des frais'
);
case 'MONTANT':
return renderInputField('base_amount', currentData.base_amount, handleChange, 'Montant de base');
return renderInputField(
'base_amount',
currentData.base_amount,
handleChange,
'Montant de base'
);
case 'DESCRIPTION':
return renderInputField('description', currentData.description, handleChange, 'Description');
return renderInputField(
'description',
currentData.description,
handleChange,
'Description'
);
case 'ACTIONS':
return (
<div className="flex justify-center space-x-2">
<button
type="button"
onClick={() => (isEditing ? handleUpdateFee(editingFee, formData) : handleSaveNewFee())}
onClick={() =>
isEditing
? handleUpdateFee(editingFee, formData)
: handleSaveNewFee()
}
className="text-green-500 hover:text-green-700"
>
<Check className="w-5 h-5" />
</button>
<button
type="button"
onClick={() => (isEditing ? setEditingFee(null) : setNewFee(null))}
onClick={() =>
isEditing ? setEditingFee(null) : setNewFee(null)
}
className="text-red-500 hover:text-red-700"
>
<X className="w-5 h-5" />
@ -190,7 +243,11 @@ const FeesSection = ({ fees, setFees, discounts, handleCreate, handleEdit, handl
onClick={() => handleToggleActive(fee.id, fee.is_active)}
className={`text-${fee.is_active ? 'green' : 'orange'}-500 hover:text-${fee.is_active ? 'green' : 'orange'}-700`}
>
{fee.is_active ? <Eye className="w-5 h-5" /> : <EyeOff className="w-5 h-5" />}
{fee.is_active ? (
<Eye className="w-5 h-5" />
) : (
<EyeOff className="w-5 h-5" />
)}
</button>
<button
type="button"
@ -203,18 +260,26 @@ const FeesSection = ({ fees, setFees, discounts, handleCreate, handleEdit, handl
type="button"
onClick={() => {
setRemovePopupVisible(true);
setRemovePopupMessage("Attentions ! \nVous êtes sur le point de supprimer un " + labelTypeFrais + ".\nÊtes-vous sûr(e) de vouloir poursuivre l'opération ?");
setRemovePopupMessage(
'Attentions ! \nVous êtes sur le point de supprimer un ' +
labelTypeFrais +
".\nÊtes-vous sûr(e) de vouloir poursuivre l'opération ?"
);
setRemovePopupOnConfirm(() => () => {
handleRemoveFee(fee.id)
.then(data => {
.then((data) => {
logger.debug('Success:', data);
setPopupMessage(labelTypeFrais + " correctement supprimé");
setPopupMessage(
labelTypeFrais + ' correctement supprimé'
);
setPopupVisible(true);
setRemovePopupVisible(false);
})
.catch(error => {
.catch((error) => {
logger.error('Error archiving data:', error);
setPopupMessage("Erreur lors de la suppression du " + labelTypeFrais);
setPopupMessage(
'Erreur lors de la suppression du ' + labelTypeFrais
);
setPopupVisible(true);
setRemovePopupVisible(false);
});
@ -248,29 +313,33 @@ const FeesSection = ({ fees, setFees, discounts, handleCreate, handleEdit, handl
{ name: 'NOM', label: 'Nom' },
{ name: 'DESCRIPTION', label: 'Description' },
{ name: 'MONTANT', label: 'Montant de base' },
{ name: '', label: 'Sélection' }
{ name: '', label: 'Sélection' },
]
: [
{ name: 'NOM', label: 'Nom' },
{ name: 'MONTANT', label: 'Montant de base' },
{ name: 'DESCRIPTION', label: 'Description' },
{ name: 'MISE A JOUR', label: 'Date mise à jour' },
{ name: 'ACTIONS', label: 'Actions' }
{ name: 'ACTIONS', label: 'Actions' },
];
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 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>
<button type="button" onClick={handleAddFee} className="text-emerald-500 hover:text-emerald-700">
<Plus className="w-5 h-5" />
</button>
</div>
)}
)}
<Table
data={newFee ? [newFee, ...fees] : fees}
columns={columns}
@ -293,4 +362,4 @@ const FeesSection = ({ fees, setFees, discounts, handleCreate, handleEdit, handl
);
};
export default FeesSection;
export default FeesSection;