fix: Mise à jour des plannings

This commit is contained in:
N3WT DE COMPET
2026-04-05 11:09:32 +02:00
parent 1f2a1b88ac
commit 12939fca85
9 changed files with 292 additions and 68 deletions

View File

@ -9,6 +9,7 @@ import EventModal from '@/components/Calendar/EventModal';
import ScheduleNavigation from '@/components/Calendar/ScheduleNavigation';
import { useState } from 'react';
import { useEstablishment } from '@/context/EstablishmentContext';
import { usePlanning } from '@/context/PlanningContext';
export default function Page() {
const [isModalOpen, setIsModalOpen] = useState(false);
@ -29,33 +30,36 @@ export default function Page() {
});
const { selectedEstablishmentId } = useEstablishment();
const initializeNewEvent = (date = new Date()) => {
// S'assurer que date est un objet Date valide
const eventDate = date instanceof Date ? date : new Date();
const PlanningContent = ({ isDrawerOpen, setIsDrawerOpen, isModalOpen, setIsModalOpen, eventData, setEventData }) => {
const { selectedSchedule, schedules } = usePlanning();
setEventData({
title: '',
description: '',
start: eventDate.toISOString(),
end: new Date(eventDate.getTime() + 2 * 60 * 60 * 1000).toISOString(),
location: '',
planning: '', // Ne pas définir de valeur par défaut ici non plus
recursionType: RecurrenceType.NONE,
selectedDays: [],
recursionEnd: new Date(
eventDate.getTime() + 2 * 60 * 60 * 1000
).toISOString(),
customInterval: 1,
customUnit: 'days',
});
setIsModalOpen(true);
};
const initializeNewEvent = (date = new Date()) => {
const eventDate = date instanceof Date ? date : new Date();
return (
<PlanningProvider
establishmentId={selectedEstablishmentId}
modeSet={PlanningModes.PLANNING}
>
const selected =
schedules.find((schedule) => Number(schedule.id) === Number(selectedSchedule)) ||
schedules[0];
setEventData({
title: '',
description: '',
start: eventDate.toISOString(),
end: new Date(eventDate.getTime() + 2 * 60 * 60 * 1000).toISOString(),
location: '',
planning: selected?.id || '',
color: selected?.color || '',
recursionType: RecurrenceType.NONE,
selectedDays: [],
recursionEnd: new Date(
eventDate.getTime() + 2 * 60 * 60 * 1000
).toISOString(),
customInterval: 1,
customUnit: 'days',
});
setIsModalOpen(true);
};
return (
<div className="flex h-full overflow-hidden">
<ScheduleNavigation
isOpen={isDrawerOpen}
@ -76,6 +80,22 @@ export default function Page() {
setEventData={setEventData}
/>
</div>
);
};
return (
<PlanningProvider
establishmentId={selectedEstablishmentId}
modeSet={PlanningModes.PLANNING}
>
<PlanningContent
isDrawerOpen={isDrawerOpen}
setIsDrawerOpen={setIsDrawerOpen}
isModalOpen={isModalOpen}
setIsModalOpen={setIsModalOpen}
eventData={eventData}
setEventData={setEventData}
/>
</PlanningProvider>
);
}

View File

@ -13,7 +13,7 @@ import { getWeekEvents } from '@/utils/events';
import { CalendarDays, ChevronLeft, ChevronRight, Plus } from 'lucide-react';
const DayView = ({ onDateClick, onEventClick, events, onOpenDrawer }) => {
const { currentDate, setCurrentDate, parentView } = usePlanning();
const { currentDate, setCurrentDate, parentView, schedules } = usePlanning();
const [currentTime, setCurrentTime] = useState(new Date());
const scrollRef = useRef(null);
@ -43,11 +43,28 @@ const DayView = ({ onDateClick, onEventClick, events, onOpenDrawer }) => {
return `${(hours + minutes / 60) * 5}rem`;
};
const getScheduleColor = (event) => {
const schedule = schedules?.find(
(item) => Number(item.id) === Number(event.planning)
);
return schedule?.color || event.color || '#6B7280';
};
const getScheduleClassLevelLabel = (event) => {
const schedule = schedules?.find(
(item) => Number(item.id) === Number(event.planning)
);
const scheduleName = schedule?.name || '';
if (!scheduleName) return '';
return scheduleName;
};
const calculateEventStyle = (event, allDayEvents) => {
const start = new Date(event.start);
const end = new Date(event.end);
const startMinutes = (start.getMinutes() / 60) * 5;
const duration = ((end - start) / (1000 * 60 * 60)) * 5;
const scheduleColor = getScheduleColor(event);
const overlapping = allDayEvents.filter((other) => {
if (other.id === event.id) return false;
@ -179,8 +196,11 @@ const DayView = ({ onDateClick, onEventClick, events, onOpenDrawer }) => {
>
{dayEvents
.filter((e) => new Date(e.start).getHours() === hour)
.map((event) => (
<div
.map((event) => {
const scheduleColor = getScheduleColor(event);
const classLevelLabel = getScheduleClassLevelLabel(event);
return (
<div
key={event.id}
className="rounded-sm overflow-hidden cursor-pointer hover:shadow-lg"
style={calculateEventStyle(event, dayEvents)}
@ -192,32 +212,53 @@ const DayView = ({ onDateClick, onEventClick, events, onOpenDrawer }) => {
onEventClick(event);
}
}
>
<div className="p-1">
<div
className="font-semibold text-xs truncate"
style={{ color: event.color }}
>
{event.title}
</div>
<div
className="text-xs"
style={{ color: event.color, opacity: 0.75 }}
>
{format(new Date(event.start), 'HH:mm')} {' '}
{format(new Date(event.end), 'HH:mm')}
</div>
{event.location && (
>
{classLevelLabel && (
<div
className="text-xs truncate"
style={{ color: event.color, opacity: 0.75 }}
className="px-1 py-0.5 border-t-2"
style={{
borderTopColor: scheduleColor,
backgroundColor: `${scheduleColor}22`,
}}
>
{event.location}
<span
className="text-[10px] font-semibold uppercase tracking-wide truncate block text-center"
style={{ color: scheduleColor }}
>
{classLevelLabel}
</span>
</div>
)}
<div className="p-1">
<div
className="font-semibold text-xs truncate flex items-center gap-1"
style={{ color: event.color }}
>
<span
className="w-2 h-2 rounded-full shrink-0"
style={{ backgroundColor: event.color }}
/>
<span className="truncate flex-1">{event.title}</span>
</div>
<div
className="text-xs"
style={{ color: event.color, opacity: 0.75 }}
>
{format(new Date(event.start), 'HH:mm')} {' '}
{format(new Date(event.end), 'HH:mm')}
</div>
{event.location && (
<div
className="text-xs truncate"
style={{ color: event.color, opacity: 0.75 }}
>
{event.location}
</div>
)}
</div>
</div>
</div>
))}
);
})}
</div>
</React.Fragment>
))}

View File

@ -10,20 +10,31 @@ export default function EventModal({
eventData,
setEventData,
}) {
const { addEvent, handleUpdateEvent, handleDeleteEvent, schedules } =
const {
addEvent,
handleUpdateEvent,
handleDeleteEvent,
schedules,
selectedSchedule,
} =
usePlanning();
const { showNotification } = useNotification();
// S'assurer que planning est défini lors du premier rendu
React.useEffect(() => {
if (!eventData?.planning && schedules.length > 0) {
const defaultSchedule =
schedules.find(
(schedule) => Number(schedule.id) === Number(selectedSchedule)
) || schedules[0];
setEventData((prev) => ({
...prev,
planning: schedules[0].id,
color: schedules[0].color,
planning: defaultSchedule.id,
color: defaultSchedule.color,
}));
}
}, [schedules, eventData?.planning]);
}, [schedules, selectedSchedule, eventData?.planning]);
if (!isOpen) return null;

View File

@ -14,7 +14,24 @@ import { fr } from 'date-fns/locale';
import { getEventsForDate } from '@/utils/events';
const MonthView = ({ onDateClick, onEventClick }) => {
const { currentDate, setViewType, setCurrentDate, events } = usePlanning();
const { currentDate, setViewType, setCurrentDate, events, schedules } =
usePlanning();
const getScheduleColor = (event) => {
const schedule = schedules?.find(
(item) => Number(item.id) === Number(event.planning)
);
return schedule?.color || event.color || '#6B7280';
};
const getScheduleClassLevelLabel = (event) => {
const schedule = schedules?.find(
(item) => Number(item.id) === Number(event.planning)
);
const scheduleName = schedule?.name || '';
if (!scheduleName) return '';
return scheduleName;
};
// Obtenir tous les jours du mois actuel
const monthStart = startOfMonth(currentDate);
@ -53,8 +70,11 @@ const MonthView = ({ onDateClick, onEventClick }) => {
</span>
</div>
<div className="space-y-1 flex-1">
{dayEvents.map((event, index) => (
<div
{dayEvents.map((event) => {
const scheduleColor = getScheduleColor(event);
const classLevelLabel = getScheduleClassLevelLabel(event);
return (
<div
key={event.id}
className="text-xs p-1 rounded truncate cursor-pointer"
style={{
@ -67,9 +87,32 @@ const MonthView = ({ onDateClick, onEventClick }) => {
onEventClick(event);
}}
>
{event.title}
{classLevelLabel && (
<div
className="-mx-1 -mt-1 mb-1 px-1 py-0.5 border-t-2"
style={{
borderTopColor: scheduleColor,
backgroundColor: `${scheduleColor}22`,
}}
>
<span
className="text-[10px] font-semibold uppercase tracking-wide truncate block text-center"
style={{ color: scheduleColor }}
>
{classLevelLabel}
</span>
</div>
)}
<span className="inline-flex items-center gap-1 max-w-full">
<span
className="w-2 h-2 rounded-full shrink-0"
style={{ backgroundColor: event.color }}
/>
<span className="truncate flex-1">{event.title}</span>
</span>
</div>
))}
);
})}
</div>
</div>
);

View File

@ -7,7 +7,7 @@ import { isToday } from 'date-fns';
const WeekView = ({ onDateClick, onEventClick, events }) => {
const { currentDate, planningMode, parentView } = usePlanning();
const { currentDate, planningMode, parentView, schedules } = usePlanning();
const [currentTime, setCurrentTime] = useState(new Date());
const scrollContainerRef = useRef(null); // Ajouter cette référence
@ -71,11 +71,28 @@ const WeekView = ({ onDateClick, onEventClick, events }) => {
});
};
const getScheduleColor = (event) => {
const schedule = schedules?.find(
(item) => Number(item.id) === Number(event.planning)
);
return schedule?.color || event.color || '#6B7280';
};
const getScheduleClassLevelLabel = (event) => {
const schedule = schedules?.find(
(item) => Number(item.id) === Number(event.planning)
);
const scheduleName = schedule?.name || '';
if (!scheduleName) return '';
return scheduleName;
};
const calculateEventStyle = (event, dayEvents) => {
const start = new Date(event.start);
const end = new Date(event.end);
const startMinutes = (start.getMinutes() / 60) * 5;
const duration = ((end - start) / (1000 * 60 * 60)) * 5;
const scheduleColor = getScheduleColor(event);
// Trouver les événements qui se chevauchent
const overlappingEvents = findOverlappingEvents(event, dayEvents);
@ -101,6 +118,8 @@ const WeekView = ({ onDateClick, onEventClick, events }) => {
const renderEventInCell = (event, dayEvents) => {
const eventStyle = calculateEventStyle(event, dayEvents);
const scheduleColor = getScheduleColor(event);
const classLevelLabel = getScheduleClassLevelLabel(event);
return (
<div
@ -116,12 +135,32 @@ const WeekView = ({ onDateClick, onEventClick, events }) => {
}
}
>
{classLevelLabel && (
<div
className="px-1 py-0.5 border-t-2"
style={{
borderTopColor: scheduleColor,
backgroundColor: `${scheduleColor}22`,
}}
>
<span
className="text-[10px] font-semibold uppercase tracking-wide truncate block text-center"
style={{ color: scheduleColor }}
>
{classLevelLabel}
</span>
</div>
)}
<div className="p-1">
<div
className="font-semibold text-xs truncate"
className="font-semibold text-xs truncate flex items-center gap-1"
style={{ color: event.color }}
>
{event.title}
<span
className="w-2 h-2 rounded-full shrink-0"
style={{ backgroundColor: event.color }}
/>
<span className="truncate flex-1">{event.title}</span>
</div>
<div
className="text-xs"

View File

@ -24,6 +24,29 @@ const ItemTypes = {
TEACHER: 'teacher',
};
const CLASS_PLANNING_COLORS = [
'#EF4444',
'#3B82F6',
'#F59E0B',
'#8B5CF6',
'#EC4899',
'#14B8A6',
'#F97316',
'#6366F1',
'#06B6D4',
'#84CC16',
];
const getClassPlanningColor = (schoolClass) => {
const numericId = Number(schoolClass?.id);
if (!Number.isFinite(numericId) || numericId <= 0) {
return CLASS_PLANNING_COLORS[0];
}
const index = (numericId - 1) % CLASS_PLANNING_COLORS.length;
return CLASS_PLANNING_COLORS[index];
};
const TeachersDropZone = ({
classe,
handleTeachersChange,
@ -245,13 +268,14 @@ const ClassesSection = ({
setNewClass(null);
setLocalErrors({});
// Creation des plannings associé à la classe
const classPlanningColor = getClassPlanningColor(createdClass);
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
color: classPlanningColor,
school_class: createdClass.id,
};
addSchedule(newPlanning);

View File

@ -12,13 +12,22 @@ export default function ScheduleEventModal({
teachers,
classes,
}) {
const { addEvent, handleUpdateEvent, handleDeleteEvent, schedules } =
const {
addEvent,
handleUpdateEvent,
handleDeleteEvent,
schedules,
selectedSchedule,
} =
usePlanning();
const { showNotification } = useNotification();
React.useEffect(() => {
if (!eventData?.planning && schedules.length > 0) {
const defaultSchedule = schedules[0];
const defaultSchedule =
schedules.find(
(schedule) => Number(schedule.id) === Number(selectedSchedule)
) || schedules[0];
if (eventData?.planning !== defaultSchedule.id) {
setEventData((prev) => ({
...prev,
@ -26,7 +35,7 @@ export default function ScheduleEventModal({
}));
}
}
}, [schedules, eventData?.planning]);
}, [schedules, selectedSchedule, eventData?.planning]);
const handleSpecialityChange = (specialityId) => {
const selectedSpeciality = specialities.find(

View File

@ -2,7 +2,7 @@
import React, { useState } from 'react';
import { RecurrenceType } from '@/context/PlanningContext';
import { RecurrenceType, usePlanning } from '@/context/PlanningContext';
import Calendar from '@/components/Calendar/Calendar';
import ScheduleEventModal from '@/components/Structure/Planning/ScheduleEventModal';
import ScheduleNavigation from '@/components/Calendar/ScheduleNavigation';
@ -12,6 +12,7 @@ export default function ScheduleManagement({
specialities,
teachers,
}) {
const { selectedSchedule, schedules } = usePlanning();
const [isModalOpen, setIsModalOpen] = useState(false);
const [eventData, setEventData] = useState({
title: '',
@ -32,13 +33,18 @@ export default function ScheduleManagement({
// S'assurer que date est un objet Date valide
const eventDate = date instanceof Date ? date : new Date();
const selected =
schedules.find((schedule) => Number(schedule.id) === Number(selectedSchedule)) ||
schedules[0];
setEventData({
title: '',
description: '',
start: eventDate.toISOString(),
end: new Date(eventDate.getTime() + 2 * 60 * 60 * 1000).toISOString(),
location: '',
planning: '', // Ne pas définir de valeur par défaut ici non plus
planning: selected?.id || '',
color: selected?.color || '',
recursionType: RecurrenceType.NONE,
selectedDays: [],
recursionEnd: new Date(

View File

@ -36,6 +36,29 @@ export const PlanningModes = Object.freeze({
PLANNING: 'planning'
});
const CLASS_SCHEDULE_COLORS = [
'#EF4444',
'#3B82F6',
'#F59E0B',
'#8B5CF6',
'#EC4899',
'#14B8A6',
'#F97316',
'#6366F1',
'#06B6D4',
'#84CC16',
];
const getClassScheduleColor = (schoolClassId) => {
const numericId = Number(schoolClassId);
if (!Number.isFinite(numericId) || numericId <= 0) {
return '#10b981';
}
const index = (numericId - 1) % CLASS_SCHEDULE_COLORS.length;
return CLASS_SCHEDULE_COLORS[index];
};
export function PlanningProvider({
children,
modeSet = PlanningModes.PLANNING,
@ -65,7 +88,15 @@ export function PlanningProvider({
const reloadPlanning = () => {
fetchPlannings(selectedEstablishmentId, planningMode).then((data) => {
setSchedules(data);
const normalizedSchedules =
planningMode === PlanningModes.CLASS_SCHEDULE
? data.map((schedule) => ({
...schedule,
color: getClassScheduleColor(schedule.school_class),
}))
: data;
setSchedules(normalizedSchedules);
if (data.length > 0) {
setSelectedSchedule(data[0].id);
}