feat(call-log): Wiedervorlage-Auto-Berechnung und Kunde/Projekt-Select
- Wiedervorlage-Datum wird automatisch aus ausgewählten Tags berechnet (Minimum von wiedervorlage_abstand_tage aller Tags mit loest_wiedervorlage_aus) - Kunde/Projekt: Conditional Rendering — vorbelegt als Read-Only, nicht vorbelegt als abhängige Select-Widgets - Live-Binding für datum und wiedervorlage_datum via oninput, um Mithril-Redraw-Override durch FormData-Auslesung zu verhindern Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
1 parent
5a39c64880
commit
4e721e9a47
1 file changed
+96
-21
@@ -229,6 +229,9 @@ export const Call_Log: m.ClosureComponent = (vnode) => {
|
|||||||
let kundenListe: iKunde[] = []
|
let kundenListe: iKunde[] = []
|
||||||
let projektListe: iProjekt[] = []
|
let projektListe: iProjekt[] = []
|
||||||
let kontaktadressenListe: iKontaktadresse[] = []
|
let kontaktadressenListe: iKontaktadresse[] = []
|
||||||
|
// Im Auswahl-Modus (nicht vorbelegt): gewählte Kunden-ID für die
|
||||||
|
// abhängige Projekt-Filterung.
|
||||||
|
let selectedKundenId: number = -1
|
||||||
// Asynchron geladene Tag-Liste (nur zuordenbare, nicht is_parent).
|
// Asynchron geladene Tag-Liste (nur zuordenbare, nicht is_parent).
|
||||||
let tagListe: iTag[] = []
|
let tagListe: iTag[] = []
|
||||||
// Aktuell ausgewählte Tag-IDs im Formular.
|
// Aktuell ausgewählte Tag-IDs im Formular.
|
||||||
@@ -272,6 +275,27 @@ export const Call_Log: m.ClosureComponent = (vnode) => {
|
|||||||
return min.toString().padStart(2, '0') + ':' + sek.toString().padStart(2, '0')
|
return min.toString().padStart(2, '0') + ':' + sek.toString().padStart(2, '0')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Berechnet das Wiedervorlage-Datum automatisch aus den ausgewählten
|
||||||
|
* Tags. Basis ist das Datum der Notiz (currentLog.datum) oder heute,
|
||||||
|
* falls dieses leer ist. Aus allen ausgewählten Tags mit
|
||||||
|
* loest_wiedervorlage_aus=True wird das Minimum von
|
||||||
|
* wiedervorlage_abstand_tage ermittelt und zum Basisdatum addiert.
|
||||||
|
* Liefert einen leeren String, wenn kein Tag mit
|
||||||
|
* loest_wiedervorlage_aus=True ausgewählt ist.
|
||||||
|
*/
|
||||||
|
const berechneWiedervorlageDatum = (tagIds: number[]): string => {
|
||||||
|
const basis = currentLog.datum || new Date().toISOString().split('T')[0]
|
||||||
|
const wvTags = tagListe.filter((t: iTag) =>
|
||||||
|
tagIds.includes(t.id) && t.loest_wiedervorlage_aus && t.wiedervorlage_abstand_tage != null
|
||||||
|
)
|
||||||
|
if (wvTags.length === 0) return ""
|
||||||
|
const minAbstand = Math.min(...wvTags.map(t => t.wiedervorlage_abstand_tage as number))
|
||||||
|
const basisDate = new Date(basis)
|
||||||
|
basisDate.setDate(basisDate.getDate() + minAbstand)
|
||||||
|
return basisDate.toISOString().split('T')[0]
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
oninit: (vnode) => {
|
oninit: (vnode) => {
|
||||||
const routeParams = m.route.param()
|
const routeParams = m.route.param()
|
||||||
@@ -332,23 +356,12 @@ export const Call_Log: m.ClosureComponent = (vnode) => {
|
|||||||
guard.cleanup()
|
guard.cleanup()
|
||||||
},
|
},
|
||||||
view: (vnode) => {
|
view: (vnode) => {
|
||||||
// kunden_id aus dem geladenen Projekt ableiten (fuer abhaengiges
|
|
||||||
// Projekt-Select). Wird nur bei nicht voreingestelltem Projekt
|
|
||||||
// benoetigt.
|
|
||||||
const internKundenId = (() => {
|
|
||||||
if (currentLog.projekt_id !== -1) {
|
|
||||||
const p = projektListe.find((p: iProjekt) => p.id === currentLog.projekt_id)
|
|
||||||
return p ? p.kunden_id : -1
|
|
||||||
}
|
|
||||||
return -1
|
|
||||||
})()
|
|
||||||
|
|
||||||
const kundenOptionen: iSelectOption[] = kundenListe.map((k: iKunde) => {
|
const kundenOptionen: iSelectOption[] = kundenListe.map((k: iKunde) => {
|
||||||
return {value: k.id, label: k.name}
|
return {value: k.id, label: k.name}
|
||||||
})
|
})
|
||||||
// Projekt-Optionen sind vom gewählten Kunden abhängig.
|
// Projekt-Optionen sind vom gewählten Kunden abhängig.
|
||||||
const projektOptionen: iSelectOption[] = projektListe
|
const projektOptionen: iSelectOption[] = projektListe
|
||||||
.filter((p: iProjekt) => internKundenId === -1 || p.kunden_id === internKundenId)
|
.filter((p: iProjekt) => selectedKundenId === -1 || p.kunden_id === selectedKundenId)
|
||||||
.map((p: iProjekt) => {
|
.map((p: iProjekt) => {
|
||||||
return {value: p.id, label: p.projektname}
|
return {value: p.id, label: p.projektname}
|
||||||
})
|
})
|
||||||
@@ -370,17 +383,19 @@ export const Call_Log: m.ClosureComponent = (vnode) => {
|
|||||||
const formData = new FormData(form)
|
const formData = new FormData(form)
|
||||||
const gespeichertesLog: iCallLog = {
|
const gespeichertesLog: iCallLog = {
|
||||||
id: currentLog.id,
|
id: currentLog.id,
|
||||||
projekt_id: currentLog.projekt_id,
|
projekt_id: projektVoreingestellt
|
||||||
|
? currentLog.projekt_id
|
||||||
|
: parseInt((formData.get("projekt_id_select") as string) || "-1"),
|
||||||
projekt_name: "",
|
projekt_name: "",
|
||||||
kunden_name: "",
|
kunden_name: "",
|
||||||
datum: (formData.get("datum") as string) || "",
|
datum: currentLog.datum || "",
|
||||||
uhrzeit_beginn: (formData.get("uhrzeit_beginn") as string) || "",
|
uhrzeit_beginn: (formData.get("uhrzeit_beginn") as string) || "",
|
||||||
dauer: (formData.get("dauer") as string) || "",
|
dauer: (formData.get("dauer") as string) || "",
|
||||||
betreff: (formData.get("betreff") as string) || "",
|
betreff: (formData.get("betreff") as string) || "",
|
||||||
name_ansprechpartner: (formData.get("name_ansprechpartner") as string) || "",
|
name_ansprechpartner: (formData.get("name_ansprechpartner") as string) || "",
|
||||||
telefon: (formData.get("telefon") as string) || "",
|
telefon: (formData.get("telefon") as string) || "",
|
||||||
notiz: (formData.get("notiz") as string) || "",
|
notiz: (formData.get("notiz") as string) || "",
|
||||||
wiedervorlage_datum: (formData.get("wiedervorlage_datum") as string) || "",
|
wiedervorlage_datum: currentLog.wiedervorlage_datum || "",
|
||||||
erledigt: formData.get("erledigt") === "on",
|
erledigt: formData.get("erledigt") === "on",
|
||||||
tag_ids: formData.getAll("tag_ids").map((v) => parseInt(v as string)).filter((n) => !isNaN(n))
|
tag_ids: formData.getAll("tag_ids").map((v) => parseInt(v as string)).filter((n) => !isNaN(n))
|
||||||
}
|
}
|
||||||
@@ -400,8 +415,13 @@ export const Call_Log: m.ClosureComponent = (vnode) => {
|
|||||||
} as iBreadcrumbBar),
|
} as iBreadcrumbBar),
|
||||||
m("div", {style: {flex: '1', 'min-height': '0'}},
|
m("div", {style: {flex: '1', 'min-height': '0'}},
|
||||||
m(Form, guard.formAttrs(submit) as iForm, [
|
m(Form, guard.formAttrs(submit) as iForm, [
|
||||||
m("input", {type: "hidden", name: "projekt_id", value: currentLog.projekt_id}),
|
projektVoreingestellt
|
||||||
// Kunde und Projekt als Read-Only-Text, da voreingestellt.
|
? m("input", {type: "hidden", name: "projekt_id", value: currentLog.projekt_id})
|
||||||
|
: null,
|
||||||
|
// Kunde und Projekt: vorbelegt als Read-Only-Text,
|
||||||
|
// nicht vorbelegt als auswählbare Select-Widgets.
|
||||||
|
projektVoreingestellt
|
||||||
|
? [
|
||||||
m("label", "Kunde",
|
m("label", "Kunde",
|
||||||
m("input.w3-input.w3-border", {
|
m("input.w3-input.w3-border", {
|
||||||
type: "text",
|
type: "text",
|
||||||
@@ -415,11 +435,50 @@ export const Call_Log: m.ClosureComponent = (vnode) => {
|
|||||||
value: currentLog.projekt_name,
|
value: currentLog.projekt_name,
|
||||||
readonly: true,
|
readonly: true,
|
||||||
style: {'margin-bottom': '5px'}
|
style: {'margin-bottom': '5px'}
|
||||||
})),
|
}))
|
||||||
|
]
|
||||||
|
: [
|
||||||
|
m("label", "Kunde", m(Select, {
|
||||||
|
name: "kunden_id",
|
||||||
|
value: selectedKundenId,
|
||||||
|
options: kundenOptionen,
|
||||||
|
required: true,
|
||||||
|
onchanged: (kundenId: number) => {
|
||||||
|
selectedKundenId = kundenId
|
||||||
|
// Projekt zurücksetzen, da es zum
|
||||||
|
// alten Kunden gehörte.
|
||||||
|
currentLog.projekt_id = -1
|
||||||
|
currentLog.projekt_name = ""
|
||||||
|
currentLog.kunden_name = kundenListe.find(k => k.id === kundenId)?.name || ""
|
||||||
|
guard.markDirty()
|
||||||
|
}
|
||||||
|
} as iSelect)),
|
||||||
|
m("label", "Projekt", m(Select, {
|
||||||
|
name: "projekt_id_select",
|
||||||
|
value: currentLog.projekt_id,
|
||||||
|
options: projektOptionen,
|
||||||
|
required: true,
|
||||||
|
onchanged: (projektId: number) => {
|
||||||
|
const p = projektListe.find(pr => pr.id === projektId)
|
||||||
|
currentLog.projekt_id = projektId
|
||||||
|
currentLog.projekt_name = p?.projektname || ""
|
||||||
|
currentLog.kunden_name = p?.kunden_name || ""
|
||||||
|
guard.markDirty()
|
||||||
|
}
|
||||||
|
} as iSelect))
|
||||||
|
],
|
||||||
// Datum, Startzeit und Dauer in einer Zeile.
|
// Datum, Startzeit und Dauer in einer Zeile.
|
||||||
m("div", {style: {display: 'flex', gap: '8px', 'margin-bottom': '5px'}},
|
m("div", {style: {display: 'flex', gap: '8px', 'margin-bottom': '5px'}},
|
||||||
m("label", {style: {flex: '1'}}, "Datum",
|
m("label", {style: {flex: '1'}}, "Datum",
|
||||||
m(Input, {name: "datum", value: currentLog.datum, type: "date"} as iInput)),
|
m(Input, {
|
||||||
|
name: "datum",
|
||||||
|
value: currentLog.datum,
|
||||||
|
type: "date",
|
||||||
|
oninput: (e: Event) => {
|
||||||
|
currentLog.datum = (e.target as HTMLInputElement).value
|
||||||
|
guard.markDirty()
|
||||||
|
}
|
||||||
|
} as iInput)),
|
||||||
m("label", {style: {flex: '1'}}, "Uhrzeit Beginn",
|
m("label", {style: {flex: '1'}}, "Uhrzeit Beginn",
|
||||||
m(Input, {name: "uhrzeit_beginn", value: currentLog.uhrzeit_beginn, type: "time"} as iInput)),
|
m(Input, {name: "uhrzeit_beginn", value: currentLog.uhrzeit_beginn, type: "time"} as iInput)),
|
||||||
m("label", {style: {flex: '1'}}, "Dauer (mm:ss)",
|
m("label", {style: {flex: '1'}}, "Dauer (mm:ss)",
|
||||||
@@ -463,9 +522,25 @@ export const Call_Log: m.ClosureComponent = (vnode) => {
|
|||||||
placeholder: "kein Tag",
|
placeholder: "kein Tag",
|
||||||
modalTitle: "Tags auswählen",
|
modalTitle: "Tags auswählen",
|
||||||
editTooltip: "Tags bearbeiten",
|
editTooltip: "Tags bearbeiten",
|
||||||
onchanged: () => { guard.markDirty() }
|
onchanged: (newSelection: number[]) => {
|
||||||
|
selectedTagIds.splice(0, selectedTagIds.length)
|
||||||
|
Array.prototype.push.apply(selectedTagIds, newSelection)
|
||||||
|
const vorschlag = berechneWiedervorlageDatum(newSelection)
|
||||||
|
if (vorschlag) {
|
||||||
|
currentLog.wiedervorlage_datum = vorschlag
|
||||||
|
}
|
||||||
|
guard.markDirty()
|
||||||
|
}
|
||||||
} as iMultiSelect)),
|
} as iMultiSelect)),
|
||||||
m("label", "Wiedervorlage Datum", m(Input, {name: "wiedervorlage_datum", value: currentLog.wiedervorlage_datum, type: "date"} as iInput)),
|
m("label", "Wiedervorlage Datum", m(Input, {
|
||||||
|
name: "wiedervorlage_datum",
|
||||||
|
value: currentLog.wiedervorlage_datum,
|
||||||
|
type: "date",
|
||||||
|
oninput: (e: Event) => {
|
||||||
|
currentLog.wiedervorlage_datum = (e.target as HTMLInputElement).value
|
||||||
|
guard.markDirty()
|
||||||
|
}
|
||||||
|
} as iInput)),
|
||||||
m("label", m(Input, {name: "erledigt", type: "checkbox", value: currentLog.erledigt} as iInput), "Erledigt")
|
m("label", m(Input, {name: "erledigt", type: "checkbox", value: currentLog.erledigt} as iInput), "Erledigt")
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in new issue
Block a user