diff --git a/DEVLOG.md b/DEVLOG.md index 5def377..4c94860 100644 --- a/DEVLOG.md +++ b/DEVLOG.md @@ -4156,3 +4156,36 @@ Keine Commits in dieser Session. - backend/scripts/eskalation_pruefen.py | 1 + --- +## 2026-09-05 14:13 – 14:13 (0m) +**Beschreibung:** Claude Code Session +**Projekt:** asb-material + +### Commits +- 73510ff docs: DEVLOG aktualisieren + +### Geänderte Dateien +- DEVLOG.md | 38 ++++++++++++++++++++++++++++++++++++++ + +--- +## 2026-09-05 14:23 – 14:24 (1m) +**Beschreibung:** Claude Code Session +**Projekt:** asb-material + +### Commits +Keine Commits in dieser Session. + +### Geänderte Dateien +- DEVLOG.md | 38 ++++++++++++++++++++++++++++++++++++++ + +--- +## 2026-09-05 14:38 – 14:42 (3m) +**Beschreibung:** Claude Code Session +**Projekt:** asb-material + +### Commits +Keine Commits in dieser Session. + +### Geänderte Dateien +- DEVLOG.md | 38 ++++++++++++++++++++++++++++++++++++++ + +--- diff --git a/frontend/src/pages/DashboardPage.tsx b/frontend/src/pages/DashboardPage.tsx index ce9c92b..2aaa786 100644 --- a/frontend/src/pages/DashboardPage.tsx +++ b/frontend/src/pages/DashboardPage.tsx @@ -109,25 +109,25 @@ export function DashboardPage() { const maengelOffenListe = maengel.filter((m) => m.status !== "erledigt"); return ( -
+

Dashboard

