10 KiB
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 inCalendar.tsxwork — leave them unless a task here explicitly says to change them. - TypeScript only — no
.js/.jsx. Noas 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.tsximports./header.css,PlannedDayModal.tsximports./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, andnpm run coveragegreen 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 aCalendar.test.tsxif 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). Rungit statusbefore 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:
- Create
frontend/src/pages/calendar.css. - At the top of
Calendar.tsx, addimport './calendar.css'(mirror howHeader.tsximports./header.css). - 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 smallgap. 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 inlinebackgroundColorstyle already set inCalendarDay.tsx— don't hardcode them again in CSS.:focus-visibleoutline on.calendar-day(it'srole="button" tabIndex={0}and already handles Enter/Space).
- 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__legendas a compact horizontal row of small swatch + label pairs. Either restyle.calendar-eventwhen 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.
- Monday-first week. Sweden starts the week on Monday, not Sunday.
- Weekday headers are currently
['Sun','Mon',…,'Sat']inCalendar.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 usestartOffsetwherestartingDayOfWeekis 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.
- Weekday headers are currently
- Highlight "today." Add a
calendar-day--todaymodifier when a cell's date equals the real current date (compareYYYY-MM-DDstrings; reuse the existingformatDatehelper — don't introduce a timezone bug by usingDatecomparison). Give it a distinct but subtle ring/background. (You can compute this inCalendar.tsxand pass a prop intoCalendarDay, or compute insideCalendarDay.) - Weekend tint (optional, nice-to-have). Lightly tint Saturday/Sunday columns so the week structure reads at a glance.
- Default month.
Calendar.tsxhardcodesuseState(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--disabledwhenisCurrentMonth={false}, renders one.calendar-day__eventper event with the rightbackgroundColor, firesonClickon click and on Enter/Space, and applies--todaywhen given today's date.Calendar(page): with a mockedusePlannedDays/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
feat(calendar): add calendar.css grid layout + styled day cells and legend(P1)fix(calendar): Monday-first Swedish week, today highlight, default to current month(P2)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.