Files
fete/frontend/src/composables/useEventGrouping.ts
nitrix a52d0cd1d3
All checks were successful
CI / backend-test (push) Successful in 58s
CI / frontend-test (push) Successful in 23s
CI / frontend-e2e (push) Successful in 1m9s
CI / build-and-publish (push) Has been skipped
Add temporal grouping to event list (Today/This Week/Next Week/Later/Past)
Group events into five temporal sections with section headers, date subheaders,
and context-aware time display (clock time for upcoming, relative for past).
Includes new useEventGrouping composable, SectionHeader and DateSubheader
components, full unit and E2E test coverage.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 17:26:58 +01:00

150 lines
3.9 KiB
TypeScript

import type { StoredEvent } from './useEventStorage'
export type SectionKey = 'today' | 'thisWeek' | 'nextWeek' | 'later' | 'past'
export interface DateGroup {
dateKey: string
label: string
events: StoredEvent[]
showSubheader: boolean
}
export interface EventSection {
key: SectionKey
label: string
dateGroups: DateGroup[]
emphasized: boolean
}
const SECTION_ORDER: SectionKey[] = ['today', 'thisWeek', 'nextWeek', 'later', 'past']
const SECTION_LABELS: Record<SectionKey, string> = {
today: 'Today',
thisWeek: 'This Week',
nextWeek: 'Next Week',
later: 'Later',
past: 'Past',
}
function startOfDay(date: Date): Date {
const d = new Date(date)
d.setHours(0, 0, 0, 0)
return d
}
function endOfDay(date: Date): Date {
const d = new Date(date)
d.setHours(23, 59, 59, 999)
return d
}
function endOfWeek(date: Date): Date {
const d = new Date(date)
const dayOfWeek = d.getDay() // 0=Sun, 1=Mon, ..., 6=Sat
// ISO week: Monday is first day. End of week = Sunday.
// If today is Sunday (0), end of week is today.
// Otherwise, days until Sunday = 7 - dayOfWeek
const daysUntilSunday = dayOfWeek === 0 ? 0 : 7 - dayOfWeek
d.setDate(d.getDate() + daysUntilSunday)
return endOfDay(d)
}
function endOfNextWeek(date: Date): Date {
const thisWeekEnd = endOfWeek(date)
const d = new Date(thisWeekEnd)
d.setDate(d.getDate() + 7)
return endOfDay(d)
}
function toDateKey(date: Date): string {
const y = date.getFullYear()
const m = String(date.getMonth() + 1).padStart(2, '0')
const d = String(date.getDate()).padStart(2, '0')
return `${y}-${m}-${d}`
}
function formatDateLabel(date: Date): string {
return new Intl.DateTimeFormat(undefined, {
weekday: 'short',
day: 'numeric',
month: 'short',
}).format(date)
}
function classifyEvent(eventDate: Date, todayStart: Date, todayEnd: Date, weekEnd: Date, nextWeekEnd: Date): SectionKey {
if (eventDate < todayStart) return 'past'
if (eventDate <= todayEnd) return 'today'
if (eventDate <= weekEnd) return 'thisWeek'
if (eventDate <= nextWeekEnd) return 'nextWeek'
return 'later'
}
export function useEventGrouping(events: StoredEvent[], now: Date = new Date()): EventSection[] {
const todayStart = startOfDay(now)
const todayEnd = endOfDay(now)
const weekEnd = endOfWeek(now)
const nextWeekEnd = endOfNextWeek(now)
// Classify events into sections
const buckets: Record<SectionKey, StoredEvent[]> = {
today: [],
thisWeek: [],
nextWeek: [],
later: [],
past: [],
}
for (const event of events) {
const eventDate = new Date(event.dateTime)
const section = classifyEvent(eventDate, todayStart, todayEnd, weekEnd, nextWeekEnd)
buckets[section].push(event)
}
// Build sections
const sections: EventSection[] = []
for (const key of SECTION_ORDER) {
const sectionEvents = buckets[key]
if (sectionEvents.length === 0) continue
// Sort events
const ascending = key !== 'past'
sectionEvents.sort((a, b) => {
const diff = new Date(a.dateTime).getTime() - new Date(b.dateTime).getTime()
return ascending ? diff : -diff
})
// Group by date
const dateGroupMap = new Map<string, StoredEvent[]>()
for (const event of sectionEvents) {
const dateKey = toDateKey(new Date(event.dateTime))
const group = dateGroupMap.get(dateKey)
if (group) {
group.push(event)
} else {
dateGroupMap.set(dateKey, [event])
}
}
// Convert to DateGroup array (order preserved from sorted events)
const dateGroups: DateGroup[] = []
for (const [dateKey, groupEvents] of dateGroupMap) {
dateGroups.push({
dateKey,
label: formatDateLabel(new Date(groupEvents[0]!.dateTime)),
events: groupEvents,
showSubheader: key !== 'today',
})
}
sections.push({
key,
label: SECTION_LABELS[key],
dateGroups,
emphasized: key === 'today',
})
}
return sections
}