mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-01-29 07:53:23 +00:00
feat: Configuration et gestion du planning [#2]
This commit is contained in:
@ -1,195 +1,184 @@
|
||||
'use client'
|
||||
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||
import { DndProvider } from 'react-dnd';
|
||||
import { AnimatePresence, findSpring, motion } from 'framer-motion'; // Ajouter cet import
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import PlanningClassView from '@/components/Structure/Planning/PlanningClassView';
|
||||
import SpecialityEventModal from '@/components/Structure/Planning/SpecialityEventModal';
|
||||
import ClassesInfo from '@/components/Structure/Planning/ClassesInfo';
|
||||
import SpecialitiesList from '@/components/Structure/Planning/SpecialitiesList';
|
||||
import { BK_GESTIONENSEIGNANTS_PLANNING_URL } from '@/utils/Url';
|
||||
import { ChevronLeft, ChevronRight, Calendar } from 'lucide-react'
|
||||
import SelectChoice from '@/components/SelectChoice';
|
||||
import { useClasses } from '@/context/ClassesContext';
|
||||
import { ClasseFormProvider } from '@/context/ClasseFormContext';
|
||||
import TabsStructure from '@/components/Structure/Configuration/TabsStructure';
|
||||
import { Bookmark, Users, BookOpen, Newspaper } from 'lucide-react';
|
||||
|
||||
const ScheduleManagement = ({ schedules, setSchedules, handleUpdatePlanning, specialities, teachers, classes }) => {
|
||||
const ScheduleManagement = ({ handleUpdatePlanning, classes }) => {
|
||||
const currentYear = new Date().getFullYear();
|
||||
const currentMonth = new Date().getMonth();
|
||||
const currentSchoolYearStart = currentMonth >= 8 ? currentYear : currentYear - 1;
|
||||
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [selectedClass, setSelectedClass] = useState(null);
|
||||
const [schedule, setSchedule] = useState(null);
|
||||
const scheduleRef = useRef(null);
|
||||
const scheduleId = useRef(null);
|
||||
const [planningType, setPlanningType] = useState('TRIMESTRIEL');
|
||||
const [currentPeriod, setCurrentPeriod] = useState('T1');
|
||||
const [selectedClass, setSelectedClass] = useState(null);
|
||||
const [selectedLevel, setSelectedLevel] = useState('');
|
||||
const [schedule, setSchedule] = useState(null);
|
||||
|
||||
const planningChoices = [
|
||||
{ value: 'TRIMESTRIEL', label: 'TRIMESTRIEL' },
|
||||
{ value: 'SEMESTRIEL', label: 'SEMESTRIEL' },
|
||||
{ value: 'ANNUEL', label: 'ANNUEL' },
|
||||
];
|
||||
const { getNiveauxTabs } = useClasses();
|
||||
const niveauxLabels = Array.isArray(selectedClass?.niveaux) ? getNiveauxTabs(selectedClass.niveaux) : [];
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedClass) {
|
||||
scheduleId.current = selectedClass.planning.id;
|
||||
setSchedule(selectedClass.planning);
|
||||
} else {
|
||||
setSchedule(null);
|
||||
}
|
||||
}, [selectedClass]);
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const handleOpenModal = () => setIsModalOpen(true);
|
||||
const handleCloseModal = () => setIsModalOpen(false);
|
||||
|
||||
// Synchroniser scheduleRef avec schedule
|
||||
useEffect(() => {
|
||||
scheduleRef.current = schedule;
|
||||
}, [schedule]);
|
||||
useEffect(() => {
|
||||
if (selectedClass) {
|
||||
const defaultLevel = niveauxLabels.length > 0 ? niveauxLabels[0].id : '';
|
||||
const niveau = selectedLevel || defaultLevel;
|
||||
|
||||
const handleClassSelect = (cls) => {
|
||||
setSelectedClass(cls);
|
||||
};
|
||||
setSelectedLevel(niveau);
|
||||
|
||||
useEffect(() => {
|
||||
if (schedule) {
|
||||
console.log("Schedule data:", schedule);
|
||||
}
|
||||
}, [schedule]);
|
||||
|
||||
|
||||
// Fonction onDrop dans PlanningClassView ou un composant parent
|
||||
const onDrop = (item, hour, day) => {
|
||||
// Les données de l'élément drag and drop (spécialité par exemple)
|
||||
const { id, name, color, teachers } = item;
|
||||
|
||||
// Créez une nouvelle copie du planning
|
||||
const newSchedule = { ...scheduleRef.current };
|
||||
|
||||
// Vérifiez s'il existe déjà une entrée pour le jour
|
||||
if (!newSchedule.emploiDuTemps[day]) {
|
||||
newSchedule.emploiDuTemps[day] = [];
|
||||
}
|
||||
const courseTime = `${hour.toString().padStart(2, '0')}:00`;
|
||||
|
||||
// Rechercher s'il y a déjà un cours à l'heure spécifiée
|
||||
const existingCourseIndex = newSchedule.emploiDuTemps[day].findIndex(course =>
|
||||
course.heure === courseTime
|
||||
);
|
||||
|
||||
const newCourse = {
|
||||
duree: '1',
|
||||
heure: courseTime,
|
||||
matiere: name,
|
||||
teachers: teachers,
|
||||
color: color,
|
||||
};
|
||||
|
||||
// S'il existe déjà un cours à cette heure, on le remplace
|
||||
if (existingCourseIndex !== -1) {
|
||||
newSchedule.emploiDuTemps[day][existingCourseIndex] = newCourse;
|
||||
} else {
|
||||
// Sinon on ajoute le nouveau cours
|
||||
newSchedule.emploiDuTemps[day].push(newCourse);
|
||||
}
|
||||
|
||||
// Mettez à jour l'état du planning
|
||||
setSchedule(newSchedule)
|
||||
|
||||
// Appelez la fonction handleUpdatePlanning en dehors de setSchedule
|
||||
handleUpdatePlanning(`${BK_GESTIONENSEIGNANTS_PLANNING_URL}`, scheduleId.current, newSchedule);
|
||||
};
|
||||
|
||||
const getPeriodLabel = (period) => {
|
||||
switch(period) {
|
||||
case 'T1': return '1er trimestre';
|
||||
case 'T2': return '2e trimestre';
|
||||
case 'T3': return '3e trimestre';
|
||||
case 'S1': return '1er semestre';
|
||||
case 'S2': return '2e semestre';
|
||||
default: return period;
|
||||
}
|
||||
};
|
||||
|
||||
const handlePeriodChange = (direction) => {
|
||||
if (planningType === 'TRIMESTRIEL') {
|
||||
if (direction === 'prev') {
|
||||
setCurrentPeriod(currentPeriod === 'T1' ? 'T3' : `T${parseInt(currentPeriod.slice(1)) - 1}`);
|
||||
} else {
|
||||
setCurrentPeriod(currentPeriod === 'T3' ? 'T1' : `T${parseInt(currentPeriod.slice(1)) + 1}`);
|
||||
}
|
||||
} else if (planningType === 'SEMESTRIEL') {
|
||||
setCurrentPeriod(currentPeriod === 'S1' ? 'S2' : 'S1');
|
||||
const currentPlanning = selectedClass.plannings_read.find(planning => planning.niveau === niveau);
|
||||
setSchedule(currentPlanning ? currentPlanning.planning : {});
|
||||
}
|
||||
}, [selectedClass, niveauxLabels]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedClass && selectedLevel) {
|
||||
const currentPlanning = selectedClass.plannings_read.find(planning => planning.niveau === selectedLevel);
|
||||
setSchedule(currentPlanning ? currentPlanning.planning : {});
|
||||
}
|
||||
}, [selectedClass, selectedLevel]);
|
||||
|
||||
const handleLevelSelect = (niveau) => {
|
||||
setSelectedLevel(niveau);
|
||||
};
|
||||
|
||||
const handleClassSelect = (classId) => {
|
||||
const selectedClasse = categorizedClasses['Actives'].find(classe => classe.id === classId);
|
||||
setSelectedClass(selectedClasse);
|
||||
setSelectedLevel('');
|
||||
};
|
||||
|
||||
const onDrop = (item, hour, day) => {
|
||||
const { id, name, color, teachers } = item;
|
||||
const newSchedule = { ...schedule, emploiDuTemps: schedule.emploiDuTemps || {} };
|
||||
|
||||
if (!newSchedule.emploiDuTemps[day]) {
|
||||
newSchedule.emploiDuTemps[day] = [];
|
||||
}
|
||||
const courseTime = `${hour.toString().padStart(2, '0')}:00`;
|
||||
|
||||
const existingCourseIndex = newSchedule.emploiDuTemps[day].findIndex(course => course.heure === courseTime);
|
||||
|
||||
const newCourse = {
|
||||
duree: '1',
|
||||
heure: courseTime,
|
||||
matiere: name,
|
||||
teachers: teachers,
|
||||
color: color,
|
||||
};
|
||||
|
||||
if (existingCourseIndex !== -1) {
|
||||
newSchedule.emploiDuTemps[day][existingCourseIndex] = newCourse;
|
||||
} else {
|
||||
newSchedule.emploiDuTemps[day].push(newCourse);
|
||||
}
|
||||
|
||||
// Mettre à jour scheduleRef
|
||||
setSchedule(newSchedule)
|
||||
|
||||
// Utiliser `handleUpdatePlanning` pour mettre à jour le planning du niveau de la classe
|
||||
const planningId = selectedClass.plannings_read.find(planning => planning.niveau === selectedLevel)?.planning.id;
|
||||
if (planningId) {
|
||||
console.log('newSchedule : ', newSchedule)
|
||||
handleUpdatePlanning(BK_GESTIONENSEIGNANTS_PLANNING_URL, planningId, newSchedule);
|
||||
}
|
||||
};
|
||||
|
||||
// Fonctionnalité de gestion des emplois du temps
|
||||
return (
|
||||
<div className="flex flex-col h-full overflow-hidden">
|
||||
<DndProvider backend={HTML5Backend}>
|
||||
<ClassesInfo classes={classes} onClassSelect={handleClassSelect}/>
|
||||
const categorizedClasses = classes.reduce((acc, classe) => {
|
||||
const { annee_scolaire } = classe;
|
||||
const [startYear] = annee_scolaire.split('-').map(Number);
|
||||
const category = startYear >= currentSchoolYearStart ? 'Actives' : 'Anciennes';
|
||||
|
||||
<div className="flex justify-between items-center p-4 w-full">
|
||||
<div className="flex items-center w-full">
|
||||
<SelectChoice
|
||||
name="planningType"
|
||||
IconItem={Calendar}
|
||||
selected={planningType}
|
||||
choices={planningChoices}
|
||||
callback={(e) => {
|
||||
setPlanningType(e.target.value);
|
||||
setCurrentPeriod(e.target.value === 'TRIMESTRIEL' ? 'T1' : 'S1');
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
if (!acc[category]) {
|
||||
acc[category] = [];
|
||||
}
|
||||
acc[category].push(classe);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
{planningType !== 'ANNUEL' && (
|
||||
<div className="flex items-center justify-center w-full">
|
||||
<button
|
||||
onClick={() => handlePeriodChange('prev')}
|
||||
className={`mr-4 p-2 border rounded-lg ${
|
||||
currentPeriod === 'T1' || currentPeriod === 'S1' ? 'bg-gray-300 text-gray-700 cursor-not-allowed' : 'bg-emerald-500 text-white hover:bg-emerald-600'
|
||||
} transition-colors duration-300`}
|
||||
disabled={currentPeriod === 'T1' || currentPeriod === 'S1'}
|
||||
>
|
||||
<ChevronLeft className="h-6 w-6" />
|
||||
</button>
|
||||
<span className="text-lg font-semibold mx-4">{getPeriodLabel(currentPeriod)}</span>
|
||||
<button
|
||||
onClick={() => handlePeriodChange('next')}
|
||||
className={`ml-4 p-2 border rounded-lg ${
|
||||
(planningType === 'TRIMESTRIEL' && currentPeriod === 'T3') || (planningType === 'SEMESTRIEL' && currentPeriod === 'S2') ? 'bg-gray-300 text-gray-700 cursor-not-allowed' : 'bg-emerald-500 text-white hover:bg-emerald-600'
|
||||
} transition-colors duration-300`}
|
||||
disabled={(planningType === 'TRIMESTRIEL' && currentPeriod === 'T3') || (planningType === 'SEMESTRIEL' && currentPeriod === 'S2')}
|
||||
>
|
||||
<ChevronRight className="h-6 w-6" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex-1 max-h-[calc(100vh-192px)] overflow-hidden">
|
||||
<AnimatePresence mode="wait">
|
||||
<motion.div
|
||||
key="year"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -20 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
>
|
||||
<PlanningClassView
|
||||
schedule={schedule}
|
||||
onDrop={onDrop}
|
||||
planningType={planningType}
|
||||
currentPeriod={currentPeriod}
|
||||
/>
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</DndProvider>
|
||||
{/* <SpecialityEventModal
|
||||
isOpen={isModalOpen}
|
||||
onClose={() => setIsModalOpen(false)}
|
||||
eventData={eventData}
|
||||
setEventData={setEventData}
|
||||
selectedClass={selectedClass}
|
||||
/> */}
|
||||
return (
|
||||
<div className="flex flex-col h-full">
|
||||
<DndProvider backend={HTML5Backend}>
|
||||
<div className="p-4 bg-gray-100 border-b">
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
|
||||
{/* Colonne Classes */}
|
||||
<div className="p-4 bg-gray-50 rounded-lg shadow-inner">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h2 className="text-3xl text-gray-800 flex items-center">
|
||||
<Users className="w-8 h-8 mr-2" />
|
||||
Classes
|
||||
</h2>
|
||||
</div>
|
||||
{categorizedClasses['Actives'] &&
|
||||
<TabsStructure
|
||||
activeTab={selectedClass?.id}
|
||||
setActiveTab={handleClassSelect}
|
||||
tabs={categorizedClasses['Actives'].map(classe => ({
|
||||
id: classe.id,
|
||||
title: classe.nom_ambiance,
|
||||
icon: Users
|
||||
}))}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
|
||||
{/* Colonne Niveaux */}
|
||||
<div className="p-4 bg-gray-50 rounded-lg shadow-inner">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h2 className="text-3xl text-gray-800 flex items-center">
|
||||
<Bookmark className="w-8 h-8 mr-2" />
|
||||
Niveaux
|
||||
</h2>
|
||||
</div>
|
||||
{niveauxLabels &&
|
||||
<TabsStructure activeTab={selectedLevel} setActiveTab={handleLevelSelect} tabs={niveauxLabels} />
|
||||
}
|
||||
</div>
|
||||
|
||||
{/* Colonne Spécialités */}
|
||||
<div className="p-4 bg-gray-50 rounded-lg shadow-inner">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h2 className="text-3xl text-gray-800 flex items-center">
|
||||
<BookOpen className="w-8 h-8 mr-2" />
|
||||
Spécialités
|
||||
</h2>
|
||||
</div>
|
||||
<SpecialitiesList teachers={selectedClass ? selectedClass.enseignants : []} />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
<div className="flex-1 p-4 overflow-y-auto">
|
||||
<AnimatePresence mode="wait">
|
||||
<motion.div
|
||||
key="year"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -20 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
className="flex-1 relative"
|
||||
>
|
||||
<ClasseFormProvider initialClasse={selectedClass || {}}>
|
||||
<PlanningClassView schedule={schedule} onDrop={onDrop} selectedLevel={selectedLevel} handleUpdatePlanning={handleUpdatePlanning} classe={selectedClass} />
|
||||
</ClasseFormProvider>
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</DndProvider>
|
||||
</div>
|
||||
);
|
||||
|
||||
};
|
||||
|
||||
export default ScheduleManagement;
|
||||
|
||||
Reference in New Issue
Block a user