diff --git a/backend/dashboard_widget_config.py b/backend/dashboard_widget_config.py index d84e9cf..b14e9ea 100644 --- a/backend/dashboard_widget_config.py +++ b/backend/dashboard_widget_config.py @@ -33,6 +33,32 @@ _QUICK_CAPTURE_KEYS: frozenset[str] = frozenset({ _KPI_TILE_FIXED: frozenset[str] = frozenset({"body_fat", "avg_kcal"}) _KPI_REF_TILE_RE = re.compile(r"^ref:[a-z0-9_]{1,64}$") +_BODY_HISTORY_VIZ_BOOL_KEYS: frozenset[str] = frozenset({ + "show_goals_strip", + "show_intro_blurb", + "show_layer_meta", + "show_kpis", + "show_weight_chart", + "show_body_fat_chart", + "show_proportion_chart", + "show_circumference_index_chart", + "show_circumference_lines_chart", +}) + +_BODY_HISTORY_VIZ_DEFAULTS: dict[str, Any] = { + "chart_days": 30, + "show_goals_strip": False, + "show_intro_blurb": False, + "show_layer_meta": False, + "show_kpis": True, + "kpi_detail": "compact", + "show_weight_chart": True, + "show_body_fat_chart": False, + "show_proportion_chart": False, + "show_circumference_index_chart": False, + "show_circumference_lines_chart": False, +} + def _config_json_size_bytes(config: dict[str, Any]) -> int: return len(json.dumps(config, sort_keys=True, ensure_ascii=False).encode("utf-8")) @@ -40,21 +66,26 @@ def _config_json_size_bytes(config: dict[str, Any]) -> int: def validate_widget_entry_config(widget_id: str, raw: Any) -> dict[str, Any]: if raw is None: - return {} + raw = {} if not isinstance(raw, dict): raise ValueError(f"Widget {widget_id}: config muss ein Objekt sein") if _config_json_size_bytes(raw) > MAX_WIDGET_CONFIG_JSON_BYTES: raise ValueError(f"Widget {widget_id}: config zu groß (max. {MAX_WIDGET_CONFIG_JSON_BYTES} Byte JSON)") - if not raw: - return {} if widget_id not in WIDGETS_ALLOWING_CONFIG: - raise ValueError(f"Widget {widget_id}: keine Konfiguration unterstützt") + if raw: + raise ValueError(f"Widget {widget_id}: keine Konfiguration unterstützt") + return {} + + if not raw: + if widget_id == "body_history_viz": + return _validate_body_history_viz_config({}) + return {} if widget_id == "body_overview": return _validate_chart_days_only(raw, label="body_overview") if widget_id == "body_history_viz": - return _validate_chart_days_only(raw, label="body_history_viz") + return _validate_body_history_viz_config(raw) if widget_id == "activity_overview": return _validate_chart_days_only(raw, label="activity_overview") if widget_id == "kpi_board": @@ -153,6 +184,44 @@ def _parse_chart_days(v: Any, label: str) -> int: raise ValueError(f"{label}: chart_days muss ganze Zahl sein") +def _validate_body_history_viz_config(raw: dict[str, Any]) -> dict[str, Any]: + label = "body_history_viz" + allowed = _BODY_HISTORY_VIZ_BOOL_KEYS | frozenset({"chart_days", "kpi_detail"}) + unknown = set(raw) - allowed + if unknown: + raise ValueError(f"{label}: unbekannte config-Felder: {sorted(unknown)}") + out: dict[str, Any] = dict(_BODY_HISTORY_VIZ_DEFAULTS) + for k in _BODY_HISTORY_VIZ_BOOL_KEYS: + if k not in raw: + continue + v = raw[k] + if not isinstance(v, bool): + raise ValueError(f"{label}: {k} muss boolean sein") + out[k] = v + if "kpi_detail" in raw: + kd = raw["kpi_detail"] + if kd not in ("compact", "full"): + raise ValueError(f"{label}: kpi_detail muss 'compact' oder 'full' sein") + out["kpi_detail"] = kd + if "chart_days" in raw: + v = _parse_chart_days(raw["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 out["show_kpis"] and not any( + out[k] + for k in ( + "show_weight_chart", + "show_body_fat_chart", + "show_proportion_chart", + "show_circumference_index_chart", + "show_circumference_lines_chart", + ) + ): + raise ValueError(f"{label}: mindestens KPIs oder ein Chart muss sichtbar sein") + return out + + def _validate_chart_days_only(raw: dict[str, Any], *, label: str) -> dict[str, Any]: allowed = frozenset({"chart_days"}) unknown = set(raw) - allowed diff --git a/backend/tests/test_dashboard_widget_config.py b/backend/tests/test_dashboard_widget_config.py index e3bb2f1..42b57d9 100644 --- a/backend/tests/test_dashboard_widget_config.py +++ b/backend/tests/test_dashboard_widget_config.py @@ -14,13 +14,36 @@ def test_body_chart_days_bounds(): validate_widget_entry_config("body_overview", {"chart_days": 91}) -def test_body_history_viz_chart_days(): - assert validate_widget_entry_config("body_history_viz", {}) == {} - assert validate_widget_entry_config("body_history_viz", {"chart_days": 60}) == {"chart_days": 60} +def test_body_history_viz_empty_expands_defaults(): + d = validate_widget_entry_config("body_history_viz", {}) + assert d["chart_days"] == 30 + assert d["show_kpis"] is True + assert d["show_weight_chart"] is True + assert d["kpi_detail"] == "compact" + assert d["show_body_fat_chart"] is False + + +def test_body_history_viz_chart_days_and_merge(): + d = validate_widget_entry_config("body_history_viz", {"chart_days": 60}) + assert d["chart_days"] == 60 + assert d["show_goals_strip"] is False with pytest.raises(ValueError): validate_widget_entry_config("body_history_viz", {"chart_days": 5}) +def test_body_history_viz_requires_visible_block(): + with pytest.raises(ValueError): + validate_widget_entry_config( + "body_history_viz", + {"show_kpis": False, "show_weight_chart": False}, + ) + + +def test_body_history_viz_unknown_key(): + with pytest.raises(ValueError): + validate_widget_entry_config("body_history_viz", {"evil": True}) + + def test_welcome_config_rejected_unknown_key(): with pytest.raises(ValueError): validate_widget_entry_config("welcome", {"x": 1}) diff --git a/backend/version.py b/backend/version.py index c772c0d..d1b2bb0 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.12.1", # GET layout: merge_missing_catalog_widgets (neue Katalog-IDs sichtbar) + "app_dashboard": "1.13.0", # body_history_viz: Sichtbarkeits-Config + Defaults schlank "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 f94f75e..e6007b2 100644 --- a/backend/widget_catalog.py +++ b/backend/widget_catalog.py @@ -45,7 +45,7 @@ WIDGET_CATALOG: list[WidgetCatalogEntry] = [ { "id": "body_history_viz", "title": "Körper (Verlauf-Bundle)", - "description": "Wie Verlauf → Körper: GET /charts/body-history-viz (optional chart_days 7–90); Feature weight_entries", + "description": "Layer-2b body-history-viz: schlanker Standard (KPI kompakt + Gewicht); optional Blöcke/Charts per config (show_* , kpi_detail); chart_days 7–90; Feature weight_entries", "requires_feature": "weight_entries", }, { diff --git a/frontend/src/components/dashboard-widgets/BodyHistoryVizWidget.jsx b/frontend/src/components/dashboard-widgets/BodyHistoryVizWidget.jsx index f7d2c04..5cb1a9c 100644 --- a/frontend/src/components/dashboard-widgets/BodyHistoryVizWidget.jsx +++ b/frontend/src/components/dashboard-widgets/BodyHistoryVizWidget.jsx @@ -1,16 +1,18 @@ import { useNavigate } from 'react-router-dom' import BodyHistoryVizSection from '../history/BodyHistoryVizSection' import { useProfile } from '../../context/ProfileContext' -import { BODY_CHART_DAYS_DEFAULT, normalizeBodyChartDays } from '../../widgetSystem/bodyChartDays' +import { normalizeBodyChartDays } from '../../widgetSystem/bodyChartDays' +import { normalizeBodyHistoryVizConfig } from '../../widgetSystem/bodyHistoryVizConfig' /** - * Verlauf → Körper als Dashboard-Widget: GET /charts/body-history-viz (Layer 2b), optional chart_days 7–90. - * @param {{ refreshTick?: number, chartDays?: number }} props + * Verlauf → Körper als Dashboard-Widget: GET /charts/body-history-viz (Layer 2b), Umfang über Layout-Config. + * @param {{ refreshTick?: number, bodyHistoryVizConfig?: Record }} props */ -export default function BodyHistoryVizWidget({ refreshTick = 0, chartDays }) { +export default function BodyHistoryVizWidget({ refreshTick = 0, bodyHistoryVizConfig }) { const nav = useNavigate() const { activeProfile } = useProfile() - const days = chartDays != null ? normalizeBodyChartDays(chartDays) : BODY_CHART_DAYS_DEFAULT + const cfg = normalizeBodyHistoryVizConfig(bodyHistoryVizConfig) + const days = normalizeBodyChartDays(cfg.chart_days) return (
@@ -23,7 +25,13 @@ export default function BodyHistoryVizWidget({ refreshTick = 0, chartDays }) { Verlauf →
- + ) } diff --git a/frontend/src/components/history/BodyHistoryVizSection.jsx b/frontend/src/components/history/BodyHistoryVizSection.jsx index 1a32d03..05a6ff8 100644 --- a/frontend/src/components/history/BodyHistoryVizSection.jsx +++ b/frontend/src/components/history/BodyHistoryVizSection.jsx @@ -17,12 +17,25 @@ import 'dayjs/locale/de' import { api } from '../../utils/api' import { getStatusColor } from '../../utils/interpret' import KpiTilesOverview from '../KpiTilesOverview' +import { + BODY_HISTORY_VIZ_HISTORY_FULL, + filterBodyHistoryKpiTiles, +} from '../../widgetSystem/bodyHistoryVizConfig' import { EmptySection, NavToCaliper, NavToCircum, PeriodSelector, SectionHeader } from './historyPageChrome' dayjs.locale('de') const fmtDate = (d) => dayjs(d).format('DD.MM') +/** Recharts: in schmalen Flex-Spalten sonst Breite 0. */ +function ChartFrame({ heightPx, children }) { + return ( +
+ {children} +
+ ) +} + function verdictShort(status) { if (status === 'good') return 'Gut' if (status === 'warn') return 'Hinweis' @@ -222,8 +235,15 @@ function BodyGoalsStrip({ grouped }) { * @param {number} [props.externalPeriod] — festes Fenster (Dashboard); sonst interner Zeitraum + PeriodSelector * @param {import('react').ReactNode} [props.footer] — z. B. KI-InsightBox im Verlauf * @param {boolean} [props.embedded] — true im Dashboard-Widget: keine große Section-Überschrift (Karte hat eigenen Titel) + * @param {object} [props.visibility] — Sichtbarkeit (Dashboard-Config); fehlt → wie Verlauf (alles an, kpi_detail full) */ -export default function BodyHistoryVizSection({ profile, externalPeriod, footer = null, embedded = false }) { +export default function BodyHistoryVizSection({ profile, externalPeriod, footer = null, embedded = false, visibility }) { + const display = visibility === undefined ? BODY_HISTORY_VIZ_HISTORY_FULL : visibility + const chartHMain = embedded ? 176 : 200 + const chartHSecondary = embedded ? 152 : 170 + const chartHIndex = embedded ? 160 : 180 + const chartHFallback = embedded ? 140 : 150 + const [internalPeriod, setInternalPeriod] = useState(90) const period = externalPeriod !== undefined ? externalPeriod : internalPeriod const showPeriodSelector = externalPeriod === undefined @@ -234,6 +254,10 @@ export default function BodyHistoryVizSection({ profile, externalPeriod, footer const [vizError, setVizError] = useState(null) useEffect(() => { + if (!display.show_goals_strip) { + setGroupedGoals({}) + return undefined + } let cancelled = false api.listGoalsGrouped() .then((g) => { @@ -245,7 +269,7 @@ export default function BodyHistoryVizSection({ profile, externalPeriod, footer return () => { cancelled = true } - }, []) + }, [display.show_goals_strip]) useEffect(() => { let cancelled = false @@ -334,6 +358,7 @@ export default function BodyHistoryVizSection({ profile, externalPeriod, footer weightTrendKpi: w?.trend_kpi, goalW, }) + const kpiTilesShown = display.show_kpis ? filterBodyHistoryKpiTiles(kpiTiles, display.kpi_detail || 'full') : [] const hasAnyData = (w?.data_points > 0) || @@ -378,23 +403,25 @@ export default function BodyHistoryVizSection({ profile, externalPeriod, footer )} {showPeriodSelector && } - + {display.show_goals_strip && } -

- Daten und Kennzahlen aus dem Backend-Bundle (gleiche Quelle wie Platzhalter). Training: Verlauf → Fitness. -

+ {display.show_intro_blurb && ( +

+ Daten und Kennzahlen aus dem Backend-Bundle (gleiche Quelle wie Platzhalter). Training: Verlauf → Fitness. +

+ )} - {viz?.meta?.layer_2a_alignment && ( + {display.show_layer_meta && viz?.meta?.layer_2a_alignment && (
{viz.meta.layer_2a_alignment}
)} - + {kpiTilesShown.length > 0 && } {vizLoading &&
Aktualisiere…
} - {hasWeight && ( + {display.show_weight_chart && hasWeight && (
@@ -404,7 +431,8 @@ export default function BodyHistoryVizSection({ profile, externalPeriod, footer Daten
- + + @@ -420,7 +448,8 @@ export default function BodyHistoryVizSection({ profile, externalPeriod, footer - + +
● Täglich Ø 7T @@ -433,13 +462,14 @@ export default function BodyHistoryVizSection({ profile, externalPeriod, footer
)} - {bfCd.length >= 2 && ( + {display.show_body_fat_chart && bfCd.length >= 2 && (
Körperfett (Caliper)
- + + @@ -448,12 +478,13 @@ export default function BodyHistoryVizSection({ profile, externalPeriod, footer {goalBf != null && } - + +
Magermasse aus Gewicht und KF% — zweite Kurve entfällt.
)} - {propChartData.length >= 2 && ( + {display.show_proportion_chart && propChartData.length >= 2 && (
@@ -469,7 +500,8 @@ export default function BodyHistoryVizSection({ profile, externalPeriod, footer
- + + @@ -487,7 +519,8 @@ export default function BodyHistoryVizSection({ profile, externalPeriod, footer {showBellyOnProp && } - + +
Brust − Taille gleitender Mittelwert @@ -496,7 +529,7 @@ export default function BodyHistoryVizSection({ profile, externalPeriod, footer
)} - {idxOk && ( + {display.show_circumference_index_chart && idxOk && (
@@ -505,7 +538,8 @@ export default function BodyHistoryVizSection({ profile, externalPeriod, footer
- + + @@ -516,7 +550,8 @@ export default function BodyHistoryVizSection({ profile, externalPeriod, footer {idxSeries.some((d) => d.waist_idx != null) && } {idxSeries.some((d) => d.belly_idx != null) && } - + +
Brust Taille @@ -525,14 +560,15 @@ export default function BodyHistoryVizSection({ profile, externalPeriod, footer
)} - {propChartData.length < 2 && cirCd.length >= 2 && ( + {display.show_circumference_lines_chart && propChartData.length < 2 && cirCd.length >= 2 && (
Umfänge (Taille / Hüfte / Bauch)
Mit Brust- und Taillenumfang erscheint die Proportionen-Ansicht oben.
- + + @@ -542,7 +578,8 @@ export default function BodyHistoryVizSection({ profile, externalPeriod, footer {cirCd.some((d) => d.belly) && } - + +
)} diff --git a/frontend/src/pages/DashboardConfigurePage.jsx b/frontend/src/pages/DashboardConfigurePage.jsx index 5271bbb..5776033 100644 --- a/frontend/src/pages/DashboardConfigurePage.jsx +++ b/frontend/src/pages/DashboardConfigurePage.jsx @@ -11,6 +11,7 @@ import { } from '../widgetSystem/bodyChartDays' import KpiBoardConfigEditor from '../widgetSystem/KpiBoardConfigEditor' import QuickCaptureConfigEditor from '../widgetSystem/QuickCaptureConfigEditor' +import BodyHistoryVizConfigEditor from '../widgetSystem/BodyHistoryVizConfigEditor' import { moveWidget, moveWidgetToIndex, @@ -496,6 +497,23 @@ export default function DashboardConfigurePage({ adminMode = false } = {}) { />
)} + {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 } } + }), + }) + ) + } + /> + )} ) })} diff --git a/frontend/src/pages/DashboardLabPage.jsx b/frontend/src/pages/DashboardLabPage.jsx index aaba5ac..26720f3 100644 --- a/frontend/src/pages/DashboardLabPage.jsx +++ b/frontend/src/pages/DashboardLabPage.jsx @@ -12,6 +12,7 @@ import { } from '../widgetSystem/bodyChartDays' import KpiBoardConfigEditor from '../widgetSystem/KpiBoardConfigEditor' import QuickCaptureConfigEditor from '../widgetSystem/QuickCaptureConfigEditor' +import BodyHistoryVizConfigEditor from '../widgetSystem/BodyHistoryVizConfigEditor' import { moveWidget, normalizeLayoutForEditor, toggleWidget } from '../widgetSystem/layoutEditor' /** Widgets mit optionalem config.chart_days (7–90), gleiche UX im Editor */ @@ -319,11 +320,13 @@ export default function DashboardLabPage() {
)} + {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 } } + }), + }) + ) + } + /> + )} ) })} diff --git a/frontend/src/widgetSystem/BodyHistoryVizConfigEditor.jsx b/frontend/src/widgetSystem/BodyHistoryVizConfigEditor.jsx new file mode 100644 index 0000000..83e6cd4 --- /dev/null +++ b/frontend/src/widgetSystem/BodyHistoryVizConfigEditor.jsx @@ -0,0 +1,91 @@ +import { BODY_HISTORY_VIZ_WIDGET_DEFAULTS, normalizeBodyHistoryVizConfig } from './bodyHistoryVizConfig' + +const CHART_TOGGLES = [ + { key: 'show_weight_chart', label: 'Gewichts-Chart' }, + { key: 'show_body_fat_chart', label: 'Körperfett (Caliper)' }, + { key: 'show_proportion_chart', label: 'Silhouette & Proportion' }, + { key: 'show_circumference_index_chart', label: 'Umfänge — Index' }, + { key: 'show_circumference_lines_chart', label: 'Umfänge — Linien (Fallback)' }, +] + +const OTHER_TOGGLES = [ + { key: 'show_goals_strip', label: 'Körper-Ziele (Strip)' }, + { key: 'show_intro_blurb', label: 'Hinweistext unter Zielen' }, + { key: 'show_layer_meta', label: 'Layer-2a-Hinweis (Meta)' }, + { key: 'show_kpis', label: 'KPI-Kacheln' }, +] + +/** + * @param {{ config: Record, onChange: (next: Record) => void }} props + */ +export default function BodyHistoryVizConfigEditor({ config, onChange }) { + const merged = normalizeBodyHistoryVizConfig(config) + + const patch = (partial) => { + const next = { ...merged, ...partial } + const def = BODY_HISTORY_VIZ_WIDGET_DEFAULTS + const stored = {} + for (const k of Object.keys(def)) { + if (next[k] !== def[k]) stored[k] = next[k] + } + onChange(stored) + } + + const setBool = (key, checked) => { + patch({ [key]: checked }) + } + + return ( +
+
+ Körper (Verlauf-Bundle): welche Blöcke auf der Übersicht erscheinen. Unbelegte Felder = schlanker + Standard (nur KPI kompakt + Gewicht). +
+
KPI-Umfang
+ + +
Bereiche
+
+ {OTHER_TOGGLES.map(({ key, label }) => ( + + ))} +
+
Charts
+
+ {CHART_TOGGLES.map(({ key, label }) => ( + + ))} +
+ +
+ ) +} diff --git a/frontend/src/widgetSystem/bodyHistoryVizConfig.js b/frontend/src/widgetSystem/bodyHistoryVizConfig.js new file mode 100644 index 0000000..682952b --- /dev/null +++ b/frontend/src/widgetSystem/bodyHistoryVizConfig.js @@ -0,0 +1,81 @@ +/** + * Sichtbarkeit / Umfang für body_history_viz (sync mit backend dashboard_widget_config). + * `null` / fehlend → Verlauf: alles sichtbar (vollständige Parität zur History-Seite). + */ + +/** Verlauf-Tab Körper: volle Parität (kein Layout-Config). */ +export const BODY_HISTORY_VIZ_HISTORY_FULL = { + chart_days: 90, + show_goals_strip: true, + show_intro_blurb: true, + show_layer_meta: true, + show_kpis: true, + kpi_detail: 'full', + show_weight_chart: true, + show_body_fat_chart: true, + show_proportion_chart: true, + show_circumference_index_chart: true, + show_circumference_lines_chart: true, +} + +/** Default für Dashboard-Widget (schlank). */ +export const BODY_HISTORY_VIZ_WIDGET_DEFAULTS = { + chart_days: 30, + show_goals_strip: false, + show_intro_blurb: false, + show_layer_meta: false, + show_kpis: true, + kpi_detail: 'compact', + show_weight_chart: true, + show_body_fat_chart: false, + show_proportion_chart: false, + show_circumference_index_chart: false, + show_circumference_lines_chart: false, +} + +const BOOL_KEYS = [ + 'show_goals_strip', + 'show_intro_blurb', + 'show_layer_meta', + 'show_kpis', + 'show_weight_chart', + 'show_body_fat_chart', + 'show_proportion_chart', + 'show_circumference_index_chart', + 'show_circumference_lines_chart', +] + +/** + * @param {Record|null|undefined} raw — aus Layout-Config (Backend liefert nach Save ggf. alle Keys) + * @returns {typeof BODY_HISTORY_VIZ_WIDGET_DEFAULTS} + */ +export function normalizeBodyHistoryVizConfig(raw) { + const base = { ...BODY_HISTORY_VIZ_WIDGET_DEFAULTS } + if (!raw || typeof raw !== 'object') return base + for (const k of BOOL_KEYS) { + if (Object.prototype.hasOwnProperty.call(raw, k)) { + base[k] = raw[k] === true + } + } + if (raw.kpi_detail === 'full' || raw.kpi_detail === 'compact') { + base.kpi_detail = raw.kpi_detail + } + if (raw.chart_days != null) { + const n = Number(raw.chart_days) + if (Number.isFinite(n)) { + base.chart_days = Math.min(90, Math.max(7, Math.round(n))) + } + } + return base +} + +const COMPACT_KPI_KEYS = new Set(['weight', 'bf', 'lean_ffmi']) + +/** + * @param {Array<{ key: string }>} tiles + * @param {'compact'|'full'} detail + */ +export function filterBodyHistoryKpiTiles(tiles, detail) { + if (detail === 'full' || !Array.isArray(tiles)) return tiles + return tiles.filter((t) => COMPACT_KPI_KEYS.has(t.key)) +} diff --git a/frontend/src/widgetSystem/registerPilotLabWidgets.js b/frontend/src/widgetSystem/registerPilotLabWidgets.js index 1cb1a78..96ad498 100644 --- a/frontend/src/widgetSystem/registerPilotLabWidgets.js +++ b/frontend/src/widgetSystem/registerPilotLabWidgets.js @@ -15,6 +15,7 @@ import TrendKcalWeightWidget from '../components/dashboard-widgets/TrendKcalWeig import NutritionActivitySummaryWidget from '../components/dashboard-widgets/NutritionActivitySummaryWidget' import NutritionDetailChartsWidget from '../components/dashboard-widgets/NutritionDetailChartsWidget' import BodyHistoryVizWidget from '../components/dashboard-widgets/BodyHistoryVizWidget' +import { normalizeBodyHistoryVizConfig } from './bodyHistoryVizConfig' import RecoveryChartsPanelWidget from '../components/dashboard-widgets/RecoveryChartsPanelWidget' import ProgressPhotosWidget from '../components/dashboard-widgets/ProgressPhotosWidget' import RecoverySleepRestWidget from '../components/dashboard-widgets/RecoverySleepRestWidget' @@ -63,7 +64,7 @@ export function ensurePilotLabWidgetsRegistered() { Component: BodyHistoryVizWidget, mapProps: (ctx) => ({ refreshTick: ctx.refreshTick, - chartDays: ctx.layoutEntry?.config?.chart_days, + bodyHistoryVizConfig: normalizeBodyHistoryVizConfig(ctx.layoutEntry?.config), }), }) registerDashboardWidget({