feat(isdirty): Navigations-Guard gegen ungespeichertes Verlassen von Bearbeitungsmasken

Zentraler DirtyGuard (dirtyguard.ts) mit DirtyMaskHelper-Klasse und
3-Button-Bestätigungs-Modal (Speichern/Verwerden/Abbrechen). Form meldet
Dirty-State via onDirtyChange nach oben; input-Event ergänzt change für
frühzeitiges Dirty-Marking. Alle App-Navigationen (Breadcrumb, Menü,
In-Masken-Buttons, Abbrechen) laufen über guardedNavigate. Auto-Redraw
bei dirty per e.redraw=false unterbunden, damit DOM-Eingaben erhalten
bleiben (Input-Widget hat kein Live-Binding).

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
gg committed 2026-09-19 18:21:58 +02:00
1 parent fd30049a32
commit a67921ec74
10 files changed
+462 -120

No files matched your search

+20 -11
View File
@@ -11,6 +11,7 @@ import { Call_Log, call_log_liste } from "./Components/call_log"
import { Kontakt, kontaktliste } from "./Components/kontakt"
import { Kontaktadresse, kontaktadresseliste } from "./Components/kontaktadresse"
import { Tag, TagList } from "./Components/tags"
import { guardedNavigate } from "./Components/widgets/dirtyguard"
function healthcheck () {
@@ -118,21 +119,29 @@ const knopf: m.Component = {
}
const menu: m.ClosureComponent = () => {
let showProjektBerechtigungen = false
// Menü-Eintrag, der die Navigation über den Dirty-Guard leitet, so dass
// aus einer Bearbeitungsmaske heraus ungespeicherte Änderungen abgefragt
// werden, bevor die Ansicht wechselt.
const menuItem = (route: string, label: string) => {
return m("a.w3-button.w3-bar-item",
{href: "#", onclick: (e: Event) => {e.preventDefault(); guardedNavigate(route, undefined, e)}},
label)
}
return {
oninit: (vnode) => {
showProjektBerechtigungen = false
},
view: (vnode) => {
return m(menubar, [
m("a.w3-button.w3-bar-item", {href: "#!/kundenliste"}, "Kunden"),
m("a.w3-button.w3-bar-item", {href: "#!/projektliste"}, "Projekte"),
m("a.w3-button.w3-bar-item", {href: "#!/calllogliste"}, "Gesprächsnotizen"),
menuItem("/kundenliste", "Kunden"),
menuItem("/projektliste", "Projekte"),
menuItem("/calllogliste", "Gesprächsnotizen"),
m("div.w3-dropdown-hover",
m("button.w3-button", "Optionen"),
m("div.w3-dropdown-content.w3-bar-block.w3-card-4",
m("a.w3-button.w3-bar-item", {href: "#!/taglist"}, "Tags"),
m("a.w3-button.w3-bar-item", {href: "#!/default"}, "Datenexport"),
m("a.w3-button.w3-bar-item", {href: "#!/default"}, "Datenimport"),
menuItem("/taglist", "Tags"),
menuItem("/default", "Datenexport"),
menuItem("/default", "Datenimport"),
// Projekt-Berechtigungen nur für SysAdmin/AppAdmin sichtbar.
// Als Toggle-Button realisiert, da verschachtelte w3-dropdown-hover
// innerhalb eines offenen Dropdowns nicht zuverlässig funktionieren.
@@ -147,10 +156,10 @@ const menu: m.ClosureComponent = () => {
showProjektBerechtigungen
? m("div.w3-bar-block",
m("a.w3-button.w3-bar-item",
{href: "#!/projektuserliste", style: {'padding-left': '32px'}},
{href: "#", style: {'padding-left': '32px'}, onclick: (e: Event) => {e.preventDefault(); guardedNavigate("/projektuserliste", undefined, e)}},
"Projekt-Zugriffe"),
m("a.w3-button.w3-bar-item",
{href: "#!/userprojektliste", style: {'padding-left': '32px'}},
{href: "#", style: {'padding-left': '32px'}, onclick: (e: Event) => {e.preventDefault(); guardedNavigate("/userprojektliste", undefined, e)}},
"Nutzer-Zugriffe")
)
: null
@@ -162,9 +171,9 @@ const menu: m.ClosureComponent = () => {
m("div.w3-dropdown-hover.w3-right",
m("button.w3-button", "System"),
m("div.w3-dropdown-content.w3-bar-block.w3-card-4",
m("a.w3-button.w3-bar-item", {href: "#!/login"}, "Login"),
m("a.w3-button.w3-bar-item", {href: "#!/changepassword"}, "Kennwort ändern"),
m("a.w3-button.w3-bar-item", {href: "#!/usermanagement"}, "Verwalten"),
menuItem("/login", "Login"),
menuItem("/changepassword", "Kennwort ändern"),
menuItem("/usermanagement", "Verwalten"),
)
)
])