Files
betreuungs-cockpit/src/components/kontakte/kontakt-formular.tsx
T
Florian Wilde 9e68d10787 Ansprechpartner am Vorgang hinterlegen
Ein namentlich adressiertes Schreiben landet bei der Person, die den
Vorgang kennt, statt in der Poststelle. Am Vorgang laesst sich jetzt ein
Kontakt aus dem Verzeichnis als Ansprechpartner benennen; die
Sachstandsanfrage redet ihn an, und sein Name steht ueber der Behoerde.

Die Sachstandsanfrage konnte das schon - sie bekam den Namen nur nie
uebergeben. entwurf.ts lud einen der verknuepften Kontakte und verwarf
ihn wieder. Wer angeredet wird, ist jetzt eine Entscheidung und kein
Zufall.

Aufbau:
- Migration: ansprechpartnerId am Vorgang (SetNull - ein Vorgang darf
  nicht daran haengen, dass es den Kontakt noch gibt) und anrede am
  Kontakt.
- Die Anrede als reine Funktion in kontakte/anrede.ts. Geraten wird nie:
  ohne hinterlegte Anrede wird mit vollem Namen angeredet. Ein leerer
  Name mit gesetzter Anrede faellt auf die allgemeine Anrede zurueck -
  "Sehr geehrte Frau ," darf nie herausgehen.
- Der Wechsel des Ansprechpartners steht mit NAMEN im Protokoll. Zwei
  cuids nebeneinander waeren protokolliert, aber nicht nachvollziehbar,
  und nachvollziehbar war die Anforderung.
- Der Ansprechpartner wird gegen die Akte geprueft, bevor er gespeichert
  wird - sein Name steht anschliessend im Briefkopf.

Nebenbei behoben: Der Schreiben-Editor verwarf die empfaengerAnschrift
aus dem Entwurf, sein State startete immer leer. Jetzt kommt die
Anschrift des Ansprechpartners im Formular an.

Geprueft: neun Einheitstests fuer die Anrede, fuenf End-to-End-Tests von
der Auswahl bis zum Protokolleintrag.

Refs #9
2026-08-23 16:31:10 +02:00

