Add event list feature (009-list-events)
All checks were successful
CI / backend-test (push) Successful in 57s
CI / frontend-test (push) Successful in 22s
CI / frontend-e2e (push) Successful in 1m4s
CI / build-and-publish (push) Has been skipped

Enable users to see all their saved events on the home screen, sorted
by date with upcoming events first. Key capabilities:

- EventCard with title, relative time display, and organizer/attendee
  role badge
- Sortable EventList with past-event visual distinction (faded style)
- Empty state when no events are stored
- Swipe-to-delete gesture with confirmation dialog
- Floating action button for quick event creation
- Rename router param :token → :eventToken across all views
- useRelativeTime composable (Intl.RelativeTimeFormat)
- useEventStorage: add validation, removeEvent(), reactive versioning
- Full E2E and unit test coverage for all new components

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-08 15:53:55 +01:00
parent 1b3eafa8d1
commit e56998b17c
28 changed files with 1989 additions and 27 deletions

View File

@@ -0,0 +1,99 @@
# Data Model: Event List on Home Page
**Feature**: 009-list-events | **Date**: 2026-03-08
## Entities
### StoredEvent (existing — no changes)
The `StoredEvent` interface in `frontend/src/composables/useEventStorage.ts` already contains all fields needed for the event list feature.
```typescript
interface StoredEvent {
eventToken: string // Required — UUID, used for navigation
organizerToken?: string // Present if user created this event
title: string // Required — displayed on card
dateTime: string // Required — ISO 8601, used for sorting + relative time
expiryDate: string // Stored but not displayed in list view
rsvpToken?: string // Present if user RSVP'd to this event
rsvpName?: string // User's name at RSVP time
}
```
### Validation Rules
An event entry is considered **valid** for display if all of:
- `eventToken` is a non-empty string
- `title` is a non-empty string
- `dateTime` is a non-empty string that parses to a valid `Date`
Invalid entries are silently excluded from the list (FR-010).
### Derived Properties (computed at render time)
| Property | Derivation |
|----------|-----------|
| `isPast` | `new Date(dateTime) < new Date()` |
| `isOrganizer` | `organizerToken !== undefined` |
| `isAttendee` | `rsvpToken !== undefined && organizerToken === undefined` |
| `relativeTime` | `Intl.RelativeTimeFormat` applied to `dateTime` vs now |
| `detailRoute` | `/events/${eventToken}` |
### Sorting Order
1. **Upcoming events** (`dateTime >= now`): ascending by `dateTime` (soonest first)
2. **Past events** (`dateTime < now`): descending by `dateTime` (most recently passed first)
### Composable Extension
The `useEventStorage` composable needs one new function:
```typescript
function removeEvent(eventToken: string): void {
const events = readEvents().filter((e) => e.eventToken !== eventToken)
writeEvents(events)
}
```
Returned alongside existing functions from `useEventStorage()`.
## State Transitions
```
localStorage read
Parse JSON ──(error)──► empty array
Validate entries ──(invalid)──► silently excluded
Split: upcoming / past
Sort each group
Concatenate ──► rendered list
```
### Remove Event Flow
```
User taps delete icon / swipes left
ConfirmDialog opens
┌────┴────┐
│ Cancel │ Confirm
│ │ │
│ ▼ ▼
│ removeEvent(token)
│ │
│ ▼
│ Event removed from localStorage
│ List re-renders (event disappears)
└────────────────────────────────┘
```