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
375 Vorkommen in 210 Dateien. Betrifft Kommentare, Oberflaechentexte und die README; an keiner Stelle Bedeutung, nur Zeichensetzung.
170 lines
5.8 KiB
TypeScript
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>
|
|
)
|
|
}
|