feat(workflow): Erledigt-Feld entfernen und Tages-Workflow-Maske überarbeiten
Das redundante 'erledigt'-Feld wird aus CallLog entfernt (Modell, API, Client, Migration _db_V01_15 DROP COLUMN). Der Erledigt-Status ergibt sich dynamisch aus den zugeordneten Tags (is_erledigend). Die Tages-Workflow-Maske zeigt Datum, Uhrzeit Beginn und Dauer der neuen Notiz als Read-Only-Panel; die Dauer läuft als Timer (mm:ss) mit. Ein Betreff-Feld ergänzt die neue Notiz. Mit flexContent wird der Formular-Inhalt als Flex-Spalte gerendert, das Historie-Panel füllt den verbleibenden Platz und scrollt in sich. Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
1 parent
929664453e
commit
6e3cc7375d
9 files changed
+287
-122
No files matched your search
@@ -52,7 +52,6 @@ export const call_log_liste: m.ClosureComponent = (vnode) => {
|
||||
telefon: "",
|
||||
notiz: "",
|
||||
wiedervorlage_datum: "",
|
||||
erledigt: false,
|
||||
from_liste: "1"
|
||||
}
|
||||
if (filterProjektId !== null) {
|
||||
@@ -105,7 +104,6 @@ export const call_log_liste: m.ClosureComponent = (vnode) => {
|
||||
m("span", log.dauer + " min"),
|
||||
m("span", log.betreff, ...tagBadges),
|
||||
m("span", log.wiedervorlage_datum),
|
||||
m("span", log.erledigt ? "Erledigt" : "Offen"),
|
||||
m("button.w3-button.w3-small", {title: "Gesprächsnotiz bearbeiten", style: {padding: '0 2px'}, onclick: (e: Event) => editCallLog(log, e)}, m("i.material-icons", "edit"))
|
||||
]
|
||||
}
|
||||
@@ -200,7 +198,6 @@ export const Call_Log: m.ClosureComponent = (vnode) => {
|
||||
telefon: string
|
||||
notiz: string
|
||||
wiedervorlage_datum: string
|
||||
erledigt: boolean
|
||||
} = {
|
||||
id: -1,
|
||||
projekt_id: -1,
|
||||
@@ -214,8 +211,7 @@ export const Call_Log: m.ClosureComponent = (vnode) => {
|
||||
name_ansprechpartner: "",
|
||||
telefon: "",
|
||||
notiz: "",
|
||||
wiedervorlage_datum: "",
|
||||
erledigt: false
|
||||
wiedervorlage_datum: ""
|
||||
}
|
||||
// Ist das Projekt voreingestellt (Liste wurde projektgefiltert)?
|
||||
let projektVoreingestellt: boolean = false
|
||||
@@ -312,7 +308,6 @@ export const Call_Log: m.ClosureComponent = (vnode) => {
|
||||
currentLog.telefon = routeParams && routeParams.telefon ? routeParams.telefon : ""
|
||||
currentLog.notiz = routeParams && routeParams.notiz ? routeParams.notiz : ""
|
||||
currentLog.wiedervorlage_datum = routeParams && routeParams.wiedervorlage_datum ? routeParams.wiedervorlage_datum : ""
|
||||
currentLog.erledigt = routeParams && (routeParams.erledigt === true || routeParams.erledigt === "true")
|
||||
// Kontaktadresse-ID aus den Route-Params übernehmen, wenn die
|
||||
// Maske aus der Kontaktadressenliste geöffnet wurde.
|
||||
currentLog.kontaktadresse_id = routeParams && routeParams.kontaktadresse_id ? parseInt(routeParams.kontaktadresse_id) : -1
|
||||
@@ -391,7 +386,6 @@ export const Call_Log: m.ClosureComponent = (vnode) => {
|
||||
telefon: (formData.get("telefon") as string) || "",
|
||||
notiz: (formData.get("notiz") 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))
|
||||
}
|
||||
if (currentLog.id === -1) {
|
||||
@@ -540,8 +534,7 @@ export const Call_Log: m.ClosureComponent = (vnode) => {
|
||||
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")
|
||||
} as iInput))
|
||||
])
|
||||
)
|
||||
)
|
||||
|
||||
@@ -73,7 +73,6 @@ export const kontaktadresseliste: m.ClosureComponent = (vnode) => {
|
||||
telefon: kontaktadresse.telefon,
|
||||
notiz: "",
|
||||
wiedervorlage_datum: "",
|
||||
erledigt: false,
|
||||
bc: serializeBc(chain)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -85,7 +85,6 @@ export const projektliste: m.ClosureComponent = (vnode) => {
|
||||
telefon: "",
|
||||
notiz: "",
|
||||
wiedervorlage_datum: "",
|
||||
erledigt: false,
|
||||
bc: serializeBc(chain)
|
||||
})
|
||||
}
|
||||
|
||||
+247
-105
@@ -53,6 +53,21 @@ function jetztUhrzeit(): string {
|
||||
return now.toTimeString().split(':')[0] + ':' + now.toTimeString().split(':')[1]
|
||||
}
|
||||
|
||||
/**
|
||||
* Aktualisierungsintervall für die Dauer-Anzeige in der Workflow-Maske
|
||||
* (Millisekunden), analog zur CallLog-Maske.
|
||||
*/
|
||||
const DAUER_UPDATE_INTERVALL = 1000
|
||||
|
||||
/**
|
||||
* Formatiert verstrichene Sekunden als mm:ss (wie die CallLog-Maske).
|
||||
*/
|
||||
function formatDauer(sekunden: number): string {
|
||||
const min = Math.floor(sekunden / 60)
|
||||
const sek = sekunden % 60
|
||||
return min.toString().padStart(2, '0') + ':' + sek.toString().padStart(2, '0')
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// TAGES-WORKFLOW-LISTE
|
||||
// ============================================================================
|
||||
@@ -395,6 +410,26 @@ export const TagesWorkflowMaske: m.ClosureComponent = () => {
|
||||
let notizText: string = ""
|
||||
let wiedervorlageDatum: string = ""
|
||||
let selectedTagIds: number[] = []
|
||||
// Datum, Uhrzeit Beginn, Dauer und Betreff der neuen Notiz
|
||||
// (analog zur CallLog-Maske). Datum, Uhrzeit und Dauer werden beim
|
||||
// Öffnen der Maske gesetzt und als Read-Only-Anzeige dargestellt —
|
||||
// die Dauer läuft als Timer mit (mm:ss). Ein Erledigt-Feld gibt es
|
||||
// nicht: Der Status ergibt sich aus einem ggf. zugeordneten
|
||||
// erledigend-Tag.
|
||||
let notizDatum: string = ""
|
||||
let notizUhrzeit: string = ""
|
||||
let notizDauer: string = ""
|
||||
let notizBetreff: string = ""
|
||||
// Startzeit für die Dauer-Messung: wird bei jedem Laden (Maskenaufruf
|
||||
// und Kontaktwechsel) neu gesetzt; der Timer läuft durch und aktualisiert
|
||||
// die Dauer-Anzeige laufend.
|
||||
let startZeit: Date | null = null
|
||||
// Timer-Handle für die Dauer-Aktualisierung.
|
||||
let timerId: number | null = null
|
||||
// Direkte DOM-Referenz auf die Dauer-Anzeige: Der Timer ist kein
|
||||
// Mithril-Event und löst keinen Redraw aus — die Anzeige wird daher
|
||||
// direkt im DOM aktualisiert (wie in der CallLog-Maske).
|
||||
let dauerAnzeige: HTMLElement | null = null
|
||||
// Asynchron geladene Tag-Liste (nur zuordenbare, nicht is_parent).
|
||||
let tagListe: iTag[] = []
|
||||
// Hinweis aus der Navigation (Kontakt übersprungen), siehe workflowHinweis.
|
||||
@@ -424,7 +459,13 @@ export const TagesWorkflowMaske: m.ClosureComponent = () => {
|
||||
notizText = ""
|
||||
wiedervorlageDatum = ""
|
||||
selectedTagIds = []
|
||||
notizDatum = heuteIso()
|
||||
notizUhrzeit = jetztUhrzeit()
|
||||
notizDauer = "00:00"
|
||||
notizBetreff = ""
|
||||
formDirty = false
|
||||
// Dauer-Messung für die neue Notiz starten.
|
||||
startZeit = new Date()
|
||||
// Hinweis der vorherigen Navigation übernehmen und konsumieren.
|
||||
hinweis = workflowHinweis
|
||||
workflowHinweis = null
|
||||
@@ -508,6 +549,17 @@ export const TagesWorkflowMaske: m.ClosureComponent = () => {
|
||||
? parseInt(routeParams.kontaktadresse_id) : -1
|
||||
guard.registerGuard()
|
||||
laden(kontaktadresseId)
|
||||
// Dauer-Timer starten (läuft über Kontaktwechsel hinweg; die
|
||||
// Startzeit setzt laden() bei jedem Kontakt neu).
|
||||
if (timerId == null) {
|
||||
timerId = window.setInterval(() => {
|
||||
if (startZeit) {
|
||||
const sekunden = Math.floor((Date.now() - startZeit.getTime()) / 1000)
|
||||
notizDauer = formatDauer(sekunden)
|
||||
if (dauerAnzeige) { dauerAnzeige.textContent = notizDauer }
|
||||
}
|
||||
}, DAUER_UPDATE_INTERVALL)
|
||||
}
|
||||
// Tags laden — nur zuordenbare (is_parent=false) Tags.
|
||||
fetchTags().then((result) => {
|
||||
tagListe = result.filter((t: iTag) => !t.is_parent)
|
||||
@@ -526,6 +578,7 @@ export const TagesWorkflowMaske: m.ClosureComponent = () => {
|
||||
return true
|
||||
},
|
||||
onremove: (vnode) => {
|
||||
if (timerId != null) { window.clearInterval(timerId) }
|
||||
guard.cleanup()
|
||||
},
|
||||
view: (vnode) => {
|
||||
@@ -541,15 +594,16 @@ export const TagesWorkflowMaske: m.ClosureComponent = () => {
|
||||
projekt_name: "",
|
||||
kunden_name: "",
|
||||
mitarbeiter_name: null,
|
||||
datum: heuteIso(),
|
||||
uhrzeit_beginn: jetztUhrzeit(),
|
||||
dauer: "",
|
||||
betreff: "",
|
||||
// Datum, Uhrzeit und Dauer kommen aus den Read-Only-
|
||||
// Anzeigen (Status), nicht aus dem Formular.
|
||||
datum: notizDatum || heuteIso(),
|
||||
uhrzeit_beginn: notizUhrzeit || jetztUhrzeit(),
|
||||
dauer: notizDauer,
|
||||
betreff: ((formData.get("betreff") as string) || "").trim(),
|
||||
name_ansprechpartner: kontaktadresse.name,
|
||||
telefon: kontaktadresse.telefon || "",
|
||||
notiz: ((formData.get("notiz") as string) || "").trim(),
|
||||
wiedervorlage_datum: wiedervorlageDatum || "",
|
||||
erledigt: false,
|
||||
tag_ids: selectedTagIds.slice()
|
||||
}
|
||||
createCallLogREST(log).then(() => {
|
||||
@@ -614,6 +668,11 @@ export const TagesWorkflowMaske: m.ClosureComponent = () => {
|
||||
m("i.material-icons", {style: {verticalAlign: 'middle', marginRight: '4px'}}, "arrow_forward"),
|
||||
"Nächster Kontakt")
|
||||
]
|
||||
// Inhaltsbereich der Form als Flex-Spalte rendern (flexContent):
|
||||
// Das Historie-Panel unten füllt damit den verbleibenden Platz
|
||||
// und scrollt in sich — lange Historien machen die Maske nicht
|
||||
// selbst scrollbar.
|
||||
formAttrs.flexContent = true
|
||||
|
||||
// Tag-Optionen und Farb-Map für die Mehrfachauswahl.
|
||||
const tagOptionen: iSelectOption[] = tagListe.map((t: iTag) => {
|
||||
@@ -651,110 +710,193 @@ export const TagesWorkflowMaske: m.ClosureComponent = () => {
|
||||
: null,
|
||||
m("div", {style: {flex: '1', 'min-height': '0'}},
|
||||
m(Form, formAttrs, [
|
||||
// ---- Kopfzeile: Projekt- und Kontaktinformationen ----
|
||||
m("div.w3-panel.w3-theme-l4.w3-round-large", {style: {'margin-bottom': '12px'}},
|
||||
m("div.w3-row",
|
||||
m("div.w3-half",
|
||||
m("p", {style: {margin: '4px 0'}},
|
||||
m("strong", "Kunde: "), kontaktadresse ? kontaktadresse.kunden_name : ""),
|
||||
m("p", {style: {margin: '4px 0'}},
|
||||
m("strong", "Projekt: "), kontaktadresse ? kontaktadresse.projekt_name : "")),
|
||||
m("div.w3-half",
|
||||
m("p", {style: {margin: '4px 0'}},
|
||||
m("strong", "Kontakt: "),
|
||||
kontaktadresse ? kontaktadresse.name : "",
|
||||
kontaktadresse && kontaktadresse.telefon ? " | " + kontaktadresse.telefon : "",
|
||||
kontaktadresse && kontaktadresse.email ? " | " + kontaktadresse.email : ""),
|
||||
m("p", {style: {margin: '4px 0'}},
|
||||
m("strong", "Status: "),
|
||||
m("span.w3-tag" + (kontaktadresse && kontaktadresse.is_erledigt ? ".w3-green" : ".w3-grey"),
|
||||
kontaktadresse && kontaktadresse.is_erledigt ? "Erledigt" : "Offen"),
|
||||
m("button.w3-button.w3-theme.w3-right",
|
||||
{
|
||||
type: "button",
|
||||
style: {'margin-top': '-4px'},
|
||||
onclick: kontaktBearbeiten
|
||||
},
|
||||
m("i.material-icons", {style: {verticalAlign: 'middle', marginRight: '4px'}}, "edit"),
|
||||
"Kontakt bearbeiten")))
|
||||
)
|
||||
// Fester Teil der Maske (Kopfzeile, Neue Gesprächsnotiz):
|
||||
// flex '0 0 auto' verhindert, dass der Flex-Inhaltsbereich
|
||||
// der Form (flexContent) diese Blöcke staucht — bei zu
|
||||
// wenig Platz scrollt die Maske stattdessen als Ganzes.
|
||||
m("div", {style: {flex: '0 0 auto'}},
|
||||
// ---- Kopfzeile: Projekt- und Kontaktinformationen ----
|
||||
m("div.w3-panel.w3-theme-l4.w3-round-large", {style: {'margin-bottom': '12px'}},
|
||||
m("div.w3-row",
|
||||
m("div.w3-half",
|
||||
m("p", {style: {margin: '4px 0'}},
|
||||
m("strong", "Kunde: "), kontaktadresse ? kontaktadresse.kunden_name : ""),
|
||||
m("p", {style: {margin: '4px 0'}},
|
||||
m("strong", "Projekt: "), kontaktadresse ? kontaktadresse.projekt_name : "")),
|
||||
m("div.w3-half",
|
||||
m("p", {style: {margin: '4px 0'}},
|
||||
m("strong", "Kontakt: "),
|
||||
kontaktadresse ? kontaktadresse.name : "",
|
||||
kontaktadresse && kontaktadresse.telefon ? " | " + kontaktadresse.telefon : "",
|
||||
kontaktadresse && kontaktadresse.email ? " | " + kontaktadresse.email : ""),
|
||||
m("p", {style: {margin: '4px 0'}},
|
||||
m("strong", "Status: "),
|
||||
m("span.w3-tag" + (kontaktadresse && kontaktadresse.is_erledigt ? ".w3-green" : ".w3-grey"),
|
||||
kontaktadresse && kontaktadresse.is_erledigt ? "Erledigt" : "Offen"),
|
||||
m("button.w3-button.w3-theme.w3-right",
|
||||
{
|
||||
type: "button",
|
||||
style: {'margin-top': '-4px'},
|
||||
onclick: kontaktBearbeiten
|
||||
},
|
||||
m("i.material-icons", {style: {verticalAlign: 'middle', marginRight: '4px'}}, "edit"),
|
||||
"Kontakt bearbeiten")))
|
||||
)
|
||||
),
|
||||
// ---- Formular für die neue Gesprächsnotiz ----
|
||||
m("h2", {style: {fontSize: '20px'}}, "Neue Gesprächsnotiz"),
|
||||
// Datum, Uhrzeit Beginn und Dauer als Read-Only-Info im
|
||||
// Stil der Kopfzeile (gleiches Panel-Layout): Die Werte
|
||||
// werden beim Öffnen der Maske gesetzt, die Dauer läuft
|
||||
// als Timer mit (mm:ss) und wird direkt im DOM
|
||||
// aktualisiert. w3-third bricht die Zeile auf kleinen
|
||||
// Bildschirmen um.
|
||||
m("div.w3-panel.w3-theme-l4.w3-round-large", {style: {'margin-bottom': '12px'}},
|
||||
m("div.w3-row",
|
||||
m("div.w3-third",
|
||||
m("p", {style: {margin: '4px 0'}},
|
||||
m("strong", "Datum: "), notizDatum || "—")),
|
||||
m("div.w3-third",
|
||||
m("p", {style: {margin: '4px 0'}},
|
||||
m("strong", "Uhrzeit Beginn: "), notizUhrzeit || "—")),
|
||||
m("div.w3-third",
|
||||
m("p", {style: {margin: '4px 0'}},
|
||||
m("strong", "Dauer: "),
|
||||
m("span", {
|
||||
oncreate: (vnode: m.VnodeDOM) => {
|
||||
dauerAnzeige = vnode.dom as HTMLElement
|
||||
}
|
||||
}, notizDauer))))
|
||||
),
|
||||
m("label", "Betreff", m(Input, {
|
||||
name: "betreff",
|
||||
value: notizBetreff,
|
||||
type: "text",
|
||||
oninput: (e: Event) => {
|
||||
notizBetreff = (e.target as HTMLInputElement).value
|
||||
}
|
||||
} as iInput)),
|
||||
m("label", "Notiz",
|
||||
m("textarea.w3-input.w3-border", {
|
||||
name: "notiz",
|
||||
value: notizText,
|
||||
rows: 4,
|
||||
required: true,
|
||||
style: {'margin-bottom': '8px'},
|
||||
oninput: (e: Event) => {
|
||||
notizText = (e.target as HTMLTextAreaElement).value
|
||||
}
|
||||
})),
|
||||
// Tags (links) und Wiedervorlage-Datum (rechts) in
|
||||
// einer Zeile, um Platz zu sparen. w3-twothird/w3-third
|
||||
// brechen die Zeile auf kleinen Bildschirmen um.
|
||||
m("div.w3-row-padding",
|
||||
m("div.w3-twothird",
|
||||
m("label", "Tags", m(MultiSelect, {
|
||||
name: "tag_ids",
|
||||
selected: selectedTagIds,
|
||||
options: tagOptionen,
|
||||
colors: tagColors,
|
||||
placeholder: "kein Tag",
|
||||
modalTitle: "Tags auswählen",
|
||||
editTooltip: "Tags bearbeiten",
|
||||
onchanged: (newSelection: number[]) => {
|
||||
selectedTagIds = newSelection.slice()
|
||||
// Wiedervorlage-Datum automatisch aus den Tags
|
||||
// berechnen (Basis heute), falls ein Tag mit
|
||||
// loest_wiedervorlage_aus gewählt wurde.
|
||||
const vorschlag = berechneWiedervorlageDatum(
|
||||
newSelection, tagListe, heuteIso())
|
||||
if (vorschlag) {
|
||||
wiedervorlageDatum = vorschlag
|
||||
}
|
||||
}
|
||||
} as iMultiSelect))),
|
||||
m("div.w3-third",
|
||||
m("label", "Wiedervorlage Datum", m(Input, {
|
||||
name: "wiedervorlage_datum",
|
||||
value: wiedervorlageDatum,
|
||||
type: "date",
|
||||
oninput: (e: Event) => {
|
||||
wiedervorlageDatum = (e.target as HTMLInputElement).value
|
||||
}
|
||||
} as iInput)))
|
||||
),
|
||||
),
|
||||
// ---- Historie der bisherigen Gesprächsnotizen ----
|
||||
m("h2", {style: {fontSize: '20px'}}, "Bisherige Gesprächsnotizen"),
|
||||
m("div", {style: {'overflow-x': 'auto', 'margin-bottom': '16px'}},
|
||||
m("table.w3-table.w3-bordered.w3-striped",
|
||||
m("thead",
|
||||
m("tr.w3-theme",
|
||||
m("th", "Datum"),
|
||||
m("th", "Mitarbeiter"),
|
||||
m("th", "Notiz"),
|
||||
m("th", "Tags"),
|
||||
m("th", "Wiedervorlage"))),
|
||||
historieSortiert.map((log: iCallLog) =>
|
||||
m("tr",
|
||||
m("td", {style: {'white-space': 'nowrap'}},
|
||||
log.datum + (log.uhrzeit_beginn ? ", " + log.uhrzeit_beginn : "")),
|
||||
m("td", {style: {'white-space': 'nowrap'}},
|
||||
log.mitarbeiter_name || "unbekannt"),
|
||||
m("td", log.notiz || ""),
|
||||
m("td", (log.tags || []).map((t: iTag) =>
|
||||
m("span", {
|
||||
style: {
|
||||
backgroundColor: t.color || '#999',
|
||||
color: 'white',
|
||||
padding: '1px 5px',
|
||||
'border-radius': '3px',
|
||||
'font-size': '11px',
|
||||
'margin-left': '4px',
|
||||
display: 'inline-block'
|
||||
},
|
||||
title: t.name
|
||||
}, t.name))),
|
||||
m("td", {style: {'white-space': 'nowrap'}}, log.wiedervorlage_datum || ""))
|
||||
// Steht bewusst unterhalb des Formulars: Der Nutzer
|
||||
// sieht beim Öffnen der Maske direkt die Eingabefelder
|
||||
// für die neue Notiz, die Historie dient als Nachschlag.
|
||||
// Das Panel füllt den verbleibenden Platz des Inhalts-
|
||||
// bereichs (flex '1 0 200px': füllt auf, schrumpft aber
|
||||
// nie unter 200px — reicht der Platz nicht, scrollt die
|
||||
// Maske als Ganzes). Die Tabelle scrollt darin in sich.
|
||||
// Abgerundeter grauer Panel-Hintergrund (analog der
|
||||
// Kopfzeile); die Schriften im Block sind eine Stufe
|
||||
// kleiner gehalten (Überschrift 18px, Tabelle 13px),
|
||||
// damit die Historie optisch hinter dem Formular
|
||||
// zurücktritt.
|
||||
m("div.w3-panel.w3-light-grey.w3-round-large", {
|
||||
style: {
|
||||
flex: '1 0 200px',
|
||||
'min-height': '0',
|
||||
display: 'flex',
|
||||
'flex-direction': 'column',
|
||||
'margin-bottom': '0'
|
||||
}
|
||||
},
|
||||
m("h2", {style: {fontSize: '18px', flex: '0 0 auto'}}, "Bisherige Gesprächsnotizen"),
|
||||
// Eigener Scroll-Container: Bei langer Historie
|
||||
// scrollt nur die Tabelle, nicht die gesamte Maske.
|
||||
// Wichtig: Der div muss selbst der Scroll-Container
|
||||
// sein (nicht die Tabelle), damit sich position:sticky
|
||||
// der th-Zellen auf ihn bezieht (Muster wie im
|
||||
// Treetable-Widget).
|
||||
m("div", {style: {flex: '1', 'min-height': '0', 'overflow-x': 'auto', 'overflow-y': 'auto'}},
|
||||
m("table.w3-table.w3-bordered.w3-striped", {style: {fontSize: '13px'}},
|
||||
m("thead",
|
||||
m("tr.w3-theme",
|
||||
["Datum", "Mitarbeiter", "Dauer", "Betreff", "Notiz", "Tags", "Wiedervorlage"].map((titel: string) =>
|
||||
m("th.w3-theme", {
|
||||
// Sticky-Header mit eigenem Hintergrund:
|
||||
// Der tr-Hintergrund wandert beim
|
||||
// Scrollen mit den Daten weg, ohne
|
||||
// eigene Fläche scheinen Zeilen
|
||||
// durch (Muster wie Treetable).
|
||||
style: {
|
||||
position: 'sticky',
|
||||
top: '0',
|
||||
'z-index': '1',
|
||||
'text-align': 'left'
|
||||
}
|
||||
}, titel)))),
|
||||
historieSortiert.map((log: iCallLog) =>
|
||||
m("tr",
|
||||
m("td", {style: {'white-space': 'nowrap'}},
|
||||
log.datum + (log.uhrzeit_beginn ? ", " + log.uhrzeit_beginn : "")),
|
||||
m("td", {style: {'white-space': 'nowrap'}},
|
||||
log.mitarbeiter_name || "unbekannt"),
|
||||
m("td", {style: {'white-space': 'nowrap'}}, log.dauer || ""),
|
||||
m("td", log.betreff || ""),
|
||||
m("td", log.notiz || ""),
|
||||
m("td", (log.tags || []).map((t: iTag) =>
|
||||
m("span", {
|
||||
style: {
|
||||
backgroundColor: t.color || '#999',
|
||||
color: 'white',
|
||||
padding: '1px 5px',
|
||||
'border-radius': '3px',
|
||||
'font-size': '11px',
|
||||
'margin-left': '4px',
|
||||
display: 'inline-block'
|
||||
},
|
||||
title: t.name
|
||||
}, t.name))),
|
||||
m("td", {style: {'white-space': 'nowrap'}}, log.wiedervorlage_datum || ""))
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
// ---- Formular für die neue Gesprächsnotiz ----
|
||||
m("h2", {style: {fontSize: '20px'}}, "Neue Gesprächsnotiz"),
|
||||
m("label", "Notiz",
|
||||
m("textarea.w3-input.w3-border", {
|
||||
name: "notiz",
|
||||
value: notizText,
|
||||
rows: 4,
|
||||
required: true,
|
||||
style: {'margin-bottom': '8px'},
|
||||
oninput: (e: Event) => {
|
||||
notizText = (e.target as HTMLTextAreaElement).value
|
||||
}
|
||||
})),
|
||||
m("label", "Tags", m(MultiSelect, {
|
||||
name: "tag_ids",
|
||||
selected: selectedTagIds,
|
||||
options: tagOptionen,
|
||||
colors: tagColors,
|
||||
placeholder: "kein Tag",
|
||||
modalTitle: "Tags auswählen",
|
||||
editTooltip: "Tags bearbeiten",
|
||||
onchanged: (newSelection: number[]) => {
|
||||
selectedTagIds = newSelection.slice()
|
||||
// Wiedervorlage-Datum automatisch aus den Tags
|
||||
// berechnen (Basis heute), falls ein Tag mit
|
||||
// loest_wiedervorlage_aus gewählt wurde.
|
||||
const vorschlag = berechneWiedervorlageDatum(
|
||||
newSelection, tagListe, heuteIso())
|
||||
if (vorschlag) {
|
||||
wiedervorlageDatum = vorschlag
|
||||
}
|
||||
}
|
||||
} as iMultiSelect)),
|
||||
m("label", "Wiedervorlage Datum", m(Input, {
|
||||
name: "wiedervorlage_datum",
|
||||
value: wiedervorlageDatum,
|
||||
type: "date",
|
||||
oninput: (e: Event) => {
|
||||
wiedervorlageDatum = (e.target as HTMLInputElement).value
|
||||
}
|
||||
} as iInput))
|
||||
)
|
||||
])
|
||||
)
|
||||
)
|
||||
|
||||
Reference in new issue
Block a user