feat: Enhance reservation functionality

This commit is contained in:
2026-04-22 10:23:22 -04:00
parent 7f1e82acc2
commit 534d66c774
25 changed files with 1236 additions and 91 deletions

View File

@@ -35,7 +35,13 @@
No upcoming reservations.
</p>
<IonList v-else lines="full">
<IonItem v-for="r in upcomingReservations" :key="r.id">
<IonItem
v-for="r in upcomingReservations"
:key="r.id"
button
detail
@click="openActionSheet(r)"
>
<IonLabel>
<h3>{{ boatName(r) }}</h3>
<p>{{ formatDateRange(r.start_time, r.end_time) }}</p>
@@ -71,9 +77,9 @@ import {
IonPage, IonHeader, IonToolbar, IonTitle, IonContent,
IonButtons, IonMenuButton, IonButton, IonCard, IonCardHeader,
IonCardTitle, IonCardContent, IonList, IonItem, IonLabel,
IonBadge, IonSpinner, IonIcon,
IonBadge, IonSpinner, IonIcon, actionSheetController, onIonViewWillEnter,
} from '@ionic/vue'
import { addCircleOutline } from 'ionicons/icons'
import { addCircleOutline, createOutline, trashOutline } from 'ionicons/icons'
import { useAuthStore } from '~/stores/auth'
import type { Database, ReservationStatus } from '~/types/supabase'
@@ -86,6 +92,7 @@ definePageMeta({ layout: false })
const user = useSupabaseUser()
const auth = useAuthStore()
const supabase = useSupabaseClient<Database>()
const router = useRouter()
const today = new Date()
const loadingReservations = ref(true)
@@ -99,13 +106,47 @@ async function fetchReservations() {
.select('*, boats(name, display_name)')
.eq('user_id', user.value.id)
.gte('start_time', new Date().toISOString())
.neq('status', 'cancelled')
.order('start_time', { ascending: true })
.limit(3)
upcomingReservations.value = (data as ReservationWithBoat[]) ?? []
loadingReservations.value = false
}
// Initial load and re-fetch on every page visit (Ionic caches the page, so
// watch(user) alone won't re-run when navigating back from create/edit).
watch(user, (val) => { if (val) fetchReservations() }, { immediate: true })
onIonViewWillEnter(() => { if (user.value) fetchReservations() })
async function openActionSheet(r: ReservationWithBoat) {
const isFuture = new Date(r.start_time) > new Date()
const sheet = await actionSheetController.create({
header: `${boatName(r)}${formatDateRange(r.start_time, r.end_time)}`,
buttons: [
...(isFuture ? [
{
text: 'Edit',
icon: createOutline,
handler: () => void router.push(`/reservations/edit/${r.id}`),
},
{
text: 'Cancel Reservation',
role: 'destructive',
icon: trashOutline,
handler: () => void cancelReservation(r.id),
},
] : []),
{ text: 'Dismiss', role: 'cancel' },
],
})
await sheet.present()
}
async function cancelReservation(id: string) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
await (supabase as any).from('reservations').update({ status: 'cancelled' }).eq('id', id)
upcomingReservations.value = upcomingReservations.value.filter(r => r.id !== id)
}
function boatName(r: ReservationWithBoat) {
return r.boats?.display_name || r.boats?.name || 'Unknown boat'
@@ -122,9 +163,10 @@ function formatDateRange(start: string, end: string) {
function statusColor(status: ReservationStatus): string {
const colors: Record<ReservationStatus, string> = {
pending: 'warning',
pending: 'warning',
tentative: 'medium',
confirmed: 'success',
cancelled: 'danger',
}
return colors[status]
}