feat(nav): Breadcrumb-Navigation für Hierarchie-Aufrufkette
Neue BreadcrumbBar-Widget-Komponente (breadcrumb.ts) zeigt die Aufrufkette als anklickbare Leiste. Der Route-Param bc (JSON-String) transportiert die Vorgänger-Ansichten durch alle Listen und Masken. Jede Ansicht hängt beim Weiternavigieren ihren eigenen Crumb an die Kette an; Masken reichen bc beim Speichern/Abbrechen zurück. Erfasst: kunde, projekt, kontaktadresse, call_log, kontakt. Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
1 parent
f1e5938ae1
commit
fd30049a32
6 files changed
+316
-27
No files matched your search
@@ -3,6 +3,7 @@ import {Form, iForm} from "./widgets/form"
|
|||||||
import { Input, iInput, Textarea, iTextarea} from "./widgets/inputs"
|
import { Input, iInput, Textarea, iTextarea} from "./widgets/inputs"
|
||||||
import {Select, iSelect, MultiSelect, iMultiSelect, iSelectOption} from "./widgets/select"
|
import {Select, iSelect, MultiSelect, iMultiSelect, iSelectOption} from "./widgets/select"
|
||||||
import {Treenode, iTreenode, Treetable, iTreetable, TopbarButton, iTopbarButton} from "./widgets/Treetable_V2"
|
import {Treenode, iTreenode, Treetable, iTreetable, TopbarButton, iTopbarButton} from "./widgets/Treetable_V2"
|
||||||
|
import {BreadcrumbBar, iBreadcrumb, iBreadcrumbBar, parseBc, serializeBc} from "./widgets/breadcrumb"
|
||||||
import {iCallLog, fetchCallLogs, fetchCallLogById, createCallLogREST, updateCallLogREST} from "../models/call_log"
|
import {iCallLog, fetchCallLogs, fetchCallLogById, createCallLogREST, updateCallLogREST} from "../models/call_log"
|
||||||
import {iKunde, fetchKunden} from "../models/kunde"
|
import {iKunde, fetchKunden} from "../models/kunde"
|
||||||
import {iProjekt, fetchProjekte, fetchProjektById} from "../models/projekt"
|
import {iProjekt, fetchProjekte, fetchProjektById} from "../models/projekt"
|
||||||
@@ -22,11 +23,20 @@ export const call_log_liste: m.ClosureComponent = (vnode) => {
|
|||||||
// da es nicht in den Route-Params enthalten ist.
|
// da es nicht in den Route-Params enthalten ist.
|
||||||
let projektName: string = ""
|
let projektName: string = ""
|
||||||
let kundenName: string = ""
|
let kundenName: string = ""
|
||||||
|
// Breadcrumb-Kette der Vorgänger-Ansichten (aus Route-Param bc).
|
||||||
|
let breadcrumbs: iBreadcrumb[] = []
|
||||||
|
|
||||||
const handleRowMarked = (node: iTreenode) => {
|
const handleRowMarked = (node: iTreenode) => {
|
||||||
selectedNode = node
|
selectedNode = node
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Eigener Listen-Crumb für die Breadcrumb-Kette.
|
||||||
|
const ownCrumb = (): iBreadcrumb => ({
|
||||||
|
route: "/calllogliste",
|
||||||
|
params: filterProjektId !== null ? {projekt_id: filterProjektId} : {},
|
||||||
|
label: filterProjektId !== null ? "Notizen: " + projektName : "Gesprächsnotizen"
|
||||||
|
})
|
||||||
|
|
||||||
const addCallLogRoute = () => {
|
const addCallLogRoute = () => {
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
const datum = now.toISOString().split('T')[0]
|
const datum = now.toISOString().split('T')[0]
|
||||||
@@ -51,6 +61,8 @@ export const call_log_liste: m.ClosureComponent = (vnode) => {
|
|||||||
params.return_projekt_id = filterProjektId
|
params.return_projekt_id = filterProjektId
|
||||||
params.return_projekt_name = projektName
|
params.return_projekt_name = projektName
|
||||||
}
|
}
|
||||||
|
// Breadcrumb-Kette um eigene Position erweitern.
|
||||||
|
params.bc = serializeBc([...breadcrumbs, ownCrumb()])
|
||||||
m.route.set("/call_log", params)
|
m.route.set("/call_log", params)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,6 +73,8 @@ export const call_log_liste: m.ClosureComponent = (vnode) => {
|
|||||||
params.return_projekt_id = filterProjektId
|
params.return_projekt_id = filterProjektId
|
||||||
params.return_projekt_name = projektName
|
params.return_projekt_name = projektName
|
||||||
}
|
}
|
||||||
|
// Breadcrumb-Kette um eigene Position erweitern.
|
||||||
|
params.bc = serializeBc([...breadcrumbs, ownCrumb()])
|
||||||
m.route.set("/call_log", params)
|
m.route.set("/call_log", params)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,6 +132,8 @@ export const call_log_liste: m.ClosureComponent = (vnode) => {
|
|||||||
filterProjektId = null
|
filterProjektId = null
|
||||||
projektName = ""
|
projektName = ""
|
||||||
}
|
}
|
||||||
|
// Breadcrumb-Kette aus Route-Param parsen.
|
||||||
|
breadcrumbs = parseBc(routeParams && routeParams.bc)
|
||||||
// Gesprächsnotizen asynchron vom Server laden. Die Filterung
|
// Gesprächsnotizen asynchron vom Server laden. Die Filterung
|
||||||
// nach projekt_id erfolgt serverseitig, sofern ein Filter
|
// nach projekt_id erfolgt serverseitig, sofern ein Filter
|
||||||
// gesetzt ist.
|
// gesetzt ist.
|
||||||
@@ -143,6 +159,10 @@ export const call_log_liste: m.ClosureComponent = (vnode) => {
|
|||||||
height: 'inherit',
|
height: 'inherit',
|
||||||
'overflow-y': 'inherit'}},
|
'overflow-y': 'inherit'}},
|
||||||
m("h1", projektName ? "Gesprächsnotizen für " + projektName : "Gesprächsnotizen"),
|
m("h1", projektName ? "Gesprächsnotizen für " + projektName : "Gesprächsnotizen"),
|
||||||
|
m(BreadcrumbBar, {
|
||||||
|
breadcrumbs: breadcrumbs,
|
||||||
|
current: filterProjektId !== null ? "Notizen: " + projektName : "Gesprächsnotizen"
|
||||||
|
} as iBreadcrumbBar),
|
||||||
m("div", {style: {flex: '1', 'overflow-y': 'inherit'}},
|
m("div", {style: {flex: '1', 'overflow-y': 'inherit'}},
|
||||||
m(Treetable,
|
m(Treetable,
|
||||||
{
|
{
|
||||||
@@ -219,6 +239,8 @@ export const Call_Log: m.ClosureComponent = (vnode) => {
|
|||||||
// null bedeutet ungefiltert; ein Wert bedeutet projektgefiltert.
|
// null bedeutet ungefiltert; ein Wert bedeutet projektgefiltert.
|
||||||
let returnProjektId: number | null = null
|
let returnProjektId: number | null = null
|
||||||
let returnProjektName: string = ""
|
let returnProjektName: string = ""
|
||||||
|
// Breadcrumb-Kette der Vorgänger-Ansichten (aus Route-Param bc).
|
||||||
|
let breadcrumbs: iBreadcrumb[] = []
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Formatiert verstrichene Sekunden als mm:ss.
|
* Formatiert verstrichene Sekunden als mm:ss.
|
||||||
@@ -240,6 +262,8 @@ export const Call_Log: m.ClosureComponent = (vnode) => {
|
|||||||
returnProjektId = parseInt(routeParams.return_projekt_id)
|
returnProjektId = parseInt(routeParams.return_projekt_id)
|
||||||
returnProjektName = routeParams.return_projekt_name || ""
|
returnProjektName = routeParams.return_projekt_name || ""
|
||||||
}
|
}
|
||||||
|
// Breadcrumb-Kette aus Route-Param parsen.
|
||||||
|
breadcrumbs = parseBc(routeParams && routeParams.bc)
|
||||||
if (id !== -1) {
|
if (id !== -1) {
|
||||||
// Gesprächsnotiz frisch vom Server holen.
|
// Gesprächsnotiz frisch vom Server holen.
|
||||||
fetchCallLogById(id).then((result) => {
|
fetchCallLogById(id).then((result) => {
|
||||||
@@ -334,6 +358,11 @@ export const Call_Log: m.ClosureComponent = (vnode) => {
|
|||||||
params.projekt_id = currentLog.projekt_id
|
params.projekt_id = currentLog.projekt_id
|
||||||
params.projekt_name = currentLog.projekt_name
|
params.projekt_name = currentLog.projekt_name
|
||||||
}
|
}
|
||||||
|
// Breadcrumb-Kette ohne das letzte Glied (die Notizliste
|
||||||
|
// selbst) an die Liste durchreichen, damit diese die
|
||||||
|
// Vorgänger-Ansichten wieder als Leiste anzeigen kann.
|
||||||
|
const listBc = serializeBc(breadcrumbs.slice(0, -1))
|
||||||
|
if (listBc) params.bc = listBc
|
||||||
m.route.set("/calllogliste", params)
|
m.route.set("/calllogliste", params)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -367,6 +396,10 @@ export const Call_Log: m.ClosureComponent = (vnode) => {
|
|||||||
}
|
}
|
||||||
return m("div.w3-container.w3-auto.form-view-container",
|
return m("div.w3-container.w3-auto.form-view-container",
|
||||||
m("h1", currentLog.id === -1 ? "Neue Gesprächsnotiz anlegen" : "Gesprächsnotiz"),
|
m("h1", currentLog.id === -1 ? "Neue Gesprächsnotiz anlegen" : "Gesprächsnotiz"),
|
||||||
|
m(BreadcrumbBar, {
|
||||||
|
breadcrumbs: breadcrumbs,
|
||||||
|
current: currentLog.id === -1 ? "Neue Gesprächsnotiz" : "Gesprächsnotiz"
|
||||||
|
} as iBreadcrumbBar),
|
||||||
m("div", {style: {flex: '1', 'min-height': '0'}},
|
m("div", {style: {flex: '1', 'min-height': '0'}},
|
||||||
m(Form, {showButtons: true, onsubmit: submit, oncancel: cancel} as iForm, [
|
m(Form, {showButtons: true, onsubmit: submit, oncancel: cancel} as iForm, [
|
||||||
m("input", {type: "hidden", name: "projekt_id", value: currentLog.projekt_id}),
|
m("input", {type: "hidden", name: "projekt_id", value: currentLog.projekt_id}),
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import m from "mithril"
|
|||||||
import {Form, iForm} from "./widgets/form"
|
import {Form, iForm} from "./widgets/form"
|
||||||
import { Input, iInput, Textarea, iTextarea} from "./widgets/inputs"
|
import { Input, iInput, Textarea, iTextarea} from "./widgets/inputs"
|
||||||
import {Treenode, iTreenode, Treetable, iTreetable, TopbarButton, iTopbarButton} from "./widgets/Treetable_V2"
|
import {Treenode, iTreenode, Treetable, iTreetable, TopbarButton, iTopbarButton} from "./widgets/Treetable_V2"
|
||||||
|
import {BreadcrumbBar, iBreadcrumb, iBreadcrumbBar, parseBc, serializeBc} from "./widgets/breadcrumb"
|
||||||
import {iKontakt, fetchKontakte, fetchKontaktById, createKontaktREST, updateKontaktREST} from "../models/kontakt"
|
import {iKontakt, fetchKontakte, fetchKontaktById, createKontaktREST, updateKontaktREST} from "../models/kontakt"
|
||||||
import {iKunde, fetchKundeById} from "../models/kunde"
|
import {iKunde, fetchKundeById} from "../models/kunde"
|
||||||
import {showServerError} from "./auth/authmessages"
|
import {showServerError} from "./auth/authmessages"
|
||||||
@@ -13,11 +14,20 @@ export const kontaktliste: m.ClosureComponent = (vnode) => {
|
|||||||
// Kundenname für die Überschrift, asynchron per fetchKundeById
|
// Kundenname für die Überschrift, asynchron per fetchKundeById
|
||||||
// aufgelöst, falls nicht in den Route-Params enthalten.
|
// aufgelöst, falls nicht in den Route-Params enthalten.
|
||||||
let kundenName: string = ""
|
let kundenName: string = ""
|
||||||
|
// Breadcrumb-Kette der Vorgänger-Ansichten (aus Route-Param bc).
|
||||||
|
let breadcrumbs: iBreadcrumb[] = []
|
||||||
|
|
||||||
const handleRowMarked = (node: iTreenode) => {
|
const handleRowMarked = (node: iTreenode) => {
|
||||||
selectedNode = node
|
selectedNode = node
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Eigener Listen-Crumb für die Breadcrumb-Kette.
|
||||||
|
const ownCrumb = (): iBreadcrumb => ({
|
||||||
|
route: "/kontaktliste",
|
||||||
|
params: filterKundenId !== null ? {kunden_id: filterKundenId} : {},
|
||||||
|
label: filterKundenId !== null ? "Kontakte: " + kundenName : "Kontakte"
|
||||||
|
})
|
||||||
|
|
||||||
const addKontaktRoute = () => {
|
const addKontaktRoute = () => {
|
||||||
const params: any = {id: -1}
|
const params: any = {id: -1}
|
||||||
// Nur bei kundengefilterter Liste den Kunden vorbelegen.
|
// Nur bei kundengefilterter Liste den Kunden vorbelegen.
|
||||||
@@ -25,12 +35,15 @@ export const kontaktliste: m.ClosureComponent = (vnode) => {
|
|||||||
params.kunden_id = filterKundenId
|
params.kunden_id = filterKundenId
|
||||||
params.kunden_name = kundenName
|
params.kunden_name = kundenName
|
||||||
}
|
}
|
||||||
|
// Breadcrumb-Kette um eigene Position erweitern.
|
||||||
|
params.bc = serializeBc([...breadcrumbs, ownCrumb()])
|
||||||
m.route.set("/kontakt", params)
|
m.route.set("/kontakt", params)
|
||||||
}
|
}
|
||||||
|
|
||||||
const editKontakt = (kontakt: iKontakt, e: Event) => {
|
const editKontakt = (kontakt: iKontakt, e: Event) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
m.route.set("/kontakt", {id: kontakt.id})
|
// Breadcrumb-Kette um eigene Position erweitern.
|
||||||
|
m.route.set("/kontakt", {id: kontakt.id, bc: serializeBc([...breadcrumbs, ownCrumb()])})
|
||||||
}
|
}
|
||||||
|
|
||||||
const columnmapper: (node: iTreenode) => any[] = (node: iTreenode) => {
|
const columnmapper: (node: iTreenode) => any[] = (node: iTreenode) => {
|
||||||
@@ -66,6 +79,8 @@ export const kontaktliste: m.ClosureComponent = (vnode) => {
|
|||||||
filterKundenId = null
|
filterKundenId = null
|
||||||
kundenName = ""
|
kundenName = ""
|
||||||
}
|
}
|
||||||
|
// Breadcrumb-Kette aus Route-Param parsen.
|
||||||
|
breadcrumbs = parseBc(routeParams && routeParams.bc)
|
||||||
// Kontakte asynchron vom Server laden. Die Filterung nach
|
// Kontakte asynchron vom Server laden. Die Filterung nach
|
||||||
// kunden_id erfolgt serverseitig, sofern ein Filter gesetzt ist.
|
// kunden_id erfolgt serverseitig, sofern ein Filter gesetzt ist.
|
||||||
fetchKontakte(filterKundenId !== null ? filterKundenId : undefined).then((result) => {
|
fetchKontakte(filterKundenId !== null ? filterKundenId : undefined).then((result) => {
|
||||||
@@ -86,6 +101,10 @@ export const kontaktliste: m.ClosureComponent = (vnode) => {
|
|||||||
height: 'inherit',
|
height: 'inherit',
|
||||||
'overflow-y': 'inherit'}},
|
'overflow-y': 'inherit'}},
|
||||||
m("h1", kundenName ? "Kontaktliste für " + kundenName : "Kontaktliste"),
|
m("h1", kundenName ? "Kontaktliste für " + kundenName : "Kontaktliste"),
|
||||||
|
m(BreadcrumbBar, {
|
||||||
|
breadcrumbs: breadcrumbs,
|
||||||
|
current: filterKundenId !== null ? "Kontakte: " + kundenName : "Kontakte"
|
||||||
|
} as iBreadcrumbBar),
|
||||||
m("div", {style: {flex: '1', 'overflow-y': 'inherit'}},
|
m("div", {style: {flex: '1', 'overflow-y': 'inherit'}},
|
||||||
m(Treetable,
|
m(Treetable,
|
||||||
{
|
{
|
||||||
@@ -129,11 +148,15 @@ export const Kontakt: m.ClosureComponent = (vnode) => {
|
|||||||
email: "",
|
email: "",
|
||||||
notiz: ""
|
notiz: ""
|
||||||
}
|
}
|
||||||
|
// Breadcrumb-Kette der Vorgänger-Ansichten (aus Route-Param bc).
|
||||||
|
let breadcrumbs: iBreadcrumb[] = []
|
||||||
|
|
||||||
return {
|
return {
|
||||||
oninit: (vnode) => {
|
oninit: (vnode) => {
|
||||||
const routeParams = m.route.param()
|
const routeParams = m.route.param()
|
||||||
const id = routeParams && routeParams.id ? parseInt(routeParams.id) : -1
|
const id = routeParams && routeParams.id ? parseInt(routeParams.id) : -1
|
||||||
|
// Breadcrumb-Kette aus Route-Param parsen.
|
||||||
|
breadcrumbs = parseBc(routeParams && routeParams.bc)
|
||||||
if (id !== -1) {
|
if (id !== -1) {
|
||||||
// Kontaktdaten frisch vom Server holen, um zwischenzeitliche
|
// Kontaktdaten frisch vom Server holen, um zwischenzeitliche
|
||||||
// Änderungen anderer Nutzer nicht zu überschreiben.
|
// Änderungen anderer Nutzer nicht zu überschreiben.
|
||||||
@@ -148,6 +171,14 @@ export const Kontakt: m.ClosureComponent = (vnode) => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
view: (vnode) => {
|
view: (vnode) => {
|
||||||
|
// Rückkehr zur Kontaktliste; das bc ohne das letzte Glied (die
|
||||||
|
// Kontaktliste selbst) durchreichen.
|
||||||
|
const goBack = () => {
|
||||||
|
const listBc = serializeBc(breadcrumbs.slice(0, -1))
|
||||||
|
const routeParams: any = {kunden_id: currentKontakt.kunden_id, kunden_name: currentKontakt.kunden_name}
|
||||||
|
if (listBc) routeParams.bc = listBc
|
||||||
|
m.route.set("/kontaktliste", routeParams)
|
||||||
|
}
|
||||||
const submit = (form: HTMLFormElement): boolean => {
|
const submit = (form: HTMLFormElement): boolean => {
|
||||||
const formData = new FormData(form)
|
const formData = new FormData(form)
|
||||||
const gespeicherterKontakt: iKontakt = {
|
const gespeicherterKontakt: iKontakt = {
|
||||||
@@ -162,19 +193,23 @@ export const Kontakt: m.ClosureComponent = (vnode) => {
|
|||||||
notiz: (formData.get("notiz") as string) || ""
|
notiz: (formData.get("notiz") as string) || ""
|
||||||
}
|
}
|
||||||
if (currentKontakt.id === -1) {
|
if (currentKontakt.id === -1) {
|
||||||
createKontaktREST(gespeicherterKontakt).then(() => m.route.set("/kontaktliste", {kunden_id: currentKontakt.kunden_id, kunden_name: currentKontakt.kunden_name})).catch((e) => showServerError(e))
|
createKontaktREST(gespeicherterKontakt).then(() => goBack()).catch((e) => showServerError(e))
|
||||||
} else {
|
} else {
|
||||||
updateKontaktREST(gespeicherterKontakt).then(() => m.route.set("/kontaktliste", {kunden_id: currentKontakt.kunden_id, kunden_name: currentKontakt.kunden_name})).catch((e) => showServerError(e))
|
updateKontaktREST(gespeicherterKontakt).then(() => goBack()).catch((e) => showServerError(e))
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
const cancel = (e: Event) => {
|
const cancel = (e: Event) => {
|
||||||
m.route.set("/kontaktliste", {kunden_id: currentKontakt.kunden_id, kunden_name: currentKontakt.kunden_name})
|
goBack()
|
||||||
}
|
}
|
||||||
return m("div.w3-container.w3-auto.form-view-container",
|
return m("div.w3-container.w3-auto.form-view-container",
|
||||||
m("h1", currentKontakt.id === -1
|
m("h1", currentKontakt.id === -1
|
||||||
? "Neuer Kontakt für " + currentKontakt.kunden_name
|
? "Neuer Kontakt für " + currentKontakt.kunden_name
|
||||||
: "Kontakt-Stammblatt"),
|
: "Kontakt-Stammblatt"),
|
||||||
|
m(BreadcrumbBar, {
|
||||||
|
breadcrumbs: breadcrumbs,
|
||||||
|
current: currentKontakt.id === -1 ? "Neuer Kontakt" : "Kontakt-Stammblatt"
|
||||||
|
} as iBreadcrumbBar),
|
||||||
m("div", {style: {flex: '1', 'min-height': '0'}},
|
m("div", {style: {flex: '1', 'min-height': '0'}},
|
||||||
m(Form, {showButtons: true, onsubmit: submit, oncancel: cancel} as iForm, [
|
m(Form, {showButtons: true, onsubmit: submit, oncancel: cancel} as iForm, [
|
||||||
m("input", {type: "hidden", name: "kunden_id", value: currentKontakt.kunden_id}),
|
m("input", {type: "hidden", name: "kunden_id", value: currentKontakt.kunden_id}),
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import m from "mithril"
|
|||||||
import {Form, iForm} from "./widgets/form"
|
import {Form, iForm} from "./widgets/form"
|
||||||
import { Input, iInput, Textarea, iTextarea} from "./widgets/inputs"
|
import { Input, iInput, Textarea, iTextarea} from "./widgets/inputs"
|
||||||
import {Treenode, iTreenode, Treetable, iTreetable, TopbarButton, iTopbarButton} from "./widgets/Treetable_V2"
|
import {Treenode, iTreenode, Treetable, iTreetable, TopbarButton, iTopbarButton} from "./widgets/Treetable_V2"
|
||||||
|
import {BreadcrumbBar, iBreadcrumb, iBreadcrumbBar, parseBc, serializeBc} from "./widgets/breadcrumb"
|
||||||
import {iKontaktadresse, fetchKontaktadressen, fetchKontaktadresseById, createKontaktadresseREST, updateKontaktadresseREST} from "../models/kontaktadresse"
|
import {iKontaktadresse, fetchKontaktadressen, fetchKontaktadresseById, createKontaktadresseREST, updateKontaktadresseREST} from "../models/kontaktadresse"
|
||||||
import {iProjekt, fetchProjektById} from "../models/projekt"
|
import {iProjekt, fetchProjektById} from "../models/projekt"
|
||||||
import {showServerError} from "./auth/authmessages"
|
import {showServerError} from "./auth/authmessages"
|
||||||
@@ -13,23 +14,35 @@ export const kontaktadresseliste: m.ClosureComponent = (vnode) => {
|
|||||||
// Projektname für die Überschrift, asynchron per fetchProjektById
|
// Projektname für die Überschrift, asynchron per fetchProjektById
|
||||||
// aufgelöst, falls nicht in den Route-Params enthalten.
|
// aufgelöst, falls nicht in den Route-Params enthalten.
|
||||||
let projektName: string = ""
|
let projektName: string = ""
|
||||||
|
// Breadcrumb-Kette der Vorgänger-Ansichten (aus Route-Param bc).
|
||||||
|
let breadcrumbs: iBreadcrumb[] = []
|
||||||
|
|
||||||
const handleRowMarked = (node: iTreenode) => {
|
const handleRowMarked = (node: iTreenode) => {
|
||||||
selectedNode = node
|
selectedNode = node
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Eigener Listen-Crumb für die Breadcrumb-Kette.
|
||||||
|
const ownCrumb = (): iBreadcrumb => ({
|
||||||
|
route: "/kontaktadresseliste",
|
||||||
|
params: filterProjektId !== null ? {projekt_id: filterProjektId} : {},
|
||||||
|
label: filterProjektId !== null ? "Kontaktadressen: " + projektName : "Kontaktadressen"
|
||||||
|
})
|
||||||
|
|
||||||
const addKontaktadresseRoute = () => {
|
const addKontaktadresseRoute = () => {
|
||||||
const params: any = {id: -1}
|
const params: any = {id: -1}
|
||||||
if (filterProjektId !== null) {
|
if (filterProjektId !== null) {
|
||||||
params.projekt_id = filterProjektId
|
params.projekt_id = filterProjektId
|
||||||
params.projekt_name = projektName
|
params.projekt_name = projektName
|
||||||
}
|
}
|
||||||
|
// Breadcrumb-Kette um eigene Position erweitern.
|
||||||
|
params.bc = serializeBc([...breadcrumbs, ownCrumb()])
|
||||||
m.route.set("/kontaktadresse", params)
|
m.route.set("/kontaktadresse", params)
|
||||||
}
|
}
|
||||||
|
|
||||||
const editKontaktadresse = (kontaktadresse: iKontaktadresse, e: Event) => {
|
const editKontaktadresse = (kontaktadresse: iKontaktadresse, e: Event) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
m.route.set("/kontaktadresse", {id: kontaktadresse.id})
|
// Breadcrumb-Kette um eigene Position erweitern.
|
||||||
|
m.route.set("/kontaktadresse", {id: kontaktadresse.id, bc: serializeBc([...breadcrumbs, ownCrumb()])})
|
||||||
}
|
}
|
||||||
|
|
||||||
const addCallLog = (kontaktadresse: iKontaktadresse, e: Event) => {
|
const addCallLog = (kontaktadresse: iKontaktadresse, e: Event) => {
|
||||||
@@ -37,6 +50,14 @@ export const kontaktadresseliste: m.ClosureComponent = (vnode) => {
|
|||||||
const now = new Date()
|
const now = new Date()
|
||||||
const datum = now.toISOString().split('T')[0]
|
const datum = now.toISOString().split('T')[0]
|
||||||
const uhrzeit = now.toTimeString().split(':')[0] + ':' + now.toTimeString().split(':')[1]
|
const uhrzeit = now.toTimeString().split(':')[0] + ':' + now.toTimeString().split(':')[1]
|
||||||
|
// Breadcrumb-Kette um Kontaktadressenliste und Notizliste
|
||||||
|
// erweitern, da die Maske nach Speichern/Abbrechen in die
|
||||||
|
// projektgefilterte Notizliste zurückkehrt.
|
||||||
|
const chain = [...breadcrumbs, ownCrumb(), {
|
||||||
|
route: "/calllogliste",
|
||||||
|
params: {projekt_id: kontaktadresse.projekt_id},
|
||||||
|
label: "Notizen: " + kontaktadresse.projekt_name
|
||||||
|
} as iBreadcrumb]
|
||||||
m.route.set("/call_log", {
|
m.route.set("/call_log", {
|
||||||
id: -1,
|
id: -1,
|
||||||
projekt_id: kontaktadresse.projekt_id,
|
projekt_id: kontaktadresse.projekt_id,
|
||||||
@@ -49,7 +70,8 @@ export const kontaktadresseliste: m.ClosureComponent = (vnode) => {
|
|||||||
telefon: kontaktadresse.telefon,
|
telefon: kontaktadresse.telefon,
|
||||||
notiz: "",
|
notiz: "",
|
||||||
wiedervorlage_datum: "",
|
wiedervorlage_datum: "",
|
||||||
erledigt: false
|
erledigt: false,
|
||||||
|
bc: serializeBc(chain)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,6 +114,8 @@ export const kontaktadresseliste: m.ClosureComponent = (vnode) => {
|
|||||||
filterProjektId = null
|
filterProjektId = null
|
||||||
projektName = ""
|
projektName = ""
|
||||||
}
|
}
|
||||||
|
// Breadcrumb-Kette aus Route-Param parsen.
|
||||||
|
breadcrumbs = parseBc(routeParams && routeParams.bc)
|
||||||
// Kontaktadressen asynchron vom Server laden. Die Filterung
|
// Kontaktadressen asynchron vom Server laden. Die Filterung
|
||||||
// nach projekt_id erfolgt serverseitig, sofern ein Filter
|
// nach projekt_id erfolgt serverseitig, sofern ein Filter
|
||||||
// gesetzt ist.
|
// gesetzt ist.
|
||||||
@@ -113,6 +137,10 @@ export const kontaktadresseliste: m.ClosureComponent = (vnode) => {
|
|||||||
height: 'inherit',
|
height: 'inherit',
|
||||||
'overflow-y': 'inherit'}},
|
'overflow-y': 'inherit'}},
|
||||||
m("h1", projektName ? "Kontaktadressen für " + projektName : "Kontaktadressen"),
|
m("h1", projektName ? "Kontaktadressen für " + projektName : "Kontaktadressen"),
|
||||||
|
m(BreadcrumbBar, {
|
||||||
|
breadcrumbs: breadcrumbs,
|
||||||
|
current: filterProjektId !== null ? "Kontaktadressen: " + projektName : "Kontaktadressen"
|
||||||
|
} as iBreadcrumbBar),
|
||||||
m("div", {style: {flex: '1', 'overflow-y': 'inherit'}},
|
m("div", {style: {flex: '1', 'overflow-y': 'inherit'}},
|
||||||
m(Treetable,
|
m(Treetable,
|
||||||
{
|
{
|
||||||
@@ -166,11 +194,15 @@ export const Kontaktadresse: m.ClosureComponent = (vnode) => {
|
|||||||
land: "",
|
land: "",
|
||||||
notiz: ""
|
notiz: ""
|
||||||
}
|
}
|
||||||
|
// Breadcrumb-Kette der Vorgänger-Ansichten (aus Route-Param bc).
|
||||||
|
let breadcrumbs: iBreadcrumb[] = []
|
||||||
|
|
||||||
return {
|
return {
|
||||||
oninit: (vnode) => {
|
oninit: (vnode) => {
|
||||||
const routeParams = m.route.param()
|
const routeParams = m.route.param()
|
||||||
const id = routeParams && routeParams.id ? parseInt(routeParams.id) : -1
|
const id = routeParams && routeParams.id ? parseInt(routeParams.id) : -1
|
||||||
|
// Breadcrumb-Kette aus Route-Param parsen.
|
||||||
|
breadcrumbs = parseBc(routeParams && routeParams.bc)
|
||||||
if (id !== -1) {
|
if (id !== -1) {
|
||||||
// Kontaktadresse frisch vom Server holen.
|
// Kontaktadresse frisch vom Server holen.
|
||||||
fetchKontaktadresseById(id).then((result) => {
|
fetchKontaktadresseById(id).then((result) => {
|
||||||
@@ -183,6 +215,14 @@ export const Kontaktadresse: m.ClosureComponent = (vnode) => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
view: (vnode) => {
|
view: (vnode) => {
|
||||||
|
// Rückkehr zur Kontaktadressenliste; das bc ohne das letzte
|
||||||
|
// Glied (die Kontaktadressenliste selbst) durchreichen.
|
||||||
|
const goBack = () => {
|
||||||
|
const listBc = serializeBc(breadcrumbs.slice(0, -1))
|
||||||
|
const routeParams: any = {projekt_id: currentKontaktadresse.projekt_id, projekt_name: currentKontaktadresse.projekt_name}
|
||||||
|
if (listBc) routeParams.bc = listBc
|
||||||
|
m.route.set("/kontaktadresseliste", routeParams)
|
||||||
|
}
|
||||||
const submit = (form: HTMLFormElement): boolean => {
|
const submit = (form: HTMLFormElement): boolean => {
|
||||||
const formData = new FormData(form)
|
const formData = new FormData(form)
|
||||||
const gespeicherteKontaktadresse: iKontaktadresse = {
|
const gespeicherteKontaktadresse: iKontaktadresse = {
|
||||||
@@ -202,19 +242,25 @@ export const Kontaktadresse: m.ClosureComponent = (vnode) => {
|
|||||||
notiz: (formData.get("notiz") as string) || ""
|
notiz: (formData.get("notiz") as string) || ""
|
||||||
}
|
}
|
||||||
if (currentKontaktadresse.id === -1) {
|
if (currentKontaktadresse.id === -1) {
|
||||||
createKontaktadresseREST(gespeicherteKontaktadresse).then(() => m.route.set("/kontaktadresseliste", {projekt_id: currentKontaktadresse.projekt_id, projekt_name: currentKontaktadresse.projekt_name})).catch((e) => showServerError(e))
|
createKontaktadresseREST(gespeicherteKontaktadresse).then(() => goBack()).catch((e) => showServerError(e))
|
||||||
} else {
|
} else {
|
||||||
updateKontaktadresseREST(gespeicherteKontaktadresse).then(() => m.route.set("/kontaktadresseliste", {projekt_id: currentKontaktadresse.projekt_id, projekt_name: currentKontaktadresse.projekt_name})).catch((e) => showServerError(e))
|
updateKontaktadresseREST(gespeicherteKontaktadresse).then(() => goBack()).catch((e) => showServerError(e))
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
const cancel = (e: Event) => {
|
const cancel = (e: Event) => {
|
||||||
m.route.set("/kontaktadresseliste", {projekt_id: currentKontaktadresse.projekt_id, projekt_name: currentKontaktadresse.projekt_name})
|
goBack()
|
||||||
}
|
}
|
||||||
return m("div.w3-container.w3-auto.form-view-container",
|
return m("div.w3-container.w3-auto.form-view-container",
|
||||||
m("h1", currentKontaktadresse.id === -1
|
m("h1", currentKontaktadresse.id === -1
|
||||||
? "Neue Kontaktadresse für " + currentKontaktadresse.projekt_name
|
? "Neue Kontaktadresse für " + currentKontaktadresse.projekt_name
|
||||||
: "Kontaktadresse-Stammblatt"),
|
: "Kontaktadresse-Stammblatt"),
|
||||||
|
m(BreadcrumbBar, {
|
||||||
|
breadcrumbs: breadcrumbs,
|
||||||
|
current: currentKontaktadresse.id === -1
|
||||||
|
? "Neue Kontaktadresse"
|
||||||
|
: "Kontaktadresse-Stammblatt"
|
||||||
|
} as iBreadcrumbBar),
|
||||||
m("div", {style: {flex: '1', 'min-height': '0'}},
|
m("div", {style: {flex: '1', 'min-height': '0'}},
|
||||||
m(Form, {showButtons: true, onsubmit: submit, oncancel: cancel} as iForm, [
|
m(Form, {showButtons: true, onsubmit: submit, oncancel: cancel} as iForm, [
|
||||||
m("input", {type: "hidden", name: "projekt_id", value: currentKontaktadresse.projekt_id}),
|
m("input", {type: "hidden", name: "projekt_id", value: currentKontaktadresse.projekt_id}),
|
||||||
|
|||||||
+41
-11
@@ -2,6 +2,7 @@ import m from "mithril"
|
|||||||
import {Form, iForm} from "./widgets/form"
|
import {Form, iForm} from "./widgets/form"
|
||||||
import { Input, iInput} from "./widgets/inputs"
|
import { Input, iInput} from "./widgets/inputs"
|
||||||
import {Treenode, iTreenode, Treetable, iTreetable, TopbarButton, iTopbarButton} from "./widgets/Treetable_V2"
|
import {Treenode, iTreenode, Treetable, iTreetable, TopbarButton, iTopbarButton} from "./widgets/Treetable_V2"
|
||||||
|
import {BreadcrumbBar, iBreadcrumb, iBreadcrumbBar, parseBc, serializeBc} from "./widgets/breadcrumb"
|
||||||
import {iKunde, fetchKunden, fetchKundeById, createKundeREST, updateKundeREST} from "../models/kunde"
|
import {iKunde, fetchKunden, fetchKundeById, createKundeREST, updateKundeREST} from "../models/kunde"
|
||||||
import {showServerError} from "./auth/authmessages"
|
import {showServerError} from "./auth/authmessages"
|
||||||
|
|
||||||
@@ -14,11 +15,15 @@ export const kunde: m.ClosureComponent = (vnode) => {
|
|||||||
ort: "",
|
ort: "",
|
||||||
land: ""
|
land: ""
|
||||||
}
|
}
|
||||||
|
// Breadcrumb-Kette der Vorgänger-Ansichten (aus Route-Param bc).
|
||||||
|
let breadcrumbs: iBreadcrumb[] = []
|
||||||
|
|
||||||
return {
|
return {
|
||||||
oninit: (vnode) => {
|
oninit: (vnode) => {
|
||||||
const routeParams = m.route.param()
|
const routeParams = m.route.param()
|
||||||
const id = routeParams && routeParams.id ? parseInt(routeParams.id) : -1
|
const id = routeParams && routeParams.id ? parseInt(routeParams.id) : -1
|
||||||
|
// Breadcrumb-Kette aus Route-Param parsen.
|
||||||
|
breadcrumbs = parseBc(routeParams && routeParams.bc)
|
||||||
if (id !== -1) {
|
if (id !== -1) {
|
||||||
// Kundendaten frisch vom Server holen, um zwischenzeitliche
|
// Kundendaten frisch vom Server holen, um zwischenzeitliche
|
||||||
// Änderungen anderer Nutzer nicht zu überschreiben.
|
// Änderungen anderer Nutzer nicht zu überschreiben.
|
||||||
@@ -57,6 +62,10 @@ export const kunde: m.ClosureComponent = (vnode) => {
|
|||||||
m("i.material-icons", "contact_page"), " Kontakte")
|
m("i.material-icons", "contact_page"), " Kontakte")
|
||||||
: null
|
: null
|
||||||
),
|
),
|
||||||
|
m(BreadcrumbBar, {
|
||||||
|
breadcrumbs: breadcrumbs,
|
||||||
|
current: currentKunde.id === -1 ? "Neuer Kunde" : "Kundenstammblatt"
|
||||||
|
} as iBreadcrumbBar),
|
||||||
m("div", {style: {flex: '1', 'min-height': '0'}},
|
m("div", {style: {flex: '1', 'min-height': '0'}},
|
||||||
m(Form, {showButtons: true, onsubmit: submit, oncancel: cancel} as iForm, [
|
m(Form, {showButtons: true, onsubmit: submit, oncancel: cancel} as iForm, [
|
||||||
m("label", "Kundenname", m(Input, {name: "kundenname", value: currentKunde.name, type: "text", required: true} as iInput)),
|
m("label", "Kundenname", m(Input, {name: "kundenname", value: currentKunde.name, type: "text", required: true} as iInput)),
|
||||||
@@ -74,28 +83,43 @@ export const kunde: m.ClosureComponent = (vnode) => {
|
|||||||
export const kundenliste: m.ClosureComponent = (vnode) => {
|
export const kundenliste: m.ClosureComponent = (vnode) => {
|
||||||
let treenodes: iTreenode[] = []
|
let treenodes: iTreenode[] = []
|
||||||
let selectedNode: iTreenode | null = null
|
let selectedNode: iTreenode | null = null
|
||||||
|
// Breadcrumb-Kette der Vorgänger-Ansichten (aus Route-Param bc).
|
||||||
|
let breadcrumbs: iBreadcrumb[] = []
|
||||||
|
|
||||||
const handleRowMarked = (node: iTreenode) => {
|
const handleRowMarked = (node: iTreenode) => {
|
||||||
selectedNode = node
|
selectedNode = node
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Eigener Listen-Crumb für die Breadcrumb-Kette.
|
||||||
|
const ownCrumb = (): iBreadcrumb => ({
|
||||||
|
route: "/kundenliste",
|
||||||
|
params: {},
|
||||||
|
label: "Kunden"
|
||||||
|
})
|
||||||
|
|
||||||
const editKunde = (kunde: iKunde, e: Event) => {
|
const editKunde = (kunde: iKunde, e: Event) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
m.route.set("/kunde", {id: kunde.id})
|
// Breadcrumb-Kette um eigene Position erweitern.
|
||||||
|
m.route.set("/kunde", {id: kunde.id, bc: serializeBc([...breadcrumbs, ownCrumb()])})
|
||||||
}
|
}
|
||||||
|
|
||||||
const showProjekte = (kunde: iKunde, e: Event) => {
|
const showProjekte = (kunde: iKunde, e: Event) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
m.route.set("/projektliste", {kunden_id: kunde.id})
|
// Breadcrumb-Kette: Kundenliste als Vorgänger der Projektliste.
|
||||||
|
const bc = serializeBc([...breadcrumbs, ownCrumb()])
|
||||||
|
m.route.set("/projektliste", {kunden_id: kunde.id, bc})
|
||||||
}
|
}
|
||||||
|
|
||||||
const showKontakte = (kunde: iKunde, e: Event) => {
|
const showKontakte = (kunde: iKunde, e: Event) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
m.route.set("/kontaktliste", {kunden_id: kunde.id})
|
// Breadcrumb-Kette: Kundenliste als Vorgänger der Kontaktliste.
|
||||||
|
const bc = serializeBc([...breadcrumbs, ownCrumb()])
|
||||||
|
m.route.set("/kontaktliste", {kunden_id: kunde.id, bc})
|
||||||
}
|
}
|
||||||
|
|
||||||
const addKundeRoute = () => {
|
const addKundeRoute = () => {
|
||||||
m.route.set("/kunde", {id: -1})
|
// Breadcrumb-Kette um eigene Position erweitern.
|
||||||
|
m.route.set("/kunde", {id: -1, bc: serializeBc([...breadcrumbs, ownCrumb()])})
|
||||||
}
|
}
|
||||||
|
|
||||||
const columnmapper: (node: iTreenode) => any[] = (node: iTreenode) => {
|
const columnmapper: (node: iTreenode) => any[] = (node: iTreenode) => {
|
||||||
@@ -127,6 +151,8 @@ export const kundenliste: m.ClosureComponent = (vnode) => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
oninit: (vnode) => {
|
oninit: (vnode) => {
|
||||||
|
// Breadcrumb-Kette aus Route-Param parsen (bei Top-Level leer).
|
||||||
|
breadcrumbs = parseBc(m.route.param() && m.route.param().bc)
|
||||||
// Kundenliste asynchron vom Server laden. m.request löst nach
|
// Kundenliste asynchron vom Server laden. m.request löst nach
|
||||||
// Promise-Auflösung automatisch ein Redraw aus.
|
// Promise-Auflösung automatisch ein Redraw aus.
|
||||||
fetchKunden().then((kunden: iKunde[]) => {
|
fetchKunden().then((kunden: iKunde[]) => {
|
||||||
@@ -136,11 +162,15 @@ export const kundenliste: m.ClosureComponent = (vnode) => {
|
|||||||
view: (vnode) => {
|
view: (vnode) => {
|
||||||
return m("div.w3-container.w3-auto", {
|
return m("div.w3-container.w3-auto", {
|
||||||
style: {
|
style: {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
'flex-direction': 'column',
|
'flex-direction': 'column',
|
||||||
height: 'inherit',
|
height: 'inherit',
|
||||||
'overflow-y': 'inherit'}},
|
'overflow-y': 'inherit'}},
|
||||||
m("h1", "Kundenliste"),
|
m("h1", "Kundenliste"),
|
||||||
|
m(BreadcrumbBar, {
|
||||||
|
breadcrumbs: breadcrumbs,
|
||||||
|
current: "Kunden"
|
||||||
|
} as iBreadcrumbBar),
|
||||||
m("div", {style: {flex: '1', 'overflow-y': 'inherit'}},
|
m("div", {style: {flex: '1', 'overflow-y': 'inherit'}},
|
||||||
m(Treetable,
|
m(Treetable,
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import m from "mithril"
|
|||||||
import {Form, iForm} from "./widgets/form"
|
import {Form, iForm} from "./widgets/form"
|
||||||
import { Input, iInput} from "./widgets/inputs"
|
import { Input, iInput} from "./widgets/inputs"
|
||||||
import {Treenode, iTreenode, Treetable, iTreetable, TopbarButton, iTopbarButton} from "./widgets/Treetable_V2"
|
import {Treenode, iTreenode, Treetable, iTreetable, TopbarButton, iTopbarButton} from "./widgets/Treetable_V2"
|
||||||
|
import {BreadcrumbBar, iBreadcrumb, iBreadcrumbBar, parseBc, serializeBc} from "./widgets/breadcrumb"
|
||||||
import {iProjekt, fetchProjekte, fetchProjektById, createProjektREST, updateProjektREST} from "../models/projekt"
|
import {iProjekt, fetchProjekte, fetchProjektById, createProjektREST, updateProjektREST} from "../models/projekt"
|
||||||
import {iKunde, fetchKundeById} from "../models/kunde"
|
import {iKunde, fetchKundeById} from "../models/kunde"
|
||||||
import {showServerError} from "./auth/authmessages"
|
import {showServerError} from "./auth/authmessages"
|
||||||
@@ -13,11 +14,20 @@ export const projektliste: m.ClosureComponent = (vnode) => {
|
|||||||
// Kundenname für die Überschrift, asynchron per fetchKundeById
|
// Kundenname für die Überschrift, asynchron per fetchKundeById
|
||||||
// aufgelöst, falls nicht in den Route-Params enthalten.
|
// aufgelöst, falls nicht in den Route-Params enthalten.
|
||||||
let kundenName: string = ""
|
let kundenName: string = ""
|
||||||
|
// Breadcrumb-Kette der Vorgänger-Ansichten (aus Route-Param bc).
|
||||||
|
let breadcrumbs: iBreadcrumb[] = []
|
||||||
|
|
||||||
const handleRowMarked = (node: iTreenode) => {
|
const handleRowMarked = (node: iTreenode) => {
|
||||||
selectedNode = node
|
selectedNode = node
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Eigener Listen-Crumb für die Breadcrumb-Kette.
|
||||||
|
const ownCrumb = (): iBreadcrumb => ({
|
||||||
|
route: "/projektliste",
|
||||||
|
params: filterKundenId !== null ? {kunden_id: filterKundenId, kunden_name: kundenName} : {},
|
||||||
|
label: filterKundenId !== null ? "Projekte: " + kundenName : "Projekte"
|
||||||
|
})
|
||||||
|
|
||||||
const addProjektRoute = () => {
|
const addProjektRoute = () => {
|
||||||
const params: any = {id: -1}
|
const params: any = {id: -1}
|
||||||
// Nur bei kundengefilterter Liste den Kunden vorbelegen.
|
// Nur bei kundengefilterter Liste den Kunden vorbelegen.
|
||||||
@@ -25,22 +35,27 @@ export const projektliste: m.ClosureComponent = (vnode) => {
|
|||||||
params.kunden_id = filterKundenId
|
params.kunden_id = filterKundenId
|
||||||
params.kunden_name = kundenName
|
params.kunden_name = kundenName
|
||||||
}
|
}
|
||||||
|
// Breadcrumb-Kette um eigene Position erweitern.
|
||||||
|
params.bc = serializeBc([...breadcrumbs, ownCrumb()])
|
||||||
m.route.set("/projekt", params)
|
m.route.set("/projekt", params)
|
||||||
}
|
}
|
||||||
|
|
||||||
const editProjekt = (projekt: iProjekt, e: Event) => {
|
const editProjekt = (projekt: iProjekt, e: Event) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
m.route.set("/projekt", {id: projekt.id})
|
// Breadcrumb-Kette um eigene Position erweitern.
|
||||||
|
m.route.set("/projekt", {id: projekt.id, bc: serializeBc([...breadcrumbs, ownCrumb()])})
|
||||||
}
|
}
|
||||||
|
|
||||||
const showCallLogs = (projekt: iProjekt, e: Event) => {
|
const showCallLogs = (projekt: iProjekt, e: Event) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
m.route.set("/calllogliste", {projekt_id: projekt.id})
|
// Breadcrumb-Kette um eigene Position erweitern.
|
||||||
|
m.route.set("/calllogliste", {projekt_id: projekt.id, bc: serializeBc([...breadcrumbs, ownCrumb()])})
|
||||||
}
|
}
|
||||||
|
|
||||||
const showKontaktadressen = (projekt: iProjekt, e: Event) => {
|
const showKontaktadressen = (projekt: iProjekt, e: Event) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
m.route.set("/kontaktadresseliste", {projekt_id: projekt.id})
|
// Breadcrumb-Kette um eigene Position erweitern.
|
||||||
|
m.route.set("/kontaktadresseliste", {projekt_id: projekt.id, bc: serializeBc([...breadcrumbs, ownCrumb()])})
|
||||||
}
|
}
|
||||||
|
|
||||||
const addCallLog = (projekt: iProjekt, e: Event) => {
|
const addCallLog = (projekt: iProjekt, e: Event) => {
|
||||||
@@ -48,6 +63,14 @@ export const projektliste: m.ClosureComponent = (vnode) => {
|
|||||||
const now = new Date()
|
const now = new Date()
|
||||||
const datum = now.toISOString().split('T')[0]
|
const datum = now.toISOString().split('T')[0]
|
||||||
const uhrzeit = now.toTimeString().split(':')[0] + ':' + now.toTimeString().split(':')[1]
|
const uhrzeit = now.toTimeString().split(':')[0] + ':' + now.toTimeString().split(':')[1]
|
||||||
|
// Breadcrumb-Kette um Projektliste und Notizliste erweitern, da
|
||||||
|
// die Maske nach Speichern/Abbrechen in die projektgefilterte
|
||||||
|
// Notizliste zurückkehrt.
|
||||||
|
const chain = [...breadcrumbs, ownCrumb(), {
|
||||||
|
route: "/calllogliste",
|
||||||
|
params: {projekt_id: projekt.id},
|
||||||
|
label: "Notizen: " + projekt.projektname
|
||||||
|
} as iBreadcrumb]
|
||||||
m.route.set("/call_log", {
|
m.route.set("/call_log", {
|
||||||
id: -1,
|
id: -1,
|
||||||
kunden_name: projekt.kunden_name,
|
kunden_name: projekt.kunden_name,
|
||||||
@@ -61,7 +84,8 @@ export const projektliste: m.ClosureComponent = (vnode) => {
|
|||||||
telefon: "",
|
telefon: "",
|
||||||
notiz: "",
|
notiz: "",
|
||||||
wiedervorlage_datum: "",
|
wiedervorlage_datum: "",
|
||||||
erledigt: false
|
erledigt: false,
|
||||||
|
bc: serializeBc(chain)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,6 +127,8 @@ export const projektliste: m.ClosureComponent = (vnode) => {
|
|||||||
filterKundenId = null
|
filterKundenId = null
|
||||||
kundenName = ""
|
kundenName = ""
|
||||||
}
|
}
|
||||||
|
// Breadcrumb-Kette aus Route-Param parsen.
|
||||||
|
breadcrumbs = parseBc(routeParams && routeParams.bc)
|
||||||
// Projekte asynchron vom Server laden. Die Filterung nach
|
// Projekte asynchron vom Server laden. Die Filterung nach
|
||||||
// kunden_id erfolgt serverseitig, sofern ein Filter gesetzt ist.
|
// kunden_id erfolgt serverseitig, sofern ein Filter gesetzt ist.
|
||||||
fetchProjekte(filterKundenId !== null ? filterKundenId : undefined).then((result) => {
|
fetchProjekte(filterKundenId !== null ? filterKundenId : undefined).then((result) => {
|
||||||
@@ -123,6 +149,10 @@ export const projektliste: m.ClosureComponent = (vnode) => {
|
|||||||
height: 'inherit',
|
height: 'inherit',
|
||||||
'overflow-y': 'inherit'}},
|
'overflow-y': 'inherit'}},
|
||||||
m("h1", kundenName ? "Projektliste für " + kundenName : "Projektliste"),
|
m("h1", kundenName ? "Projektliste für " + kundenName : "Projektliste"),
|
||||||
|
m(BreadcrumbBar, {
|
||||||
|
breadcrumbs: breadcrumbs,
|
||||||
|
current: kundenName ? "Projekte: " + kundenName : "Projekte"
|
||||||
|
} as iBreadcrumbBar),
|
||||||
m("div", {style: {flex: '1', 'overflow-y': 'inherit'}},
|
m("div", {style: {flex: '1', 'overflow-y': 'inherit'}},
|
||||||
m(Treetable,
|
m(Treetable,
|
||||||
{
|
{
|
||||||
@@ -162,11 +192,15 @@ export const Projekt: m.ClosureComponent = (vnode) => {
|
|||||||
verantwortlicher: "",
|
verantwortlicher: "",
|
||||||
startdatum: ""
|
startdatum: ""
|
||||||
}
|
}
|
||||||
|
// Breadcrumb-Kette der Vorgänger-Ansichten (aus Route-Param bc).
|
||||||
|
let breadcrumbs: iBreadcrumb[] = []
|
||||||
|
|
||||||
return {
|
return {
|
||||||
oninit: (vnode) => {
|
oninit: (vnode) => {
|
||||||
const routeParams = m.route.param()
|
const routeParams = m.route.param()
|
||||||
const id = routeParams && routeParams.id ? parseInt(routeParams.id) : -1
|
const id = routeParams && routeParams.id ? parseInt(routeParams.id) : -1
|
||||||
|
// Breadcrumb-Kette aus Route-Param parsen.
|
||||||
|
breadcrumbs = parseBc(routeParams && routeParams.bc)
|
||||||
if (id !== -1) {
|
if (id !== -1) {
|
||||||
// Projektdaten frisch vom Server holen, um zwischenzeitliche
|
// Projektdaten frisch vom Server holen, um zwischenzeitliche
|
||||||
// Änderungen anderer Nutzer nicht zu überschreiben.
|
// Änderungen anderer Nutzer nicht zu überschreiben.
|
||||||
@@ -192,15 +226,23 @@ export const Projekt: m.ClosureComponent = (vnode) => {
|
|||||||
verantwortlicher: (formData.get("verantwortlicher") as string) || "",
|
verantwortlicher: (formData.get("verantwortlicher") as string) || "",
|
||||||
startdatum: (formData.get("startdatum") as string) || ""
|
startdatum: (formData.get("startdatum") as string) || ""
|
||||||
}
|
}
|
||||||
|
// Bei Rückkehr zur Projektliste das bc ohne das letzte
|
||||||
|
// Glied (die Projektliste selbst) durchreichen.
|
||||||
|
const listBc = serializeBc(breadcrumbs.slice(0, -1))
|
||||||
|
const routeParams: any = {kunden_id: currentProjekt.kunden_id, kunden_name: currentProjekt.kunden_name}
|
||||||
|
if (listBc) routeParams.bc = listBc
|
||||||
if (currentProjekt.id === -1) {
|
if (currentProjekt.id === -1) {
|
||||||
createProjektREST(gespeichertesProjekt).then(() => m.route.set("/projektliste", {kunden_id: currentProjekt.kunden_id, kunden_name: currentProjekt.kunden_name})).catch((e) => showServerError(e))
|
createProjektREST(gespeichertesProjekt).then(() => m.route.set("/projektliste", routeParams)).catch((e) => showServerError(e))
|
||||||
} else {
|
} else {
|
||||||
updateProjektREST(gespeichertesProjekt).then(() => m.route.set("/projektliste", {kunden_id: currentProjekt.kunden_id, kunden_name: currentProjekt.kunden_name})).catch((e) => showServerError(e))
|
updateProjektREST(gespeichertesProjekt).then(() => m.route.set("/projektliste", routeParams)).catch((e) => showServerError(e))
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
const cancel = (e: Event) => {
|
const cancel = (e: Event) => {
|
||||||
m.route.set("/projektliste", {kunden_id: currentProjekt.kunden_id, kunden_name: currentProjekt.kunden_name})
|
const listBc = serializeBc(breadcrumbs.slice(0, -1))
|
||||||
|
const routeParams: any = {kunden_id: currentProjekt.kunden_id, kunden_name: currentProjekt.kunden_name}
|
||||||
|
if (listBc) routeParams.bc = listBc
|
||||||
|
m.route.set("/projektliste", routeParams)
|
||||||
}
|
}
|
||||||
return m("div.w3-container.w3-auto.form-view-container",
|
return m("div.w3-container.w3-auto.form-view-container",
|
||||||
m("div", {style: {display: 'flex', 'align-items': 'center', 'justify-content': 'space-between'}},
|
m("div", {style: {display: 'flex', 'align-items': 'center', 'justify-content': 'space-between'}},
|
||||||
@@ -219,6 +261,10 @@ export const Projekt: m.ClosureComponent = (vnode) => {
|
|||||||
]
|
]
|
||||||
: null
|
: null
|
||||||
),
|
),
|
||||||
|
m(BreadcrumbBar, {
|
||||||
|
breadcrumbs: breadcrumbs,
|
||||||
|
current: currentProjekt.id === -1 ? "Neues Projekt" : "Projekt-Stammblatt"
|
||||||
|
} as iBreadcrumbBar),
|
||||||
m("div", {style: {flex: '1', 'min-height': '0'}},
|
m("div", {style: {flex: '1', 'min-height': '0'}},
|
||||||
m(Form, {showButtons: true, onsubmit: submit, oncancel: cancel} as iForm, [
|
m(Form, {showButtons: true, onsubmit: submit, oncancel: cancel} as iForm, [
|
||||||
m("input", {type: "hidden", name: "kunden_id", value: currentProjekt.kunden_id}),
|
m("input", {type: "hidden", name: "kunden_id", value: currentProjekt.kunden_id}),
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
import m from "mithril"
|
||||||
|
|
||||||
|
/** Ein Breadcrumb-Glied: Route, Filter-Params und Anzeige-Label einer
|
||||||
|
* Vorgänger-Ansicht in der Aufrufkette. */
|
||||||
|
export interface iBreadcrumb {
|
||||||
|
route: string
|
||||||
|
params: any
|
||||||
|
label: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Attribute der BreadcrumbBar: die Vorgänger-Kette (ancestors) und das
|
||||||
|
* Label der aktuellen Ansicht (wird als letztes, nicht klickbares Glied
|
||||||
|
* angezeigt). Ist breadcrumbs leer, wird keine Leiste gerendert. */
|
||||||
|
export interface iBreadcrumbBar {
|
||||||
|
breadcrumbs: iBreadcrumb[]
|
||||||
|
current: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Parst den Route-Param `bc` (JSON-String) in ein Breadcrumb-Array.
|
||||||
|
* Liefert bei fehlendem/ungültigem `bc` ein leeres Array. */
|
||||||
|
export function parseBc(bc: string | undefined): iBreadcrumb[] {
|
||||||
|
if (!bc) return []
|
||||||
|
try { return JSON.parse(bc) } catch { return [] }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Serialisiert ein Breadcrumb-Array in den Route-Param `bc`.
|
||||||
|
* Liefert undefined bei leerer Kette (kein bc in der Route). */
|
||||||
|
export function serializeBc(crumbs: iBreadcrumb[]): string | undefined {
|
||||||
|
return crumbs.length > 0 ? JSON.stringify(crumbs) : undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BreadcrumbBar: m.ClosureComponent<iBreadcrumbBar> = () => {
|
||||||
|
return {
|
||||||
|
view: (vnode) => {
|
||||||
|
const crumbs = vnode.attrs.breadcrumbs || []
|
||||||
|
const current = vnode.attrs.current
|
||||||
|
// Ohne Vorgänger und ohne aktuelles Label keine Leiste rendern
|
||||||
|
// (Top-Level-Ansichten ohne Hierarchie-Kontext).
|
||||||
|
if (crumbs.length === 0 && !current) return null
|
||||||
|
|
||||||
|
const items: any[] = []
|
||||||
|
crumbs.forEach((crumb: iBreadcrumb, i: number) => {
|
||||||
|
if (i > 0) {
|
||||||
|
items.push(m("span.w3-small", {
|
||||||
|
style: {margin: '0 2px', opacity: '0.6'}
|
||||||
|
}, "›"))
|
||||||
|
}
|
||||||
|
// Vorgänger sind klickbar: Navigation zur jeweiligen Ansicht
|
||||||
|
// mit deren Filter-Params und der auf dieses Glied gekürzten
|
||||||
|
// Kette als neuem bc.
|
||||||
|
items.push(m("a.w3-button.w3-small.w3-theme-l2", {
|
||||||
|
style: {
|
||||||
|
padding: '2px 8px',
|
||||||
|
'border-radius': '4px',
|
||||||
|
'text-decoration': 'none',
|
||||||
|
'white-space': 'nowrap'
|
||||||
|
},
|
||||||
|
title: "Zurück zu " + crumb.label,
|
||||||
|
onclick: () => {
|
||||||
|
const subChain = crumbs.slice(0, i)
|
||||||
|
const params: any = {...crumb.params}
|
||||||
|
const bc = serializeBc(subChain)
|
||||||
|
if (bc) params.bc = bc
|
||||||
|
m.route.set(crumb.route, params)
|
||||||
|
}
|
||||||
|
}, crumb.label))
|
||||||
|
})
|
||||||
|
// Aktuelle Ansicht als letztes, nicht klickbares Glied (fett).
|
||||||
|
if (current) {
|
||||||
|
if (crumbs.length > 0) {
|
||||||
|
items.push(m("span.w3-small", {
|
||||||
|
style: {margin: '0 2px', opacity: '0.6'}
|
||||||
|
}, "›"))
|
||||||
|
}
|
||||||
|
items.push(m("span.w3-small.w3-theme-d3", {
|
||||||
|
style: {
|
||||||
|
padding: '2px 8px',
|
||||||
|
'border-radius': '4px',
|
||||||
|
'font-weight': 'bold',
|
||||||
|
'white-space': 'nowrap'
|
||||||
|
}
|
||||||
|
}, current))
|
||||||
|
}
|
||||||
|
|
||||||
|
return m("div.w3-theme-l1", {
|
||||||
|
style: {
|
||||||
|
display: 'flex',
|
||||||
|
'flex-wrap': 'nowrap',
|
||||||
|
'align-items': 'center',
|
||||||
|
padding: '4px 8px',
|
||||||
|
'border-radius': '4px',
|
||||||
|
'margin-bottom': '8px',
|
||||||
|
'overflow-x': 'auto',
|
||||||
|
'overflow-y': 'hidden'
|
||||||
|
}
|
||||||
|
}, items)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user