From 1c99dcf326690074e30a06f673ddccc1113b845c Mon Sep 17 00:00:00 2001 From: Gerd Date: Mon, 31 Aug 2026 18:25:21 +0200 Subject: [PATCH] =?UTF-8?q?Gespr=C3=A4chsnotizen:=20Datenmodell=20ausgelag?= =?UTF-8?q?ert,=20Neuanlage=20mit=20Selects=20und=20responsives=20Design?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Datenmodell und In-Memory-Funktionen für Gesprächsnotizen in Client/models/call_log.ts ausgelagert (inkl. REST-API-Gerüst). call_log.ts nutzt das Modell; Container auf w3-container.w3-auto umgestellt. Call_Log-Formular mit Select-Widgets für Kunde und Projekt (Projekt-Optionen abhängig vom gewählten Kunden, Voreinstellung readonly bei projektfiltrierter Liste). call_log_liste: TopbarButton zur Neuanlage, Vorbelegung bei projektgefilterter Liste. projekt-Modell um Hilfsfunktion getProjektById ergänzt. Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe --- Client/Components/call_log.ts | 132 +++++++++++++++++++++---------- Client/models/call_log.ts | 144 ++++++++++++++++++++++++++++++++++ Client/models/projekt.ts | 7 ++ 3 files changed, 243 insertions(+), 40 deletions(-) create mode 100644 Client/models/call_log.ts diff --git a/Client/Components/call_log.ts b/Client/Components/call_log.ts index b2e52c2..0c82a61 100644 --- a/Client/Components/call_log.ts +++ b/Client/Components/call_log.ts @@ -1,37 +1,15 @@ import m from "mithril" import {Form, iForm} from "./widgets/form" import { Input, iInput} from "./widgets/inputs" -import {Treenode, iTreenode, Treetable, iTreetable} from "./widgets/Treetable_V2" - -interface iCallLog { - id: number - kundenName: string - projektId: number - projektName: string - datum: string - uhrzeitBeginn: string - dauer: string - betreff: string - nameAnsprechpartner: string - telefon: string - notiz: string - wiedervorlageDatum: string - erledigt: boolean -} - -const callLogDaten: iCallLog[] = [ - {id: 1, kundenName: "Musterfirma GmbH", projektId: 1, projektName: "Website-Relaunch", datum: "2024-01-20", uhrzeitBeginn: "09:30", dauer: "45", betreff: "Anforderungen klären", nameAnsprechpartner: "Herr Schmidt", telefon: "+49 123 45678", notiz: "Kunde möchte responsive Design und neue Farbgestaltung. Budget noch offen.", wiedervorlageDatum: "2024-01-27", erledigt: false}, - {id: 2, kundenName: "Musterfirma GmbH", projektId: 1, projektName: "Website-Relaunch", datum: "2024-01-25", uhrzeitBeginn: "14:00", dauer: "30", betreff: "Feedback zum Design", nameAnsprechpartner: "Frau Müller", telefon: "+49 123 45679", notiz: "Design-Entwürfe wurden positiv aufgenommen. Kleine Änderungen an der Navigation nötig.", wiedervorlageDatum: "2024-02-01", erledigt: false}, - {id: 3, kundenName: "Beispiel AG", projektId: 2, projektName: "E-Commerce Plattform", datum: "2024-02-10", uhrzeitBeginn: "10:00", dauer: "60", betreff: "Technische Anforderungen", nameAnsprechpartner: "Herr Weber", telefon: "+49 123 45680", notiz: "Payment-Gateway und Versand-Schnittstellen müssen integriert werden. Zeitplan besprochen.", wiedervorlageDatum: "2024-02-15", erledigt: true}, - {id: 4, kundenName: "Beispiel AG", projektId: 3, projektName: "CRM-System", datum: "2024-02-18", uhrzeitBeginn: "11:15", dauer: "20", betreff: "Schulungstermin", nameAnsprechpartner: "Frau Bauer", telefon: "+49 123 45681", notiz: "Schulung für 10 Mitarbeiter geplant. Raum noch zu buchen.", wiedervorlageDatum: "2024-02-20", erledigt: false}, - {id: 5, kundenName: "Test Unternehmen", projektId: 4, projektName: "Mobile App", datum: "2024-03-05", uhrzeitBeginn: "15:45", dauer: "35", betreff: "Feature-Request", nameAnsprechpartner: "Herr König", telefon: "+49 123 45682", notiz: "Kunde wünscht Push-Benachrichtigungen und Offline-Modus.", wiedervorlageDatum: "2024-03-12", erledigt: false} -] +import {Select, iSelect, iSelectOption} from "./widgets/select" +import {Treenode, iTreenode, Treetable, iTreetable, TopbarButton, iTopbarButton} from "./widgets/Treetable_V2" +import {iCallLog, getCallLogsByProjektId, getNewCallLogId, addCallLogData, updateCallLogData} from "../models/call_log" +import {iKunde, getKunden} from "../models/kunde" +import {getProjekteByKundenId, getProjektById} from "../models/projekt" function createCallLogTreenodes(projektId: number | null = null): iTreenode[] { - let filteredLogs = projektId - ? callLogDaten.filter(log => log.projektId === projektId) - : callLogDaten - + let filteredLogs = getCallLogsByProjektId(projektId) + return filteredLogs.map((log: iCallLog) => { return new Treenode(log, true, []) }) @@ -45,6 +23,34 @@ export const call_log_liste: m.ClosureComponent = (vnode) => { const handleRowMarked = (node: iTreenode) => { selectedNode = node } + + const addCallLogRoute = () => { + const now = new Date() + const datum = now.toISOString().split('T')[0] + const uhrzeit = now.toTimeString().split(':')[0] + ':' + now.toTimeString().split(':')[1] + const params: any = { + id: -1, + datum: datum, + uhrzeitBeginn: uhrzeit, + dauer: "", + betreff: "", + nameAnsprechpartner: "", + telefon: "", + notiz: "", + wiedervorlageDatum: "", + erledigt: false + } + // Bei projektgefilterter Liste Kunde und Projekt vorbelegen. + if (filterProjektId !== null) { + const projekt = getProjektById(filterProjektId) + if (projekt) { + params.kundenName = projekt.kundenName + params.projektId = projekt.id + params.projektName = projekt.projektname + } + } + m.route.set("/call_log", params) + } const editCallLog = (log: iCallLog, e: Event) => { e.stopPropagation() @@ -106,11 +112,8 @@ export const call_log_liste: m.ClosureComponent = (vnode) => { treenodes = createCallLogTreenodes(filterProjektId) }, view: (vnode) => { - return m("div.w3-container", { + return m("div.w3-container.w3-auto", { style: { - 'max-width': '80%', - 'margin-left': 'auto', - 'margin-right': 'auto', display: 'flex', 'flex-direction': 'column', height: 'inherit', @@ -123,7 +126,10 @@ export const call_log_liste: m.ClosureComponent = (vnode) => { columnmapper: columnmapper, filter: filterFunction, onRowMarked: handleRowMarked - } as iTreetable + } as iTreetable, + m(TopbarButton, {onclick: addCallLogRoute, tooltip: "Neue Gesprächsnotiz anlegen"} as iTopbarButton, + m("i.material-icons", "add_call") + ) ) ) ) @@ -132,8 +138,11 @@ export const call_log_liste: m.ClosureComponent = (vnode) => { } export const Call_Log: m.ClosureComponent = (vnode) => { + // currentLog hält den Formularzustand. kundenId ist intern und wird über + // das Projekt aufgelöst; kundenName/projektName dienen der Anzeige. let currentLog: { id: number + kundenId: number kundenName: string projektId: number projektName: string @@ -148,6 +157,7 @@ export const Call_Log: m.ClosureComponent = (vnode) => { erledigt: boolean } = { id: -1, + kundenId: -1, kundenName: "", projektId: -1, projektName: "", @@ -161,15 +171,27 @@ export const Call_Log: m.ClosureComponent = (vnode) => { wiedervorlageDatum: "", erledigt: false } - + // Sind Kunde und Projekt voreingestellt (Liste wurde projektgefiltert + // oder aus der Projektliste aufgerufen)? + let kundeVoreingestellt: boolean = false + return { oninit: (vnode) => { const routeParams = m.route.param() if (routeParams) { + const projektId = routeParams.projektId ? parseInt(routeParams.projektId) : -1 + kundeVoreingestellt = projektId !== -1 + // kundenId aus dem Projekt ableiten, falls nicht direkt gesetzt. + let kundenId = -1 + if (kundeVoreingestellt) { + const projekt = getProjektById(projektId) + if (projekt) { kundenId = projekt.kundenId } + } currentLog = { id: routeParams.id ? parseInt(routeParams.id) : -1, + kundenId: kundenId, kundenName: routeParams.kundenName || "", - projektId: routeParams.projektId ? parseInt(routeParams.projektId) : -1, + projektId: projektId, projektName: routeParams.projektName || "", datum: routeParams.datum || "", uhrzeitBeginn: routeParams.uhrzeitBeginn || "", @@ -184,10 +206,40 @@ export const Call_Log: m.ClosureComponent = (vnode) => { } }, view: (vnode) => { - return m("div.w3-container.w3-twothird", - m("h1", "Gesprächsnotiz"), + // Select-Optionen aus Kunden- und Projektliste aufbauen. + const kundenOptionen: iSelectOption[] = getKunden().map((k: iKunde) => { + return {value: k.id, label: k.name} + }) + // Projekt-Optionen sind vom gewählten Kunden abhängig. + const projektOptionen: iSelectOption[] = getProjekteByKundenId(currentLog.kundenId).map((p) => { + return {value: p.id, label: p.projektname} + }) + return m("div.w3-container.w3-auto", + m("h1", currentLog.id === -1 ? "Neue Gesprächsnotiz anlegen" : "Gesprächsnotiz"), m(Form, {showButtons: true, onsubmit: (null), oncancel: (null)} as iForm, [ - m("h4.w3-padding-small", currentLog.kundenName + " / " + currentLog.projektName), + m("label", "Kunde", + m(Select, { + name: "kundenId", + value: currentLog.kundenId, + options: kundenOptionen, + readonly: kundeVoreingestellt, + required: true, + onchanged: (kundenId: number) => { + // Bei Kundenwechsel die kundenId übernehmen + // und projektId zurücksetzen, da die Projekte + // kundenabhängig sind. + currentLog.kundenId = kundenId + currentLog.projektId = -1 + } + } as iSelect)), + m("label", "Projekt", + m(Select, { + name: "projektId", + value: currentLog.projektId, + options: projektOptionen, + readonly: kundeVoreingestellt, + required: true + } as iSelect)), m("label", "Datum", m(Input, {name: "datum", value: currentLog.datum, type: "date"} as iInput)), m("label", "Uhrzeit Beginn", m(Input, {name: "uhrzeitBeginn", value: currentLog.uhrzeitBeginn, type: "time"} as iInput)), m("label", "Dauer (Minuten)", m(Input, {name: "dauer", value: currentLog.dauer, type: "number"} as iInput)), @@ -197,7 +249,7 @@ export const Call_Log: m.ClosureComponent = (vnode) => { m("label", "Notiz", m(Input, {name: "notiz", value: currentLog.notiz, type: "text"} as iInput)), m("label", "Wiedervorlage Datum", m(Input, {name: "wiedervorlageDatum", value: currentLog.wiedervorlageDatum, type: "date"} as iInput)), m("label", m(Input, {name: "erledigt", type: "checkbox", value: currentLog.erledigt} as iInput), "Erledigt") - ]) + ]) ) } } diff --git a/Client/models/call_log.ts b/Client/models/call_log.ts new file mode 100644 index 0000000..ef25ae7 --- /dev/null +++ b/Client/models/call_log.ts @@ -0,0 +1,144 @@ +import m from "mithril" + +/** + * Datenmodell für eine Gesprächsnotiz (Call-Log). + */ +export interface iCallLog { + id: number + kundenName: string + projektId: number + projektName: string + datum: string + uhrzeitBeginn: string + dauer: string + betreff: string + nameAnsprechpartner: string + telefon: string + notiz: string + wiedervorlageDatum: string + erledigt: boolean +} + +/** + * In-Memory-Datenspeicher für Gesprächsnotizen. Wird als Fallback solange + * verwendet, bis die serverseitigen REST-Endpunkte verfügbar sind. + */ +export const callLogDaten: iCallLog[] = [ + {id: 1, kundenName: "Musterfirma GmbH", projektId: 1, projektName: "Website-Relaunch", datum: "2024-01-20", uhrzeitBeginn: "09:30", dauer: "45", betreff: "Anforderungen klären", nameAnsprechpartner: "Herr Schmidt", telefon: "+49 123 45678", notiz: "Kunde möchte responsive Design und neue Farbgestaltung. Budget noch offen.", wiedervorlageDatum: "2024-01-27", erledigt: false}, + {id: 2, kundenName: "Musterfirma GmbH", projektId: 1, projektName: "Website-Relaunch", datum: "2024-01-25", uhrzeitBeginn: "14:00", dauer: "30", betreff: "Feedback zum Design", nameAnsprechpartner: "Frau Müller", telefon: "+49 123 45679", notiz: "Design-Entwürfe wurden positiv aufgenommen. Kleine Änderungen an der Navigation nötig.", wiedervorlageDatum: "2024-02-01", erledigt: false}, + {id: 3, kundenName: "Beispiel AG", projektId: 2, projektName: "E-Commerce Plattform", datum: "2024-02-10", uhrzeitBeginn: "10:00", dauer: "60", betreff: "Technische Anforderungen", nameAnsprechpartner: "Herr Weber", telefon: "+49 123 45680", notiz: "Payment-Gateway und Versand-Schnittstellen müssen integriert werden. Zeitplan besprochen.", wiedervorlageDatum: "2024-02-15", erledigt: true}, + {id: 4, kundenName: "Beispiel AG", projektId: 3, projektName: "CRM-System", datum: "2024-02-18", uhrzeitBeginn: "11:15", dauer: "20", betreff: "Schulungstermin", nameAnsprechpartner: "Frau Bauer", telefon: "+49 123 45681", notiz: "Schulung für 10 Mitarbeiter geplant. Raum noch zu buchen.", wiedervorlageDatum: "2024-02-20", erledigt: false}, + {id: 5, kundenName: "Test Unternehmen", projektId: 4, projektName: "Mobile App", datum: "2024-03-05", uhrzeitBeginn: "15:45", dauer: "35", betreff: "Feature-Request", nameAnsprechpartner: "Herr König", telefon: "+49 123 45682", notiz: "Kunde wünscht Push-Benachrichtigungen und Offline-Modus.", wiedervorlageDatum: "2024-03-12", erledigt: false} +] + +/** + * Liefert die nächste freie Call-Log-ID auf Basis des In-Memory-Speichers. + * Sobald die serverseitige ID-Vergabe verfügbar ist, entfällt diese Funktion. + */ +export function getNewCallLogId(): number { + if (callLogDaten.length === 0) { + return 1 + } + return Math.max(...callLogDaten.map((log: iCallLog) => log.id)) + 1 +} + +/** + * Fügt eine neue Gesprächsnotiz dem In-Memory-Speicher hinzu. + */ +export function addCallLogData(log: iCallLog): void { + callLogDaten.push(log) +} + +/** + * Aktualisiert eine bestehende Gesprächsnotiz im In-Memory-Speicher. + */ +export function updateCallLogData(log: iCallLog): void { + const index = callLogDaten.findIndex((l: iCallLog) => l.id === log.id) + if (index !== -1) { + callLogDaten[index] = log + } +} + +/** + * Liefert eine Kopie aller Gesprächsnotizen aus dem In-Memory-Speicher. + */ +export function getCallLogs(): iCallLog[] { + return [...callLogDaten] +} + +/** + * Liefert die Gesprächsnotizen eines bestimmten Projekts. Ist projektId null, + * werden alle Gesprächsnotizen zurückgegeben. + */ +export function getCallLogsByProjektId(projektId: number | null): iCallLog[] { + return projektId + ? callLogDaten.filter((l: iCallLog) => l.projektId === projektId) + : [...callLogDaten] +} + +/* ------------------------------------------------------------------ */ +/* REST-API-Aufrufe (Client-Seite) */ +/* ------------------------------------------------------------------ */ +/* Die folgenden Funktionen kapseln die Kommunikation mit den */ +/* serverseitigen REST-Endpunkten. Die Endpunkte sind noch nicht */ +/* implementiert und werden später nachgereicht. */ +/* ------------------------------------------------------------------ */ + +/** + * Basis-Pfad der Call-Log-REST-Endpunkte. + */ +const CALLLOG_REST_BASE = "/xhr/calllog" + +/** + * Lädt die Liste der Gesprächsnotizen vom Server. + */ +export function fetchCallLogs(): Promise { + return m.request({ + method: "GET", + url: CALLLOG_REST_BASE + "/list" + }).then((result) => result as iCallLog[]) +} + +/** + * Lädt eine einzelne Gesprächsnotiz anhand ihrer ID vom Server. + */ +export function fetchCallLogById(id: number): Promise { + return m.request({ + method: "GET", + url: CALLLOG_REST_BASE + "/get", + params: {id: id} + }).then((result) => result as iCallLog) +} + +/** + * Legt eine neue Gesprächsnotiz serverseitig an. + */ +export function createCallLogREST(log: iCallLog): Promise { + return m.request({ + method: "POST", + url: CALLLOG_REST_BASE + "/create", + body: log + }).then((result) => result as iCallLog) +} + +/** + * Aktualisiert eine bestehende Gesprächsnotiz serverseitig. + */ +export function updateCallLogREST(log: iCallLog): Promise { + return m.request({ + method: "PUT", + url: CALLLOG_REST_BASE + "/update", + body: log + }).then((result) => result as iCallLog) +} + +/** + * Löscht eine Gesprächsnotiz serverseitig anhand ihrer ID. + */ +export function deleteCallLogREST(id: number): Promise { + return m.request({ + method: "DELETE", + url: CALLLOG_REST_BASE + "/delete", + params: {id: id} + }).then(() => undefined) +} diff --git a/Client/models/projekt.ts b/Client/models/projekt.ts index 653f7eb..b2793a9 100644 --- a/Client/models/projekt.ts +++ b/Client/models/projekt.ts @@ -69,6 +69,13 @@ export function getProjekteByKundenId(kundenId: number | null): iProjekt[] { : [...projektDaten] } +/** + * Liefert ein einzelnes Projekt anhand seiner ID oder undefined. + */ +export function getProjektById(id: number): iProjekt | undefined { + return projektDaten.find((p: iProjekt) => p.id === id) +} + /* ------------------------------------------------------------------ */ /* REST-API-Aufrufe (Client-Seite) */ /* ------------------------------------------------------------------ */