Call_Log: Ansprechpartner als Select aus Kontaktadressen des Projekts
Im Call-Log-Formular wird "Name Ansprechpartner" durch ein Select ersetzt, das die Kontaktadressen des gewählten Projekts anbietet, sofern der Name nicht bereits voreingestellt ist (z. B. aus der Kontaktadressenliste). Bei Auswahl einer Kontaktadresse werden Name und Telefon automatisch übernommen. Bei Projektwechsel werden Ansprechpartner und Telefon zurückgesetzt. Kontaktadressenliste: add_call-Button pro Zeile, der eine Gesprächsnotiz mit den ForeignKeys aus der Kontaktadresse (projektId, kundenName, name, telefon) anlegt. AGENTS.md: Vorgabe ergänzt — Farben priorisiert aus app-theme.css. Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
1 parent
ad24981a0b
commit
a8838a1a38
3 files changed
+71
-4
No files matched your search
@@ -6,6 +6,7 @@ In den Programmen sollen aussagekräftige Kommentare in deutsch erstellt werden.
|
|||||||
Verwende bei der GUI primar die widgets, die es in client/Components/widgets gibt. Fehler widgets, gib eines Hinweis und erstelle einen Vorschlag für ein entsprechendes neues Widget.
|
Verwende bei der GUI primar die widgets, die es in client/Components/widgets gibt. Fehler widgets, gib eines Hinweis und erstelle einen Vorschlag für ein entsprechendes neues Widget.
|
||||||
Es soll immer ein responsive Design implmentiert werden, da die App sowohl auf PCs als auch auf mobilen Geräten laufen soll.
|
Es soll immer ein responsive Design implmentiert werden, da die App sowohl auf PCs als auch auf mobilen Geräten laufen soll.
|
||||||
Verwende für Attribute der GUI-Oberfläche nach Möglichkeit Klassen aus NGINX_Server/html/static/w3.css, insbesondere wenn es darum geht, eine Respnosive Design zu implentieren.
|
Verwende für Attribute der GUI-Oberfläche nach Möglichkeit Klassen aus NGINX_Server/html/static/w3.css, insbesondere wenn es darum geht, eine Respnosive Design zu implentieren.
|
||||||
|
Für Farben (foreground und background) verwende priorisiert die Klassen aus NGINX_Server/html/static/app-theme.css
|
||||||
View-Container werden mit `w3-container.w3-auto` gewrappt, damit sie auf kleinen Bildschirmen 100% und auf großen bis max. 1140px (bzw. 95%) zentriert nutzen.
|
View-Container werden mit `w3-container.w3-auto` gewrappt, damit sie auf kleinen Bildschirmen 100% und auf großen bis max. 1140px (bzw. 95%) zentriert nutzen.
|
||||||
|
|
||||||
Bei Mithril `m(...)`-Aufrufen mit längeren Attributlisten (z.B. mehrere `style`-Eigenschaften oder mehrere Attribute) schreibe die Attribute untereinander, sobald sie in einer Zeile nicht mehr gut lesbar sind (Faustregel: mehr als 2–3 Attribute oder ein mehrzeiliges `style`-Objekt). Kurze Attributlisten (1–2 Attribute, einzeiliges `style`) dürfen in einer Zeile bleiben. Formatbeispiel:
|
Bei Mithril `m(...)`-Aufrufen mit längeren Attributlisten (z.B. mehrere `style`-Eigenschaften oder mehrere Attribute) schreibe die Attribute untereinander, sobald sie in einer Zeile nicht mehr gut lesbar sind (Faustregel: mehr als 2–3 Attribute oder ein mehrzeiliges `style`-Objekt). Kurze Attributlisten (1–2 Attribute, einzeiliges `style`) dürfen in einer Zeile bleiben. Formatbeispiel:
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {Treenode, iTreenode, Treetable, iTreetable, TopbarButton, iTopbarButton}
|
|||||||
import {iCallLog, getCallLogsByProjektId, getNewCallLogId, addCallLogData, updateCallLogData} from "../models/call_log"
|
import {iCallLog, getCallLogsByProjektId, getNewCallLogId, addCallLogData, updateCallLogData} from "../models/call_log"
|
||||||
import {iKunde, getKunden} from "../models/kunde"
|
import {iKunde, getKunden} from "../models/kunde"
|
||||||
import {getProjekteByKundenId, getProjektById} from "../models/projekt"
|
import {getProjekteByKundenId, getProjektById} from "../models/projekt"
|
||||||
|
import {getKontaktadressenByProjektId, getKontaktadresseById} from "../models/kontaktadresse"
|
||||||
|
|
||||||
// Aktualisierungsintervall für die Dauer-Anzeige in Millisekunden.
|
// Aktualisierungsintervall für die Dauer-Anzeige in Millisekunden.
|
||||||
// 1000 = jede Sekunde, 60000 = jede Minute.
|
// 1000 = jede Sekunde, 60000 = jede Minute.
|
||||||
@@ -177,6 +178,10 @@ export const Call_Log: m.ClosureComponent = (vnode) => {
|
|||||||
// Sind Kunde und Projekt voreingestellt (Liste wurde projektgefiltert
|
// Sind Kunde und Projekt voreingestellt (Liste wurde projektgefiltert
|
||||||
// oder aus der Projektliste aufgerufen)?
|
// oder aus der Projektliste aufgerufen)?
|
||||||
let kundeVoreingestellt: boolean = false
|
let kundeVoreingestellt: boolean = false
|
||||||
|
// Ist der Ansprechpartner voreingestellt (aus Kontaktadressenliste)?
|
||||||
|
// Wenn ja, wird ein readonly Input gezeigt; sonst ein Select, das die
|
||||||
|
// Kontaktadressen des gewählten Projekts anbietet.
|
||||||
|
let ansprechpartnerVoreingestellt: boolean = false
|
||||||
// Startzeit für die Dauer-Messung (nur bei Neuanlage gesetzt).
|
// Startzeit für die Dauer-Messung (nur bei Neuanlage gesetzt).
|
||||||
let startZeit: Date | null = null
|
let startZeit: Date | null = null
|
||||||
// Timer-Handle für die Dauer-Aktualisierung.
|
// Timer-Handle für die Dauer-Aktualisierung.
|
||||||
@@ -224,6 +229,7 @@ export const Call_Log: m.ClosureComponent = (vnode) => {
|
|||||||
erledigt: routeParams.erledigt === true || routeParams.erledigt === "true"
|
erledigt: routeParams.erledigt === true || routeParams.erledigt === "true"
|
||||||
}
|
}
|
||||||
// Bei Neuanlage Startzeit merken und Dauer-Timer starten.
|
// Bei Neuanlage Startzeit merken und Dauer-Timer starten.
|
||||||
|
ansprechpartnerVoreingestellt = currentLog.nameAnsprechpartner !== ""
|
||||||
if (currentLog.id === -1) {
|
if (currentLog.id === -1) {
|
||||||
startZeit = new Date()
|
startZeit = new Date()
|
||||||
timerId = window.setInterval(() => {
|
timerId = window.setInterval(() => {
|
||||||
@@ -254,6 +260,13 @@ export const Call_Log: m.ClosureComponent = (vnode) => {
|
|||||||
const projektOptionen: iSelectOption[] = getProjekteByKundenId(currentLog.kundenId).map((p) => {
|
const projektOptionen: iSelectOption[] = getProjekteByKundenId(currentLog.kundenId).map((p) => {
|
||||||
return {value: p.id, label: p.projektname}
|
return {value: p.id, label: p.projektname}
|
||||||
})
|
})
|
||||||
|
// Kontaktadressen-Optionen sind vom gewählten Projekt abhängig.
|
||||||
|
// Werden nur benötigt, wenn der Ansprechpartner nicht voreingestellt ist.
|
||||||
|
const kontaktadressenOptionen: iSelectOption[] = !ansprechpartnerVoreingestellt
|
||||||
|
? getKontaktadressenByProjektId(currentLog.projektId).map((ka) => {
|
||||||
|
return {value: ka.id, label: ka.name + (ka.firma ? " (" + ka.firma + ")" : "")}
|
||||||
|
})
|
||||||
|
: []
|
||||||
// Einheit der Dauer-Anzeige im Format mm:ss.
|
// Einheit der Dauer-Anzeige im Format mm:ss.
|
||||||
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"),
|
||||||
@@ -283,7 +296,16 @@ export const Call_Log: m.ClosureComponent = (vnode) => {
|
|||||||
value: currentLog.projektId,
|
value: currentLog.projektId,
|
||||||
options: projektOptionen,
|
options: projektOptionen,
|
||||||
readonly: kundeVoreingestellt,
|
readonly: kundeVoreingestellt,
|
||||||
required: true
|
required: true,
|
||||||
|
onchanged: (projektId: number) => {
|
||||||
|
currentLog.projektId = projektId
|
||||||
|
// Bei Projektwechsel Ansprechpartner und Telefon
|
||||||
|
// zurücksetzen, falls nicht voreingestellt.
|
||||||
|
if (!ansprechpartnerVoreingestellt) {
|
||||||
|
currentLog.nameAnsprechpartner = ""
|
||||||
|
currentLog.telefon = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
} as iSelect)),
|
} 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'}},
|
||||||
@@ -306,9 +328,26 @@ export const Call_Log: m.ClosureComponent = (vnode) => {
|
|||||||
),
|
),
|
||||||
m("label", "Betreff", m(Input, {name: "betreff", value: currentLog.betreff, type: "text"} as iInput)),
|
m("label", "Betreff", m(Input, {name: "betreff", value: currentLog.betreff, type: "text"} as iInput)),
|
||||||
// Ansprechpartner und Telefon in einer Zeile.
|
// Ansprechpartner und Telefon in einer Zeile.
|
||||||
|
// Wenn der Ansprechpartner voreingestellt ist (aus Kontaktadressenliste),
|
||||||
|
// wird ein readonly Input gezeigt; sonst ein Select, das die
|
||||||
|
// Kontaktadressen des gewählten Projekts anbietet. Bei Auswahl
|
||||||
|
// werden Name und Telefon automatisch übernommen.
|
||||||
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'}}, "Name Ansprechpartner",
|
m("label", {style: {flex: '1'}}, "Name Ansprechpartner",
|
||||||
m(Input, {name: "nameAnsprechpartner", value: currentLog.nameAnsprechpartner, type: "text"} as iInput)),
|
ansprechpartnerVoreingestellt
|
||||||
|
? m(Input, {name: "nameAnsprechpartner", value: currentLog.nameAnsprechpartner, type: "text", readonly: true} as iInput)
|
||||||
|
: m(Select, {
|
||||||
|
name: "kontaktadresseId",
|
||||||
|
value: -1,
|
||||||
|
options: kontaktadressenOptionen,
|
||||||
|
onchanged: (kontaktadresseId: number) => {
|
||||||
|
const ka = getKontaktadresseById(kontaktadresseId)
|
||||||
|
if (ka) {
|
||||||
|
currentLog.nameAnsprechpartner = ka.name
|
||||||
|
currentLog.telefon = ka.telefon
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} as iSelect)),
|
||||||
m("label", {style: {flex: '1'}}, "Telefon",
|
m("label", {style: {flex: '1'}}, "Telefon",
|
||||||
m(Input, {name: "telefon", value: currentLog.telefon, type: "tel"} as iInput))
|
m(Input, {name: "telefon", value: currentLog.telefon, type: "tel"} as iInput))
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { Input, iInput, Textarea, iTextarea} from "./widgets/inputs"
|
|||||||
import {Select, iSelect, iSelectOption} from "./widgets/select"
|
import {Select, iSelect, 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 {iKontaktadresse, getKontaktadressenByProjektId, getNewKontaktadresseId, addKontaktadresse, updateKontaktadresse} from "../models/kontaktadresse"
|
import {iKontaktadresse, getKontaktadressenByProjektId, getNewKontaktadresseId, addKontaktadresse, updateKontaktadresse} from "../models/kontaktadresse"
|
||||||
import {iProjekt, getProjekte} from "../models/projekt"
|
import {iProjekt, getProjekte, getProjektById} from "../models/projekt"
|
||||||
|
|
||||||
function createKontaktadresseTreenodes(projektId: number | null = null): iTreenode[] {
|
function createKontaktadresseTreenodes(projektId: number | null = null): iTreenode[] {
|
||||||
let filteredKontaktadressen = getKontaktadressenByProjektId(projektId)
|
let filteredKontaktadressen = getKontaktadressenByProjektId(projektId)
|
||||||
@@ -67,6 +67,30 @@ export const kontaktadresseliste: m.ClosureComponent = (vnode) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const addCallLog = (kontaktadresse: iKontaktadresse, e: Event) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
const now = new Date()
|
||||||
|
const datum = now.toISOString().split('T')[0]
|
||||||
|
const uhrzeit = now.toTimeString().split(':')[0] + ':' + now.toTimeString().split(':')[1]
|
||||||
|
// Kundennamen über das Projekt auflösen.
|
||||||
|
const projekt = getProjektById(kontaktadresse.projektId)
|
||||||
|
m.route.set("/call_log", {
|
||||||
|
id: -1,
|
||||||
|
kundenName: projekt ? projekt.kundenName : "",
|
||||||
|
projektId: kontaktadresse.projektId,
|
||||||
|
projektName: kontaktadresse.projektName,
|
||||||
|
datum: datum,
|
||||||
|
uhrzeitBeginn: uhrzeit,
|
||||||
|
dauer: "",
|
||||||
|
betreff: "",
|
||||||
|
nameAnsprechpartner: kontaktadresse.name,
|
||||||
|
telefon: kontaktadresse.telefon,
|
||||||
|
notiz: "",
|
||||||
|
wiedervorlageDatum: "",
|
||||||
|
erledigt: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const columnmapper: (node: iTreenode) => any[] = (node: iTreenode) => {
|
const columnmapper: (node: iTreenode) => any[] = (node: iTreenode) => {
|
||||||
let kontaktadresse: iKontaktadresse = node.nodevalue
|
let kontaktadresse: iKontaktadresse = node.nodevalue
|
||||||
|
|
||||||
@@ -77,7 +101,10 @@ export const kontaktadresseliste: m.ClosureComponent = (vnode) => {
|
|||||||
m("span", kontaktadresse.email),
|
m("span", kontaktadresse.email),
|
||||||
m("span", kontaktadresse.ort),
|
m("span", kontaktadresse.ort),
|
||||||
m("span", kontaktadresse.projektName),
|
m("span", kontaktadresse.projektName),
|
||||||
m("button.w3-button.w3-small", {style: {padding: '0 2px'}, onclick: (e: Event) => editKontaktadresse(kontaktadresse, e)}, m("i.material-icons", "edit"))
|
m("div", {style: {display: 'flex', gap: '0', 'white-space': 'nowrap'}},
|
||||||
|
m("button.w3-button.w3-small", {style: {padding: '0 2px'}, onclick: (e: Event) => editKontaktadresse(kontaktadresse, e)}, m("i.material-icons", "edit")),
|
||||||
|
m("button.w3-button.w3-small", {title: "Gesprächsnotiz hinzufügen", style: {padding: '0 2px'}, onclick: (e: Event) => addCallLog(kontaktadresse, e)}, m("i.material-icons", "add_call"))
|
||||||
|
)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user