From 24d3ae011c28bb1cf86e8b9f2d00fc94eb5f24e0 Mon Sep 17 00:00:00 2001 From: Alma Date: Sun, 13 Apr 2025 12:49:00 +0200 Subject: [PATCH] calendar 2 --- app/calendar/page.tsx | 90 ++--- components/calendar/calendar-client.tsx | 452 +++++++++++++----------- components/calendar/calendar-widget.tsx | 173 +++++++++ components/navbar.tsx | 15 + components/navbar/calendar-button.tsx | 32 ++ components/sidebar.tsx | 2 + components/sidebar/calendar-nav.tsx | 24 ++ 7 files changed, 540 insertions(+), 248 deletions(-) create mode 100644 components/calendar/calendar-widget.tsx create mode 100644 components/navbar.tsx create mode 100644 components/navbar/calendar-button.tsx create mode 100644 components/sidebar/calendar-nav.tsx diff --git a/app/calendar/page.tsx b/app/calendar/page.tsx index e50864ce..f78d9176 100644 --- a/app/calendar/page.tsx +++ b/app/calendar/page.tsx @@ -1,3 +1,5 @@ +"use client"; + import { getServerSession } from "next-auth/next"; import { authOptions } from "@/app/api/auth/[...nextauth]/route"; import { redirect } from "next/navigation"; @@ -12,52 +14,58 @@ export const metadata = { export default async function CalendarPage() { const session = await getServerSession(authOptions); - if (!session) { - redirect("/signin"); + if (!session?.user) { + redirect("/api/auth/signin"); } - try { - // Récupérer tous les calendriers de l'utilisateur - const userCalendars = await prisma.calendar.findMany({ - where: { + // Get user's calendars + const userCalendars = await prisma.calendar.findMany({ + where: { + userId: session.user.username || session.user.email, + }, + include: { + events: { + orderBy: { + start: 'asc' + } + } + }, + orderBy: { + createdAt: "desc", + }, + }); + + // Create default calendar if none exists + let calendars = userCalendars; + if (calendars.length === 0) { + const defaultCalendar = await prisma.calendar.create({ + data: { + name: "Calendrier principal", + color: "#0082c9", + description: "Calendrier par défaut", userId: session.user.username || session.user.email, }, include: { - events: true, - }, + events: true + } }); - - // Si aucun calendrier n'existe, en créer un par défaut - let calendars = userCalendars; - if (calendars.length === 0) { - const defaultCalendar = await prisma.calendar.create({ - data: { - name: "Calendrier principal", - color: "#0082c9", - description: "Calendrier par défaut", - userId: session.user.username || session.user.email, - }, - }); - calendars = [defaultCalendar]; - } - - return ( -
-
-

Calendrier

-

- Gérez vos rendez-vous et événements -

-
- -
- ); - } catch (error) { - console.error("Database Error:", error); - return ( -
-

Unable to load calendar data. Please try again later.

-
- ); + calendars = [defaultCalendar]; } + + return ( +
+
+

Calendrier

+

+ Gérez vos rendez-vous et événements +

+
+
+ +
+
+ ); } diff --git a/components/calendar/calendar-client.tsx b/components/calendar/calendar-client.tsx index 743fc997..3c2694dc 100644 --- a/components/calendar/calendar-client.tsx +++ b/components/calendar/calendar-client.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState, useEffect, useRef } from "react"; +import { useState, useRef } from "react"; import FullCalendar from "@fullcalendar/react"; import dayGridPlugin from "@fullcalendar/daygrid"; import timeGridPlugin from "@fullcalendar/timegrid"; @@ -10,132 +10,163 @@ import { Card } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Loader2, Plus } from "lucide-react"; -import { useCalendarEvents } from "@/hooks/use-calendar-events"; -import { EventDialog } from "@/components/calendar/event-dialog"; -import { Calendar, Calendar as CalendarType } from "@prisma/client"; -import { useToast } from "@/components/ui/use-toast"; -import { CalendarDialog } from "./calendar-dialog"; +import { Calendar, Event } from "@prisma/client"; +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog"; +import { Input } from "@/components/ui/input"; +import { Textarea } from "@/components/ui/textarea"; interface CalendarClientProps { - initialCalendars: CalendarType[]; + initialCalendars: (Calendar & { events: Event[] })[]; + userId: string; } -export function CalendarClient({ initialCalendars }: CalendarClientProps) { - const [isCalendarDialogOpen, setIsCalendarDialogOpen] = useState(false); - const [calendars, setCalendars] = useState(initialCalendars); +interface EventFormData { + title: string; + description: string | null; + start: string; + end: string; + allDay: boolean; + location: string | null; + calendarId?: string; +} + +export function CalendarClient({ initialCalendars, userId }: CalendarClientProps) { + const [calendars, setCalendars] = useState(initialCalendars); const [selectedCalendarId, setSelectedCalendarId] = useState( initialCalendars[0]?.id || "" ); - const [view, setView] = useState< - "dayGridMonth" | "timeGridWeek" | "timeGridDay" - >("dayGridMonth"); - const [isDialogOpen, setIsDialogOpen] = useState(false); - const [selectedEvent, setSelectedEvent] = useState(null); - const [dateRange, setDateRange] = useState({ - start: new Date(), - end: new Date(new Date().setMonth(new Date().getMonth() + 1)), + const [view, setView] = useState<"dayGridMonth" | "timeGridWeek" | "timeGridDay">("dayGridMonth"); + const [isEventModalOpen, setIsEventModalOpen] = useState(false); + const [selectedEvent, setSelectedEvent] = useState(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [eventForm, setEventForm] = useState({ + title: "", + description: null, + start: "", + end: "", + allDay: false, + location: null, }); const calendarRef = useRef(null); - const { toast } = useToast(); - const { - events, - loading, - error, - refresh, - createEvent, - updateEvent, - deleteEvent, - } = useCalendarEvents(selectedCalendarId, dateRange.start, dateRange.end); - - // Mettre à jour la plage de dates lorsque la vue change - const handleDatesSet = (arg: any) => { - setDateRange({ - start: arg.start, - end: arg.end, - }); - }; - - // Gérer la sélection d'une plage de dates pour créer un événement const handleDateSelect = (selectInfo: any) => { - setSelectedEvent({ - start: selectInfo.startStr, - end: selectInfo.endStr, + const startDate = new Date(selectInfo.start); + const endDate = new Date(selectInfo.end); + + setEventForm({ + title: "", + description: null, + start: startDate.toISOString().slice(0, 16), + end: endDate.toISOString().slice(0, 16), allDay: selectInfo.allDay, + location: null, + calendarId: selectedCalendarId, }); - setIsDialogOpen(true); + setIsEventModalOpen(true); }; - // Gérer le clic sur un événement existant const handleEventClick = (clickInfo: any) => { - setSelectedEvent({ - id: clickInfo.event.id, - title: clickInfo.event.title, - description: clickInfo.event.extendedProps.description, - start: clickInfo.event.startStr, - end: clickInfo.event.endStr, - location: clickInfo.event.extendedProps.location, - allDay: clickInfo.event.allDay, + const event = clickInfo.event; + const startDate = new Date(event.start); + const endDate = new Date(event.end || event.start); + + setSelectedEvent(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, }); - setIsDialogOpen(true); + setIsEventModalOpen(true); }; - // Gérer la création ou mise à jour d'un événement - const handleEventSave = async (eventData: any) => { + const handleEventSubmit = async () => { try { - if (eventData.id) { - await updateEvent(eventData); - toast({ - title: "Événement mis à jour", - description: "L'événement a été modifié avec succès.", - }); - } else { - await createEvent({ - ...eventData, - calendarId: selectedCalendarId, - }); - toast({ - title: "Événement créé", - description: "L'événement a été ajouté au calendrier.", - }); + setLoading(true); + const method = selectedEvent ? "PUT" : "POST"; + const url = selectedEvent + ? `/api/calendar?id=${selectedEvent.id}` + : "/api/calendar"; + + const response = await fetch(url, { + method, + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + ...eventForm, + id: selectedEvent?.id, + start: new Date(eventForm.start), + end: new Date(eventForm.end), + userId, + }), + }); + + if (!response.ok) { + throw new Error("Erreur lors de la sauvegarde de l'événement"); } - setIsDialogOpen(false); - refresh(); - } catch (error) { - console.error("Erreur lors de la sauvegarde de l'événement:", error); - toast({ - title: "Erreur", - description: "Impossible d'enregistrer l'événement.", - variant: "destructive", + + // Refresh calendar data + const eventsResponse = await fetch("/api/calendar"); + const updatedEvents = await eventsResponse.json(); + setCalendars(calendars.map(cal => ({ + ...cal, + events: updatedEvents.filter((event: Event) => event.calendarId === cal.id) + }))); + + setIsEventModalOpen(false); + setSelectedEvent(null); + setEventForm({ + title: "", + description: null, + start: "", + end: "", + allDay: false, + location: null, }); + } catch (error) { + setError((error as Error).message); + } finally { + setLoading(false); } }; - // Gérer la suppression d'un événement - const handleEventDelete = async (eventId: string) => { + const handleEventDelete = async () => { + if (!selectedEvent) return; + try { - await deleteEvent(eventId); - toast({ - title: "Événement supprimé", - description: "L'événement a été supprimé du calendrier.", + setLoading(true); + const response = await fetch(`/api/calendar?id=${selectedEvent.id}`, { + method: "DELETE", }); - setIsDialogOpen(false); - refresh(); + + if (!response.ok) { + throw new Error("Erreur lors de la suppression de l'événement"); + } + + // Refresh calendar data + const eventsResponse = await fetch("/api/calendar"); + const updatedEvents = await eventsResponse.json(); + setCalendars(calendars.map(cal => ({ + ...cal, + events: updatedEvents.filter((event: Event) => event.calendarId === cal.id) + }))); + + setIsEventModalOpen(false); + setSelectedEvent(null); } catch (error) { - console.error("Erreur lors de la suppression de l'événement:", error); - toast({ - title: "Erreur", - description: "Impossible de supprimer l'événement.", - variant: "destructive", - }); + setError((error as Error).message); + } finally { + setLoading(false); } }; - // Changer la vue du calendrier - const handleViewChange = ( - newView: "dayGridMonth" | "timeGridWeek" | "timeGridDay" - ) => { + const handleViewChange = (newView: "dayGridMonth" | "timeGridWeek" | "timeGridDay") => { setView(newView); if (calendarRef.current) { const calendarApi = calendarRef.current.getApi(); @@ -143,126 +174,67 @@ export function CalendarClient({ initialCalendars }: CalendarClientProps) { } }; - // Changer le calendrier sélectionné - const handleCalendarChange = (calendarId: string) => { - setSelectedCalendarId(calendarId); - }; - - // Fonction pour créer un nouveau calendrier - const handleCreateCalendar = async (calendarData: Partial) => { - try { - const response = await fetch("/api/calendars", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(calendarData), - }); - - if (!response.ok) { - throw new Error(`Erreur ${response.status}: ${await response.text()}`); - } - - const newCalendar = await response.json(); - - // Mettre à jour la liste des calendriers - setCalendars([...calendars, newCalendar]); - - // Sélectionner automatiquement le nouveau calendrier - setSelectedCalendarId(newCalendar.id); - - toast({ - title: "Calendrier créé", - description: `Le calendrier "${newCalendar.name}" a été créé avec succès.`, - }); - } catch (error) { - console.error("Erreur lors de la création du calendrier:", error); - toast({ - title: "Erreur", - description: "Impossible de créer le calendrier.", - variant: "destructive", - }); - } - }; - return ( -
- {/* Options et filtres du calendrier */} -
-
+
+ {/* Calendar filters and options */} +
+
{calendars.map((calendar) => ( ))} -
- {/* Sélecteur de vue */} - - + {/* View selector */} + + handleViewChange("dayGridMonth")} > Mois handleViewChange("timeGridWeek")} > Semaine handleViewChange("timeGridDay")} > Jour - {/* Affichage du calendrier */} - + {/* Calendar display */} + {error && ( -
- Erreur: {error.message} +
+ Erreur: {error}
)} - {loading && !events.length ? ( -
- - Chargement des événements... + {loading ? ( +
+ + Chargement des événements...
) : ( ({ - id: event.id, - title: event.title, - start: event.start, - end: event.end, - allDay: event.isAllDay, - extendedProps: { + events={calendars.flatMap(cal => + cal.events.map(event => ({ + id: event.id, + title: event.title, + start: event.start, + end: event.end, + allDay: event.isAllDay, description: event.description, location: event.location, - }, - }))} + calendarId: event.calendarId, + originalEvent: event, + })) + )} + locale={frLocale} selectable={true} selectMirror={true} dayMaxEvents={true} weekends={true} - locale={frLocale} select={handleDateSelect} eventClick={handleEventClick} - datesSet={handleDatesSet} - height='auto' + height="auto" aspectRatio={1.8} /> )} - {isCalendarDialogOpen && ( - setIsCalendarDialogOpen(false)} - onSave={handleCreateCalendar} - /> - )} - - {/* Dialogue pour créer/modifier un événement */} - {isDialogOpen && ( - setIsDialogOpen(false)} - onSave={handleEventSave} - onDelete={selectedEvent?.id ? handleEventDelete : undefined} - calendars={calendars} - /> - )} + {/* Event creation/edit dialog */} + + + + + {selectedEvent ? "Modifier l'événement" : "Nouvel événement"} + + +
+
+ + + setEventForm({ ...eventForm, title: e.target.value }) + } + /> +
+
+ +