mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-01-29 07:53:23 +00:00
236 lines
8.0 KiB
JavaScript
236 lines
8.0 KiB
JavaScript
import React, { useEffect, useState, useRef } from 'react';
|
|
import { usePlanning, PlanningModes } from '@/context/PlanningContext';
|
|
import { format, startOfWeek, addDays, isSameDay } from 'date-fns';
|
|
import { fr } from 'date-fns/locale';
|
|
import { getWeekEvents } from '@/utils/events';
|
|
import { isToday } from 'date-fns';
|
|
|
|
|
|
const WeekView = ({ onDateClick, onEventClick, events }) => {
|
|
const { currentDate, planningMode, parentView } = usePlanning();
|
|
const [currentTime, setCurrentTime] = useState(new Date());
|
|
const scrollContainerRef = useRef(null); // Ajouter cette référence
|
|
|
|
// Déplacer ces déclarations avant leur utilisation
|
|
const timeSlots = Array.from({ length: 24 }, (_, i) => i);
|
|
const weekStart = startOfWeek(currentDate, { weekStartsOn: 1 });
|
|
const weekDays = Array.from({ length: 7 }, (_, i) => addDays(weekStart, i));
|
|
|
|
// Maintenant on peut utiliser weekDays
|
|
const isCurrentWeek = weekDays.some((day) => isSameDay(day, new Date()));
|
|
|
|
// 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();
|
|
const rowHeight = 5; // Hauteur des lignes en rem (h-20 = 5rem)
|
|
return `${(hours + minutes / 60) * rowHeight}rem`;
|
|
};
|
|
|
|
// Utiliser les événements déjà filtrés passés en props
|
|
const weekEventsMap = weekDays.reduce((acc, day) => {
|
|
acc[format(day, 'yyyy-MM-dd')] = getWeekEvents(day, events);
|
|
return acc;
|
|
}, {});
|
|
|
|
const isWeekend = (date) => {
|
|
const day = date.getDay();
|
|
return day === 0 || day === 6;
|
|
};
|
|
|
|
const findOverlappingEvents = (event, dayEvents) => {
|
|
const eventStart = new Date(event.start);
|
|
const eventEnd = new Date(event.end);
|
|
|
|
return dayEvents.filter((otherEvent) => {
|
|
if (otherEvent.id === event.id) return false;
|
|
const otherStart = new Date(otherEvent.start);
|
|
const otherEnd = new Date(otherEvent.end);
|
|
return !(otherEnd <= eventStart || otherStart >= eventEnd);
|
|
});
|
|
};
|
|
|
|
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;
|
|
|
|
// Trouver les événements qui se chevauchent
|
|
const overlappingEvents = findOverlappingEvents(event, dayEvents);
|
|
const eventIndex = overlappingEvents.findIndex((e) => e.id > event.id) + 1;
|
|
const totalOverlapping = overlappingEvents.length + 1;
|
|
|
|
// Calculer la largeur et la position horizontale
|
|
const width = `calc((100% / ${totalOverlapping}) - 4px)`;
|
|
const left = `calc((100% / ${totalOverlapping}) * ${eventIndex})`;
|
|
|
|
return {
|
|
height: `${duration}rem`,
|
|
position: 'absolute',
|
|
width,
|
|
left,
|
|
backgroundColor: `${event.color}15`,
|
|
borderLeft: `3px solid ${event.color}`,
|
|
borderRadius: '0.25rem',
|
|
zIndex: 1,
|
|
transform: `translateY(${startMinutes}rem)`,
|
|
};
|
|
};
|
|
|
|
const renderEventInCell = (event, dayEvents) => {
|
|
const eventStyle = calculateEventStyle(event, dayEvents);
|
|
|
|
return (
|
|
<div
|
|
key={event.id}
|
|
className="rounded-sm overflow-hidden cursor-pointer hover:shadow-lg"
|
|
style={eventStyle}
|
|
onClick={
|
|
parentView
|
|
? undefined
|
|
: (e) => {
|
|
e.stopPropagation();
|
|
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 && (
|
|
<div
|
|
className="text-xs truncate"
|
|
style={{ color: event.color, opacity: 0.75 }}
|
|
>
|
|
{event.location}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
return (
|
|
<div className="flex flex-col h-full overflow-y-auto">
|
|
{/* En-tête des jours */}
|
|
<div
|
|
className="grid gap-[1px] w-full bg-gray-100"
|
|
style={{ gridTemplateColumns: '2.5rem repeat(7, 1fr)' }}
|
|
>
|
|
<div className="bg-white h-14"></div>
|
|
{weekDays.map((day) => (
|
|
<div
|
|
key={day}
|
|
className={`h-14 p-2 text-center border-b
|
|
${isWeekend(day) ? 'bg-gray-50' : 'bg-white'}
|
|
${isToday(day) ? 'bg-emerald-100 border-x border-emerald-600' : ''}`}
|
|
>
|
|
<div className="text-xs font-medium text-gray-500">
|
|
{format(day, 'EEEE', { locale: fr })}
|
|
</div>
|
|
<div
|
|
className={`text-sm font-semibold inline-block rounded-full w-7 h-7 leading-7
|
|
${isToday(day) ? 'bg-emerald-500 text-white' : ''}`}
|
|
>
|
|
{format(day, 'd', { locale: fr })}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{/* Grille horaire */}
|
|
<div ref={scrollContainerRef} className="flex-1 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-2 w-2 h-2 rounded-full bg-emerald-500" />
|
|
</div>
|
|
)}
|
|
|
|
<div
|
|
className="grid gap-[1px] w-full bg-gray-100"
|
|
style={{ gridTemplateColumns: '2.5rem repeat(7, 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 dayKey = format(day, 'yyyy-MM-dd');
|
|
const dayEvents = weekEventsMap[dayKey] || [];
|
|
return (
|
|
<div
|
|
key={`${hour}-${day}`}
|
|
className={`h-20 relative border-b border-gray-100
|
|
${isWeekend(day) ? 'bg-gray-50' : 'bg-white'}
|
|
${isToday(day) ? 'bg-emerald-100/50 border-x border-emerald-600' : ''}`}
|
|
onClick={
|
|
parentView
|
|
? undefined
|
|
: () => {
|
|
const date = new Date(day);
|
|
date.setHours(hour);
|
|
onDateClick(date);
|
|
}
|
|
}
|
|
>
|
|
<div className="grid gap-1">
|
|
{dayEvents
|
|
.filter((event) => {
|
|
const eventStart = new Date(event.start);
|
|
return eventStart.getHours() === hour;
|
|
})
|
|
.map((event) => renderEventInCell(event, dayEvents))}
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</React.Fragment>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default WeekView;
|