refactor: Création de nouveaux composants / update formulaire de

création de classe (#2)
This commit is contained in:
N3WT DE COMPET
2024-12-14 15:28:07 +01:00
parent cf144310a1
commit 7acae479da
43 changed files with 2374 additions and 441 deletions

View File

@ -0,0 +1,252 @@
import React, { useState } from 'react';
import InputTextIcon from '@/components/InputTextIcon';
import Button from '@/components/Button';
import SelectChoice from '@/components/SelectChoice';
import CheckBoxList from '@/components/CheckBoxList';
import PlanningConfiguration from '@/components/Structure/Configuration/PlanningConfiguration';
import TeachersSelectionConfiguration from '@/components/Structure/Configuration/TeachersSelectionConfiguration';
import { Users, Maximize2, Calendar, UserPlus } from 'lucide-react';
import { useClasseForm } from '@/context/ClasseFormContext';
import { useClasses } from '@/context/ClassesContext';
const ClassForm = ({ onSubmit, isNew, teachers }) => {
const { formData, setFormData } = useClasseForm();
const { schoolYears, getNiveauxLabels, generateAgeToNiveaux, niveauxPremierCycle, niveauxSecondCycle, niveauxTroisiemeCycle, typeEmploiDuTemps, updatePlanning } = useClasses();
const [selectedTeachers, setSelectedTeachers] = useState(formData.enseignants_ids);
const handleTeacherSelection = (teacher) => {
setSelectedTeachers(prevState =>
prevState.includes(teacher.id)
? prevState.filter(id => id !== teacher.id)
: [...prevState, teacher.id]
);
setFormData(prevState => ({
...prevState,
enseignants_ids: prevState.enseignants_ids.includes(teacher.id)
? prevState.enseignants_ids.filter(id => id !== teacher.id)
: [...prevState.enseignants_ids, teacher.id]
}));
};
const handleTimeChange = (e, index) => {
const { value } = e.target;
setFormData(prevState => {
const updatedTimes = [...prevState.plage_horaire];
updatedTimes[index] = value;
const updatedFormData = {
...prevState,
plage_horaire: updatedTimes,
};
updatedFormData.planning = updatePlanning(updatedFormData);
return updatedFormData;
});
};
const handleJoursChange = (e) => {
const { value, checked } = e.target;
const dayId = parseInt(value, 10);
setFormData((prevState) => {
const updatedJoursOuverture = checked
? [...prevState.jours_ouverture, dayId]
: prevState.jours_ouverture.filter((id) => id !== dayId);
const updatedFormData = {
...prevState,
jours_ouverture: updatedJoursOuverture,
};
updatedFormData.planning = updatePlanning(updatedFormData);
return updatedFormData;
});
};
const handleChange = (e) => {
e.preventDefault();
const { name, value, type, checked } = e.target;
setFormData(prevState => {
let updatedFormData = { ...prevState };
if (type === 'checkbox') {
const newValues = checked
? [...(prevState[name] || []), parseInt(value)]
: (prevState[name] || []).filter(v => v !== parseInt(value));
updatedFormData[name] = newValues;
} else if (name === 'tranche_age') {
const [minAgeStr, maxAgeStr] = value.split('-');
const minAge = minAgeStr ? parseInt(minAgeStr) : null;
const maxAge = minAgeStr ? parseInt(maxAgeStr) : null;
const selectedNiveaux = generateAgeToNiveaux(minAge, maxAge);
const niveauxLabels = getNiveauxLabels(selectedNiveaux);
updatedFormData = {
...prevState,
[name]: value,
niveaux: selectedNiveaux.length > 0 ? selectedNiveaux : [],
niveaux_label: niveauxLabels.length > 0 ? niveauxLabels : []
};
} else if (type === 'radio') {
updatedFormData[name] = parseInt(value, 10);
} else {
updatedFormData[name] = value;
}
console.log('Updated formData:', updatedFormData);
updatedFormData.planning = updatePlanning(updatedFormData);
console.log('Final formData:', updatedFormData);
return updatedFormData;
});
};
const handleSubmit = () => {
onSubmit(formData);
};
const [minAge, maxAge] = formData.tranche_age.length === 2 ? formData.tranche_age : [null, null];
const selectedAgeGroup = generateAgeToNiveaux(minAge, maxAge);
return (
<div className="h-[80vh] overflow-y-auto">
<form onSubmit={handleSubmit} className="space-y-4 mt-8">
<div className="flex justify-between space-x-4">
{/* Section Ambiance */}
<div className="w-1/2 space-y-4">
<label className="block text-lg font-medium text-gray-700">Ambiance <i>(optionnel)</i></label>
<div className="space-y-4">
<div>
<InputTextIcon
name="nom_ambiance"
type="text"
IconItem={Users}
placeholder="Nom de l'ambiance"
value={formData.nom_ambiance}
onChange={handleChange}
className="w-full"
/>
</div>
<div>
<InputTextIcon
name="tranche_age"
type="text"
IconItem={Maximize2}
placeholder="Tranche d'âge (ex: 3-6)"
value={formData.tranche_age}
onChange={handleChange}
className="w-full"
/>
</div>
</div>
</div>
{/* Section Niveau */}
<div className="w-1/2 space-y-2">
<label className="block text-lg font-medium text-gray-700">Niveaux</label>
<div className="grid grid-cols-3 gap-4">
<CheckBoxList
items={niveauxPremierCycle}
formData={formData}
handleChange={handleChange}
fieldName="niveaux"
labelAttenuated={(item) => !selectedAgeGroup.includes(parseInt(item.id))}
className="w-full"
/>
<CheckBoxList
items={niveauxSecondCycle}
formData={formData}
handleChange={handleChange}
fieldName="niveaux"
labelAttenuated={(item) => !selectedAgeGroup.includes(parseInt(item.id))}
className="w-full"
/>
<CheckBoxList
items={niveauxTroisiemeCycle}
formData={formData}
handleChange={handleChange}
fieldName="niveaux"
labelAttenuated={(item) => !selectedAgeGroup.includes(parseInt(item.id))}
className="w-full"
/>
</div>
</div>
</div>
<div className="flex justify-between space-x-4">
{/* Section Capacité */}
<div className="w-1/2 space-y-4">
<label className="block text-lg font-medium text-gray-700">Capacité</label>
<div className="space-y-4">
<InputTextIcon
name="nombre_eleves"
type="number"
IconItem={UserPlus}
placeholder="Capacité max"
value={formData.nombre_eleves}
onChange={handleChange}
className="w-full"
/>
</div>
</div>
{/* Année scolaire */}
<div className="w-1/2 space-y-4">
<label className="block text-lg font-medium text-gray-700">Année scolaire</label>
<div className="space-y-4">
<SelectChoice
name="annee_scolaire"
placeholder="Sélectionner l'année scolaire"
selected={formData.annee_scolaire}
callback={handleChange}
choices={schoolYears}
IconItem={Calendar}
className="w-full"
/>
</div>
</div>
</div>
{/* Section Enseignants */}
<TeachersSelectionConfiguration formData={formData}
teachers={teachers}
handleTeacherSelection={handleTeacherSelection}
selectedTeachers={selectedTeachers}
/>
{/* Section Emploi du temps */}
<PlanningConfiguration formData={formData}
handleChange={handleChange}
handleTimeChange={handleTimeChange}
handleJoursChange={handleJoursChange}
typeEmploiDuTemps={typeEmploiDuTemps}
/>
<div className="flex justify-end mt-4 space-x-4">
<Button
text={`${isNew ? "Créer" : "Modifier"}`}
onClick={handleSubmit}
className={`px-4 py-2 rounded-md shadow-sm focus:outline-none ${
(formData.niveaux.length === 0 || !formData.annee_scolaire || !formData.nombre_eleves || formData.enseignants_ids.length === 0)
? "bg-gray-300 text-gray-700 cursor-not-allowed"
: "bg-emerald-500 text-white hover:bg-emerald-600"
}`}
primary
disabled={(formData.niveaux.length === 0 || !formData.annee_scolaire || !formData.nombre_eleves || formData.enseignants_ids.length === 0)}
type="submit"
name="Create"
/>
</div>
</form>
</div>
);
};
export default ClassForm;

View File

@ -0,0 +1,179 @@
import { Users, Trash2, MoreVertical, Edit3, Plus, ZoomIn } from 'lucide-react';
import { useState } from 'react';
import Table from '@/components/Table';
import DropdownMenu from '@/components/DropdownMenu';
import Modal from '@/components/Modal';
import ClassForm from '@/components/Structure/Configuration/ClassForm';
import ClasseDetails from '@/components/ClasseDetails';
import { ClasseFormProvider } from '@/context/ClasseFormContext';
import { useClasses } from '@/context/ClassesContext';
const ClassesSection = ({ classes, specialities, teachers, handleCreate, handleEdit, handleDelete }) => {
const { getNiveauxLabels } = useClasses();
const [isOpen, setIsOpen] = useState(false);
const [isOpenDetails, setIsOpenDetails] = useState(false);
const [editingClass, setEditingClass] = useState(null);
const openEditModal = (classe) => {
setIsOpen(true);
setEditingClass(classe);
}
const openEditModalDetails = (classe) => {
setIsOpenDetails(true);
setEditingClass(classe);
}
const closeEditModal = () => {
setIsOpen(false);
setEditingClass(null);
};
const closeEditModalDetails = () => {
setIsOpenDetails(false);
setEditingClass(null);
};
const handleModalSubmit = (updatedData) => {
if (editingClass) {
handleEdit(editingClass.id, updatedData);
} else {
handleCreate(updatedData);
}
closeEditModal();
};
return (
<div className="mb-8">
<div className="flex justify-between items-center mb-4 max-w-8xl ml-0">
<h2 className="text-3xl text-gray-800 flex items-center">
<Users className="w-8 h-8 mr-2" />
Classes
</h2>
<button
onClick={() => openEditModal(null)} // ouvrir le modal pour créer une nouvelle spécialité
className="flex items-center bg-emerald-600 text-white p-2 rounded-full shadow hover:bg-emerald-900 transition duration-200"
>
<Plus className="w-5 h-5" />
</button>
</div>
<div className="bg-white rounded-lg border border-gray-200 max-w-8xl ml-0">
<Table
columns={[
{
name: 'AMBIANCE',
transform: (row) => {
const ambiance = row.nom_ambiance ? row.nom_ambiance : '';
const trancheAge = row.tranche_age ? `${row.tranche_age} ans` : '';
if (ambiance && trancheAge) {
return `${ambiance} (${trancheAge})`;
} else if (ambiance) {
return ambiance;
} else if (trancheAge) {
return trancheAge;
} else {
return 'Non spécifié';
}
}
},
{
name: 'NIVEAUX',
transform: (row) => {
const niveauxLabels = Array.isArray(row.niveaux) ? getNiveauxLabels(row.niveaux) : [];
return (
<div className="flex flex-wrap justify-center items-center space-x-2">
{niveauxLabels.length > 0
? niveauxLabels.map((label, index) => (
<div
key={index}
className={`px-3 py-1 rounded-md shadow-sm ${
index % 2 === 0 ? 'bg-white' : 'bg-gray-100'
} border border-gray-200 text-gray-700`}>
{label}
</div>
))
: 'Aucun niveau'}
</div>
);
}
},
{ name: 'CAPACITÉ MAX', transform: (row) => row.nombre_eleves },
{ name: 'ANNÉE SCOLAIRE', transform: (row) => row.annee_scolaire },
{
name: 'ENSEIGNANTS',
transform: (row) => (
<div key={row.id} className="flex flex-wrap justify-center items-center space-x-2">
{row.enseignants.map((teacher, index) => (
<div
key={teacher.id}
className={`px-3 py-1 rounded-md shadow-sm ${
index % 2 === 0 ? 'bg-white' : 'bg-gray-100'
} border border-gray-200 text-gray-700`}
>
<span className="font-bold">{teacher.nom} {teacher.prenom}</span>
</div>
))}
</div>
)
},
{ name: 'DATE DE CREATION', transform: (row) => row.dateCreation_formattee },
{
name: 'ACTIONS', transform: (row) => (
<DropdownMenu
buttonContent={<MoreVertical size={20} className="text-gray-400 hover:text-gray-600" />}
items={[
{ label: 'Inspecter', icon: ZoomIn, onClick: () => openEditModalDetails(row) },
{ label: 'Modifier', icon: Edit3, onClick: () => openEditModal(row) },
{ label: 'Supprimer', icon: Trash2, onClick: () => handleDelete(row.id) }
]
}
buttonClassName="text-gray-400 hover:text-gray-600"
menuClassName="absolute right-0 mt-2 w-48 bg-white border border-gray-200 rounded-md shadow-lg z-10 flex flex-col items-center"
/>
)
}
]}
data={classes}
/>
</div>
{isOpen && (
<ClasseFormProvider initialClasse={editingClass || {}}>
<Modal
isOpen={isOpen}
setIsOpen={setIsOpen}
title={editingClass ? "Modification de la classe" : "Création d'une nouvelle classe"}
size='sm:w-1/2'
ContentComponent={() => (
<ClassForm classe={editingClass || {}} onSubmit={handleModalSubmit} isNew={!editingClass} teachers={teachers} />
)}
/>
</ClasseFormProvider>
)}
{isOpenDetails && (
<Modal
isOpen={isOpenDetails}
setIsOpen={setIsOpenDetails}
title={(
<div className="flex items-center">
<Users className="w-8 h-8 mr-2" />
{editingClass ? (
<>
{editingClass.nom_ambiance} - {editingClass.tranche_age[0]} à {editingClass.tranche_age[1]} ans
</>
) : ''}
</div>
)}
ContentComponent={() => (
<ClasseDetails classe={editingClass} />
)}
/>
)}
</div>
);
};
export default ClassesSection;

View File

@ -0,0 +1,38 @@
import React from 'react';
import { Calendar } from 'lucide-react';
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>
<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
type="date"
name={nameStart}
value={valueStart}
onChange={onChange}
className="block w-full pl-10 pr-4 py-2 border border-gray-300 rounded-md shadow-sm focus:ring-emerald-500 focus:border-emerald-500 hover:ring-emerald-400 ml-8"
placeholder="Date de début"
/>
</div>
<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
type="date"
name={nameEnd}
value={valueEnd}
onChange={onChange}
className="block w-full pl-10 pr-4 py-2 border border-gray-300 rounded-md shadow-sm focus:ring-emerald-500 focus:border-emerald-500 hover:ring-emerald-400 ml-8"
placeholder="Date de fin"
/>
</div>
</div>
</div>
);
};
export default DateRange;

