diff --git a/backend/dashboard_widget_config.py b/backend/dashboard_widget_config.py index 193453b..9e8a4a0 100644 --- a/backend/dashboard_widget_config.py +++ b/backend/dashboard_widget_config.py @@ -145,10 +145,17 @@ _RECOVERY_HISTORY_VIZ_DEFAULTS: dict[str, Any] = { "show_vitals_extra_trends": False, } +_HISTORY_OVERVIEW_VIZ_SECTION_KEYS: frozenset[str] = frozenset({ + "show_section_body", + "show_section_nutrition", + "show_section_fitness", + "show_section_recovery", +}) + _HISTORY_OVERVIEW_VIZ_BOOL_KEYS: frozenset[str] = frozenset({ "show_confidence_banner", "show_intro_blurb", - "show_area_summaries", + *_HISTORY_OVERVIEW_VIZ_SECTION_KEYS, "show_correlation_c1_c3", "show_drivers_c4", }) @@ -157,7 +164,10 @@ _HISTORY_OVERVIEW_VIZ_DEFAULTS: dict[str, Any] = { "chart_days": 30, "show_confidence_banner": True, "show_intro_blurb": True, - "show_area_summaries": True, + "show_section_body": True, + "show_section_nutrition": True, + "show_section_fitness": True, + "show_section_recovery": True, "show_correlation_c1_c3": True, "show_drivers_c4": True, } @@ -457,36 +467,52 @@ def _validate_recovery_history_viz_config(raw: dict[str, Any]) -> dict[str, Any] return out +def _migrate_history_overview_viz_raw(raw: dict[str, Any]) -> dict[str, Any]: + """Alt: show_area_summaries → vier show_section_* (nur wo keine expliziten Section-Keys gesetzt).""" + r = dict(raw) + if "show_area_summaries" not in r: + return r + leg = r.pop("show_area_summaries") + if not isinstance(leg, bool): + raise ValueError("history_overview_viz: show_area_summaries muss boolean sein (veraltet — nutze show_section_*)") + for k in _HISTORY_OVERVIEW_VIZ_SECTION_KEYS: + if k not in r: + r[k] = leg + return r + + def _validate_history_overview_viz_config(raw: dict[str, Any]) -> dict[str, Any]: label = "history_overview_viz" + raw_m = _migrate_history_overview_viz_raw(raw) allowed = _HISTORY_OVERVIEW_VIZ_BOOL_KEYS | frozenset({"chart_days"}) - unknown = set(raw) - allowed + unknown = set(raw_m) - allowed if unknown: raise ValueError(f"{label}: unbekannte config-Felder: {sorted(unknown)}") out: dict[str, Any] = dict(_HISTORY_OVERVIEW_VIZ_DEFAULTS) for k in _HISTORY_OVERVIEW_VIZ_BOOL_KEYS: - if k not in raw: + if k not in raw_m: continue - v = raw[k] + v = raw_m[k] if not isinstance(v, bool): raise ValueError(f"{label}: {k} muss boolean sein") out[k] = v - if "chart_days" in raw: - v = _parse_chart_days(raw["chart_days"], label) + if "chart_days" in raw_m: + v = _parse_chart_days(raw_m["chart_days"], label) if v < 7 or v > 90: raise ValueError(f"{label}: chart_days muss zwischen 7 und 90 liegen") out["chart_days"] = v - if not any( + has_section = any(out[k] for k in _HISTORY_OVERVIEW_VIZ_SECTION_KEYS) + has_other = any( out[k] for k in ( "show_confidence_banner", - "show_area_summaries", "show_correlation_c1_c3", "show_drivers_c4", ) - ): + ) + if not has_section and not has_other: raise ValueError( - f"{label}: mindestens Datenlage-Banner, Bereichs-Kacheln, Lag-Korrelationen (C1–C3) oder Treiber (C4) muss sichtbar sein" + f"{label}: mindestens eine Bereichs-Kachel, das Datenlage-Banner, Lag-Korrelationen (C1–C3) oder Treiber (C4) muss sichtbar sein" ) return out diff --git a/backend/tests/test_dashboard_widget_config.py b/backend/tests/test_dashboard_widget_config.py index a1e0038..b39a998 100644 --- a/backend/tests/test_dashboard_widget_config.py +++ b/backend/tests/test_dashboard_widget_config.py @@ -157,7 +157,10 @@ def test_history_overview_viz_empty_expands_defaults(): d = validate_widget_entry_config("history_overview_viz", {}) assert d["chart_days"] == 30 assert d["show_confidence_banner"] is True - assert d["show_area_summaries"] is True + assert d["show_section_body"] is True + assert d["show_section_nutrition"] is True + assert d["show_section_fitness"] is True + assert d["show_section_recovery"] is True assert d["show_correlation_c1_c3"] is True assert d["show_drivers_c4"] is True @@ -176,13 +179,26 @@ def test_history_overview_viz_requires_visible_block(): "history_overview_viz", { "show_confidence_banner": False, - "show_area_summaries": False, + "show_section_body": False, + "show_section_nutrition": False, + "show_section_fitness": False, + "show_section_recovery": False, "show_correlation_c1_c3": False, "show_drivers_c4": False, }, ) +def test_history_overview_viz_legacy_show_area_summaries_maps_sections(): + d = validate_widget_entry_config( + "history_overview_viz", + {"show_area_summaries": False, "show_correlation_c1_c3": True}, + ) + assert d["show_section_body"] is False + assert d["show_section_fitness"] is False + assert d["show_correlation_c1_c3"] is True + + def test_history_overview_viz_unknown_key(): with pytest.raises(ValueError): validate_widget_entry_config("history_overview_viz", {"evil": True}) diff --git a/backend/version.py b/backend/version.py index 58b208d..e2fa4c9 100644 --- a/backend/version.py +++ b/backend/version.py @@ -30,7 +30,7 @@ MODULE_VERSIONS = { "importdata": "1.0.0", "membership": "2.1.0", "workflow": "0.7.0", # Part 3: Inline Prompts (reference + inline mode) - "app_dashboard": "1.17.0", # history_overview_viz Widget + chart_payloads im Overview-Bundle + "app_dashboard": "1.17.1", # history_overview_viz: Bereichs-Kacheln einzeln per show_section_* "csv_import": "0.3.2", # Import-Fehler: enrich_row_error / freundlichere 500-Hinweise "admin_csv_templates": "0.3.0", # POST /validate + Speichern nur bei valid (422 + warnings in Response) } diff --git a/backend/widget_catalog.py b/backend/widget_catalog.py index 5212aa2..e83720d 100644 --- a/backend/widget_catalog.py +++ b/backend/widget_catalog.py @@ -120,7 +120,7 @@ WIDGET_CATALOG: list[WidgetCatalogEntry] = [ { "id": "history_overview_viz", "title": "Verlauf — Gesamtübersicht", - "description": "Layer-2b history-overview-viz: konsolidierte Kurzinfos (Körper/Ernährung/Fitness/Erholung) + C1–C4; chart_payloads im Bundle; chart_days 7–90; Blöcke per show_*", + "description": "Layer-2b history-overview-viz: Kurzinfos pro Bereich (show_section_body/nutrition/fitness/recovery) + C1–C4; chart_payloads; chart_days 7–90", }, { "id": "recovery_charts_panel", diff --git a/frontend/src/components/history/HistoryOverviewVizSection.jsx b/frontend/src/components/history/HistoryOverviewVizSection.jsx index bed2ccc..0432f7e 100644 --- a/frontend/src/components/history/HistoryOverviewVizSection.jsx +++ b/frontend/src/components/history/HistoryOverviewVizSection.jsx @@ -363,6 +363,20 @@ export default function HistoryOverviewVizSection({ const vis = visibilityProp != null ? normalizeHistoryOverviewVizConfig(visibilityProp) : HISTORY_OVERVIEW_VIZ_PAGE_FULL + const sectionTileEnabled = (id) => { + if (id === 'body') return vis.show_section_body + if (id === 'nutrition') return vis.show_section_nutrition + if (id === 'fitness') return vis.show_section_fitness + if (id === 'recovery') return vis.show_section_recovery + return true + } + const wantsAnySectionTile = + vis.show_section_body || + vis.show_section_nutrition || + vis.show_section_fitness || + vis.show_section_recovery + const visibleSections = wantsAnySectionTile ? sections.filter((s) => sectionTileEnabled(s.id)) : [] + return (
{!embedded && } @@ -402,11 +416,11 @@ export default function HistoryOverviewVizSection({

)} - {vis.show_area_summaries && (sections.length === 0 ? ( + {wantsAnySectionTile && (visibleSections.length === 0 ? ( ) : (
- {sections.map((sec) => { + {visibleSections.map((sec) => { const tone = overviewSectionTone(sec) const stripe = getStatusColor(tone) const badgeBg = getStatusBg(tone) diff --git a/frontend/src/widgetSystem/HistoryOverviewVizConfigEditor.jsx b/frontend/src/widgetSystem/HistoryOverviewVizConfigEditor.jsx index 052db61..67c064e 100644 --- a/frontend/src/widgetSystem/HistoryOverviewVizConfigEditor.jsx +++ b/frontend/src/widgetSystem/HistoryOverviewVizConfigEditor.jsx @@ -3,10 +3,16 @@ import { normalizeHistoryOverviewVizConfig, } from './historyOverviewVizConfig' -const TOGGLES = [ +const SECTION_TOGGLES = [ + { key: 'show_section_body', label: 'Körper' }, + { key: 'show_section_nutrition', label: 'Ernährung' }, + { key: 'show_section_fitness', label: 'Fitness' }, + { key: 'show_section_recovery', label: 'Erholung' }, +] + +const OTHER_TOGGLES = [ { key: 'show_confidence_banner', label: 'Banner «Datenlage»' }, { key: 'show_intro_blurb', label: 'Hinweistext (Ernährung / API)' }, - { key: 'show_area_summaries', label: 'Kacheln Körper · Ernährung · Fitness · Erholung' }, { key: 'show_correlation_c1_c3', label: 'Lag-Korrelationen C1–C3 (Charts)' }, { key: 'show_drivers_c4', label: 'Einflussfaktoren C4' }, ] @@ -34,11 +40,20 @@ export default function HistoryOverviewVizConfigEditor({ config, onChange }) { return (
- Gesamtübersicht (Verlauf-Bundle): konsolidierte Kurzinfos und Korrelations-Kacheln — wie im Verlauf-Reiter «Gesamt». + Gesamtübersicht (Verlauf-Bundle): welche Bereichs-Kacheln und weitere Blöcke erscheinen.
-
Bereiche
+
Bereichs-Kacheln
+
+ {SECTION_TOGGLES.map(({ key, label }) => ( + + ))} +
+
Weitere Bereiche
- {TOGGLES.map(({ key, label }) => ( + {OTHER_TOGGLES.map(({ key, label }) => (