From dcc7f71bfbd17b5164b2a990f1f9bed76bf0ae07 Mon Sep 17 00:00:00 2001 From: alma Date: Mon, 21 Apr 2025 12:00:45 +0200 Subject: [PATCH] agenda page --- app/agenda/page.tsx | 28 +- components/calendar/calendar-client.tsx | 793 +++++++----------------- 2 files changed, 240 insertions(+), 581 deletions(-) diff --git a/app/agenda/page.tsx b/app/agenda/page.tsx index 04c2fc5b..fb9d9ffc 100644 --- a/app/agenda/page.tsx +++ b/app/agenda/page.tsx @@ -106,16 +106,24 @@ export default async function CalendarPage() { }, 0); return ( -
- +
+
+
+
+
+ +
+
+
+
); } diff --git a/components/calendar/calendar-client.tsx b/components/calendar/calendar-client.tsx index 1533bc16..5354a9d0 100644 --- a/components/calendar/calendar-client.tsx +++ b/components/calendar/calendar-client.tsx @@ -1,12 +1,12 @@ "use client"; -import { useState, useRef, useEffect } from "react"; +import { useState, useRef, useEffect, useMemo } from "react"; import FullCalendar from "@fullcalendar/react"; import dayGridPlugin from "@fullcalendar/daygrid"; import timeGridPlugin from "@fullcalendar/timegrid"; import interactionPlugin from "@fullcalendar/interaction"; import frLocale from "@fullcalendar/core/locales/fr"; -import { Card } from "@/components/ui/card"; +import { Card, CardHeader, CardContent } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { @@ -435,9 +435,7 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend ...cal, events: cal.events || [] }))); - const [selectedCalendarId, setSelectedCalendarId] = useState( - initialCalendars[0]?.id || "" - ); + const [selectedCalendarId, setSelectedCalendarId] = useState(undefined); const [view, setView] = useState<"dayGridMonth" | "timeGridWeek" | "timeGridDay">("dayGridMonth"); const [isEventModalOpen, setIsEventModalOpen] = useState(false); const [isCalendarModalOpen, setIsCalendarModalOpen] = useState(false); @@ -466,6 +464,81 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend const [visibleCalendarIds, setVisibleCalendarIds] = useState([]); + // Calculate events based on visible calendars + const events = useMemo(() => { + return calendars + .filter(cal => visibleCalendarIds.includes(cal.id)) + .flatMap(cal => + (cal.events || []).map(event => ({ + id: event.id, + title: event.title, + start: new Date(event.start), + end: new Date(event.end), + allDay: event.isAllDay, + description: event.description, + location: event.location, + calendarId: event.calendarId, + backgroundColor: `${cal.color}dd`, + borderColor: cal.color, + textColor: '#ffffff', + extendedProps: { + calendarName: cal.name, + location: event.location, + description: event.description, + calendarId: event.calendarId, + originalEvent: event, + color: cal.color + } + })) + ); + }, [calendars, visibleCalendarIds]); + + // Event content renderer + const renderEventContent = (arg: any) => { + return ( + + + +
+
+ {!arg.event.allDay && ( + + {new Date(arg.event.start).toLocaleTimeString('fr-FR', { + hour: '2-digit', + minute: '2-digit' + })} + + )} + + {arg.event.title} + +
+
+
+ +
+

{arg.event.title}

