- Database: kanzlai.document_templates table with RLS policies
- Seed: 4 system templates (Klageerwiderung UPC, Berufungsschrift,
Mandatsbestätigung, Kostenrechnung)
- Backend: TemplateService (CRUD + render), TemplateHandler with
endpoints: GET/POST /api/templates, GET/PUT/DELETE /api/templates/{id},
POST /api/templates/{id}/render?case_id=X
- Template variables: case.*, party.*, tenant.*, user.*, date.*, deadline.*
- Frontend: /vorlagen page with category filters, template detail/editor,
render flow (select case -> preview -> copy/download), variable toolbar
- Quick action: "Schriftsatz erstellen" button on case detail page
- Also: resolved merge conflicts between audit-trail and role-based branches,
added missing Notification/AuditLog types to frontend
122 lines
4.4 KiB
TypeScript
122 lines
4.4 KiB
TypeScript
"use client";
|
|
|
|
import { useQuery } from "@tanstack/react-query";
|
|
import { api } from "@/lib/api";
|
|
import type { DocumentTemplate } from "@/lib/types";
|
|
import { TEMPLATE_CATEGORY_LABELS } from "@/lib/types";
|
|
import { Breadcrumb } from "@/components/layout/Breadcrumb";
|
|
import Link from "next/link";
|
|
import { FileText, Plus, Loader2, Lock } from "lucide-react";
|
|
import { useState } from "react";
|
|
|
|
const CATEGORIES = ["", "schriftsatz", "vertrag", "korrespondenz", "intern"];
|
|
|
|
export default function VorlagenPage() {
|
|
const [category, setCategory] = useState("");
|
|
|
|
const { data, isLoading } = useQuery({
|
|
queryKey: ["templates", category],
|
|
queryFn: () =>
|
|
api.get<{ data: DocumentTemplate[]; total: number }>(
|
|
`/templates${category ? `?category=${category}` : ""}`,
|
|
),
|
|
});
|
|
|
|
const templates = data?.data ?? [];
|
|
|
|
return (
|
|
<div className="animate-fade-in space-y-4">
|
|
<div>
|
|
<Breadcrumb
|
|
items={[
|
|
{ label: "Dashboard", href: "/dashboard" },
|
|
{ label: "Vorlagen" },
|
|
]}
|
|
/>
|
|
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
|
<div>
|
|
<h1 className="text-lg font-semibold text-neutral-900">
|
|
Vorlagen
|
|
</h1>
|
|
<p className="mt-0.5 text-sm text-neutral-500">
|
|
Dokumentvorlagen mit automatischer Befüllung
|
|
</p>
|
|
</div>
|
|
<Link
|
|
href="/vorlagen/neu"
|
|
className="flex items-center gap-1.5 rounded-md bg-neutral-900 px-3 py-1.5 text-sm font-medium text-white transition-colors hover:bg-neutral-800"
|
|
>
|
|
<Plus className="h-3.5 w-3.5" />
|
|
Neue Vorlage
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Category filter */}
|
|
<div className="flex gap-1.5 overflow-x-auto">
|
|
{CATEGORIES.map((cat) => (
|
|
<button
|
|
key={cat}
|
|
onClick={() => setCategory(cat)}
|
|
className={`whitespace-nowrap rounded-md px-3 py-1.5 text-sm transition-colors ${
|
|
category === cat
|
|
? "bg-neutral-900 font-medium text-white"
|
|
: "bg-white text-neutral-600 ring-1 ring-neutral-200 hover:bg-neutral-50"
|
|
}`}
|
|
>
|
|
{cat === "" ? "Alle" : TEMPLATE_CATEGORY_LABELS[cat] ?? cat}
|
|
</button>
|
|
))}
|
|
</div>
|
|
|
|
{isLoading ? (
|
|
<div className="flex items-center justify-center py-12">
|
|
<Loader2 className="h-5 w-5 animate-spin text-neutral-400" />
|
|
</div>
|
|
) : templates.length === 0 ? (
|
|
<div className="flex flex-col items-center justify-center rounded-lg border border-dashed border-neutral-300 py-12 text-center">
|
|
<FileText className="mb-2 h-8 w-8 text-neutral-300" />
|
|
<p className="text-sm text-neutral-500">Keine Vorlagen gefunden</p>
|
|
</div>
|
|
) : (
|
|
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
|
|
{templates.map((t) => (
|
|
<Link
|
|
key={t.id}
|
|
href={`/vorlagen/${t.id}`}
|
|
className="group rounded-lg border border-neutral-200 bg-white p-4 transition-colors hover:border-neutral-300 hover:shadow-sm"
|
|
>
|
|
<div className="flex items-start justify-between">
|
|
<div className="flex items-center gap-2">
|
|
<FileText className="h-4 w-4 text-neutral-400" />
|
|
<h3 className="text-sm font-medium text-neutral-900 group-hover:text-neutral-700">
|
|
{t.name}
|
|
</h3>
|
|
</div>
|
|
{t.is_system && (
|
|
<Lock className="h-3.5 w-3.5 text-neutral-300" aria-label="Systemvorlage" />
|
|
)}
|
|
</div>
|
|
{t.description && (
|
|
<p className="mt-1.5 text-xs text-neutral-500 line-clamp-2">
|
|
{t.description}
|
|
</p>
|
|
)}
|
|
<div className="mt-3 flex items-center gap-2">
|
|
<span className="rounded-full bg-neutral-100 px-2 py-0.5 text-xs text-neutral-600">
|
|
{TEMPLATE_CATEGORY_LABELS[t.category] ?? t.category}
|
|
</span>
|
|
{t.is_system && (
|
|
<span className="rounded-full bg-blue-50 px-2 py-0.5 text-xs text-blue-600">
|
|
System
|
|
</span>
|
|
)}
|
|
</div>
|
|
</Link>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|