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,81 +0,0 @@
import React, { useState } from 'react';
import SelectChoice from '@/components/SelectChoice';
import { Users } from 'lucide-react';
import DraggableSpeciality from '@/components/Structure/Planning/DraggableSpeciality';
const groupSpecialitiesBySubject = (enseignants) => {
const groupedSpecialities = {};
enseignants.forEach(teacher => {
teacher.specialites.forEach(specialite => {
if (!groupedSpecialities[specialite.id]) {
groupedSpecialities[specialite.id] = {
...specialite,
teachers: [`${teacher.nom} ${teacher.prenom}`],
};
} else {
groupedSpecialities[specialite.id].teachers.push(`${teacher.nom} ${teacher.prenom}`);
}
});
});
return Object.values(groupedSpecialities);
};
const ClassesInfo = ({ classes, onClassSelect }) => {
const [selectedClass, setSelectedClass] = useState(null); // Nouvelle variable d'état pour la classe sélectionnée
const handleClassChange = (event) => {
const classId = event.target.value;
const selectedClass = classes.find(classe => classe.id === parseInt(classId));
setSelectedClass(selectedClass);
onClassSelect(selectedClass);
};
const classChoices = [
{ value: '', label: 'Sélectionner...' },
...classes.map(classe => ({
value: classe.id,
label: classe.nom_ambiance,
}))
];
// S'assurer que `selectedClass` n'est pas null avant d'appeler `groupSpecialitiesBySubject`
const groupedSpecialities = selectedClass ? groupSpecialitiesBySubject(selectedClass.enseignants) : [];
return (
<div className="p-4 bg-white shadow rounded mb-4">
{classes.length > 0 ? (
<SelectChoice
name="classes"
label="Classes"
IconItem={Users}
selected={selectedClass ? selectedClass.id : ''}
choices={classChoices}
callback={handleClassChange}
/>
) : (
<p>Aucune classe disponible.</p>
)}
{selectedClass && (
<div className="specialities mt-4">
<label className="block text-sm font-medium text-gray-700 mb-4">Spécialités</label>
{groupedSpecialities.map((specialite, index) => {
// Combiner l'ID de la spécialité avec les IDs des enseignants pour créer une clé unique
const uniqueId = `${specialite.id}-${specialite.teachers.map(teacher => teacher.id).join('-')}-${index}`;
return (
<DraggableSpeciality
key={uniqueId} // Utilisation de l'ID unique généré
specialite={specialite}
/>
);
})}
</div>
)}
</div>
);
};
export default ClassesInfo;

View File

@ -0,0 +1,30 @@
import React from 'react';
import TeacherLabel from '@/components/CustomLabels/TeacherLabel';
const ClassesInformation = ({ selectedClass, isPastYear }) => {
if (!selectedClass) {
return null;
}
return (
<div className={`w-full p-6 shadow-lg rounded-full border relative ${isPastYear ? 'bg-gray-200 border-gray-600' : 'bg-emerald-200 border-emerald-500'}`}>
<div className={`border-b pb-4 ${isPastYear ? 'border-gray-600' : 'border-emerald-500'}`}>
<p className="text-gray-700 text-center"><strong>{selectedClass.tranche_age} ans</strong></p>
</div>
<div className={`border-b pb-4 ${isPastYear ? 'border-gray-600' : 'border-emerald-500'}`}>
<div className="flex flex-wrap justify-center space-x-4">
{selectedClass.enseignants.map((teacher) => (
<div key={teacher.id} className="relative group mt-4">
<TeacherLabel nom={teacher.nom} prenom={teacher.prenom} />
<div className="absolute left-1/2 transform -translate-x-1/2 bottom-full mb-2 w-max px-4 py-2 text-white bg-gray-800 rounded-lg opacity-0 group-hover:opacity-100 transition-opacity duration-300">
<p className="text-sm">{teacher.nom} {teacher.prenom}</p>
</div>
</div>
))}
</div>
</div>
</div>
);
};
export default ClassesInformation;

View File

