feat(crm): Aufgabenbeschreibung des Projekts prominent zugänglich machen
- Projektmaske: Zweck-Feld als Textarea (längere Eingaben), Label in "Aufgabenbeschreibung" umbenannt - Tages-Workflow-Maske: Aufgabenbeschreibung als Tooltip auf dem Projekt-Label/-Namen der Kopfzeile, plus Icon-Button, der ein Modal mit Aufgabenbeschreibung, Verantwortlichem und Startdatum öffnet - Server: Kontaktadresse.to_dict() liefert zusätzlich projekt_zweck Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
1 parent
6e3cc7375d
commit
a23884bf02
5 files changed
+81
-10
No files matched your search
@@ -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
|
||||
|
||||
@@ -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: "",
|
||||
|
||||
@@ -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))
|
||||
])
|
||||
|
||||
@@ -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: "),
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user