View File

@ -0,0 +1,113 @@
import React from 'react';
import RadioList from '@/components/RadioList';
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 daysOfWeek = [
{ id: 1, name: 'lun' },
{ id: 2, name: 'mar' },
{ id: 3, name: 'mer' },
{ id: 4, name: 'jeu' },
{ id: 5, name: 'ven' },
{ id: 6, name: 'sam' },
];
const isLabelAttenuated = (item) => {
return !formData.jours_ouverture.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>
<div className="flex justify-between space-x-4 items-start">
<div className="w-1/2">
<RadioList
items={typeEmploiDuTemps}
formData={formData}
handleChange={handleChange}
fieldName="planning_type"
className="w-full"
/>
</div>
{/* Plage horaire */}
<div className="w-1/2">
<TimeRange
startTime={formData.plage_horaire[0]}
endTime={formData.plage_horaire[1]}
onStartChange={(e) => handleTimeChange(e, 0)}
onEndChange={(e) => handleTimeChange(e, 1)}
/>
{/* CheckBoxList */}
<CheckBoxList
items={daysOfWeek}
formData={formData}
handleChange={handleJoursChange}
fieldName="jours_ouverture"
horizontal={true}
labelAttenuated={isLabelAttenuated}
/>
</div>
</div>
{/* DateRange */}
<div className="space-y-4 w-full">
{formData.planning_type === 2 && (
<>
<DateRange
nameStart="date_debut_semestre_1"
nameEnd="date_fin_semestre_1"
valueStart={formData.date_debut_semestre_1}
valueEnd={formData.date_fin_semestre_1}
onChange={handleChange}
label="Semestre 1"
/>
<DateRange
nameStart="date_debut_semestre_2"
nameEnd="date_fin_semestre_2"
valueStart={formData.date_debut_semestre_2}
valueEnd={formData.date_fin_semestre_2}
onChange={handleChange}
label="Semestre 2"
/>
</>
)}
{formData.planning_type === 3 && (
<>
<DateRange
nameStart="date_debut_trimestre_1"
nameEnd="date_fin_trimestre_1"
valueStart={formData.date_debut_trimestre_1}
valueEnd={formData.date_fin_trimestre_1}
onChange={handleChange}
label="Trimestre 1"
/>
<DateRange
nameStart="date_debut_trimestre_2"
nameEnd="date_fin_trimestre_2"
valueStart={formData.date_debut_trimestre_2}
valueEnd={formData.date_fin_trimestre_2}
onChange={handleChange}
label="Trimestre 2"
/>
<DateRange
nameStart="date_debut_trimestre_3"
nameEnd="date_fin_trimestre_3"
valueStart={formData.date_debut_trimestre_3}
valueEnd={formData.date_fin_trimestre_3}
onChange={handleChange}
label="Trimestre 3"
/>
</>
)}
</div>
</div>
);
};
export default PlanningConfiguration;

