refactor(bookings): simplify capacity tracking to use seats
Replace 'pax' booking mode with 'seat' Consolidate inventory summary to track only seat counts Update database schema and UI for seat-centric capacity
This commit is contained in:
@@ -45,15 +45,15 @@
|
||||
|
||||
<UForm :state="capacityForm" class="space-y-3" @submit="saveCapacity">
|
||||
<div class="flex flex-col gap-3 sm:flex-row sm:items-end">
|
||||
<UFormField name="totalTables" label="Total Tables" class="flex-1">
|
||||
<UFormField name="totalSeats" label="Total Seats" class="flex-1">
|
||||
<UInput
|
||||
v-model="capacityForm.totalTables"
|
||||
v-model="capacityForm.totalSeats"
|
||||
type="number"
|
||||
inputmode="numeric"
|
||||
min="0"
|
||||
size="md"
|
||||
class="w-full"
|
||||
placeholder="Leave blank for no table limit"
|
||||
placeholder="Leave blank for no seat limit"
|
||||
/>
|
||||
</UFormField>
|
||||
|
||||
@@ -90,10 +90,10 @@
|
||||
<div class="grid gap-4 sm:grid-cols-2">
|
||||
<div class="rounded-lg border border-default bg-muted/20 p-3">
|
||||
<p class="text-xs uppercase tracking-wide text-muted">
|
||||
Pending tables
|
||||
Pending bookings
|
||||
</p>
|
||||
<p class="mt-1 text-xl font-semibold text-highlighted">
|
||||
{{ summary.pendingTables }}
|
||||
{{ pendingCount }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -109,7 +109,7 @@
|
||||
</UCard>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-3 sm:grid-cols-2 xl:grid-cols-5">
|
||||
<div class="grid gap-3 sm:grid-cols-2 xl:grid-cols-3">
|
||||
<UCard
|
||||
v-for="item in inventoryCards"
|
||||
:key="item.label"
|
||||
@@ -123,9 +123,6 @@
|
||||
<p class="text-2xl font-semibold leading-none text-highlighted">
|
||||
{{ item.value }}
|
||||
</p>
|
||||
<p v-if="item.meta" class="text-xs text-muted">
|
||||
{{ item.meta }}
|
||||
</p>
|
||||
</div>
|
||||
</UCard>
|
||||
</div>
|
||||
@@ -134,10 +131,10 @@
|
||||
<UCard class="border border-default bg-default shadow-sm" :ui="{ body: 'px-4 py-3' }">
|
||||
<div class="space-y-0.5">
|
||||
<p class="text-[11px] font-medium uppercase tracking-wide text-muted">
|
||||
Total Tables
|
||||
Total Seats
|
||||
</p>
|
||||
<p class="text-2xl font-semibold leading-none text-highlighted">
|
||||
{{ formatInventoryNumber(summary.totalTables) }}
|
||||
{{ formatInventoryNumber(summary.totalSeats) }}
|
||||
</p>
|
||||
</div>
|
||||
</UCard>
|
||||
@@ -213,13 +210,10 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #bookingMode-cell="{ row }">
|
||||
<template #quantity-cell="{ row }">
|
||||
<div class="space-y-0.5 py-0.5">
|
||||
<div class="text-sm font-medium text-default">
|
||||
{{ getBookingModeLabel(row.original.bookingMode) }}
|
||||
</div>
|
||||
<div class="text-xs text-muted">
|
||||
{{ row.original.quantity }} x {{ ticketLabel(row.original.ticketType) }}
|
||||
{{ ticketLabel(row.original.ticketType) }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -298,7 +292,6 @@ import type { BookingCapacitySettings, BookingInventorySummary, PublicBooking, T
|
||||
|
||||
import {
|
||||
formatBookingCurrency,
|
||||
getBookingModeLabel,
|
||||
getBookingStatusLabel,
|
||||
getTicketCatalogItem
|
||||
} from '~~/shared/booking'
|
||||
@@ -319,29 +312,22 @@ const loadingBookings = ref(false)
|
||||
const savingCapacity = ref(false)
|
||||
const searchQuery = ref('')
|
||||
const settings = reactive<BookingCapacitySettings>({
|
||||
totalTables: null,
|
||||
totalSeats: null,
|
||||
updatedAt: null
|
||||
})
|
||||
const summary = reactive<BookingInventorySummary>({
|
||||
totalTables: null,
|
||||
totalCapacitySeats: null,
|
||||
soldTables: 0,
|
||||
pendingTables: 0,
|
||||
totalSeats: null,
|
||||
soldSeats: 0,
|
||||
pendingSeats: 0,
|
||||
soldCapacitySeats: 0,
|
||||
pendingCapacitySeats: 0,
|
||||
leftTables: null,
|
||||
leftSeats: null,
|
||||
leftCapacitySeats: null
|
||||
leftSeats: null
|
||||
})
|
||||
const capacityForm = reactive({
|
||||
totalTables: ''
|
||||
totalSeats: ''
|
||||
})
|
||||
|
||||
const columns = [
|
||||
{ accessorKey: 'customerName', header: 'Guest' },
|
||||
{ accessorKey: 'bookingMode', header: 'Booking' },
|
||||
{ accessorKey: 'quantity', header: 'Booking' },
|
||||
{ accessorKey: 'seatCount', header: 'Seats / Total' },
|
||||
{ accessorKey: 'personInChargeName', header: 'PIC' },
|
||||
{ id: 'status', header: 'Status' },
|
||||
@@ -350,27 +336,18 @@ const columns = [
|
||||
]
|
||||
|
||||
const inventoryDescription = computed(() => {
|
||||
return 'Set only the total number of tables. The system treats each table as 10 seats, then auto-calculates sold and remaining inventory.'
|
||||
return 'Every booking is converted into seats immediately, so sold and remaining capacity are tracked only in seats.'
|
||||
})
|
||||
|
||||
const inventoryCards = computed(() => {
|
||||
return [
|
||||
{
|
||||
label: 'Tables Sold',
|
||||
value: String(summary.soldTables)
|
||||
},
|
||||
{
|
||||
label: 'Seats Sold',
|
||||
value: String(summary.soldSeats)
|
||||
},
|
||||
{
|
||||
label: 'Capacity Used',
|
||||
value: String(summary.soldCapacitySeats),
|
||||
meta: 'in seats'
|
||||
},
|
||||
{
|
||||
label: 'Tables Left',
|
||||
value: formatInventoryNumber(summary.leftTables)
|
||||
label: 'Pending Seats',
|
||||
value: String(summary.pendingSeats)
|
||||
},
|
||||
{
|
||||
label: 'Seats Left',
|
||||
@@ -434,27 +411,20 @@ function normalizeCapacityValue(value: string | number | null | undefined) {
|
||||
}
|
||||
|
||||
function syncCapacityForm(nextSettings: BookingCapacitySettings) {
|
||||
capacityForm.totalTables = nextSettings.totalTables === null ? '' : String(nextSettings.totalTables)
|
||||
capacityForm.totalSeats = nextSettings.totalSeats === null ? '' : String(nextSettings.totalSeats)
|
||||
}
|
||||
|
||||
function applySettings(nextSettings: BookingCapacitySettings) {
|
||||
settings.totalTables = nextSettings.totalTables
|
||||
settings.totalSeats = nextSettings.totalSeats
|
||||
settings.updatedAt = nextSettings.updatedAt
|
||||
syncCapacityForm(nextSettings)
|
||||
}
|
||||
|
||||
function applySummary(nextSummary: BookingInventorySummary) {
|
||||
summary.totalTables = nextSummary.totalTables
|
||||
summary.totalCapacitySeats = nextSummary.totalCapacitySeats
|
||||
summary.soldTables = nextSummary.soldTables
|
||||
summary.pendingTables = nextSummary.pendingTables
|
||||
summary.totalSeats = nextSummary.totalSeats
|
||||
summary.soldSeats = nextSummary.soldSeats
|
||||
summary.pendingSeats = nextSummary.pendingSeats
|
||||
summary.soldCapacitySeats = nextSummary.soldCapacitySeats
|
||||
summary.pendingCapacitySeats = nextSummary.pendingCapacitySeats
|
||||
summary.leftTables = nextSummary.leftTables
|
||||
summary.leftSeats = nextSummary.leftSeats
|
||||
summary.leftCapacitySeats = nextSummary.leftCapacitySeats
|
||||
}
|
||||
|
||||
async function refreshBookings() {
|
||||
@@ -499,7 +469,7 @@ async function saveCapacity(event: Event) {
|
||||
const response = await apiClient<{ settings: BookingCapacitySettings }>('/api/bookings/capacity', {
|
||||
method: 'PATCH',
|
||||
body: {
|
||||
totalTables: normalizeCapacityValue(capacityForm.totalTables)
|
||||
totalSeats: normalizeCapacityValue(capacityForm.totalSeats)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user