diff --git a/Client/Components/kunde.ts b/Client/Components/kunde.ts index 89bbb5c..0186312 100644 --- a/Client/Components/kunde.ts +++ b/Client/Components/kunde.ts @@ -70,11 +70,6 @@ export const kunde: m.ClosureComponent = (vnode) => { * Kundenliste. Jeder Kunde wird zu einem expandierten Treenode ohne Kinder * (expand=true, children=[]) - d.h. ein Blattknoten, der sofort in der * Tabelle sichtbar ist und sich nicht weiter aufklappen lässt. - * - * Crée la structure de Treenode pour la Treetable de la liste des clients à - * partir de la liste des données clients. Chaque client devient un Treenode - * déployé sans enfants (expand=true, children=[]) - c'est-à-dire un nœud - * feuille, immédiatement visible dans le tableau et non dépliable. */ function createKundenTreenodes(): iTreenode[] { return kundenDaten.map((kunde: iKunde) => { diff --git a/Client/Components/projekt.ts b/Client/Components/projekt.ts index b720c8d..43628d1 100644 --- a/Client/Components/projekt.ts +++ b/Client/Components/projekt.ts @@ -1,41 +1,14 @@ 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 iKunde { - id: number - name: string -} - -interface iProjekt { - id: number - kundenId: number - kundenName: string - projektname: string - zweck: string - verantwortlicher: string - startdatum: string -} - -const kundenDaten: iKunde[] = [ - {id: 1, name: "Musterfirma GmbH"}, - {id: 2, name: "Beispiel AG"}, - {id: 3, name: "Test Unternehmen"} -] - -const projektDaten: iProjekt[] = [ - {id: 1, kundenId: 1, kundenName: "Musterfirma GmbH", projektname: "Website-Relaunch", zweck: "Neugestaltung der Unternehmenswebsite", verantwortlicher: "Max Mustermann", startdatum: "2024-01-15"}, - {id: 2, kundenId: 1, kundenName: "Musterfirma GmbH", projektname: "E-Commerce Plattform", zweck: "Online-Shop Entwicklung", verantwortlicher: "Anna Schmidt", startdatum: "2024-03-01"}, - {id: 3, kundenId: 2, kundenName: "Beispiel AG", projektname: "CRM-System", zweck: "Kundenbeziehungsmanagement", verantwortlicher: "Peter Müller", startdatum: "2024-02-20"}, - {id: 4, kundenId: 3, kundenName: "Test Unternehmen", projektname: "Mobile App", zweck: "iOS und Android Entwicklung", verantwortlicher: "Lisa Bauer", startdatum: "2024-04-10"} -] +import {Select, iSelect, iSelectOption} from "./widgets/select" +import {Treenode, iTreenode, Treetable, iTreetable, TopbarButton, iTopbarButton} from "./widgets/Treetable_V2" +import {iProjekt, getProjekteByKundenId, getNewProjektId, addProjekt, updateProjekt} from "../models/projekt" +import {iKunde, getKunden} from "../models/kunde" function createProjektTreenodes(kundenId: number | null = null): iTreenode[] { - let filteredProjekte = kundenId - ? projektDaten.filter(p => p.kundenId === kundenId) - : projektDaten - + let filteredProjekte = getProjekteByKundenId(kundenId) + return filteredProjekte.map((projekt: iProjekt) => { return new Treenode(projekt, true, []) }) @@ -49,11 +22,32 @@ export const projektliste: m.ClosureComponent = (vnode) => { const handleRowMarked = (node: iTreenode) => { selectedNode = node } + + const addProjektRoute = () => { + const params: any = { + id: -1, + kundenname: "", + projektname: "", + zweck: "", + verantwortlicher: "", + startdatum: "" + } + // Nur bei kundengefilterter Liste den Kunden vorbelegen. + if (filterKundenId !== null) { + const kunde = getKunden().find((k: iKunde) => k.id === filterKundenId) + if (kunde) { + params.kundenId = kunde.id + params.kundenname = kunde.name + } + } + m.route.set("/projekt", params) + } const editProjekt = (projekt: iProjekt, e: Event) => { e.stopPropagation() m.route.set("/projekt", { id: projekt.id, + kundenId: projekt.kundenId, kundenname: projekt.kundenName, projektname: projekt.projektname, zweck: projekt.zweck, @@ -127,11 +121,8 @@ export const projektliste: m.ClosureComponent = (vnode) => { treenodes = createProjektTreenodes(filterKundenId) }, 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', @@ -144,7 +135,10 @@ export const projektliste: m.ClosureComponent = (vnode) => { columnmapper: columnmapper, filter: filterFunction, onRowMarked: handleRowMarked - } as iTreetable + } as iTreetable, + m(TopbarButton, {onclick: addProjektRoute, tooltip: "Neues Projekt anlegen"} as iTopbarButton, + m("i.material-icons", "post_add") + ) ) ) ) @@ -153,8 +147,11 @@ export const projektliste: m.ClosureComponent = (vnode) => { } export const Projekt: m.ClosureComponent = (vnode) => { + // currentProjekt hält den Formularzustand. kundenId referenziert den + // Kunden eindeutig, kundenname dient der Anzeige. let currentProjekt: { id: number + kundenId: number kundenname: string projektname: string zweck: string @@ -162,19 +159,25 @@ export const Projekt: m.ClosureComponent = (vnode) => { startdatum: string } = { id: -1, + kundenId: -1, kundenname: "", projektname: "", zweck: "", verantwortlicher: "", startdatum: "" } - + // Ist der Kunde voreingestellt (gefilterte Projektliste)? + let kundeVoreingestellt: boolean = false + return { oninit: (vnode) => { const routeParams = m.route.param() if (routeParams) { + const kundenId = routeParams.kundenId ? parseInt(routeParams.kundenId) : -1 + kundeVoreingestellt = kundenId !== -1 currentProjekt = { id: routeParams.id ? parseInt(routeParams.id) : -1, + kundenId: kundenId, kundenname: routeParams.kundenname || "", projektname: routeParams.projektname || "", zweck: routeParams.zweck || "", @@ -184,15 +187,52 @@ export const Projekt: m.ClosureComponent = (vnode) => { } }, view: (vnode) => { - return m("div.w3-container.w3-twothird", - m("h1", "Projekt-Stammblatt"), - m(Form, {showButtons: true, onsubmit: (null), oncancel: (null)} as iForm, [ - m("label", "Kundenname", m(Input, {name: "kundenname", value: currentProjekt.kundenname, type: "text"} as iInput)), - m("label", "Projektname", m(Input, {name: "projektname", value: currentProjekt.projektname, type: "text"} as iInput)), + const submit = (form: HTMLFormElement): boolean => { + const formData = new FormData(form) + const kundenId = parseInt(formData.get("kundenId") as string) + // Kundennamen zur gewählten kundenId auflösen. + const kunde = getKunden().find((k: iKunde) => k.id === kundenId) + const gespeichertesProjekt: iProjekt = { + id: currentProjekt.id, + kundenId: kundenId, + kundenName: kunde ? kunde.name : "", + projektname: (formData.get("projektname") as string) || "", + zweck: (formData.get("zweck") as string) || "", + verantwortlicher: (formData.get("verantwortlicher") as string) || "", + startdatum: (formData.get("startdatum") as string) || "" + } + if (currentProjekt.id === -1) { + gespeichertesProjekt.id = getNewProjektId() + addProjekt(gespeichertesProjekt) + } else { + updateProjekt(gespeichertesProjekt) + } + m.route.set("/projektliste") + return true + } + const cancel = (e: Event) => { + m.route.set("/projektliste") + } + // Select-Optionen aus der Kundenliste aufbauen. + const kundenOptionen: iSelectOption[] = getKunden().map((k: iKunde) => { + return {value: k.id, label: k.name} + }) + return m("div.w3-container.w3-auto", + m("h1", currentProjekt.id === -1 ? "Neues Projekt anlegen" : "Projekt-Stammblatt"), + m(Form, {showButtons: true, onsubmit: submit, oncancel: cancel} as iForm, [ + m("label", "Kunde", + m(Select, { + name: "kundenId", + value: currentProjekt.kundenId, + options: kundenOptionen, + readonly: kundeVoreingestellt, + required: true + } as iSelect)), + 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", "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/models/kunde.ts b/Client/models/kunde.ts index f92abe2..5362b1f 100644 --- a/Client/models/kunde.ts +++ b/Client/models/kunde.ts @@ -2,8 +2,6 @@ import m from "mithril" /** * Datenmodell für einen Kunden. - * - * Modèle de données pour un client. */ export interface iKunde { id: number @@ -17,9 +15,6 @@ export interface iKunde { /** * In-Memory-Datenspeicher für Kunden. Wird als Fallback solange verwendet, * bis die serverseitigen REST-Endpunkte verfügbar sind. - * - * Stockage en mémoire des clients. Utilisé comme solution de repli tant - * que les points de terminaison REST côté serveur ne sont pas disponibles. */ export const kundenDaten: iKunde[] = [ {id: 1, name: "Musterfirma GmbH", strasse: "Musterstraße 1", plz: "12345", ort: "Musterstadt", land: "Deutschland"}, @@ -30,10 +25,6 @@ export const kundenDaten: iKunde[] = [ /** * Liefert die nächste freie Kunden-ID auf Basis des In-Memory-Speichers. * Sobald die serverseitige ID-Vergabe verfügbar ist, entfällt diese Funktion. - * - * Retourne le prochain identifiant client libre à partir du stockage en - * mémoire. Cette fonction deviendra obsolète dès que l'attribution des - * identifiants côté serveur sera disponible. */ export function getNewKundeId(): number { if (kundenDaten.length === 0) { @@ -44,8 +35,6 @@ export function getNewKundeId(): number { /** * Fügt einen neuen Kunden dem In-Memory-Speicher hinzu. - * - * Ajoute un nouveau client au stockage en mémoire. */ export function addKunde(kunde: iKunde): void { kundenDaten.push(kunde) @@ -53,8 +42,6 @@ export function addKunde(kunde: iKunde): void { /** * Aktualisiert einen bestehenden Kunden im In-Memory-Speicher. - * - * Met à jour un client existant dans le stockage en mémoire. */ export function updateKunde(kunde: iKunde): void { const index = kundenDaten.findIndex((k: iKunde) => k.id === kunde.id) @@ -65,8 +52,6 @@ export function updateKunde(kunde: iKunde): void { /** * Liefert eine Kopie aller Kunden aus dem In-Memory-Speicher. - * - * Retourne une copie de tous les clients du stockage en mémoire. */ export function getKunden(): iKunde[] { return [...kundenDaten] @@ -78,23 +63,15 @@ export function getKunden(): iKunde[] { /* Die folgenden Funktionen kapseln die Kommunikation mit den */ /* serverseitigen REST-Endpunkten. Die Endpunkte sind noch nicht */ /* implementiert und werden später nachgereicht. */ -/* */ -/* Les fonctions suivantes encapsulent la communication avec les */ -/* points de terminaison REST côté serveur. Ceux-ci ne sont pas encore */ -/* implémentés et seront fournis ultérieurement. */ /* ------------------------------------------------------------------ */ /** * Basis-Pfad der Kunden-REST-Endpunkte. - * - * Chemin de base des points de terminaison REST des clients. */ const KUNDE_REST_BASE = "/xhr/kunde" /** * Lädt die Kundenliste vom Server. - * - * Charge la liste des clients depuis le serveur. */ export function fetchKunden(): Promise { return m.request({ @@ -105,8 +82,6 @@ export function fetchKunden(): Promise { /** * Lädt einen einzelnen Kunden anhand seiner ID vom Server. - * - * Charge un client unique identifié par son id depuis le serveur. */ export function fetchKundeById(id: number): Promise { return m.request({ @@ -118,8 +93,6 @@ export function fetchKundeById(id: number): Promise { /** * Legt einen neuen Kunden serverseitig an. - * - * Crée un nouveau client côté serveur. */ export function createKundeREST(kunde: iKunde): Promise { return m.request({ @@ -131,8 +104,6 @@ export function createKundeREST(kunde: iKunde): Promise { /** * Aktualisiert einen bestehenden Kunden serverseitig. - * - * Met à jour un client existant côté serveur. */ export function updateKundeREST(kunde: iKunde): Promise { return m.request({ @@ -144,8 +115,6 @@ export function updateKundeREST(kunde: iKunde): Promise { /** * Löscht einen Kunden serverseitig anhand seiner ID. - * - * Supprime un client côté serveur identifié par son id. */ export function deleteKundeREST(id: number): Promise { return m.request({ diff --git a/Client/models/projekt.ts b/Client/models/projekt.ts new file mode 100644 index 0000000..653f7eb --- /dev/null +++ b/Client/models/projekt.ts @@ -0,0 +1,137 @@ +import m from "mithril" + +/** + * Datenmodell für ein Projekt. + */ +export interface iProjekt { + id: number + kundenId: number + kundenName: string + projektname: string + zweck: string + verantwortlicher: string + startdatum: string +} + +/** + * In-Memory-Datenspeicher für Projekte. Wird als Fallback solange verwendet, + * bis die serverseitigen REST-Endpunkte verfügbar sind. + */ +export const projektDaten: iProjekt[] = [ + {id: 1, kundenId: 1, kundenName: "Musterfirma GmbH", projektname: "Website-Relaunch", zweck: "Neugestaltung der Unternehmenswebsite", verantwortlicher: "Max Mustermann", startdatum: "2024-01-15"}, + {id: 2, kundenId: 1, kundenName: "Musterfirma GmbH", projektname: "E-Commerce Plattform", zweck: "Online-Shop Entwicklung", verantwortlicher: "Anna Schmidt", startdatum: "2024-03-01"}, + {id: 3, kundenId: 2, kundenName: "Beispiel AG", projektname: "CRM-System", zweck: "Kundenbeziehungsmanagement", verantwortlicher: "Peter Müller", startdatum: "2024-02-20"}, + {id: 4, kundenId: 3, kundenName: "Test Unternehmen", projektname: "Mobile App", zweck: "iOS und Android Entwicklung", verantwortlicher: "Lisa Bauer", startdatum: "2024-04-10"} +] + +/** + * Liefert die nächste freie Projekt-ID auf Basis des In-Memory-Speichers. + * Sobald die serverseitige ID-Vergabe verfügbar ist, entfällt diese Funktion. + */ +export function getNewProjektId(): number { + if (projektDaten.length === 0) { + return 1 + } + return Math.max(...projektDaten.map((projekt: iProjekt) => projekt.id)) + 1 +} + +/** + * Fügt ein neues Projekt dem In-Memory-Speicher hinzu. + */ +export function addProjekt(projekt: iProjekt): void { + projektDaten.push(projekt) +} + +/** + * Aktualisiert ein bestehendes Projekt im In-Memory-Speicher. + */ +export function updateProjekt(projekt: iProjekt): void { + const index = projektDaten.findIndex((p: iProjekt) => p.id === projekt.id) + if (index !== -1) { + projektDaten[index] = projekt + } +} + +/** + * Liefert eine Kopie aller Projekte aus dem In-Memory-Speicher. + */ +export function getProjekte(): iProjekt[] { + return [...projektDaten] +} + +/** + * Liefert die Projekte eines bestimmten Kunden. Ist kundenId null, werden + * alle Projekte zurückgegeben. + */ +export function getProjekteByKundenId(kundenId: number | null): iProjekt[] { + return kundenId + ? projektDaten.filter((p: iProjekt) => p.kundenId === kundenId) + : [...projektDaten] +} + +/* ------------------------------------------------------------------ */ +/* 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 Projekt-REST-Endpunkte. + */ +const PROJEKT_REST_BASE = "/xhr/projekt" + +/** + * Lädt die Projektliste vom Server. + */ +export function fetchProjekte(): Promise { + return m.request({ + method: "GET", + url: PROJEKT_REST_BASE + "/list" + }).then((result) => result as iProjekt[]) +} + +/** + * Lädt ein einzelnes Projekt anhand seiner ID vom Server. + */ +export function fetchProjektById(id: number): Promise { + return m.request({ + method: "GET", + url: PROJEKT_REST_BASE + "/get", + params: {id: id} + }).then((result) => result as iProjekt) +} + +/** + * Legt ein neues Projekt serverseitig an. + */ +export function createProjektREST(projekt: iProjekt): Promise { + return m.request({ + method: "POST", + url: PROJEKT_REST_BASE + "/create", + body: projekt + }).then((result) => result as iProjekt) +} + +/** + * Aktualisiert ein bestehendes Projekt serverseitig. + */ +export function updateProjektREST(projekt: iProjekt): Promise { + return m.request({ + method: "PUT", + url: PROJEKT_REST_BASE + "/update", + body: projekt + }).then((result) => result as iProjekt) +} + +/** + * Löscht ein Projekt serverseitig anhand seiner ID. + */ +export function deleteProjektREST(id: number): Promise { + return m.request({ + method: "DELETE", + url: PROJEKT_REST_BASE + "/delete", + params: {id: id} + }).then(() => undefined) +}