+ {!arg.event.allDay && ( +

+ {new Date(arg.event.start).toLocaleTimeString('fr-FR', { + hour: '2-digit', + minute: '2-digit' + })} +

+ )} +
+
+
+
+ ); + }; + // Update useEffect to initialize visible calendars and fetch events useEffect(() => { if (calendars.length > 0) { @@ -552,24 +625,19 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend } }; - const calendarRef = useRef(null); + const calendarRef = useRef(null); const handleCalendarSelect = (calendarId: string) => { - console.log("Calendar selected:", calendarId); setSelectedCalendarId(calendarId); - setEventForm(prev => ({ - ...prev, - calendarId: calendarId - })); }; const handleCalendarSave = async (calendarData: Partial) => { try { setLoading(true); - const response = await fetch("/api/calendars", { - method: calendarData.id ? "PUT" : "POST", + const response = await fetch('/api/calendars', { + method: 'POST', headers: { - "Content-Type": "application/json", + 'Content-Type': 'application/json', }, body: JSON.stringify({ ...calendarData, @@ -578,14 +646,15 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend }); if (!response.ok) { - throw new Error("Failed to save calendar"); + throw new Error('Failed to save calendar'); } - await fetchCalendars(); + const savedCalendar = await response.json(); + setCalendars(prev => [...prev, savedCalendar]); setIsCalendarModalOpen(false); } catch (error) { - console.error("Error saving calendar:", error); - setError(error instanceof Error ? error.message : "Failed to save calendar"); + console.error('Error saving calendar:', error); + setError(error instanceof Error ? error.message : 'Failed to save calendar'); } finally { setLoading(false); } @@ -595,160 +664,98 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend try { setLoading(true); const response = await fetch(`/api/calendars/${calendarId}`, { - method: "DELETE", + method: 'DELETE', }); if (!response.ok) { - throw new Error("Failed to delete calendar"); + throw new Error('Failed to delete calendar'); } - await fetchCalendars(); - setIsCalendarModalOpen(false); - - // If the deleted calendar was selected, select another one + setCalendars(prev => prev.filter(cal => cal.id !== calendarId)); if (selectedCalendarId === calendarId) { - const remainingCalendars = calendars.filter(cal => cal.id !== calendarId); - setSelectedCalendarId(remainingCalendars[0]?.id || ""); + setSelectedCalendarId(undefined); } + setIsCalendarModalOpen(false); } catch (error) { - console.error("Error deleting calendar:", error); - setError(error instanceof Error ? error.message : "Failed to delete calendar"); + console.error('Error deleting calendar:', error); + setError(error instanceof Error ? error.message : 'Failed to delete calendar'); } finally { setLoading(false); } }; const handleDateSelect = (selectInfo: any) => { - const startDate = new Date(selectInfo.start); - const endDate = new Date(selectInfo.end); - - console.log("Date select handler - Current state:", { - calendars: calendars.map(c => ({ id: c.id, name: c.name })), - selectedCalendarId, - availableCalendars: calendars.length + setSelectedEvent(null); + setEventForm({ + title: "", + description: null, + start: selectInfo.startStr, + end: selectInfo.endStr, + allDay: selectInfo.allDay, + location: null, + calendarId: selectedCalendarId }); - - // If no calendar is selected, use the first available calendar - if (!selectedCalendarId && calendars.length > 0) { - const firstCalendar = calendars[0]; - console.log("No calendar selected, selecting first calendar:", firstCalendar); - setSelectedCalendarId(firstCalendar.id); - - setEventForm({ - title: "", - description: null, - start: startDate.toISOString(), - end: endDate.toISOString(), - allDay: selectInfo.allDay, - location: null, - calendarId: firstCalendar.id - }); - } else { - setEventForm({ - title: "", - description: null, - start: startDate.toISOString(), - end: endDate.toISOString(), - allDay: selectInfo.allDay, - location: null, - calendarId: selectedCalendarId - }); - } - setIsEventModalOpen(true); }; const handleEventClick = (clickInfo: any) => { - const event = clickInfo.event; - const startDate = new Date(event.start); - const endDate = new Date(event.end || event.start); - - setSelectedEvent(event.extendedProps.originalEvent); + setSelectedEvent(clickInfo.event.extendedProps.originalEvent); setEventForm({ - title: event.title, - description: event.extendedProps.description, - start: startDate.toISOString().slice(0, 16), - end: endDate.toISOString().slice(0, 16), - allDay: event.isAllDay, - location: event.extendedProps.location, - calendarId: event.extendedProps.calendarId, + title: clickInfo.event.title, + description: clickInfo.event.extendedProps.description, + start: clickInfo.event.startStr, + end: clickInfo.event.endStr, + allDay: clickInfo.event.allDay, + location: clickInfo.event.extendedProps.location, + calendarId: clickInfo.event.extendedProps.calendarId }); setIsEventModalOpen(true); }; const handleEventSubmit = async () => { try { - // Validate required fields including calendar - if (!eventForm.title || !eventForm.start || !eventForm.end || !eventForm.calendarId) { - console.log("Form validation failed:", { - title: eventForm.title, - start: eventForm.start, - end: eventForm.end, - calendarId: eventForm.calendarId - }); - setError("Veuillez remplir tous les champs obligatoires et sélectionner un calendrier"); - return; - } - setLoading(true); + setError(null); + const eventData = { ...eventForm, - start: new Date(eventForm.start).toISOString(), - end: new Date(eventForm.end).toISOString(), - userId, - ...(selectedEvent ? { id: selectedEvent.id } : {}), // Include ID for updates - allDay: eventForm.allDay // Use allDay instead of isAllDay + calendarId: selectedCalendarId || calendars[0]?.id }; - console.log("Submitting event with data:", eventData); - - const response = await fetch("/api/events", { - method: selectedEvent ? "PUT" : "POST", + const response = await fetch('/api/events', { + method: selectedEvent ? 'PUT' : 'POST', headers: { - "Content-Type": "application/json", + 'Content-Type': 'application/json', }, body: JSON.stringify(eventData), }); - const responseData = await response.json(); - console.log("Response from server:", responseData); - if (!response.ok) { - console.error("Error response:", responseData); - throw new Error(responseData.error || "Failed to save event"); + throw new Error('Failed to save event'); + } + + const savedEvent = await response.json(); + + if (selectedEvent) { + setCalendars(prev => prev.map(cal => ({ + ...cal, + events: cal.events.map(ev => + ev.id === selectedEvent.id ? savedEvent : ev + ) + }))); + } else { + setCalendars(prev => prev.map(cal => ({ + ...cal, + events: cal.id === eventData.calendarId + ? [...cal.events, savedEvent] + : cal.events + }))); } - // Reset form and close modal first setIsEventModalOpen(false); - setEventForm({ - title: "", - description: null, - start: "", - end: "", - allDay: false, - location: null, - calendarId: selectedCalendarId - }); - setSelectedEvent(null); - setError(null); - - // Update calendars state with the new event - const updatedCalendars = calendars.map(cal => { - if (cal.id === eventData.calendarId) { - return { - ...cal, - events: [...cal.events, responseData] - }; - } - return cal; - }); - setCalendars(updatedCalendars); - - // Fetch fresh data to ensure all calendars are up to date - await fetchCalendars(); } catch (error) { - console.error("Error saving event:", error); - setError(error instanceof Error ? error.message : "Failed to save event"); + console.error('Error saving event:', error); + setError(error instanceof Error ? error.message : 'Failed to save event'); } finally { setLoading(false); } @@ -916,455 +923,99 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend }; return ( -
- -
-
- - -
- - - - handleViewChange("dayGridMonth")} - > - Mois - - handleViewChange("timeGridWeek")} - > - Semaine - - handleViewChange("timeGridDay")} - > - Jour - - - -
- - - - - - {loading ? ( -
- - Chargement des événements... +
+ + +
+
+ +
- ) : ( + + + + handleViewChange("dayGridMonth")} + > + Mois + + handleViewChange("timeGridWeek")} + > + Semaine + + handleViewChange("timeGridDay")} + > + Jour + + + +
+
+ + +
visibleCalendarIds.includes(cal.id)) - .flatMap(cal => - (cal.events || []).map(event => ({ - id: event.id, - title: event.title, - start: new Date(event.start), - end: new Date(event.end), - allDay: event.isAllDay, - description: event.description, - location: event.location, - calendarId: event.calendarId, - backgroundColor: `${cal.color}dd`, - borderColor: cal.color, - textColor: '#ffffff', - extendedProps: { - calendarName: cal.name, - location: event.location, - description: event.description, - calendarId: event.calendarId, - originalEvent: event, - color: cal.color - } - })) - )} - eventContent={(arg) => { - return ( - - - -
-
- {!arg.event.allDay && ( - - {new Date(arg.event.start).toLocaleTimeString('fr-FR', { - hour: '2-digit', - minute: '2-digit' - })} - - )} - - {arg.event.title} - -
-
-
- -
-

{arg.event.title}

- {!arg.event.allDay && ( -

- {new Date(arg.event.start).toLocaleTimeString('fr-FR', { - hour: '2-digit', - minute: '2-digit' - })} -

- )} -
-
-
-
- ); - }} - eventClassNames="rounded-md overflow-hidden" - dayCellContent={(arg) => { - return ( -
- {arg.dayNumberText} -
- ); - }} - locale={frLocale} + initialView="dayGridMonth" + headerToolbar={false} + height="100%" + events={events} selectable={true} - selectMirror={true} - dayMaxEventRows={false} - dayMaxEvents={false} - weekends={true} select={handleDateSelect} eventClick={handleEventClick} - height="auto" - aspectRatio={1.8} - slotMinTime="06:00:00" - slotMaxTime="22:00:00" - allDaySlot={true} - allDayText="" - views={{ - timeGridWeek: { - allDayText: "", - dayHeaderFormat: { weekday: 'long', day: 'numeric', month: 'numeric' } - }, - timeGridDay: { - allDayText: "", - dayHeaderFormat: { weekday: 'long', day: 'numeric', month: 'numeric' } - } - }} - slotLabelFormat={{ - hour: '2-digit', - minute: '2-digit', - hour12: false - }} - /> - )} - - - {/* Calendar dialog */} - setIsCalendarModalOpen(false)} - onSave={handleCalendarSave} - onDelete={handleCalendarDelete} - initialData={selectedCalendar || undefined} - /> - - {/* Event dialog */} - { - if (!open) { - setIsEventModalOpen(false); - setEventForm({ - title: "", - description: null, - start: "", - end: "", - allDay: false, - location: null, - calendarId: selectedCalendarId || calendars[0]?.id - }); - setSelectedEvent(null); - setError(null); - } - }}> - - - - {selectedEvent ? "Modifier l'événement" : "Nouvel événement"} - - - - {error && ( -
- {error} -
- )} - -
-
-
- - setEventForm({ ...eventForm, title: e.target.value })} - /> -
- -
- -
- {calendars.map((cal) => ( - - ))} -
-
- -
-
- -
-
- } - /> -
- } - /> -
-
- -
- -
-
- } - minDate={getDateFromString(eventForm.start)} - /> -
- } - /> -
-
-
-
- -
- - setEventForm({ ...eventForm, allDay: checked as boolean }) - } - /> - -
- -
- - - setEventForm({ ...eventForm, location: e.target.value }) - } - placeholder="Ajouter un lieu" + eventContent={renderEventContent} + locale={frLocale} + firstDay={1} + weekends={true} + allDaySlot={true} + slotMinTime="08:00:00" + slotMaxTime="20:00:00" + expandRows={true} + stickyHeaderDates={true} + dayMaxEvents={true} + eventTimeFormat={{ + hour: '2-digit', + minute: '2-digit', + hour12: false + }} />
- -
- -