feat: refactor case detail from useState tabs to URL-based nested routes
Refactors the monolithic cases/[id]/page.tsx into Next.js nested routes with a shared layout for the case header and tab navigation bar. Route structure: - cases/[id]/layout.tsx — case header + tab bar (active tab from URL) - cases/[id]/page.tsx — redirects to ./verlauf - cases/[id]/verlauf/page.tsx — timeline tab - cases/[id]/fristen/page.tsx — deadlines tab - cases/[id]/dokumente/page.tsx — documents tab (with upload) - cases/[id]/parteien/page.tsx — parties tab - cases/[id]/notizen/page.tsx — notes tab (new, uses NotesList) New shared components: - Breadcrumb.tsx — reusable breadcrumb navigation - NotesList.tsx — reusable notes CRUD (inline create/edit/delete) - Note type added to types.ts Benefits: deep linking, browser back/forward, bookmarkable tabs.
This commit is contained in:
86
frontend/src/app/(app)/cases/[id]/fristen/page.tsx
Normal file
86
frontend/src/app/(app)/cases/[id]/fristen/page.tsx
Normal file
@@ -0,0 +1,86 @@
|
||||
"use client";
|
||||
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useParams } from "next/navigation";
|
||||
import { api } from "@/lib/api";
|
||||
import type { Deadline } from "@/lib/types";
|
||||
import { format } from "date-fns";
|
||||
import { de } from "date-fns/locale";
|
||||
import { Clock, Loader2 } from "lucide-react";
|
||||
|
||||
const DEADLINE_STATUS: Record<string, string> = {
|
||||
pending: "bg-amber-50 text-amber-700",
|
||||
completed: "bg-emerald-50 text-emerald-700",
|
||||
overdue: "bg-red-50 text-red-700",
|
||||
};
|
||||
|
||||
const DEADLINE_STATUS_LABEL: Record<string, string> = {
|
||||
pending: "Offen",
|
||||
completed: "Erledigt",
|
||||
overdue: "Ueberfaellig",
|
||||
};
|
||||
|
||||
export default function FristenPage() {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
|
||||
const { data, isLoading } = useQuery({
|
||||
queryKey: ["case-deadlines", id],
|
||||
queryFn: () =>
|
||||
api.get<{ deadlines: Deadline[]; total: number }>(
|
||||
`/deadlines?case_id=${id}`,
|
||||
),
|
||||
});
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center py-8">
|
||||
<Loader2 className="h-5 w-5 animate-spin text-neutral-400" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const deadlines = Array.isArray(data?.deadlines) ? data.deadlines : [];
|
||||
|
||||
if (deadlines.length === 0) {
|
||||
return (
|
||||
<div className="flex flex-col items-center py-8 text-center">
|
||||
<div className="rounded-xl bg-neutral-100 p-3">
|
||||
<Clock className="h-5 w-5 text-neutral-400" />
|
||||
</div>
|
||||
<p className="mt-2 text-sm text-neutral-500">
|
||||
Keine Fristen vorhanden.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
{deadlines.map((d) => (
|
||||
<div
|
||||
key={d.id}
|
||||
className="flex flex-col gap-2 rounded-md border border-neutral-200 bg-white px-4 py-3 sm:flex-row sm:items-center sm:justify-between"
|
||||
>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-neutral-900">{d.title}</p>
|
||||
{d.description && (
|
||||
<p className="mt-0.5 text-sm text-neutral-500">
|
||||
{d.description}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<span
|
||||
className={`rounded-full px-2 py-0.5 text-xs font-medium ${DEADLINE_STATUS[d.status] ?? "bg-neutral-100 text-neutral-500"}`}
|
||||
>
|
||||
{DEADLINE_STATUS_LABEL[d.status] ?? d.status}
|
||||
</span>
|
||||
<span className="whitespace-nowrap text-sm text-neutral-500">
|
||||
{format(new Date(d.due_date), "d. MMM yyyy", { locale: de })}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user