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 + + + + + + Name * + + setFormData({ ...formData, displayName: e.target.value }) + } + placeholder="Ihr Name" + required + disabled={saving} + /> + + + + Firma + + setFormData({ ...formData, companyName: e.target.value }) + } + placeholder="Firmenname" + disabled={saving} + /> + + + + Straße + + setFormData({ ...formData, street: e.target.value }) + } + placeholder="Straße" + disabled={saving} + /> + + + + Hausnummer + + setFormData({ ...formData, houseNumber: e.target.value }) + } + placeholder="Hausnummer" + disabled={saving} + /> + + + + PLZ + + setFormData({ ...formData, postalCode: e.target.value }) + } + placeholder="Postleitzahl" + disabled={saving} + /> + + + + Ort + + setFormData({ ...formData, city: e.target.value }) + } + placeholder="Ort" + disabled={saving} + /> + + + + E-Mail * + + setFormData({ ...formData, email: e.target.value }) + } + placeholder="email@beispiel.de" + required + disabled={saving} + /> + + + + Telefon + + setFormData({ ...formData, phoneNumber: e.target.value }) + } + placeholder="Telefonnummer" + disabled={saving} + /> + + + + + {saving ? "Wird gespeichert..." : "Stammdaten speichern"} + + + + + + {/* Info-Box */} + + + Hinweis + + Ihre Stammdaten werden für die Rechnungsstellung und + Kommunikation verwendet. Stellen Sie sicher, dass die + Daten aktuell und vollständig sind. + + + + + ); +}
Stammdaten
+ Verwalten Sie hier Ihre persönlichen und Unternehmensdaten. +
Profil
+ Ihre Stammdaten werden für die Rechnungsstellung und + Kommunikation verwendet. Stellen Sie sicher, dass die + Daten aktuell und vollständig sind. +