@ -0,0 +1,76 @@
import React from 'react';
import { History, Clock, Users } from 'lucide-react';
const ClassesList = ({ classes, onClassSelect, selectedClassId }) => {
const currentYear = new Date().getFullYear();
const currentMonth = new Date().getMonth();
const currentSchoolYearStart = currentMonth >= 8 ? currentYear : currentYear - 1;
const handleClassClick = (classe) => {
console.log(`Classe sélectionnée: ${classe.nom_ambiance}, Année scolaire: ${classe.annee_scolaire}`);
onClassSelect(classe);
};
const categorizedClasses = classes.reduce((acc, classe) => {
const { annee_scolaire } = classe;
const [startYear] = annee_scolaire.split('-').map(Number);
const category = startYear >= currentSchoolYearStart ? 'Actives' : 'Anciennes';
if (!acc[category]) {
acc[category] = [];
}
acc[category].push(classe);
return acc;
}, {});
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">
<Users className="w-8 h-8 mr-2" />
Classes
</h2>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
<div>
<h3 className="text-xl font-semibold mb-2 text-emerald-600 flex items-center space-x-2">
<Clock className="inline-block mr-2 w-5 h-5" /> Actives
</h3>
<div className="flex flex-col">
{categorizedClasses['Actives']?.map((classe) => (
<div
key={classe.id}
className={`flex items-center ${selectedClassId === classe.id ? 'bg-emerald-600 text-white' : 'bg-emerald-100 text-emerald-600'} border border-emerald-300 rounded-lg shadow-lg overflow-hidden hover:bg-emerald-300 hover:text-emerald-700 cursor-pointer p-4 mb-4`}
onClick={() => handleClassClick(classe)}
style={{ maxWidth: '400px' }}
>
<div className="flex-1 text-sm font-medium">{classe.nom_ambiance}</div>
<div className="flex-1 text-sm font-medium">{classe.annee_scolaire}</div>
</div>
))}
</div>
</div>
<div>
<h3 className="text-xl font-semibold mb-2 text-gray-600 flex items-center space-x-2">
<History className="inline-block mr-2 w-5 h-5" /> Anciennes
</h3>
<div className="flex flex-col">
{categorizedClasses['Anciennes']?.map((classe) => (
<div
key={classe.id}
className={`flex items-center ${selectedClassId === classe.id ? 'bg-gray-400 text-white' : 'bg-gray-100 text-gray-600'} border border-gray-300 rounded-lg shadow-lg overflow-hidden hover:bg-gray-300 hover:text-gray-700 cursor-pointer p-4 mb-4`}
onClick={() => handleClassClick(classe)}
style={{ maxWidth: '400px' }}
>
<div className="flex-1 text-sm font-medium">{classe.nom_ambiance}</div>
<div className="flex-1 text-sm font-medium">{classe.annee_scolaire}</div>
</div>
))}
</div>
</div>
</div>
</div>
);
};
export default ClassesList;

View File