179 lines
5.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client'
import { useActionState } from 'react'
import { useFormStatus } from 'react-dom'
import { AlertCircle } from 'lucide-react'
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Select } from '@/components/ui/select'
import { Textarea } from '@/components/ui/textarea'
import { Feld, Feldgruppe } from '@/components/feld'
import {
KONTAKT_ANREDEN,
KONTAKT_ROLLEN,
KONTAKT_ROLLE_BESCHRIFTUNG,
} from '@/lib/validierung/kontakte'
import { ANREDE_BESCHRIFTUNG } from '@/lib/kontakte/anrede'
import { kontaktSpeichern, type KontaktZustand } from '@/server/actions/kontakte'
/**
* Formular fuer einen Kontakt - Behoerde, Heim, Arzt, Notar.
*
* Das Aktenzeichen steht bewusst gleich neben dem Namen: Am Telefon ist
* es das Erste, wonach gefragt wird.
*/
export type KontaktWerte = {
id: string
name: string
organisation: string | null
rolle: string
anrede: string
telefon: string | null
mobil: string | null
fax: string | null
email: string | null
strasse: string | null
plz: string | null
ort: string | null
aktenzeichen: string | null
notizen: string | null
}
const ANFANG: KontaktZustand = {}
export function KontaktFormular({
betreuteId,
kontakt,
}: {
betreuteId: string
kontakt?: KontaktWerte
}) {
const [zustand, aktion] = useActionState(kontaktSpeichern, ANFANG)
const wert = (feld: keyof KontaktWerte): string =>
zustand.werte?.[feld] ?? (kontakt ? (kontakt[feld] ?? '') : '')
return (
<form action={aktion} className="space-y-8">
<input type="hidden" name="betreuteId" value={betreuteId} />
{kontakt && <input type="hidden" name="id" value={kontakt.id} />}
{zustand.fehler && (
<Alert variant="fehler">
<AlertCircle aria-hidden="true" />
<AlertTitle>Das hat noch nicht geklappt</AlertTitle>
<AlertDescription>{zustand.fehler}</AlertDescription>
</Alert>
)}
<Feldgruppe titel="Wer ist das?">
<Feld id="name" beschriftung="Name" pflicht fehler={zustand.felder?.name}>
<Input name="name" defaultValue={wert('name')} autoComplete="name" required />
</Feld>
<Feld
id="organisation"
beschriftung="Behörde, Heim oder Kanzlei"
fehler={zustand.felder?.organisation}
>
<Input name="organisation" defaultValue={wert('organisation')} autoComplete="organization" />
</Feld>
<Feld
id="rolle"
beschriftung="Rolle"
pflicht
erklaerung="Nach der Rolle wird das Verzeichnis gruppiert."
fehler={zustand.felder?.rolle}
>
<Select name="rolle" defaultValue={wert('rolle') || 'SONSTIGES'}>
{KONTAKT_ROLLEN.map((rolle) => (
<option key={rolle} value={rolle}>
{KONTAKT_ROLLE_BESCHRIFTUNG[rolle]}
</option>
))}
</Select>
</Feld>
<Feld
id="anrede"
beschriftung="Anrede"
erklaerung="Für die Briefanrede. Ohne Angabe wird mit vollem Namen angeredet — geraten wird nie."
fehler={zustand.felder?.anrede}
>
<Select name="anrede" defaultValue={wert('anrede') || 'OHNE'}>
{KONTAKT_ANREDEN.map((anrede) => (
<option key={anrede} value={anrede}>
{ANREDE_BESCHRIFTUNG[anrede]}
</option>
))}
</Select>
</Feld>
<Feld
id="aktenzeichen"
beschriftung="Aktenzeichen dort"
erklaerung="Das Zeichen, unter dem diese Stelle den Vorgang führt – nicht mein eigenes."
fehler={zustand.felder?.aktenzeichen}
>
<Input name="aktenzeichen" defaultValue={wert('aktenzeichen')} />
</Feld>
</Feldgruppe>
<Feldgruppe titel="Wie erreiche ich die Stelle?">
<Feld id="telefon" beschriftung="Telefon" fehler={zustand.felder?.telefon}>
<Input name="telefon" type="tel" defaultValue={wert('telefon')} autoComplete="tel" />
</Feld>
<Feld id="mobil" beschriftung="Mobil" fehler={zustand.felder?.mobil}>
<Input name="mobil" type="tel" defaultValue={wert('mobil')} />
</Feld>
<Feld id="email" beschriftung="E-Mail" fehler={zustand.felder?.email}>
<Input name="email" type="email" defaultValue={wert('email')} autoComplete="email" />
</Feld>
<Feld id="fax" beschriftung="Fax" fehler={zustand.felder?.fax}>
<Input name="fax" type="tel" defaultValue={wert('fax')} />
</Feld>
</Feldgruppe>
<Feldgruppe titel="Anschrift">
<Feld id="strasse" beschriftung="Straße und Hausnummer" fehler={zustand.felder?.strasse}>
<Input name="strasse" defaultValue={wert('strasse')} autoComplete="street-address" />
</Feld>
<Feld id="plz" beschriftung="PLZ" fehler={zustand.felder?.plz}>
<Input name="plz" defaultValue={wert('plz')} autoComplete="postal-code" inputMode="numeric" />
</Feld>
<Feld id="ort" beschriftung="Ort" fehler={zustand.felder?.ort}>
<Input name="ort" defaultValue={wert('ort')} autoComplete="address-level2" />
</Feld>
</Feldgruppe>
<Feld
id="notizen"
beschriftung="Notizen"
erklaerung="Sprechzeiten, Durchwahl der Vertretung, wie das letzte Gespräch lief. Wird verschlüsselt gespeichert."
fehler={zustand.felder?.notizen}
>
<Textarea name="notizen" defaultValue={wert('notizen')} rows={4} />
</Feld>
<SpeichernKnopf neu={!kontakt} />
</form>
)
}
function SpeichernKnopf({ neu }: { neu: boolean }) {
const { pending } = useFormStatus()
return (
<Button type="submit" size="lg" disabled={pending}>
{pending ? 'Wird gespeichert …' : neu ? 'Kontakt anlegen' : 'Änderungen speichern'}
</Button>
)
}