Replace 'pax' booking mode with 'seat' Consolidate inventory summary to track only seat counts Update database schema and UI for seat-centric capacity
212 lines
6.3 KiB
Vue
212 lines
6.3 KiB
Vue
<script lang="ts" setup>
|
|
import type { PublicBooking } from '~~/shared/booking'
|
|
|
|
import {
|
|
formatBookingCurrency,
|
|
getBookingStatusLabel,
|
|
getTicketCatalogItem
|
|
} from '~~/shared/booking'
|
|
|
|
import { getErrorMessage } from '../../utils/errors'
|
|
import { formatDateTime } from '../../utils/formatters'
|
|
|
|
const route = useRoute()
|
|
const toast = useToast()
|
|
const apiClient = useApiClient()
|
|
|
|
const token = String(route.params.token || '')
|
|
const confirming = ref(false)
|
|
|
|
let initialBooking: PublicBooking
|
|
|
|
try {
|
|
const response = await apiClient<{ booking: PublicBooking }>(`/api/public/bookings/${token}`)
|
|
initialBooking = response.booking
|
|
} catch (error: any) {
|
|
throw createError({
|
|
statusCode: error?.statusCode || error?.data?.statusCode || 404,
|
|
statusMessage: error?.data?.statusMessage || error?.message || 'Booking not found'
|
|
})
|
|
}
|
|
|
|
const booking = ref(initialBooking)
|
|
|
|
const statusColor = computed(() => booking.value.status === 'confirmed' ? 'success' : 'warning')
|
|
const ticketLabel = computed(() => getTicketCatalogItem(booking.value.ticketType)?.label || booking.value.ticketType.toUpperCase())
|
|
const totalFormatted = computed(() => formatBookingCurrency(booking.value.totalPrice))
|
|
const receiptPath = computed(() => `/receipt/${booking.value.receiptToken}`)
|
|
const detailRows = computed(() => {
|
|
const rows = [
|
|
{
|
|
label: 'Guest / Organizer',
|
|
value: booking.value.customerName
|
|
},
|
|
{
|
|
label: 'Contact Number',
|
|
value: booking.value.customerPhone
|
|
},
|
|
{
|
|
label: 'Person In Charge',
|
|
value: booking.value.personInChargeName
|
|
},
|
|
{
|
|
label: 'PIC Phone',
|
|
value: booking.value.personInChargePhoneNumber
|
|
},
|
|
{
|
|
label: 'Ticket Category',
|
|
value: ticketLabel.value
|
|
},
|
|
{
|
|
label: 'Seats Covered',
|
|
value: String(booking.value.seatCount)
|
|
},
|
|
{
|
|
label: 'Submitted',
|
|
value: formatDateTime(booking.value.createdAt)
|
|
}
|
|
]
|
|
|
|
if (booking.value.confirmedAt) {
|
|
rows.push({
|
|
label: 'Confirmed At',
|
|
value: formatDateTime(booking.value.confirmedAt)
|
|
})
|
|
}
|
|
|
|
return rows
|
|
})
|
|
|
|
async function confirmBooking() {
|
|
if (booking.value.status === 'confirmed') {
|
|
return
|
|
}
|
|
|
|
confirming.value = true
|
|
|
|
try {
|
|
const response = await apiClient<{ booking: PublicBooking, alreadyConfirmed: boolean }>(
|
|
`/api/public/bookings/${token}/confirm`,
|
|
{
|
|
method: 'POST'
|
|
}
|
|
)
|
|
|
|
booking.value = response.booking
|
|
|
|
toast.add({
|
|
title: response.alreadyConfirmed ? 'Booking already confirmed' : 'Booking confirmed',
|
|
description: response.alreadyConfirmed
|
|
? 'This booking had already been confirmed earlier.'
|
|
: 'The booking details have been confirmed successfully.',
|
|
color: 'success',
|
|
icon: 'i-lucide-check-circle-2'
|
|
})
|
|
} catch (error) {
|
|
toast.add({
|
|
title: 'Confirmation failed',
|
|
description: getErrorMessage(error, 'Please try again in a moment.'),
|
|
color: 'error',
|
|
icon: 'i-lucide-circle-alert'
|
|
})
|
|
} finally {
|
|
confirming.value = false
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<UContainer class="py-8">
|
|
<div class="mx-auto max-w-3xl space-y-5">
|
|
<div class="space-y-2 text-center">
|
|
<UBadge label="PIC Confirmation" color="primary" variant="soft" class="rounded-full" />
|
|
<h1 class="text-2xl font-bold tracking-tight text-highlighted sm:text-3xl">
|
|
Review Booking Details
|
|
</h1>
|
|
<p class="text-sm text-muted">
|
|
Confirm the booking after verifying the details below.
|
|
</p>
|
|
</div>
|
|
|
|
<UCard
|
|
class="border border-default bg-default shadow-sm"
|
|
:ui="{ body: 'space-y-4 p-4 sm:p-5' }"
|
|
>
|
|
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
|
<div class="space-y-1">
|
|
<p class="text-xs font-medium uppercase tracking-wide text-muted">
|
|
Booking status
|
|
</p>
|
|
<UBadge :label="getBookingStatusLabel(booking.status)" :color="statusColor" variant="soft" />
|
|
</div>
|
|
|
|
<div class="text-sm text-muted">
|
|
Submitted {{ formatDateTime(booking.createdAt) }}
|
|
</div>
|
|
</div>
|
|
|
|
<UAlert
|
|
v-if="booking.status === 'confirmed'"
|
|
title="Booking already confirmed"
|
|
:description="`Confirmed on ${formatDateTime(booking.confirmedAt)}.`"
|
|
color="success"
|
|
icon="i-lucide-badge-check"
|
|
/>
|
|
|
|
<div class="overflow-hidden rounded-xl border border-default">
|
|
<div
|
|
v-for="row in detailRows"
|
|
:key="row.label"
|
|
class="grid grid-cols-[8.5rem_minmax(0,1fr)] gap-3 border-b border-default px-4 py-3 text-sm last:border-b-0 sm:grid-cols-[11rem_minmax(0,1fr)]"
|
|
>
|
|
<div class="text-xs font-medium uppercase tracking-wide text-muted sm:text-sm sm:normal-case sm:tracking-normal">
|
|
{{ row.label }}
|
|
</div>
|
|
<div class="min-w-0 font-medium text-highlighted break-words">
|
|
{{ row.value }}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-[8.5rem_minmax(0,1fr)] gap-3 bg-primary/5 px-4 py-3 sm:grid-cols-[11rem_minmax(0,1fr)]">
|
|
<div class="text-xs font-semibold uppercase tracking-wide text-primary sm:text-sm sm:normal-case sm:tracking-normal">
|
|
Total Price
|
|
</div>
|
|
<div class="min-w-0 text-lg font-bold text-highlighted sm:text-xl">
|
|
{{ totalFormatted }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex flex-col-reverse gap-3 sm:flex-row sm:justify-end">
|
|
<UButton
|
|
to="/"
|
|
label="Back To Booking Form"
|
|
color="neutral"
|
|
variant="ghost"
|
|
class="justify-center"
|
|
/>
|
|
|
|
<UButton
|
|
v-if="booking.status === 'confirmed'"
|
|
:to="receiptPath"
|
|
label="Open Ticket Receipt"
|
|
color="neutral"
|
|
variant="outline"
|
|
icon="i-lucide-receipt"
|
|
class="justify-center"
|
|
/>
|
|
|
|
<UButton
|
|
label="Confirm This Booking"
|
|
icon="i-lucide-check-check"
|
|
class="justify-center"
|
|
:disabled="booking.status === 'confirmed'"
|
|
:loading="confirming"
|
|
@click="confirmBooking"
|
|
/>
|
|
</div>
|
|
</UCard>
|
|
</div>
|
|
</UContainer>
|
|
</template>
|