Serverseitige Filterung für Listen-Endpunkte und Einzelabrufe in Listen-Komponenten

Server: Die list-Endpunkte für Kontakt, Projekt, Kontaktadresse und CallLog
akzeptieren nun einen optionalen Query-Parameter (kunden_id bzw. projekt_id)
und filtern im ORM-Query. Neue Migration V01.05 legt Indizes auf den
FK-Spalten an.

Client: Die fetch-Funktionen erhalten einen optionalen Parent-ID-Parameter.
Die Listen-Komponenten übergeben die Parent-ID aus den Route-Params an den
Server und laden nur noch benötigte Daten. Statt kompletter Eltern-Listen
wird per fetchXxxById ein einzelner Datensatz für die Namensauflösung geladen.
Die CallLog-Bearbeitungsmaske behält ihre ungesfilterten Dropdown-Abrufe.

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
gg committed 2026-09-04 15:43:43 +02:00
1 parent e9a2f48ede
commit 93827dde0f
15 files changed
+189 -141

No files matched your search

+13 -1
View File
@@ -12,7 +12,7 @@ erfolgt ausschließlich über __bind_key__.
import datetime import datetime
import decimal import decimal
from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, String from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Index, Integer, String
from sqlalchemy.orm import relationship from sqlalchemy.orm import relationship
from db import dbapp from db import dbapp
@@ -103,6 +103,9 @@ class Kontakt(dbapp.Model):
""" """
__tablename__ = 'kontakte' __tablename__ = 'kontakte'
__bind_key__ = BINDKEY __bind_key__ = BINDKEY
__table_args__ = (
Index('ix_kontakte_kunden_id', 'kunden_id'),
)
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)
kunden_id = Column(Integer, ForeignKey('kunden.id'), nullable=False) kunden_id = Column(Integer, ForeignKey('kunden.id'), nullable=False)
anrede = Column(String(30), nullable=True) anrede = Column(String(30), nullable=True)
@@ -134,6 +137,9 @@ class Projekt(dbapp.Model):
""" """
__tablename__ = 'projekte' __tablename__ = 'projekte'
__bind_key__ = BINDKEY __bind_key__ = BINDKEY
__table_args__ = (
Index('ix_projekte_kunden_id', 'kunden_id'),
)
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)
kunden_id = Column(Integer, ForeignKey('kunden.id'), nullable=False) kunden_id = Column(Integer, ForeignKey('kunden.id'), nullable=False)
projektname = Column(String(200), nullable=False) projektname = Column(String(200), nullable=False)
@@ -163,6 +169,9 @@ class Kontaktadresse(dbapp.Model):
""" """
__tablename__ = 'kontaktadressen' __tablename__ = 'kontaktadressen'
__bind_key__ = BINDKEY __bind_key__ = BINDKEY
__table_args__ = (
Index('ix_kontaktadressen_projekt_id', 'projekt_id'),
)
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)
projekt_id = Column(Integer, ForeignKey('projekte.id'), nullable=False) projekt_id = Column(Integer, ForeignKey('projekte.id'), nullable=False)
anrede = Column(String(30), nullable=True) anrede = Column(String(30), nullable=True)
@@ -200,6 +209,9 @@ class CallLog(dbapp.Model):
""" """
__tablename__ = 'call_logs' __tablename__ = 'call_logs'
__bind_key__ = BINDKEY __bind_key__ = BINDKEY
__table_args__ = (
Index('ix_call_logs_projekt_id', 'projekt_id'),
)
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)
projekt_id = Column(Integer, ForeignKey('projekte.id'), nullable=False) projekt_id = Column(Integer, ForeignKey('projekte.id'), nullable=False)
datum = Column(String(20), nullable=True) datum = Column(String(20), nullable=True)
+10 -3
View File
@@ -17,11 +17,18 @@ from auth.model.errors import ApplicationError, HtmlStatusCode
session = dbapp.session session = dbapp.session
def get_call_logs(): def get_call_logs(projekt_id: int | None = None):
""" """
Liefert alle Gesprächsnotizen, sortiert nach Datum absteigend. Liefert Gesprächsnotizen, sortiert nach Datum absteigend.
Ist projekt_id gesetzt, werden nur die Notizen dieses Projekts
zurückgegeben (serverseitige Filterung). Ohne projekt_id werden
alle Notizen geliefert.
""" """
return CallLog.query.order_by(CallLog.datum.desc()).all() query = CallLog.query
if projekt_id is not None:
query = query.filter(CallLog.projekt_id == projekt_id)
return query.order_by(CallLog.datum.desc()).all()
def get_call_log(call_log_id: int) -> CallLog: def get_call_log(call_log_id: int) -> CallLog:
+17
View File
@@ -91,6 +91,23 @@ def _db_V01_04():
session.commit() session.commit()
def _db_V01_05():
"""
Legt Indizes auf den Fremdschlüsselspalten an, die für die
serverseitige Filterung der Listen-Endpunkte genutzt werden.
CREATE INDEX IF NOT EXISTS ist idempotent und überspringet bereits
vorhandene Indizes.
"""
from sqlalchemy import text
session.execute(text("CREATE INDEX IF NOT EXISTS ix_kontakte_kunden_id ON kontakte (kunden_id)"))
session.execute(text("CREATE INDEX IF NOT EXISTS ix_projekte_kunden_id ON projekte (kunden_id)"))
session.execute(text("CREATE INDEX IF NOT EXISTS ix_kontaktadressen_projekt_id ON kontaktadressen (projekt_id)"))
session.execute(text("CREATE INDEX IF NOT EXISTS ix_call_logs_projekt_id ON call_logs (projekt_id)"))
_updatesysteminfo('01.05')
session.commit()
def checkupdates(): def checkupdates():
""" """
Ermittelt die aktuelle DB-Version aus der Systeminfo-Tabelle und führt alle Ermittelt die aktuelle DB-Version aus der Systeminfo-Tabelle und führt alle
+10 -3
View File
@@ -17,11 +17,18 @@ from auth.model.errors import ApplicationError, HtmlStatusCode
session = dbapp.session session = dbapp.session
def get_kontakte(): def get_kontakte(kunden_id: int | None = None):
""" """
Liefert alle Kontakte, sortiert nach Name. Liefert Kontakte, sortiert nach Name.
Ist kunden_id gesetzt, werden nur die Kontakte dieses Kunden
zurückgegeben (serverseitige Filterung). Ohne kunden_id werden
alle Kontakte geliefert.
""" """
return Kontakt.query.order_by(Kontakt.name).all() query = Kontakt.query
if kunden_id is not None:
query = query.filter(Kontakt.kunden_id == kunden_id)
return query.order_by(Kontakt.name).all()
def get_kontakt(kontakt_id: int) -> Kontakt: def get_kontakt(kontakt_id: int) -> Kontakt:
+10 -3
View File
@@ -17,11 +17,18 @@ from auth.model.errors import ApplicationError, HtmlStatusCode
session = dbapp.session session = dbapp.session
def get_kontaktadressen(): def get_kontaktadressen(projekt_id: int | None = None):
""" """
Liefert alle Kontaktadressen, sortiert nach Name. Liefert Kontaktadressen, sortiert nach Name.
Ist projekt_id gesetzt, werden nur die Kontaktadressen dieses
Projekts zurückgegeben (serverseitige Filterung). Ohne projekt_id
werden alle Kontaktadressen geliefert.
""" """
return Kontaktadresse.query.order_by(Kontaktadresse.name).all() query = Kontaktadresse.query
if projekt_id is not None:
query = query.filter(Kontaktadresse.projekt_id == projekt_id)
return query.order_by(Kontaktadresse.name).all()
def get_kontaktadresse(kontaktadresse_id: int) -> Kontaktadresse: def get_kontaktadresse(kontaktadresse_id: int) -> Kontaktadresse:
+10 -3
View File
@@ -17,11 +17,18 @@ from auth.model.errors import ApplicationError, HtmlStatusCode
session = dbapp.session session = dbapp.session
def get_projekte(): def get_projekte(kunden_id: int | None = None):
""" """
Liefert alle Projekte, sortiert nach projektname. Liefert Projekte, sortiert nach projektname.
Ist kunden_id gesetzt, werden nur die Projekte dieses Kunden
zurückgegeben (serverseitige Filterung). Ohne kunden_id werden
alle Projekte geliefert.
""" """
return Projekt.query.order_by(Projekt.projektname).all() query = Projekt.query
if kunden_id is not None:
query = query.filter(Projekt.kunden_id == kunden_id)
return query.order_by(Projekt.projektname).all()
def get_projekt(projekt_id: int) -> Projekt: def get_projekt(projekt_id: int) -> Projekt:
+12 -8
View File
@@ -80,8 +80,9 @@ def kunde_delete():
@app.get("/xhr/kontakt/list") @app.get("/xhr/kontakt/list")
@authorisation_required([MITARBEITER, KUNDEN_MANAGER]) @authorisation_required([MITARBEITER, KUNDEN_MANAGER])
def kontakt_list(): def kontakt_list():
"""Liefert alle Kontakte als Liste.""" """Liefert Kontakte als Liste. Optional nach kunden_id gefiltert."""
result = [k.to_dict() for k in kontakte.get_kontakte()] kunden_id = request.args.get("kunden_id", type=int)
result = [k.to_dict() for k in kontakte.get_kontakte(kunden_id)]
return jsonify(result), 200 return jsonify(result), 200
@@ -125,8 +126,9 @@ def kontakt_delete():
@app.get("/xhr/projekt/list") @app.get("/xhr/projekt/list")
@authorisation_required([MITARBEITER, KUNDEN_MANAGER]) @authorisation_required([MITARBEITER, KUNDEN_MANAGER])
def projekt_list(): def projekt_list():
"""Liefert alle Projekte als Liste.""" """Liefert Projekte als Liste. Optional nach kunden_id gefiltert."""
result = [p.to_dict() for p in projekte.get_projekte()] kunden_id = request.args.get("kunden_id", type=int)
result = [p.to_dict() for p in projekte.get_projekte(kunden_id)]
return jsonify(result), 200 return jsonify(result), 200
@@ -170,8 +172,9 @@ def projekt_delete():
@app.get("/xhr/kontaktadresse/list") @app.get("/xhr/kontaktadresse/list")
@authorisation_required([MITARBEITER, KUNDEN_MANAGER]) @authorisation_required([MITARBEITER, KUNDEN_MANAGER])
def kontaktadresse_list(): def kontaktadresse_list():
"""Liefert alle Kontaktadressen als Liste.""" """Liefert Kontaktadressen als Liste. Optional nach projekt_id gefiltert."""
result = [k.to_dict() for k in kontaktadressen.get_kontaktadressen()] projekt_id = request.args.get("projekt_id", type=int)
result = [k.to_dict() for k in kontaktadressen.get_kontaktadressen(projekt_id)]
return jsonify(result), 200 return jsonify(result), 200
@@ -215,8 +218,9 @@ def kontaktadresse_delete():
@app.get("/xhr/calllog/list") @app.get("/xhr/calllog/list")
@authorisation_required([MITARBEITER, KUNDEN_MANAGER]) @authorisation_required([MITARBEITER, KUNDEN_MANAGER])
def calllog_list(): def calllog_list():
"""Liefert alle Gesprächsnotizen als Liste.""" """Liefert Gesprächsnotizen als Liste. Optional nach projekt_id gefiltert."""
result = [l.to_dict() for l in call_logs.get_call_logs()] projekt_id = request.args.get("projekt_id", type=int)
result = [l.to_dict() for l in call_logs.get_call_logs(projekt_id)]
return jsonify(result), 200 return jsonify(result), 200
+21 -25
View File
@@ -5,7 +5,7 @@ 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 {iCallLog, fetchCallLogs, fetchCallLogById, createCallLogREST, updateCallLogREST} from "../models/call_log" import {iCallLog, fetchCallLogs, fetchCallLogById, createCallLogREST, updateCallLogREST} from "../models/call_log"
import {iKunde, fetchKunden} from "../models/kunde" import {iKunde, fetchKunden} from "../models/kunde"
import {iProjekt, fetchProjekte} from "../models/projekt" import {iProjekt, fetchProjekte, fetchProjektById} from "../models/projekt"
import {iKontaktadresse, fetchKontaktadressen} from "../models/kontaktadresse" import {iKontaktadresse, fetchKontaktadressen} from "../models/kontaktadresse"
// Aktualisierungsintervall für die Dauer-Anzeige in Millisekunden. // Aktualisierungsintervall für die Dauer-Anzeige in Millisekunden.
@@ -15,10 +15,11 @@ export const call_log_liste: m.ClosureComponent = (vnode) => {
let treenodes: iTreenode[] = [] let treenodes: iTreenode[] = []
let selectedNode: iTreenode | null = null let selectedNode: iTreenode | null = null
let filterProjektId: number | null = null let filterProjektId: number | null = null
// Projektname für die Überschrift, asynchron aufgelöst. // Projektname für die Überschrift, asynchron per fetchProjektById
// aufgelöst. kunden_name wird ebenfalls aus dem Projekt bezogen,
// da es nicht in den Route-Params enthalten ist.
let projektName: string = "" let projektName: string = ""
// Projektliste asynchron laden, für Projektauflösung beim Neuanlegen. let kundenName: string = ""
let projektListe: iProjekt[] = []
const handleRowMarked = (node: iTreenode) => { const handleRowMarked = (node: iTreenode) => {
selectedNode = node selectedNode = node
@@ -41,12 +42,9 @@ export const call_log_liste: m.ClosureComponent = (vnode) => {
erledigt: false erledigt: false
} }
if (filterProjektId !== null) { if (filterProjektId !== null) {
const projekt = projektListe.find((p: iProjekt) => p.id === filterProjektId) params.projekt_id = filterProjektId
if (projekt) { params.projekt_name = projektName
params.projekt_id = projekt.id params.kunden_name = kundenName
params.projekt_name = projekt.projektname
params.kunden_name = projekt.kunden_name
}
} }
m.route.set("/call_log", params) m.route.set("/call_log", params)
} }
@@ -94,24 +92,22 @@ export const call_log_liste: m.ClosureComponent = (vnode) => {
filterProjektId = null filterProjektId = null
projektName = "" projektName = ""
} }
// Gesprächsnotizen asynchron vom Server laden. // Gesprächsnotizen asynchron vom Server laden. Die Filterung
fetchCallLogs().then((result) => { // nach projekt_id erfolgt serverseitig, sofern ein Filter
let filtered = result // gesetzt ist.
if (filterProjektId !== null) { fetchCallLogs(filterProjektId !== null ? filterProjektId : undefined).then((result) => {
filtered = result.filter((l: iCallLog) => l.projekt_id === filterProjektId) treenodes = result.map((log: iCallLog) => new Treenode(log, true, []))
}
treenodes = filtered.map((log: iCallLog) => new Treenode(log, true, []))
}) })
// Projektliste asynchron laden, für Projektnamen-Auflösung. // Projektnamen und Kundennamen asynchron auflösen, falls
fetchProjekte().then((result) => { // nicht in den Route-Params enthalten.
projektListe = result if (filterProjektId !== null) {
if (filterProjektId !== null && projektName === "") { fetchProjektById(filterProjektId).then((projekt: iProjekt) => {
const projekt = projektListe.find((p: iProjekt) => p.id === filterProjektId) if (projektName === "") {
if (projekt) {
projektName = projekt.projektname projektName = projekt.projektname
} }
} kundenName = projekt.kunden_name
}) })
}
}, },
view: (vnode) => { view: (vnode) => {
return m("div.w3-container.w3-auto", { return m("div.w3-container.w3-auto", {
+15 -31
View File
@@ -3,18 +3,15 @@ import {Form, iForm} from "./widgets/form"
import { Input, iInput, Textarea, iTextarea} from "./widgets/inputs" import { Input, iInput, Textarea, iTextarea} from "./widgets/inputs"
import {Treenode, iTreenode, Treetable, iTreetable, TopbarButton, iTopbarButton} from "./widgets/Treetable_V2" import {Treenode, iTreenode, Treetable, iTreetable, TopbarButton, iTopbarButton} from "./widgets/Treetable_V2"
import {iKontakt, fetchKontakte, fetchKontaktById, createKontaktREST, updateKontaktREST} from "../models/kontakt" import {iKontakt, fetchKontakte, fetchKontaktById, createKontaktREST, updateKontaktREST} from "../models/kontakt"
import {iKunde, fetchKunden} from "../models/kunde" import {iKunde, fetchKundeById} from "../models/kunde"
export const kontaktliste: m.ClosureComponent = (vnode) => { export const kontaktliste: m.ClosureComponent = (vnode) => {
let treenodes: iTreenode[] = [] let treenodes: iTreenode[] = []
let selectedNode: iTreenode | null = null let selectedNode: iTreenode | null = null
let filterKundenId: number | null = null let filterKundenId: number | null = null
// Kundenname für die Überschrift, asynchron aus der Kundenliste // Kundenname für die Überschrift, asynchron per fetchKundeById
// aufgelöst. // aufgelöst, falls nicht in den Route-Params enthalten.
let kundenName: string = "" let kundenName: string = ""
// Kundenliste asynchron vom Server laden, für die Kundenauflösung
// beim Anlegen eines neuen Kontakts.
let kundenListe: iKunde[] = []
const handleRowMarked = (node: iTreenode) => { const handleRowMarked = (node: iTreenode) => {
selectedNode = node selectedNode = node
@@ -24,11 +21,8 @@ export const kontaktliste: m.ClosureComponent = (vnode) => {
const params: any = {id: -1} const params: any = {id: -1}
// Nur bei kundengefilterter Liste den Kunden vorbelegen. // Nur bei kundengefilterter Liste den Kunden vorbelegen.
if (filterKundenId !== null) { if (filterKundenId !== null) {
const kunde = kundenListe.find((k: iKunde) => k.id === filterKundenId) params.kunden_id = filterKundenId
if (kunde) { params.kunden_name = kundenName
params.kunden_id = kunde.id
params.kunden_name = kunde.name
}
} }
m.route.set("/kontakt", params) m.route.set("/kontakt", params)
} }
@@ -71,27 +65,17 @@ export const kontaktliste: m.ClosureComponent = (vnode) => {
filterKundenId = null filterKundenId = null
kundenName = "" kundenName = ""
} }
// Kontakte asynchron vom Server laden. m.request löst nach // Kontakte asynchron vom Server laden. Die Filterung nach
// Promise-Auflösung automatisch ein Redraw aus. // kunden_id erfolgt serverseitig, sofern ein Filter gesetzt ist.
fetchKontakte().then((result) => { fetchKontakte(filterKundenId !== null ? filterKundenId : undefined).then((result) => {
let filtered = result treenodes = result.map((kontakt: iKontakt) => new Treenode(kontakt, true, []))
if (filterKundenId !== null) {
filtered = result.filter((k: iKontakt) => k.kunden_id === filterKundenId)
}
treenodes = filtered.map((kontakt: iKontakt) => new Treenode(kontakt, true, []))
})
// Kundenliste asynchron vom Server laden.
fetchKunden().then((result) => {
kundenListe = result
// Fallback: Falls kunden_name nicht in den Route-Params
// enthalten war, aus der Kundenliste auflösen.
if (filterKundenId !== null && kundenName === "") {
const kunde = kundenListe.find((k: iKunde) => k.id === filterKundenId)
if (kunde) {
kundenName = kunde.name
}
}
}) })
// Kundennamen asynchron auflösen, falls nicht in Route-Params.
if (filterKundenId !== null && kundenName === "") {
fetchKundeById(filterKundenId).then((kunde: iKunde) => {
kundenName = kunde.name
})
}
}, },
view: (vnode) => { view: (vnode) => {
return m("div.w3-container.w3-auto", { return m("div.w3-container.w3-auto", {
+16 -28
View File
@@ -3,18 +3,15 @@ import {Form, iForm} from "./widgets/form"
import { Input, iInput, Textarea, iTextarea} from "./widgets/inputs" import { Input, iInput, Textarea, iTextarea} from "./widgets/inputs"
import {Treenode, iTreenode, Treetable, iTreetable, TopbarButton, iTopbarButton} from "./widgets/Treetable_V2" import {Treenode, iTreenode, Treetable, iTreetable, TopbarButton, iTopbarButton} from "./widgets/Treetable_V2"
import {iKontaktadresse, fetchKontaktadressen, fetchKontaktadresseById, createKontaktadresseREST, updateKontaktadresseREST} from "../models/kontaktadresse" import {iKontaktadresse, fetchKontaktadressen, fetchKontaktadresseById, createKontaktadresseREST, updateKontaktadresseREST} from "../models/kontaktadresse"
import {iProjekt, fetchProjekte} from "../models/projekt" import {iProjekt, fetchProjektById} from "../models/projekt"
export const kontaktadresseliste: m.ClosureComponent = (vnode) => { export const kontaktadresseliste: m.ClosureComponent = (vnode) => {
let treenodes: iTreenode[] = [] let treenodes: iTreenode[] = []
let selectedNode: iTreenode | null = null let selectedNode: iTreenode | null = null
let filterProjektId: number | null = null let filterProjektId: number | null = null
// Projektname für die Überschrift, asynchron aus der Projektliste // Projektname für die Überschrift, asynchron per fetchProjektById
// aufgelöst. // aufgelöst, falls nicht in den Route-Params enthalten.
let projektName: string = "" let projektName: string = ""
// Projektliste asynchron vom Server laden, für die Projektauflösung
// beim Anlegen einer neuen Kontaktadresse.
let projektListe: iProjekt[] = []
const handleRowMarked = (node: iTreenode) => { const handleRowMarked = (node: iTreenode) => {
selectedNode = node selectedNode = node
@@ -23,11 +20,8 @@ export const kontaktadresseliste: m.ClosureComponent = (vnode) => {
const addKontaktadresseRoute = () => { const addKontaktadresseRoute = () => {
const params: any = {id: -1} const params: any = {id: -1}
if (filterProjektId !== null) { if (filterProjektId !== null) {
const projekt = projektListe.find((p: iProjekt) => p.id === filterProjektId) params.projekt_id = filterProjektId
if (projekt) { params.projekt_name = projektName
params.projekt_id = projekt.id
params.projekt_name = projekt.projektname
}
} }
m.route.set("/kontaktadresse", params) m.route.set("/kontaktadresse", params)
} }
@@ -97,24 +91,18 @@ export const kontaktadresseliste: m.ClosureComponent = (vnode) => {
filterProjektId = null filterProjektId = null
projektName = "" projektName = ""
} }
// Kontaktadressen asynchron vom Server laden. // Kontaktadressen asynchron vom Server laden. Die Filterung
fetchKontaktadressen().then((result) => { // nach projekt_id erfolgt serverseitig, sofern ein Filter
let filtered = result // gesetzt ist.
if (filterProjektId !== null) { fetchKontaktadressen(filterProjektId !== null ? filterProjektId : undefined).then((result) => {
filtered = result.filter((k: iKontaktadresse) => k.projekt_id === filterProjektId) treenodes = result.map((kontaktadresse: iKontaktadresse) => new Treenode(kontaktadresse, true, []))
}
treenodes = filtered.map((kontaktadresse: iKontaktadresse) => new Treenode(kontaktadresse, true, []))
})
// Projektliste asynchron vom Server laden.
fetchProjekte().then((result) => {
projektListe = result
if (filterProjektId !== null && projektName === "") {
const projekt = projektListe.find((p: iProjekt) => p.id === filterProjektId)
if (projekt) {
projektName = projekt.projektname
}
}
}) })
// Projektnamen asynchron auflösen, falls nicht in Route-Params.
if (filterProjektId !== null && projektName === "") {
fetchProjektById(filterProjektId).then((projekt: iProjekt) => {
projektName = projekt.projektname
})
}
}, },
view: (vnode) => { view: (vnode) => {
return m("div.w3-container.w3-auto", { return m("div.w3-container.w3-auto", {
+15 -28
View File
@@ -3,18 +3,15 @@ import {Form, iForm} from "./widgets/form"
import { Input, iInput} from "./widgets/inputs" import { Input, iInput} from "./widgets/inputs"
import {Treenode, iTreenode, Treetable, iTreetable, TopbarButton, iTopbarButton} from "./widgets/Treetable_V2" import {Treenode, iTreenode, Treetable, iTreetable, TopbarButton, iTopbarButton} from "./widgets/Treetable_V2"
import {iProjekt, fetchProjekte, fetchProjektById, createProjektREST, updateProjektREST} from "../models/projekt" import {iProjekt, fetchProjekte, fetchProjektById, createProjektREST, updateProjektREST} from "../models/projekt"
import {iKunde, fetchKunden} from "../models/kunde" import {iKunde, fetchKundeById} from "../models/kunde"
export const projektliste: m.ClosureComponent = (vnode) => { export const projektliste: m.ClosureComponent = (vnode) => {
let treenodes: iTreenode[] = [] let treenodes: iTreenode[] = []
let selectedNode: iTreenode | null = null let selectedNode: iTreenode | null = null
let filterKundenId: number | null = null let filterKundenId: number | null = null
// Kundenname für die Überschrift, asynchron aus der Kundenliste // Kundenname für die Überschrift, asynchron per fetchKundeById
// aufgelöst. // aufgelöst, falls nicht in den Route-Params enthalten.
let kundenName: string = "" let kundenName: string = ""
// Kundenliste asynchron vom Server laden, für die Kundenauflösung
// beim Anlegen eines neuen Projekts.
let kundenListe: iKunde[] = []
const handleRowMarked = (node: iTreenode) => { const handleRowMarked = (node: iTreenode) => {
selectedNode = node selectedNode = node
@@ -24,11 +21,8 @@ export const projektliste: m.ClosureComponent = (vnode) => {
const params: any = {id: -1} const params: any = {id: -1}
// Nur bei kundengefilterter Liste den Kunden vorbelegen. // Nur bei kundengefilterter Liste den Kunden vorbelegen.
if (filterKundenId !== null) { if (filterKundenId !== null) {
const kunde = kundenListe.find((k: iKunde) => k.id === filterKundenId) params.kunden_id = filterKundenId
if (kunde) { params.kunden_name = kundenName
params.kunden_id = kunde.id
params.kunden_name = kunde.name
}
} }
m.route.set("/projekt", params) m.route.set("/projekt", params)
} }
@@ -108,24 +102,17 @@ export const projektliste: m.ClosureComponent = (vnode) => {
filterKundenId = null filterKundenId = null
kundenName = "" kundenName = ""
} }
// Projekte asynchron vom Server laden. // Projekte asynchron vom Server laden. Die Filterung nach
fetchProjekte().then((result) => { // kunden_id erfolgt serverseitig, sofern ein Filter gesetzt ist.
let filtered = result fetchProjekte(filterKundenId !== null ? filterKundenId : undefined).then((result) => {
if (filterKundenId !== null) { treenodes = result.map((projekt: iProjekt) => new Treenode(projekt, true, []))
filtered = result.filter((p: iProjekt) => p.kunden_id === filterKundenId)
}
treenodes = filtered.map((projekt: iProjekt) => new Treenode(projekt, true, []))
})
// Kundenliste asynchron vom Server laden.
fetchKunden().then((result) => {
kundenListe = result
if (filterKundenId !== null && kundenName === "") {
const kunde = kundenListe.find((k: iKunde) => k.id === filterKundenId)
if (kunde) {
kundenName = kunde.name
}
}
}) })
// Kundennamen asynchron auflösen, falls nicht in Route-Params.
if (filterKundenId !== null && kundenName === "") {
fetchKundeById(filterKundenId).then((kunde: iKunde) => {
kundenName = kunde.name
})
}
}, },
view: (vnode) => { view: (vnode) => {
return m("div.w3-container.w3-auto", { return m("div.w3-container.w3-auto", {
+10 -2
View File
@@ -36,11 +36,19 @@ const CALLLOG_REST_BASE = "/xhr/calllog"
/** /**
* Lädt die Liste der Gesprächsnotizen vom Server. * Lädt die Liste der Gesprächsnotizen vom Server.
* Ist projektId gesetzt (>= 0), werden nur die Notizen dieses Projekts
* geliefert (serverseitige Filterung). Ohne Parameter werden alle
* Notizen geliefert.
*/ */
export function fetchCallLogs(): Promise<iCallLog[]> { export function fetchCallLogs(projektId?: number): Promise<iCallLog[]> {
const params: Record<string, any> = {}
if (projektId !== undefined && projektId >= 0) {
params.projekt_id = projektId
}
return m.request({ return m.request({
method: "GET", method: "GET",
url: CALLLOG_REST_BASE + "/list" url: CALLLOG_REST_BASE + "/list",
params: params
}).then((result) => result as iCallLog[]) }).then((result) => result as iCallLog[])
} }
+10 -2
View File
@@ -33,11 +33,19 @@ const KONTAKT_REST_BASE = "/xhr/kontakt"
/** /**
* Lädt die Kontaktliste vom Server. * Lädt die Kontaktliste vom Server.
* Ist kundenId gesetzt (>= 0), werden nur die Kontakte dieses Kunden
* geliefert (serverseitige Filterung). Ohne Parameter werden alle
* Kontakte geliefert.
*/ */
export function fetchKontakte(): Promise<iKontakt[]> { export function fetchKontakte(kundenId?: number): Promise<iKontakt[]> {
const params: Record<string, any> = {}
if (kundenId !== undefined && kundenId >= 0) {
params.kunden_id = kundenId
}
return m.request({ return m.request({
method: "GET", method: "GET",
url: KONTAKT_REST_BASE + "/list" url: KONTAKT_REST_BASE + "/list",
params: params
}).then((result) => result as iKontakt[]) }).then((result) => result as iKontakt[])
} }
+10 -2
View File
@@ -38,11 +38,19 @@ const KONTAKTADRESSE_REST_BASE = "/xhr/kontaktadresse"
/** /**
* Lädt die Kontaktadressenliste vom Server. * Lädt die Kontaktadressenliste vom Server.
* Ist projektId gesetzt (>= 0), werden nur die Kontaktadressen dieses
* Projekts geliefert (serverseitige Filterung). Ohne Parameter werden
* alle Kontaktadressen geliefert.
*/ */
export function fetchKontaktadressen(): Promise<iKontaktadresse[]> { export function fetchKontaktadressen(projektId?: number): Promise<iKontaktadresse[]> {
const params: Record<string, any> = {}
if (projektId !== undefined && projektId >= 0) {
params.projekt_id = projektId
}
return m.request({ return m.request({
method: "GET", method: "GET",
url: KONTAKTADRESSE_REST_BASE + "/list" url: KONTAKTADRESSE_REST_BASE + "/list",
params: params
}).then((result) => result as iKontaktadresse[]) }).then((result) => result as iKontaktadresse[])
} }
+10 -2
View File
@@ -30,11 +30,19 @@ const PROJEKT_REST_BASE = "/xhr/projekt"
/** /**
* Lädt die Projektliste vom Server. * Lädt die Projektliste vom Server.
* Ist kundenId gesetzt (>= 0), werden nur die Projekte dieses Kunden
* geliefert (serverseitige Filterung). Ohne Parameter werden alle
* Projekte geliefert.
*/ */
export function fetchProjekte(): Promise<iProjekt[]> { export function fetchProjekte(kundenId?: number): Promise<iProjekt[]> {
const params: Record<string, any> = {}
if (kundenId !== undefined && kundenId >= 0) {
params.kunden_id = kundenId
}
return m.request({ return m.request({
method: "GET", method: "GET",
url: PROJEKT_REST_BASE + "/list" url: PROJEKT_REST_BASE + "/list",
params: params
}).then((result) => result as iProjekt[]) }).then((result) => result as iProjekt[])
} }