Skip to content

Editor-Ablauf

Die Haupt-Editorroute ist app/pages/index.vue. Das meiste Verhalten ist an app/composables/protocol/useAcceptanceEditorPage.ts delegiert.

Hauptaufgaben

  • ermittelt die aktive Acceptance-ID aus der Route
  • lädt Acceptance-Daten über acceptanceStore.sync(...)
  • stellt Sektionszustand für die Seitenhülle bereit
  • koordiniert Speichern-Aktionen und Sync-Feedback
  • blockiert Navigation in Kundenansicht oder Finalize, wenn die Kündigungsgrund-Validierung fehlschlägt
  • verwendet app/utils/protocolWorkflow.ts für gemeinsame Workflow-Guards und Step-Zielpfade
  • verwendet useCustomerViewNavigation() als gemeinsamen Wechsel in die Kundenansicht, damit Editor-Button und Top-Bar-Aktionen dieselben Prüfungen und Auto-Save-Regeln nutzen

Ablaufdiagramm

Laden der Acceptance

Der Editor-Ablauf verwendet route.query.acceptance als primären Identitätseingang.

Das Laden der Acceptance läuft über useAcceptanceStore() statt über direkte Entity-Endpoint-Aufrufe aus Komponenten heraus.

Zusammensetzung der Sektionen

Die Editor-Hülle verwendet useMainAccordionSections(), um festzulegen:

  • welche Sektionen aktiv sind
  • ihre Reihenfolge
  • ihre Labels und Icons
  • ob eine Sektion dirty oder invalid ist

Jede Sektionskomponente arbeitet anschließend mit ihrem eigenen Pinia-Store und den gemeinsamen Wrapper-Komponenten.

Der Editor kann die Sektionen in zwei Layouts darstellen. Die Auswahl liegt in useSettingsStore().protocolSectionLayout und wird lokal unter app-settings gespeichert:

  • accordion: Standardansicht über MainAccordion.vue, in der mehrere Accordion-Sektionen im Editor sichtbar sein können
  • sidebar: Vollbreitenansicht über MainSectionSidebar.vue, in der links eine Nuxt-UI-Sidebar die aktiven Sektionen navigiert und rechts immer nur die aktuell ausgewählte Sektion gerendert wird

In der Accordion-Ansicht werden Sektionsinhalte lazy gemountet. Eine Section-Komponente wird dadurch erst initialisiert, wenn ihre Accordion-Zeile geöffnet wird, und bleibt danach für den restlichen Editor-Lebenszyklus gemountet. Das entlastet den Editor-Start bei vielen oder schweren Sections, während der Zustand bereits geöffneter Sections erhalten bleibt.

Die Accordion-Höhenmessung läuft entkoppelt nach dem DOM-Update, damit Interaktionen weniger direkt durch Layoutmessungen blockiert werden. Die Raumsektion behält dadurch dieselbe sichtbare Accordion-Animation wie andere Sections, rendert ihre schweren Detailinhalte aber erst beim Öffnen einzelner Räume.

Die Sidebar-Ansicht verwendet weiterhin dieselben Sektionskonfigurationen aus useMainAccordionSections(). Dirty- und Invalid-Zustände werden in der Navigation angezeigt. Die aktuell sichtbare Sektion wird zusätzlich in der URL als section-Query synchronisiert, damit Browser-Zurück/Vor und direkte Links auf eine Sektion funktionieren.

Der Quickjump in der Topbar berücksichtigt dieses Layout: In der Accordion-Ansicht fokussiert er wie bisher eine Sektion, in der Sidebar-Ansicht setzt er die aktive Sektion und aktualisiert die section-Query ohne Accordion-Scroll.

Optional kann der Editor die in den globalen Sektionseinstellungen gepflegten Texte vor und nach einer Sektion auch im Formular anzeigen. Die lokale Einstellung liegt in useSettingsStore().showSectionTextsInForm. MainAccordion.vue und MainSectionSidebar.vue stellen den aktuellen Section-Key über SectionFormTextProvider.vue bereit; SectionWrapper.vue löst daraus pro aktuellem Protokolltyp setupStore.getMergedSection(sectionKey, acceptance.type) auf und rendert pdfTextTop beziehungsweise pdfTextBottom als HTML-Hinweisblöcke. Rein leere HTML-Absätze wie <p></p> erzeugen keinen Block. Die PDF-Ausgabe bleibt davon unabhängig und unverändert.

Validierung vor der Kundenansicht

Der Store für Kündigungsgründe kann spätere Workflow-Schritte blockieren.

Wenn das aktuelle Setup einen Kündigungsgrund verlangt:

  • verwendet der Editor resolveProtocolWorkflowBlockRedirect(...) aus app/utils/protocolWorkflow.ts
  • werden Setup-Load, markValidationAttempted() und getValidationBlock(...) nicht mehr lokal neu kombiniert
  • fokussiert blockierte Navigation den im Section-Descriptor hinterlegten Kündigungsgrund-Key über useMainAccordionSections().focusSection(...)

Der sichtbare Einstieg in die Kundenansicht kann aus dem Editor-Button oder aus den Top-Bar-Aktionen kommen. TopBarMain.vue und TopBarSettings.vue verwenden dafür ebenfalls useCustomerViewNavigation(), damit auch aus den Einstellungen heraus dieselbe Auto-Save- und Validierungslogik greift.

