Files
AfiS-Aktensystem/web/app/(app)/faelle/neu/NewCaseForm.tsx
T
HarlekinandClaude Opus 5 1e5a470daf Phase 2, erster Teil: Akten anlegen, lesen, Vermerke schreiben
Fachlogik in lib/cases/service.ts, Zugriffsprüfung in lib/access/. Jede
Funktion prüft selbst und schreibt ihren Protokolleintrag in derselben
Transaktion wie die Änderung — die Server Actions daneben sind dünn.

Schreibgrenzen nach docs/03: niemand legt eine Akte über der eigenen Stufe an,
niemand stuft über die eigene Stufe hoch, archiviert und versiegelt sind
schreibgeschützt auch für Admins. Eine Herabstufung verlangt eine Begründung
und landet als sichtbarer Systemeintrag im Verlauf, nicht nur im Protokoll.

Aktenzeichen über CaseCounter mit INSERT ... ON CONFLICT DO UPDATE, geprüft mit
acht gleichzeitigen Anlagen ohne Doppelung. Wer eine Akte anlegt, wird ihr
Ermittler, sonst verliert er sie aus dem Blick, sobald jemand hochstuft.

Fallliste cursorbasiert auf (updatedAt, id), Sichtbarkeit und Trefferzahl in
derselben Abfrage. Aktenansicht mit Kopfdaten sofort und Verlauf in eigener
Suspense-Grenze. Vermerke optimistisch mit clientseitiger UUIDv7, die zugleich
Idempotenzschlüssel ist — zweimal gesendet bleibt ein Eintrag.

Lesezugriffe ab Stufe 3 werden protokolliert, gepuffert und alle zwei Sekunden
gebündelt geschrieben, damit das Protokoll die Antwort nicht ausbremst.

Die vier Startvorlagen stehen jetzt im Seed statt in Phase 3: ohne Vorlage
lässt sich keine Akte anlegen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-01 22:57:38 +02:00

86 lines
2.5 KiB
TypeScript

'use client'
import { useActionState } from 'react'
import { de } from '@/lib/i18n/de'
import { createCaseAction, type CreateState } from '../actions'
export function NewCaseForm({
templates,
levels,
defaultLevel,
}: {
templates: { key: string; name: string }[]
levels: { level: number; name: string }[]
defaultLevel: number
}) {
const [state, action, pending] = useActionState<CreateState, FormData>(createCaseAction, {})
return (
<form action={action} className="mt-6 space-y-5">
<div>
<label htmlFor="title" className="font-condensed text-xs text-text-dim">
{de.case.title}
</label>
<input
id="title"
name="title"
required
minLength={3}
maxLength={200}
className="mt-1.5 block w-full border border-line bg-raised px-3 py-2 text-sm"
/>
</div>
<div>
<label htmlFor="templateKey" className="font-condensed text-xs text-text-dim">
{de.case.type}
</label>
<select
id="templateKey"
name="templateKey"
required
className="mt-1.5 block border border-line bg-raised px-3 py-2 text-sm"
>
{templates.map((template) => (
<option key={template.key} value={template.key}>
{template.name}
</option>
))}
</select>
</div>
<div>
<label htmlFor="classification" className="font-condensed text-xs text-text-dim">
{de.clearance.classification}
</label>
{/* Die Auswahl zeigt nur, was erlaubt ist. Geprüft wird trotzdem serverseitig. */}
<select
id="classification"
name="classification"
defaultValue={defaultLevel}
className="mt-1.5 block border border-line bg-raised px-3 py-2 text-sm"
>
{levels.map((level) => (
<option key={level.level} value={level.level}>
{level.level} · {level.name}
</option>
))}
</select>
</div>
<div className="flex items-center gap-3">
<button
type="submit"
disabled={pending}
className="border border-line bg-raised px-4 py-2 font-condensed text-xs text-accent disabled:text-text-dim"
>
{pending ? de.case.saving : de.case.create}
</button>
{state.error ? (
<span className="font-condensed text-2xs text-stamp-4">{state.error}</span>
) : null}
</div>
</form>
)
}