calendar 33
This commit is contained in:
parent
fefff8a8ee
commit
1996bdc5bb
@ -988,153 +988,246 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend
|
||||
};
|
||||
|
||||
return (
|
||||
<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: "",
|
||||
end: "",
|
||||
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 className="flex flex-col md:flex-row h-full gap-4 p-4">
|
||||
{/* Sidebar */}
|
||||
<div className="w-full md:w-64 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: "",
|
||||
end: "",
|
||||
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>
|
||||
|
||||
<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 />
|
||||
</Card>
|
||||
|
||||
<CalendarSelector />
|
||||
<StatisticsPanel statistics={statistics} />
|
||||
</div>
|
||||
|
||||
{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={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,
|
||||
{/* Calendar */}
|
||||
<div className="flex-1 bg-white rounded-lg shadow">
|
||||
<style jsx global>{`
|
||||
.fc-daygrid-day-events {
|
||||
max-height: 80px;
|
||||
overflow-y: auto;
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
.fc-daygrid-day-events::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
.fc-daygrid-day-events::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.fc-daygrid-day-events::-webkit-scrollbar-thumb {
|
||||
background: #888;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.fc-daygrid-day-events::-webkit-scrollbar-thumb:hover {
|
||||
background: #555;
|
||||
}
|
||||
|
||||
.fc .fc-daygrid-event-harness {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.fc-daygrid-day-frame {
|
||||
min-height: 100px !important;
|
||||
height: 100px !important;
|
||||
}
|
||||
|
||||
.fc-daygrid-event {
|
||||
padding: 2px 4px !important;
|
||||
border: none !important;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1) !important;
|
||||
}
|
||||
|
||||
.fc-daygrid-event:hover {
|
||||
filter: brightness(1.1);
|
||||
}
|
||||
|
||||
.fc-daygrid-event .fc-event-title {
|
||||
font-weight: 600 !important;
|
||||
font-size: 0.8rem !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.fc-daygrid-event .fc-event-time {
|
||||
font-size: 0.7rem !important;
|
||||
opacity: 0.9 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.fc-daygrid-event .event-location {
|
||||
font-size: 0.7rem !important;
|
||||
opacity: 0.8 !important;
|
||||
}
|
||||
|
||||
.fc-event-title-container {
|
||||
padding: 0 !important;
|
||||
}
|
||||
`}</style>
|
||||
<FullCalendar
|
||||
ref={calendarRef}
|
||||
plugins={[dayGridPlugin, timeGridPlugin, interactionPlugin]}
|
||||
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,
|
||||
borderColor: cal.color,
|
||||
textColor: '#ffffff',
|
||||
extendedProps: {
|
||||
location: event.location,
|
||||
description: event.description,
|
||||
calendarId: event.calendarId,
|
||||
backgroundColor: cal.color,
|
||||
borderColor: cal.color,
|
||||
textColor: '#ffffff',
|
||||
extendedProps: {
|
||||
location: event.location,
|
||||
description: event.description,
|
||||
calendarId: event.calendarId,
|
||||
originalEvent: event
|
||||
}
|
||||
}))
|
||||
)}
|
||||
eventContent={(arg) => (
|
||||
<div className="p-1 overflow-hidden">
|
||||
<div className="font-semibold truncate">{arg.event.title}</div>
|
||||
originalEvent: event
|
||||
}
|
||||
}))
|
||||
)}
|
||||
eventContent={(arg) => (
|
||||
<div className="flex flex-col gap-0.5 w-full overflow-hidden p-1">
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="flex-1 font-semibold text-[0.8rem] truncate">
|
||||
{arg.event.title}
|
||||
</div>
|
||||
{!arg.event.allDay && (
|
||||
<div className="text-xs opacity-90">
|
||||
<div className="text-[0.7rem] whitespace-nowrap">
|
||||
{new Date(arg.event.start).toLocaleTimeString('fr-FR', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
})}
|
||||
{arg.event.end && (
|
||||
<> - {new Date(arg.event.end).toLocaleTimeString('fr-FR', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
})}</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{arg.event.extendedProps.location && (
|
||||
<div className="text-xs opacity-75 truncate">
|
||||
<MapPin className="inline-block w-3 h-3 mr-1" />
|
||||
{arg.event.extendedProps.location}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
eventClassNames="rounded-md shadow-sm hover:shadow-md transition-shadow cursor-pointer"
|
||||
locale={frLocale}
|
||||
selectable={true}
|
||||
selectMirror={true}
|
||||
dayMaxEvents={true}
|
||||
weekends={true}
|
||||
select={handleDateSelect}
|
||||
eventClick={handleEventClick}
|
||||
height="auto"
|
||||
aspectRatio={1.8}
|
||||
slotMinTime="06:00:00"
|
||||
slotMaxTime="22:00:00"
|
||||
allDaySlot={true}
|
||||
allDayText="Toute la journée"
|
||||
slotLabelFormat={{
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
hour12: false
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Card>
|
||||
{arg.event.extendedProps.location && (
|
||||
<div className="event-location flex items-center gap-1 truncate">
|
||||
<MapPin className="inline-block w-3 h-3" />
|
||||
{arg.event.extendedProps.location}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
eventClassNames={(arg) => {
|
||||
// Generate a lighter version of the calendar color for the background
|
||||
const color = arg.event.backgroundColor;
|
||||
return [
|
||||
'rounded-md',
|
||||
'transition-all',
|
||||
'duration-200',
|
||||
'hover:shadow-md',
|
||||
'cursor-pointer',
|
||||
`hover:translate-y-[-1px]`,
|
||||
'border-l-4'
|
||||
];
|
||||
}}
|
||||
eventBackgroundColor={(arg) => {
|
||||
const color = arg.event.backgroundColor;
|
||||
// Convert hex to RGB and lighten it
|
||||
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(color);
|
||||
if (result) {
|
||||
const r = parseInt(result[1], 16);
|
||||
const g = parseInt(result[2], 16);
|
||||
const b = parseInt(result[3], 16);
|
||||
return `rgba(${r}, ${g}, ${b}, 0.15)`;
|
||||
}
|
||||
return color;
|
||||
}}
|
||||
eventBorderColor={(arg) => arg.event.backgroundColor}
|
||||
eventTextColor={(arg) => {
|
||||
const color = arg.event.backgroundColor;
|
||||
return color;
|
||||
}}
|
||||
locale={frLocale}
|
||||
selectable={true}
|
||||
selectMirror={true}
|
||||
dayMaxEvents={false}
|
||||
weekends={true}
|
||||
select={handleDateSelect}
|
||||
eventClick={handleEventClick}
|
||||
height="auto"
|
||||
aspectRatio={1.8}
|
||||
slotMinTime="06:00:00"
|
||||
slotMaxTime="22:00:00"
|
||||
allDaySlot={true}
|
||||
allDayText="Toute la journée"
|
||||
slotLabelFormat={{
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
hour12: false
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Calendar dialog */}
|
||||
<CalendarDialog
|
||||
|
||||
Loading…
Reference in New Issue
Block a user