Files
AfiS-Aktensystem/web/app/(app)/personalakten/neu/NewPersonnelForm.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.5 KiB
TypeScript

'use client'
import { useActionState } from 'react'
import { clearanceText, commonText } from '@/lib/i18n/common'
import { personnelText } from '@/lib/i18n/people'
import { createPersonnelAction, type ActionResult } from '../actions'
/**
* Neue Personalakte.
*
* Das Discord-Konto ist **optional**. Personen ohne Konto - Zeugen, ehemalige
* Mitarbeiter, Externe - brauchen genauso eine Akte, und ihr Name ist dann das
* einzige, was sie identifiziert.
*/
export function NewPersonnelForm({
members,
levels,
statusTags,
departments,
defaultLevel,
}: {
members: { id: string; displayName: string }[]
levels: { level: number; name: string }[]
statusTags: { key: string; name: string }[]
departments: { id: string; key: string; name: string }[]
defaultLevel: number
}) {
const [state, action, pending] = useActionState<ActionResult | null, FormData>(
createPersonnelAction,
null,
)
return (
<form action={action} className="mt-6 space-y-5">
<Feld id="displayName" label={personnelText.name} required minLength={2} />
<div>
<label htmlFor="userId" className="field-label">
{personnelText.linkedAccount}
</label>
<select
id="userId"
name="userId"
defaultValue=""
className="mt-1.5 block w-full field"
>
<option value="">{personnelText.noAccount}</option>
{members.map((member) => (
<option key={member.id} value={member.id}>
{member.displayName}
</option>
))}
</select>
</div>
<div className="grid gap-5 sm:grid-cols-2">
<Feld id="rank" label={personnelText.rank} />
<label className="block">
<span className="field-label">
{personnelText.department}
</span>
<select name="departmentId" defaultValue="" className="field w-full">
<option value="">{personnelText.noDepartment}</option>
{departments.map((abteilung) => (
<option key={abteilung.id} value={abteilung.id}>
{abteilung.name}
</option>
))}
</select>
</label>
</div>
<fieldset>
<legend className="font-condensed text-xs text-text-dim">{personnelText.status}</legend>
{/* Kästchen statt Auswahlliste: mehrere Etiketten sind der Normalfall,
und eine Mehrfachauswahl mit Strg-Klick findet niemand. */}
<div className="mt-2 flex flex-wrap gap-x-6 gap-y-2">
{statusTags.map((tag) => (
<label key={tag.key} className="flex items-center gap-2 text-sm">
<input type="checkbox" name="statusTags" value={tag.key} />
{tag.name}
</label>
))}
</div>
<p className="field-hint">{personnelText.statusHint}</p>
</fieldset>
<div>
<label htmlFor="note" className="field-label">
{personnelText.note}
</label>
<textarea
id="note"
name="note"
rows={3}
className="mt-1.5 block w-full field"
/>
<p className="field-hint">{personnelText.noteHelp}</p>
</div>
<div>
<label htmlFor="classification" className="field-label">
{clearanceText.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 field"
>
{levels.map((level) => (
<option key={level.level} value={level.level}>
{level.level} · {level.name}
</option>
))}
</select>
</div>
{state && !state.ok ? (
<p className="font-condensed text-2xs text-stamp-4">{state.error}</p>
) : null}
<button
type="submit"
disabled={pending}
className="btn btn-primary"
>
{pending ? commonText.loading : personnelText.create}
</button>
</form>
)
}
function Feld({
id,
label,
required,
minLength,
}: {
id: string
label: string
required?: boolean
minLength?: number
}) {
return (
<div>
<label htmlFor={id} className="field-label">
{label}
</label>
<input
id={id}
name={id}
required={required}
minLength={minLength}
maxLength={120}
className="mt-1.5 block w-full field"
/>
</div>
)
}