"use client"; import { useState, useEffect, useRef } from "react"; import { format, isToday, isTomorrow, addDays } from "date-fns"; import { fr } from "date-fns/locale"; import { CalendarIcon, ChevronRight } from "lucide-react"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import Link from "next/link"; import { useSession } from "next-auth/react"; import { useWidgetNotification } from "@/hooks/use-widget-notification"; type Event = { id: string; title: string; start: string | Date; end: string | Date; isAllDay: boolean; calendarId: string; calendarName?: string; calendarColor?: string; }; export function CalendarWidget() { const { data: session } = useSession(); const [events, setEvents] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const { triggerNotification } = useWidgetNotification(); const lastEventCountRef = useRef(-1); useEffect(() => { // Ne charger les événements que si l'utilisateur est connecté if (!session) return; const fetchUpcomingEvents = async () => { try { setLoading(true); // Récupérer d'abord les calendriers de l'utilisateur const calendarsRes = await fetch("/api/calendars"); if (!calendarsRes.ok) { throw new Error("Impossible de charger les calendriers"); } const calendars = await calendarsRes.json(); if (calendars.length === 0) { setEvents([]); setLoading(false); return; } // Date actuelle et date dans 7 jours const now = new Date(); const nextWeek = addDays(now, 7); // Helper function to get display name for calendar const getCalendarDisplayName = (calendar: any) => { // If calendar is synced to an external account, use the account name if (calendar.syncConfig?.syncEnabled && calendar.syncConfig?.mailCredential) { return calendar.syncConfig.mailCredential.display_name || calendar.syncConfig.mailCredential.email; } // For non-synced calendars, use the calendar name return calendar.name; }; // Récupérer les événements pour chaque calendrier const allEventsPromises = calendars.map(async (calendar: any) => { const eventsRes = await fetch( `/api/calendars/${ calendar.id }/events?start=${now.toISOString()}&end=${nextWeek.toISOString()}` ); if (!eventsRes.ok) { console.warn( `Impossible de charger les événements du calendrier ${calendar.id}` ); return []; } const events = await eventsRes.json(); // Ajouter les informations du calendrier à chaque événement return events.map((event: any) => ({ ...event, calendarName: getCalendarDisplayName(calendar), calendarColor: calendar.color, })); }); // Attendre toutes les requêtes d'événements const allEventsArrays = await Promise.all(allEventsPromises); // Fusionner tous les événements en un seul tableau const allEvents = allEventsArrays.flat(); // Trier par date de début const sortedEvents = allEvents.sort( (a, b) => new Date(a.start).getTime() - new Date(b.start).getTime() ); // Limiter à 5 événements pour l'affichage const upcomingEvents = sortedEvents.slice(0, 5); // Calculate current event count (all events, not just displayed) const currentEventCount = sortedEvents.length; // Prepare notification items for all upcoming events const notificationItems = sortedEvents.map(event => ({ id: event.id, title: event.title, message: event.isAllDay ? `Aujourd'hui (toute la journée)` : `Le ${format(new Date(event.start), 'dd/MM à HH:mm', { locale: fr })}`, link: '/agenda', timestamp: new Date(event.start), metadata: { calendarId: event.calendarId, calendarName: event.calendarName, isAllDay: event.isAllDay, }, })); // Always trigger notification update to keep count fresh in Redis await triggerNotification({ source: 'calendar', count: currentEventCount, items: notificationItems, }); // Update last count reference if (currentEventCount !== lastEventCountRef.current) { lastEventCountRef.current = currentEventCount; } setEvents(upcomingEvents); // Dispatch event for Outlook-style notifications const eventsForNotification = sortedEvents.map(evt => ({ id: evt.id, title: evt.title, start: new Date(evt.start), end: new Date(evt.end), isAllDay: evt.isAllDay, calendarName: evt.calendarName, calendarColor: evt.calendarColor, })); try { window.dispatchEvent(new CustomEvent('calendar-events-updated', { detail: { events: eventsForNotification, } })); } catch (error) { console.error('[Calendar Widget] Error dispatching event', error); } setError(null); } catch (err) { console.error("Calendar Widget - Error in fetchUpcomingEvents:", err); setError("Impossible de charger les événements à venir"); } finally { setLoading(false); } }; fetchUpcomingEvents(); }, [session]); const formatEventDate = (date: string | Date, isAllDay: boolean) => { const eventDate = new Date(date); let dateString = ""; if (isToday(eventDate)) { dateString = "Aujourd'hui"; } else if (isTomorrow(eventDate)) { dateString = "Demain"; } else { dateString = format(eventDate, "EEEE d MMMM", { locale: fr }); } if (!isAllDay) { dateString += ` · ${format(eventDate, "HH:mm", { locale: fr })}`; } return dateString; }; return ( Événements à venir {loading ? (
Chargement...
) : error ? (

{error}

) : events.length === 0 ? (

Aucun événement à venir cette semaine

) : (
{events.map((event) => (
{event.title}
{formatEventDate(event.start, event.isAllDay)}
))}
)} ); }