Recurring event editing (rrule) with this/all occurrence scope, web push subscriptions (VAPID) with a cached config layer for notification settings, and email delivery via nodemailer. Also fixes the calendar view never actually stacking on mobile: the Calendars filter column used flex:1 with minWidth:0 on its sibling, so flex-wrap never triggered regardless of viewport width, squeezing the grid and view switcher into a sliver next to a fixed 220px sidebar. Adds a proper mobile breakpoint that stacks the layout, scrolls the week grid horizontally instead of compressing it, and enlarges touch targets.
51 lines
1.8 KiB
TypeScript
51 lines
1.8 KiB
TypeScript
import type { EventSummary } from '../../types'
|
|
import { monthGridDays, isSameDay, eventOccursOnDay, formatTime, WEEKDAY_LABELS } from '../../dateUtils'
|
|
|
|
const MAX_VISIBLE = 3
|
|
|
|
export default function MonthGrid({ events, date, onSelectDate, onSelectEvent }: {
|
|
events: EventSummary[]
|
|
date: Date
|
|
onSelectDate?: (d: Date) => void
|
|
onSelectEvent: (ev: EventSummary) => void
|
|
}) {
|
|
const days = monthGridDays(date)
|
|
const today = new Date()
|
|
|
|
return (
|
|
<div className="cal-grid">
|
|
{WEEKDAY_LABELS.map(d => (
|
|
<div key={d} className="cal-day-header">{d}</div>
|
|
))}
|
|
{days.map(day => {
|
|
const dayEvents = events
|
|
.filter(ev => eventOccursOnDay(ev, day))
|
|
.sort((a, b) => a.start_at.localeCompare(b.start_at))
|
|
const cls = [
|
|
'cal-day',
|
|
day.getMonth() !== date.getMonth() ? 'other-month' : '',
|
|
isSameDay(day, today) ? 'today' : '',
|
|
].filter(Boolean).join(' ')
|
|
return (
|
|
<div key={day.toISOString()} className={cls} onClick={() => onSelectDate?.(day)}>
|
|
<div className="cal-day-num">{day.getDate()}</div>
|
|
{dayEvents.slice(0, MAX_VISIBLE).map(ev => (
|
|
<span
|
|
key={ev.id}
|
|
className="cal-event-chip"
|
|
style={{ background: ev.calendar_color, color: '#fff' }}
|
|
title={ev.title}
|
|
onClick={e => { e.stopPropagation(); onSelectEvent(ev) }}
|
|
>
|
|
{!ev.all_day && `${formatTime(ev.start_at)} `}{ev.title}
|
|
</span>
|
|
))}
|
|
{dayEvents.length > MAX_VISIBLE && (
|
|
<span className="cal-event-more">+{dayEvents.length - MAX_VISIBLE} more</span>
|
|
)}
|
|
</div>
|
|
)
|
|
})}
|
|
</div>
|
|
)
|
|
}
|