- Deadline detail page (/fristen/[id]) with status badge, due date, case context, complete button, and notes - Appointment detail page (/termine/[id]) with datetime, location, type badge, case link, description, and notes - Case event detail page (/cases/[id]/ereignisse/[eventId]) with event type icon, description, metadata, and notes - Standalone deadline creation (/fristen/neu) with case dropdown - Standalone appointment creation (/termine/neu) with optional case - Reusable Breadcrumb component for navigation hierarchy - Reusable NotesList component with inline create/edit/delete - Added Note and RecentActivity types to lib/types.ts
181 lines
5.6 KiB
TypeScript
181 lines
5.6 KiB
TypeScript
"use client";
|
|
|
|
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
|
import { useRouter } from "next/navigation";
|
|
import { api } from "@/lib/api";
|
|
import type { Case, Deadline } from "@/lib/types";
|
|
import { Breadcrumb } from "@/components/layout/Breadcrumb";
|
|
import { useState } from "react";
|
|
import { toast } from "sonner";
|
|
|
|
const inputClass =
|
|
"w-full rounded-md border border-neutral-200 px-3 py-1.5 text-sm outline-none focus:border-neutral-400 focus:ring-1 focus:ring-neutral-400";
|
|
const labelClass = "mb-1 block text-xs font-medium text-neutral-600";
|
|
|
|
export default function NewDeadlinePage() {
|
|
const router = useRouter();
|
|
const queryClient = useQueryClient();
|
|
|
|
const [caseId, setCaseId] = useState("");
|
|
const [title, setTitle] = useState("");
|
|
const [description, setDescription] = useState("");
|
|
const [dueDate, setDueDate] = useState("");
|
|
const [warningDate, setWarningDate] = useState("");
|
|
const [notes, setNotes] = useState("");
|
|
|
|
const { data: casesData } = useQuery({
|
|
queryKey: ["cases"],
|
|
queryFn: () => api.get<{ cases: Case[]; total: number } | Case[]>("/cases"),
|
|
});
|
|
|
|
const cases = Array.isArray(casesData)
|
|
? casesData
|
|
: Array.isArray(casesData?.cases)
|
|
? casesData.cases
|
|
: [];
|
|
|
|
const createMutation = useMutation({
|
|
mutationFn: (body: Record<string, unknown>) =>
|
|
api.post<Deadline>(`/cases/${caseId}/deadlines`, body),
|
|
onSuccess: (data) => {
|
|
queryClient.invalidateQueries({ queryKey: ["deadlines"] });
|
|
queryClient.invalidateQueries({ queryKey: ["dashboard"] });
|
|
toast.success("Frist erstellt");
|
|
router.push(`/fristen/${data.id}`);
|
|
},
|
|
onError: () => toast.error("Fehler beim Erstellen der Frist"),
|
|
});
|
|
|
|
function handleSubmit(e: React.FormEvent) {
|
|
e.preventDefault();
|
|
if (!caseId || !title.trim() || !dueDate) return;
|
|
|
|
const body: Record<string, unknown> = {
|
|
title: title.trim(),
|
|
due_date: new Date(dueDate).toISOString(),
|
|
source: "manual",
|
|
};
|
|
if (description.trim()) body.description = description.trim();
|
|
if (warningDate) body.warning_date = new Date(warningDate).toISOString();
|
|
if (notes.trim()) body.notes = notes.trim();
|
|
|
|
createMutation.mutate(body);
|
|
}
|
|
|
|
return (
|
|
<div className="animate-fade-in">
|
|
<Breadcrumb
|
|
items={[
|
|
{ label: "Dashboard", href: "/dashboard" },
|
|
{ label: "Fristen", href: "/fristen" },
|
|
{ label: "Neue Frist" },
|
|
]}
|
|
/>
|
|
|
|
<h1 className="text-lg font-semibold text-neutral-900">
|
|
Neue Frist anlegen
|
|
</h1>
|
|
<p className="mt-0.5 text-sm text-neutral-500">
|
|
Erstellen Sie eine neue Frist fuer eine Akte.
|
|
</p>
|
|
|
|
<form
|
|
onSubmit={handleSubmit}
|
|
className="mt-6 max-w-lg space-y-4 rounded-lg border border-neutral-200 bg-white p-5"
|
|
>
|
|
<div>
|
|
<label className={labelClass}>Akte *</label>
|
|
<select
|
|
value={caseId}
|
|
onChange={(e) => setCaseId(e.target.value)}
|
|
required
|
|
className={inputClass}
|
|
>
|
|
<option value="">Akte auswaehlen...</option>
|
|
{cases.map((c) => (
|
|
<option key={c.id} value={c.id}>
|
|
{c.case_number} — {c.title}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
|
|
<div>
|
|
<label className={labelClass}>Bezeichnung *</label>
|
|
<input
|
|
type="text"
|
|
value={title}
|
|
onChange={(e) => setTitle(e.target.value)}
|
|
required
|
|
className={inputClass}
|
|
placeholder="z.B. Klageschrift einreichen"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className={labelClass}>Beschreibung</label>
|
|
<input
|
|
type="text"
|
|
value={description}
|
|
onChange={(e) => setDescription(e.target.value)}
|
|
className={inputClass}
|
|
placeholder="Optionale Beschreibung"
|
|
/>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<div>
|
|
<label className={labelClass}>Faellig am *</label>
|
|
<input
|
|
type="date"
|
|
value={dueDate}
|
|
onChange={(e) => setDueDate(e.target.value)}
|
|
required
|
|
className={inputClass}
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className={labelClass}>Warnung am</label>
|
|
<input
|
|
type="date"
|
|
value={warningDate}
|
|
onChange={(e) => setWarningDate(e.target.value)}
|
|
className={inputClass}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label className={labelClass}>Notizen</label>
|
|
<textarea
|
|
value={notes}
|
|
onChange={(e) => setNotes(e.target.value)}
|
|
rows={3}
|
|
className={inputClass}
|
|
placeholder="Optionale Notizen zur Frist"
|
|
/>
|
|
</div>
|
|
|
|
<div className="flex items-center justify-end gap-2 pt-2">
|
|
<button
|
|
type="button"
|
|
onClick={() => router.push("/fristen")}
|
|
className="rounded-md border border-neutral-200 bg-white px-3 py-1.5 text-sm text-neutral-700 hover:bg-neutral-50"
|
|
>
|
|
Abbrechen
|
|
</button>
|
|
<button
|
|
type="submit"
|
|
disabled={
|
|
createMutation.isPending || !caseId || !title.trim() || !dueDate
|
|
}
|
|
className="rounded-md bg-neutral-900 px-3 py-1.5 text-sm font-medium text-white hover:bg-neutral-800 disabled:opacity-50"
|
|
>
|
|
{createMutation.isPending ? "Erstellen..." : "Frist anlegen"}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
);
|
|
}
|