Conversation
Toggle between Agenda (existing), Week, and Month views using a segmented control. Month view is a hand-rolled CSS-grid calendar showing event/task dots per day. Week view shows 7-day columns with item count badges. Both views support prev/next navigation and click-to-expand day detail inline — no modal needed. Reuses the existing combined event+task data source (agendaItems). Styled with Herstel design tokens; no new dependencies. Resolves IRL-28
| const navigateMonth = (delta: number) => { | ||
| setViewDate((prev) => { | ||
| const next = new Date(prev); | ||
| next.setMonth(next.getMonth() + delta); | ||
| return next; | ||
| }); | ||
| setSelectedDate(null); | ||
| }; |
There was a problem hiding this comment.
🔴 navigateMonth skips months when viewDate day > 28 due to JS Date overflow
When viewDate has a day component of 29, 30, or 31, navigateMonth uses setMonth() which causes JavaScript's Date to overflow into the next month. For example, if viewDate is May 31 and the user navigates forward, setMonth(5) creates "June 31" which JS rolls to July 1 — skipping June entirely.
This is easily triggered because viewDate is shared across week and month views. A user in week view can navigate until viewDate lands on the 31st (e.g., May 31), switch to month view, then navigate forward — the calendar jumps two months. Backward navigation has the same problem (e.g., from March 31 → "February 31" → March 3).
Concrete reproduction steps
- Open widget on any date (e.g. April 19)
- Switch to Week view
- Navigate forward ~2 weeks so viewDate becomes e.g. May 3, then keep going to May 31
- Switch to Month view (shows May)
- Click forward arrow → expects June, lands on July
| const navigateMonth = (delta: number) => { | |
| setViewDate((prev) => { | |
| const next = new Date(prev); | |
| next.setMonth(next.getMonth() + delta); | |
| return next; | |
| }); | |
| setSelectedDate(null); | |
| }; | |
| const navigateMonth = (delta: number) => { | |
| setViewDate((prev) => { | |
| const next = new Date(prev); | |
| next.setDate(1); | |
| next.setMonth(next.getMonth() + delta); | |
| return next; | |
| }); | |
| setSelectedDate(null); | |
| }; |
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Pull request overview
Adds week/month calendar visualizations to the existing EventsWidget so users can scan upcoming events/tasks by day, while keeping the existing agenda list view intact.
Changes:
- Added a segmented view toggle (Agenda / Week / Month) and navigation controls to
EventsWidget. - Introduced new
CalendarMonthViewandCalendarWeekViewcomponents (CSS-grid based) with day selection and inline detail panels. - Added new styling for the toggle/navigation and both calendar views.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| src/components/EventsWidget.tsx | Adds view state, toggle UI, and week/month navigation wiring. |
| src/components/EventsWidget.module.css | Styles for the view toggle and navigation controls. |
| src/components/CalendarMonthView.tsx | New month grid view with item dots and day detail panel. |
| src/components/CalendarMonthView.module.css | Styling for month grid, day states, and detail panel. |
| src/components/CalendarWeekView.tsx | New week grid view with per-day item counts and day detail panel. |
| src/components/CalendarWeekView.module.css | Styling for week grid columns, today/selected states, and detail panel. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| import { useMemo } from "react"; | ||
| import styles from "./CalendarMonthView.module.css"; | ||
| import type { AgendaItem } from "./EventsWidget"; |
There was a problem hiding this comment.
CalendarMonthView uses React hooks (useMemo) but the file is missing a "use client" directive. In Next.js App Router this will be treated as a Server Component and cannot be imported/used by the client-side EventsWidget, causing a build/runtime error. Add "use client" at the top of this file (before imports).
| import { useMemo } from "react"; | ||
| import styles from "./CalendarWeekView.module.css"; | ||
| import type { AgendaItem } from "./EventsWidget"; |
There was a problem hiding this comment.
CalendarWeekView uses React hooks (useMemo) but the file is missing a "use client" directive. In Next.js App Router this will be treated as a Server Component and cannot be imported/used by the client-side EventsWidget, causing a build/runtime error. Add "use client" at the top of this file (before imports).
|
|
||
| const navigateMonth = (delta: number) => { | ||
| setViewDate((prev) => { | ||
| const next = new Date(prev); |
There was a problem hiding this comment.
navigateMonth mutates the month via Date#setMonth while keeping the current day-of-month. When viewDate is on the 29th/30th/31st, navigating to a shorter month can overflow into the following month (e.g., Jan 31 + 1 month -> Mar 3), causing the month view to skip/land on the wrong month. Consider normalizing viewDate to a safe day (e.g., setDate(1)) before shifting months, or store an explicit (year, month) anchor for month navigation.
| const next = new Date(prev); | |
| const next = new Date(prev); | |
| next.setDate(1); |
| <button | ||
| key={key} | ||
| type="button" | ||
| className={`${styles.viewBtn} ${view === key ? styles.viewBtnActive : ""}`} | ||
| onClick={() => { | ||
| setView(key); | ||
| setSelectedDate(null); | ||
| }} | ||
| title={label} | ||
| > | ||
| <Icon size={14} /> | ||
| </button> | ||
| ))} | ||
| </div> | ||
|
|
||
| {view !== "agenda" && ( | ||
| <div className={styles.navControls}> | ||
| <button | ||
| type="button" | ||
| className={`btn-icon ${styles.navBtn}`} | ||
| onClick={() => (view === "month" ? navigateMonth(-1) : navigateWeek(-1))} | ||
| > | ||
| <ChevronLeft size={14} /> | ||
| </button> | ||
| <button | ||
| type="button" | ||
| className={styles.navLabel} | ||
| onClick={goToToday} | ||
| title="Go to today" | ||
| > | ||
| {getNavLabel()} | ||
| </button> | ||
| <button | ||
| type="button" | ||
| className={`btn-icon ${styles.navBtn}`} | ||
| onClick={() => (view === "month" ? navigateMonth(1) : navigateWeek(1))} | ||
| > | ||
| <ChevronRight size={14} /> | ||
| </button> |
There was a problem hiding this comment.
The view toggle and month/week navigation use icon-only elements without an accessible name. Relying on title (or having no label at all on the chevrons) is not reliably announced by screen readers. Add an aria-label (or visually hidden text) for each button (e.g., "Agenda view", "Next month", "Previous week").
| function sameDay(a: Date, b: Date): boolean { | ||
| return ( | ||
| a.getFullYear() === b.getFullYear() && | ||
| a.getMonth() === b.getMonth() && | ||
| a.getDate() === b.getDate() | ||
| ); | ||
| } |
There was a problem hiding this comment.
sameDay is now duplicated in EventsWidget, CalendarMonthView, and CalendarWeekView. To reduce drift/inconsistency, consider extracting a shared helper (e.g., in src/lib/dates.ts or a small local util) and reusing it across the three components.
Assignee: @alecvdp (alecvdpoel)
Summary
Adds Month and Week calendar views to the EventsWidget, complementing the existing Agenda view. Users can now answer "is this weekend full?" at a glance instead of scrolling a linear list.
What changed
agendaItems(events + incomplete tasks with due dates)Files changed
CalendarMonthView.tsx+.module.cssCalendarWeekView.tsx+.module.cssEventsWidget.tsxEventsWidget.module.cssTesting
Resolves IRL-28