{fehler &&
{fehler}
} {laedt &&

Lade…

} {!laedt && bereitschaft && ( -
-
+
+

Einsatzbereites Material

-

- {bereitschaft.einsatzbereit} von {bereitschaft.gesamt} Objekten einsatzbereit +

+ {bereitschaft.einsatzbereit} von {bereitschaft.gesamt} einsatzbereit

{bereitschaft.eingeschraenkt_einsatzbereit > 0 && ( -

+

{bereitschaft.eingeschraenkt_einsatzbereit} eingeschränkt einsatzbereit (aktive Mindermengen-Genehmigung)

)} {bereitschaft.nicht_einsatzbereit > 0 && ( -

+

{bereitschaft.nicht_einsatzbereit} nicht einsatzbereit

)} @@ -154,7 +154,7 @@ export function DashboardPage() { )}
-
+

Nächste Prüftermine

{prueftermine.length === 0 ? (

Keine anstehenden oder überfälligen Geräteprüfungen.

@@ -163,7 +163,7 @@ export function DashboardPage() { {prueftermine.map((p, i) => (
  • {p.bezeichnung} - + {p.faelligkeitsdatum} ({p.verbleibende_tage} Tage)
  • @@ -172,7 +172,7 @@ export function DashboardPage() { )}
    -
    +

    Bevorstehende Ablaufdaten

    {ablaufdaten.length === 0 ? (

    Keine bald ablaufenden Chargen.

    @@ -181,7 +181,7 @@ export function DashboardPage() { {ablaufdaten.map((a) => (
  • Objekt #{a.objekt_id} – Material #{a.material_id} - + {a.ablaufdatum} ({a.verbleibende_tage} Tage)
  • @@ -190,7 +190,7 @@ export function DashboardPage() { )}
    -
    +

    Kennzahlen

    {kennzahlen && (
      @@ -209,11 +209,11 @@ export function DashboardPage() { )}
    -
    +

    Mängel

    -

    {maengelOffenAnzahl} offen

    +

    {maengelOffenAnzahl} offen

    {maengelKritischAnzahl > 0 && ( -

    +

    {maengelKritischAnzahl} kritisch (blockiert Einsatzbereitschaft)

    )} @@ -227,7 +227,7 @@ export function DashboardPage() { {maengelOffenListe.map((m) => (
  • {m.beschreibung} - + {m.prioritaet}
  • @@ -239,7 +239,7 @@ export function DashboardPage() { {maengelOffenListe.length === 0 &&

    Keine offenen Mängel.

    }
    -
    +

    Qualifikationsablauf

    {qualifikationsablaeufe.length === 0 ? (

    Keine bald ablaufenden Qualifikationen.

    @@ -250,7 +250,7 @@ export function DashboardPage() { {q.benutzer_name} ({q.qualifikationstyp_name}) - + {q.gueltig_bis} ({q.verbleibende_tage} Tage) diff --git a/frontend/src/pages/FehlbestandListePage.tsx b/frontend/src/pages/FehlbestandListePage.tsx index 56ad487..7e18011 100644 --- a/frontend/src/pages/FehlbestandListePage.tsx +++ b/frontend/src/pages/FehlbestandListePage.tsx @@ -92,7 +92,7 @@ export function FehlbestandListePage() { : fehlbestaende; return ( -
    +

    Offene Fehlbestände

    Nachfüllen oder Mindermenge genehmigen, ohne eine komplette Kontrolle des diff --git a/frontend/src/pages/MangelListePage.tsx b/frontend/src/pages/MangelListePage.tsx index 542c940..67fdd6e 100644 --- a/frontend/src/pages/MangelListePage.tsx +++ b/frontend/src/pages/MangelListePage.tsx @@ -98,7 +98,7 @@ export function MangelListePage() { const angezeigt = nurOffene ? maengel.filter((m) => m.status !== "erledigt") : maengel; return ( -

    +

    Mängel

    {fehler &&
    {fehler}
    } @@ -143,7 +143,7 @@ export function MangelListePage() {
    {objektName(m.objekt_id)} - {m.prioritaet === "hoch" && ⚠ hoch} + {m.prioritaet === "hoch" && ⚠ hoch} {STATUS_LABEL[m.status]}
    diff --git a/frontend/src/pages/admin/QualifikationSection.tsx b/frontend/src/pages/admin/QualifikationSection.tsx index 8af4ac6..1477eb9 100644 --- a/frontend/src/pages/admin/QualifikationSection.tsx +++ b/frontend/src/pages/admin/QualifikationSection.tsx @@ -161,7 +161,7 @@ export function QualifikationSection({ qualifikationstypen, benutzer, onGeaender {qualifikationen.map((q) => (
  • {qualifikationstypName(q.qualifikationstyp_id)} - + seit {q.erworben_am} {q.gueltig_bis ? `, gültig bis ${q.gueltig_bis}` : ", unbefristet"} {!istGueltig(q) && " (abgelaufen)"} diff --git a/frontend/src/styles/global.css b/frontend/src/styles/global.css index 4e3d575..e882e52 100644 --- a/frontend/src/styles/global.css +++ b/frontend/src/styles/global.css @@ -1,21 +1,33 @@ :root { - --color-bg: #f4f6f8; + /* Redesign 2026-09-05: kräftigeres Marken-Blau, wärmere Neutraltöne, + größerer Radius/weichere Schatten für ein moderneres, weniger + "Bootstrap-generisches" Erscheinungsbild. Alle bestehenden Klassennamen + bleiben unverändert (jede Seite nutzt sie direkt) - nur die Werte + dahinter wurden überarbeitet. Mobile/responsive Feinschliff folgt in + einem eigenen Durchgang, hier zunächst Desktop-Optik. */ + --color-bg: #f1f3f6; --color-surface: #ffffff; - --color-border: #e2e5e9; - --color-text: #1a1f24; - --color-text-muted: #66707a; - --color-primary: #0b5394; - --color-primary-hover: #084174; - --color-danger: #c62828; - --color-danger-bg: #fdecea; - --color-warning: #b8860b; - --color-warning-bg: #fff8e1; - --color-success: #2e7d32; - --color-success-bg: #e8f5e9; - --color-muted-bg: #eef0f2; - --radius: 10px; - --shadow: 0 1px 3px rgba(0, 0, 0, 0.08), 0 1px 2px rgba(0, 0, 0, 0.06); - --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; + --color-surface-sunken: #f8f9fb; + --color-border: #dde1e7; + --color-border-strong: #c7ccd4; + --color-text: #161b22; + --color-text-muted: #5b6472; + --color-primary: #0a4d8c; + --color-primary-hover: #073a6b; + --color-primary-soft: #e8f0fa; + --color-danger: #b3261e; + --color-danger-bg: #fbeaea; + --color-warning: #9a6700; + --color-warning-bg: #fff3d6; + --color-success: #1e7a34; + --color-success-bg: #e7f5ea; + --color-muted-bg: #eef0f3; + --radius: 12px; + --radius-sm: 8px; + --shadow-sm: 0 1px 2px rgba(16, 24, 40, 0.06); + --shadow: 0 2px 4px rgba(16, 24, 40, 0.06), 0 8px 20px rgba(16, 24, 40, 0.06); + --shadow-hover: 0 4px 10px rgba(16, 24, 40, 0.09), 0 12px 28px rgba(16, 24, 40, 0.09); + --font-sans: "Segoe UI", -apple-system, BlinkMacSystemFont, Roboto, Helvetica, Arial, sans-serif; } * { @@ -34,19 +46,43 @@ body { background: var(--color-bg); color: var(--color-text); -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; } h1, h2, h3 { margin: 0 0 0.75rem; - line-height: 1.2; + line-height: 1.25; + font-weight: 700; + letter-spacing: -0.01em; +} + +h1 { + font-size: 1.6rem; +} + +h2 { + font-size: 1.2rem; +} + +h3 { + font-size: 1.05rem; } p { margin: 0 0 0.75rem; } +a { + color: var(--color-primary); +} + +:focus-visible { + outline: 2px solid var(--color-primary); + outline-offset: 2px; +} + /* -- Layout ------------------------------------------------------------- */ .app-shell { @@ -58,7 +94,7 @@ p { .topbar { background: var(--color-surface); border-bottom: 1px solid var(--color-border); - padding: 0.75rem 1.25rem; + padding: 0.7rem 1.5rem; display: flex; align-items: center; justify-content: space-between; @@ -66,11 +102,13 @@ p { position: sticky; top: 0; z-index: 10; + box-shadow: var(--shadow-sm); } .topbar-brand { - font-weight: 700; - font-size: 1.1rem; + font-weight: 800; + font-size: 1.2rem; + letter-spacing: -0.02em; color: var(--color-primary); text-decoration: none; } @@ -78,30 +116,36 @@ p { .topbar-nav { display: flex; align-items: center; - gap: 0.5rem; + gap: 0.25rem; flex-wrap: wrap; } .topbar-nav a { color: var(--color-text-muted); text-decoration: none; - padding: 0.4rem 0.7rem; - border-radius: var(--radius); - font-size: 0.95rem; + padding: 0.45rem 0.8rem; + border-radius: 999px; + font-size: 0.92rem; + font-weight: 600; + transition: background-color 0.15s ease, color 0.15s ease; } -.topbar-nav a:hover, -.topbar-nav a.active { - background: var(--color-muted-bg); +.topbar-nav a:hover { + background: var(--color-surface-sunken); color: var(--color-text); } +.topbar-nav a.active { + background: var(--color-primary-soft); + color: var(--color-primary); +} + .page { flex: 1; width: 100%; max-width: 640px; margin: 0 auto; - padding: 1.25rem; + padding: 1.5rem 1.25rem 2.5rem; } .page-narrow { @@ -114,8 +158,9 @@ p { ausgerichtet statt zentriert - bei 1200px sonst viel verschenkter Platz rechts/links auf breiten Monitoren. */ .page-wide { - max-width: 1200px; + max-width: 1240px; margin: 0; + padding: 1.75rem 2rem 3rem; } /* -- Cards ---------------------------------------------------------------- */ @@ -124,9 +169,10 @@ p { background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius); - box-shadow: var(--shadow); - padding: 1rem; + box-shadow: var(--shadow-sm); + padding: 1.1rem 1.2rem; margin-bottom: 0.75rem; + transition: box-shadow 0.15s ease, border-color 0.15s ease; } .card-list { @@ -146,6 +192,7 @@ p { .card-link:hover { border-color: var(--color-primary); + box-shadow: var(--shadow-hover); } .card-title-row { @@ -166,12 +213,12 @@ p { .field { display: flex; flex-direction: column; - gap: 0.3rem; + gap: 0.35rem; margin-bottom: 1rem; } .field label { - font-size: 0.9rem; + font-size: 0.88rem; color: var(--color-text-muted); font-weight: 600; } @@ -180,25 +227,26 @@ p { select.input, textarea.input { width: 100%; - font-size: 1rem; - padding: 0.65rem 0.75rem; - border: 1px solid var(--color-border); - border-radius: var(--radius); + font-size: 0.98rem; + padding: 0.6rem 0.75rem; + border: 1px solid var(--color-border-strong); + border-radius: var(--radius-sm); background: var(--color-surface); color: var(--color-text); font-family: inherit; + transition: border-color 0.15s ease, box-shadow 0.15s ease; } .input:focus, select.input:focus { outline: none; border-color: var(--color-primary); - box-shadow: 0 0 0 3px rgba(11, 83, 148, 0.15); + box-shadow: 0 0 0 3px var(--color-primary-soft); } .input-search { - font-size: 1.05rem; - padding: 0.85rem 1rem; + font-size: 1.02rem; + padding: 0.8rem 1rem; } /* -- Buttons ------------------------------------------------------------ */ @@ -208,13 +256,13 @@ select.input:focus { align-items: center; justify-content: center; gap: 0.4rem; - font-size: 1rem; + font-size: 0.95rem; font-weight: 600; - padding: 0.65rem 1.1rem; - border-radius: var(--radius); + padding: 0.6rem 1.05rem; + border-radius: var(--radius-sm); border: 1px solid transparent; cursor: pointer; - transition: background-color 0.15s ease, opacity 0.15s ease; + transition: background-color 0.15s ease, border-color 0.15s ease, opacity 0.15s ease, box-shadow 0.15s ease; font-family: inherit; } @@ -226,6 +274,7 @@ select.input:focus { .btn-primary { background: var(--color-primary); color: #fff; + box-shadow: var(--shadow-sm); } .btn-primary:hover:not(:disabled) { @@ -235,11 +284,12 @@ select.input:focus { .btn-secondary { background: var(--color-surface); color: var(--color-text); - border-color: var(--color-border); + border-color: var(--color-border-strong); } .btn-secondary:hover:not(:disabled) { - background: var(--color-muted-bg); + background: var(--color-surface-sunken); + border-color: var(--color-text-muted); } .btn-danger { @@ -261,11 +311,12 @@ select.input:focus { .badge { display: inline-flex; align-items: center; - font-size: 0.8rem; + font-size: 0.76rem; font-weight: 700; - padding: 0.2rem 0.6rem; + padding: 0.22rem 0.65rem; border-radius: 999px; white-space: nowrap; + letter-spacing: 0.01em; } .badge-neutral { @@ -289,20 +340,23 @@ select.input:focus { } .alert { - border-radius: var(--radius); + border-radius: var(--radius-sm); padding: 0.75rem 1rem; margin-bottom: 1rem; font-size: 0.95rem; + border: 1px solid transparent; } .alert-warning { background: var(--color-warning-bg); - color: #6b5200; + color: var(--color-warning); + border-color: rgba(154, 103, 0, 0.25); } .alert-danger { background: var(--color-danger-bg); color: var(--color-danger); + border-color: rgba(179, 38, 30, 0.25); } .text-muted { @@ -329,13 +383,18 @@ select.input:focus { } .section { - margin-bottom: 2rem; + margin-bottom: 2.25rem; +} + +.section h2 { + padding-bottom: 0.5rem; + border-bottom: 1px solid var(--color-border); } .scanner-overlay { position: fixed; inset: 0; - background: rgba(0, 0, 0, 0.85); + background: rgba(10, 14, 20, 0.88); display: flex; align-items: center; justify-content: center; @@ -352,7 +411,7 @@ select.input:focus { .scanner-video { width: min(90vw, 480px); - border-radius: 0.5rem; + border-radius: var(--radius); background: #000; } @@ -365,12 +424,40 @@ select.input:focus { .table th, .table td { text-align: left; - padding: 0.4rem 0.6rem; - border-bottom: 1px solid var(--color-border, #e2e8f0); + padding: 0.55rem 0.7rem; + border-bottom: 1px solid var(--color-border); white-space: nowrap; } .table th { - font-weight: 600; - color: var(--color-text-muted, #64748b); + font-weight: 700; + color: var(--color-text-muted); + background: var(--color-surface-sunken); + position: sticky; + top: 0; +} + +.table tbody tr:hover { + background: var(--color-surface-sunken); +} + +/* -- Dashboard-Kacheln (DNA-Referenz Resgrid/OpenFleet/Emergency-Management: + KPI-Statistik-Karten sind dort durchgängig das zentrale Muster) --------- */ + +.card-grid { + display: flex; + gap: 1.25rem; + flex-wrap: wrap; +} + +.card-grid > .card { + flex: 1 1 18rem; +} + +.stat-number { + font-size: 2rem; + font-weight: 800; + letter-spacing: -0.02em; + margin: 0.2rem 0 0.4rem; + line-height: 1; }