Add event list feature (009-list-events)
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:
79
frontend/src/components/EventList.vue
Normal file
79
frontend/src/components/EventList.vue
Normal file
@@ -0,0 +1,79 @@
|
||||
<template>
|
||||
<div class="event-list" role="list" aria-label="Your events">
|
||||
<div v-for="event in sortedEvents" :key="event.eventToken" role="listitem">
|
||||
<EventCard
|
||||
:event-token="event.eventToken"
|
||||
:title="event.title"
|
||||
:relative-time="formatRelativeTime(event.dateTime)"
|
||||
:is-past="isPast(event.dateTime)"
|
||||
:event-role="getRole(event)"
|
||||
@delete="requestDelete"
|
||||
/>
|
||||
</div>
|
||||
<ConfirmDialog
|
||||
:open="!!pendingDeleteToken"
|
||||
title="Remove event?"
|
||||
message="This event will be removed from your list."
|
||||
confirm-label="Remove"
|
||||
cancel-label="Cancel"
|
||||
@confirm="confirmDelete"
|
||||
@cancel="cancelDelete"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { useEventStorage, isValidStoredEvent } from '../composables/useEventStorage'
|
||||
import { formatRelativeTime } from '../composables/useRelativeTime'
|
||||
import EventCard from './EventCard.vue'
|
||||
import ConfirmDialog from './ConfirmDialog.vue'
|
||||
import type { StoredEvent } from '../composables/useEventStorage'
|
||||
|
||||
const { getStoredEvents, removeEvent } = useEventStorage()
|
||||
|
||||
const pendingDeleteToken = ref<string | null>(null)
|
||||
|
||||
function requestDelete(eventToken: string) {
|
||||
pendingDeleteToken.value = eventToken
|
||||
}
|
||||
|
||||
function confirmDelete() {
|
||||
if (pendingDeleteToken.value) {
|
||||
removeEvent(pendingDeleteToken.value)
|
||||
}
|
||||
pendingDeleteToken.value = null
|
||||
}
|
||||
|
||||
function cancelDelete() {
|
||||
pendingDeleteToken.value = null
|
||||
}
|
||||
|
||||
function isPast(dateTime: string): boolean {
|
||||
return new Date(dateTime) < new Date()
|
||||
}
|
||||
|
||||
function getRole(event: StoredEvent): 'organizer' | 'attendee' | undefined {
|
||||
if (event.organizerToken) return 'organizer'
|
||||
if (event.rsvpToken) return 'attendee'
|
||||
return undefined
|
||||
}
|
||||
|
||||
const sortedEvents = computed(() => {
|
||||
const valid = getStoredEvents().filter(isValidStoredEvent)
|
||||
const now = new Date()
|
||||
const upcoming = valid.filter((e) => new Date(e.dateTime) >= now)
|
||||
const past = valid.filter((e) => new Date(e.dateTime) < now)
|
||||
upcoming.sort((a, b) => new Date(a.dateTime).getTime() - new Date(b.dateTime).getTime())
|
||||
past.sort((a, b) => new Date(b.dateTime).getTime() - new Date(a.dateTime).getTime())
|
||||
return [...upcoming, ...past]
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.event-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-sm);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user