- Introduced the `recovery_history_viz` widget to the dashboard, enabling users to visualize recovery history data. - Updated widget configuration to include `recovery_history_viz` in the allowed widgets and added validation for its configuration. - Enhanced the widget catalog with details for the new `recovery_history_viz` entry. - Implemented default values and validation logic for the widget's configuration, ensuring proper handling of user inputs. - Added tests to ensure proper validation of the `recovery_history_viz` widget configuration. - Bumped application version to reflect the addition of the new widget.
107 lines
4.7 KiB
JavaScript
107 lines
4.7 KiB
JavaScript
import { RECOVERY_HISTORY_VIZ_WIDGET_DEFAULTS, normalizeRecoveryHistoryVizConfig } from './recoveryHistoryVizConfig'
|
|
|
|
const CHART_TOGGLES = [
|
|
{ key: 'show_chart_recovery_score', label: 'HRV-/Recovery-Score-Verlauf' },
|
|
{ key: 'show_chart_sleep_quality', label: 'Schlaf: Dauer & Qualität' },
|
|
{ key: 'show_chart_sleep_debt', label: 'Schlafschuld' },
|
|
{ key: 'show_chart_hrv_rhr', label: 'HRV & Ruhepuls (Zeitverlauf)' },
|
|
]
|
|
|
|
const SECTION_TOGGLES = [
|
|
{ key: 'show_sleep_section_heading', label: 'Zwischenüberschrift «Schlaf & Erholung»' },
|
|
{ key: 'show_heart_section_heading', label: 'Zwischenüberschrift «Herz & Kreislauf»' },
|
|
{ key: 'show_heart_context_card', label: 'Herz: Einordnung, Zonen, Snapshots' },
|
|
{ key: 'show_vitals_extra_heading', label: 'Überschrift «Weitere Vitalparameter»' },
|
|
{ key: 'show_vitals_extra_trends', label: 'VO2 / SpO2 / Atemfrequenz (Verläufe)' },
|
|
]
|
|
|
|
const OTHER_TOGGLES = [
|
|
{ key: 'show_layer_meta', label: 'Meta-Zeile (Fenster-Tage, Data-Layer)' },
|
|
{ key: 'show_kpis', label: 'KPI-Kacheln' },
|
|
{ key: 'show_progress_insights', label: 'Überblick: Recovery & Schlaf (Karten)' },
|
|
]
|
|
|
|
/**
|
|
* @param {{ config: Record<string, unknown>, onChange: (next: Record<string, unknown>) => void }} props
|
|
*/
|
|
export default function RecoveryHistoryVizConfigEditor({ config, onChange }) {
|
|
const merged = normalizeRecoveryHistoryVizConfig(config)
|
|
|
|
const patch = (partial) => {
|
|
const next = { ...merged, ...partial }
|
|
const def = RECOVERY_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 (
|
|
<div style={{ marginTop: 10, marginLeft: 28 }}>
|
|
<div style={{ fontSize: 12, color: 'var(--text2)', marginBottom: 8, lineHeight: 1.5 }}>
|
|
<strong>Erholung (Verlauf-Bundle):</strong> welche Blöcke auf der Übersicht erscheinen. Unbelegte Felder = schlanker
|
|
Standard (KPI kompakt, Schlaf-Charts, HRV/RHR — ohne Kontextkarte und Extra-Vitals).
|
|
</div>
|
|
<div style={{ fontSize: 12, color: 'var(--text2)', marginBottom: 6 }}>KPI-Umfang</div>
|
|
<label style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 13, marginBottom: 10, cursor: 'pointer' }}>
|
|
<input
|
|
type="radio"
|
|
name="recovery_hist_kpi_detail"
|
|
checked={merged.kpi_detail === 'compact'}
|
|
onChange={() => patch({ kpi_detail: 'compact' })}
|
|
/>
|
|
<span>Kompakt (erste 4 Kacheln)</span>
|
|
</label>
|
|
<label style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 13, marginBottom: 12, cursor: 'pointer' }}>
|
|
<input
|
|
type="radio"
|
|
name="recovery_hist_kpi_detail"
|
|
checked={merged.kpi_detail === 'full'}
|
|
onChange={() => patch({ kpi_detail: 'full' })}
|
|
/>
|
|
<span>Voll (alle Kacheln)</span>
|
|
</label>
|
|
<div style={{ fontSize: 12, color: 'var(--text2)', marginBottom: 6 }}>Bereiche</div>
|
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
|
{OTHER_TOGGLES.map(({ key, label }) => (
|
|
<label key={key} style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 13, cursor: 'pointer' }}>
|
|
<input type="checkbox" checked={merged[key]} onChange={(e) => setBool(key, e.target.checked)} />
|
|
<span>{label}</span>
|
|
</label>
|
|
))}
|
|
</div>
|
|
<div style={{ fontSize: 12, color: 'var(--text2)', margin: '12px 0 6px' }}>Abschnitte</div>
|
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
|
{SECTION_TOGGLES.map(({ key, label }) => (
|
|
<label key={key} style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 13, cursor: 'pointer' }}>
|
|
<input type="checkbox" checked={merged[key]} onChange={(e) => setBool(key, e.target.checked)} />
|
|
<span>{label}</span>
|
|
</label>
|
|
))}
|
|
</div>
|
|
<div style={{ fontSize: 12, color: 'var(--text2)', margin: '12px 0 6px' }}>Charts</div>
|
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
|
{CHART_TOGGLES.map(({ key, label }) => (
|
|
<label key={key} style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 13, cursor: 'pointer' }}>
|
|
<input type="checkbox" checked={merged[key]} onChange={(e) => setBool(key, e.target.checked)} />
|
|
<span>{label}</span>
|
|
</label>
|
|
))}
|
|
</div>
|
|
<button
|
|
type="button"
|
|
className="btn btn-secondary"
|
|
style={{ marginTop: 10, fontSize: 12, padding: '6px 12px' }}
|
|
onClick={() => onChange({})}
|
|
>
|
|
Auf schlanken Standard zurück
|
|
</button>
|
|
</div>
|
|
)
|
|
}
|