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
179 lines
5.9 KiB
TypeScript
179 lines
5.9 KiB
TypeScript
'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>
|
||
)
|
||
}
|