mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-01-29 07:53:23 +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"
|
||||
|
||||
Reference in New Issue
Block a user