mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-04-03 16:51:26 +00:00
588 lines
18 KiB
JavaScript
588 lines
18 KiB
JavaScript
import { Trash2, Edit3, ZoomIn, Users, Check, X, Hand } from 'lucide-react';
|
|
import React, { useState, useEffect } from 'react';
|
|
import Table from '@/components/Table';
|
|
import Popup from '@/components/Popup';
|
|
import InputText from '@/components/Form/InputText';
|
|
import SelectChoice from '@/components/Form/SelectChoice';
|
|
import TeacherItem from '@/components/Structure/Configuration/TeacherItem';
|
|
import MultiSelect from '@/components/Form/MultiSelect';
|
|
import LevelLabel from '@/components/CustomLabels/LevelLabel';
|
|
import { DndProvider, useDrop } from 'react-dnd';
|
|
import { HTML5Backend } from 'react-dnd-html5-backend';
|
|
import logger from '@/utils/logger';
|
|
import SectionHeader from '@/components/SectionHeader';
|
|
import { useEstablishment } from '@/context/EstablishmentContext';
|
|
import { FE_ADMIN_STRUCTURE_SCHOOLCLASS_MANAGEMENT_URL } from '@/utils/Url';
|
|
import { usePlanning } from '@/context/PlanningContext';
|
|
import { useClasses } from '@/context/ClassesContext';
|
|
import { useRouter } from 'next/navigation';
|
|
import AlertMessage from '@/components/AlertMessage';
|
|
|
|
const ItemTypes = {
|
|
TEACHER: 'teacher',
|
|
};
|
|
|
|
const TeachersDropZone = ({
|
|
classe,
|
|
handleTeachersChange,
|
|
teachers,
|
|
isEditing,
|
|
}) => {
|
|
const [localTeachers, setLocalTeachers] = useState(
|
|
classe.teachers_details || []
|
|
);
|
|
|
|
useEffect(() => {}, [teachers]);
|
|
|
|
useEffect(() => {
|
|
setLocalTeachers(classe.teachers_details || []);
|
|
}, [classe.teachers_details]);
|
|
|
|
useEffect(() => {
|
|
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);
|
|
if (!exists) {
|
|
setLocalTeachers((prevTeachers) => {
|
|
const updatedTeachers = [
|
|
...prevTeachers,
|
|
{
|
|
id: item.id,
|
|
last_name: teacherDetails.last_name,
|
|
first_name: teacherDetails.first_name,
|
|
},
|
|
];
|
|
return updatedTeachers;
|
|
});
|
|
}
|
|
},
|
|
collect: (monitor) => ({
|
|
isOver: !!monitor.isOver(),
|
|
canDrop: !!monitor.canDrop(),
|
|
}),
|
|
canDrop: () => {
|
|
return isEditing;
|
|
},
|
|
});
|
|
|
|
const handleRemoveTeacher = (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' : ''
|
|
}`}
|
|
>
|
|
{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} />
|
|
{isEditing && (
|
|
<button
|
|
type="button"
|
|
onClick={() => handleRemoveTeacher(teacher.id)}
|
|
className="text-red-500 hover:text-red-700"
|
|
>
|
|
<X className="w-4 h-4" />
|
|
</button>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
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 [removePopupVisible, setRemovePopupVisible] = useState(false);
|
|
const [removePopupMessage, setRemovePopupMessage] = useState('');
|
|
const [removePopupOnConfirm, setRemovePopupOnConfirm] = useState(() => {});
|
|
const { selectedEstablishmentId, profileRole } = useEstablishment();
|
|
const { addSchedule, reloadPlanning, reloadEvents } = usePlanning();
|
|
const { getNiveauxLabels, allNiveaux } = useClasses();
|
|
const router = useRouter();
|
|
|
|
// Fonction pour générer les années scolaires
|
|
const getSchoolYearChoices = () => {
|
|
const currentDate = new Date();
|
|
const currentYear = currentDate.getFullYear();
|
|
const currentMonth = currentDate.getMonth() + 1; // Les mois sont indexés à partir de 0
|
|
|
|
// Si nous sommes avant septembre, l'année scolaire en cours a commencé l'année précédente
|
|
const startYear = currentMonth >= 9 ? currentYear : currentYear - 1;
|
|
|
|
const choices = [];
|
|
for (let i = 0; i < 3; i++) {
|
|
const year = startYear + i;
|
|
choices.push({
|
|
value: `${year}-${year + 1}`,
|
|
label: `${year}-${year + 1}`,
|
|
});
|
|
}
|
|
return choices;
|
|
};
|
|
|
|
// Récupération des messages d'erreur
|
|
const getError = (field) => {
|
|
return localErrors?.[field]?.[0];
|
|
};
|
|
|
|
const handleAddClass = () => {
|
|
setNewClass({
|
|
id: Date.now(),
|
|
atmosphere_name: '',
|
|
age_range: '',
|
|
levels: [],
|
|
number_of_students: null,
|
|
school_year: '',
|
|
teachers: [],
|
|
establishment: selectedEstablishmentId,
|
|
});
|
|
setFormData({
|
|
atmosphere_name: '',
|
|
age_range: '',
|
|
levels: [],
|
|
number_of_students: null,
|
|
school_year: '',
|
|
teachers: [],
|
|
establishment: selectedEstablishmentId,
|
|
});
|
|
};
|
|
|
|
const handleChange = (e) => {
|
|
const { name, value } = e.target;
|
|
|
|
if (editingClass) {
|
|
setFormData((prevData) => ({
|
|
...prevData,
|
|
[name]: value,
|
|
}));
|
|
} else if (newClass) {
|
|
setNewClass((prevData) => ({
|
|
...prevData,
|
|
[name]: value,
|
|
}));
|
|
}
|
|
};
|
|
|
|
const handleSaveNewClass = () => {
|
|
if (
|
|
newClass.atmosphere_name &&
|
|
newClass.levels.length > 0 &&
|
|
newClass.school_year
|
|
) {
|
|
handleCreate(newClass)
|
|
.then((createdClass) => {
|
|
setClasses((prevClasses) => [createdClass, ...classes]);
|
|
setNewClass(null);
|
|
setLocalErrors({});
|
|
// Creation des plannings associé à la classe
|
|
|
|
createdClass.levels.forEach((level) => {
|
|
const levelName = allNiveaux.find((lvl) => lvl.id === level)?.name;
|
|
const planningName = `${createdClass.atmosphere_name} - ${levelName}`;
|
|
const newPlanning = {
|
|
name: planningName,
|
|
color: '#FF5733', // Couleur par défaut
|
|
school_class: createdClass.id,
|
|
};
|
|
addSchedule(newPlanning);
|
|
});
|
|
})
|
|
.catch((error) => {
|
|
logger.error('Error:', error.message);
|
|
if (error.details) {
|
|
logger.error('Form errors:', error.details);
|
|
setLocalErrors(error.details);
|
|
}
|
|
});
|
|
} else {
|
|
setPopupMessage('Tous les champs doivent être remplis et valides');
|
|
setPopupVisible(true);
|
|
}
|
|
};
|
|
|
|
const handleUpdateClass = (id, updatedData) => {
|
|
if (
|
|
updatedData.atmosphere_name &&
|
|
updatedData.levels.length > 0 &&
|
|
updatedData.school_year
|
|
) {
|
|
handleEdit(id, updatedData)
|
|
.then((updatedClass) => {
|
|
setClasses((prevClasses) =>
|
|
prevClasses.map((classe) =>
|
|
classe.id === id ? updatedClass : classe
|
|
)
|
|
);
|
|
setEditingClass(null);
|
|
setFormData({});
|
|
setLocalErrors({});
|
|
})
|
|
.catch((error) => {
|
|
logger.error('Error:', error.message);
|
|
if (error.details) {
|
|
logger.error('Form errors:', error.details);
|
|
setLocalErrors(error.details);
|
|
}
|
|
});
|
|
} else {
|
|
setPopupMessage('Tous les champs doivent être remplis et valides');
|
|
setPopupVisible(true);
|
|
}
|
|
};
|
|
|
|
const handleTeachersChange = (selectedTeachers) => {
|
|
if (editingClass) {
|
|
setFormData((prevData) => ({
|
|
...prevData,
|
|
teachers: selectedTeachers,
|
|
}));
|
|
} else if (newClass) {
|
|
setNewClass((prevData) => ({
|
|
...prevData,
|
|
teachers: selectedTeachers,
|
|
}));
|
|
setFormData((prevData) => ({
|
|
...prevData,
|
|
teachers: selectedTeachers,
|
|
}));
|
|
}
|
|
};
|
|
|
|
const handleMultiSelectChange = (selectedOptions) => {
|
|
const levels = selectedOptions.map((option) => option.id);
|
|
|
|
if (editingClass) {
|
|
setFormData((prevData) => ({
|
|
...prevData,
|
|
levels,
|
|
}));
|
|
} else if (newClass) {
|
|
setNewClass((prevData) => ({
|
|
...prevData,
|
|
levels,
|
|
}));
|
|
setFormData((prevData) => ({
|
|
...prevData,
|
|
levels,
|
|
}));
|
|
}
|
|
};
|
|
|
|
const renderClassCell = (classe, column) => {
|
|
const isEditing = editingClass === classe.id;
|
|
const isCreating = newClass && newClass.id === classe.id;
|
|
const currentData = isEditing ? formData : newClass || {};
|
|
|
|
if (isEditing || isCreating) {
|
|
switch (column) {
|
|
case 'AMBIANCE':
|
|
return (
|
|
<InputText
|
|
name="atmosphere_name"
|
|
value={currentData.atmosphere_name}
|
|
onChange={handleChange}
|
|
placeholder="Nom d'ambiance"
|
|
errorMsg={getError('atmosphere_name')}
|
|
/>
|
|
);
|
|
case "TRANCHE D'AGE":
|
|
return (
|
|
<InputText
|
|
name="age_range"
|
|
value={currentData.age_range}
|
|
onChange={handleChange}
|
|
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)
|
|
)
|
|
: []
|
|
}
|
|
onChange={handleMultiSelectChange}
|
|
errorMsg={getError('levels')}
|
|
/>
|
|
);
|
|
case 'CAPACITE':
|
|
return (
|
|
<InputText
|
|
name="number_of_students"
|
|
type="number"
|
|
value={currentData.number_of_students}
|
|
onChange={handleChange}
|
|
placeholder="Capacité"
|
|
errorMsg={getError('number_of_students')}
|
|
/>
|
|
);
|
|
case 'ANNÉE SCOLAIRE':
|
|
return (
|
|
<SelectChoice
|
|
type="select"
|
|
name="school_year"
|
|
placeHolder="Sélectionnez une année scolaire"
|
|
choices={getSchoolYearChoices()}
|
|
callback={handleChange}
|
|
selected={currentData.school_year || ''}
|
|
errorMsg={getError('school_year')}
|
|
IconItem={null}
|
|
disabled={false}
|
|
/>
|
|
);
|
|
case 'ENSEIGNANTS':
|
|
return (
|
|
<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()
|
|
}
|
|
className="text-green-500 hover:text-green-700"
|
|
>
|
|
<Check className="w-5 h-5" />
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={() =>
|
|
isEditing ? setEditingClass(null) : setNewClass(null)
|
|
}
|
|
className="text-red-500 hover:text-red-700"
|
|
>
|
|
<X className="w-5 h-5" />
|
|
</button>
|
|
</div>
|
|
);
|
|
default:
|
|
return null;
|
|
}
|
|
} else {
|
|
switch (column) {
|
|
case 'AMBIANCE':
|
|
return classe.atmosphere_name;
|
|
case "TRANCHE D'AGE":
|
|
return classe.age_range;
|
|
case 'NIVEAUX':
|
|
const levelLabels = Array.isArray(classe.levels)
|
|
? getNiveauxLabels(classe.levels.sort((a, b) => a - b)) // Trier les niveaux par ordre croissant
|
|
: [];
|
|
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'}
|
|
</div>
|
|
);
|
|
case 'CAPACITE':
|
|
return classe.number_of_students;
|
|
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}
|
|
/>
|
|
))}
|
|
</div>
|
|
);
|
|
case 'MISE A JOUR':
|
|
return classe.updated_date_formatted;
|
|
case 'ACTIONS':
|
|
// Affichage des actions en mode affichage (hors édition/création)
|
|
if (profileRole === 0) {
|
|
// Si professeur, uniquement le bouton ZoomIn
|
|
return (
|
|
<div className="flex justify-center space-x-2">
|
|
<button
|
|
type="button"
|
|
onClick={() => {
|
|
const url = `${FE_ADMIN_STRUCTURE_SCHOOLCLASS_MANAGEMENT_URL}?schoolClassId=${classe.id}`;
|
|
router.push(`${url}`);
|
|
}}
|
|
className="text-gray-500 hover:text-gray-700"
|
|
>
|
|
<ZoomIn className="w-5 h-5" />
|
|
</button>
|
|
</div>
|
|
);
|
|
}
|
|
// Sinon, toutes les actions (admin)
|
|
return (
|
|
<div className="flex justify-center space-x-2">
|
|
<button
|
|
type="button"
|
|
onClick={() =>
|
|
setEditingClass(classe.id) || setFormData(classe)
|
|
}
|
|
className="text-blue-500 hover:text-blue-700"
|
|
>
|
|
<Edit3 className="w-5 h-5" />
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={() => {
|
|
setRemovePopupVisible(true);
|
|
setRemovePopupMessage(
|
|
'Attention ! \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) => {
|
|
logger.debug('Success:', data);
|
|
setPopupMessage(
|
|
'La classe ' +
|
|
classe.atmosphere_name +
|
|
' a été correctement supprimée'
|
|
);
|
|
setPopupVisible(true);
|
|
setRemovePopupVisible(false);
|
|
reloadPlanning();
|
|
reloadEvents();
|
|
})
|
|
.catch((error) => {
|
|
logger.error('Error archiving data:', error);
|
|
setPopupMessage(
|
|
'Erreur lors de la suppression de la classe ' +
|
|
classe.atmosphere_name
|
|
);
|
|
setPopupVisible(true);
|
|
setRemovePopupVisible(false);
|
|
});
|
|
});
|
|
}}
|
|
className="text-red-500 hover:text-red-700"
|
|
>
|
|
<Trash2 className="w-5 h-5" />
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={() => {
|
|
const url = `${FE_ADMIN_STRUCTURE_SCHOOLCLASS_MANAGEMENT_URL}?schoolClassId=${classe.id}`;
|
|
router.push(`${url}`);
|
|
}}
|
|
className="text-gray-500 hover:text-gray-700"
|
|
>
|
|
<ZoomIn className="w-5 h-5" />
|
|
</button>
|
|
</div>
|
|
);
|
|
default:
|
|
return null;
|
|
}
|
|
}
|
|
};
|
|
|
|
const columns = [
|
|
{ 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' },
|
|
];
|
|
|
|
return (
|
|
<DndProvider backend={HTML5Backend}>
|
|
<div className="space-y-4">
|
|
<SectionHeader
|
|
icon={Users}
|
|
title="Liste des classes"
|
|
description="Gérez les classes de votre école"
|
|
button={profileRole !== 0}
|
|
onClick={handleAddClass}
|
|
/>
|
|
<Table
|
|
data={newClass ? [newClass, ...classes] : classes}
|
|
columns={columns}
|
|
renderCell={renderClassCell}
|
|
emptyMessage={
|
|
<AlertMessage
|
|
type="warning"
|
|
title="Aucune classe enregistrée"
|
|
message="Veuillez procéder à la création d'une nouvelle classe."
|
|
/>
|
|
}
|
|
/>
|
|
<Popup
|
|
isOpen={popupVisible}
|
|
message={popupMessage}
|
|
onConfirm={() => setPopupVisible(false)}
|
|
onCancel={() => setPopupVisible(false)}
|
|
uniqueConfirmButton={true}
|
|
/>
|
|
<Popup
|
|
isOpen={removePopupVisible}
|
|
message={removePopupMessage}
|
|
onConfirm={removePopupOnConfirm}
|
|
onCancel={() => setRemovePopupVisible(false)}
|
|
/>
|
|
</div>
|
|
</DndProvider>
|
|
);
|
|
};
|
|
|
|
export default ClassesSection;
|