feat: Improve body chart days configuration in DashboardLabPage
All checks were successful
Deploy Development / deploy (push) Successful in 46s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 15s

- Introduced a draft state for body chart days input to allow for temporary edits without immediate clamping.
- Updated input handling to support numeric input mode and improved accessibility with aria-label.
- Enhanced save and reset functionalities to manage the new draft state effectively.
- Refactored layout normalization to accommodate changes in body chart days configuration.
This commit is contained in:
Lars 2026-04-07 12:05:05 +02:00
parent 87c4cbc4b4
commit 4493b140bd

View File

@ -28,15 +28,28 @@ export default function DashboardLabPage() {
const [err, setErr] = useState(null) const [err, setErr] = useState(null)
const [busy, setBusy] = useState(false) const [busy, setBusy] = useState(false)
const [msg, setMsg] = useState(null) const [msg, setMsg] = useState(null)
/** Während der Fokus im Körper-Chart-Feld: Rohstring, damit Tippen (z. B. „14“) nicht sofort geclamped wird */
const [bodyChartDaysDraft, setBodyChartDaysDraft] = useState(null)
const metaById = catalogMetaById(catalog) const metaById = catalogMetaById(catalog)
const commitBodyChartDraftToLayout = useCallback((draftStr, baseLayout) => {
const clamped = normalizeBodyChartDays(draftStr === '' || draftStr == null ? BODY_CHART_DAYS_DEFAULT : draftStr)
return {
...baseLayout,
widgets: baseLayout.widgets.map((x) =>
x.id !== 'body_overview' ? x : { ...x, config: { ...x.config, chart_days: clamped } }
),
}
}, [])
const load = useCallback(async () => { const load = useCallback(async () => {
setErr(null) setErr(null)
try { try {
const [cat, b] = await Promise.all([api.getAppWidgetsCatalog(), api.getAppDashboardLayout()]) const [cat, b] = await Promise.all([api.getAppWidgetsCatalog(), api.getAppDashboardLayout()])
setCatalog(cat) setCatalog(cat)
setBundle(b) setBundle(b)
setBodyChartDaysDraft(null)
setLayout(normalizeLayoutForEditor(b.layout)) setLayout(normalizeLayoutForEditor(b.layout))
} catch (e) { } catch (e) {
setErr(formatFastApiDetail(null, e.message)) setErr(formatFastApiDetail(null, e.message))
@ -49,11 +62,17 @@ export default function DashboardLabPage() {
const save = async () => { const save = async () => {
if (!layout) return if (!layout) return
let toSave = layout
if (bodyChartDaysDraft !== null) {
toSave = normalizeLayoutForEditor(commitBodyChartDraftToLayout(bodyChartDaysDraft, layout))
setLayout(toSave)
setBodyChartDaysDraft(null)
}
setBusy(true) setBusy(true)
setMsg(null) setMsg(null)
setErr(null) setErr(null)
try { try {
await api.putAppDashboardLayout(layout) await api.putAppDashboardLayout(toSave)
setMsg('Layout gespeichert.') setMsg('Layout gespeichert.')
await load() await load()
} catch (e) { } catch (e) {
@ -70,6 +89,7 @@ export default function DashboardLabPage() {
setErr(null) setErr(null)
try { try {
const r = await api.resetAppDashboardLayout() const r = await api.resetAppDashboardLayout()
setBodyChartDaysDraft(null)
setLayout(normalizeLayoutForEditor(r.layout)) setLayout(normalizeLayoutForEditor(r.layout))
setMsg('Auf Standard zurückgesetzt.') setMsg('Auf Standard zurückgesetzt.')
await load() await load()
@ -82,6 +102,7 @@ export default function DashboardLabPage() {
const applyDefaultLocal = () => { const applyDefaultLocal = () => {
if (bundle?.default_layout) { if (bundle?.default_layout) {
setBodyChartDaysDraft(null)
setLayout(normalizeLayoutForEditor(structuredClone(bundle.default_layout))) setLayout(normalizeLayoutForEditor(structuredClone(bundle.default_layout)))
setMsg('Standard geladen (noch nicht gespeichert).') setMsg('Standard geladen (noch nicht gespeichert).')
} }
@ -206,37 +227,29 @@ export default function DashboardLabPage() {
Körper-Chart Zeitraum (Tage): {BODY_CHART_DAYS_MIN}{BODY_CHART_DAYS_MAX} Körper-Chart Zeitraum (Tage): {BODY_CHART_DAYS_MIN}{BODY_CHART_DAYS_MAX}
</label> </label>
<input <input
type="number" type="text"
inputMode="numeric"
autoComplete="off"
className="form-input" className="form-input"
style={{ maxWidth: 120 }} style={{ maxWidth: 120 }}
min={BODY_CHART_DAYS_MIN} aria-label="Körper-Chart Zeitraum in Tagen"
max={BODY_CHART_DAYS_MAX} value={
value={chartDaysVal} bodyChartDaysDraft !== null
onChange={(e) => { ? bodyChartDaysDraft
const v = parseInt(e.target.value, 10) : String(chartDaysVal)
setLayout((L) => ({
...L,
widgets: L.widgets.map((x, j) =>
j !== i
? x
: {
...x,
config: {
...x.config,
chart_days: Number.isFinite(v) ? v : BODY_CHART_DAYS_DEFAULT,
},
} }
), onFocus={() => setBodyChartDaysDraft(String(chartDaysVal))}
})) onChange={(e) => setBodyChartDaysDraft(e.target.value)}
onBlur={() => {
setLayout((L) =>
normalizeLayoutForEditor(
commitBodyChartDraftToLayout(bodyChartDaysDraft ?? String(chartDaysVal), L)
)
)
setBodyChartDaysDraft(null)
}} }}
onBlur={(e) => { onKeyDown={(e) => {
const clamped = normalizeBodyChartDays(e.target.value) if (e.key === 'Enter') e.currentTarget.blur()
setLayout((L) => ({
...L,
widgets: L.widgets.map((x, j) =>
j !== i ? x : { ...x, config: { ...x.config, chart_days: clamped } }
),
}))
}} }}
/> />
</div> </div>