@ -1,36 +1,34 @@
import React from 'react';
import { useDrag } from 'react-dnd';
import { UserIcon } from 'lucide-react'; // Assure-toi d'importer l'icône que tu souhaites utiliser
const DraggableSpeciality = ({ specialite }) => {
const DraggableSpeciality = ({ speciality }) => {
const [{ isDragging }, drag] = useDrag(() => ({
type: 'SPECIALITY',
item: { id: specialite.id,
name: specialite.nom,
color: specialite.codeCouleur,
teachers: specialite.teachers,
duree: specialite.duree },
item: {
id: speciality.id,
name: speciality.nom,
color: speciality.codeCouleur,
teachers: speciality.teachers
},
collect: (monitor) => ({
isDragging: monitor.isDragging(),
isDragging: !!monitor.isDragging(),
}),
}));
const isColorDark = (color) => {
const r = parseInt(color.slice(1, 3), 16);
const g = parseInt(color.slice(3, 5), 16);
const b = parseInt(color.slice(5, 7), 16);
return ((r * 0.299 + g * 0.587 + b * 0.114) < 150);
};
return (
<span
ref={drag}
className="speciality-tag p-2 m-1 rounded cursor-pointer"
style={{
backgroundColor: specialite.codeCouleur,
color: isColorDark(specialite.codeCouleur) ? 'white' : 'black',
opacity: isDragging ? 0.5 : 1,
}}
key={speciality.id}
className={`relative flex items-center px-4 py-2 rounded-full font-bold text-white text-center shadow-lg cursor-pointer transition-transform duration-200 ease-in-out transform ${isDragging ? 'opacity-50 scale-95' : 'scale-100 hover:scale-105 hover:shadow-xl'}`}
style={{ backgroundColor: speciality.codeCouleur, minWidth: '200px', maxWidth: '400px' }}
title={speciality.nom}
>
{specialite.nom} ({specialite.teachers.join(', ')})
{speciality.nom}
<span className="absolute top-0 right-0 mt-1 mr-1 flex items-center justify-center text-xs bg-black bg-opacity-50 rounded-full px-2 py-1">
<UserIcon size={16} className="ml-1" />
{speciality.teachers.length}
</span>
</span>
);
};

View File

@ -2,17 +2,19 @@ import React from 'react';
import { useDrop } from 'react-dnd';
import PropTypes from 'prop-types';
const DropTargetCell = ({ day, hour, course, onDrop }) => {
const [{ isOver }, drop] = useDrop(() => ({
// Définition du composant DropTargetCell
const DropTargetCell = ({ day, hour, courses, onDrop, onClick }) => {
const [{ isOver, canDrop }, drop] = useDrop(() => ({
accept: 'SPECIALITY',
drop: (item) => onDrop(item, hour, day),
collect: (monitor) => ({
isOver: monitor.isOver(),
canDrop: monitor.canDrop(),
}),
}));
}), [hour, day]);
const isColorDark = (color) => {
if (!color) return false; // Vérification si color est défini
if (!color) return false;
const r = parseInt(color.slice(1, 3), 16);
const g = parseInt(color.slice(3, 5), 16);
const b = parseInt(color.slice(5, 7), 16);
@ -26,39 +28,42 @@ const DropTargetCell = ({ day, hour, course, onDrop }) => {
someDate.getFullYear() === today.getFullYear();
};
const cellBackgroundColor = course ? course.color : 'transparent';
const cellTextColor = isColorDark(course?.color) ? '#E5E5E5' : '#333333';
// Vérifie si c'est une heure pleine
const isFullHour = parseInt(hour.split(':')[1], 10) === 0;
return (
<div
ref={drop}
className={`h-20 relative border-b border-gray-100 cursor-pointer ${isToday(new Date(day)) ? 'bg-emerald-100/50 border-x border-emerald-600' : ''} hover:bg-emerald-100`}
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'column',
backgroundColor: cellBackgroundColor,
color: cellTextColor
}}
onClick={() => onClick(hour, day)}
className={`relative cursor-pointer
${isToday(new Date(day)) ? 'bg-emerald-100/50 border-x border-emerald-600' : ''}
hover:bg-emerald-100 h-10 border-b
${isFullHour ? 'border-emerald-200' : 'border-gray-300'}
${isOver && canDrop ? 'bg-emerald-200' : ''}`} // Ajouté pour indiquer le drop
style={{ display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'column',
width: '100%' }}
>
<div className="flex flex-col items-center gap-1">
{course && (
<>
<div className="text-base font-bold">{course.matiere}</div>
<div className="text-sm">{course.teachers.join(", ")}</div>
</>
)}
</div>
{courses.map(course => (
<div key={course.matiere}
className="flex flex-row items-center justify-center gap-2"
style={{ backgroundColor: course.color, color: isColorDark(course.color) ? '#E5E5E5' : '#333333', width: '100%', height: '100%' }}>
<div style={{ textAlign: 'center', fontWeight: 'bold' }}>{course.matiere}</div>
<div style={{ fontStyle: 'italic', textAlign: 'center' }}>{course.teachers.join(', ')}</div>
</div>
))}
</div>
);
};
DropTargetCell.propTypes = {
day: PropTypes.string.isRequired,
hour: PropTypes.number.isRequired,
course: PropTypes.object,
onDrop: PropTypes.func.isRequired
hour: PropTypes.string.isRequired,
courses: PropTypes.array.isRequired,
onDrop: PropTypes.func.isRequired,
onClick: PropTypes.func.isRequired,
formData: PropTypes.object.isRequired,
};
export default DropTargetCell;

View File

@ -1,249 +0,0 @@
import React, { useEffect, useState, useRef } from 'react';
import { format, startOfWeek, addDays, isSameDay } from 'date-fns';
import { fr } from 'date-fns/locale';
import { isToday } from 'date-fns';
import DropTargetCell from '@/components/Structure/Planning/DropTargetCell';
import { BK_GESTIONENSEIGNANTS_PLANNING_URL } from '@/utils/Url';
const PlanningClassView = ({ schedule }) => {
const [currentTime, setCurrentTime] = useState(new Date());
const scrollContainerRef = React.useRef(null);
const scheduleIdRef = useRef(scheduleId);
const eventsRef = useRef(events);
const [planning, setPlanning] = useState([])
// Fonction pour récupérer les données du depuis l'API
const fetchPlanning = () => {
if (schedule) {
fetch(`${BK_GESTIONENSEIGNANTS_PLANNING_URL}/${schedule.id}`)
.then(response => response.json())
.then(data => {
console.log('DATA : ', data);
if (!data || data.emploiDuTemps.length === 0) {
setEvents([]);
setPlanning([]);
return;
}
const planningData = data.emploiDuTemps || {};
console.log('succès : ', planningData);
let events = [];
Object.keys(planningData).forEach(day => {
if (planningData[day]) {
planningData[day].forEach(event => {
if (event) {
events.push({
...event,
day: day.toLowerCase(), // Ajouter une clé jour en minuscule pour faciliter le filtrage
scheduleId: data.id
});
}
});
}
});
if (JSON.stringify(events) !== JSON.stringify(eventsRef.current)) {
setEvents(events);
setPlanning(events);
}
})
.catch(error => {
console.error('Erreur lors de la récupération du planning :', error);
});
}
};
useEffect(() => {
setEvents([])
setPlanning([]);
fetchPlanning();
}, [scheduleId]);
useEffect(() => {
scheduleIdRef.current = scheduleId;
// Mettre à jour la référence chaque fois que scheduleId change
}, [scheduleId]);
/*useEffect(() => {
eventsRef.current = events;
// Mettre à jour la référence chaque fois que events change
}, [events]);*/
// Déplacer ces déclarations avant leur utilisation
const timeSlots = Array.from({ length: 11 }, (_, i) => i + 8);
const weekStart = startOfWeek(currentDate, { weekStartsOn: 1 });
const weekDays = Array.from({ length: 6 }, (_, i) => addDays(weekStart, i));
// Maintenant on peut utiliser weekDays
const isCurrentWeek = weekDays.some(day => isSameDay(day, new Date()));
const getFilteredEvents = (day, hour) => {
const dayName = format(day, 'eeee', { locale: fr }).toLowerCase(); // Obtenir le nom du jour en minuscule pour le filtrage
if (!Array.isArray(planning)) {
console.error("planning n'est pas un tableau:", planning);
return [];
}
return planning.filter(event => {
const eventDay = event.day.toLowerCase(); // Convertir le jour de l'événement en minuscule
return (
eventDay === dayName &&
event.heure.startsWith(hour.toString().padStart(2, '0')) // Comparer l'heure sans les minutes
);
});
};
/*const handleDrop = (item, hour, day) => {
const dateKey = format(new Date(day), 'yyyy-MM-dd');
const newEvent = {
id: `event-${dateKey}-${hour}`,
start: new Date(day).setHours(hour),
end: new Date(day).setHours(hour + 1),
matiere: item.matiere || item.name, // Assurer que 'matiere' soit défini
color: item.color || '#FFFFFF', // Ajouter une couleur par défaut si indéfini
teachers: item.teachers || [], // Assurer que 'teachers' soit défini
day: format(new Date(day), 'eeee', { locale: fr }).toLowerCase(), // Ajouter le jour
heure: hour.toString().padStart(2, '0') + ':00', // Ajouter l'heure ici
scheduleId: scheduleIdRef.current // Utiliser la référence à scheduleId ici
};
// Utiliser la référence pour accéder aux événements actuels
const existingEvents = eventsRef.current.filter(event => {
const eventHour = event.heure;
const eventDay = event.day;
console.log("DEBUG : " + event);
console.log("DEBUG : " + eventHour + " - " + hour.toString().padStart(2, '0') + ':00');
console.log("DEBUG : " + eventDay + " - " + format(new Date(day), 'eeee', { locale: fr }).toLowerCase());
console.log("DEBUG : " + event.scheduleId + " - " + scheduleIdRef.current);
console.log("----");
// Comparer la date, l'heure et le scheduleId pour trouver les événements correspondants
return eventHour === hour.toString().padStart(2, '0') + ':00' && eventDay === format(new Date(day), 'eeee', { locale: fr }).toLowerCase() && event.scheduleId === scheduleIdRef.current;
});
console.log("existingEvents :", existingEvents);
if (existingEvents.length > 0) {
existingEvents.forEach(event => {
deleteEvent(event.id); // Supprimer l'événement existant
});
}
// Ajouter l'événement à l'état global des événements
addEvent(newEvent);
setPlanning(prevEvents => {
// Filtrer les événements pour conserver ceux qui n'ont pas le même jour et créneau horaire
const updatedEvents = prevEvents.filter(event =>
!(event.day === newEvent.day && event.heure === newEvent.heure)
);
// Ajouter le nouvel événement
updatedEvents.push(newEvent);
return updatedEvents;
});
};*/
// Mettre à jour la position de la ligne toutes les minutes
useEffect(() => {
const interval = setInterval(() => {
setCurrentTime(new Date());
}, 60000);
return () => clearInterval(interval);
}, []);
// Modifier l'useEffect pour l'auto-scroll
useEffect(() => {
if (scrollContainerRef.current && isCurrentWeek) {
const currentHour = new Date().getHours();
const scrollPosition = currentHour * 80;
// Ajout d'un délai pour laisser le temps au DOM de se mettre à jour
setTimeout(() => {
scrollContainerRef.current.scrollTop = scrollPosition - 200;
}, 0);
}
}, [currentDate, isCurrentWeek]); // Ajout de currentDate dans les dépendances
// 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
}
const totalMinutes = (hours - 8) * 60 + minutes; // Minutes écoulées depuis 8h
const cellHeight = 80; // Hauteur des cellules (par exemple 80px pour 20rem / 24)
const position = (totalMinutes / 60) * cellHeight;
return position;
};
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>
{weekDays.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">
{/* Ligne de temps actuelle */}
{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>
)}
<div className="grid gap-[1px] bg-white" style={{ gridTemplateColumns: "2.5rem repeat(6, 1fr)" }}>
{timeSlots.map(hour => (
<React.Fragment key={hour}>
<div 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.map(day => {
const filteredEvents = getFilteredEvents(day, hour);
return(
<DropTargetCell
key={`${hour}-${day}`}
hour={hour}
day={day}
onDrop={handleDrop}
onDateClick={onDateClick}
filteredEvents={filteredEvents}
/>
)
})}
</React.Fragment>
))}
</div>
</div>
</div>
);
};
export default PlanningClassView;

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,
};

