153 lines
6.1 KiB
TypeScript
153 lines
6.1 KiB
TypeScript
"use client";
|
|
|
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
import { Button } from "@/components/ui/button";
|
|
import { RefreshCw, Calendar as CalendarIcon, ChevronRight } from "lucide-react";
|
|
import { useRouter } from "next/navigation";
|
|
import Link from "next/link";
|
|
import { format, isToday, isTomorrow } from "date-fns";
|
|
import { fr } from "date-fns/locale";
|
|
import { useCalendarEvents, CalendarEvent } from "@/hooks/use-calendar-events";
|
|
|
|
interface CalendarProps {
|
|
limit?: number;
|
|
showMore?: boolean;
|
|
showRefresh?: boolean;
|
|
cardClassName?: string;
|
|
}
|
|
|
|
export function Calendar({
|
|
limit = 5,
|
|
showMore = true,
|
|
showRefresh = true,
|
|
cardClassName = "transition-transform duration-500 ease-in-out transform hover:scale-105 bg-white/95 backdrop-blur-sm border-0 shadow-lg"
|
|
}: CalendarProps) {
|
|
const { events, loading, error, refresh } = useCalendarEvents({ limit });
|
|
const router = useRouter();
|
|
|
|
const formatEventDate = (date: Date | string, isAllDay: boolean) => {
|
|
const eventDate = date instanceof Date ? date : new Date(date);
|
|
let dateString = "";
|
|
|
|
if (isToday(eventDate)) {
|
|
dateString = "Today";
|
|
} else if (isTomorrow(eventDate)) {
|
|
dateString = "Tomorrow";
|
|
} else {
|
|
dateString = format(eventDate, "EEEE d MMMM", { locale: fr });
|
|
}
|
|
|
|
if (!isAllDay) {
|
|
dateString += ` · ${format(eventDate, "HH:mm", { locale: fr })}`;
|
|
}
|
|
|
|
return dateString;
|
|
};
|
|
|
|
return (
|
|
<Card className={cardClassName}>
|
|
<CardHeader className="flex flex-row items-center justify-between pb-2 border-b border-gray-100">
|
|
<CardTitle className="text-lg font-semibold text-gray-800 flex items-center gap-2">
|
|
<CalendarIcon className="h-5 w-5 text-gray-600" />
|
|
Calendar
|
|
</CardTitle>
|
|
<div className="flex items-center gap-1">
|
|
{showRefresh && (
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
onClick={refresh}
|
|
className="h-7 w-7 p-0 hover:bg-gray-100/50 rounded-full"
|
|
disabled={loading}
|
|
>
|
|
<RefreshCw className="h-3.5 w-3.5 text-gray-600" />
|
|
</Button>
|
|
)}
|
|
{showMore && (
|
|
<Link href='/agenda' passHref>
|
|
<Button variant='ghost' size='sm' className='h-8 w-8 p-0'>
|
|
<ChevronRight className='h-4 w-4' />
|
|
<span className='sr-only'>View calendar</span>
|
|
</Button>
|
|
</Link>
|
|
)}
|
|
</div>
|
|
</CardHeader>
|
|
<CardContent className="p-3">
|
|
{loading ? (
|
|
<div className="flex items-center justify-center py-6">
|
|
<div className="h-4 w-4 animate-spin rounded-full border-2 border-blue-500 border-t-transparent" />
|
|
</div>
|
|
) : error ? (
|
|
<div className="text-xs text-red-500 text-center py-3">{error}</div>
|
|
) : events.length === 0 ? (
|
|
<div className="text-xs text-gray-500 text-center py-6">No upcoming events</div>
|
|
) : (
|
|
<div className="space-y-2 max-h-[400px] overflow-y-auto pr-1 scrollbar-thin scrollbar-thumb-gray-200 scrollbar-track-transparent">
|
|
{events.map((event) => (
|
|
<div
|
|
key={event.id}
|
|
className="p-2 rounded-lg bg-white shadow-sm hover:shadow-md transition-all duration-200 border border-gray-100"
|
|
>
|
|
<div className="flex gap-2">
|
|
<div
|
|
className="flex-shrink-0 w-14 h-14 rounded-lg flex flex-col items-center justify-center border"
|
|
style={{
|
|
backgroundColor: `${event.calendarColor || '#4F46E5'}10`,
|
|
borderColor: event.calendarColor || '#4F46E5'
|
|
}}
|
|
>
|
|
<span
|
|
className="text-[10px] font-medium"
|
|
style={{ color: event.calendarColor || '#4F46E5' }}
|
|
>
|
|
{format(event.start instanceof Date ? event.start : new Date(event.start), 'MMM', { locale: fr })}
|
|
</span>
|
|
<span
|
|
className="text-[10px] font-bold mt-0.5"
|
|
style={{ color: event.calendarColor || '#4F46E5' }}
|
|
>
|
|
{format(event.start instanceof Date ? event.start : new Date(event.start), 'dd', { locale: fr })}
|
|
</span>
|
|
</div>
|
|
<div className="flex-1 min-w-0 space-y-1">
|
|
<div className="flex items-start justify-between gap-2">
|
|
<p className="text-sm font-medium text-gray-800 line-clamp-2 flex-1">
|
|
{event.title}
|
|
</p>
|
|
{!event.isAllDay && (
|
|
<span className="text-[10px] text-gray-500 whitespace-nowrap">
|
|
{format(event.start instanceof Date ? event.start : new Date(event.start), 'HH:mm', { locale: fr })} - {format(event.end instanceof Date ? event.end : new Date(event.end), 'HH:mm', { locale: fr })}
|
|
</span>
|
|
)}
|
|
</div>
|
|
<div
|
|
className="flex items-center text-[10px] px-1.5 py-0.5 rounded-md"
|
|
style={{
|
|
backgroundColor: `${event.calendarColor || '#4F46E5'}10`,
|
|
color: event.calendarColor || '#4F46E5'
|
|
}}
|
|
>
|
|
<span className="truncate">{event.calendarName || 'Calendar'}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
|
|
{showMore && (
|
|
<Link href='/agenda' passHref>
|
|
<Button
|
|
size='sm'
|
|
className='w-full transition-all ease-in-out duration-500 bg-gray-100 text-gray-700 hover:bg-gray-200 mt-2'
|
|
>
|
|
View all events
|
|
</Button>
|
|
</Link>
|
|
)}
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|