View File

@ -0,0 +1,96 @@
import { BookOpen, Trash2, MoreVertical, Edit3, Plus } from 'lucide-react';
import { useState } from 'react';
import Table from '@/components/Table';
import DropdownMenu from '@/components/DropdownMenu';
import Modal from '@/components/Modal';
import SpecialityForm from '@/components/Structure/Configuration/SpecialityForm';
import { SpecialityFormProvider } from '@/context/SpecialityFormContext';
const SpecialitiesSection = ({ specialities, handleCreate, handleEdit, handleDelete }) => {
const [isOpen, setIsOpen] = useState(false);
const [editingSpeciality, setEditingSpeciality] = useState(null);
const openEditModal = (speciality) => {
setIsOpen(true);
setEditingSpeciality(speciality);
}
const closeEditModal = () => {
setIsOpen(false);
setEditingSpeciality(null);
};
const handleModalSubmit = (updatedData) => {
if (editingSpeciality) {
handleEdit(editingSpeciality.id, updatedData);
} else {
handleCreate(updatedData);
}
closeEditModal();
};
return (
<div className="mb-8">
<div className="flex justify-between items-center mb-4 max-w-4xl ml-0">
<h2 className="text-3xl text-gray-800 flex items-center">
<BookOpen className="w-8 h-8 mr-2" />
Spécialités
</h2>
<button
onClick={() => openEditModal(null)} // ouvrir le modal pour créer une nouvelle spécialité
className="flex items-center bg-emerald-600 text-white p-2 rounded-full shadow hover:bg-emerald-900 transition duration-200"
>
<Plus className="w-5 h-5" />
</button>
</div>
<div className="bg-white rounded-lg border border-gray-200 max-w-4xl ml-0">
<Table
columns={[
{
name: 'INTITULÉ',
transform: (row) => (
<div
className="inline-block px-3 py-1 rounded-full font-bold text-white"
style={{ backgroundColor: row.codeCouleur }}
title={row.codeCouleur}
>
<span className="font-bold text-white">{row.nom.toUpperCase()}</span>
</div>
)
},
{ name: 'DATE DE CREATION', transform: (row) => row.dateCreation_formattee },
{ name: 'ACTIONS', transform: (row) => (
<DropdownMenu
buttonContent={<MoreVertical size={20} className="text-gray-400 hover:text-gray-600" />}
items={[
{ label: 'Modifier', icon:Edit3, onClick: () => openEditModal(row) },
{ label: 'Supprimer', icon: Trash2, onClick: () => handleDelete(row.id) }
]
}
buttonClassName="text-gray-400 hover:text-gray-600"
menuClassName="absolute right-0 mt-2 w-48 bg-white border border-gray-200 rounded-md shadow-lg z-10 flex flex-col items-center"
/>
)}
]}
data={specialities}
/>
</div>
{isOpen && (
<SpecialityFormProvider initialSpeciality={editingSpeciality || {}}>
<Modal
isOpen={isOpen}
setIsOpen={setIsOpen}
title={editingSpeciality ? "Modification de la spécialité" : "Création d'une nouvelle spécialité"}
size='sm:w-1/6'
ContentComponent={() => (
<SpecialityForm onSubmit={handleModalSubmit} isNew={!editingSpeciality} />
)}
/>
</SpecialityFormProvider>
)}
</div>
);
};
export default SpecialitiesSection;

