Appearance
Sektions-Architektur
Die Editor-Oberfläche ist sektionsgetrieben. Verfügbarkeit, Reihenfolge, Labels, Dirty-Status und Komponentenwahl werden aus Setup-Daten, einem zentralen Descriptor-Register und app-nahen Section-Metadaten zusammengesetzt.
Wenn du eine neue Sektion konkret anlegen willst, starte am besten mit /add-new-section. Diese Seite hier erklärt vor allem das zugrunde liegende Architektur- und Wrapper-Modell.
Sektions-Registry
Die Section-Metadaten sind bewusst auf kleine Dateien verteilt, damit eine neue Standardsektion nicht mehr in mehreren Consumer-Dateien manuell nachgetragen werden muss.
app/utils/sectionDescriptors.ts definiert pro logischer Sektion:
- kanonischen Runtime-Key
- Alias-Keys fuer Setup, Backend oder Legacy-Verbraucher
- Standard-Übersetzungsschlüssel
- optionale gemeinsame Flags wie globale Templates
app/utils/sectionRuntime.ts ergänzt pro Descriptor:
- Icon
- Store-ID
- Server-Endpunkt
- Lade-Text
app/utils/sectionComponents.ts hält das Vue-Komponenten-Mapping. app/utils/pdf/section-generators.ts hält das PDF-Generator-Mapping.
app/composables/protocol/useMainAccordionSections.ts und app/utils/pdf/section-registry.ts konsumieren diese Metadaten. Neue Standardsektionen sollen dort nicht mehr als eigene lokale Mapping-Einträge ergänzt werden.
Die aktuelle Descriptor-Registry umfasst IDs wie:
meterkeyroomadditional_partnersbank_detailsother_remarkstermination_reasonobject_equipmentbauliche_veraenderungentenant_confirmation
Zusätzlich bleiben Runtime-Keys wie bankverbindung, sonstige_bemerkungen, kuendigungsgrund, object_ausstattung und Alias-Keys wie obuilding_changes über dasselbe Descriptor-Register verfügbar.
Setup-Merge
Die im Editor sichtbare Sektionsliste ist nicht nur aus der Registry hart kodiert.
useMainAccordionSections() führt zusammen:
- das Descriptor-Register für gemeinsame Schlüssel- und Label-Metadaten
- die Runtime- und Komponenten-Metadaten für Icons, Store-Zuordnung und Komponenten
setupStore.sectionsfür serverdefinierte SektionsdatensätzesetupStore.getMergedSection(sectionKey, protocolTypeId)für Überschreibungen je Protokolltyp
Sektionen mit isActive === false werden ausgeschlossen.
Reihenfolge
Die endgültige Reihenfolge kombiniert:
- serverseitige Sortierung aus
setupStore.sections - lokale Benutzerreihenfolge aus
settingsStore.sectionOrder
Das Composable führt beide Quellen zusammen und behält nur aktuell aktive Sektionen.
Persistenz der offenen Sektion
Die aktuell offene Akkordeon-Sektion wird pro Acceptance-ID über VueUse-Storage gespeichert.
- Storage-Key:
accordion-open-sections - Wert: Zuordnung von Acceptance-ID zu Section-Key
Wrapper-Komponenten
Für einfache Collection-Sektionen ist CollectionSection.vue die bevorzugte Hülle. Sie kombiniert useSectionWrapperProps(...), SectionWrapper.vue, SectionTable.vue, den Empty-State und optional den Standard-Add-Footer in einem Baustein.
Direktes Wiring über SectionWrapper.vue bleibt sinnvoll, wenn eine Sektion kein normales Einzeltabellen-Layout hat, zum Beispiel bei Singleton-Formularen oder stärker gruppierten Spezialfällen.
CollectionSection.vue
Gemeinsame Hülle für standardisierte Collection-Sektionen.
Aufgaben:
- bindet den Sektionsstore über
useSectionWrapperProps(...)an - rendert standardmäßig
SectionTable.vuemit Empty-State - kann über Props einen
SectionAddFootererzeugen - erlaubt über Slots weiterhin angepasste Zellen, Footer oder komplexeren Content
- rendert Modals und andere aus dem Tabellenlayout herausgelöste UI über den expliziten
#overlaySlot; der Default-Slot bleibt für Tabellen-/Content-Slots reserviert
useSingletonSection(...)
Bevorzugtes Composable für einfache Singleton-Sektionen mit genau einem aktiven Eintrag.
Aufgaben:
- bindet den Sektionsstore über
useSectionWrapperProps(...)an - liefert
entryals ersten aktiven Eintrag - erzeugt beim Mount automatisch einen leeren Eintrag, falls die Sektion noch leer ist
- reduziert Boilerplate für
activeEntries[0]undonMounted(addEmpty)
SectionWrapper.vue
Gemeinsame Hülle für Sektionsinhalt.
Aufgaben:
- Header mit Text zur letzten Änderung
- Speichern-Button mit Dirty-Indikator
- Fehleranzeige über
SectionSyncError - optionale Anzeige von
pdfTextTopundpdfTextBottomals Formularhinweise, wennshowSectionTextsInFormaktiv ist und ein Section-Key überSectionFormTextProvider.vuebereitsteht - optionaler Footer-Slot für Add-Row-Aktionen
SectionTable.vue
Schmale gemeinsame Hülle um UTable.
Aufgaben:
- konsistente Tabellenhülle und Border-Styling
- Weitergabe des Expanded-Row-Zustands
- Weitergabe des Column-Pinnings
- Weitergabe der Slots an die zugrunde liegende Tabelle
Descriptor-backed Table Cells
Für häufige storegebundene Tabellenzellen gibt es Column-Descriptor-Helper in app/utils/sectionTableColumns.ts:
textColumn(...)für TexteingabennumberColumn(...)für ZahleneingabendateColumn(...)für DatumsfelderselectColumn(...)für AuswahlfeldercheckboxColumn(...)für boolesche Felder
Diese Helper schreiben über updateFieldByKey(...) in den Store und vermeiden wiederholte Template-Blöcke aus #<field>-cell, StoreCell-Wrappern und @update:model-value-Handlern.
Die Wrapper-Komponenten StoreCellInput.vue, StoreCellDate.vue, StoreSelectCell.vue und StoreCheckboxCell.vue bleiben interne Bausteine der Descriptor-Helper. StoreCell.vue bleibt die niedrigere Primitive für Spezialfälle.
Aufgaben:
- computed Zwei-Wege-Bindung auf ein Entry-Feld
- optionale debouncte Updates
- optionale String-Konvertierung für numerische oder optionale Eingaben
- sofortiges UI-Feedback, solange der debouncte Write noch aussteht
Für Tabellenspalten liegt das gemeinsame Basismuster in app/utils/sectionTableColumns.ts:
sectionColumn(...)erzeugt einfacheaccessorKey-Spalten mit Header und LayoutoptionentextColumn(...),numberColumn(...),dateColumn(...),selectColumn(...)undcheckboxColumn(...)erzeugen Standardzellen mit Store-BindingentityActionsColumn(...)erzeugt die Standard-Action-Spalte fürEntityActions.vue
Row-Action-Atoms
Für Tabellenaktionen liefert EntityActions.vue den gemeinsamen Pfad für Delete plus die häufigen Standard-Aktionen Note, Image, Edit und Clone.
Die eigentlichen wiederverwendbaren Action-Atome bleiben kleine Bausteine, zum Beispiel:
app/components/Shared/EntityNoteAction.vueapp/components/Shared/EntityImageToggleAction.vue
Einfache Tabellen hängen diese Aktionen über entityActionsColumn(...) an EntityActions.vue und reichen nur noch die benötigten Konfigurationen und Handler durch.
Bevorzugtes Muster:
ts
entityActionsColumn<Thing>((entry) => ({
deleteTitle: t('thing.deleteConfirm'),
note: {
modelValue: entry.note,
placeholder: t('thing.notePlaceholder'),
title: t('thing.note'),
},
imageToggle: {
acceptanceId: setupStore.acceptanceId,
parentType: 'thing',
parentId: entry.id,
isExpanded: expandedId.value === entry.id,
},
showEdit: isEditMode.value,
onDelete: () => thingStore.remove(entry.id),
onNoteSave: (value) => thingStore.updateField(entry.id, 'note', value),
onImageToggle: () => toggleExpand(entry.id),
onEdit: () => edit(entry),
}))Nur wenn eine Zeile echte Sonderlogik braucht, sollte dafür noch eine kleine sibling Action-Komponente entstehen. Der Default ist jetzt der Shared-Pfad über entityActionsColumn(...) und EntityActions.vue.
Neue Sektion hinzufügen
Beim Anlegen einer neuen Sektion sollte zuerst entschieden werden, welches UI-Muster gemeint ist.
Für die vollständige Schritt-für-Schritt-Liste der Implementierungs-Touchpoints siehe /add-new-section.
Wahl des Musters
table: mehrere Einträge, standardisierte Tabelle, Add-Footer und Empty-Statesingleton: genau ein aktiver Eintrag mit direkter Feldbindungsetup-drivenoder Spezialfall: stark eigenes Layout oder setup-/registry-getriebener Inhalt
Welche Hülle oder welches Composable wird verwendet?
- Für
table-Sektionen:CollectionSection.vueplus Descriptor-Helper für Standardzellen - Für
singleton-Sektionen:useSingletonSection(...)zusammen mitSectionWrapper.vue - Für Spezialfälle: direkt
useSectionWrapperProps(...)zusammen mitSectionWrapper.vue
Relevante Form-Composables
useSectionWrapperProps(...): gemeinsame Save-, Dirty- und Error-Anbindung für fast alle SektionenuseSectionSync(...): eigentliche Save-Logik unterhalb vonuseSectionWrapperProps(...)useSingletonSection(...): Wrapper-Binding plus auto-create undentryfür Singleton-FormulareuseStoreField(...): optional für debouncte Zwei-Wege-Bindung einzelner Store-FelderuseExpandedRow(...): gemeinsame Quelle für expandierbare Tabellenzeilen; Tabellen gebenexpandedStateundhandleUpdateweiter, gruppierte Tabellen nutzengetExpandedStateForGroup(...)undhandleUpdateScoped(...)useConditionOptions(...): optional für Status-/Condition-Auswahl mit gemeinsamen Labels, Farben und Icons
Generator oder manuell?
Der Section-Generator in scripts/section-generator/templates.ts erzeugt das Grundgerüst bereits passend zum gewählten Typ:
tableerzeugt eineCollectionSection-basierte Form mit Descriptor-Helpern wietextColumn(...)singletonerzeugt eine Form mituseSingletonSection(...)setup-drivenerzeugt eine minimal verdrahteteSectionWrapper-Hülle
Manuelles Anlegen bleibt sinnvoll, wenn die Sektion stark vom Standardmuster abweicht.
Typische Dateien für eine neue Sektion
app/schemas/<entity>.tsapp/schemas/entities.tsapp/stores/entityStores.tsoder ein spezialisierter Store unterapp/stores/<entity>.ts- optional
app/stores/<entity>.tsals expliziter Exportpfad fuer Standard-Stores app/stores/registry.ts, falls die Sektion nicht ueber die Standard-Store-Registry laeuftapp/components/<Section>/<Section>Form.vueapp/utils/sectionDescriptors.tsapp/utils/sectionRuntime.tsapp/utils/sectionComponents.tsi18n/locales/de.jsonapp/db/index.tsnur dann, wenn eine neue persistierte Tabelle hinzukommt
Sichtbarkeit im Accordion
Damit die neue Sektion im Haupt-Accordion erscheint, braucht sie einen Descriptor, einen Runtime-Eintrag und ein Komponenten-Mapping. useMainAccordionSections.ts leitet daraus die sichtbare Accordion-Registry ab.
Dazu gehören mindestens:
- der kanonische Section-Key und nötige Aliase
- Icon, Store-ID, Server-Endpunkt und Lade-Text
- Form-Komponente
- optional spezielles Invalid-Wiring, wenn der Consumer es noch nicht generisch ableiten kann