From 775b09ebeb81dd0f2d34628273014fdc5a585390 Mon Sep 17 00:00:00 2001 From: Sven Carstensen Date: Wed, 18 Mar 2026 09:09:57 +0100 Subject: [PATCH] feat: Add Stammdaten page for admin to manage own profile - Add AdminProfilePage component for admin to edit own data - Add 'Stammdaten' menu item in admin navigation - Add route /admin/stammdaten for the new page - Use existing POST /api/portal/users endpoint to save changes - Update session context after successful save --- README.md | 5 + frontend/src/App.tsx | 2 + frontend/src/layout/AppShell.tsx | 8 + frontend/src/pages/AdminProfilePage.tsx | 284 ++++++++++++++++++++++++ 4 files changed, 299 insertions(+) create mode 100644 frontend/src/pages/AdminProfilePage.tsx diff --git a/README.md b/README.md index 0a59b27..d3b0034 100644 --- a/README.md +++ b/README.md @@ -105,3 +105,8 @@ Kundenregistrierung: - `cd backend && mvn test` - `cd frontend && npm run build` + +## Docker +docker buildx build --platform linux/amd64 -t gitea.appcreation.de/sven/muh:0.8.0 --push . + +docker run -d --name muh --network br0 --ip 192.168.180.26 --restart unless-stopped -e MUH_MONGODB_URL=mongodb://192.168.180.25:27017/muh -e MUH_TOKEN_SECRET=local-dev-muh-token-secret-2026-03-13 -e MUH_TOKEN_VALIDITY_HOURS=12 -e MUH_ALLOWED_ORIGINS=https://muh.appcreation.de gitea.appcreation.de/sven/muh:0.8.0 \ No newline at end of file diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index cfc2f77..7ce2bb3 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -18,6 +18,7 @@ import ReportTemplatePage from "./pages/ReportTemplatePage"; import InvoiceTemplatePage from "./pages/InvoiceTemplatePage"; import InvoiceManagementPage from "./pages/InvoiceManagementPage"; import PricingPage from "./pages/PricingPage"; +import AdminProfilePage from "./pages/AdminProfilePage"; function ProtectedRoutes() { const { user, ready } = useSession(); @@ -48,6 +49,7 @@ function ProtectedRoutes() { } /> } /> } /> + } /> } /> } /> } /> diff --git a/frontend/src/layout/AppShell.tsx b/frontend/src/layout/AppShell.tsx index 88d447b..008c4e4 100644 --- a/frontend/src/layout/AppShell.tsx +++ b/frontend/src/layout/AppShell.tsx @@ -8,6 +8,7 @@ const PAGE_TITLES: Record = { "/samples/new": "Neuanlage einer Probe", "/portal": "MUH-Portal", "/report-template": "Bericht", + "/admin/stammdaten": "Meine Stammdaten", "/admin/preistabelle": "Preistabelle", "/admin/rechnung/verwalten": "Rechnungsverwaltung", "/admin/rechnung/template": "Rechnungsvorlage", @@ -65,6 +66,13 @@ export default function AppShell() { + `nav-link ${isActive ? "is-active" : ""}`} + > + Stammdaten + + `nav-link ${isActive ? "is-active" : ""}`} diff --git a/frontend/src/pages/AdminProfilePage.tsx b/frontend/src/pages/AdminProfilePage.tsx new file mode 100644 index 0000000..93f222d --- /dev/null +++ b/frontend/src/pages/AdminProfilePage.tsx @@ -0,0 +1,284 @@ +import { useEffect, useState } from "react"; +import { apiGet, apiPost } from "../lib/api"; +import { useSession } from "../lib/session"; +import type { UserRow } from "../lib/types"; + +export default function AdminProfilePage() { + const { user, updateUser } = useSession(); + const [loading, setLoading] = useState(true); + const [saving, setSaving] = useState(false); + const [message, setMessage] = useState(null); + + const [formData, setFormData] = useState({ + displayName: "", + companyName: "", + street: "", + houseNumber: "", + postalCode: "", + city: "", + email: "", + phoneNumber: "", + }); + + // Load current user data + useEffect(() => { + async function loadUserData() { + try { + const users = await apiGet("/portal/users"); + const currentUser = users.find((u) => u.id === user?.id); + if (currentUser) { + setFormData({ + displayName: currentUser.displayName || "", + companyName: currentUser.companyName || "", + street: currentUser.street || "", + houseNumber: currentUser.houseNumber || "", + postalCode: currentUser.postalCode || "", + city: currentUser.city || "", + email: currentUser.email || "", + phoneNumber: currentUser.phoneNumber || "", + }); + } + } catch (error) { + setMessage((error as Error).message); + } finally { + setLoading(false); + } + } + + if (user?.id) { + void loadUserData(); + } + }, [user?.id]); + + async function handleSubmit(e: React.FormEvent) { + e.preventDefault(); + + if (!formData.displayName.trim()) { + setMessage("Name ist erforderlich."); + return; + } + + if (!formData.email.trim()) { + setMessage("E-Mail ist erforderlich."); + return; + } + + setSaving(true); + setMessage(null); + + try { + const response = await apiPost("/portal/users", { + id: user?.id, + displayName: formData.displayName.trim(), + companyName: formData.companyName.trim() || null, + street: formData.street.trim() || null, + houseNumber: formData.houseNumber.trim() || null, + postalCode: formData.postalCode.trim() || null, + city: formData.city.trim() || null, + email: formData.email.trim(), + phoneNumber: formData.phoneNumber.trim() || null, + active: true, + }); + + // Update session user + if (updateUser && user) { + updateUser({ + ...user, + displayName: response.displayName, + companyName: response.companyName, + street: response.street, + houseNumber: response.houseNumber, + postalCode: response.postalCode, + city: response.city, + email: response.email, + phoneNumber: response.phoneNumber, + }); + } + + setMessage("Stammdaten wurden erfolgreich gespeichert."); + setTimeout(() => setMessage(null), 3000); + } catch (error) { + setMessage((error as Error).message); + } finally { + setSaving(false); + } + } + + if (loading) { + return ( +
+
+
Stammdaten werden geladen...
+
+
+ ); + } + + return ( +
+ {/* Header */} +
+
+

Stammdaten

+

Meine Stammdaten

+

+ Verwalten Sie hier Ihre persönlichen und Unternehmensdaten. +

+
+
+ + {/* Status-Meldung */} + {message && ( +
+ {message} +
+ )} + + {/* Stammdaten-Formular */} +
+
+
+

Profil

+

Stammdaten bearbeiten

+
+
+ +
+ + + + + + + + + + + + + + + + +
+ +
+
+
+ + {/* Info-Box */} +
+
+ Hinweis +

+ Ihre Stammdaten werden für die Rechnungsstellung und + Kommunikation verwendet. Stellen Sie sicher, dass die + Daten aktuell und vollständig sind. +

+
+
+
+ ); +}