Agenda refactor

This commit is contained in:
alma 2026-01-15 17:59:17 +01:00
parent 3f20f11736
commit 0f6c48b0a8

View File

@ -783,6 +783,8 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend
const [visibleCalendarIds, setVisibleCalendarIds] = useState<string[]>([]);
const [selectedDate, setSelectedDate] = useState<Date | null>(null);
const clickTimerRef = useRef<NodeJS.Timeout | null>(null);
const lastClickDateRef = useRef<Date | null>(null);
// Update useEffect to initialize visible calendars and fetch events
useEffect(() => {
@ -793,6 +795,15 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend
}
}, [calendars]);
// Cleanup timer on unmount
useEffect(() => {
return () => {
if (clickTimerRef.current) {
clearTimeout(clickTimerRef.current);
}
};
}, []);
const updateStatistics = () => {
const now = new Date();
const stats = {
@ -941,14 +952,70 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend
};
const handleDateClick = (clickInfo: any) => {
// Set the selected date to show events for that day
const clickedDate = new Date(clickInfo.date);
// Reset time to start of day for comparison
clickedDate.setHours(0, 0, 0, 0);
setSelectedDate(clickedDate);
// Check if this is a double click (same date clicked within 300ms)
const now = new Date();
const isDoubleClick = lastClickDateRef.current &&
lastClickDateRef.current.getTime() === clickedDate.getTime() &&
now.getTime() - (lastClickDateRef.current as any).clickTime < 300;
// Clear any pending single click timer
if (clickTimerRef.current) {
clearTimeout(clickTimerRef.current);
clickTimerRef.current = null;
}
if (isDoubleClick) {
// Double click: Open event form
const startDate = new Date(clickInfo.date);
startDate.setHours(new Date().getHours(), 0, 0, 0);
const endDate = new Date(startDate);
endDate.setHours(startDate.getHours() + 1);
// If no calendar is selected, use the first available calendar
if (!selectedCalendarId && calendars.length > 0) {
const firstCalendar = calendars[0];
setSelectedCalendarId(firstCalendar.id);
setEventForm({
title: "",
description: null,
start: startDate.toISOString(),
end: endDate.toISOString(),
allDay: false,
location: null,
calendarId: firstCalendar.id
});
} else {
setEventForm({
title: "",
description: null,
start: startDate.toISOString(),
end: endDate.toISOString(),
allDay: false,
location: null,
calendarId: selectedCalendarId
});
}
setIsEventModalOpen(true);
lastClickDateRef.current = null;
} else {
// Single click: Show events column (with a small delay to allow double click detection)
lastClickDateRef.current = clickedDate;
(lastClickDateRef.current as any).clickTime = now.getTime();
clickTimerRef.current = setTimeout(() => {
setSelectedDate(clickedDate);
clickTimerRef.current = null;
}, 300);
}
};
const handleDateSelect = (selectInfo: any) => {
// Double click or drag selection: Open event form
const startDate = new Date(selectInfo.start);
const endDate = new Date(selectInfo.end);
@ -958,7 +1025,7 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend
availableCalendars: calendars.length
});
// Set selected date to show events for the selected day
// Also set selected date to show events for the selected day
const selectDate = new Date(startDate);
selectDate.setHours(0, 0, 0, 0);
setSelectedDate(selectDate);
@ -1320,115 +1387,7 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend
</Card>
</div>
{/* Middle column for day events */}
{selectedDate && (
<div className="w-80 flex-shrink-0">
<Card className="p-4 h-full flex flex-col">
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-semibold text-gray-900">
{selectedDate.toLocaleDateString('fr-FR', {
weekday: 'long',
day: 'numeric',
month: 'long',
year: 'numeric'
})}
</h3>
<Button
variant="ghost"
size="icon"
onClick={() => setSelectedDate(null)}
className="h-8 w-8"
>
<X className="h-4 w-4" />
</Button>
</div>
<ScrollArea className="flex-1">
{dayEvents.length === 0 ? (
<div className="text-center text-gray-500 py-8">
<CalendarIcon className="h-12 w-12 mx-auto mb-2 opacity-50" />
<p>Aucun événement ce jour</p>
</div>
) : (
<div className="space-y-3">
{dayEvents.map((event) => {
const calendar = event.calendar as CalendarWithMission;
const startDate = new Date(event.start);
const endDate = new Date(event.end);
const isAllDay = event.isAllDay;
return (
<Card
key={event.id}
className="p-3 cursor-pointer hover:shadow-md transition-shadow"
onClick={() => handleEventClick({ event: {
title: event.title,
start: startDate,
end: endDate,
isAllDay: isAllDay,
extendedProps: {
description: event.description,
location: event.location,
calendarId: event.calendarId,
originalEvent: event,
color: calendar.color
}
}})}
>
<div className="flex items-start gap-3">
<div
className="w-1 h-full rounded-full flex-shrink-0 mt-1"
style={{ backgroundColor: calendar.color }}
/>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-1">
<h4 className="font-medium text-gray-900 truncate">
{event.title}
</h4>
<div
className="w-2 h-2 rounded-full flex-shrink-0"
style={{ backgroundColor: calendar.color }}
/>
</div>
<div className="flex items-center gap-2 text-sm text-gray-600">
<Clock className="h-3 w-3" />
<span>
{isAllDay
? "Toute la journée"
: `${startDate.toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' })} - ${endDate.toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' })}`
}
</span>
</div>
{event.location && (
<div className="flex items-center gap-2 text-sm text-gray-600 mt-1">
<MapPin className="h-3 w-3" />
<span className="truncate">{event.location}</span>
</div>
)}
<div className="mt-1">
<Badge
variant="outline"
className="text-xs"
style={{
borderColor: calendar.color,
color: calendar.color
}}
>
{getCalendarDisplayName(calendar)}
</Badge>
</div>
</div>
</div>
</Card>
);
})}
</div>
)}
</ScrollArea>
</Card>
</div>
)}
{/* Right column for calendar view */}
{/* Middle column for calendar view */}
<div className="flex-1 flex flex-col">
<Card className="p-4 flex-1 flex flex-col">
<div className="flex items-center justify-between mb-4">
@ -1693,12 +1652,10 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend
);
}}
locale={frLocale}
selectable={true}
selectMirror={true}
selectable={false}
dayMaxEventRows={false}
dayMaxEvents={false}
weekends={true}
select={handleDateSelect}
eventClick={handleEventClick}
height="auto"
aspectRatio={1.8}
@ -1727,6 +1684,114 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend
</Card>
</div>
{/* Right column for day events */}
{selectedDate && (
<div className="w-80 flex-shrink-0">
<Card className="p-4 h-full flex flex-col">
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-semibold text-gray-900">
{selectedDate.toLocaleDateString('fr-FR', {
weekday: 'long',
day: 'numeric',
month: 'long',
year: 'numeric'
})}
</h3>
<Button
variant="ghost"
size="icon"
onClick={() => setSelectedDate(null)}
className="h-8 w-8"
>
<X className="h-4 w-4" />
</Button>
</div>
<ScrollArea className="flex-1">
{dayEvents.length === 0 ? (
<div className="text-center text-gray-500 py-8">
<CalendarIcon className="h-12 w-12 mx-auto mb-2 opacity-50" />
<p>Aucun événement ce jour</p>
</div>
) : (
<div className="space-y-3">
{dayEvents.map((event) => {
const calendar = event.calendar as CalendarWithMission;
const startDate = new Date(event.start);
const endDate = new Date(event.end);
const isAllDay = event.isAllDay;
return (
<Card
key={event.id}
className="p-3 cursor-pointer hover:shadow-md transition-shadow"
onClick={() => handleEventClick({ event: {
title: event.title,
start: startDate,
end: endDate,
isAllDay: isAllDay,
extendedProps: {
description: event.description,
location: event.location,
calendarId: event.calendarId,
originalEvent: event,
color: calendar.color
}
}})}
>
<div className="flex items-start gap-3">
<div
className="w-1 h-full rounded-full flex-shrink-0 mt-1"
style={{ backgroundColor: calendar.color }}
/>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-1">
<h4 className="font-medium text-gray-900 truncate">
{event.title}
</h4>
<div
className="w-2 h-2 rounded-full flex-shrink-0"
style={{ backgroundColor: calendar.color }}
/>
</div>
<div className="flex items-center gap-2 text-sm text-gray-600">
<Clock className="h-3 w-3" />
<span>
{isAllDay
? "Toute la journée"
: `${startDate.toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' })} - ${endDate.toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' })}`
}
</span>
</div>
{event.location && (
<div className="flex items-center gap-2 text-sm text-gray-600 mt-1">
<MapPin className="h-3 w-3" />
<span className="truncate">{event.location}</span>
</div>
)}
<div className="mt-1">
<Badge
variant="outline"
className="text-xs"
style={{
borderColor: calendar.color,
color: calendar.color
}}
>
{getCalendarDisplayName(calendar)}
</Badge>
</div>
</div>
</div>
</Card>
);
})}
</div>
)}
</ScrollArea>
</Card>
</div>
)}
{/* Calendar dialog */}
<CalendarDialog
open={isCalendarModalOpen}