Files
AfiS-Aktensystem/web/app/(app)/admin/abteilungen/DepartmentForm.tsx
T
Harlekin e0b03ea336
Prüfung / Lint, Typen, Tests (push) Canceled after 0s
Prüfung / Images bauen (pdf) (push) Canceled after 0s
Prüfung / Images bauen (web) (push) Canceled after 0s
Prüfung / Images bauen (worker) (push) Canceled after 0s
Images / bauen (pdf, docker/pdf.Dockerfile) (push) Canceled after 0s
Images / bauen (web, docker/web.Dockerfile) (push) Canceled after 0s
Images / bauen (worker, docker/worker.Dockerfile) (push) Canceled after 0s
Gedankenstriche durch Bindestriche ersetzt
375 Vorkommen in 210 Dateien. Betrifft Kommentare, Oberflaechentexte und die
README; an keiner Stelle Bedeutung, nur Zeichensetzung.
2026-09-04 08:54:52 +02:00

162 lines
4.9 KiB
TypeScript

'use client'
import { useActionState, useState } from 'react'
import { departmentAdminText } from '@/lib/i18n/departments'
import {
createDepartmentAction,
updateDepartmentAction,
type ActionResult,
} from './actions'
type Existing = {
id: string
key: string
name: string
description: string | null
minClearance: number
isArchived: boolean
memberCount: number | null
}
/**
* Anlegen und Ändern in einem Formular.
*
* Das Kürzel steht nach dem Anlegen fest: es steckt in der Adresse jeder
* Abteilungsseite, und ein wandernder Bezeichner macht geteilte Links wertlos.
* Wer sich vertan hat, legt neu an und archiviert die alte - archivieren
* statt löschen, weil an einer Abteilung Vermerke und Zuordnungen hängen.
*/
export function DepartmentForm({
levels,
existing,
}: {
levels: { level: number; name: string }[]
existing?: Existing
}) {
const [state, action, pending] = useActionState<ActionResult | null, FormData>(
existing ? updateDepartmentAction : createDepartmentAction,
null,
)
const [offen, setOffen] = useState(!existing)
if (existing && !offen) {
return (
<div className="record-row">
<span className="record-key">{existing.key}</span>
<span className="record-title">{existing.name}</span>
{existing.memberCount === null ? null : (
<span className="font-condensed text-2xs text-text-dim">
{departmentAdminText.members} {existing.memberCount}
</span>
)}
{existing.isArchived ? (
<span className="font-condensed text-2xs text-text-dim">
{departmentAdminText.archived}
</span>
) : null}
<button type="button" onClick={() => setOffen(true)} className="btn btn-sm">
{departmentAdminText.edit}
</button>
</div>
)
}
return (
<form action={action} className="max-w-(--container-measure) space-y-4">
{existing ? <input type="hidden" name="id" value={existing.id} /> : null}
<div className="grid gap-4 sm:grid-cols-2">
<label className="block">
<span className="field-label">
{departmentAdminText.key}
</span>
{existing ? (
<p className="mt-1.5 font-mono text-sm">{existing.key}</p>
) : (
<>
<input name="key" required maxLength={21} className="field w-full font-mono" />
<span className="field-hint">
{departmentAdminText.keyHint}
</span>
</>
)}
</label>
<label className="block">
<span className="field-label">
{departmentAdminText.name}
</span>
<input
name="name"
required
maxLength={120}
defaultValue={existing?.name}
className="field w-full"
/>
</label>
</div>
<label className="block">
<span className="field-label">
{departmentAdminText.description}
</span>
<textarea
name="description"
rows={2}
maxLength={500}
defaultValue={existing?.description ?? ''}
className="field w-full"
/>
</label>
<div className="flex flex-wrap items-end gap-6">
<label className="block">
<span className="field-label">
{departmentAdminText.minClearance}
</span>
<select
name="minClearance"
defaultValue={existing?.minClearance ?? 0}
className="field"
>
{levels.map((level) => (
<option key={level.level} value={level.level}>
{level.level} · {level.name}
</option>
))}
</select>
<span className="field-hint">
{departmentAdminText.minClearanceHint}
</span>
</label>
{existing ? (
<label className="flex items-center gap-2 pb-2 text-sm">
<input type="checkbox" name="isArchived" defaultChecked={existing.isArchived} />
{departmentAdminText.archived}
</label>
) : null}
</div>
<div className="flex flex-wrap items-center gap-4">
<button type="submit" disabled={pending} aria-busy={pending} className="btn btn-primary">
{existing ? departmentAdminText.save : departmentAdminText.create}
</button>
{existing ? (
<button type="button" onClick={() => setOffen(false)} className="btn btn-sm">
{departmentAdminText.cancel}
</button>
) : null}
{state?.ok ? (
<span className="font-condensed text-2xs text-text-dim">
{existing ? departmentAdminText.saved : departmentAdminText.created}
</span>
) : null}
{state && !state.ok ? (
<span className="font-condensed text-2xs text-stamp-4">{state.error}</span>
) : null}
</div>
</form>
)
}