agenda page

This commit is contained in:
alma 2025-04-21 12:01:32 +02:00
parent dcc7f71bfb
commit 42233f3141
2 changed files with 582 additions and 241 deletions

View File

@ -106,24 +106,16 @@ export default async function CalendarPage() {
}, 0);
return (
<div className="flex h-[calc(100vh-4rem)]">
<div className="flex-1 flex flex-col">
<div className="flex-1 p-6">
<div className="h-full flex flex-col">
<div className="flex-1">
<CalendarClient
initialCalendars={calendars}
userId={session.user.id}
userProfile={{
name: session.user.name || '',
email: session.user.email || '',
avatar: session.user.image || undefined
}}
/>
</div>
</div>
</div>
</div>
<div className="container mx-auto py-10">
<CalendarClient
initialCalendars={calendars}
userId={session.user.id}
userProfile={{
name: session.user.name || '',
email: session.user.email || '',
avatar: session.user.image || undefined
}}
/>
</div>
);
}

View File

@ -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<string | undefined>(undefined);
const [selectedCalendarId, setSelectedCalendarId] = useState<string>(
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<string[]>([]);
// 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 (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<div
className="px-2 py-1 overflow-hidden w-full transition-all rounded-sm hover:brightness-110"
style={{
backgroundColor: `${arg.event.backgroundColor}`,
boxShadow: `inset 0 0 0 1px ${arg.event.borderColor}, 0 2px 4px ${arg.event.borderColor}40`
}}
>
<div className="flex items-center gap-1.5 text-xs text-white">
{!arg.event.allDay && (
<span className="font-medium whitespace-nowrap shrink-0">
{new Date(arg.event.start).toLocaleTimeString('fr-FR', {
hour: '2-digit',
minute: '2-digit'
})}
</span>
)}
<span className="font-medium truncate max-w-[calc(100%-4.5rem)]">
{arg.event.title}
</span>
</div>
</div>
</TooltipTrigger>
<TooltipContent>
<div className="text-sm">
<p className="font-medium">{arg.event.title}</p>
{!arg.event.allDay && (
<p className="text-xs text-gray-400 mt-1">
{new Date(arg.event.start).toLocaleTimeString('fr-FR', {
hour: '2-digit',
minute: '2-digit'
})}
</p>
)}
</div>
</TooltipContent>
</Tooltip>
</TooltipProvider>
);
};
// 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<FullCalendar>(null);
const calendarRef = useRef<any>(null);
const handleCalendarSelect = (calendarId: string) => {
console.log("Calendar selected:", calendarId);
setSelectedCalendarId(calendarId);
setEventForm(prev => ({
...prev,
calendarId: calendarId
}));
};
const handleCalendarSave = async (calendarData: Partial<Calendar>) => {
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 (
<div className="h-full flex flex-col">
<Card className="flex-1">
<CardHeader className="pb-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-4">
<Button
variant="outline"
onClick={() => {
setSelectedCalendar(null);
setIsCalendarModalOpen(true);
}}
className="bg-white hover:bg-gray-50 text-gray-900 border-gray-200"
>
<Plus className="mr-2 h-4 w-4" />
<span className="font-medium">Nouveau calendrier</span>
</Button>
<Button
onClick={() => {
setSelectedEvent(null);
setEventForm({
title: "",
description: null,
start: new Date().toISOString(),
end: new Date(new Date().setHours(new Date().getHours() + 1)).toISOString(),
allDay: false,
location: null,
calendarId: selectedCalendarId
});
setIsEventModalOpen(true);
}}
className="bg-primary hover:bg-primary/90 text-white"
>
<Plus className="mr-2 h-4 w-4" />
<span className="font-medium">Nouvel événement</span>
</Button>
</div>
<div className="space-y-4">
<Card className="p-4">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-4">
<Button
variant="outline"
onClick={() => {
setSelectedCalendar(null);
setIsCalendarModalOpen(true);
}}
className="bg-white hover:bg-gray-50 text-gray-900 border-gray-200"
>
<Plus className="mr-2 h-4 w-4" />
<span className="font-medium">Nouveau calendrier</span>
</Button>
<Button
onClick={() => {
setSelectedEvent(null);
setEventForm({
title: "",
description: null,
start: new Date().toISOString(),
end: new Date(new Date().setHours(new Date().getHours() + 1)).toISOString(),
allDay: false,
location: null,
calendarId: selectedCalendarId
});
setIsEventModalOpen(true);
}}
className="bg-primary hover:bg-primary/90 text-white"
>
<Plus className="mr-2 h-4 w-4" />
<span className="font-medium">Nouvel événement</span>
</Button>
</div>
<Tabs value={view} className="w-auto">
<TabsList>
<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>
</Tabs>
</div>
</CardHeader>
<CardContent>
<CalendarSelector />
<div className="mt-4">
<Tabs value={view} className="w-auto">
<TabsList>
<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>
</Tabs>
</div>
<CalendarSelector />
<style jsx global>{`
/* Fixed height and scrolling for day cells only */
.fc .fc-daygrid-day-frame {
min-height: 100px !important;
max-height: 100px !important;
overflow-y: auto !important;
}
/* Keep events container relative positioning */
.fc .fc-daygrid-day-events {
margin-bottom: 0 !important;
}
/* Custom scrollbar styles */
.fc .fc-daygrid-day-frame::-webkit-scrollbar {
width: 4px;
}
.fc .fc-daygrid-day-frame::-webkit-scrollbar-track {
background: transparent;
}
.fc .fc-daygrid-day-frame::-webkit-scrollbar-thumb {
background: #888;
border-radius: 4px;
}
.fc .fc-daygrid-day-frame::-webkit-scrollbar-thumb:hover {
background: #555;
}
/* Hide scrollbar when not needed */
.fc .fc-daygrid-day-frame::-webkit-scrollbar {
display: none;
}
.fc .fc-daygrid-day-frame:hover::-webkit-scrollbar {
display: block;
}
`}</style>
{loading ? (
<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="dayGridMonth"
headerToolbar={false}
height="100%"
events={events}
initialView={view}
headerToolbar={{
left: "prev,next today",
center: "title",
right: "",
}}
events={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
}
}))
)}
eventContent={(arg) => {
return (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<div
className="px-2 py-1 overflow-hidden w-full transition-all rounded-sm hover:brightness-110"
style={{
backgroundColor: `${arg.event.backgroundColor}`,
boxShadow: `inset 0 0 0 1px ${arg.event.borderColor}, 0 2px 4px ${arg.event.borderColor}40`
}}
>
<div className="flex items-center gap-1.5 text-xs text-white">
{!arg.event.allDay && (
<span className="font-medium whitespace-nowrap shrink-0">
{new Date(arg.event.start).toLocaleTimeString('fr-FR', {
hour: '2-digit',
minute: '2-digit'
})}
</span>
)}
<span className="font-medium truncate max-w-[calc(100%-4.5rem)]">
{arg.event.title}
</span>
</div>
</div>
</TooltipTrigger>
<TooltipContent>
<div className="text-sm">
<p className="font-medium">{arg.event.title}</p>
{!arg.event.allDay && (
<p className="text-xs text-gray-400 mt-1">
{new Date(arg.event.start).toLocaleTimeString('fr-FR', {
hour: '2-digit',
minute: '2-digit'
})}
</p>
)}
</div>
</TooltipContent>
</Tooltip>
</TooltipProvider>
);
}}
eventClassNames="rounded-md overflow-hidden"
dayCellContent={(arg) => {
return (
<div className="text-xs font-medium">
{arg.dayNumberText}
</div>
);
}}
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
}}
/>
)}
</Card>
{/* Calendar dialog */}
<CalendarDialog
open={isCalendarModalOpen}
onClose={() => setIsCalendarModalOpen(false)}
onSave={handleCalendarSave}
onDelete={handleCalendarDelete}
initialData={selectedCalendar || undefined}
/>
{/* Event dialog */}
<Dialog open={isEventModalOpen} onOpenChange={(open) => {
if (!open) {
setIsEventModalOpen(false);
setEventForm({
title: "",
description: null,
start: "",
end: "",
allDay: false,
location: null,
calendarId: selectedCalendarId || calendars[0]?.id
});
setSelectedEvent(null);
setError(null);
}
}}>
<DialogContent className="sm:max-w-lg">
<DialogHeader>
<DialogTitle>
{selectedEvent ? "Modifier l'événement" : "Nouvel événement"}
</DialogTitle>
</DialogHeader>
{error && (
<div className="bg-red-50 border border-red-200 text-red-600 px-4 py-2 rounded-md text-sm">
{error}
</div>
)}
<div className="space-y-4 py-4">
<div className="grid gap-4 py-4">
<div className="grid gap-2">
<Label htmlFor="title" className="text-base font-semibold">Titre</Label>
<Input
id="title"
placeholder="Titre de l'événement"
value={eventForm.title}
onChange={(e) => setEventForm({ ...eventForm, title: e.target.value })}
/>
</div>
<div className="space-y-2">
<Label className="text-base font-semibold">Calendrier</Label>
<div className="grid grid-cols-2 gap-2">
{calendars.map((cal) => (
<button
key={cal.id}
type="button"
onClick={() => {
setEventForm(prev => ({
...prev,
calendarId: cal.id
}));
}}
className={`flex items-center gap-2 px-3 py-2 rounded-lg transition-all ${
eventForm.calendarId === cal.id
? 'bg-white ring-2 ring-primary'
: 'bg-gray-900 hover:bg-gray-800'
}`}
>
<div
className="w-3 h-3 rounded-full"
style={{ backgroundColor: cal.color }}
/>
<span className={`text-sm ${
eventForm.calendarId === cal.id
? 'font-medium text-gray-900'
: 'text-gray-100'
}`}>
{cal.name}
</span>
</button>
))}
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label>Début</Label>
<div className="flex gap-2">
<div className="flex-1">
<DatePicker
selected={getDateFromString(eventForm.start)}
onChange={handleStartDateChange}
dateFormat="dd/MM/yyyy"
locale="fr"
className="w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary"
placeholderText="Date"
customInput={<Input />}
/>
</div>
<DatePicker
selected={getDateFromString(eventForm.start)}
onChange={handleStartDateChange}
showTimeSelect
showTimeSelectOnly
timeIntervals={15}
timeCaption="Heure"
dateFormat="HH:mm"
className="w-32"
customInput={<Input />}
/>
</div>
</div>
<div className="space-y-2">
<Label>Fin</Label>
<div className="flex gap-2">
<div className="flex-1">
<DatePicker
selected={getDateFromString(eventForm.end)}
onChange={handleEndDateChange}
dateFormat="dd/MM/yyyy"
locale="fr"
className="w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary"
placeholderText="Date"
customInput={<Input />}
minDate={getDateFromString(eventForm.start)}
/>
</div>
<DatePicker
selected={getDateFromString(eventForm.end)}
onChange={handleEndDateChange}
showTimeSelect
showTimeSelectOnly
timeIntervals={15}
timeCaption="Heure"
dateFormat="HH:mm"
className="w-32"
customInput={<Input />}
/>
</div>
</div>
</div>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="allDay"
checked={eventForm.allDay}
onCheckedChange={(checked) =>
setEventForm({ ...eventForm, allDay: checked as boolean })
}
/>
<Label htmlFor="allDay">Toute la journée</Label>
</div>
<div className="space-y-2">
<Label>Lieu</Label>
<Input
value={eventForm.location || ""}
onChange={(e) =>
setEventForm({ ...eventForm, location: e.target.value })
}
placeholder="Ajouter un lieu"
/>
</div>
</CardContent>
</Card>
<div className="space-y-2">
<Label>Description</Label>
<Textarea
value={eventForm.description || ""}
onChange={(e) =>
setEventForm({ ...eventForm, description: e.target.value })
}
placeholder="Ajouter une description"
/>
</div>
</div>
<DialogFooter>
{selectedEvent && (
<Button
variant="destructive"
onClick={handleEventDelete}
disabled={loading}
>
{loading ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Suppression...
</>
) : (
"Supprimer"
)}
</Button>
)}
<div className="flex space-x-2">
<Button
variant="outline"
onClick={() => setIsEventModalOpen(false)}
disabled={loading}
>
Annuler
</Button>
<Button onClick={handleEventSubmit} disabled={loading}>
{loading ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Enregistrement...
</>
) : selectedEvent ? (
"Mettre à jour"
) : (
"Créer"
)}
</Button>
</div>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
);
}