mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-01-29 16:03:21 +00:00
chore: application prettier
This commit is contained in:
@ -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;
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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 => (
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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" />
|
||||
|
||||
@ -53,4 +53,4 @@ export default function RegistrationFileGroupForm({ onSubmit, initialData }) {
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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'inscription</h2>
|
||||
<ul>
|
||||
{groups.map(group => (
|
||||
{groups.map((group) => (
|
||||
<li key={group.id}>{group.name}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>
|
||||
))}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -17,5 +17,3 @@ const SpecialitiesList = ({ teachers }) => {
|
||||
};
|
||||
|
||||
export default SpecialitiesList;
|
||||
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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'inscription</h2>
|
||||
<h2 className="text-2xl font-semibold mb-4">
|
||||
Frais d'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;
|
||||
|
||||
@ -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;
|
||||
|
||||
Reference in New Issue
Block a user