Files
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

170 lines
5.8 KiB
TypeScript

import { de } from '@/lib/i18n/de'
import { fillPlaceholders, isInputField } from '@/lib/templates/fields'
import { RedactedText } from '@/components/Redacted'
import { SectionEditor } from './SectionEditor'
import { SectionsForm } from './SectionsForm'
import { PhotoField } from '@/components/files/PhotoField'
import type { SaveSection, SaveSections } from './types'
import type { RenderedSection } from '@/lib/sections/render'
/**
* Ausgefüllte Vorlage: der Sachstand einer Fallakte oder der Inhalt eines
* Dokuments. Beide sind dasselbe Gebilde, deshalb dieselbe Darstellung.
*
* Feste Textbausteine der Vorlage werden gerendert, nicht bearbeitet - ihre
* Platzhalter ziehen die Werte der Eingabefelder. Damit trägt dieselbe
* Darstellung ein Formular und einen Vertragstext.
*
* Gespeichert wird das Blatt als Ganzes: ein Knopf unten, nicht einer je Feld.
* Lichtbilder sind die Ausnahme - ein Bild wird hochgeladen und geprüft, das
* ist ein eigener Vorgang und keine Feldänderung.
*/
export function Sections({
scope,
save,
saveAll,
saveAllLabel,
sections,
canEdit,
documentNumber,
topic,
photoUrls,
}: {
scope: string
save: SaveSection
/** Alle geänderten Felder in einem Zug. Fehlt sie, trägt jedes Feld seinen Knopf. */
saveAll?: SaveSections
saveAllLabel?: string
sections: RenderedSection[]
canEdit: boolean
/** Nur bei Dokumenten gesetzt: Ziel für das Lichtbild. */
documentNumber?: string
/** Thema für die Rückmeldung der Dateiprüfung. */
topic?: string
/** Bereits geprüfte Lichtbilder, je Feldschlüssel. */
photoUrls?: Record<string, string>
}) {
if (sections.length === 0) {
return <p className="text-text-dim">{de.section.empty}</p>
}
const werte = Object.fromEntries(
sections.filter((s) => s.visible).map((s) => [s.fieldKey, s.value]),
)
const blatt = (
<dl className="space-y-8">
{sections.map((section) => {
const field = section.field
// Feste Bausteine der Vorlage.
if (!isInputField(field)) {
if (field.type === 'pagebreak') return <hr key={field.key} className="border-line" />
const text = fillPlaceholders(field.text ?? '', werte)
return field.type === 'heading' ? (
<h3 key={field.key} className="font-condensed">
{text || field.label}
</h3>
) : (
<p key={field.key} className="max-w-(--container-measure) text-sm whitespace-pre-wrap">
{text}
</p>
)
}
if (field.type === 'photo') {
// Ohne Dokument gibt es kein Ziel für den Upload - Fallabschnitte
// tragen keine Bilder, ihre Dateien hängen an der Akte.
if (!documentNumber) {
return (
<div key={field.key}>
<dt className="font-condensed text-xs text-text-dim">{field.label}</dt>
<dd className="mt-1 text-2xs text-text-dim">{de.document.photoLater}</dd>
</div>
)
}
return (
<PhotoField
key={field.key}
save={save}
fieldKey={field.key}
label={field.label}
documentNumber={documentNumber}
topic={topic ?? ''}
initialAttachmentId={
section.visible && typeof section.value === 'string' ? section.value : null
}
initialUrl={photoUrls?.[field.key] ?? null}
initialVersion={section.version}
readOnly={!canEdit}
/>
)
}
return (
<div key={field.key}>
<dt className="flex flex-wrap items-baseline gap-x-3">
<span className="font-condensed text-xs text-text-dim">{field.label}</span>
{field.requiredToClose ? (
<span className="font-condensed text-2xs text-stamp-3">
{de.section.requiredToClose}
</span>
) : null}
</dt>
<dd>
{!section.visible ? (
// Serverseitig ersetzt: der echte Wert ist gar nicht erst hier.
<p className="mt-1.5 flex items-center gap-2">
<span className="redaction inline-block h-[1.1rem] w-40" aria-hidden />
<span className="font-mono text-2xs text-text-dim">
{de.section.lockedField(section.value.requiredLevel)}
</span>
</p>
) : section.segments ? (
<div className="mt-1.5">
<RedactedText segments={section.segments} />
{canEdit ? (
<SectionEditor
save={save}
scope={scope}
fieldKey={field.key}
label={field.label}
type={field.type}
options={field.options}
requiredToClose={field.requiredToClose}
initialValue={section.value}
initialVersion={section.version}
readOnly={false}
/>
) : null}
</div>
) : (
<SectionEditor
save={save}
scope={scope}
fieldKey={field.key}
label={field.label}
type={field.type}
options={field.options}
requiredToClose={field.requiredToClose}
initialValue={section.value}
initialVersion={section.version}
readOnly={!canEdit}
/>
)}
</dd>
</div>
)
})}
</dl>
)
if (!canEdit || !saveAll) return blatt
return (
<SectionsForm save={saveAll} label={saveAllLabel ?? de.section.saveAll}>
{blatt}
</SectionsForm>
)
}