From 141df021c1b7e5ed6244aa0ebe6f56f5bd9e1cdd Mon Sep 17 00:00:00 2001 From: Lars Date: Thu, 23 Apr 2026 16:18:10 +0200 Subject: [PATCH] refactor: rename Dashboard-Lab-Widgets to Dashboard-Widgets and update related documentation - Renamed references from "Dashboard-Lab-Widgets" to "Dashboard-Widgets" across documentation and codebase for consistency. - Removed the deprecated Dashboard-Lab page and integrated its functionality into the new Dashboard-Widgets layout. - Updated widget registration and configuration handling to reflect the new naming convention. - Adjusted documentation in `.claude/docs/technical/DASHBOARD_WIDGETS_AGENT_GUIDE.md` and other related files to ensure clarity on the updated structure. - Bumped application version to reflect these changes. --- .claude/docs/README.md | 2 +- .../DASHBOARD_WIDGETS_AGENT_GUIDE.md | 20 +- .claude/rules/ARCHITECTURE.md | 6 +- CLAUDE.md | 6 +- backend/dashboard_layout_schema.py | 4 +- backend/main.py | 2 +- backend/routers/app_dashboard.py | 4 +- backend/widget_catalog.py | 2 +- frontend/src/App.jsx | 2 - .../QuickCaptureWidget.jsx | 5 +- frontend/src/pages/DashboardLabPage.jsx | 512 ------------------ frontend/src/pages/SettingsPage.jsx | 31 +- frontend/src/utils/api.js | 2 +- .../widgetSystem/QuickCaptureConfigEditor.jsx | 2 +- 14 files changed, 29 insertions(+), 571 deletions(-) delete mode 100644 frontend/src/pages/DashboardLabPage.jsx diff --git a/.claude/docs/README.md b/.claude/docs/README.md index 8c409d9..4e26d9a 100644 --- a/.claude/docs/README.md +++ b/.claude/docs/README.md @@ -52,7 +52,7 @@ _Dieser Ordner `.claude/docs/` ist per `.gitignore`-Ausnahme **versioniert** (Sp |--------|-------------|-------------------| | Data Layer / Charts (Phase 0c) | `functional/DATA_ARCHITECTURE.md`, `technical/DATA_LAYER_EXTENSION_GUIDE.md` | `backend/data_layer/`, `backend/routers/charts.py` | | Platzhalter / Registry | `technical/PLACEHOLDER_REGISTRY_FRAMEWORK.md`, `technical/PLACEHOLDER_DEVELOPMENT_GUIDE.md` | `backend/placeholder_registrations/`, `backend/placeholder_resolver.py` | -| Dashboard-Lab-Widgets | `technical/DASHBOARD_WIDGETS_AGENT_GUIDE.md` | Widget-Katalog + Registrierung (siehe Guide) | +| Dashboard-Widgets | `technical/DASHBOARD_WIDGETS_AGENT_GUIDE.md` | Widget-Katalog + Registrierung (siehe Guide) | | Training Profiler / Resolver | `technical/TRAINING_PROFILE_RESOLVER_LAYER1.md`, `functional/TRAINING_TYPE_PROFILES.md` | Resolver-Module wie im Guide genannt | | Universal CSV Import | `technical/UNIVERSAL_CSV_IMPORT_AGENT_GUIDE.md` | `backend/csv_parser/`, `routers/csv_import.py`, `routers/admin_csv_templates.py` | | Aktivität Produktionsreife | `technical/ACTIVITY_PRODUCTION_ARCHITECTURE_AND_PHASES.md` (+ EAV-Guide) | `backend/data_layer/activity_session_metrics.py`, `activity_metrics.py`, CSV-Orchestrierung | diff --git a/.claude/docs/technical/DASHBOARD_WIDGETS_AGENT_GUIDE.md b/.claude/docs/technical/DASHBOARD_WIDGETS_AGENT_GUIDE.md index d0c53da..dffae50 100644 --- a/.claude/docs/technical/DASHBOARD_WIDGETS_AGENT_GUIDE.md +++ b/.claude/docs/technical/DASHBOARD_WIDGETS_AGENT_GUIDE.md @@ -1,7 +1,7 @@ -# Dashboard-Lab-Widgets – Anleitung für Coding-Agenten +# Dashboard-Widgets – Anleitung für Coding-Agenten -Ziel: Ein neues Dashboard-Widget **end-to-end** korrekt einbinden (Backend-Katalog, Validierung, API-Layout, Frontend-Registrierung, optional Lab-Editor für `config`). -Kontext: **Dashboard-Lab** unter geschützten Endpoints `GET/PUT /api/app/...` (siehe `backend/routers/app_dashboard.py`). Layout liegt pro Profil in `profiles.dashboard_layout` (JSON). +Ziel: Ein neues Dashboard-Widget **end-to-end** korrekt einbinden (Backend-Katalog, Validierung, API-Layout, Frontend-Registrierung, optional Editor für `config` in **Übersicht anpassen**). +Kontext: Geschützte Endpoints `GET/PUT /api/app/...` (siehe `backend/routers/app_dashboard.py`). Layout liegt pro Profil in `profiles.dashboard_layout` (JSON). Nutzer-Oberfläche: `frontend/src/pages/DashboardConfigurePage.jsx` (Route z. B. `/settings/dashboard-layout`). --- @@ -23,7 +23,7 @@ Kontext: **Dashboard-Lab** unter geschützten Endpoints `GET/PUT /api/app/...` ( | Anforderung | Beschreibung | |-------------|--------------| | **A1 – Zentrale Auflösung** | Backend ermittelt pro Profil (effektiver Tier + Restrictions), welche Widget-IDs **erlaubt** sind – idealerweise in **einer** Stelle (Erweiterung des Katalog-Endpoints oder dedizierter Entitlements-Teil der Response). Intern: `check_feature_access` und später ggf. Mapping Widget-ID → Feature-ID(n) / Cluster. | -| **A2 – Nutzer-Konfigurator** | Im Dashboard-Lab (und jedem späteren Layout-Konfigurator): Widgets **ohne Berechtigung nicht anbieten** (ausgeblendet oder gar nicht in der Liste). Alle **erlaubten** Widgets bleiben wie heute wählbar. | +| **A2 – Nutzer-Konfigurator** | Im Layout-Konfigurator (**Übersicht anpassen**): Widgets **ohne Berechtigung nicht anbieten** (ausgeblendet oder gar nicht in der Liste). Alle **erlaubten** Widgets bleiben wie heute wählbar. | | **A3 – Layout-Persistenz** | `PUT /api/app/dashboard-layout`: Layout darf **keine** nicht erlaubten Widgets dauerhaft speichern – entweder **ablehnen** (422) oder **beim Speichern entfernen/deaktivieren** (Policy festlegen und dokumentieren). Verhindert „gespeichert, aber nie sichtbar“-Zombies. | | **A4 – API-/Datenschutz** | Sichtbarkeit im UI reicht nicht: Endpoints, die **Inhalte** für gated Widgets liefern (Charts, KI, …), müssen weiterhin wie heute **eigenständig** über Features abgesichert sein (`check_feature_access`, 403). | @@ -43,7 +43,7 @@ Kontext: **Dashboard-Lab** unter geschützten Endpoints `GET/PUT /api/app/...` ( 2. **`backend/dashboard_layout_schema.py`** – `DashboardLayoutPayload`: jede Zeile hat `id`, `enabled`, optional `config`. IDs müssen in `ALLOWED_WIDGET_IDS` sein (aus dem Katalog abgeleitet). 3. **`backend/dashboard_widget_config.py`** – `validate_widget_entry_config`: **nur** Widgets in `WIDGETS_ALLOWING_CONFIG` dürfen **nicht-leere** `config` haben; Keys werden streng validiert (unbekannte Keys → Fehler). 4. **Frontend** – `ensureDashboardWidgetsRegistered()` in `frontend/src/widgetSystem/registerDashboardWidgets.js`: verbindet jede Katalog-ID mit einer React-Komponente und mappt `ctx.layoutEntry.config` auf Props. -5. **Dashboard-Lab-UI** – `frontend/src/pages/DashboardLabPage.jsx`: Umsortieren, Ein/Aus, Speichern; **zusätzliche** UI nur nötig, wenn das Widget konfigurierbare Felder braucht. +5. **Layout-Editor (Produkt)** – `frontend/src/pages/DashboardConfigurePage.jsx`: Umsortieren, Ein/Aus, Speichern; **zusätzliche** UI nur nötig, wenn das Widget konfigurierbare Felder braucht. --- @@ -52,7 +52,7 @@ Kontext: **Dashboard-Lab** unter geschützten Endpoints `GET/PUT /api/app/...` ( | Schritt | Datei | Aktion | |--------|--------|--------| | A | `backend/widget_catalog.py` | Neuen Eintrag `{ "id", "title", "description" }` in `WIDGET_CATALOG` einfügen (Reihenfolge = Default-Reihenfolge im Layout). Optional `"requires_feature": ""` für Tarif-Gating (`dashboard_widget_entitlements`). | -| B | `backend/widget_catalog.py` | Optional: ID zu `DEFAULT_LAB_WIDGET_IDS` hinzufügen, wenn es im Standard-Lab **aktiv** sein soll. | +| B | `backend/widget_catalog.py` | Optional: ID zu `DEFAULT_LAB_WIDGET_IDS` hinzufügen, wenn es im Server-Standardlayout **aktiv** sein soll (Feld `lab_default_layout` in der Layout-API). | | C | `frontend/src/components/dashboard-widgets/MyWidget.jsx` (oder Legacy-Widget unter `dashboard-widgets-legacy/`) | React-Komponente implementieren; typischerweise `refreshTick` aus `mapProps` nutzen, um Daten neu zu laden. | | D | `frontend/src/widgetSystem/registerDashboardWidgets.js` | `import` + `registerDashboardWidget({ id, Component, mapProps })` – `id` **exakt** wie im Katalog. | | E | `backend/tests/test_widget_catalog.py` | Läuft implizit mit; bei Strukturänderungen Katalog-Tests beachten. | @@ -110,11 +110,11 @@ mapProps: (ctx) => ({ **Abgleich mit Chart-Zeitraum:** Für `chart_days` existiert `frontend/src/widgetSystem/bodyChartDays.js` (`BODY_CHART_DAYS_MIN/MAX`, `normalizeBodyChartDays`). Entweder in `mapProps` normalisieren (wie `body_overview`) oder rohen Wert durchreichen und in der Widget-Komponente normalisieren (wie `nutrition_detail_charts` / `TrendKcalWeightWidget`) – **beides** ist im Projekt vertreten; wichtig ist Konsistenz mit der Backend-Grenze 7–90. -### 3.4 Dashboard-Lab-Editor (`DashboardLabPage.jsx`) +### 3.4 Layout-Editor (`DashboardConfigurePage.jsx`) Ohne UI-Änderung bleibt `config` beim Nutzer `{}` – konfigurierbare Widgets brauchen **Editor-Controls**: -- **Einfaches Zahlfeld `chart_days`:** Eintrag in `CHART_DAYS_WIDGET_IDS` (Set oben in der Datei) + bestehendes Label/`aria-label`-Pattern für die Zeitraum-Zeile erweitern (siehe `body_overview`, `nutrition_detail_charts`). +- **Einfaches Zahlfeld `chart_days`:** Eintrag in `CHART_DAYS_WIDGET_IDS` (Set oben in `DashboardConfigurePage.jsx`) + bestehendes Label/`aria-label`-Pattern für die Zeitraum-Zeile erweitern (siehe `body_overview`, `nutrition_detail_charts`). - **Strukturierte Config (Listen, mehrere Booleans):** Eigenes Editor-Komponenten-File nach Vorbild `KpiBoardConfigEditor.jsx` / `QuickCaptureConfigEditor.jsx` einbinden und `setLayout` + `normalizeLayoutForEditor` wie bei den bestehenden Blöcken verwenden. Nach Speichern ruft die Seite `api.putAppDashboardLayout(layout)` auf; das Backend validiert über `DashboardLayoutPayload` → `validate_widget_entry_config`. @@ -137,7 +137,7 @@ Nach Speichern ruft die Seite `api.putAppDashboardLayout(layout)` auf; das Backe ## 5. API zum Prüfen - `GET /api/app/widgets/catalog` – Katalog inkl. `allowed` je Widget (Auth + `X-Profile-Id` wie andere App-Endpoints). -- `GET /api/app/dashboard-layout` – `layout` (effektiv, bereinigt), `custom`, `product_default_layout` (Übersichts-Standard), `lab_default_layout` (Dashboard-Lab-Standard). +- `GET /api/app/dashboard-layout` – `layout` (effektiv, bereinigt), `custom`, `product_default_layout` (Übersichts-Standard), `lab_default_layout` (Servertemplate für Editor/Reset; Feldname historisch). - `PUT /api/app/dashboard-layout` – Body `{ "version": 1, "widgets": [ ... ] }` (unerlaubte Widgets werden auf `enabled: false` gesetzt). --- @@ -160,4 +160,4 @@ Nach Speichern ruft die Seite `api.putAppDashboardLayout(layout)` auf; das Backe | HTTP | `backend/routers/app_dashboard.py` | | Registry + Render | `frontend/src/widgetSystem/dashboardWidgetRegistry.jsx` | | Dashboard-Widget-Registrierung | `frontend/src/widgetSystem/registerDashboardWidgets.js` | -| Lab-UI | `frontend/src/pages/DashboardLabPage.jsx` | +| Layout-Editor (Nutzer) | `frontend/src/pages/DashboardConfigurePage.jsx` | diff --git a/.claude/rules/ARCHITECTURE.md b/.claude/rules/ARCHITECTURE.md index d5e4e3b..df4b64b 100644 --- a/.claude/rules/ARCHITECTURE.md +++ b/.claude/rules/ARCHITECTURE.md @@ -455,15 +455,15 @@ NIEMALS gegen mitai.jinkendo.de --- -## 10. Dashboard-Lab-Widgets und Feature-System +## 10. Dashboard-Widgets und Feature-System -**Kontext:** Dashboard-Widgets (`backend/widget_catalog.py`, Lab unter `/api/app/...`) und das **Subscription-/Feature-Modell** (`features`, `tier_limits`, `check_feature_access` in `backend/auth.py`) sind **getrennte Schichten**, müssen aber bei tariffrelevanten Widgets **verknüpft** werden. +**Kontext:** Dashboard-Widgets (`backend/widget_catalog.py`, API unter `/api/app/...`) und das **Subscription-/Feature-Modell** (`features`, `tier_limits`, `check_feature_access` in `backend/auth.py`) sind **getrennte Schichten**, müssen aber bei tariffrelevanten Widgets **verknüpft** werden. **Bindend:** 1. **Keine fest codierten Tier-Namen** für Widget-Rechte – Tiers und Limits kommen aus der DB. 2. **Komplexität** (Module aus, Unter-Stufen, KI vs. Standard) liegt in der **Feature-/Subscription-Logik**, nicht verteilt in Widget-Komponenten. -3. **Nutzer-Konfigurator** (z. B. Dashboard-Lab): Widgets **ohne** passende Berechtigung **nicht anzeigen**; alle erlaubten Widgets bleiben verfügbar. +3. **Nutzer-Konfigurator** (**Übersicht anpassen** / `DashboardConfigurePage`): Widgets **ohne** passende Berechtigung **nicht anzeigen**; alle erlaubten Widgets bleiben verfügbar. 4. **Backend** liefert die effektive Erlaubnis (z. B. über erweiterten Katalog oder Entitlements), und **validiert beim Speichern** des Layouts, dass keine unerlaubten Widget-IDs persistiert werden (Policy: ablehnen oder strippen – einheitlich halten). 5. **Daten/API:** Zusätzlich zur UI-Filterung müssen die **inhaltsliefernden Endpoints** weiterhin über `check_feature_access` geschützt sein (kein Leck über direkte API-Aufrufe). diff --git a/CLAUDE.md b/CLAUDE.md index a8384c5..df89feb 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -10,7 +10,7 @@ > | **Gitea-Landkarte (lokal gepflegt)** | **`.claude/docs/GITEA_ISSUES_INDEX.md`** | > | **Universal CSV Import** (neues Modul / Executor / Vorlagen) | **`.claude/docs/technical/UNIVERSAL_CSV_IMPORT_AGENT_GUIDE.md`** | > | **GUI / IA / Admin / Nav / PWA-Leiste** | **`docs/issues/GUI_IA_ADMIN_NAV_2026-04-05.md`** | -> | **Dashboard-Lab-Widgets** (Katalog, Registrierung, `config`) | **`.claude/docs/technical/DASHBOARD_WIDGETS_AGENT_GUIDE.md`** | +> | **Dashboard-Widgets** (Katalog, Registrierung, `config`) | **`.claude/docs/technical/DASHBOARD_WIDGETS_AGENT_GUIDE.md`** | > | **Agent-Einstieg** | **`.claude/README.md`** | > | **Activity Session Metrics (EAV, Attributprofile)** | **`.claude/docs/technical/ACTIVITY_SESSION_METRICS_EAV_AGENT_GUIDE.md`** | @@ -102,7 +102,7 @@ frontend/src/ ### Updates (23.04.2026 - Dashboard: veraltete Demo-Route entfernt, klare Produkt-Registry) -- **Frontend:** Veraltete Visualisierungs-Demo-Route und festes Demo-Layout entfernt; Widget-Registrierung in `frontend/src/widgetSystem/registerDashboardWidgets.js` (`ensureDashboardWidgetsRegistered`). Kern-Widgets unter `frontend/src/components/dashboard-widgets-legacy/`. Chart-Hilfen in `frontend/src/widgetSystem/dashboardChartUtils.js`. +- **Frontend:** Veraltete Visualisierungs-Demo-Route und festes Demo-Layout entfernt; Widget-Registrierung in `frontend/src/widgetSystem/registerDashboardWidgets.js` (`ensureDashboardWidgetsRegistered`). Kern-Widgets unter `frontend/src/components/dashboard-widgets-legacy/`. Chart-Hilfen in `frontend/src/widgetSystem/dashboardChartUtils.js`. Experimentelles Layout-Lab entfernt; Konfiguration nur noch **Übersicht anpassen** (`DashboardConfigurePage`). - **Doku:** `.claude/docs/technical/DASHBOARD_WIDGETS_AGENT_GUIDE.md` und Kommentar in `backend/widget_catalog.py` angepasst. ### Updates (09.04.2026 - Universal CSV Import, Prod-Migration abgeschlossen) @@ -896,7 +896,7 @@ Bottom-Padding Mobile: 80px (Navigation) |Auth-Flow|`.claude/library/AUTH.md`|Sicherheit + Sessions| |API-Referenz|`.claude/library/API\_REFERENCE.md`|Alle Endpoints| |Datenbankschema|`.claude/library/DATABASE.md`|Tabellen + Beziehungen| -|Dashboard-Lab-Widgets|`.claude/docs/technical/DASHBOARD_WIDGETS_AGENT_GUIDE.md`|Katalog, Validierung, Frontend-Registry, konfigurierbare `config`| +|Dashboard-Widgets|`.claude/docs/technical/DASHBOARD_WIDGETS_AGENT_GUIDE.md`|Katalog, Validierung, Frontend-Registry, konfigurierbare `config`| |Projekt-Doku (Git)|`docs/README.md` + `docs/issues/`|Issue-Specs, Reviews, Platzhalter-Governance, Status-Snapshots| > Library-Dateien werden mit `/document` generiert und nach größeren diff --git a/backend/dashboard_layout_schema.py b/backend/dashboard_layout_schema.py index 017ab32..df98d5d 100644 --- a/backend/dashboard_layout_schema.py +++ b/backend/dashboard_layout_schema.py @@ -1,5 +1,5 @@ """ -Dashboard-Layout v1: Validierung, Produkt-Standard (Übersicht) und Lab-Standard. +Dashboard-Layout v1: Validierung, Produkt-Standard (Übersicht) und Servertemplate (`lab_default_layout_dict`). Erlaubte Widget-IDs und Reihenfolge: widget_catalog.WIDGET_CATALOG. """ @@ -32,7 +32,7 @@ __all__ = [ def lab_default_layout_dict() -> dict[str, Any]: - """Standard für Dashboard-Lab (Experimentier-Widgets).""" + """Serverseitiges Standardlayout (DEFAULT_LAB_WIDGET_IDS); API-Feld `lab_default_layout`, u. a. für Editor/Reset.""" on = DEFAULT_LAB_WIDGET_IDS return { "version": 1, diff --git a/backend/main.py b/backend/main.py index c6ee964..c94ef5d 100644 --- a/backend/main.py +++ b/backend/main.py @@ -34,7 +34,7 @@ from routers import workflow_questions # Phase 1 Workflow Engine - Question Cat from routers import workflows # Phase 2 Workflow Engine - Execution from routers import reference_values # Persönliche Referenzwerte (Profil) from routers import admin_reference_value_types # Admin: Referenzwert-Typen -from routers import app_dashboard # Geschützter App-Bereich: Dashboard-Lab Layout +from routers import app_dashboard # Geschützter App-Bereich: Dashboard-Layout + Widget-Katalog from routers import csv_import, admin_csv_templates # Issue #21 Universal CSV Parser from routers import admin_training_parameters, admin_activity_attribute_profiles # EAV session metrics diff --git a/backend/routers/app_dashboard.py b/backend/routers/app_dashboard.py index fe8fdc5..36abf38 100644 --- a/backend/routers/app_dashboard.py +++ b/backend/routers/app_dashboard.py @@ -1,5 +1,5 @@ """ -Geschützter App-Bereich: Dashboard-Lab Layout (kein Produktiv-Dashboard). +Geschützter App-Bereich: Dashboard-Layout und Widget-Katalog. /api/app/dashboard-layout — nur mit Session + aktivem Profil (X-Profile-Id). """ @@ -20,7 +20,7 @@ from db import get_cursor, get_db from routers.profiles import get_pid from system_dashboard_product_default import get_product_default_base_dict -router = APIRouter(prefix="/api/app", tags=["app-dashboard-lab"]) +router = APIRouter(prefix="/api/app", tags=["app-dashboard"]) @router.get("/widgets/catalog") diff --git a/backend/widget_catalog.py b/backend/widget_catalog.py index fc6ee57..37849ba 100644 --- a/backend/widget_catalog.py +++ b/backend/widget_catalog.py @@ -1,5 +1,5 @@ """ -Öffentlicher Widget-Katalog (Dashboard-Lab / später Produkt-Dashboard). +Öffentlicher Widget-Katalog (konfigurierbare Übersicht / API). Single Source für: erlaubte IDs, Standard-Reihenfolge, Anzeige-Metadaten für API/GUI. Frontend-Komponenten registrieren dieselben IDs lokal (siehe widgetSystem/registerDashboardWidgets.js, Funktion ensureDashboardWidgetsRegistered). diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index fe9e5e1..dd42411 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -25,7 +25,6 @@ import Analysis from './pages/Analysis' import SettingsPage from './pages/SettingsPage' import SettingsShell from './layouts/SettingsShell' import ProfileReferenceValuesPage from './pages/ProfileReferenceValuesPage' -import DashboardLabPage from './pages/DashboardLabPage' import DashboardConfigurePage from './pages/DashboardConfigurePage' import GuidePage from './pages/GuidePage' import AdminTierLimitsPage from './pages/AdminTierLimitsPage' @@ -270,7 +269,6 @@ function AppShell() { }/> }/> - } /> diff --git a/frontend/src/components/dashboard-widgets-legacy/QuickCaptureWidget.jsx b/frontend/src/components/dashboard-widgets-legacy/QuickCaptureWidget.jsx index 55c7899..2fc8938 100644 --- a/frontend/src/components/dashboard-widgets-legacy/QuickCaptureWidget.jsx +++ b/frontend/src/components/dashboard-widgets-legacy/QuickCaptureWidget.jsx @@ -130,8 +130,9 @@ export default function QuickCaptureWidget({ onSaved, captureConfig }) {
Schnelleingabe (heute)

- Für dieses Widget sind keine Eingabebereiche aktiviert. Im Dashboard-Lab die Sichtbarkeit prüfen - oder Vitalwerte-Seite nutzen. + Für dieses Widget sind keine Eingabebereiche aktiviert. Unter{' '} + Übersicht anpassen die Schnelleingabe-Konfiguration prüfen oder{' '} + Vitalwerte-Seite nutzen.

) diff --git a/frontend/src/pages/DashboardLabPage.jsx b/frontend/src/pages/DashboardLabPage.jsx deleted file mode 100644 index 93bf4e0..0000000 --- a/frontend/src/pages/DashboardLabPage.jsx +++ /dev/null @@ -1,512 +0,0 @@ -import { useCallback, useEffect, useMemo, useState } from 'react' -import { ChevronDown, ChevronUp, LayoutGrid } from 'lucide-react' -import { Link } from 'react-router-dom' -import { api, formatFastApiDetail } from '../utils/api' -import { WidgetRenderer } from '../widgetSystem/dashboardWidgetRegistry' -import { ensureDashboardWidgetsRegistered } from '../widgetSystem/registerDashboardWidgets' -import { - BODY_CHART_DAYS_DEFAULT, - BODY_CHART_DAYS_MAX, - BODY_CHART_DAYS_MIN, - normalizeBodyChartDays, -} from '../widgetSystem/bodyChartDays' -import KpiBoardConfigEditor from '../widgetSystem/KpiBoardConfigEditor' -import QuickCaptureConfigEditor from '../widgetSystem/QuickCaptureConfigEditor' -import BodyHistoryVizConfigEditor from '../widgetSystem/BodyHistoryVizConfigEditor' -import NutritionHistoryVizConfigEditor from '../widgetSystem/NutritionHistoryVizConfigEditor' -import FitnessHistoryVizConfigEditor from '../widgetSystem/FitnessHistoryVizConfigEditor' -import RecoveryHistoryVizConfigEditor from '../widgetSystem/RecoveryHistoryVizConfigEditor' -import HistoryOverviewVizConfigEditor from '../widgetSystem/HistoryOverviewVizConfigEditor' -import { moveWidget, normalizeLayoutForEditor, toggleWidget } from '../widgetSystem/layoutEditor' - -/** Widgets mit optionalem config.chart_days (7–90), gleiche UX im Editor */ -const CHART_DAYS_WIDGET_IDS = new Set([ - 'body_overview', - 'body_history_viz', - 'activity_overview', - 'nutrition_detail_charts', - 'nutrition_history_viz', - 'fitness_history_viz', - 'recovery_history_viz', - 'history_overview_viz', - 'recovery_charts_panel', -]) - -function catalogMetaById(catalog) { - if (!catalog?.widgets?.length) return {} - return Object.fromEntries(catalog.widgets.map((w) => [w.id, w])) -} - -export default function DashboardLabPage() { - ensureDashboardWidgetsRegistered() - - const [refreshTick, setRefreshTick] = useState(0) - const requestRefresh = () => setRefreshTick((t) => t + 1) - const [catalog, setCatalog] = useState(null) - const [bundle, setBundle] = useState(null) - const [layout, setLayout] = useState(null) - const [err, setErr] = useState(null) - const [busy, setBusy] = useState(false) - const [msg, setMsg] = useState(null) - /** Pro Widget-ID: Rohstring während der Eingabe (Tippen ohne sofortiges Clampen) */ - const [chartDaysDraftByWidgetId, setChartDaysDraftByWidgetId] = useState({}) - - const metaById = catalogMetaById(catalog) - - const isWidgetCatalogAllowed = useCallback( - (widgetId) => { - const m = metaById[widgetId] - if (m == null) return true - return m.allowed !== false - }, - [metaById], - ) - - const visibleEditorIndices = useMemo( - () => - layout?.widgets?.map((_, i) => i).filter((i) => isWidgetCatalogAllowed(layout.widgets[i].id)) ?? [], - [layout, isWidgetCatalogAllowed], - ) - - const layoutForPreview = useMemo( - () => - layout - ? { - ...layout, - widgets: layout.widgets.map((w) => ({ - ...w, - enabled: w.enabled && isWidgetCatalogAllowed(w.id), - })), - } - : null, - [layout, isWidgetCatalogAllowed], - ) - - const commitChartDaysDraftToLayout = useCallback((draftStr, baseLayout, widgetId) => { - const clamped = normalizeBodyChartDays( - draftStr === '' || draftStr == null ? BODY_CHART_DAYS_DEFAULT : draftStr - ) - return { - ...baseLayout, - widgets: baseLayout.widgets.map((x) => - x.id !== widgetId ? x : { ...x, config: { ...x.config, chart_days: clamped } } - ), - } - }, []) - - const load = useCallback(async () => { - setErr(null) - try { - const [cat, b] = await Promise.all([api.getAppWidgetsCatalog(), api.getAppDashboardLayout()]) - setCatalog(cat) - setBundle(b) - setChartDaysDraftByWidgetId({}) - setLayout(normalizeLayoutForEditor(b.layout)) - } catch (e) { - setErr(formatFastApiDetail(null, e.message)) - } - }, []) - - useEffect(() => { - load() - }, [load]) - - const save = async () => { - if (!layout) return - let toSave = layout - const draftEntries = Object.entries(chartDaysDraftByWidgetId) - if (draftEntries.length) { - for (const [wid, val] of draftEntries) { - toSave = normalizeLayoutForEditor(commitChartDaysDraftToLayout(val, toSave, wid)) - } - setLayout(toSave) - setChartDaysDraftByWidgetId({}) - } - setBusy(true) - setMsg(null) - setErr(null) - try { - await api.putAppDashboardLayout(toSave) - setMsg('Layout gespeichert.') - await load() - } catch (e) { - setErr(formatFastApiDetail(null, e.message)) - } finally { - setBusy(false) - } - } - - const reset = async () => { - if (!confirm('Persönliches Layout löschen und Standard wiederherstellen?')) return - setBusy(true) - setMsg(null) - setErr(null) - try { - const r = await api.resetAppDashboardLayout() - setChartDaysDraftByWidgetId({}) - setLayout(normalizeLayoutForEditor(r.layout)) - setMsg('Auf Standard zurückgesetzt.') - await load() - } catch (e) { - setErr(formatFastApiDetail(null, e.message)) - } finally { - setBusy(false) - } - } - - const applyDefaultLocal = () => { - if (bundle?.lab_default_layout) { - setChartDaysDraftByWidgetId({}) - setLayout(normalizeLayoutForEditor(structuredClone(bundle.lab_default_layout))) - setMsg('Lab-Standard geladen (noch nicht gespeichert).') - } - } - - if (err && !layout) { - return ( -
-

{err}

- -
- ) - } - - if (!layout) { - return ( -
-
-
- ) - } - - return ( -
-
- - ← Einstellungen - -

- - App-Bereich: Dashboard-Lab -

-

- Widget-System: Katalog, Registry, Renderer; optional pro Widget config (z. B.{' '} - Körper / Aktivität: Zeitraum 7–90 Tage; KPI: Kacheln - wählen & sortieren). Layout pro Profil in der DB — dieselben Widgets wie auf der{' '} - - Produkt-Übersicht - - , hier mit Editor und API-Fokus. -

-
- -
-
- Layout (v1) -
- {bundle && ( -

- Status: {bundle.custom ? 'individuell gespeichert' : 'Standard (nicht in DB)'} -

- )} - {err &&

{err}

} - {msg &&

{msg}

} -
    - {visibleEditorIndices.map((i) => { - const w = layout.widgets[i] - const label = metaById[w.id]?.title || w.id - const chartDaysVal = - w.config?.chart_days != null - ? normalizeBodyChartDays(w.config.chart_days) - : BODY_CHART_DAYS_DEFAULT - return ( -
  • -
    - -
    - - -
    -
    - {w.id === 'quick_capture' && ( - - setLayout((L) => - normalizeLayoutForEditor({ - ...L, - widgets: L.widgets.map((x, j) => { - if (j !== i) return x - const cfg = { ...(x.config || {}) } - for (const k of ['show_weight', 'show_resting_hr', 'show_hrv', 'show_vo2_max']) { - delete cfg[k] - } - Object.assign(cfg, next) - return { ...x, config: cfg } - }), - }) - ) - } - /> - )} - {w.id === 'kpi_board' && ( - - setLayout((L) => - normalizeLayoutForEditor({ - ...L, - widgets: L.widgets.map((x, j) => { - if (j !== i) return x - const cfg = { ...(x.config || {}) } - if (next === undefined) { - delete cfg.tiles - } else { - cfg.tiles = next - } - return { ...x, config: cfg } - }), - }) - ) - } - /> - )} - {CHART_DAYS_WIDGET_IDS.has(w.id) && ( -
    - - - setChartDaysDraftByWidgetId((prev) => ({ - ...prev, - [w.id]: String(chartDaysVal), - })) - } - onChange={(e) => - setChartDaysDraftByWidgetId((prev) => ({ - ...prev, - [w.id]: e.target.value, - })) - } - onBlur={(e) => { - const raw = e.target.value - setLayout((L) => - normalizeLayoutForEditor(commitChartDaysDraftToLayout(raw, L, w.id)) - ) - setChartDaysDraftByWidgetId((prev) => { - const next = { ...prev } - delete next[w.id] - return next - }) - }} - onKeyDown={(e) => { - if (e.key === 'Enter') e.currentTarget.blur() - }} - /> -
    - )} - {w.id === 'body_history_viz' && ( - - setLayout((L) => - normalizeLayoutForEditor({ - ...L, - widgets: L.widgets.map((x, j) => { - if (j !== i) return x - if (Object.keys(next).length === 0) return { ...x, config: {} } - return { ...x, config: { ...(x.config || {}), ...next } } - }), - }) - ) - } - /> - )} - {w.id === 'nutrition_history_viz' && ( - - setLayout((L) => - normalizeLayoutForEditor({ - ...L, - widgets: L.widgets.map((x, j) => { - if (j !== i) return x - if (Object.keys(next).length === 0) return { ...x, config: {} } - return { ...x, config: { ...(x.config || {}), ...next } } - }), - }) - ) - } - /> - )} - {w.id === 'fitness_history_viz' && ( - - setLayout((L) => - normalizeLayoutForEditor({ - ...L, - widgets: L.widgets.map((x, j) => { - if (j !== i) return x - if (Object.keys(next).length === 0) return { ...x, config: {} } - return { ...x, config: { ...(x.config || {}), ...next } } - }), - }) - ) - } - /> - )} - {w.id === 'recovery_history_viz' && ( - - setLayout((L) => - normalizeLayoutForEditor({ - ...L, - widgets: L.widgets.map((x, j) => { - if (j !== i) return x - if (Object.keys(next).length === 0) return { ...x, config: {} } - return { ...x, config: { ...(x.config || {}), ...next } } - }), - }) - ) - } - /> - )} - {w.id === 'history_overview_viz' && ( - - setLayout((L) => - normalizeLayoutForEditor({ - ...L, - widgets: L.widgets.map((x, j) => { - if (j !== i) return x - if (Object.keys(next).length === 0) return { ...x, config: {} } - return { ...x, config: { ...(x.config || {}), ...next } } - }), - }) - ) - } - /> - )} -
  • - ) - })} -
