diff --git a/DEVLOG.md b/DEVLOG.md index c64fc94..6833f9c 100644 --- a/DEVLOG.md +++ b/DEVLOG.md @@ -6311,3 +6311,34 @@ Keine Commits in dieser Session. - frontend/src/pages/kontrolle/PositionCard.tsx | 48 ++++++++++++++++++++++++++++++++++-------------- --- +## 2026-09-07 12:02 – 12:03 (0m) +**Beschreibung:** Claude Code Session +**Projekt:** frontend + +### Commits +- 5fc8079 feat(dashboard): Dashboard-Start für Verantwortliche, Kachel-Anpassung, Status-Monitor + +### Geänderte Dateien +- DEVLOG.md | 1923 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ +- backend/DEVLOG.md | 128 ++++ +- frontend/DEVLOG.md | 267 ++++++++ +- frontend/src/App.tsx | 5 +- +- frontend/src/components/AppShell.tsx | 2 - +- frontend/src/components/StartseiteAuswahl.tsx | 29 - +- frontend/src/pages/DashboardPage.tsx | 349 ---------- +- frontend/src/pages/LoginPage.tsx | 3 +- +- frontend/src/pages/dashboard/DashboardPage.tsx | 215 ++++++ +- frontend/src/pages/dashboard/TileFrame.tsx | 49 ++ +- frontend/src/pages/dashboard/layout.ts | 55 ++ +- frontend/src/pages/dashboard/tiles/AblaufdatenTile.tsx | 28 + +- frontend/src/pages/dashboard/tiles/AktivitaetTile.tsx | 34 + +- frontend/src/pages/dashboard/tiles/AufgabenTile.tsx | 58 ++ +- frontend/src/pages/dashboard/tiles/EinsatzbereitschaftTile.tsx | 74 +++ +- frontend/src/pages/dashboard/tiles/KennzahlenTile.tsx | 19 + +- frontend/src/pages/dashboard/tiles/MaengelTile.tsx | 58 ++ +- frontend/src/pages/dashboard/tiles/PrueftermineTile.tsx | 28 + +- frontend/src/pages/dashboard/tiles/QualifikationTile.tsx | 26 + +- frontend/src/pages/dashboard/types.ts | 55 ++ +- frontend/src/settings/startseite.ts | 30 - + +--- diff --git a/frontend/DEVLOG.md b/frontend/DEVLOG.md index 4a1e949..da87e38 100644 --- a/frontend/DEVLOG.md +++ b/frontend/DEVLOG.md @@ -551,3 +551,65 @@ Keine Commits in dieser Session. - frontend/src/pages/kontrolle/PositionCard.tsx | 48 ++++++++++++++++++++++++++++++++++-------------- --- +## 2026-09-07 12:05 – 12:07 (2m) +**Beschreibung:** Claude Code Session +**Projekt:** asb-material + +### Commits +Keine Commits in dieser Session. + +### Geänderte Dateien +- DEVLOG.md | 1923 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ +- backend/DEVLOG.md | 128 ++++ +- frontend/DEVLOG.md | 267 ++++++++ +- frontend/src/App.tsx | 5 +- +- frontend/src/components/AppShell.tsx | 2 - +- frontend/src/components/StartseiteAuswahl.tsx | 29 - +- frontend/src/pages/DashboardPage.tsx | 349 ---------- +- frontend/src/pages/LoginPage.tsx | 3 +- +- frontend/src/pages/dashboard/DashboardPage.tsx | 215 ++++++ +- frontend/src/pages/dashboard/TileFrame.tsx | 49 ++ +- frontend/src/pages/dashboard/layout.ts | 55 ++ +- frontend/src/pages/dashboard/tiles/AblaufdatenTile.tsx | 28 + +- frontend/src/pages/dashboard/tiles/AktivitaetTile.tsx | 34 + +- frontend/src/pages/dashboard/tiles/AufgabenTile.tsx | 58 ++ +- frontend/src/pages/dashboard/tiles/EinsatzbereitschaftTile.tsx | 74 +++ +- frontend/src/pages/dashboard/tiles/KennzahlenTile.tsx | 19 + +- frontend/src/pages/dashboard/tiles/MaengelTile.tsx | 58 ++ +- frontend/src/pages/dashboard/tiles/PrueftermineTile.tsx | 28 + +- frontend/src/pages/dashboard/tiles/QualifikationTile.tsx | 26 + +- frontend/src/pages/dashboard/types.ts | 55 ++ +- frontend/src/settings/startseite.ts | 30 - + +--- +## 2026-09-07 12:08 – 12:08 (0m) +**Beschreibung:** Claude Code Session +**Projekt:** frontend + +### Commits +Keine Commits in dieser Session. + +### Geänderte Dateien +- DEVLOG.md | 1923 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ +- backend/DEVLOG.md | 128 ++++ +- frontend/DEVLOG.md | 267 ++++++++ +- frontend/src/App.tsx | 5 +- +- frontend/src/components/AppShell.tsx | 2 - +- frontend/src/components/StartseiteAuswahl.tsx | 29 - +- frontend/src/pages/DashboardPage.tsx | 349 ---------- +- frontend/src/pages/LoginPage.tsx | 3 +- +- frontend/src/pages/dashboard/DashboardPage.tsx | 215 ++++++ +- frontend/src/pages/dashboard/TileFrame.tsx | 49 ++ +- frontend/src/pages/dashboard/layout.ts | 55 ++ +- frontend/src/pages/dashboard/tiles/AblaufdatenTile.tsx | 28 + +- frontend/src/pages/dashboard/tiles/AktivitaetTile.tsx | 34 + +- frontend/src/pages/dashboard/tiles/AufgabenTile.tsx | 58 ++ +- frontend/src/pages/dashboard/tiles/EinsatzbereitschaftTile.tsx | 74 +++ +- frontend/src/pages/dashboard/tiles/KennzahlenTile.tsx | 19 + +- frontend/src/pages/dashboard/tiles/MaengelTile.tsx | 58 ++ +- frontend/src/pages/dashboard/tiles/PrueftermineTile.tsx | 28 + +- frontend/src/pages/dashboard/tiles/QualifikationTile.tsx | 26 + +- frontend/src/pages/dashboard/types.ts | 55 ++ +- frontend/src/settings/startseite.ts | 30 - + +--- diff --git a/frontend/package-lock.json b/frontend/package-lock.json index e0e3a43..8408472 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,17 +8,24 @@ "name": "mabea-frontend", "version": "0.1.0", "dependencies": { + "@radix-ui/react-slot": "^1.3.3", "@zxing/browser": "^0.1.5", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", "idb-keyval": "^6.2.1", + "lucide-react": "^1.42.0", "react": "^18.3.1", "react-dom": "^18.3.1", - "react-router-dom": "^6.26.2" + "react-router-dom": "^6.26.2", + "tailwind-merge": "^3.6.0" }, "devDependencies": { "@playwright/test": "^1.47.2", + "@tailwindcss/vite": "^4.3.3", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", "@vitejs/plugin-react": "^4.3.1", + "tailwindcss": "^4.3.3", "typescript": "^5.5.4", "vite": "^5.4.6", "vite-plugin-pwa": "^0.20.5" @@ -2110,6 +2117,39 @@ "node": ">=20" } }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.5.tgz", + "integrity": "sha512-+48PbAAbq3didjJxa+OaWY2ZwgAKsNiRGyeHKszblZMQ+kcpd9pAaT11cMkGEie0vsOi3QdeTE6d5Fe3Gn61kA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.3.3.tgz", + "integrity": "sha512-qx7oqnYbxnK9kYI9m317qmFmEgo6ywqWvbTogdj7cL9p3/yx4M48p7Rnw5z3H890cL/ow/EeWJsuTykeZVXP5Q==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.5" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@remix-run/router": { "version": "1.23.4", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.4.tgz", @@ -2596,6 +2636,278 @@ "win32" ] }, + "node_modules/@tailwindcss/node": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.3.tgz", + "integrity": "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.24.1", + "jiti": "^2.7.0", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.3.3" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.3.tgz", + "integrity": "sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.3.3", + "@tailwindcss/oxide-darwin-arm64": "4.3.3", + "@tailwindcss/oxide-darwin-x64": "4.3.3", + "@tailwindcss/oxide-freebsd-x64": "4.3.3", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.3", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.3", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.3", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.3", + "@tailwindcss/oxide-linux-x64-musl": "4.3.3", + "@tailwindcss/oxide-wasm32-wasi": "4.3.3", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.3", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.3" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.3.tgz", + "integrity": "sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.3.tgz", + "integrity": "sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.3.tgz", + "integrity": "sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.3.tgz", + "integrity": "sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.3.tgz", + "integrity": "sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.3.tgz", + "integrity": "sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.3.tgz", + "integrity": "sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.3.tgz", + "integrity": "sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.3.tgz", + "integrity": "sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.3.tgz", + "integrity": "sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.11.1", + "@emnapi/runtime": "^1.11.1", + "@emnapi/wasi-threads": "^1.2.2", + "@napi-rs/wasm-runtime": "^1.1.4", + "@tybys/wasm-util": "^0.10.2", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.3.tgz", + "integrity": "sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.3.tgz", + "integrity": "sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.3.tgz", + "integrity": "sha512-yYU8cogLeSh/ms2jh8Fj7jaba/EWa7Ja6GoUqYZaraEuCI5YS6ms6ObZgjjedm+jm6XZjdNRWBpPP6Z86oOxcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.3.3", + "@tailwindcss/oxide": "4.3.3", + "tailwindcss": "4.3.3" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7 || ^8" + } + }, "node_modules/@trickfilm400/rollup-plugin-off-main-thread": { "version": "3.0.0-pre1", "resolved": "https://registry.npmjs.org/@trickfilm400/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-3.0.0-pre1.tgz", @@ -2668,14 +2980,14 @@ "version": "15.7.15", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@types/react": { "version": "18.3.31", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.31.tgz", "integrity": "sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@types/prop-types": "*", @@ -3064,6 +3376,27 @@ ], "license": "CC-BY-4.0" }, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", @@ -3134,7 +3467,7 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/data-view-buffer": { @@ -3255,6 +3588,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -3293,6 +3636,20 @@ "dev": true, "license": "ISC" }, + "node_modules/enhanced-resolve": { + "version": "5.24.5", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.5.tgz", + "integrity": "sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/es-abstract": { "version": "1.24.2", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", @@ -4448,6 +4805,16 @@ "node": ">=10" } }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -4520,6 +4887,267 @@ "node": ">=6" } }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -4549,6 +5177,15 @@ "yallist": "^3.0.2" } }, + "node_modules/lucide-react": { + "version": "1.42.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.42.0.tgz", + "integrity": "sha512-b3jprplnoLS8n5etw1z8xODe3hF/yjKATrTitsrKrnjUhCef5BdDct6Ppv3zVvzFwmtfWgLO6XNM3C9fAD93ug==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -5529,6 +6166,37 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/tailwind-merge": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.6.0.tgz", + "integrity": "sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwindcss": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.3.tgz", + "integrity": "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, "node_modules/temp-dir": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 01d1df6..9fa6a90 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,17 +11,24 @@ "test:e2e:ui": "playwright test --ui" }, "dependencies": { + "@radix-ui/react-slot": "^1.3.3", "@zxing/browser": "^0.1.5", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", "idb-keyval": "^6.2.1", + "lucide-react": "^1.42.0", "react": "^18.3.1", "react-dom": "^18.3.1", - "react-router-dom": "^6.26.2" + "react-router-dom": "^6.26.2", + "tailwind-merge": "^3.6.0" }, "devDependencies": { "@playwright/test": "^1.47.2", + "@tailwindcss/vite": "^4.3.3", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", "@vitejs/plugin-react": "^4.3.1", + "tailwindcss": "^4.3.3", "typescript": "^5.5.4", "vite": "^5.4.6", "vite-plugin-pwa": "^0.20.5" diff --git a/frontend/src/components/status/ReadinessBadge.tsx b/frontend/src/components/status/ReadinessBadge.tsx index 491cd04..3d1bd92 100644 --- a/frontend/src/components/status/ReadinessBadge.tsx +++ b/frontend/src/components/status/ReadinessBadge.tsx @@ -1,3 +1,5 @@ +import { Badge } from "../ui/badge"; + export type Readiness = "ready" | "limited" | "not-ready" | "unknown"; const LABEL: Record = { @@ -7,18 +9,18 @@ const LABEL: Record = { unknown: "Unbekannt", }; -const ICON: Record = { - ready: "🟢", - limited: "🟡", - "not-ready": "🔴", - unknown: "⚪", +const VARIANT: Record = { + ready: "success", + limited: "warning", + "not-ready": "danger", + unknown: "neutral", }; -const KLASSE: Record = { - ready: "badge-ready", - limited: "badge-limited", - "not-ready": "badge-not-ready", - unknown: "badge-unknown", +const PUNKT_FARBE: Record = { + ready: "bg-success", + limited: "bg-warning", + "not-ready": "bg-danger", + unknown: "bg-muted-foreground", }; interface Props { @@ -29,12 +31,14 @@ interface Props { /** * einzige Stelle, die Einsatzbereitschaft farblich/textlich * darstellt - "unbekannt" (noch nie kontrolliert) sieht bewusst NIE wie - * "einsatzbereit" aus (eigene Farbe/Icon, siehe global.css badge-unknown). + * "einsatzbereit" aus (eigene Farbe, kein Emoji-Icon mehr - konsistenter auf + * Wandmonitoren, wo Emoji-Rendering je nach OS variiert). */ export function ReadinessBadge({ status, label }: Props) { return ( - - {ICON[status]} {label ?? LABEL[status]} - + + + {label ?? LABEL[status]} + ); } diff --git a/frontend/src/components/ui/badge.tsx b/frontend/src/components/ui/badge.tsx new file mode 100644 index 0000000..ecf8c16 --- /dev/null +++ b/frontend/src/components/ui/badge.tsx @@ -0,0 +1,25 @@ +import { cva, type VariantProps } from "class-variance-authority"; +import type { HTMLAttributes } from "react"; + +import { cn } from "../../lib/utils"; + +const badgeVariants = cva("inline-flex items-center gap-1.5 rounded-full px-2.5 py-0.5 text-xs font-medium", { + variants: { + variant: { + neutral: "bg-surface-sunken text-muted-foreground", + success: "bg-success-bg text-success", + warning: "bg-warning-bg text-warning", + danger: "bg-danger-bg text-danger", + primary: "bg-primary-soft text-primary", + }, + }, + defaultVariants: { + variant: "neutral", + }, +}); + +export interface BadgeProps extends HTMLAttributes, VariantProps {} + +export function Badge({ className, variant, ...props }: BadgeProps) { + return ; +} diff --git a/frontend/src/components/ui/button.tsx b/frontend/src/components/ui/button.tsx new file mode 100644 index 0000000..06a1e9d --- /dev/null +++ b/frontend/src/components/ui/button.tsx @@ -0,0 +1,42 @@ +import { Slot } from "@radix-ui/react-slot"; +import { cva, type VariantProps } from "class-variance-authority"; +import { forwardRef, type ButtonHTMLAttributes } from "react"; + +import { cn } from "../../lib/utils"; + +const buttonVariants = cva( + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary disabled:pointer-events-none disabled:opacity-50", + { + variants: { + variant: { + default: "bg-primary text-white hover:bg-primary-hover", + secondary: "bg-surface-sunken text-foreground border border-border hover:bg-border/40", + ghost: "hover:bg-surface-sunken text-foreground", + danger: "bg-danger text-white hover:opacity-90", + }, + size: { + default: "h-9 px-4 py-2", + sm: "h-8 px-3 text-xs", + icon: "h-9 w-9", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +); + +export interface ButtonProps + extends ButtonHTMLAttributes, + VariantProps { + asChild?: boolean; +} + +export const Button = forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button"; + return ; + } +); +Button.displayName = "Button"; diff --git a/frontend/src/components/ui/card.tsx b/frontend/src/components/ui/card.tsx new file mode 100644 index 0000000..8dcc04b --- /dev/null +++ b/frontend/src/components/ui/card.tsx @@ -0,0 +1,29 @@ +import { forwardRef, type HTMLAttributes } from "react"; + +import { cn } from "../../lib/utils"; + +export const Card = forwardRef>(({ className, ...props }, ref) => ( +
+)); +Card.displayName = "Card"; + +export const CardHeader = forwardRef>(({ className, ...props }, ref) => ( +
+)); +CardHeader.displayName = "CardHeader"; + +export const CardTitle = forwardRef>( + ({ className, ...props }, ref) => ( +

+ ) +); +CardTitle.displayName = "CardTitle"; + +export const CardContent = forwardRef>(({ className, ...props }, ref) => ( +
+)); +CardContent.displayName = "CardContent"; diff --git a/frontend/src/lib/utils.ts b/frontend/src/lib/utils.ts new file mode 100644 index 0000000..b500fb1 --- /dev/null +++ b/frontend/src/lib/utils.ts @@ -0,0 +1,6 @@ +import { clsx, type ClassValue } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export function cn(...inputs: ClassValue[]): string { + return twMerge(clsx(inputs)); +} diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index dd41241..44717ed 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -4,6 +4,7 @@ import { BrowserRouter } from "react-router-dom"; import App from "./App"; import "./styles/global.css"; +import "./styles/tailwind.css"; createRoot(document.getElementById("root")!).render( diff --git a/frontend/src/pages/dashboard/DashboardPage.tsx b/frontend/src/pages/dashboard/DashboardPage.tsx index 366c576..2711baa 100644 --- a/frontend/src/pages/dashboard/DashboardPage.tsx +++ b/frontend/src/pages/dashboard/DashboardPage.tsx @@ -2,8 +2,11 @@ import { useCallback, useEffect, useState } from "react"; import { apiRequest } from "../../api/client"; import type { HistorieEintrag, Mangel } from "../../api/types"; -import type { DashboardTileId } from "./layout"; -import { layoutLaden, layoutSpeichern, STANDARD_REIHENFOLGE } from "./layout"; +import { Button } from "../../components/ui/button"; +import { cn } from "../../lib/utils"; +import type { DashboardTileGroesse, DashboardTileId } from "./layout"; +import { flexBasisFuerGroesse, GROESSEN_REIHENFOLGE, layoutLaden, layoutSpeichern, STANDARD_REIHENFOLGE } from "./layout"; +import type { TileAkzent } from "./TileFrame"; import { TileFrame } from "./TileFrame"; import { AblaufdatenTile } from "./tiles/AblaufdatenTile"; import { AktivitaetTile } from "./tiles/AktivitaetTile"; @@ -41,6 +44,7 @@ export function DashboardPage() { const [layout, setLayout] = useState(() => layoutLaden()); const [bearbeitenModus, setBearbeitenModus] = useState(false); const [monitorModus, setMonitorModus] = useState(false); + const [zuletztAktualisiert, setZuletztAktualisiert] = useState(null); const ladeDaten = useCallback(() => { return Promise.all([ @@ -61,6 +65,7 @@ export function DashboardPage() { setQualifikationsablaeufe(q); setAktivitaet(h); setFehler(null); + setZuletztAktualisiert(new Date()); }) .catch(() => setFehler("Dashboard-Daten konnten nicht geladen werden.")); }, []); @@ -102,8 +107,16 @@ export function DashboardPage() { layoutSpeichern(neuesLayout); } + function groesseAendern(id: DashboardTileId) { + const aktuelle = layout.groessen[id] ?? "normal"; + const naechsteIndex = (GROESSEN_REIHENFOLGE.indexOf(aktuelle) + 1) % GROESSEN_REIHENFOLGE.length; + const neuesLayout = { ...layout, groessen: { ...layout.groessen, [id]: GROESSEN_REIHENFOLGE[naechsteIndex] } }; + setLayout(neuesLayout); + layoutSpeichern(neuesLayout); + } + function layoutZuruecksetzen() { - const neuesLayout = { reihenfolge: STANDARD_REIHENFOLGE, ausgeblendet: [] }; + const neuesLayout: typeof layout = { reihenfolge: STANDARD_REIHENFOLGE, ausgeblendet: [], groessen: {} }; setLayout(neuesLayout); layoutSpeichern(neuesLayout); } @@ -128,6 +141,25 @@ export function DashboardPage() { const pruefungenUeberfaellig = prueftermine.filter((p) => p.status === "abgelaufen").length; const ablaufdatenAbgelaufen = ablaufdaten.filter((a) => a.status === "abgelaufen").length; + function tileAkzent(id: DashboardTileId): TileAkzent { + switch (id) { + case "maengel": + if (maengelKritischAnzahl > 0) return "danger"; + if (maengelNeuAnzahl > 0) return "warning"; + return "neutral"; + case "prueftermine": + return pruefungenUeberfaellig > 0 ? "danger" : "neutral"; + case "ablaufdaten": + return ablaufdatenAbgelaufen > 0 ? "danger" : "neutral"; + case "einsatzbereitschaft": + if (bereitschaft && bereitschaft.nicht_einsatzbereit > 0) return "danger"; + if (bereitschaft && bereitschaft.eingeschraenkt_einsatzbereit > 0) return "warning"; + return "neutral"; + default: + return "neutral"; + } + } + function tileInhalt(id: DashboardTileId) { switch (id) { case "aufgaben": @@ -163,44 +195,73 @@ export function DashboardPage() { } return ( -
-
-

Dashboard

-
- {!monitorModus && ( - - )} - {bearbeitenModus && ( - - )} - + + {bearbeitenModus && ( + + )} + +
-
+ )} + {monitorModus && ( + + )} {fehler &&
{fehler}
} {laedt &&

Lade…

} + {bearbeitenModus && layout.ausgeblendet.length > 0 && ( +
+ Ausgeblendet:{" "} + {layout.ausgeblendet.map((id, i) => ( + + {i > 0 && ", "} + + + ))} +
+ )} {!laedt && bereitschaft && ( -
+
{layout.reihenfolge.map((id, index) => { const ausgeblendet = layout.ausgeblendet.includes(id); if (ausgeblendet && !bearbeitenModus) return null; const inhalt = tileInhalt(id); if (!inhalt) return null; + const groesse: DashboardTileGroesse = layout.groessen[id] ?? (id === "aufgaben" ? "voll" : "normal"); return ( 0} kannRunterVerschieben={index < layout.reihenfolge.length - 1} onAusblendenUmschalten={() => ausblendenUmschalten(id)} + onGroesseAendern={() => groesseAendern(id)} onHoch={() => tileVerschieben(id, -1)} onRunter={() => tileVerschieben(id, 1)} > diff --git a/frontend/src/pages/dashboard/TileFrame.tsx b/frontend/src/pages/dashboard/TileFrame.tsx index 040a885..48b722c 100644 --- a/frontend/src/pages/dashboard/TileFrame.tsx +++ b/frontend/src/pages/dashboard/TileFrame.tsx @@ -1,13 +1,29 @@ import type { ReactNode } from "react"; +import { Button } from "../../components/ui/button"; +import { Card, CardContent, CardHeader, CardTitle } from "../../components/ui/card"; +import { cn } from "../../lib/utils"; +import { GROESSE_LABEL, type DashboardTileGroesse } from "./layout"; + +export type TileAkzent = "neutral" | "warning" | "danger"; + +const AKZENT_KLASSE: Record = { + neutral: "border-l-border", + warning: "border-l-warning", + danger: "border-l-danger", +}; + interface Props { title: string; - vollbreite?: boolean; + flexBasis: string; + groesse: DashboardTileGroesse; + akzent?: TileAkzent; bearbeitenAktiv: boolean; ausgeblendet: boolean; kannHochVerschieben: boolean; kannRunterVerschieben: boolean; onAusblendenUmschalten: () => void; + onGroesseAendern: () => void; onHoch: () => void; onRunter: () => void; children: ReactNode; @@ -15,35 +31,58 @@ interface Props { export function TileFrame({ title, - vollbreite, + flexBasis, + groesse, + akzent = "neutral", bearbeitenAktiv, ausgeblendet, kannHochVerschieben, kannRunterVerschieben, onAusblendenUmschalten, + onGroesseAendern, onHoch, onRunter, children, }: Props) { return ( -
-
-

{title}

+ + + {title} {bearbeitenAktiv && ( -
- - + - + + +
)} -
- {!ausgeblendet && children} -
+ + {!ausgeblendet && {children}} + ); } diff --git a/frontend/src/pages/dashboard/layout.ts b/frontend/src/pages/dashboard/layout.ts index 7c5f080..b3b66da 100644 --- a/frontend/src/pages/dashboard/layout.ts +++ b/frontend/src/pages/dashboard/layout.ts @@ -19,9 +19,21 @@ export const STANDARD_REIHENFOLGE: DashboardTileId[] = [ "aktivitaet", ]; +export type DashboardTileGroesse = "schmal" | "normal" | "breit" | "voll"; + +export const GROESSEN_REIHENFOLGE: DashboardTileGroesse[] = ["schmal", "normal", "breit", "voll"]; + +export const GROESSE_LABEL: Record = { + schmal: "Schmal", + normal: "Normal", + breit: "Breit", + voll: "Volle Breite", +}; + export interface DashboardLayout { reihenfolge: DashboardTileId[]; ausgeblendet: DashboardTileId[]; + groessen: Partial>; } const SPEICHER_KEY = "mabea-dashboard-layout"; @@ -30,19 +42,42 @@ function istGueltigeId(wert: unknown): wert is DashboardTileId { return typeof wert === "string" && (STANDARD_REIHENFOLGE as string[]).includes(wert); } +function istGueltigeGroesse(wert: unknown): wert is DashboardTileGroesse { + return typeof wert === "string" && (GROESSEN_REIHENFOLGE as string[]).includes(wert); +} + export function layoutLaden(): DashboardLayout { try { const roh = localStorage.getItem(SPEICHER_KEY); - if (!roh) return { reihenfolge: STANDARD_REIHENFOLGE, ausgeblendet: [] }; + if (!roh) return { reihenfolge: STANDARD_REIHENFOLGE, ausgeblendet: [], groessen: {} }; const geparst = JSON.parse(roh); const gespeicherteReihenfolge = Array.isArray(geparst.reihenfolge) ? geparst.reihenfolge.filter(istGueltigeId) : []; const fehlende = STANDARD_REIHENFOLGE.filter((id) => !gespeicherteReihenfolge.includes(id)); const ausgeblendet = Array.isArray(geparst.ausgeblendet) ? geparst.ausgeblendet.filter(istGueltigeId) : []; - return { reihenfolge: [...gespeicherteReihenfolge, ...fehlende], ausgeblendet }; + const groessen: Partial> = {}; + if (geparst.groessen && typeof geparst.groessen === "object") { + for (const [id, groesse] of Object.entries(geparst.groessen)) { + if (istGueltigeId(id) && istGueltigeGroesse(groesse)) groessen[id] = groesse; + } + } + return { reihenfolge: [...gespeicherteReihenfolge, ...fehlende], ausgeblendet, groessen }; } catch { - return { reihenfolge: STANDARD_REIHENFOLGE, ausgeblendet: [] }; + return { reihenfolge: STANDARD_REIHENFOLGE, ausgeblendet: [], groessen: {} }; + } +} + +export function flexBasisFuerGroesse(groesse: DashboardTileGroesse): string { + switch (groesse) { + case "schmal": + return "14rem"; + case "breit": + return "28rem"; + case "voll": + return "100%"; + default: + return "18rem"; } } diff --git a/frontend/src/styles/tailwind.css b/frontend/src/styles/tailwind.css new file mode 100644 index 0000000..d279d8d --- /dev/null +++ b/frontend/src/styles/tailwind.css @@ -0,0 +1,23 @@ +/* Nur Theme+Utilities, kein Preflight - global.css bleibt für den Rest der App + die Basis, Tailwind wird gezielt fürs Dashboard-Redesign genutzt. */ +@import "tailwindcss/theme.css" layer(theme); +@import "tailwindcss/utilities.css" layer(utilities); + +@theme { + --color-background: var(--color-bg); + --color-surface: var(--color-surface); + --color-surface-sunken: var(--color-surface-sunken); + --color-border: var(--color-border); + --color-border-strong: var(--color-border-strong); + --color-foreground: var(--color-text); + --color-muted-foreground: var(--color-text-muted); + --color-primary: var(--color-primary); + --color-primary-hover: var(--color-primary-hover); + --color-primary-soft: var(--color-primary-soft); + --color-danger: var(--color-danger); + --color-danger-bg: var(--color-danger-bg); + --color-warning: var(--color-warning); + --color-warning-bg: var(--color-warning-bg); + --color-success: var(--color-success); + --color-success-bg: var(--color-success-bg); +} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index d3b2593..55bd4c3 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -1,3 +1,4 @@ +import tailwindcss from "@tailwindcss/vite"; import react from "@vitejs/plugin-react"; import { defineConfig } from "vite"; import { VitePWA } from "vite-plugin-pwa"; @@ -9,6 +10,7 @@ import { VitePWA } from "vite-plugin-pwa"; // nicht aus, ohne Background-Sync-Komplexität einzuführen. export default defineConfig({ plugins: [ + tailwindcss(), react(), VitePWA({ registerType: "autoUpdate",