feat: Configuration et gestion du planning [#2]

This commit is contained in:
N3WT DE COMPET
2025-01-11 19:37:29 +01:00
parent 3c27133cdb
commit 830d9a48c0
26 changed files with 1163 additions and 1071 deletions

View File

@ -1,112 +1,181 @@
import React, {useRef} from 'react';
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { format, isToday, isSameDay, startOfWeek, addDays } from 'date-fns';
import { format, addDays, startOfWeek } from 'date-fns';
import { fr } from 'date-fns/locale';
import DropTargetCell from './DropTargetCell';
import DropTargetCell from '@/components/Structure/Planning/DropTargetCell';
import { useClasseForm } from '@/context/ClasseFormContext';
import { useClasses } from '@/context/ClassesContext';
import { Calendar } from 'lucide-react';
import SpecialityEventModal from '@/components/Structure/Planning/SpecialityEventModal'; // Assurez-vous du bon chemin d'importation
const PlanningClassView = ({ schedule, onDrop, planningType }) => {
const weekDays = ["lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi"];
const isCurrentWeek = weekDays.some(day => isSameDay(day, new Date()));
const scrollContainerRef = useRef(null);
// Calcul des dates des jours de la semaine
const startDate = startOfWeek(new Date(), { weekStartsOn: 1 }); // Début de la semaine (lundi)
const weekDayDates = weekDays.map((_, index) => addDays(startDate, index));
const PlanningClassView = ({ schedule, onDrop, selectedLevel, handleUpdatePlanning, classe }) => {
const { formData } = useClasseForm();
const { determineInitialPeriod } = useClasses();
// Fonction pour formater l'heure
const formatTime = (time) => {
const [hour, minute] = time.split(':');
return `${hour}h${minute}`;
};
const [currentPeriod, setCurrentPeriod] = useState(schedule?.emploiDuTemps ? determineInitialPeriod(schedule.emploiDuTemps) : null);
const [isModalOpen, setIsModalOpen] = useState(false);
const [selectedCell, setSelectedCell] = useState(null);
const [existingEvent, setExistingEvent] = useState(null);
const renderCells = () => {
const cells = [];
const timeSlots = Array.from({ length: 12 }, (_, index) => index + 8); // Heures de 08:00 à 19:00
timeSlots.forEach(hour => {
cells.push(
<div key={`hour-${hour}`} className="h-20 p-1 text-right text-sm text-gray-500 bg-gray-100 font-medium">
{`${hour.toString().padStart(2, '0')}:00`}
</div>
);
weekDays.forEach(day => {
const daySchedule = schedule?.emploiDuTemps?.[day] || [];
const courses = daySchedule.filter(course => {
const courseHour = parseInt(course.heure.split(':')[0], 10);
const courseDuration = parseInt(course.duree, 10); // Utiliser la durée comme un nombre
return courseHour <= hour && hour < (courseHour + courseDuration);
});
const course = courses.length > 0 ? courses[0] : null;
cells.push(
<DropTargetCell
key={`${day}-${hour}`}
day={day}
hour={hour}
course={course}
onDrop={onDrop}
/>
);
});
});
return cells;
};
// Calculer la position de la ligne de temps
const getCurrentTimePosition = () => {
const hours = currentTime.getHours();
const minutes = currentTime.getMinutes();
if (hours < 8 || hours > 18) {
return -1; // Hors de la plage horaire
useEffect(() => {
if (schedule?.emploiDuTemps) {
setCurrentPeriod(determineInitialPeriod(schedule.emploiDuTemps));
}
const totalMinutes = (hours - 8) * 60 + minutes; // Minutes écoulées depuis 8h
const cellHeight = 80; // Hauteur des cellules (par exemple 80px pour 20rem / 24)
}, [schedule]);
if (!schedule || !schedule.emploiDuTemps) {
return (
<div className="w-full 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">
<Calendar className="w-8 h-8 mr-2" />
Planning
</h2>
</div>
</div>
);
}
const emploiDuTemps = schedule.emploiDuTemps[currentPeriod] || schedule.emploiDuTemps;
const joursOuverture = Object.keys(emploiDuTemps);
const currentWeekDays = joursOuverture
.map(day => {
switch(day.toLowerCase()) {
case 'lundi': return 1;
case 'mardi': return 2;
case 'mercredi': return 3;
case 'jeudi': return 4;
case 'vendredi': return 5;
case 'samedi': return 6;
case 'dimanche': return 7;
default: return 0;
}
})
.sort((a, b) => a - b) // Trier les jours dans l'ordre croissant
.map(day => addDays(startOfWeek(new Date(), { weekStartsOn: 1 }), day - 1)); // Calculer les dates à partir du lundi
const getFilteredEvents = (day, time, level) => {
const [hour, minute] = time.split(':').map(Number);
const startTime = hour + minute / 60; // Convertir l'heure en fraction d'heure
return emploiDuTemps[day.toLowerCase()]?.filter(event => {
const [eventHour, eventMinute] = event.heure.split(':').map(Number);
const eventStartTime = eventHour + eventMinute / 60;
const eventEndTime = eventStartTime + parseFloat(event.duree);
// Filtrer en fonction du selectedLevel
return schedule.niveau === level && startTime >= eventStartTime && startTime < eventEndTime;
}) || [];
};
const handleCellClick = (hour, day) => {
const cellEvents = getFilteredEvents(day, hour, selectedLevel);
setSelectedCell({ hour, day, selectedLevel });
setExistingEvent(cellEvents.length ? cellEvents[0] : null);
setIsModalOpen(true);
};
const renderTimeSlots = () => {
const timeSlots = [];
const position = (totalMinutes / 60) * cellHeight;
return position;
for (let hour = parseInt(formData.plage_horaire[0], 10); hour <= parseInt(formData.plage_horaire[1], 10); hour++) {
const hourString = hour.toString().padStart(2, '0');
timeSlots.push(
<React.Fragment key={`${hourString}:00-${Math.random()}`}>
<div className="h-20 p-1 text-right text-sm text-gray-500 bg-gray-100 font-medium">
{`${hourString}:00`}
</div>
{currentWeekDays.map((date, index) => {
const day = format(date, 'iiii', { locale: fr }).toLowerCase();
const uniqueKey = `${hourString}:00-${day}-${index}`;
return (
<div key={uniqueKey} className="flex flex-col">
<DropTargetCell
hour={`${hourString}:00`}
day={day}
courses={getFilteredEvents(day, `${hourString}:00`, selectedLevel)}
onDrop={onDrop}
onClick={(hour, day) => handleCellClick(hour, day)}
/>
<DropTargetCell
hour={`${hourString}:30`}
day={day}
courses={getFilteredEvents(day, `${hourString}:30`, selectedLevel)}
onDrop={onDrop}
onClick={(hour, day) => handleCellClick(hour, day)}
/>
</div>
);
})}
</React.Fragment>
);
}
return timeSlots;
};
return (
<div className="flex-1 max-h-[calc(100vh-192px)] overflow-hidden">
{/* En-tête des jours */}
<div className="grid gap-[1px] bg-gray-100 w-full" style={{ gridTemplateColumns: "2.5rem repeat(6, 1fr)" }}>
<div className="bg-white h-14"></div>
{weekDayDates.map((day) => (
<div
key={day}
className={`p-3 text-center border-b ${isToday(day) ? 'bg-emerald-400 border-x border-emerald-600' : 'bg-white'}`} >
<div className={`text font-medium ${isToday(day) ? 'text-white' : 'text-gray-500'}`}>
{format(day, 'EEEE', { locale: fr })}
</div>
</div>
))}
</div>
{/* Grille horaire */}
<div ref={scrollContainerRef} className="flex-1 overflow-y-auto relative">
{isCurrentWeek && (
<div
className="absolute left-0 right-0 z-10 border-emerald-500 border pointer-events-none"
style={{
top: getCurrentTimePosition(),
}}
>
<div
className="absolute -left-2 -top-1 w-2 h-2 rounded-full bg-emerald-500"
/>
<div className="w-full 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">
<Calendar className="w-8 h-8 mr-2" />
Planning
</h2>
{schedule.emploiDuTemps.S1 && schedule.emploiDuTemps.S2 && (
<div>
<button onClick={() => setCurrentPeriod('S1')} className={`px-4 py-2 ${currentPeriod === 'S1' ? 'bg-emerald-600 text-white' : 'bg-gray-200 text-gray-800'}`}>
Semestre 1
</button>
<button onClick={() => setCurrentPeriod('S2')} className={`px-4 py-2 ${currentPeriod === 'S2' ? 'bg-emerald-600 text-white' : 'bg-gray-200 text-gray-800'}`}>
Semestre 2
</button>
</div>
)}
<div className={`grid gap-[1px] bg-white`} style={{ gridTemplateColumns: `2.5rem repeat(6, 1fr)` }}>
{renderCells()}
{schedule.emploiDuTemps.T1 && schedule.emploiDuTemps.T2 && schedule.emploiDuTemps.T3 && (
<div>
<button onClick={() => setCurrentPeriod('T1')} className={`px-4 py-2 ${currentPeriod === 'T1' ? 'bg-emerald-600 text-white' : 'bg-gray-200 text-gray-800'}`}>
Trimestre 1
</button>
<button onClick={() => setCurrentPeriod('T2')} className={`px-4 py-2 ${currentPeriod === 'T2' ? 'bg-emerald-600 text-white' : 'bg-gray-200 text-gray-800'}`}>
Trimestre 2
</button>
<button onClick={() => setCurrentPeriod('T3')} className={`px-4 py-2 ${currentPeriod === 'T3' ? 'bg-emerald-600 text-white' : 'bg-gray-200 text-gray-800'}`}>
Trimestre 3
</button>
</div>
)}
</div>
<div className="flex-1 max-h-[calc(100vh-192px)] overflow-hidden">
{/* En-tête des jours */}
<div className="grid w-full" style={{ gridTemplateColumns: `2.5rem repeat(${currentWeekDays.length}, 1fr)` }}>
<div className="bg-gray-50 h-14"></div>
{currentWeekDays.map((date, index) => (
<div
key={`${date}-${index}`}
className="p-3 text-center bg-emerald-100 text-emerald-800 border-r border-emerald-200">
<div className="text font-semibold">
{format(date, 'EEEE', { locale: fr })}
</div>
</div>
))}
</div>
{/* Contenu du planning */}
<div className="flex-1 overflow-y-auto relative" style={{ maxHeight: 'calc(100vh - 300px)' }}>
<div className="grid bg-white relative" style={{ gridTemplateColumns: `2.5rem repeat(${currentWeekDays.length}, 1fr)` }}>
{renderTimeSlots()}
</div>
</div>
</div>
<SpecialityEventModal
isOpen={isModalOpen}
onClose={() => setIsModalOpen(false)}
selectedCell={selectedCell}
existingEvent={existingEvent}
handleUpdatePlanning={handleUpdatePlanning}
classe={classe}
/>
</div>
);
};
@ -124,6 +193,11 @@ PlanningClassView.propTypes = {
})
)
).isRequired,
plageHoraire: PropTypes.shape({
startHour: PropTypes.number.isRequired,
endHour: PropTypes.number.isRequired,
}).isRequired,
joursOuverture: PropTypes.arrayOf(PropTypes.number).isRequired,
}).isRequired,
};