NeahStable/hooks/use-calendar-event-notifications.ts

192 lines
6.7 KiB
TypeScript

import { useState, useEffect, useRef } from 'react';
import { Calendar } from 'lucide-react';
import { OutlookNotificationData } from '@/components/outlook-notification';
import { format } from 'date-fns';
import { fr } from 'date-fns/locale';
interface CalendarEvent {
id: string;
title: string;
start: Date;
end: Date;
isAllDay: boolean;
calendarName?: string;
calendarColor?: string;
}
/**
* Hook to manage calendar event notifications and show Outlook-style notifications
* when an event's start time arrives
*/
export function useCalendarEventNotifications() {
const [eventNotification, setEventNotification] = useState<OutlookNotificationData | null>(null);
const notifiedEventIdsRef = useRef<Set<string>>(new Set());
const eventsRef = useRef<CalendarEvent[]>([]);
const checkIntervalRef = useRef<NodeJS.Timeout | null>(null);
useEffect(() => {
console.log('[useCalendarEventNotifications] 🎧 Hook initialized, listening for calendar-events-updated');
// Listen for calendar events updates
const handleEventsUpdate = (event: CustomEvent) => {
const events = event.detail?.events || [];
console.log('[useCalendarEventNotifications] 📅 Received calendar events update', {
eventsCount: events.length,
events: events.map((e: any) => ({
id: e.id,
title: e.title,
start: e.start,
isAllDay: e.isAllDay,
})),
});
if (!events || events.length === 0) {
eventsRef.current = [];
return;
}
// Convert events to CalendarEvent format
const calendarEvents: CalendarEvent[] = events.map((evt: any) => ({
id: evt.id,
title: evt.title,
start: new Date(evt.start),
end: new Date(evt.end),
isAllDay: evt.isAllDay || false,
calendarName: evt.calendarName,
calendarColor: evt.calendarColor,
}));
eventsRef.current = calendarEvents;
console.log('[useCalendarEventNotifications] Events stored', {
count: calendarEvents.length,
events: calendarEvents.map(e => ({
id: e.id,
title: e.title,
start: e.start.toISOString(),
})),
});
};
window.addEventListener('calendar-events-updated', handleEventsUpdate as EventListener);
// Check for events that are starting now (every minute)
const checkForStartingEvents = () => {
const now = new Date();
const currentTime = now.getTime();
console.log('[useCalendarEventNotifications] 🔍 Checking for starting events', {
now: now.toISOString(),
eventsCount: eventsRef.current.length,
notifiedCount: notifiedEventIdsRef.current.size,
});
// Check events that start within the next 5 minutes (to catch events that just started or are about to start)
const upcomingWindow = 5 * 60 * 1000; // 5 minutes in milliseconds
const startingEvents = eventsRef.current.filter((event) => {
// Skip if already notified
if (notifiedEventIdsRef.current.has(event.id)) {
console.log('[useCalendarEventNotifications] ⏭️ Event already notified', {
id: event.id,
title: event.title,
});
return false;
}
const eventStartTime = event.start.getTime();
const timeUntilStart = eventStartTime - currentTime;
const timeUntilStartMinutes = Math.round(timeUntilStart / 1000 / 60);
console.log('[useCalendarEventNotifications] ⏰ Checking event', {
id: event.id,
title: event.title,
start: event.start.toISOString(),
now: now.toISOString(),
timeUntilStartMinutes,
inWindow: timeUntilStart >= -2 * 60 * 1000 && timeUntilStart <= upcomingWindow,
});
// Event is starting now (within last 2 minutes) or within the next 5 minutes
// This allows catching events that just started or are about to start
return (
timeUntilStart >= -2 * 60 * 1000 && // Not more than 2 minutes ago (just started)
timeUntilStart <= upcomingWindow // Within next 5 minutes
);
});
if (startingEvents.length > 0) {
// Sort by start time (earliest first)
startingEvents.sort((a, b) => a.start.getTime() - b.start.getTime());
// Show notification for the first event starting
const event = startingEvents[0];
console.log('[useCalendarEventNotifications] 📅 Event starting detected!', {
title: event.title,
start: event.start.toISOString(),
now: now.toISOString(),
timeUntilStart: Math.round((event.start.getTime() - currentTime) / 1000 / 60),
});
const timeStr = event.isAllDay
? 'Toute la journée'
: format(event.start, 'HH:mm', { locale: fr });
const notification: OutlookNotificationData = {
id: `calendar-${event.id}-${Date.now()}`,
source: 'calendar',
title: 'Agenda',
subtitle: event.isAllDay ? 'Événement aujourd\'hui' : 'Événement maintenant',
message: `${event.title}${event.isAllDay ? '' : ` à ${timeStr}`}${event.calendarName ? ` (${event.calendarName})` : ''}`,
icon: Calendar,
iconColor: 'text-orange-600',
iconBgColor: 'bg-orange-100',
borderColor: 'border-orange-500',
link: '/agenda',
timestamp: event.start,
autoDismiss: 30000, // 30 seconds for calendar events
actions: [
{
label: 'Ouvrir',
onClick: () => {
window.location.href = '/agenda';
},
variant: 'default',
className: 'bg-orange-600 hover:bg-orange-700 text-white',
},
],
};
setEventNotification(notification);
notifiedEventIdsRef.current.add(event.id);
// Clean up old notified events (older than 1 hour) to allow re-notification if needed
setTimeout(() => {
notifiedEventIdsRef.current.delete(event.id);
}, 60 * 60 * 1000); // 1 hour
}
};
// Check immediately and then every 10 seconds for more responsive notifications
checkForStartingEvents();
checkIntervalRef.current = setInterval(checkForStartingEvents, 10000); // Every 10 seconds
return () => {
window.removeEventListener('calendar-events-updated', handleEventsUpdate as EventListener);
if (checkIntervalRef.current) {
clearInterval(checkIntervalRef.current);
}
};
}, []);
const handleDismiss = () => {
setEventNotification(null);
};
return {
eventNotification,
setEventNotification: handleDismiss,
};
}