Replace 'pax' booking mode with 'seat' Consolidate inventory summary to track only seat counts Update database schema and UI for seat-centric capacity
173 lines
6.4 KiB
Vue
173 lines
6.4 KiB
Vue
<script lang="ts" setup>
|
|
import type { PublicSeatReceipt } from '~~/shared/booking'
|
|
|
|
import {
|
|
DINNER_EVENT_DATE_LABEL,
|
|
DINNER_EVENT_TIME_LABEL,
|
|
DINNER_EVENT_TITLE,
|
|
DINNER_EVENT_VENUE,
|
|
formatBookingCurrency,
|
|
getSeatLabel,
|
|
getTicketCatalogItem
|
|
} from '~~/shared/booking'
|
|
|
|
import { formatDateTime } from '../../utils/formatters'
|
|
|
|
const route = useRoute()
|
|
const apiClient = useApiClient()
|
|
|
|
const token = String(route.params.token || '')
|
|
|
|
let initialReceipt: PublicSeatReceipt
|
|
|
|
try {
|
|
initialReceipt = await apiClient<PublicSeatReceipt>(`/api/public/seats/${token}`)
|
|
} catch (error: any) {
|
|
throw createError({
|
|
statusCode: error?.statusCode || error?.data?.statusCode || 404,
|
|
statusMessage: error?.data?.statusMessage || error?.message || 'Seat ticket not found'
|
|
})
|
|
}
|
|
|
|
const receipt = ref(initialReceipt)
|
|
|
|
const ticketLabel = computed(() => getTicketCatalogItem(receipt.value.booking.ticketType)?.label || receipt.value.booking.ticketType.toUpperCase())
|
|
const totalFormatted = computed(() => formatBookingCurrency(receipt.value.booking.totalPrice))
|
|
</script>
|
|
|
|
<template>
|
|
<UContainer class="py-8">
|
|
<div class="mx-auto max-w-4xl space-y-6">
|
|
<div class="space-y-2 text-center">
|
|
<UBadge label="Seat Ticket" color="primary" variant="soft" class="rounded-full" />
|
|
<h1 class="text-2xl font-bold tracking-tight text-highlighted sm:text-3xl">
|
|
{{ getSeatLabel(receipt.seat.seatNumber) }}
|
|
</h1>
|
|
<p class="text-sm text-muted">
|
|
{{ DINNER_EVENT_TITLE }}
|
|
</p>
|
|
</div>
|
|
|
|
<div class="grid gap-6 lg:grid-cols-[18rem_minmax(0,1fr)]">
|
|
<UCard class="border border-default bg-default shadow-sm" :ui="{ body: 'space-y-4 p-4 sm:p-5' }">
|
|
<div class="space-y-1 text-center">
|
|
<p class="text-sm font-semibold text-highlighted">
|
|
QR Code
|
|
</p>
|
|
<p class="text-xs text-muted">
|
|
Present this QR code at check-in.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="flex justify-center">
|
|
<QrCodeSvg :value="receipt.seat.seatUrl" :size="220" />
|
|
</div>
|
|
|
|
<div class="rounded-2xl border border-default bg-elevated p-4 text-sm text-default">
|
|
<p class="font-medium text-highlighted">
|
|
{{ receipt.seat.recipientName || receipt.booking.customerName }}
|
|
</p>
|
|
<p v-if="receipt.seat.recipientPhone" class="mt-1 text-muted">
|
|
{{ receipt.seat.recipientPhone }}
|
|
</p>
|
|
<p class="mt-3 text-muted">
|
|
{{ receipt.booking.status === 'confirmed' ? 'Booking confirmed' : 'Booking pending confirmation' }}
|
|
</p>
|
|
</div>
|
|
</UCard>
|
|
|
|
<UCard class="border border-default bg-default shadow-sm" :ui="{ body: 'space-y-5 p-4 sm:p-5' }">
|
|
<div class="space-y-1">
|
|
<h2 class="text-xl font-semibold text-highlighted">
|
|
Booking Details
|
|
</h2>
|
|
<p class="text-sm text-muted">
|
|
Linked back to the main booking receipt.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="grid gap-3 sm:grid-cols-2">
|
|
<div class="rounded-2xl border border-default bg-elevated p-4">
|
|
<p class="text-xs uppercase tracking-wide text-muted">
|
|
Guest / Organizer
|
|
</p>
|
|
<p class="mt-2 font-semibold text-highlighted">
|
|
{{ receipt.booking.customerName }}
|
|
</p>
|
|
</div>
|
|
<div class="rounded-2xl border border-default bg-elevated p-4">
|
|
<p class="text-xs uppercase tracking-wide text-muted">
|
|
Ticket Category
|
|
</p>
|
|
<p class="mt-2 font-semibold text-highlighted">
|
|
{{ ticketLabel }}
|
|
</p>
|
|
</div>
|
|
<div class="rounded-2xl border border-default bg-elevated p-4">
|
|
<p class="text-xs uppercase tracking-wide text-muted">
|
|
Total Seats
|
|
</p>
|
|
<p class="mt-2 font-semibold text-highlighted">
|
|
{{ receipt.booking.seatCount }} seats
|
|
</p>
|
|
</div>
|
|
<div class="rounded-2xl border border-default bg-elevated p-4">
|
|
<p class="text-xs uppercase tracking-wide text-muted">
|
|
Total Booking Value
|
|
</p>
|
|
<p class="mt-2 font-semibold text-highlighted">
|
|
{{ totalFormatted }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="space-y-3 rounded-2xl border border-default bg-elevated p-4">
|
|
<div class="flex items-center gap-3 text-sm text-default">
|
|
<UIcon name="i-lucide-calendar-days" class="size-4 text-muted" />
|
|
<span>{{ DINNER_EVENT_DATE_LABEL }}</span>
|
|
</div>
|
|
<div class="flex items-center gap-3 text-sm text-default">
|
|
<UIcon name="i-lucide-clock-6" class="size-4 text-muted" />
|
|
<span>{{ DINNER_EVENT_TIME_LABEL }}</span>
|
|
</div>
|
|
<div class="flex items-center gap-3 text-sm text-default">
|
|
<UIcon name="i-lucide-map-pin" class="size-4 text-muted" />
|
|
<span>{{ DINNER_EVENT_VENUE }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="rounded-2xl border border-default bg-elevated p-4 text-sm text-default">
|
|
<p class="font-medium text-highlighted">
|
|
Seat shared {{ receipt.seat.sharedAt ? formatDateTime(receipt.seat.sharedAt) : 'recently' }}
|
|
</p>
|
|
<p class="mt-1 text-muted">
|
|
Submitted {{ formatDateTime(receipt.booking.createdAt) }}
|
|
</p>
|
|
<p v-if="receipt.booking.confirmedAt" class="mt-1 text-muted">
|
|
Confirmed {{ formatDateTime(receipt.booking.confirmedAt) }}
|
|
</p>
|
|
</div>
|
|
|
|
<div class="flex flex-col gap-2 sm:flex-row">
|
|
<UButton
|
|
:to="receipt.receiptUrl"
|
|
label="Open Main Receipt"
|
|
icon="i-lucide-receipt"
|
|
class="flex-1 justify-center"
|
|
/>
|
|
<UButton
|
|
:to="receipt.seat.seatUrl"
|
|
target="_blank"
|
|
label="Open Ticket Link"
|
|
color="neutral"
|
|
variant="outline"
|
|
icon="i-lucide-external-link"
|
|
class="flex-1 justify-center"
|
|
/>
|
|
</div>
|
|
</UCard>
|
|
</div>
|
|
</div>
|
|
</UContainer>
|
|
</template>
|