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.
|
- 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
|
- 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
|
## medium prio
|
||||||
- Update all deprecated things in the backend code.
|
- Update all deprecated things in the backend code.
|
||||||
-> fix-deprecations-backend-plan.md -> in progress
|
-> fix-deprecations-backend-plan.md -> DONE
|
||||||
|
|
||||||
## lowest prio
|
## lowest prio
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user