Files
leocrm/frontend/src/components/dashboard/CalendarUpcomingWidget.tsx
T

60 lines
2.1 KiB
TypeScript

/**
* CalendarUpcomingWidget — shows next 3 upcoming events (Task 5.25).
*/
import React from 'react';
import { useQuery } from '@tanstack/react-query';
import { useTranslation } from 'react-i18next';
import { listEntries, type CalendarEntry } from '@/api/calendar';
import { formatDateTime } from '@/utils/date';
import { Calendar, MapPin } from 'lucide-react';
export function CalendarUpcomingWidget() {
const { t } = useTranslation();
const { data, isLoading, isError } = useQuery({
queryKey: ['calendarEntries', 'upcoming'],
queryFn: () => listEntries(),
staleTime: 60 * 1000,
});
if (isLoading) {
return <div className="animate-pulse space-y-2" data-testid="calendar-upcoming-loading">{[...Array(2)].map((_, i) => <div key={i} className="h-4 bg-secondary-100 rounded" />)}</div>;
}
if (isError) {
return <p className="text-sm text-secondary-500" data-testid="calendar-upcoming-error">{t('dashboard.widgetError')}</p>;
}
const entries: CalendarEntry[] = data ?? [];
const now = new Date();
const upcoming = entries
.filter((e: CalendarEntry) => new Date(e.start_at || e.due_date || '') >= now)
.slice(0, 3);
if (upcoming.length === 0) {
return <p className="text-sm text-secondary-500" data-testid="calendar-upcoming-empty">{t('dashboard.noUpcomingEvents')}</p>;
}
return (
<div className="space-y-3" data-testid="calendar-upcoming-widget">
{upcoming.map((entry: CalendarEntry) => (
<div key={entry.id} className="flex items-start gap-2 text-sm">
<Calendar className="w-3.5 h-3.5 text-primary-500 mt-0.5" />
<div className="flex-1">
<div className="font-medium text-secondary-800">{entry.title || '—'}</div>
<div className="text-secondary-500 text-xs">
{formatDateTime(entry.start_at || entry.due_date) || ''}
</div>
{entry.location && (
<div className="flex items-center gap-1 text-secondary-400 text-xs mt-0.5">
<MapPin className="w-3 h-3" />
{entry.location}
</div>
)}
</div>
</div>
))}
</div>
);
}