From fc093aef90a44a7fc363e055cec26932d2c66e9a Mon Sep 17 00:00:00 2001 From: lmezasemet Date: Wed, 20 Aug 2025 20:13:27 +0200 Subject: [PATCH] fix: allow decimal weights; keep 0.x; trim integer leading zeros --- .../ui/workout-session-set.tsx | 43 +++++++++++- src/shared/lib/number/normalize.ts | 68 +++++++++++++++++++ 2 files changed, 108 insertions(+), 3 deletions(-) create mode 100644 src/shared/lib/number/normalize.ts diff --git a/src/features/workout-session/ui/workout-session-set.tsx b/src/features/workout-session/ui/workout-session-set.tsx index dec6c6ac..b69cab90 100644 --- a/src/features/workout-session/ui/workout-session-set.tsx +++ b/src/features/workout-session/ui/workout-session-set.tsx @@ -5,6 +5,7 @@ import { AVAILABLE_WORKOUT_SET_TYPES, MAX_WORKOUT_SET_COLUMNS } from "@/shared/c import { WorkoutSet, WorkoutSetType, WorkoutSetUnit } from "@/features/workout-session/types/workout-set"; import { getWorkoutSetTypeLabels } from "@/features/workout-session/lib/workout-set-labels"; import { Button } from "@/components/ui/button"; +import { normalizeInteger, normalizeDecimal } from "@/shared/lib/number/normalize"; interface WorkoutSetRowProps { set: WorkoutSet; @@ -19,6 +20,21 @@ export function WorkoutSessionSet({ set, setIndex, onChange, onFinish, onRemove const types = set.types || []; const typeLabels = getWorkoutSetTypeLabels(t); + // Store weight as deci-units (x10). Example: "2.5" -> 25; "7,5" -> 75. + const parseWeightToDeci = (raw: string) => { + const n = parseFloat(String(raw).replace(",", ".")); + if (!Number.isFinite(n) || n < 0) return 0; + // tiny epsilon avoids cases like 1.3 * 10 = 12.999999... + return Math.trunc(n * 10 + 1e-8); + }; + + // Show stored deci back with at most 1 decimal. 25 -> "2.5", 20 -> "2" + const formatDeciToDisplay = (val?: number) => { + if (val === undefined || val === null) return ""; + const num = val / 10; + return Number.isInteger(num) ? String(num) : num.toFixed(1); + }; + const handleTypeChange = (columnIndex: number) => (e: React.ChangeEvent) => { const newTypes = [...types]; newTypes[columnIndex] = e.target.value as WorkoutSetType; @@ -37,6 +53,13 @@ export function WorkoutSessionSet({ set, setIndex, onChange, onFinish, onRemove onChange(setIndex, { valuesSec: newValuesSec }); }; + // Weight accepts decimals; store as deci-units inside valuesInt + const handleValueWeightChange = (columnIndex: number) => (e: React.ChangeEvent) => { + const newValuesInt = Array.isArray(set.valuesInt) ? [...set.valuesInt] : []; + newValuesInt[columnIndex] = parseWeightToDeci(e.target.value); + onChange(setIndex, { valuesInt: newValuesInt }); + }; + const handleUnitChange = (columnIndex: number) => (e: React.ChangeEvent) => { const newUnits = Array.isArray(set.units) ? [...set.units] : []; newUnits[columnIndex] = e.target.value as WorkoutSetUnit; @@ -85,7 +108,9 @@ export function WorkoutSessionSet({ set, setIndex, onChange, onFinish, onRemove disabled={set.completed} min={0} onChange={handleValueIntChange(columnIndex)} + onBlur={(e) => { e.currentTarget.value = normalizeInteger(e.currentTarget.value); }} pattern="[0-9]*" + inputMode="numeric" placeholder="min" type="number" value={valuesInt[columnIndex] ?? ""} @@ -96,7 +121,9 @@ export function WorkoutSessionSet({ set, setIndex, onChange, onFinish, onRemove max={59} min={0} onChange={handleValueSecChange(columnIndex)} + onBlur={(e) => { e.currentTarget.value = normalizeInteger(e.currentTarget.value); }} pattern="[0-9]*" + inputMode="numeric" placeholder="sec" type="number" value={valuesSec[columnIndex] ?? ""} @@ -110,11 +137,19 @@ export function WorkoutSessionSet({ set, setIndex, onChange, onFinish, onRemove className="border border-black rounded px-1 py-2 w-1/2 text-base text-center font-bold dark:bg-slate-800" disabled={set.completed} min={0} - onChange={handleValueIntChange(columnIndex)} - pattern="[0-9]*" + step="0.5" + inputMode="decimal" + onChange={handleValueWeightChange(columnIndex)} + onBlur={(e) => { + e.currentTarget.value = normalizeDecimal(e.currentTarget.value, { maxDecimals: 1 }); + }} placeholder="" type="number" - value={valuesInt[columnIndex] ?? ""} + value={ + valuesInt[columnIndex] !== undefined + ? formatDeciToDisplay(valuesInt[columnIndex]) + : "" + } />