View File

@ -0,0 +1,66 @@
import { useState } from 'react';
import { BookOpen, Palette } from 'lucide-react';
import InputTextIcon from '@/components/InputTextIcon';
import InputColorIcon from '@/components/InputColorIcon';
import Button from '@/components/Button';
import { useSpecialityForm } from '@/context/SpecialityFormContext';
const SpecialityForm = ({ onSubmit, isNew }) => {
const { formData, setFormData } = useSpecialityForm();
const handleChange = (e) => {
const { name, value } = e.target;
setFormData((prevState) => ({
...prevState,
[name]: value,
}));
};
const handleSubmit = (e) => {
e.preventDefault();
onSubmit(formData);
};
return (
<div className="space-y-4 mt-8">
<div>
<InputTextIcon
type="text"
name="nom"
IconItem={BookOpen}
placeholder="Nom de la spécialité"
value={formData.nom}
onChange={handleChange}
className="w-full mt-4"
/>
</div>
<div className="mt-4">
<InputColorIcon
type="color"
name="codeCouleur"
IconItem={Palette}
placeholder="Nom de la spécialité"
value={formData.codeCouleur}
onChange={handleChange}
className="w-full mt-4"
/>
</div>
<div className="flex justify-end mt-4 space-x-4">
<Button text={`${isNew ? "Créer" : "Modifier"}`}
onClick={handleSubmit}
className={`px-4 py-2 rounded-md shadow-sm focus:outline-none ${
!formData.nom
? "bg-gray-300 text-gray-700 cursor-not-allowed"
: "bg-emerald-500 text-white hover:bg-emerald-600"
}`}
primary
disabled={!formData.nom}
type="submit"
name="Create" />
</div>
</div>
);
};
export default SpecialityForm;

