diff --git a/App_Server/root/crm/model/__init__.py b/App_Server/root/crm/model/__init__.py index 8ae348c..925f33c 100644 --- a/App_Server/root/crm/model/__init__.py +++ b/App_Server/root/crm/model/__init__.py @@ -263,13 +263,14 @@ class Kontaktadresse(dbapp.Model): def to_dict(self): """ Liefert die Kontaktdaten als Dictionary. Die Tabellenspalten werden - über columns2dict serialisiert; projekt_name und kunden_name werden - zusätzlich aus der Projekt- bzw. Kunde-Relationship eingefügt. - is_erledigt wird dynamisch aus den Gesprächsnotizen des Kontakts - berechnet. + über columns2dict serialisiert; projekt_name, projekt_zweck und + kunden_name werden zusätzlich aus der Projekt- bzw. Kunde-Relationship + eingefügt. is_erledigt wird dynamisch aus den Gesprächsnotizen des + Kontakts berechnet. """ result = columns2dict(self) result['projekt_name'] = self.projekt.projektname if self.projekt else None + result['projekt_zweck'] = self.projekt.zweck if self.projekt else None result['kunden_name'] = self.projekt.kunde.name if self.projekt and self.projekt.kunde else None result['is_erledigt'] = self.is_erledigt() return result diff --git a/Client/Components/kontaktadresse.ts b/Client/Components/kontaktadresse.ts index b89775f..9e9dedc 100644 --- a/Client/Components/kontaktadresse.ts +++ b/Client/Components/kontaktadresse.ts @@ -237,6 +237,7 @@ export const Kontaktadresse: m.ClosureComponent = (vnode) => { id: currentKontaktadresse.id, projekt_id: currentKontaktadresse.projekt_id, projekt_name: "", + projekt_zweck: "", // Server-seitig aus den Relationships abgeleitet; // Platzhalter beim Senden. kunden_name: "", diff --git a/Client/Components/projekt.ts b/Client/Components/projekt.ts index 6c29c5a..784dbeb 100644 --- a/Client/Components/projekt.ts +++ b/Client/Components/projekt.ts @@ -1,6 +1,6 @@ import m from "mithril" import {Form, iForm} from "./widgets/form" -import { Input, iInput} from "./widgets/inputs" +import {Input, iInput, Textarea, iTextarea} from "./widgets/inputs" import {Treenode, iTreenode, Treetable, iTreetable, TopbarButton, iTopbarButton} from "./widgets/Treetable_V2" import {BreadcrumbBar, iBreadcrumb, iBreadcrumbBar, parseBc, serializeBc} from "./widgets/breadcrumb" import {iProjekt, fetchProjekte, fetchProjektById, createProjektREST, updateProjektREST} from "../models/projekt" @@ -275,7 +275,7 @@ export const Projekt: m.ClosureComponent = (vnode) => { m(Form, guard.formAttrs(submit) as iForm, [ m("input", {type: "hidden", name: "kunden_id", value: currentProjekt.kunden_id}), m("label", "Projektname", m(Input, {name: "projektname", value: currentProjekt.projektname, type: "text", required: true} as iInput)), - m("label", "Zweck", m(Input, {name: "zweck", value: currentProjekt.zweck, type: "text"} as iInput)), + m("label", "Aufgabenbeschreibung", m(Textarea, {name: "zweck", value: currentProjekt.zweck, rows: 4} as iTextarea)), m("label", "Verantwortlicher", m(Input, {name: "verantwortlicher", value: currentProjekt.verantwortlicher, type: "text"} as iInput)), m("label", "Startdatum", m(Input, {name: "startdatum", value: currentProjekt.startdatum, type: "date"} as iInput)) ]) diff --git a/Client/Components/tages_workflow.ts b/Client/Components/tages_workflow.ts index 7614f47..5ae6084 100644 --- a/Client/Components/tages_workflow.ts +++ b/Client/Components/tages_workflow.ts @@ -10,6 +10,7 @@ import {DirtyMaskHelper} from "./widgets/dirtyguard" import {serializeBc, iBreadcrumb} from "./widgets/breadcrumb" import {iKunde, fetchKunden} from "../models/kunde" import {iKontaktadresse, fetchKontaktadresseById} from "../models/kontaktadresse" +import {iProjekt, fetchProjektById} from "../models/projekt" import {iCallLog, fetchCallLogsByKontaktadresse, createCallLogREST} from "../models/call_log" import {iTag, fetchTags, berechneWiedervorlageDatum} from "../models/tag" import {iTagesWorkflowEntry, iTagesWorkflowFilter, fetchTagesWorkflowList} from "../models/tages_workflow" @@ -700,6 +701,53 @@ export const TagesWorkflowMaske: m.ClosureComponent = () => { m.route.set("/kontaktadresse", {id: geladeneId, bc: bc}) } + /** + * Öffnet ein Modal mit den Projektdetails (Aufgabenbeschreibung, + * Verantwortlicher, Startdatum) des Projekts des aktuellen Kontakts + * (Info-Button hinter dem Projektnamen in der Kopfzeile). Das + * Projekt wird on-demand frisch vom Server geladen, damit + * Verantwortlicher und Startdatum aktuell sind. + */ + const zweckAnzeigen = () => { + if (!kontaktadresse) return + const kontaktadresseId = kontaktadresse.id + fetchProjektById(kontaktadresse.projekt_id).then((projekt: iProjekt) => { + m.mount(gridMountpoints.modalhook, { + view: () => { + return m(ModalView, + { + title: "Aufgabenbeschreibung des Projekts", + type: ModalType.MESSAGE, + width: '40%' + } as iModalView, + m("div.w3-panel", + m("p", m("strong", "Projekt: "), projekt.projektname), + m("p", m("strong", "Verantwortlicher: "), projekt.verantwortlicher || "—"), + m("p", m("strong", "Startdatum: "), projekt.startdatum || "—"), + m("p", m("strong", "Aufgabenbeschreibung: ")), + m("p", projekt.zweck + ? projekt.zweck + : "Für dieses Projekt ist keine Aufgabenbeschreibung hinterlegt.")), + m("div.w3-bar.w3-padding-16", + m("button.w3-button.w3-theme.w3-right", + { + onclick: () => { + m.mount(gridMountpoints.modalhook, null) + } + }, + "Schließen")) + ) + } + }) + }).catch((e: any) => { + // Der Kontakt kann inzwischen gewechselt haben — dann das + // Modal für den alten Kontakt nicht mehr öffnen. + if (kontaktadresse && kontaktadresse.id === kontaktadresseId) { + showServerError(e) + } + }) + } + return m("div.w3-container.w3-auto.form-view-container", m("h1", "Tages-Workflow" + (kontaktadresse ? ": " + kontaktadresse.name : "")), // Hinweis, falls beim Weiterschalten ein Kontakt übersprungen wurde. @@ -721,8 +769,28 @@ export const TagesWorkflowMaske: m.ClosureComponent = () => { 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 : "")), + // Zweck des Projekts als Tooltip: wird beim + // Überfahren von Label und Projektname angezeigt. + m("p", { + style: {margin: '4px 0'}, + title: kontaktadresse && kontaktadresse.projekt_zweck ? kontaktadresse.projekt_zweck : undefined + }, + m("strong", "Projekt: "), kontaktadresse ? kontaktadresse.projekt_name : "", + // Info-Button hinter dem Projektnamen: öffnet + // ein Modal mit der Aufgabenbeschreibung + // (Alternative zum Tooltip, auch auf + // Touch-Geräten nutzbar). + // Ohne Hintergrundfarbe — nur das Icon ist + // sichtbar, der w3-button liefert weiterhin + // den Hover-Effekt als Klick-Feedback. + m("button.w3-button.w3-small", + { + type: "button", + title: "Projekt-Aufgabenbeschreibung anzeigen", + style: {padding: '0 6px', 'margin-left': '4px'}, + onclick: zweckAnzeigen + }, + m("i.material-icons", {style: {fontSize: '16px'}}, "info")))), m("div.w3-half", m("p", {style: {margin: '4px 0'}}, m("strong", "Kontakt: "), diff --git a/Client/models/kontaktadresse.ts b/Client/models/kontaktadresse.ts index c7c43a3..935ae83 100644 --- a/Client/models/kontaktadresse.ts +++ b/Client/models/kontaktadresse.ts @@ -4,8 +4,8 @@ import m from "mithril" * Datenmodell für eine Kontaktadresse (Akquise-Ziel innerhalb eines Projekts). * Jede Kontaktadresse ist genau einem Projekt zugeordnet (FK projekt_id). * Die Feldnamen entsprechen den serverseitigen ORM-Spalten (snake_case). - * projekt_name und kunden_name sind keine ORM-Spalten, sondern werden - * serverseitig aus den Projekt-/Kunde-Relationships abgeleitet und im + * projekt_name, projekt_zweck und kunden_name sind keine ORM-Spalten, sondern + * werden serverseitig aus den Projekt-/Kunde-Relationships abgeleitet und im * JSON ergänzt. is_erledigt ist ebenfalls keine Spalte, sondern wird * serverseitig dynamisch berechnet: True, sobald mindestens eine * Gesprächsnotiz des Kontakts einen Tag mit is_erledigend=True hat. @@ -14,6 +14,7 @@ export interface iKontaktadresse { id: number projekt_id: number projekt_name: string + projekt_zweck: string kunden_name: string anrede: string name: string