mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-04-05 20:51:26 +00:00
fix: Mise à jour des plannings
This commit is contained in:
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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>
|
||||
))}
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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>
|
||||
);
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user