View File

@ -0,0 +1,46 @@
import React from 'react';
import SpecialitiesSection from '@/components/Structure/Configuration/SpecialitiesSection';
import TeachersSection from '@/components/Structure/Configuration/TeachersSection';
import ClassesSection from '@/components/Structure/Configuration/ClassesSection';
import { ClassesProvider } from '@/context/ClassesContext';
import { BK_GESTIONENSEIGNANTS_SPECIALITE_URL,
BK_GESTIONENSEIGNANTS_TEACHER_URL,
BK_GESTIONENSEIGNANTS_CLASSE_URL } from '@/utils/Url';
const StructureManagement = ({ specialities, setSpecialities, teachers, setTeachers, classes, setClasses, handleCreate, handleEdit, handleDelete }) => {
return (
<div className='p-8'>
<ClassesProvider>
<SpecialitiesSection
specialities={specialities}
setSpecialities={setSpecialities}
handleCreate={(newData) => handleCreate(`${BK_GESTIONENSEIGNANTS_SPECIALITE_URL}`, newData, setSpecialities)}
handleEdit={(id, updatedData) => handleEdit(`${BK_GESTIONENSEIGNANTS_SPECIALITE_URL}`, id, updatedData, setSpecialities)}
handleDelete={(id) => handleDelete(`${BK_GESTIONENSEIGNANTS_SPECIALITE_URL}`, id, setSpecialities)}
/>
<TeachersSection
teachers={teachers}
specialities={specialities}
handleCreate={(newData) => handleCreate(`${BK_GESTIONENSEIGNANTS_TEACHER_URL}`, newData, setTeachers)}
handleEdit={(id, updatedData) => handleEdit(`${BK_GESTIONENSEIGNANTS_TEACHER_URL}`, id, updatedData, setTeachers)}
handleDelete={(id) => handleDelete(`${BK_GESTIONENSEIGNANTS_TEACHER_URL}`, id, setTeachers)}
/>
<ClassesSection
classes={classes}
specialities={specialities}
teachers={teachers}
handleCreate={(newData) => handleCreate(`${BK_GESTIONENSEIGNANTS_CLASSE_URL}`, newData, setClasses)}
handleEdit={(id, updatedData) => handleEdit(`${BK_GESTIONENSEIGNANTS_CLASSE_URL}`, id, updatedData, setClasses)}
handleDelete={(id) => handleDelete(`${BK_GESTIONENSEIGNANTS_CLASSE_URL}`, id, setClasses)}
/>
</ClassesProvider>
</div>
);
};
export default StructureManagement;

