Styling fix and scraper bug fix
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
# TODO — Dashboard/Calendar show no data (snake_case ↔ camelCase wire mismatch)
|
||||
|
||||
**Status:** open · **Area:** frontend · **Owner:** _unassigned_ · **Filed:** 2026-06-14
|
||||
|
||||
## Symptom
|
||||
|
||||
The Dashboard renders but the "Översikt" metrics, the parental-leave breakdown, and the
|
||||
planned-days breakdown are all empty/zero, even though `parental_leave_fetch` has valid
|
||||
rows and the API returns data.
|
||||
|
||||
## Two things this is NOT
|
||||
|
||||
- **Not a "latest scrape" problem.** The backend already selects the newest snapshot:
|
||||
`get_latest_by_parent` (and `AnalyticsService`) use `ORDER BY fetched_at DESC LIMIT 1`.
|
||||
The newest row (`fetched_at 2026-06-14T17:09:47`, the one with `total=240/used=76/...`)
|
||||
is the one used; the older zero/negative rows are correctly ignored. No backend change.
|
||||
- **Not (only) the planned-days 405.** That 405 (see `PLANNED_DAYS_405_TODO.md`) blocks the
|
||||
planned-days list, but the empty *metrics* and *parental-leave breakdown* come from
|
||||
separate endpoints that DO return 200.
|
||||
|
||||
## Root cause
|
||||
|
||||
The backend response models serialize **snake_case** (`combined_days_used`, `days_used`,
|
||||
`day_type`, `start_date`, `tier_data`, …), but the frontend entity types and components read
|
||||
**camelCase** (`combinedDaysUsed`, `daysUsed`, `dayType`, …). `src/services/api.ts`'s
|
||||
`apiFetch` returns the raw JSON with no conversion, so every camelCase field is `undefined`
|
||||
and renders as `0`/blank via the `?? 0` fallbacks.
|
||||
|
||||
Examples:
|
||||
- `Dashboard.tsx` reads `analytics.combinedDaysUsed`; backend sends `combined_days_used`.
|
||||
- `Dashboard.tsx` filters `plannedDays.filter(d => d.dayType === 'vacation')`; backend sends `day_type`.
|
||||
- `ParentalLeaveBreakdown` reads camelCase off `ParentalLeaveFetch`; backend sends snake_case.
|
||||
|
||||
**Why tests didn't catch it:** the hook/component tests `vi.mock('@/services/api')` and return
|
||||
camelCase fixtures, so they mock *past* the missing conversion layer.
|
||||
|
||||
## Decision
|
||||
|
||||
Keep the backend wire format **snake_case**; do the snake→camel mapping in the **frontend
|
||||
`api.ts`**. This matches existing convention — `api.ts` already does the reverse (camel→snake)
|
||||
for request bodies (e.g. `register` sends `display_name` from `req.displayName`;
|
||||
`upsertVacationDays` sends `personal_data_uuid` from `req.personalDataUuid`). Response mappers
|
||||
just complete the pattern and leave the working snake_case endpoints (`getUserContext`,
|
||||
`TokenResponse`) untouched.
|
||||
|
||||
## Fix
|
||||
|
||||
In `frontend/src/services/api.ts`, for each function that returns a camelCase entity, add a
|
||||
typed `RawX` wire shape and a `toX(raw): X` mapper, then map the `apiFetch` result before
|
||||
returning. **Type-safe only** — `@typescript-eslint/no-explicit-any` is a hard CI error and
|
||||
`npm run lint` runs with `--max-warnings 0`, so no `as any` / `@ts-ignore`.
|
||||
|
||||
Functions to cover:
|
||||
- `getWorkspaceOverview` — incl. nested `parent_projections` → `parentProjections` (each `Age4Projection`).
|
||||
- `getParentalLeave` — incl. nested `tier_data` → `tierData` (the `TierData` shape).
|
||||
- `getPlannedDays`, `createPlannedDay`, `updatePlannedDay` — `day_type`→`dayType`, `is_dubbeldag`→`isDubbeldag`, etc.
|
||||
- `getBreakPeriods`, `createBreakPeriod` — `start_date`→`startDate`, `end_date`→`endDate`, `target_weeks`→`targetWeeks`.
|
||||
- `getWorkspace`, `getVacationDaysByWorkspace` — for consistency (same mismatch latent there).
|
||||
|
||||
Do **not** change outbound request bodies, and leave `getUserContext` / `TokenResponse`
|
||||
as-is (already consumed snake_case in `WorkspaceContext`/`AuthContext`).
|
||||
|
||||
## Verification
|
||||
|
||||
From `frontend/`:
|
||||
|
||||
```bash
|
||||
npx tsc --noEmit && npm run lint && npm run coverage # CI gate: 90% coverage
|
||||
```
|
||||
|
||||
Add at least one test that drives a **real** mapper at the `fetch` boundary (mock global
|
||||
`fetch` to return a snake_case body, call `api.getWorkspaceOverview(...)`, assert the result
|
||||
is camelCase). The existing `vi.mock('@/services/api')` tests cannot catch this class of bug.
|
||||
|
||||
Manual: Dashboard metrics + parental-leave breakdown populate; planned-days breakdown counts
|
||||
are correct once the 405 fix lands.
|
||||
|
||||
## Related
|
||||
|
||||
Depends on `PLANNED_DAYS_405_TODO.md` for the planned-days list to return 200 in the first place.
|
||||
Reference in New Issue
Block a user