- Breadcrumb component: reusable nav with items array (label+href)
- DeadlineTrafficLights: buttons → Links to /fristen?status={filter}
- CaseOverviewGrid: static metrics → clickable Links to /cases?status={filter}
- UpcomingTimeline: items → clickable Links to /fristen/{id} or /termine/{id}
with case number links and hover chevron
- QuickActions: swap CalDAV Sync for "Neuer Termin" → /termine/neu,
fix "Frist eintragen" → /fristen/neu
- AISummaryCard: add RefreshCw button with spinning animation
- RecentActivityList: new component showing recent case events
- DeadlineList: accept initialStatus prop, add this_week/ok filters
- fristen/page.tsx: read searchParams.status for initial filter
- Add breadcrumbs to dashboard, fristen, cases, termine pages
- Add RecentActivity type, update DashboardData type
81 lines
2.4 KiB
TypeScript
81 lines
2.4 KiB
TypeScript
"use client";
|
|
|
|
import Link from "next/link";
|
|
import { formatDistanceToNow, parseISO } from "date-fns";
|
|
import { de } from "date-fns/locale";
|
|
import {
|
|
FileText,
|
|
Scale,
|
|
Calendar,
|
|
Clock,
|
|
MessageSquare,
|
|
ChevronRight,
|
|
} from "lucide-react";
|
|
import type { RecentActivity } from "@/lib/types";
|
|
|
|
const EVENT_ICONS: Record<string, typeof FileText> = {
|
|
status_changed: Scale,
|
|
deadline_created: Clock,
|
|
appointment_created: Calendar,
|
|
document_uploaded: FileText,
|
|
note_added: MessageSquare,
|
|
};
|
|
|
|
interface Props {
|
|
activities: RecentActivity[];
|
|
}
|
|
|
|
export function RecentActivityList({ activities }: Props) {
|
|
const safe = Array.isArray(activities) ? activities : [];
|
|
|
|
if (safe.length === 0) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<div className="rounded-xl border border-neutral-200 bg-white p-5">
|
|
<h2 className="text-sm font-semibold text-neutral-900">
|
|
Letzte Aktivität
|
|
</h2>
|
|
<div className="mt-3 divide-y divide-neutral-100">
|
|
{safe.map((activity) => {
|
|
const Icon = EVENT_ICONS[activity.event_type ?? ""] ?? FileText;
|
|
const timeAgo = activity.created_at
|
|
? formatDistanceToNow(parseISO(activity.created_at), {
|
|
addSuffix: true,
|
|
locale: de,
|
|
})
|
|
: "";
|
|
|
|
return (
|
|
<Link
|
|
key={activity.id}
|
|
href={`/cases/${activity.case_id}`}
|
|
className="group flex items-center gap-3 py-2.5 transition-colors first:pt-0 last:pb-0 hover:bg-neutral-50 -mx-5 px-5"
|
|
>
|
|
<div className="rounded-md bg-neutral-100 p-1.5">
|
|
<Icon className="h-3.5 w-3.5 text-neutral-500" />
|
|
</div>
|
|
<div className="min-w-0 flex-1">
|
|
<p className="truncate text-sm text-neutral-900">
|
|
{activity.title}
|
|
</p>
|
|
<div className="flex items-center gap-2 text-xs text-neutral-500">
|
|
<span>{activity.case_number}</span>
|
|
{timeAgo && (
|
|
<>
|
|
<span className="text-neutral-300">·</span>
|
|
<span>{timeAgo}</span>
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
<ChevronRight className="h-4 w-4 shrink-0 text-neutral-300 transition-colors group-hover:text-neutral-500" />
|
|
</Link>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|