View File

@ -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;

View File

@ -0,0 +1,21 @@
import React from 'react';
import { useClasses } from '@/context/ClassesContext';
import DraggableSpeciality from '@/components/Structure/Planning/DraggableSpeciality';
const SpecialitiesList = ({ teachers }) => {
const { groupSpecialitiesBySubject } = useClasses();
return (
<div className="flex justify-center items-center w-full">
<div className="flex flex-wrap gap-2 mt-4 justify-center">
{groupSpecialitiesBySubject(teachers).map((speciality) => (
<DraggableSpeciality key={speciality.id} speciality={speciality} />
))}
</div>
</div>
);
};
export default SpecialitiesList;

View File

@ -1,77 +1,151 @@
import { usePlanning } from '@/context/PlanningContext';
import React, { useState, useEffect } from 'react';
import SelectChoice from '@/components/SelectChoice';
import { format } from 'date-fns';
import React, { useEffect } from 'react';
import { Users, BookOpen } from 'lucide-react';
import { useClasses } from '@/context/ClassesContext';
import { useClasseForm } from '@/context/ClasseFormContext';
import { BK_GESTIONENSEIGNANTS_PLANNING_URL } from '@/utils/Url';
import { BookOpen, Users } from 'lucide-react';
export default function SpecialityEventModal({ isOpen, onClose, eventData, setEventData, selectedClass }) {
const { addEvent, updateEvent, deleteEvent, schedules } = usePlanning();
const SpecialityEventModal = ({ isOpen, onClose, selectedCell, existingEvent, handleUpdatePlanning, classe }) => {
const { formData, setFormData } = useClasseForm();
const { groupSpecialitiesBySubject } = useClasses();
const [selectedSpeciality, setSelectedSpeciality] = useState('');
const [selectedTeacher, setSelectedTeacher] = useState('');
const [eventData, setEventData] = useState({
specialiteId: '',
teacherId: '',
start: '',
duration: '1h'
});
useEffect(() => {
if (!isOpen) {
// Réinitialiser eventData lorsque la modale se ferme
setEventData({
scheduleId: '',
specialiteId: '',
specialities: [],
// Réinitialiser d'autres champs si nécessaire
teacherId: '',
start: '',
duration: '1h'
});
setSelectedSpeciality('');
setSelectedTeacher('');
}
}, [isOpen, setEventData]);
}, [isOpen]);
useEffect(() => {
if (isOpen && selectedClass) {
setEventData(prev => ({
...prev,
scheduleId: selectedClass.id,
specialities: Array.from(new Map(
selectedClass.enseignants.flatMap(teacher =>
teacher.specialites.map(specialite => [specialite.id, {
id: specialite.id,
nom: specialite.nom,
codeCouleur: specialite.codeCouleur
}])
)
).values())
}));
if (isOpen) {
console.log('debug : ', selectedCell);
if (existingEvent) {
// Mode édition
setEventData(existingEvent);
setSelectedSpeciality(existingEvent.specialiteId);
setSelectedTeacher(existingEvent.teacherId);
} else {
// Mode création
setEventData(prev => ({
...prev,
start: selectedCell.hour,
duration: '1h'
}));
setSelectedSpeciality('');
setSelectedTeacher('');
}
}
}, [isOpen, selectedClass, setEventData]);
}, [isOpen, existingEvent, selectedCell]);
if (!isOpen) return null;
const handleSubmit = (e) => {
e.preventDefault();
if (!eventData.scheduleId) {
if (!eventData.specialiteId) {
alert('Veuillez sélectionner une spécialité');
return;
}
const selectedSchedule = schedules.find(s => s.id === eventData.scheduleId);
if (eventData.id) {
updateEvent(eventData.id, {
...eventData,
scheduleId: eventData.scheduleId,
color: eventData.color || selectedSchedule?.color
});
} else {
addEvent({
...eventData,
id: `event-${Date.now()}`,
scheduleId: eventData.scheduleId,
color: eventData.color || selectedSchedule?.color
});
if (!eventData.teacherId) {
alert('Veuillez sélectionner un enseignant');
return;
}
// Transformer eventData pour correspondre au format du planning
const selectedTeacherData = formData.enseignants.find(teacher => teacher.id === parseInt(eventData.teacherId, 10));
const newCourse = {
color: '#FF0000', // Vous pouvez définir la couleur de manière dynamique si nécessaire
teachers: selectedTeacherData ? [`${selectedTeacherData.nom} ${selectedTeacherData.prenom}`] : [],
heure: `${eventData.start}:00`,
duree: eventData.duration.replace('h', ''), // Supposons que '1h' signifie 1
matiere: 'GROUPE'
};
// Mettre à jour le planning
const updatedPlannings = classe.plannings_read.map(planning => {
if (planning.niveau === selectedCell.selectedLevel) {
const newEmploiDuTemps = { ...planning.emploiDuTemps };
if (!newEmploiDuTemps[selectedCell.day]) {
newEmploiDuTemps[selectedCell.day] = [];
}
const courseTime = newCourse.heure;
const existingCourseIndex = newEmploiDuTemps[selectedCell.day].findIndex(course => course.heure === courseTime);
if (existingCourseIndex !== -1) {
newEmploiDuTemps[selectedCell.day][existingCourseIndex] = newCourse;
} else {
newEmploiDuTemps[selectedCell.day].push(newCourse);
}
return {
...planning,
emploiDuTemps: newEmploiDuTemps
};
}
return planning;
});
const updatedPlanning = updatedPlannings.find(planning => planning.niveau === selectedCell.selectedLevel);
setFormData(prevFormData => ({
...prevFormData,
plannings: updatedPlannings
}));
// Appeler handleUpdatePlanning avec les arguments appropriés
const planningId = updatedPlanning ? updatedPlanning.planning.id : null;
console.log("id : ", planningId)
if (planningId) {
handleUpdatePlanning(BK_GESTIONENSEIGNANTS_PLANNING_URL, planningId, updatedPlanning.emploiDuTemps);
}
onClose();
};
const handleDelete = () => {
if (eventData.id && confirm('Êtes-vous sûr de vouloir supprimer cet événement ?')) {
deleteEvent(eventData.id);
onClose();
}
const filteredTeachers = selectedSpeciality
? formData.enseignants.filter(teacher =>
teacher.specialites_ids.includes(parseInt(selectedSpeciality, 10))
)
: formData.enseignants;
const handleSpecialityChange = (e) => {
const specialityId = e.target.value;
setSelectedSpeciality(specialityId);
// Mettre à jour eventData
setEventData(prev => ({
...prev,
specialiteId: specialityId
}));
};
const handleTeacherChange = (e) => {
const teacherId = e.target.value;
setSelectedTeacher(teacherId);
// Mettre à jour eventData
setEventData(prev => ({
...prev,
teacherId: teacherId
}));
};
return (
@ -84,103 +158,77 @@ export default function SpecialityEventModal({ isOpen, onClose, eventData, setEv
<form onSubmit={handleSubmit} className="space-y-4">
{/* Sélection de la Spécialité */}
<div>
{eventData.scheduleId && eventData.specialities && eventData.specialities.length > 0 ? (
<SelectChoice
name={`spécialités-${eventData.scheduleId}`}
label="Spécialités"
selected={eventData.specialiteId ? eventData.specialiteId : ''}
choices={eventData.specialities.map(specialite => ({
value: specialite.id,
label: specialite.nom
}))}
callback={(event) => {
const selectedSpecialityId = event.target.value;
const selectedSpeciality = eventData.specialities.find(specialite => specialite.id === parseInt(selectedSpecialityId, 10));
setEventData({
...eventData,
specialiteId: selectedSpecialityId,
color: selectedSpeciality?.codeCouleur || '#10b981'
});
}}
IconItem={BookOpen}
/>
) : (
<p>Aucune spécialité disponible pour cette classe.</p>
)}
<SelectChoice
name="specialites"
label="Spécialités"
selected={selectedSpeciality}
choices={[
{ value: '', label: 'Sélectionner une spécialité' },
...groupSpecialitiesBySubject(formData.enseignants).map((speciality) => ({
value: speciality.id,
label: speciality.nom
}))
]}
callback={handleSpecialityChange}
IconItem={BookOpen}
/>
</div>
{/* Dates */}
<div className="grid grid-cols-2 gap-4">
{/* Sélection de l'enseignant */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Début
</label>
<input
type="datetime-local"
value={format(new Date(eventData.start), "yyyy-MM-dd'T'HH:mm")}
onChange={(e) => setEventData({ ...eventData, start: new Date(e.target.value).toISOString() })}
className="w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-emerald-500"
required
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Fin
</label>
<input
type="datetime-local"
value={format(new Date(eventData.end), "yyyy-MM-dd'T'HH:mm")}
onChange={(e) => setEventData({ ...eventData, end: new Date(e.target.value).toISOString() })}
className="w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-emerald-500"
required
/>
</div>
<SelectChoice
name="teachers"
label="Enseignants"
selected={selectedTeacher}
choices={[
{ value: '', label: 'Sélectionner un enseignant'},
...filteredTeachers.map(teacher => ({
value: teacher.id,
label: `${teacher.nom} ${teacher.prenom}`
}))
]}
callback={handleTeacherChange}
IconItem={Users}
disabled={!selectedSpeciality} // Désactive le sélecteur si aucune spécialité n'est sélectionnée
/>
</div>
{/* Lieu */}
{/* Durée */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Lieu
Durée
</label>
<input
type="text"
value={eventData.location || ''}
onChange={(e) => setEventData({ ...eventData, location: e.target.value })}
value={eventData.duration}
onChange={(e) => setEventData(prev => ({
...prev,
duration: e.target.value
}))}
className="w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-emerald-500"
/>
</div>
{/* Boutons */}
<div className="flex justify-between gap-2 mt-6">
<div>
{eventData.id && (
<button
type="button"
onClick={handleDelete}
className="px-4 py-2 text-red-600 hover:bg-red-50 rounded"
>
Supprimer
</button>
)}
</div>
<div className="flex gap-2">
<button
type="button"
onClick={onClose}
className="px-4 py-2 text-gray-600 hover:bg-gray-100 rounded"
>
Annuler
</button>
<button
type="submit"
className="px-4 py-2 bg-emerald-600 text-white rounded hover:bg-emerald-700"
>
{eventData.id ? 'Modifier' : 'Créer'}
</button>
</div>
<button
type="button"
onClick={onClose}
className="px-4 py-2 text-gray-600 hover:bg-gray-100 rounded"
>
Annuler
</button>
<button
type="submit"
className="px-4 py-2 bg-emerald-600 text-white rounded hover:bg-emerald-700"
>
{eventData.id ? 'Modifier' : 'Créer'}
</button>
</div>
</form>
</div>
</div>
);
}
};
export default SpecialityEventModal;