diff --git a/.gitea/workflows/test-build-only.yml b/.gitea/workflows/test-build-only.yml new file mode 100644 index 0000000..fb73e32 --- /dev/null +++ b/.gitea/workflows/test-build-only.yml @@ -0,0 +1,12 @@ +name: Test Build Only (temporary, no push) + +on: + workflow_dispatch: + +jobs: + build: + runs-on: linux + steps: + - uses: actions/checkout@v4 + - name: Docker build (no push) + run: docker build -t materialschein-test:local . diff --git a/client/index.html b/client/index.html index de56f29..54d91e1 100644 --- a/client/index.html +++ b/client/index.html @@ -7,6 +7,14 @@ BergVOLT Elektrotechnik – Materialschein +
diff --git a/client/src/components/Header.tsx b/client/src/components/Header.tsx index 4bec622..75e7173 100644 --- a/client/src/components/Header.tsx +++ b/client/src/components/Header.tsx @@ -1,5 +1,11 @@ +import { useState } from 'react'; import { Link, useLocation } from 'react-router-dom'; import { useMsal } from '@azure/msal-react'; +import { getStoredTheme, setTheme, type ThemeMode } from '../lib/theme'; + +const THEME_ICON: Record = { system: '🖥️', light: '☀️', dark: '🌙' }; +const THEME_LABEL: Record = { system: 'System', light: 'Hell', dark: 'Dunkel' }; +const NEXT_THEME: Record = { system: 'light', light: 'dark', dark: 'system' }; export default function Header({ localUser, onLocalLogout }: { localUser: string | null; onLocalLogout: () => void }) { const { instance, accounts } = useMsal(); @@ -9,12 +15,19 @@ export default function Header({ localUser, onLocalLogout }: { localUser: string const displayName = localUser ?? account?.name ?? account?.username ?? '–'; const roleLine = localUser ? 'Lokales Konto' : (account?.username ?? ''); const initials = displayName.split(' ').map(n => n[0]).join('').substring(0, 2).toUpperCase(); + const [themeMode, setThemeMode] = useState(getStoredTheme()); function logout() { if (account) instance.logoutPopup(); onLocalLogout(); } + function cycleTheme() { + const next = NEXT_THEME[themeMode]; + setTheme(next); + setThemeMode(next); + } + return ( <>
@@ -24,6 +37,13 @@ export default function Header({ localUser, onLocalLogout }: { localUser: string
Elektrotechnik
+
{initials}
diff --git a/client/src/lib/theme.ts b/client/src/lib/theme.ts new file mode 100644 index 0000000..ac26255 --- /dev/null +++ b/client/src/lib/theme.ts @@ -0,0 +1,23 @@ +export type ThemeMode = 'system' | 'light' | 'dark'; + +const KEY = 'bv_theme'; + +export function getStoredTheme(): ThemeMode { + const v = localStorage.getItem(KEY); + return v === 'light' || v === 'dark' ? v : 'system'; +} + +export function applyTheme(mode: ThemeMode) { + const root = document.documentElement; + if (mode === 'system') root.removeAttribute('data-theme'); + else root.setAttribute('data-theme', mode); +} + +export function setTheme(mode: ThemeMode) { + localStorage.setItem(KEY, mode); + applyTheme(mode); +} + +export function initTheme() { + applyTheme(getStoredTheme()); +} diff --git a/client/src/main.tsx b/client/src/main.tsx index 946cf12..eef2fe9 100644 --- a/client/src/main.tsx +++ b/client/src/main.tsx @@ -2,9 +2,12 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; import { MsalProvider } from '@azure/msal-react'; import { msalInstance } from './lib/authConfig'; +import { initTheme } from './lib/theme'; import App from './App'; import './styles.css'; +initTheme(); + msalInstance.initialize().then(() => { ReactDOM.createRoot(document.getElementById('root')!).render( diff --git a/client/src/styles.css b/client/src/styles.css index 1f50d5a..fbff97d 100644 --- a/client/src/styles.css +++ b/client/src/styles.css @@ -1,3 +1,7 @@ +/* Dunkel ist die Grundeinstellung (bare :root). Header/Tabs/Login bleiben in + beiden Modi dunkel-navy (Markenidentität) – nur der Content-Bereich (Karten, + Tabellen, Formulare) wechselt. Siehe [data-theme="light"] weiter unten für + die Werte und wie System-Erkennung + expliziter Umschalter zusammenspielen. */ :root{ --bg:#0a1622; --bg2:#0f2033; @@ -13,6 +17,39 @@ body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;background:var(--bg);color:var(--text);min-height:100vh;font-size:14px;line-height:1.4} button,input,select,textarea{font-family:inherit} +/* ── Theme: Hell ── + Zwei Auslöser, die dieselben Werte setzen: + 1) [data-theme="light"] – explizit vom Umschalter gewählt, unabhängig vom System. + 2) @media(prefers-color-scheme:light) ohne explizites [data-theme="dark"] – folgt + der Systemeinstellung, solange niemand manuell "Dunkel" erzwungen hat. */ +:root[data-theme="light"]{ + --bg:#f3f6f8;--bg2:#ffffff;--card:#ffffff;--cyan:#0086a3;--text:#0f2130;--muted:#5c7686;--bor:#dbe4ea; + --sh:0 3px 16px rgba(15,35,50,.08); +} +:root[data-theme="light"] .btn-ghost{background:rgba(15,35,50,.05)} +:root[data-theme="light"] .status-aktiv{background:rgba(0,170,90,.12);color:#0a8a4c} +:root[data-theme="light"] .status-abgeschlossen{background:rgba(92,118,134,.12)} +:root[data-theme="light"] .badge-nym{background:rgba(0,110,200,.14);color:#0064b8} +:root[data-theme="light"] .badge-nyy{background:rgba(210,110,0,.14);color:#a35b00} +:root[data-theme="light"] .badge-h07{background:rgba(120,0,180,.14);color:#7a1fae} +:root[data-theme="light"] .badge-ysly{background:rgba(210,40,40,.14);color:#b32a2a} +:root[data-theme="light"] .badge-schlauch{background:rgba(20,150,90,.14);color:#0f7d4f} + +@media(prefers-color-scheme:light){ + :root:not([data-theme="dark"]){ + --bg:#f3f6f8;--bg2:#ffffff;--card:#ffffff;--cyan:#0086a3;--text:#0f2130;--muted:#5c7686;--bor:#dbe4ea; + --sh:0 3px 16px rgba(15,35,50,.08); + } + :root:not([data-theme="dark"]) .btn-ghost{background:rgba(15,35,50,.05)} + :root:not([data-theme="dark"]) .status-aktiv{background:rgba(0,170,90,.12);color:#0a8a4c} + :root:not([data-theme="dark"]) .status-abgeschlossen{background:rgba(92,118,134,.12)} + :root:not([data-theme="dark"]) .badge-nym{background:rgba(0,110,200,.14);color:#0064b8} + :root:not([data-theme="dark"]) .badge-nyy{background:rgba(210,110,0,.14);color:#a35b00} + :root:not([data-theme="dark"]) .badge-h07{background:rgba(120,0,180,.14);color:#7a1fae} + :root:not([data-theme="dark"]) .badge-ysly{background:rgba(210,40,40,.14);color:#b32a2a} + :root:not([data-theme="dark"]) .badge-schlauch{background:rgba(20,150,90,.14);color:#0f7d4f} +} + /* Login */ .login-screen{display:flex;align-items:center;justify-content:center;min-height:100vh;background:#070f18;position:relative;overflow:hidden;padding:20px} .login-screen::before{content:'';position:absolute;inset:0;background:radial-gradient(ellipse 70% 60% at 30% 30%,rgba(0,200,216,.08) 0%,transparent 70%)} @@ -39,6 +76,8 @@ header{background:linear-gradient(135deg,#081420 0%,#0c1c30 100%);border-bottom: .hdr-role{font-size:9.5px;color:var(--muted)} .btn-logout{background:rgba(255,255,255,.06);border:1px solid var(--bor);color:var(--muted);border-radius:8px;padding:8px 12px;font-size:11px;font-weight:700;cursor:pointer;flex-shrink:0} .btn-logout:hover{color:#ff6b6b;border-color:rgba(255,85,85,.4)} +.btn-theme{background:rgba(255,255,255,.06);border:1px solid var(--bor);border-radius:8px;width:36px;height:36px;font-size:15px;cursor:pointer;flex-shrink:0;display:flex;align-items:center;justify-content:center;line-height:1} +.btn-theme:hover{background:rgba(255,255,255,.12)} /* Tabs */ .tabs{background:#081420;display:flex;padding:0 20px;gap:2px;border-bottom:1px solid var(--bor);overflow-x:auto}