Die Top-Bar-Aktionen enthalten im Editor außerdem Protokoll leeren. Nach Bestätigung markiert useProtocolLocalClear() bestehende Sektionsdaten als lokale Löschentwürfe, blendet Bilder lokal aus und setzt die Acceptance auf ungespeichert. Auto-Save wird für diesen Zustand übersprungen. Der gelbe Speichern-Einstieg in der Topbar öffnet vor dem Server-Speichern ein nicht wegklickbares Entscheidungsmodal: Auf Server speichern überträgt den aktuellen lokalen Stand nach dem Leeren inklusive Bildlöschungen an den Server, Abbrechen beziehungsweise Zurücknehmen verwirft den lokalen Löschentwurf und stellt den Stand von vor dem Leeren wieder her. Aktualisieren warnt bei lokalen Formularänderungen, Löschentwürfen oder pending Bildänderungen direkt in der Topbar, bevor der Serverstand inklusive acceptance-weitem Bildsync geladen und der lokale Stand bewusst verworfen wird. Neue Sektionen müssen den dafür nötigen Store- und Server-Vertrag aus /add-new-section erfüllen.

Save-Integration

Speichern auf Acceptance-Ebene verwendet useFormSave(). Dieser Save-Flow speichert zuerst dirty Sektionsstores und synchronisiert danach pending Bilder, Bildmetadaten und vorgemerkte Bildlöschungen für die aktuelle Acceptance. Wenn nur Bilder oder Bildmetadaten pending sind, läuft trotzdem der Medien-Sync und der Save endet bei Erfolg mit saved statt no_changes.

Der Editor bietet zusätzlich ein lokales Opt-in für automatisches Speichern: useProtocolAutoSave() beobachtet useUnsavedChanges(), useOnlineStatus() und den aktuellen Sync-State. Dadurch zählen auch reine Bildänderungen zum Auto-Save-Signal. Bei aktivem useSettingsStore().autoSaveProtocolChanges wird nach kurzer Inaktivität useAcceptanceEditorPage().saveToServer(...) aufgerufen. Erfolgs-Toasts werden nur gezeigt, wenn useSettingsStore().showAutoSaveProtocolChangesToast aktiv ist, verwenden die kurze Meldung Änderungen gespeichert und bleiben bei Auto-Save länger sichtbar als normale manuelle Save-Toasts. Der Auto-Save startet nicht offline, nicht parallel zu einem laufenden Save, nicht bei gesperrten Protokollen und nicht in Konfliktzuständen wie local_changes_present oder server_data_changed.

Der persistente Hinweis Nur lokal gespeichert bleibt sichtbar, wenn Auto-Save nicht speichern kann, zum Beispiel offline, nach einem Serverfehler oder bei Konflikten. Während ein möglicher Auto-Save geplant oder aktiv ist, wird der Hinweis unterdrückt und bei erfolgreichem Save durch den bereinigten Sync-State entfernt. Wenn Auto-Save aktiv ist und der Hinweis sichtbar bleibt, enthält er keine Jetzt speichern-Action, weil der Auto-Save selbst den nächsten Speicherversuch plant.

Der Sync-Banner SyncStatusAlert bekommt dieselben Auto-Save-Signale. Reine Local-only-Infos aus offline_loaded werden unterdrückt, solange Auto-Save online und konfliktfrei speichern kann. Echte Offline-/Serverfehler bleiben sichtbar, damit klar bleibt, warum nicht automatisch synchronisiert wird.

Die Topbar behandelt den Auto-Save-Schalter ebenfalls als Local-only-Suppression: Bei aktivem Auto-Save zeigt der Speichern-Button keinen Dirty-Warnzustand und keinen Nur lokal gespeichert-Tooltip mehr. Offline bleibt weiterhin sichtbar.

Die Unterschriftenseite app/pages/customer-view/signatures.vue nutzt denselben Auto-Save-Schalter. Vor dem Save werden ausstehende Signatur-Draft-Persistierungen geleert, danach läuft useFormSave().saveCurrentAcceptance(...); auch hier steuert showAutoSaveProtocolChangesToast, ob Erfolgs-Toasts angezeigt werden. Die Einstellungen selbst lösen keinen Server-Auto-Save aus; sie stellen nur die lokalen Optionen bereit.

Die Save-UX auf Sektions-Ebene wird durch useSectionWrapperProps(...) und die vom Dexie-Sync-Plugin bereitgestellten Methoden jedes Sektionsstores gesteuert.

Einfache Collection-Sektionen nutzen diese Anbindung meist indirekt über CollectionSection.vue. Einfache Singleton-Sektionen verwenden useSingletonSection(...), das Wrapper-Binding, den ersten aktiven Eintrag und das automatische addEmpty() beim Mount zusammenfasst. Spezialisierte Formulare können weiterhin direkt auf SectionWrapper.vue und useSectionWrapperProps(...) setzen.

Übergänge und Animationen

Sektionswechsel in der Sidebar-Ansicht laufen über AppPageTransition.vue. Die Komponente verwendet die globale page-Transition, rendert aber ohne <Transition>, wenn useSettingsStore().animationsDisabled aktiv ist.

Die globale Animationsabschaltung setzt zusätzlich die Klasse app-animations-disabled im App-Wrapper. Dadurch werden CSS-Transitionen, CSS-Animationen und sanftes Scrollen deaktiviert. Die Einstellung ist bewusst global, damit Seitenübergänge, Bereichswechsel und andere UI-Bewegungen konsistent abschaltbar sind.