feat(tag): Workflow-Felder für Telefon-Akquise (is_erledigend, Wiedervorlage)

Tag-Modell um is_erledigend, loest_wiedervorlage_aus und
wiedervorlage_abstand_tage erweitert. Migration V01_12 idempotent
angelegt, Geschäftslogik mit Validierung, Client-Maske mit Controls
und Workflow-Badges. Farb-Default Grau bei fehlender Farbe, parent_id
im onchange direkt in currentTag übernehmen.

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
gg committed 2026-09-20 20:20:52 +02:00
1 parent 1d8201f46a
commit 6f86bc9808
5 files changed
+206 -4

No files matched your search

+115 -4
View File
@@ -77,6 +77,9 @@ export const Tag: m.ClosureComponent = () => {
color: "",
parent_id: null,
is_parent: false,
is_erledigend: false,
loest_wiedervorlage_aus: false,
wiedervorlage_abstand_tage: null,
}
// Liste aller Parent-Tags fuer die Eltern-Auswahl (nur Parent-Tags
@@ -109,6 +112,9 @@ export const Tag: m.ClosureComponent = () => {
color: routeParams.color || "",
parent_id: routeParams.parent_id ? parseInt(routeParams.parent_id) : null,
is_parent: routeParams.is_parent ? routeParams.is_parent === "true" : false,
is_erledigend: routeParams.is_erledigend ? routeParams.is_erledigend === "true" : false,
loest_wiedervorlage_aus: routeParams.loest_wiedervorlage_aus ? routeParams.loest_wiedervorlage_aus === "true" : false,
wiedervorlage_abstand_tage: routeParams.wiedervorlage_abstand_tage ? parseInt(routeParams.wiedervorlage_abstand_tage) : null,
}
}
// Parent-Tag-Liste vom Server laden (nur is_parent-Tags sind
@@ -129,8 +135,11 @@ export const Tag: m.ClosureComponent = () => {
name: (formData.get("name") as string) || "",
description: (formData.get("description") as string) || "",
color: currentTag.color || "",
parent_id: formData.get("parent_id") ? parseInt(formData.get("parent_id") as string) : null,
parent_id: currentTag.parent_id,
is_parent: currentTag.is_parent,
is_erledigend: currentTag.is_erledigend,
loest_wiedervorlage_aus: currentTag.loest_wiedervorlage_aus,
wiedervorlage_abstand_tage: currentTag.wiedervorlage_abstand_tage,
}
if (currentTag.id === -1) {
createTagREST(gespeicherterTag).then(() => guard.navigateAfterSave()).catch((e) => { showServerError(e); guard.markDirty() })
@@ -168,7 +177,11 @@ export const Tag: m.ClosureComponent = () => {
m("select.w3-select", {
name: "parent_id",
value: currentTag.parent_id || "",
onchange: () => { guard.markDirty() }
onchange: (e: Event) => {
const val = (e.target as HTMLSelectElement).value
currentTag.parent_id = val ? parseInt(val) : null
guard.markDirty()
}
},
m("option", {value: ""}, "-- Kein übergeordneter Tag --"),
allParentTags.map(tag =>
@@ -176,6 +189,62 @@ export const Tag: m.ClosureComponent = () => {
)
),
// Workflow-Felder (nur für echte, zuordenbare Tags sichtbar).
// Parent-Tags sind Gruppierungsknoten und nicht zuordenbar,
// daher sind diese Felder für sie irrelevant.
currentTag.is_parent ? null : [
m("div", {style: {'margin-top': '16px', 'margin-bottom': '5px'}},
m("label",
m("input.w3-check", {
type: "checkbox",
name: "is_erledigend",
checked: currentTag.is_erledigend,
style: {'margin-right': '8px'},
onclick: (e: Event) => {
currentTag.is_erledigend = (e.target as HTMLInputElement).checked
guard.markDirty()
}
}),
"Kontakt als erledigt markieren"
)
),
m("div", {style: {'margin-bottom': '5px'}},
m("label",
m("input.w3-check", {
type: "checkbox",
name: "loest_wiedervorlage_aus",
checked: currentTag.loest_wiedervorlage_aus,
style: {'margin-right': '8px'},
onclick: (e: Event) => {
currentTag.loest_wiedervorlage_aus = (e.target as HTMLInputElement).checked
if (!currentTag.loest_wiedervorlage_aus) {
currentTag.wiedervorlage_abstand_tage = null
}
guard.markDirty()
}
}),
"Löst Wiedervorlage aus"
)
),
m("label", "Wiedervorlage-Abstand (Tage)"),
m("input.w3-input", {
type: "number",
name: "wiedervorlage_abstand_tage",
value: currentTag.wiedervorlage_abstand_tage ?? "",
min: 0,
disabled: !currentTag.loest_wiedervorlage_aus,
style: {
'margin-bottom': '12px',
opacity: currentTag.loest_wiedervorlage_aus ? '1' : '0.5'
},
oninput: (e: Event) => {
const val = (e.target as HTMLInputElement).value
currentTag.wiedervorlage_abstand_tage = val === "" ? null : parseInt(val)
guard.markDirty()
}
})
],
m("label", "Farbe"),
m("div", {style: {display: 'flex', flexWrap: 'wrap', gap: '8px', marginBottom: '12px'}},
colorPalette.map(color =>
@@ -279,6 +348,47 @@ export const TagList: m.ClosureComponent = () => {
const tagIcon = tag.is_parent ? "folder" : "label"
const tagTitle = tag.is_parent ? "Parent-Tag (nicht zuordenbar)" : ""
// Workflow-Badges für echte Tags (nicht für Parent-Tags)
let workflowBadges: any[] = []
if (!tag.is_parent) {
workflowBadges = []
if (tag.is_erledigend) {
workflowBadges.push(
m("i.material-icons", {
title: "Erledigend: Kontakt gilt als erledigt",
style: {
verticalAlign: 'middle',
marginRight: '4px',
fontSize: '16px',
color: '#2e7d32'
}
}, "check_circle")
)
}
if (tag.loest_wiedervorlage_aus) {
workflowBadges.push(
m("span", {
title: "Löst Wiedervorlage aus: " + (tag.wiedervorlage_abstand_tage ?? 0) + " Tage",
style: {
verticalAlign: 'middle',
marginRight: '4px',
fontSize: '12px',
color: '#1565c0'
}
},
m("i.material-icons", {
style: {
verticalAlign: 'middle',
marginRight: '2px',
fontSize: '16px'
}
}, "schedule"),
tag.wiedervorlage_abstand_tage != null ? tag.wiedervorlage_abstand_tage + "d" : ""
)
)
}
}
return [
m("span",
m("i.material-icons", {
@@ -291,7 +401,7 @@ export const TagList: m.ClosureComponent = () => {
}, tagIcon),
m("span", {
style: {
backgroundColor: tag.color,
backgroundColor: tag.color || '#9e9e9e',
color: 'white',
padding: '2px 6px',
borderRadius: '3px',
@@ -300,7 +410,8 @@ export const TagList: m.ClosureComponent = () => {
marginRight: '8px'
},
title: tagTitle
}, tag.name)
}, tag.name),
workflowBadges
),
m("span", tag.description),
m("div", {style: {display: 'flex', gap: '0', 'white-space': 'nowrap', 'justify-content': 'flex-end'}},