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
+109
-34
+109
-34
@@ -229,6 +229,9 @@ export const Call_Log: m.ClosureComponent = (vnode) => {
|
||||
let kundenListe: iKunde[] = []
|
||||
let projektListe: iProjekt[] = []
|
||||
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).
|
||||
let tagListe: iTag[] = []
|
||||
// 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')
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
oninit: (vnode) => {
|
||||
const routeParams = m.route.param()
|
||||
@@ -332,23 +356,12 @@ export const Call_Log: m.ClosureComponent = (vnode) => {
|
||||
guard.cleanup()
|
||||
},
|
||||
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) => {
|
||||
return {value: k.id, label: k.name}
|
||||
})
|
||||
// Projekt-Optionen sind vom gewählten Kunden abhängig.
|
||||
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) => {
|
||||
return {value: p.id, label: p.projektname}
|
||||
})
|
||||
@@ -370,17 +383,19 @@ export const Call_Log: m.ClosureComponent = (vnode) => {
|
||||
const formData = new FormData(form)
|
||||
const gespeichertesLog: iCallLog = {
|
||||
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: "",
|
||||
kunden_name: "",
|
||||
datum: (formData.get("datum") as string) || "",
|
||||
datum: currentLog.datum || "",
|
||||
uhrzeit_beginn: (formData.get("uhrzeit_beginn") as string) || "",
|
||||
dauer: (formData.get("dauer") as string) || "",
|
||||
betreff: (formData.get("betreff") as string) || "",
|
||||
name_ansprechpartner: (formData.get("name_ansprechpartner") as string) || "",
|
||||
telefon: (formData.get("telefon") 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",
|
||||
tag_ids: formData.getAll("tag_ids").map((v) => parseInt(v as string)).filter((n) => !isNaN(n))
|
||||
}
|
||||
@@ -400,26 +415,70 @@ export const Call_Log: m.ClosureComponent = (vnode) => {
|
||||
} as iBreadcrumbBar),
|
||||
m("div", {style: {flex: '1', 'min-height': '0'}},
|
||||
m(Form, guard.formAttrs(submit) as iForm, [
|
||||
m("input", {type: "hidden", name: "projekt_id", value: currentLog.projekt_id}),
|
||||
// Kunde und Projekt als Read-Only-Text, da voreingestellt.
|
||||
m("label", "Kunde",
|
||||
m("input.w3-input.w3-border", {
|
||||
type: "text",
|
||||
value: currentLog.kunden_name,
|
||||
readonly: true,
|
||||
style: {'margin-bottom': '5px'}
|
||||
})),
|
||||
m("label", "Projekt",
|
||||
m("input.w3-input.w3-border", {
|
||||
type: "text",
|
||||
value: currentLog.projekt_name,
|
||||
readonly: true,
|
||||
style: {'margin-bottom': '5px'}
|
||||
})),
|
||||
projektVoreingestellt
|
||||
? 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("input.w3-input.w3-border", {
|
||||
type: "text",
|
||||
value: currentLog.kunden_name,
|
||||
readonly: true,
|
||||
style: {'margin-bottom': '5px'}
|
||||
})),
|
||||
m("label", "Projekt",
|
||||
m("input.w3-input.w3-border", {
|
||||
type: "text",
|
||||
value: currentLog.projekt_name,
|
||||
readonly: true,
|
||||
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.
|
||||
m("div", {style: {display: 'flex', gap: '8px', 'margin-bottom': '5px'}},
|
||||
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(Input, {name: "uhrzeit_beginn", value: currentLog.uhrzeit_beginn, type: "time"} as iInput)),
|
||||
m("label", {style: {flex: '1'}}, "Dauer (mm:ss)",
|
||||
@@ -463,9 +522,25 @@ export const Call_Log: m.ClosureComponent = (vnode) => {
|
||||
placeholder: "kein Tag",
|
||||
modalTitle: "Tags auswählen",
|
||||
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)),
|
||||
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")
|
||||
])
|
||||
)
|
||||
|
||||
Reference in new issue
Block a user