Update todo by human
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
# Fix number-input clearing UX + drop the redundant preschool estimate field
|
||||
|
||||
## Context
|
||||
|
||||
Two problems in Settings, both reported by the user:
|
||||
|
||||
1. **Clearing a number input traps you at `0`.** The per-year "Planned days" input
|
||||
(`Settings.tsx:186-192`) keeps its draft as a `number`, and `Number('') === 0`, so
|
||||
blanking the box writes a literal `0` to the server and snaps the display to `0`.
|
||||
Typing `5` then produces `05`. The same hazard exists in `VacationDaysTable` and
|
||||
`PeriodPopover`. `VacationDaysInput` has the mirror bug: it silently rejects a
|
||||
non-numeric value, so the field can't be blanked to retype at all.
|
||||
|
||||
2. **Two preschool controls look duplicated.** "Preschool planning days per year"
|
||||
(the child-level estimate, `Child.preschoolPlanningDaysPerYear`) sits directly above
|
||||
the "Planning days per year" heading for the per-year rows. They are not duplicates —
|
||||
the estimate seeds each year row and is the allocator's fallback for years with no
|
||||
saved row (`backend/app/services/allocation_service.py:545-573`, `:2985-3001`) — but
|
||||
the UI gives no hint of that, so it reads as dead weight.
|
||||
|
||||
**Decisions taken:** remove the estimate *input* from the UI while keeping the backend
|
||||
field (default `6`) as the seed/fallback; extract one shared number input and roll it out
|
||||
to every numeric field that still has the `Number('') === 0` hazard.
|
||||
|
||||
**Out of scope (flagged, not fixed):** `backend/app/services/tips_service.py:579` passes
|
||||
the *global* `settings.rules.preschool_planning_days_per_year` into
|
||||
`preschool_buffer_needed` instead of the child's own value, so a customised per-child
|
||||
value is ignored by that tip. Separate bug, separate change.
|
||||
|
||||
## 1. New shared component: `frontend/src/components/inputs/NumberField.tsx`
|
||||
|
||||
Lift the working pattern already used by the "Days taken" input (`Settings.tsx:199-217`).
|
||||
|
||||
```tsx
|
||||
export interface NumberFieldProps {
|
||||
value: number
|
||||
onChange: (value: number) => void
|
||||
label?: string
|
||||
min?: number
|
||||
max?: number
|
||||
id?: string
|
||||
className?: string
|
||||
inputClassName?: string
|
||||
disabled?: boolean
|
||||
placeholder?: string
|
||||
'data-testid'?: string
|
||||
'aria-invalid'?: boolean
|
||||
'aria-describedby'?: string
|
||||
}
|
||||
```
|
||||
|
||||
Behaviour contract:
|
||||
|
||||
- Holds an internal **string** draft. `''` is a legal transient state.
|
||||
- `onChange` fires only when the draft parses to a finite number — **never on empty**, so
|
||||
the previously saved value survives while the box is blank.
|
||||
- No clamping while typing (clamping mid-keystroke is what makes small `max` values
|
||||
unusable). Clamp to `[min, max]` on blur only, and commit the clamped value.
|
||||
- On blur with an empty or unparseable draft: revert the draft to the current `value`
|
||||
prop; do not call `onChange`.
|
||||
- Sync the draft from the `value` prop only while the input is **not focused** (a ref
|
||||
flag set on focus/blur), so a debounced server round-trip can't overwrite live typing.
|
||||
- Renders `type="number" inputMode="numeric"` with `min`/`max` attributes and the existing
|
||||
`field` / `field__control` BEM classes; `label` renders as `field__label` when given.
|
||||
No hardcoded `id`/`data-testid` — both are props (the bug that makes `VacationDaysInput`
|
||||
non-reuse-safe today).
|
||||
|
||||
Co-located test `NumberField.test.tsx` covering: clear-then-retype yields `5` not `05`;
|
||||
empty draft fires no `onChange`; blur-empty restores the prior value; blur clamps to
|
||||
`min`/`max`; external `value` change while focused does not clobber the draft.
|
||||
|
||||
## 2. Remove the child-level estimate input
|
||||
|
||||
`frontend/src/pages/Settings.tsx`
|
||||
|
||||
- Delete the `VacationDaysInput` block (`:562-571`) and its import (`:21`).
|
||||
- Delete the `preschoolPlanningDays` state (`:252`), its prefill in the `childDetails`
|
||||
effect (`:285`), and `preschoolPlanningDaysPerYear` from the `updateChildDetails` call in
|
||||
`handleSaveFamily` (`:366`). The field is optional in `ChildUpdate`
|
||||
(`backend/app/models/child.py:40`), so omitting it leaves the stored value untouched.
|
||||
- Pass `defaultPlannedDays={childDetails?.preschoolPlanningDaysPerYear ?? 6}` to
|
||||
`PreschoolPlanningYearRow` (`:583`) instead of the removed state.
|
||||
- Move the `settings.preschoolHelp` paragraph (`:563`) into the per-year block, under the
|
||||
heading, and drop the now-redundant `settings.preschoolPerYearHelp` line or merge the two
|
||||
into one sentence.
|
||||
- Retitle the section so it names the subject: `settings.preschoolPerYearTitle` →
|
||||
`"Preschool planning days"` / `"Förskolans planeringsdagar"`.
|
||||
|
||||
`frontend/src/i18n/{en,sv}/settings.ts` — remove `settings.preschoolPlanningDaysLabel`
|
||||
(`:83` in both), reword `settings.preschoolHelp` (`:82`) so it reads as the intro to the
|
||||
year rows, and update `settings.preschoolPerYearTitle` (`:126`).
|
||||
|
||||
Delete `frontend/src/components/inputs/VacationDaysInput.tsx` and
|
||||
`VacationDaysInput.test.tsx` — `Settings.tsx:564` was the only usage.
|
||||
|
||||
## 3. Roll `NumberField` out
|
||||
|
||||
| File | Inputs |
|
||||
|---|---|
|
||||
| `frontend/src/pages/Settings.tsx:181-195` | per-year **Planned days** (the reported bug) |
|
||||
| `frontend/src/pages/Settings.tsx:199-217` | per-year **Days taken** — keep its `null` = "not recorded" sentinel; it needs `value: number \| null` handling, so either give `NumberField` an optional `nullable` mode or leave this one as-is and note why |
|
||||
| `frontend/src/components/vacation-days/VacationDaysTable.tsx:174-183, 188-197` | `totalDays`, `savedDaysIn` |
|
||||
| `frontend/src/components/calendar/PeriodPopover.tsx:458-470` | `lastYear` (min 2000 / max 2200 — blanking currently shows `0`) |
|
||||
|
||||
`frontend/src/components/modals/ParentBalanceForm.tsx` already handles empty correctly via
|
||||
`parseField` (`:134`, `value={x ?? ''}`) — **leave it alone**, or migrate it only if
|
||||
`NumberField` gains the nullable mode above. Do not regress its `null` semantics.
|
||||
|
||||
Existing `data-testid`s (`preschool-planned-{year}`, `vacation-total-days-{year}`,
|
||||
`period-last-year-input`, …) must be preserved so current tests keep targeting the same
|
||||
elements.
|
||||
|
||||
## 4. Tests to update
|
||||
|
||||
- `frontend/src/pages/Settings.test.tsx:416-437` — drop the `vacation-days-field` prefill
|
||||
and the "change to 10 → `updateChildDetails({ preschoolPlanningDaysPerYear: 10 })`"
|
||||
assertions; assert instead that the family save no longer sends that key and that
|
||||
year rows still prefill from `childDetails.preschoolPlanningDaysPerYear`.
|
||||
- `frontend/src/pages/Settings.test.tsx:459-500` — add a clear-then-retype case on
|
||||
`preschool-planned-{year}` asserting the committed value is `5`, and that clearing alone
|
||||
does not call `setPlanned`.
|
||||
- `frontend/src/hooks/useChildSettings.test.ts:100-113` — unchanged (the hook still
|
||||
supports the field); no edit expected, verify it still passes.
|
||||
- Backend tests unchanged: no model, service, entity or migration touched.
|
||||
|
||||
Mock only with the typed factories in `frontend/tests/fixtures/` and `vi.mocked(fn)` —
|
||||
no `as any`, no casts (CLAUDE.md anti-patterns).
|
||||
|
||||
## Verification
|
||||
|
||||
Delegate the full suites to a **Haiku** subagent, one at a time (never in parallel), with
|
||||
raw verbatim output required:
|
||||
|
||||
```bash
|
||||
# from frontend/
|
||||
npm run typecheck
|
||||
npm run lint # zero warnings
|
||||
npm run coverage # gates: lines 96 | functions 91 | branches 92 | statements 96
|
||||
```
|
||||
|
||||
Backend is untouched, so a backend run is only needed if something unexpected changes there.
|
||||
|
||||
Then a **Sonnet** subagent drives Chrome (never the coordinator) against `npm run dev`:
|
||||
|
||||
1. Settings → Family panel: confirm the standalone "Preschool planning days per year"
|
||||
input is gone and the year rows still show the seeded value with the "assumed" badge.
|
||||
2. In a **Planned days** box: select-all, delete → box goes empty (not `0`); type `5` →
|
||||
shows `5`, not `05`; blur → autosaves `5`.
|
||||
3. Blank a box and click away without typing → the previous value returns, nothing saved.
|
||||
4. Repeat 2-3 on a `VacationDaysTable` row and on the period `lastYear` input.
|
||||
5. Check the reworded section in **both Swedish and English** — the labels must not clip
|
||||
(per the bilingual-CSS rule).
|
||||
|
||||
Finish with `graphify update .`.
|
||||
@@ -11,7 +11,7 @@ Following three have a plan (free-days-and-auto-planning-plan.md):
|
||||
|
||||
- On every event that updates the days off (i.e. the calendar is making some changes), that should trigger the planner to recalculate the best kinds of time-off for all days.
|
||||
|
||||
-> In progress
|
||||
-> In progress, super long time.....
|
||||
|
||||
|
||||
- Problem with the yearly timelines in general tab
|
||||
@@ -30,7 +30,7 @@ Following three have a plan (free-days-and-auto-planning-plan.md):
|
||||
|
||||
## medium prio
|
||||
- Update all deprecated things in the backend code.
|
||||
-> fix-deprecations-backend-plan.md -> in progress
|
||||
-> fix-deprecations-backend-plan.md -> DONE
|
||||
|
||||
## lowest prio
|
||||
|
||||
|
||||
Reference in New Issue
Block a user