Files
F-r-ldraledigplannerare/junior_calendar_fix.md
T
2026-06-14 13:05:53 +02:00

118 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Junior Developer — Calendar View Fix
**Owner:** Junior developer
**Status:** Ready to implement
**Scope:** The **Kalender** view only (`/calendar`). Do not touch Dashboard, Settings, Tips, or backend.
**Goal:** Make the month calendar render as a real 7-column grid that looks like a calendar, behaves correctly for Swedish users, and is keyboard/mobile friendly — **without** adding a calendar library and **without** rewriting the working planning logic.
---
## TL;DR — what's actually wrong
The calendar **logic is already correct and complete**. `Calendar.tsx` builds weeks, pads leading/trailing days, maps planned days to colored events, and opens the day-edit modal with full CRUD. The markup already uses good BEM class names (`calendar__grid`, `calendar-day`, `calendar-day__number`, `calendar__weekday`, …).
**The only real problem: those classes have no CSS.** The app ships exactly one stylesheet (`header.css`) and the calendar's classes are unstyled, so every cell falls back to a default block `<div>` and stacks vertically. That vertical list of "31 1 2 3 …" in the screenshot is unstyled markup, nothing more.
So this is a **CSS task first**, with a few small correctness fixes layered on top. **We are building this ourselves — no FullCalendar / react-big-calendar.** A month grid is `display: grid` + cell styling; a library would force our bespoke day-type / dubbeldag / age-4 model into its generic event API and is not worth it.
---
## Guardrails (read before starting)
- **Do NOT add a dependency.** No FullCalendar, react-big-calendar, day-picker, Tailwind, etc. Plain CSS only.
- **Do NOT rewrite the planning logic.** The week-building loop, `getEventsForDate`, the modal wiring, and the CRUD handlers in `Calendar.tsx` work — leave them unless a task here explicitly says to change them.
- **TypeScript only** — no `.js`/`.jsx`. No `as any`, `@ts-ignore`, or `@ts-expect-error` (CI fails on ESLint warnings with `--max-warnings 0`).
- **Match the existing CSS pattern:** components own a co-located stylesheet imported at the top of the file — e.g. `Header.tsx` imports `./header.css`, `PlannedDayModal.tsx` imports `./planned-day-modal.css`. Follow that exact pattern for the calendar. **Do not** wait on a global stylesheet to land.
- **Read before edit.** Open every file before changing it; confirm structure first.
- Keep `npx tsc --noEmit`, `npm run lint`, and `npm run coverage` green after every task.
---
## Files you will touch
- `frontend/src/pages/Calendar.tsx` — add a CSS import; small correctness edits (P2).
- `frontend/src/pages/calendar.css` — **new** stylesheet (the bulk of the work).
- `frontend/src/components/common/CalendarDay.tsx` — only if P2 needs a "today"/weekend hook.
- Tests: `frontend/src/components/common/CalendarDay.test.tsx`, and a `Calendar.test.tsx` if one doesn't exist.
> ⚠️ **Confirm git state first.** `PlannedDayModal.tsx`, `ManualFetchModal.tsx`, and their CSS appear to be **untracked** (created during the MVP work but not committed). Run `git status` before you start. Don't accidentally revert or clobber them — commit or stash deliberately.
---
## P1 — Make it an actual calendar grid (the core fix)
**Problem:** `calendar__grid` has no layout, so the seven weekday headers and ~35 day cells render as a vertical stack.
**Do:**
1. Create `frontend/src/pages/calendar.css`.
2. At the top of `Calendar.tsx`, add `import './calendar.css'` (mirror how `Header.tsx` imports `./header.css`).
3. Style the structure that already exists in the JSX:
- `.calendar` — center it, constrain width (~`max-width: 920px; margin: 0 auto; padding: 16px`).
- `.calendar__header` — `display: flex; align-items: center; justify-content: space-between;` so the `←` / month title / `→` sit on one row. Style the nav buttons as clear, tappable controls (min 36×36px).
- `.calendar__grid` — **this is the key rule:** `display: grid; grid-template-columns: repeat(7, 1fr);` with a small `gap`. The weekday headers and day cells are direct children, so they'll flow into the 7 columns automatically.
- `.calendar__weekday` — centered, muted, smaller, semibold column header.
- `.calendar-day` — give it a min-height (~`84px`), light border/`border-radius`, white background, padding, `cursor: pointer`, and a hover state. Day number top-left (`.calendar-day__number`).
- `.calendar-day--disabled` — the leading/trailing days from adjacent months: muted text, faded background, no hover affordance.
- `.calendar-day--has-events` — subtle emphasis is fine (optional).
- `.calendar-day__events` — a small row/wrap of event chips at the bottom of the cell.
- `.calendar-day__event` — the colored dot/bar per planned day. Keep it small (a dot or a thin bar), **not** a full-width block. Colors come from the inline `backgroundColor` style already set in `CalendarDay.tsx` — don't hardcode them again in CSS.
- `:focus-visible` outline on `.calendar-day` (it's `role="button" tabIndex={0}` and already handles Enter/Space).
4. **Fix the legend.** Right now the legend reuses `CalendarEvent`, which renders full-width colored bars (the green/orange/blue stripes in the screenshot). Style `.calendar__legend` as a compact horizontal row of small swatch + label pairs. Either restyle `.calendar-event` when it sits inside `.calendar__legend-item` (small inline-flex, fixed-size swatch, auto width) **or** replace the legend markup with simple swatch spans. Keep it tidy and small.
**Acceptance:** The Kalender view shows a recognizable month grid — 7 columns, weekday headers across the top, square-ish day cells, adjacent-month days dimmed, planned days marked with small colored dots, and a compact legend. No vertical stacking. Looks calm and readable on desktop.
---
## P2 — Correctness & polish (Swedish conventions)
Do these after P1 looks right. They're small but matter for a Swedish app.
1. **Monday-first week.** Sweden starts the week on Monday, not Sunday.
- Weekday headers are currently `['Sun','Mon',…,'Sat']` in `Calendar.tsx`. Change to Swedish, Monday-first: `['Mån','Tis','Ons','Tor','Fre','Lör','Sön']`.
- The leading-blank calculation uses `firstDay.getDay()` (0 = Sunday). Convert to Monday-based: `const startOffset = (firstDay.getDay() + 6) % 7;` and use `startOffset` where `startingDayOfWeek` is used (both the leading-pad loop and any references). Re-verify the trailing-pad math still fills to a multiple of 7.
- **Test the boundary:** a month that starts on Sunday (e.g. **March 2026** starts Sunday) must show that Sunday in the **last** column, not the first.
2. **Highlight "today."** Add a `calendar-day--today` modifier when a cell's date equals the real current date (compare `YYYY-MM-DD` strings; reuse the existing `formatDate` helper — don't introduce a timezone bug by using `Date` comparison). Give it a distinct but subtle ring/background. (You can compute this in `Calendar.tsx` and pass a prop into `CalendarDay`, or compute inside `CalendarDay`.)
3. **Weekend tint (optional, nice-to-have).** Lightly tint Saturday/Sunday columns so the week structure reads at a glance.
4. **Default month.** `Calendar.tsx` hardcodes `useState(new Date(2026, 5))` (June 2026). Default to the **current month** (`new Date()`) instead, so the calendar opens on a useful month. Keep the existing prev/next navigation.
> **Out of scope but worth flagging to the senior, don't fix here:** the header shows a stray "August" dropdown **and** an "August" link next to the parent name (visible top-right in the screenshot) — that looks like a duplicate/placeholder child selector in `Header.tsx`, unrelated to the calendar. Note it; leave it.
**Acceptance:** Week runs Mån→Sön; a Sunday-starting month renders correctly; today is visibly marked; the view opens on the current month.
---
## P3 — Tests
Keep the frontend coverage gate (90%) green.
- **`CalendarDay`:** assert the grid cell renders the day number, applies `--disabled` when `isCurrentMonth={false}`, renders one `.calendar-day__event` per event with the right `backgroundColor`, fires `onClick` on click and on Enter/Space, and applies `--today` when given today's date.
- **`Calendar` (page):** with a mocked `usePlannedDays`/`useWorkspace`, assert that 7 weekday headers render, that the grid contains the expected number of day cells for a known month, that the **Monday-first** ordering is correct (first header is "Mån"), and that clicking a current-month day opens the modal. Use the boundary month from P2 as a regression test.
- Don't delete or comment out existing tests to go green — fix the code or update the test with a clear reason.
**Acceptance:** `npx tsc --noEmit`, `npm run lint` (zero warnings), and `npm run coverage` (≥90%) all pass.
---
## Definition of done
- Kalender view renders as a proper 7-column month grid, styled calmly and legibly, with a compact legend and small event markers.
- Week is Monday-first with Swedish weekday labels; Sunday-starting months render correctly; today is highlighted; opens on the current month.
- No new dependencies; no `.js`/`.jsx`; no type suppressions.
- All three frontend gates pass; existing modal/Dashboard behavior untouched.
- `graphify update .` run after the code changes (per repo protocol).
---
## Suggested commit slices
1. `feat(calendar): add calendar.css grid layout + styled day cells and legend` (P1)
2. `fix(calendar): Monday-first Swedish week, today highlight, default to current month` (P2)
3. `test(calendar): grid layout, week ordering, and day-cell coverage` (P3)
---
## A note on "build vs. library" (for context)
We deliberately chose to build this ourselves. The hard parts of a calendar — date math, navigation, event mapping, the day-edit flow — are already done and working. The gap was purely CSS. A third-party calendar library would mean adapting our domain model (sjukpenning / lägstanivå / vacation day types, dubbeldagar, the age-4 cutoff, reserved days) to its generic event API and restyling it anyway — more work and less control, not less. A fixed month grid is the cheapest possible calendar to own. If we ever need drag-to-reschedule across an agenda view, revisit then; not now.