325 lines
9.8 KiB
TypeScript
325 lines
9.8 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useEffect, useRef } 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 { 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";
|
|
|
|
interface CalendarClientProps {
|
|
initialCalendars: CalendarType[];
|
|
}
|
|
|
|
export function CalendarClient({ initialCalendars }: CalendarClientProps) {
|
|
const [isCalendarDialogOpen, setIsCalendarDialogOpen] = useState(false);
|
|
const [calendars, setCalendars] = useState<CalendarType[]>(initialCalendars);
|
|
const [selectedCalendarId, setSelectedCalendarId] = useState<string>(
|
|
initialCalendars[0]?.id || ""
|
|
);
|
|
const [view, setView] = useState<
|
|
"dayGridMonth" | "timeGridWeek" | "timeGridDay"
|
|
>("dayGridMonth");
|
|
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
|
const [selectedEvent, setSelectedEvent] = useState<any>(null);
|
|
const [dateRange, setDateRange] = useState({
|
|
start: new Date(),
|
|
end: new Date(new Date().setMonth(new Date().getMonth() + 1)),
|
|
});
|
|
|
|
const calendarRef = useRef<any>(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,
|
|
allDay: selectInfo.allDay,
|
|
});
|
|
setIsDialogOpen(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,
|
|
});
|
|
setIsDialogOpen(true);
|
|
};
|
|
|
|
// Gérer la création ou mise à jour d'un événement
|
|
const handleEventSave = async (eventData: any) => {
|
|
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.",
|
|
});
|
|
}
|
|
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",
|
|
});
|
|
}
|
|
};
|
|
|
|
// Gérer la suppression d'un événement
|
|
const handleEventDelete = async (eventId: string) => {
|
|
try {
|
|
await deleteEvent(eventId);
|
|
toast({
|
|
title: "Événement supprimé",
|
|
description: "L'événement a été supprimé du calendrier.",
|
|
});
|
|
setIsDialogOpen(false);
|
|
refresh();
|
|
} 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",
|
|
});
|
|
}
|
|
};
|
|
|
|
// Changer la vue du calendrier
|
|
const handleViewChange = (
|
|
newView: "dayGridMonth" | "timeGridWeek" | "timeGridDay"
|
|
) => {
|
|
setView(newView);
|
|
if (calendarRef.current) {
|
|
const calendarApi = calendarRef.current.getApi();
|
|
calendarApi.changeView(newView);
|
|
}
|
|
};
|
|
|
|
// Changer le calendrier sélectionné
|
|
const handleCalendarChange = (calendarId: string) => {
|
|
setSelectedCalendarId(calendarId);
|
|
};
|
|
|
|
// Fonction pour créer un nouveau calendrier
|
|
const handleCreateCalendar = async (calendarData: Partial<Calendar>) => {
|
|
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 (
|
|
<div className='space-y-4'>
|
|
{/* Options et filtres du calendrier */}
|
|
<div className='flex flex-wrap justify-between items-center gap-4 mb-4'>
|
|
<div className='flex flex-wrap gap-2'>
|
|
{calendars.map((calendar) => (
|
|
<Button
|
|
key={calendar.id}
|
|
variant={
|
|
calendar.id === selectedCalendarId ? "default" : "outline"
|
|
}
|
|
onClick={() => handleCalendarChange(calendar.id)}
|
|
style={{
|
|
backgroundColor:
|
|
calendar.id === selectedCalendarId
|
|
? calendar.color
|
|
: undefined,
|
|
borderColor: calendar.color,
|
|
}}
|
|
>
|
|
{calendar.name}
|
|
</Button>
|
|
))}
|
|
<Button
|
|
variant='ghost'
|
|
size='icon'
|
|
onClick={() => setIsCalendarDialogOpen(true)}
|
|
title='Créer un nouveau calendrier'
|
|
>
|
|
<Plus className='h-4 w-4' />
|
|
</Button>
|
|
</div>
|
|
<Button
|
|
onClick={() => {
|
|
setSelectedEvent(null);
|
|
setIsDialogOpen(true);
|
|
}}
|
|
>
|
|
<Plus className='mr-2 h-4 w-4' />
|
|
Nouvel événement
|
|
</Button>
|
|
</div>
|
|
|
|
{/* Sélecteur de vue */}
|
|
<Tabs value={view} className='w-full'>
|
|
<TabsList className='mb-4'>
|
|
<TabsTrigger
|
|
value='dayGridMonth'
|
|
onClick={() => handleViewChange("dayGridMonth")}
|
|
>
|
|
Mois
|
|
</TabsTrigger>
|
|
<TabsTrigger
|
|
value='timeGridWeek'
|
|
onClick={() => handleViewChange("timeGridWeek")}
|
|
>
|
|
Semaine
|
|
</TabsTrigger>
|
|
<TabsTrigger
|
|
value='timeGridDay'
|
|
onClick={() => handleViewChange("timeGridDay")}
|
|
>
|
|
Jour
|
|
</TabsTrigger>
|
|
</TabsList>
|
|
|
|
{/* Affichage du calendrier */}
|
|
<Card className='p-4'>
|
|
{error && (
|
|
<div className='p-4 mb-4 text-red-500 bg-red-50 rounded-md'>
|
|
Erreur: {error.message}
|
|
</div>
|
|
)}
|
|
|
|
{loading && !events.length ? (
|
|
<div className='h-96 flex items-center justify-center'>
|
|
<Loader2 className='h-8 w-8 animate-spin text-primary' />
|
|
<span className='ml-2'>Chargement des événements...</span>
|
|
</div>
|
|
) : (
|
|
<FullCalendar
|
|
ref={calendarRef}
|
|
plugins={[dayGridPlugin, timeGridPlugin, interactionPlugin]}
|
|
initialView={view}
|
|
headerToolbar={{
|
|
left: "prev,next today",
|
|
center: "title",
|
|
right: "",
|
|
}}
|
|
events={events.map((event) => ({
|
|
id: event.id,
|
|
title: event.title,
|
|
start: event.start,
|
|
end: event.end,
|
|
allDay: event.isAllDay,
|
|
extendedProps: {
|
|
description: event.description,
|
|
location: event.location,
|
|
},
|
|
}))}
|
|
selectable={true}
|
|
selectMirror={true}
|
|
dayMaxEvents={true}
|
|
weekends={true}
|
|
locale={frLocale}
|
|
select={handleDateSelect}
|
|
eventClick={handleEventClick}
|
|
datesSet={handleDatesSet}
|
|
height='auto'
|
|
aspectRatio={1.8}
|
|
/>
|
|
)}
|
|
</Card>
|
|
</Tabs>
|
|
|
|
{isCalendarDialogOpen && (
|
|
<CalendarDialog
|
|
open={isCalendarDialogOpen}
|
|
onClose={() => setIsCalendarDialogOpen(false)}
|
|
onSave={handleCreateCalendar}
|
|
/>
|
|
)}
|
|
|
|
{/* Dialogue pour créer/modifier un événement */}
|
|
{isDialogOpen && (
|
|
<EventDialog
|
|
open={isDialogOpen}
|
|
event={selectedEvent}
|
|
onClose={() => setIsDialogOpen(false)}
|
|
onSave={handleEventSave}
|
|
onDelete={selectedEvent?.id ? handleEventDelete : undefined}
|
|
calendars={calendars}
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|