diff --git a/app/agenda/page.tsx b/app/agenda/page.tsx index fb9d9ffc..04c2fc5b 100644 --- a/app/agenda/page.tsx +++ b/app/agenda/page.tsx @@ -106,24 +106,16 @@ export default async function CalendarPage() { }, 0); return ( -
-
-
-
-
- -
-
-
-
+
+
); } diff --git a/components/calendar/calendar-client.tsx b/components/calendar/calendar-client.tsx index 5354a9d0..1533bc16 100644 --- a/components/calendar/calendar-client.tsx +++ b/components/calendar/calendar-client.tsx @@ -1,12 +1,12 @@ "use client"; -import { useState, useRef, useEffect, useMemo } from "react"; +import { useState, useRef, useEffect } 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, CardHeader, CardContent } from "@/components/ui/card"; +import { Card } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { @@ -435,7 +435,9 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend ...cal, events: cal.events || [] }))); - const [selectedCalendarId, setSelectedCalendarId] = useState(undefined); + const [selectedCalendarId, setSelectedCalendarId] = useState( + initialCalendars[0]?.id || "" + ); const [view, setView] = useState<"dayGridMonth" | "timeGridWeek" | "timeGridDay">("dayGridMonth"); const [isEventModalOpen, setIsEventModalOpen] = useState(false); const [isCalendarModalOpen, setIsCalendarModalOpen] = useState(false); @@ -464,81 +466,6 @@ 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) { @@ -625,19 +552,24 @@ 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: 'POST', + const response = await fetch("/api/calendars", { + method: calendarData.id ? "PUT" : "POST", headers: { - 'Content-Type': 'application/json', + "Content-Type": "application/json", }, body: JSON.stringify({ ...calendarData, @@ -646,15 +578,14 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend }); if (!response.ok) { - throw new Error('Failed to save calendar'); + throw new Error("Failed to save calendar"); } - const savedCalendar = await response.json(); - setCalendars(prev => [...prev, savedCalendar]); + await fetchCalendars(); 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); } @@ -664,98 +595,160 @@ 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"); } - setCalendars(prev => prev.filter(cal => cal.id !== calendarId)); - if (selectedCalendarId === calendarId) { - setSelectedCalendarId(undefined); - } + await fetchCalendars(); setIsCalendarModalOpen(false); + + // If the deleted calendar was selected, select another one + if (selectedCalendarId === calendarId) { + const remainingCalendars = calendars.filter(cal => cal.id !== calendarId); + setSelectedCalendarId(remainingCalendars[0]?.id || ""); + } } 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) => { - setSelectedEvent(null); - setEventForm({ - title: "", - description: null, - start: selectInfo.startStr, - end: selectInfo.endStr, - allDay: selectInfo.allDay, - location: null, - calendarId: selectedCalendarId + 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 }); + + // 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) => { - setSelectedEvent(clickInfo.event.extendedProps.originalEvent); + const event = clickInfo.event; + const startDate = new Date(event.start); + const endDate = new Date(event.end || event.start); + + setSelectedEvent(event.extendedProps.originalEvent); setEventForm({ - 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 + 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, }); setIsEventModalOpen(true); }; const handleEventSubmit = async () => { try { - setLoading(true); - setError(null); + // 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); const eventData = { ...eventForm, - calendarId: selectedCalendarId || calendars[0]?.id + 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 }; - const response = await fetch('/api/events', { - method: selectedEvent ? 'PUT' : 'POST', + console.log("Submitting event with data:", eventData); + + const response = await fetch("/api/events", { + method: selectedEvent ? "PUT" : "POST", headers: { - 'Content-Type': 'application/json', + "Content-Type": "application/json", }, body: JSON.stringify(eventData), }); - if (!response.ok) { - throw new Error('Failed to save event'); - } - - const savedEvent = await response.json(); + const responseData = await response.json(); + console.log("Response from server:", responseData); - 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 - }))); + if (!response.ok) { + console.error("Error response:", responseData); + throw new Error(responseData.error || "Failed to save event"); } + // 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); } @@ -923,99 +916,455 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend }; return ( -
- - -
-
- - -
+
+ +
+
+ + +
- - - handleViewChange("dayGridMonth")} - > - Mois - - handleViewChange("timeGridWeek")} - > - Semaine - - handleViewChange("timeGridDay")} - > - Jour - - - -
- - - -
+ + + handleViewChange("dayGridMonth")} + > + Mois + + handleViewChange("timeGridWeek")} + > + Semaine + + handleViewChange("timeGridDay")} + > + Jour + + + +
+ + + + + + {loading ? ( +
+ + Chargement des événements... +
+ ) : ( 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} selectable={true} + selectMirror={true} + dayMaxEventRows={false} + dayMaxEvents={false} + weekends={true} select={handleDateSelect} eventClick={handleEventClick} - 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 - }} + 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" />
- - + +
+ +