From bccb8d1933936c6644769a76f4cf31a538a1feae Mon Sep 17 00:00:00 2001 From: patrick Date: Sun, 6 Sep 2026 09:57:49 +0200 Subject: [PATCH] feat(ui): UI-001 Designsystem-Grundlage - Dark Mode + Readiness-Farbtokens MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Epic 20 (UI-Redesign) angelegt, Stufe 1: CSS-Variablen für Dark Mode (System-Präferenz + manueller Schalter via ThemeToggle, localStorage- persistiert) und eigene Farbtokens für Einsatzbereitschaft (ready/ limited/not-ready/unknown), Basis für ReadinessBadge in UI-003. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01CVgbozhYmuEhiEJHffRXCV --- arbeitskacheln/00_index.md | 7 +- arbeitskacheln/20_ui_redesign.md | 173 ++++++++++++++++++++++++ frontend/src/components/AppShell.tsx | 2 + frontend/src/components/ThemeToggle.tsx | 58 ++++++++ frontend/src/styles/global.css | 114 ++++++++++++++++ 5 files changed, 351 insertions(+), 3 deletions(-) create mode 100644 arbeitskacheln/20_ui_redesign.md create mode 100644 frontend/src/components/ThemeToggle.tsx diff --git a/arbeitskacheln/00_index.md b/arbeitskacheln/00_index.md index 73c28a4..ea72f0d 100644 --- a/arbeitskacheln/00_index.md +++ b/arbeitskacheln/00_index.md @@ -272,10 +272,11 @@ eigenes Modul (MAINT-\*, MABEA hat nur Prüfung, keine Wartung)**. | `17_zustaendigkeit.md` | ZUST-001 … ZUST-002 (vollständig, nachträglich ergänzt) | | `18_notifications.md` | NOTIF-001 … NOTIF-002 (vollständig, nachträglich ergänzt) | | `19_satelliten_server.md` | SAT-001 (Platzhalter, nachträglich ergänzt, bewusst zurückgestellt) | +| `20_ui_redesign.md` | UI-001 … UI-008 (vollständig, nachträglich ergänzt, in Umsetzung) | -**Alle 19 Epics durchgegangen** (16 ursprüngliche + 17 Zuständigkeit/18 Notifications/19 -Satelliten-Server, nachträglich aus dem Abgleich mit den alten Arbeitskarten ergänzt). -Detaillierungsphase abgeschlossen (2026-09-05). +**Alle 20 Epics durchgegangen** (16 ursprüngliche + 17 Zuständigkeit/18 Notifications/19 +Satelliten-Server/20 UI-Redesign, nachträglich ergänzt). Detaillierungsphase +abgeschlossen (2026-09-05), UI-Redesign-Epic ergänzt (2026-09-06). ## Nachtrag: Externe Roadmap-Bewertung Shelf/Resgrid/Emergency Mgmt (2026-09-05) diff --git a/arbeitskacheln/20_ui_redesign.md b/arbeitskacheln/20_ui_redesign.md new file mode 100644 index 0000000..22e4c79 --- /dev/null +++ b/arbeitskacheln/20_ui_redesign.md @@ -0,0 +1,173 @@ +# Epic 20 — UI-Redesign (moderne BOS-Plattform-Optik) + +Ausgangspunkt: externer Redesign-Prompt vom 2026-09-06 (Nutzer-Vorgabe, siehe +Referenzen). **Keine Neuentwicklung, keine Entfernung bestehender Funktionen** — +bestehende API/Fachlogik/PWA/Offline-Sync bleiben unverändert, nur die Darstellung +wird modernisiert. React/Vite/TypeScript bleibt, kein Framework-Wechsel. + +Aktueller Stand vor Redesign: 39 Seiten-/Komponenten-Dateien, ~7.000 Zeilen, einfaches +`global.css` (560 Zeilen) ohne Designsystem, keine UI-Bibliothek. + +--- + +## UI-001 — Designsystem-Grundlage + +- **Ziel:** Ein zentrales Set an Design-Tokens (Farben, Typografie, Abstände, + Radien, Schatten) statt verstreuter Inline-Styles. +- **Beschreibung:** CSS-Variablen (Light + Dark), zentrale Status-Farben + (einsatzbereit/eingeschränkt/nicht einsatzbereit/unbekannt), Basis-Komponenten + (Button, Card, Input, Badge, Table) vereinheitlicht. +- **Benutzerwert:** Konsistentes, hochwertiges Erscheinungsbild in der ganzen App. +- **Abhängigkeiten:** keine +- **Backend:** keins +- **Frontend:** `styles/tokens.css`, überarbeitetes `global.css`, Dark-Mode via + `prefers-color-scheme` + manuellem Umschalter +- **Mobile:** Basis für alle Folge-Kacheln +- **Rechte:** keine Änderung +- **Akzeptanzkriterien:** Dark Mode umschaltbar, alle Basis-Elemente nutzen Tokens + statt Hardcoded-Werten. +- **Tests:** keine (reines Styling), visuelle Kontrolle. +- **DoD:** offen — noch nicht begonnen. + +## UI-002 — App-Shell & Navigation + +- **Ziel:** Moderne Sidebar (Desktop) / kompakte Navigation (Mobile) statt + bisheriger einfacher Navigation in `AppShell.tsx`. +- **Beschreibung:** Bereiche Dashboard/Objekte/Fahrzeuge/Material/Lager/ + Kontrollen/Prüfungen/Mängel/Personal/Administration. Globale Suche `Ctrl/⌘+K`. + Quick Actions (QR scannen, Kontrolle starten, Mangel melden, Material buchen). +- **Benutzerwert:** Schnelle Orientierung, weniger Klicks zu Kernaktionen. +- **Abhängigkeiten:** UI-001 +- **Backend:** keins (Suche client-seitig über bereits geladene Objekt-/Material-Listen, + kein neuer Such-Endpunkt in Phase 1) +- **Frontend:** `AppShell.tsx` umgebaut, neue `CommandPalette`-Komponente +- **Mobile:** Bottom-Navigation oder Drawer statt Sidebar +- **Rechte:** Navigationspunkte nach Rolle gefiltert (bereits vorhandene Rollen-Logik + wiederverwenden) +- **Akzeptanzkriterien:** alle bestehenden Routen weiter erreichbar, Suche findet + Objekte per Name/Code. +- **Tests:** keine neuen (reines Routing/UI). +- **DoD:** offen. + +## UI-003 — Zentrale Statuskomponenten + +- **Ziel:** Wiederverwendbare Status-Anzeigen statt an mehreren Stellen dupliziertem + Badge-Markup. +- **Beschreibung:** `ReadinessBadge` (einsatzbereit/eingeschränkt/nicht + einsatzbereit/unbekannt — „unbekannt" NIE als einsatzbereit darstellen), + `SyncStatus` (online/offline/synchronisiert), `InspectionStatus`, + `InventoryStatus`. Jede Komponente kapselt Farbe+Icon+Text an einer Stelle. +- **Benutzerwert:** Konsistente, sofort verständliche Statusdarstellung überall. +- **Abhängigkeiten:** UI-001 +- **Backend:** keins +- **Frontend:** `components/status/ReadinessBadge.tsx` etc., bestehende Ad-hoc-Badges + (`DashboardPage.tsx`, `AktePage.tsx`, `ObjektListPage.tsx`) darauf umgestellt +- **Rechte:** keine Änderung +- **Akzeptanzkriterien:** kein Status mehr als Inline-`` + dupliziert. +- **Tests:** ggf. kleiner Komponententest je Status-Mapping. +- **DoD:** offen. + +## UI-004 — Dashboard neu + +- **Ziel:** „Was ist einsatzbereit und was muss ich jetzt tun?" in 5 Sekunden + beantwortbar. +- **Beschreibung:** Einsatzbereitschaft-Übersicht (einsatzbereit/eingeschränkt/ + nicht einsatzbereit/unbekannt), offene Mängel, fällige Kontrollen/Prüfungen, + fehlendes Material, aktuelle Aufgaben — alles aus bereits vorhandenen + Dashboard-/Fehlbestand-/Mangel-Endpunkten, keine Fake-Daten. +- **Benutzerwert:** Kernversprechen der Kachel — sofortiger Überblick. +- **Abhängigkeiten:** UI-001, UI-003 +- **Backend:** keins zusätzlich (bestehende Dashboard-Kennzahlen wiederverwenden) +- **Frontend:** `DashboardPage.tsx` neu strukturiert, Card-Grid statt Liste +- **Mobile:** gestapeltes Layout +- **Rechte:** keine Änderung +- **Akzeptanzkriterien:** alle geforderten Kennzahlen sichtbar, keine Platzhalter-/ + Fake-Werte. +- **Tests:** bestehende Dashboard-Tests bleiben grün. +- **DoD:** offen. + +## UI-005 — Objektakte modernisiert + +- **Ziel:** `AktePage.tsx` (bereits FILE-006/Akte-Aggregation) optisch/strukturell + aufwerten, inkl. begründeter Einsatzbereitschafts-Anzeige. +- **Beschreibung:** Status mit Begründung (z. B. „🔴 Nicht einsatzbereit — Funkgerät + defekt, 2 Beladungspositionen fehlen, Prüfung überfällig"), Tabs oder klar + getrennte Bereiche für Beladung/Kontrollen/Mängel/Prüfungen/Wartungen/ + Dokumente/Historie. +- **Benutzerwert:** Sofort verständlich, WARUM ein Objekt nicht einsatzbereit ist. +- **Abhängigkeiten:** UI-001, UI-003 +- **Backend:** ggf. kleine Ergänzung am `/akte/objekt/{id}`-Endpunkt, falls die + Begründungs-Texte nicht schon aus den Einzelfeldern ableitbar sind (prüfen vor + Umsetzung) +- **Frontend:** `AktePage.tsx` Umbau, bisherige 6 gleichartigen Card-Blöcke + (bereits als Simplification-Fund dokumentiert) auf gemeinsame Komponente + vereinheitlichen +- **Rechte:** keine Änderung +- **Akzeptanzkriterien:** „unbekannt/nie kontrolliert" wird nie als einsatzbereit + dargestellt (harte Regel, Akzeptanzkriterium testbar). +- **Tests:** Begründungslogik testen (welche Bedingungen führen zu welchem Status). +- **DoD:** offen. + +## UI-006 — Kontrolle & Beladung modernisiert + +- **Ziel:** Bestehenden Kontrollworkflow (QR/Objekt → Kontrolle starten → Position + prüfen → OK/Abweichung → speichern → Abschluss) beibehalten, Fortschritt + (`38 / 40 geprüft`) und fehlerhafte Positionen visuell hervorheben. +- **Benutzerwert:** Schnellere, fehlerfreiere Kontrolle vor Ort. +- **Abhängigkeiten:** UI-001, UI-003 +- **Backend:** keins +- **Frontend:** `KontrollPage.tsx` + `pages/kontrolle/*` Fortschrittsanzeige, + hervorgehobene Abweichungen +- **Mobile:** Priorität — Haupteinsatzort dieses Screens +- **Rechte:** keine Änderung +- **Akzeptanzkriterien:** Fortschritt live sichtbar, Abweichungen nicht zu übersehen. +- **Tests:** bestehende Kontroll-Tests bleiben grün. +- **DoD:** offen. + +## UI-007 — QR-Scan-Aktionsleiste + +- **Ziel:** Nach Scan direkt Aktionen anbieten (Kontrolle starten/Mangel melden/ + Informationen), nicht nur Direkt-Navigation. +- **Beschreibung:** Baut auf MOBILE-002-Fix (Scan → Akte) auf, ergänzt auf der + Akte-Seite eine hervorgehobene Aktionsleiste statt nur einem Button. +- **Benutzerwert:** Weniger Klicks nach dem Scan. +- **Abhängigkeiten:** UI-005, MOBILE-002 (bereits erledigt) +- **Backend:** keins +- **Frontend:** Aktionsleiste in `AktePage.tsx` +- **Rechte:** je Aktion bestehende Rollenprüfung wiederverwenden +- **Akzeptanzkriterien:** alle drei Aktionen von der Akte aus erreichbar. +- **Tests:** keine neuen. +- **DoD:** offen. + +## UI-008 — Restliche Module (Material, Lager, Personal, Administration) + +- **Ziel:** Verbleibende Seiten im neuen Designsystem angleichen. +- **Beschreibung:** Admin-Tabs (`LagerSection.tsx` u.a.), `MangelListePage.tsx`, + `FehlbestandListePage.tsx` auf Tokens/Statuskomponenten umstellen. +- **Benutzerwert:** Durchgängiges Erscheinungsbild, keine Stilbrüche. +- **Abhängigkeiten:** UI-001..003 +- **Backend:** keins +- **Frontend:** verbleibende Seiten +- **Rechte:** keine Änderung +- **Akzeptanzkriterien:** keine Seite mehr im alten Stil. +- **Tests:** bestehende Tests bleiben grün. +- **DoD:** offen. + +--- + +**Reihenfolge (zwingend, laut Vorgabe):** UI-001 → UI-002 → UI-003 → UI-004 → UI-005 +→ UI-006 → UI-007 → UI-008. Jede Stufe wird einzeln abgeschlossen und bestätigt, +bevor die nächste beginnt (Arbeitskacheln-Methodik: kein Direktcoden ohne Freigabe +je Schritt). + +**Nicht-Ziele:** kein Framework-Wechsel, keine Breaking Changes an API/Fachlogik, +keine Fake-Daten, Offline/PWA/IndexedDB-Sync bleibt unangetastet, bestehende Tests +bleiben grün und werden erweitert wo sinnvoll. + +**WCAG 2.2 AA:** als Leitplanke bei UI-001 (Kontraste/Fokus-Stile) und UI-002 +(Tastaturbedienung Command Palette) mitgedacht, kein separater Prüf-Task. + +## Referenzen +Externer Redesign-Prompt, vom Nutzer am 2026-09-06 eingebracht (kompakte Version +für Coding-KI, Zielrepo MABEA). diff --git a/frontend/src/components/AppShell.tsx b/frontend/src/components/AppShell.tsx index a9b7f8d..d05f4c9 100644 --- a/frontend/src/components/AppShell.tsx +++ b/frontend/src/components/AppShell.tsx @@ -2,6 +2,7 @@ import type { ReactNode } from "react"; import { NavLink, useNavigate } from "react-router-dom"; import { useAuth } from "../auth/AuthContext"; +import { ThemeToggle } from "./ThemeToggle"; /** Gemeinsamer Rahmen (Kopfzeile + Navigation) für alle eingeloggten Screens. */ export function AppShell({ children }: { children: ReactNode }) { @@ -41,6 +42,7 @@ export function AppShell({ children }: { children: ReactNode }) { {istAdmin ? "Administration" : "Verwaltung"} )} + diff --git a/frontend/src/components/ThemeToggle.tsx b/frontend/src/components/ThemeToggle.tsx new file mode 100644 index 0000000..6073feb --- /dev/null +++ b/frontend/src/components/ThemeToggle.tsx @@ -0,0 +1,58 @@ +import { useEffect, useState } from "react"; + +type Theme = "system" | "light" | "dark"; + +const SPEICHER_KEY = "mabea-theme"; + +function angewandtesTheme(theme: Theme): void { + if (theme === "system") { + document.documentElement.removeAttribute("data-theme"); + } else { + document.documentElement.setAttribute("data-theme", theme); + } +} + +function gespeichertesTheme(): Theme { + try { + const wert = localStorage.getItem(SPEICHER_KEY); + if (wert === "light" || wert === "dark") return wert; + } catch { + // localStorage evtl. blockiert (privater Modus) - Systemeinstellung als Fallback + } + return "system"; +} + +const REIHENFOLGE: Theme[] = ["system", "light", "dark"]; +const LABEL: Record = { system: "System", light: "Hell", dark: "Dunkel" }; + +/** UI-001 (Epic 20): manueller Dark-Mode-Schalter, Präferenz überlebt Reload. */ +export function ThemeToggle() { + const [theme, setTheme] = useState(() => gespeichertesTheme()); + + useEffect(() => { + angewandtesTheme(theme); + try { + if (theme === "system") localStorage.removeItem(SPEICHER_KEY); + else localStorage.setItem(SPEICHER_KEY, theme); + } catch { + // Speichern optional - beim nächsten Laden greift dann Systemeinstellung + } + }, [theme]); + + function weiterschalten() { + const naechster = REIHENFOLGE[(REIHENFOLGE.indexOf(theme) + 1) % REIHENFOLGE.length]; + setTheme(naechster); + } + + return ( + + ); +} diff --git a/frontend/src/styles/global.css b/frontend/src/styles/global.css index 2ae89bc..4e8cb1a 100644 --- a/frontend/src/styles/global.css +++ b/frontend/src/styles/global.css @@ -21,6 +21,14 @@ --color-success: #1e7a34; --color-success-bg: #e7f5ea; --color-muted-bg: #eef0f3; + --color-ready: #1e7a34; + --color-ready-bg: #e7f5ea; + --color-limited: #9a6700; + --color-limited-bg: #fff3d6; + --color-not-ready: #b3261e; + --color-not-ready-bg: #fbeaea; + --color-unknown: #55606b; + --color-unknown-bg: #e9ebee; --radius: 8px; --radius-sm: 6px; --shadow-sm: none; @@ -29,6 +37,73 @@ --font-sans: "Segoe UI", -apple-system, BlinkMacSystemFont, Roboto, Helvetica, Arial, sans-serif; } +/* UI-001 (Epic 20): Dark Mode - Systemeinstellung per prefers-color-scheme, + manueller Override per [data-theme] auf (ThemeToggle-Komponente, + Präferenz in localStorage). Reihenfolge wichtig: System-Media-Query zuerst, + [data-theme="dark"] danach, damit der manuelle Schalter in beide Richtungen + gewinnt. */ +@media (prefers-color-scheme: dark) { + :root:not([data-theme="light"]) { + --color-bg: #14171a; + --color-surface: #1c2024; + --color-surface-sunken: #22262b; + --color-border: #333941; + --color-border-strong: #454c55; + --color-text: #e8eaed; + --color-text-muted: #9aa4ae; + --color-primary: #5b9bd8; + --color-primary-hover: #7bb0e2; + --color-primary-soft: #1e2c3a; + --color-danger: #e37c76; + --color-danger-bg: #3a2224; + --color-warning: #e0b34d; + --color-warning-bg: #3a3120; + --color-success: #6cc386; + --color-success-bg: #1f3324; + --color-muted-bg: #262b31; + --color-ready: #6cc386; + --color-ready-bg: #1f3324; + --color-limited: #e0b34d; + --color-limited-bg: #3a3120; + --color-not-ready: #e37c76; + --color-not-ready-bg: #3a2224; + --color-unknown: #9aa4ae; + --color-unknown-bg: #262b31; + } +} + +:root[data-theme="dark"] { + --color-bg: #14171a; + --color-surface: #1c2024; + --color-surface-sunken: #22262b; + --color-border: #333941; + --color-border-strong: #454c55; + --color-text: #e8eaed; + --color-text-muted: #9aa4ae; + --color-primary: #5b9bd8; + --color-primary-hover: #7bb0e2; + --color-primary-soft: #1e2c3a; + --color-danger: #e37c76; + --color-danger-bg: #3a2224; + --color-warning: #e0b34d; + --color-warning-bg: #3a3120; + --color-success: #6cc386; + --color-success-bg: #1f3324; + --color-muted-bg: #262b31; + --color-ready: #6cc386; + --color-ready-bg: #1f3324; + --color-limited: #e0b34d; + --color-limited-bg: #3a3120; + --color-not-ready: #e37c76; + --color-not-ready-bg: #3a2224; + --color-unknown: #9aa4ae; + --color-unknown-bg: #262b31; +} + +body { + transition: background-color 0.15s ease, color 0.15s ease; +} + * { box-sizing: border-box; } @@ -348,6 +423,45 @@ select.input:focus { color: var(--color-danger); } +/* UI-003 (Epic 20): Einsatzbereitschafts-Status - eigene Klassen statt + Wiederverwendung von badge-success/warning/danger, weil "unbekannt" fachlich + NIE wie "einsatzbereit" aussehen darf (siehe ReadinessBadge-Komponente). */ +.badge-ready { + background: var(--color-ready-bg); + color: var(--color-ready); +} + +.badge-limited { + background: var(--color-limited-bg); + color: var(--color-limited); +} + +.badge-not-ready { + background: var(--color-not-ready-bg); + color: var(--color-not-ready); +} + +.badge-unknown { + background: var(--color-unknown-bg); + color: var(--color-unknown); +} + +.theme-toggle { + background: transparent; + border: 1px solid var(--color-border-strong); + color: var(--color-text-muted); + border-radius: var(--radius-sm); + padding: 0.4rem 0.6rem; + cursor: pointer; + font-size: 0.85rem; + line-height: 1; +} + +.theme-toggle:hover { + background: var(--color-surface-sunken); + color: var(--color-text); +} + .alert { border-radius: var(--radius-sm); padding: 0.75rem 1rem;