'use client' import { createContext, useContext, useRef, useState, type ReactNode } from 'react' import { sectionText } from '@/lib/i18n/cases' import type { SaveSectionResult, SaveSections } from './types' /** * Das Formular um die Abschnitte herum: **ein** Knopf für das ganze Blatt. * * Vorher trug jedes Feld seinen eigenen. Einen Arbeitsvertrag auszufüllen * hiess damit zwölfmal speichern, und wer eines vergass, merkte es erst beim * Nachlesen. Jetzt sammelt dieses Formular ein, was sich geändert hat, und * schickt es in einem Zug. * * Gespeichert wird **je Feld** und nicht als Ganzes. Das ist kein Versehen: * Abschnitte sind einzeln versioniert, und wenn eines zwischenzeitlich von * jemand anderem geändert wurde, soll das die übrigen nicht aufhalten. Die * Rückmeldung sagt dann genau, was durchging und was noch offen ist. * * Die Felder melden sich hier an, statt ihren Zustand nach oben zu geben: * so löst ein Tastendruck in einem Feld kein Neuzeichnen des ganzen Blattes * aus. */ export type FieldHandle = { fieldKey: string /** Der geänderte Wert, oder null wenn unverändert. */ collect: () => { fieldKey: string; value: unknown; baseVersion: number } | null apply: (result: SaveSectionResult) => void } type Kontext = { register: (handle: FieldHandle) => () => void changed: (fieldKey: string, dirty: boolean) => void pending: boolean } const SectionsContext = createContext(null) export function useSectionsForm(): Kontext | null { return useContext(SectionsContext) } export function SectionsForm({ save, label, children, }: { save: SaveSections /** „Dokument speichern" oder „Abschnitte speichern" - je nachdem, was es ist. */ label: string children: ReactNode }) { const felder = useRef(new Map()) const [geaendert, setGeaendert] = useState>(new Set()) const [pending, setPending] = useState(false) const [meldung, setMeldung] = useState(null) const kontext: Kontext = { register(handle) { felder.current.set(handle.fieldKey, handle) return () => { felder.current.delete(handle.fieldKey) } }, changed(fieldKey, dirty) { setGeaendert((bisher) => { if (dirty === bisher.has(fieldKey)) return bisher const naechste = new Set(bisher) if (dirty) naechste.add(fieldKey) else naechste.delete(fieldKey) return naechste }) }, pending, } async function speichern() { const eintraege = [...felder.current.values()] .map((handle) => handle.collect()) .filter((eintrag) => eintrag !== null) if (eintraege.length === 0) { setMeldung(sectionText.unchanged) return } setPending(true) setMeldung(null) try { const ergebnisse = await save(eintraege) let gut = 0 let offen = 0 for (const [fieldKey, ergebnis] of Object.entries(ergebnisse)) { felder.current.get(fieldKey)?.apply(ergebnis) if (ergebnis.ok) gut++ else offen++ } setMeldung(offen === 0 ? sectionText.savedCount(gut) : sectionText.partly(gut, offen)) } finally { setPending(false) } } return ( {children}
{geaendert.size > 0 ? sectionText.changedCount(geaendert.size) : sectionText.unchanged} {meldung ? {meldung} : null}
) }