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

10 KiB
Raw Blame History

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.