feat: add frontend case list, detail, and creation pages (Phase 1F)

- Case list page (/cases) with search, status/type filters, status badges
- Case creation page (/cases/new) with reusable CaseForm component
- Case detail page (/cases/[id]) with tabs: Timeline, Deadlines, Documents, Parties
- CaseTimeline component for chronological case_events display
- PartyList component with inline party CRUD (add/delete)
- Updated sidebar navigation to route to /cases
This commit is contained in:
m
2026-03-25 13:50:20 +01:00
parent 0fac764211
commit 3a56d4cf11
7 changed files with 896 additions and 1 deletions

View File

@@ -0,0 +1,165 @@
"use client";
import { useState } from "react";
const TYPE_OPTIONS = [
{ value: "", label: "-- Typ wahlen --" },
{ value: "INF", label: "Verletzungsklage (INF)" },
{ value: "REV", label: "Widerruf (REV)" },
{ value: "CCR", label: "Einstweilige Verfugung (CCR)" },
{ value: "APP", label: "Berufung (APP)" },
{ value: "PI", label: "Vorlaufiger Rechtsschutz (PI)" },
{ value: "ZPO_CIVIL", label: "ZPO Zivilverfahren" },
];
export interface CaseFormData {
case_number: string;
title: string;
case_type?: string;
court?: string;
court_ref?: string;
status: string;
}
interface CaseFormProps {
initialData?: Partial<CaseFormData>;
onSubmit: (data: CaseFormData) => void;
isSubmitting?: boolean;
submitLabel?: string;
}
export function CaseForm({
initialData,
onSubmit,
isSubmitting,
submitLabel = "Akte anlegen",
}: CaseFormProps) {
const [form, setForm] = useState<CaseFormData>({
case_number: initialData?.case_number ?? "",
title: initialData?.title ?? "",
case_type: initialData?.case_type ?? "",
court: initialData?.court ?? "",
court_ref: initialData?.court_ref ?? "",
status: initialData?.status ?? "active",
});
function handleSubmit(e: React.FormEvent) {
e.preventDefault();
const data: CaseFormData = {
...form,
case_type: form.case_type || undefined,
court: form.court || undefined,
court_ref: form.court_ref || undefined,
};
onSubmit(data);
}
function update(field: keyof CaseFormData, value: string) {
setForm((prev) => ({ ...prev, [field]: value }));
}
const inputClass =
"w-full rounded-md border border-neutral-200 bg-white px-3 py-1.5 text-sm outline-none focus:border-neutral-400 focus:ring-1 focus:ring-neutral-400";
return (
<form onSubmit={handleSubmit} className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div>
<label className="mb-1 block text-sm font-medium text-neutral-700">
Aktenzeichen *
</label>
<input
type="text"
required
value={form.case_number}
onChange={(e) => update("case_number", e.target.value)}
placeholder="z.B. 2026/001"
className={inputClass}
/>
</div>
<div>
<label className="mb-1 block text-sm font-medium text-neutral-700">
Status
</label>
<select
value={form.status}
onChange={(e) => update("status", e.target.value)}
className={inputClass}
>
<option value="active">Aktiv</option>
<option value="pending">Anhangig</option>
<option value="closed">Geschlossen</option>
</select>
</div>
</div>
<div>
<label className="mb-1 block text-sm font-medium text-neutral-700">
Titel *
</label>
<input
type="text"
required
value={form.title}
onChange={(e) => update("title", e.target.value)}
placeholder="Bezeichnung der Akte"
className={inputClass}
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="mb-1 block text-sm font-medium text-neutral-700">
Verfahrensart
</label>
<select
value={form.case_type}
onChange={(e) => update("case_type", e.target.value)}
className={inputClass}
>
{TYPE_OPTIONS.map((o) => (
<option key={o.value} value={o.value}>
{o.label}
</option>
))}
</select>
</div>
<div>
<label className="mb-1 block text-sm font-medium text-neutral-700">
Gericht
</label>
<input
type="text"
value={form.court}
onChange={(e) => update("court", e.target.value)}
placeholder="z.B. UPC Munich Central Division"
className={inputClass}
/>
</div>
</div>
<div>
<label className="mb-1 block text-sm font-medium text-neutral-700">
Gerichtliches Aktenzeichen
</label>
<input
type="text"
value={form.court_ref}
onChange={(e) => update("court_ref", e.target.value)}
placeholder="z.B. UPC_CFI_123/2026"
className={inputClass}
/>
</div>
<div className="flex justify-end pt-2">
<button
type="submit"
disabled={isSubmitting}
className="rounded-md bg-neutral-900 px-4 py-1.5 text-sm font-medium text-white hover:bg-neutral-800 disabled:opacity-50"
>
{isSubmitting ? "Speichern..." : submitLabel}
</button>
</div>
</form>
);
}