-
- - - -
-
- - {layoutForPreview && ( - - )} -
- ) -} diff --git a/frontend/src/pages/SettingsPage.jsx b/frontend/src/pages/SettingsPage.jsx index cfd1f33..27d0cfe 100644 --- a/frontend/src/pages/SettingsPage.jsx +++ b/frontend/src/pages/SettingsPage.jsx @@ -1,5 +1,5 @@ import { useState, useEffect } from 'react' -import { Save, Download, Upload, Check, LogOut, Key, BarChart3, Target, LayoutGrid, LayoutDashboard } from 'lucide-react' +import { Save, Download, Upload, Check, LogOut, Key, BarChart3, Target, LayoutDashboard } from 'lucide-react' import { Link } from 'react-router-dom' import { useProfile } from '../context/ProfileContext' import { useAuth } from '../context/AuthContext' @@ -458,35 +458,6 @@ export default function SettingsPage() {
-
-
- Entwickler: Dashboard-Layout (API) -
-

- Experimentelles Layout-Lab mit Katalog und API (getrennt von der regulären Übersicht). Die produktive Kachelansicht - steuerst du über Übersicht anpassen oben. -

- - - Dashboard-Lab öffnen - -
- {/* Auth actions */}
🔐 Konto
diff --git a/frontend/src/utils/api.js b/frontend/src/utils/api.js index 80fba9c..a6159f1 100644 --- a/frontend/src/utils/api.js +++ b/frontend/src/utils/api.js @@ -79,7 +79,7 @@ export const api = { getProfile: () => req('/profile'), updateActiveProfile:(d)=> req('/profile', jput(d)), - // App-Bereich: Dashboard-Lab (Layout JSON, Issue #65) + Widget-Katalog + // App-Bereich: konfigurierbares Dashboard (Layout JSON) + Widget-Katalog getAppWidgetsCatalog: () => req('/app/widgets/catalog'), getAppDashboardLayout: () => req('/app/dashboard-layout'), putAppDashboardLayout: (layout) => req('/app/dashboard-layout', jput(layout)), diff --git a/frontend/src/widgetSystem/QuickCaptureConfigEditor.jsx b/frontend/src/widgetSystem/QuickCaptureConfigEditor.jsx index b861a6d..34b6b34 100644 --- a/frontend/src/widgetSystem/QuickCaptureConfigEditor.jsx +++ b/frontend/src/widgetSystem/QuickCaptureConfigEditor.jsx @@ -1,5 +1,5 @@ /** - * Sichtbarkeit der Teile im Schnelleingabe-Widget (Dashboard-Lab). + * Sichtbarkeit der Teile im Schnelleingabe-Widget (Übersicht anpassen). * Default: alle sichtbar (leeres config). */ const KEYS = [