View File

@ -0,0 +1,21 @@
import React from 'react';
import { School, Calendar } from 'lucide-react';
const TabsStructure = ({ activeTab, setActiveTab, tabs }) => {
return (
<div className="flex justify-center mb-8">
{tabs.map((tab) => (
<button
key={tab.id}
className={`tab px-4 py-2 mx-2 flex items-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" />
<span>{tab.title}</span>
</button>
))}
</div>
);
};
export default TabsStructure;

View File

@ -0,0 +1,122 @@
import React, { useState } from 'react';
import { GraduationCap, Mail, BookOpen, Check } from 'lucide-react';
import InputTextIcon from '@/components/InputTextIcon';
import Button from '@/components/Button';
import CheckBoxList from '@/components/CheckBoxList';
import ToggleSwitch from '@/components/ToggleSwitch'
import { useTeacherForm } from '@/context/TeacherFormContext';
const TeacherForm = ({ onSubmit, isNew, specialities }) => {
const { formData, setFormData } = useTeacherForm();
const handleToggleChange = () => {
setFormData({ ...formData, droit: 1-formData.droit });
};
const handleChange = (e) => {
const target = e.target || e.currentTarget;
const { name, value, type, checked } = target;
if (type === 'checkbox') {
setFormData((prevState) => {
const newValues = checked
? [...(prevState[name] || []), parseInt(value, 10)]
: (prevState[name] || []).filter((v) => v !== parseInt(value, 10));
return {
...prevState,
[name]: newValues,
};
});
} else {
setFormData((prevState) => ({
...prevState,
[name]: type === 'radio' ? parseInt(value, 10) : value,
}));
}
};
const handleSubmit = () => {
onSubmit(formData, isNew);
};
const getSpecialityLabel = (speciality) => {
return `${speciality.nom}`;
};
const isLabelAttenuated = (item) => {
return !formData.specialites_ids.includes(parseInt(item.id));
};
return (
<form onSubmit={handleSubmit} className="space-y-4 mt-8">
<div>
<InputTextIcon
name="nom"
type="text"
IconItem={GraduationCap}
placeholder="Nom de l'enseignant"
value={formData.nom}
onChange={handleChange}
className="w-full"
/>
</div>
<div>
<InputTextIcon
name="prenom"
type="text"
IconItem={GraduationCap}
placeholder="Prénom de l'enseignant"
value={formData.prenom}
onChange={handleChange}
className="w-full"
/>
</div>
<div>
<InputTextIcon
name="mail"
type="email"
IconItem={Mail}
placeholder="Email de l'enseignant"
value={formData.mail}
onChange={handleChange}
className="w-full mt-4"
/>
</div>
<div className="flex space-x-4">
<CheckBoxList
items={specialities}
formData={formData}
handleChange={handleChange}
fieldName="specialites_ids"
label="Spécialités"
icon={BookOpen}
className="w-full mt-4"
itemLabelFunc={getSpecialityLabel}
labelAttenuated={isLabelAttenuated}
/>
</div>
<div className='mt-4'>
<ToggleSwitch
label="Administrateur"
checked={formData.droit}
onChange={handleToggleChange}
/>
</div>
<div className="flex justify-end mt-4 space-x-4">
<Button text={`${isNew ? "Créer" : "Modifier"}`}
onClick={handleSubmit}
className={`px-4 py-2 rounded-md shadow-sm focus:outline-none ${
(!formData.nom || !formData.prenom || !formData.mail || formData.specialites_ids.length === 0)
? "bg-gray-300 text-gray-700 cursor-not-allowed"
: "bg-emerald-500 text-white hover:bg-emerald-600"
}`}
primary
disabled={(!formData.nom || !formData.prenom || !formData.mail || formData.specialites_ids.length === 0)}
type="submit"
name="Create" />
</div>
</form>
);
};
export default TeacherForm;

View File

@ -0,0 +1,186 @@
import { GraduationCap, Trash2, MoreVertical, Edit3, Plus } from 'lucide-react';
import { useState } from 'react';
import Table from '@/components/Table';
import DropdownMenu from '@/components/DropdownMenu';
import Modal from '@/components/Modal';
import TeacherForm from '@/components/Structure/Configuration/TeacherForm';
import {BK_PROFILE_URL} from '@/utils/Url';
import useCsrfToken from '@/hooks/useCsrfToken';
import { TeacherFormProvider } from '@/context/TeacherFormContext';
const TeachersSection = ({ teachers, handleCreate, handleEdit, handleDelete, specialities }) => {
const [isOpen, setIsOpen] = useState(false);
const [editingTeacher, setEditingTeacher] = useState(null);
const csrfToken = useCsrfToken();
const openEditModal = (teacher) => {
setIsOpen(true);
setEditingTeacher(teacher);
}
const closeEditModal = () => {
setIsOpen(false);
setEditingTeacher(null);
};
const handleModalSubmit = (updatedData) => {
if (editingTeacher) {
// Modification du profil
const request = new Request(
`${BK_PROFILE_URL}/${updatedData.profilAssocie_id}`,
{
method:'PUT',
headers: {
'Content-Type':'application/json',
'X-CSRFToken': csrfToken
},
credentials: 'include',
body: JSON.stringify( {
email: updatedData.mail,
username: updatedData.mail,
droit:updatedData.droit
}),
}
);
fetch(request).then(response => response.json())
.then(response => {
console.log('Success:', response);
console.log('UpdateData:', updatedData);
handleEdit(editingTeacher.id, updatedData);
})
.catch(error => {
console.error('Error fetching data:', error);
error = error.errorMessage;
console.log(error);
});
} else {
// Création d'un profil associé à l'adresse mail du responsable saisie
// Le profil est inactif
const request = new Request(
`${BK_PROFILE_URL}`,
{
method:'POST',
headers: {
'Content-Type':'application/json',
'X-CSRFToken': csrfToken
},
credentials: 'include',
body: JSON.stringify( {
email: updatedData.mail,
password: 'Provisoire01!',
username: updatedData.mail,
is_active: 1, // On rend le profil actif : on considère qu'au moment de la configuration de l'école un abonnement a été souscrit
droit:updatedData.droit
}),
}
);
fetch(request).then(response => response.json())
.then(response => {
console.log('Success:', response);
console.log('UpdateData:', updatedData);
if (response.id) {
let idProfil = response.id;
updatedData.profilAssocie_id = idProfil;
handleCreate(updatedData);
}
})
.catch(error => {
console.error('Error fetching data:', error);
error = error.errorMessage;
console.log(error);
});
}
closeEditModal();
};
return (
<div className="mb-8">
<div className="flex justify-between items-center mb-4 max-w-8xl ml-0">
<h2 className="text-3xl text-gray-800 flex items-center">
<GraduationCap className="w-8 h-8 mr-2" />
Enseignants
</h2>
<button
onClick={() => openEditModal(null)} // ouvrir le modal pour créer une nouvelle spécialité
className="flex items-center bg-emerald-600 text-white p-2 rounded-full shadow hover:bg-emerald-900 transition duration-200"
>
<Plus className="w-5 h-5" />
</button>
</div>
<div className="bg-white rounded-lg border border-gray-200 max-w-8xl ml-0">
<Table
columns={[
{ name: 'NOM', transform: (row) => row.nom },
{ name: 'PRENOM', transform: (row) => row.prenom },
{ name: 'MAIL', transform: (row) => row.mail },
{
name: 'SPÉCIALITÉS',
transform: (row) => (
<div key={row.id} className="flex flex-wrap justify-center items-center space-x-2">
{row.specialites.map(specialite => (
<span
key={specialite.id}
className="px-3 py-1 rounded-full font-bold text-white"
style={{ backgroundColor: specialite.codeCouleur }}
title={specialite.nom}
>
{specialite.nom}
</span>
))}
</div>
)
},
{
name: 'TYPE PROFIL',
transform: (row) => {
if (row.profilAssocie) {
const badgeClass = row.DroitLabel === 'ECOLE' ? 'bg-blue-100 text-blue-600' : 'bg-red-100 text-red-600';
return (
<div key={row.id} className="flex justify-center items-center space-x-2">
<span className={`px-3 py-1 rounded-full font-bold ${badgeClass}`}>
{row.DroitLabel}
</span>
</div>
);
} else {
return <i>Non définie</i>;
}
}
},
{ name: 'DATE DE CREATION', transform: (row) => row.dateCreation_formattee },
{ name: 'ACTIONS', transform: (row) => (
<DropdownMenu
buttonContent={<MoreVertical size={20} className="text-gray-400 hover:text-gray-600" />}
items={[
{ label: 'Modifier', icon:Edit3, onClick: () => openEditModal(row) },
{ label: 'Supprimer', icon: Trash2, onClick: () => handleDelete(row.id) }
]
}
buttonClassName="text-gray-400 hover:text-gray-600"
menuClassName="absolute right-0 mt-2 w-48 bg-white border border-gray-200 rounded-md shadow-lg z-10 flex flex-col items-center"
/>
)}
]}
data={teachers}
/>
</div>
{isOpen && (
<TeacherFormProvider initialTeacher={editingTeacher || {}}>
<Modal
isOpen={isOpen}
setIsOpen={setIsOpen}
title={editingTeacher ? "Modification de l'enseignant" : "Création d'un nouvel enseignant"}
size='sm:w-1/4'
ContentComponent={() => (
<TeacherForm teacher={editingTeacher || {}} onSubmit={handleModalSubmit} isNew={!editingTeacher} specialities={specialities} />
)}
/>
</TeacherFormProvider>
)}
</div>
);
};
export default TeachersSection;

View File

@ -0,0 +1,46 @@
import React from 'react';
import Table from '@/components/Table';
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.enseignants_ids.length !== 0 ? 'text-emerald-400' : 'text-red-300'}`}>{formData.enseignants_ids.length}</span></label>
<Table
columns={[
{
name: 'Nom',
transform: (row) => row.nom,
},
{
name: 'Prénom',
transform: (row) => row.prenom,
},
{
name: 'Spécialités',
transform: (row) => (
<div className="flex flex-wrap items-center">
{row.specialites.map(specialite => (
<span key={specialite.id} className="flex items-center mr-2 mb-1">
<div
className="w-4 h-4 rounded-full mr-2"
style={{ backgroundColor: specialite.codeCouleur }}
title={specialite.nom}
></div>
<span>{specialite.nom}</span>
</span>
))}
</div>
),
},
]}
data={teachers}
onRowClick={handleTeacherSelection}
selectedRows={selectedTeachers}
isSelectable={true}
/>
</div>
);
};
export default TeachersSelectionConfiguration;

View File

@ -0,0 +1,32 @@
import React from 'react';
const TimeRange = ({ startTime, endTime, onStartChange, onEndChange }) => {
return (
<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>
<input
type="time"
name="startTime"
value={startTime}
onChange={onStartChange}
className="block w-full border border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
/>
</div>
<div className="w-1/2">
<label className="block text-sm font-medium text-gray-700 mb-2">Heure de fin</label>
<input
type="time"
name="endTime"
value={endTime}
onChange={onEndChange}
className="block w-full border border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
/>
</div>
</div>
</div>
);
};
export default TimeRange;