'use client'; import Link from 'next/link'; import { useParams } from 'next/navigation'; import { useQuery } from '@tanstack/react-query'; import { formatDistanceToNowStrict, isAfter, isBefore } from 'date-fns'; import { AlarmClock, ChevronRight } from 'lucide-react'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { apiFetch } from '@/lib/api/client'; import { cn } from '@/lib/utils'; interface ReminderRow { id: string; title: string; dueAt: string; status: string; priority?: string | null; interestId?: string | null; clientId?: string | null; entityType?: string | null; entityId?: string | null; } interface MyRemindersResponse { data: ReminderRow[]; } const PRIORITY_BADGE: Record = { high: 'bg-rose-100 text-rose-700', medium: 'bg-amber-100 text-amber-700', low: 'bg-slate-100 text-slate-700', }; /** * Compact reminders rail for the dashboard sidebar. Lists reminders assigned * to the current user (overdue first, then upcoming). Each item links to its * subject — interest preferred, then client, then the generic entity ref. * * Limited to 6 items; "View all" routes to /reminders. */ export function MyRemindersRail() { const params = useParams<{ portSlug: string }>(); const portSlug = params?.portSlug ?? ''; const { data, isLoading } = useQuery({ queryKey: ['reminders', 'my'], queryFn: () => apiFetch('/api/v1/reminders/my'), staleTime: 60_000, }); const items = data?.data ?? []; const now = new Date(); // Overdue first, then upcoming, capped at 6 for the rail. const sorted = [...items] .sort((a, b) => new Date(a.dueAt).getTime() - new Date(b.dueAt).getTime()) .slice(0, 6); const overdueCount = items.filter((r) => isBefore(new Date(r.dueAt), now)).length; function hrefFor(r: ReminderRow): string { if (r.interestId) return `/${portSlug}/interests/${r.interestId}`; if (r.clientId) return `/${portSlug}/clients/${r.clientId}`; if (r.entityType === 'client' && r.entityId) return `/${portSlug}/clients/${r.entityId}`; if (r.entityType === 'interest' && r.entityId) return `/${portSlug}/interests/${r.entityId}`; if (r.entityType === 'berth' && r.entityId) return `/${portSlug}/berths/${r.entityId}`; return `/${portSlug}/reminders`; } return (
Reminders {overdueCount > 0 ? (

{overdueCount} overdue

) : items.length > 0 ? (

{items.length} pending

) : null}
View all
{isLoading ? (
{[0, 1, 2].map((i) => (
))}
) : sorted.length === 0 ? (

All caught up — no reminders.

) : (
    {sorted.map((r) => { const due = new Date(r.dueAt); const isOverdue = isBefore(due, now); const isUpcoming = isAfter(due, now); return (
  • {r.title} {r.priority && r.priority !== 'low' ? ( {r.priority} ) : null} {isOverdue ? formatDistanceToNowStrict(due) + ' overdue' : isUpcoming ? 'in ' + formatDistanceToNowStrict(due) : 'now'}
  • ); })}
)} ); }