feat(booking): move event and ticket configuration to database
Replace hardcoded event details and ticket types with dynamic DB records Add booking-config API endpoint to serve active event settings
This commit is contained in:
@@ -1,56 +1,62 @@
|
||||
export type BookingMode = 'table' | 'seat'
|
||||
export type TicketType = 'vip' | 'supporter'
|
||||
export type BookingMode = string
|
||||
export type TicketType = string
|
||||
export type BookingStatus = 'pending' | 'confirmed'
|
||||
|
||||
export const DINNER_EVENT_TITLE = 'DAP JOHOR 60th Anniversary Celebration'
|
||||
export const DINNER_EVENT_DATE_LABEL = 'Saturday, 30 May 2026'
|
||||
export const DINNER_EVENT_TIME_LABEL = '6:30 PM'
|
||||
export const DINNER_EVENT_VENUE = "Yong Peng's Chee Ann Kor"
|
||||
export interface DinnerEvent {
|
||||
id: string
|
||||
title: string
|
||||
dateLabel: string
|
||||
timeLabel: string
|
||||
venue: string
|
||||
}
|
||||
|
||||
export const TABLE_SEAT_COUNT = 10
|
||||
export interface BookingModeOption {
|
||||
id: string
|
||||
value: BookingMode
|
||||
label: string
|
||||
quantityLabel: string
|
||||
seatsPerUnit: number
|
||||
sortOrder: number
|
||||
}
|
||||
|
||||
export const BOOKING_MODE_OPTIONS = [
|
||||
{
|
||||
value: 'table',
|
||||
label: `Table (${TABLE_SEAT_COUNT} seats)`
|
||||
},
|
||||
{
|
||||
value: 'seat',
|
||||
label: 'Seat'
|
||||
}
|
||||
] satisfies Array<{ value: BookingMode, label: string }>
|
||||
export interface TicketCatalogItem {
|
||||
id: string
|
||||
value: TicketType
|
||||
label: string
|
||||
description: string
|
||||
price: number
|
||||
sortOrder: number
|
||||
}
|
||||
|
||||
export const BOOKING_TICKET_CATALOG = [
|
||||
{
|
||||
value: 'vip',
|
||||
label: 'VIP',
|
||||
description: 'RM150 / seat',
|
||||
price: 150
|
||||
},
|
||||
{
|
||||
value: 'supporter',
|
||||
label: 'Supporter',
|
||||
description: 'RM60 / seat',
|
||||
price: 60
|
||||
}
|
||||
] satisfies Array<{ value: TicketType, label: string, description: string, price: number }>
|
||||
export interface PublicBookingConfig {
|
||||
event: DinnerEvent
|
||||
bookingModes: BookingModeOption[]
|
||||
ticketCatalog: TicketCatalogItem[]
|
||||
}
|
||||
|
||||
export interface PublicBooking {
|
||||
id: string
|
||||
confirmationToken: string
|
||||
receiptToken: string
|
||||
event: DinnerEvent
|
||||
customerName: string
|
||||
customerPhone: string
|
||||
bookingModeId: string | null
|
||||
bookingMode: BookingMode
|
||||
bookingModeLabel: string
|
||||
quantity: number
|
||||
seatCount: number
|
||||
ticketTypeId: string | null
|
||||
ticketType: TicketType
|
||||
ticketLabel: string
|
||||
ticketDescription: string | null
|
||||
unitPrice: number
|
||||
totalPrice: number
|
||||
personInChargeId: string
|
||||
personInChargeName: string
|
||||
personInChargePhoneNumber: string
|
||||
status: BookingStatus
|
||||
statusLabel: string
|
||||
createdAt: string
|
||||
confirmedAt: string | null
|
||||
}
|
||||
@@ -58,15 +64,22 @@ export interface PublicBooking {
|
||||
export interface ReceiptBooking {
|
||||
id: string
|
||||
receiptToken: string
|
||||
event: DinnerEvent
|
||||
customerName: string
|
||||
customerPhone: string
|
||||
bookingModeId: string | null
|
||||
bookingMode: BookingMode
|
||||
bookingModeLabel: string
|
||||
quantity: number
|
||||
seatCount: number
|
||||
ticketTypeId: string | null
|
||||
ticketType: TicketType
|
||||
ticketLabel: string
|
||||
ticketDescription: string | null
|
||||
unitPrice: number
|
||||
totalPrice: number
|
||||
status: BookingStatus
|
||||
statusLabel: string
|
||||
createdAt: string
|
||||
confirmedAt: string | null
|
||||
}
|
||||
@@ -131,32 +144,28 @@ export interface ConfirmBookingResponse {
|
||||
ticketReceiptWhatsApp: WhatsAppDeliveryResult
|
||||
}
|
||||
|
||||
export function isBookingMode(value: string | null | undefined): value is BookingMode {
|
||||
return value === 'table' || value === 'seat'
|
||||
}
|
||||
|
||||
export function isTicketType(value: string | null | undefined): value is TicketType {
|
||||
return value === 'vip' || value === 'supporter'
|
||||
}
|
||||
|
||||
export function isBookingStatus(value: string | null | undefined): value is BookingStatus {
|
||||
return value === 'pending' || value === 'confirmed'
|
||||
}
|
||||
|
||||
export function getBookingModeLabel(value: BookingMode) {
|
||||
return value === 'table' ? `Table (${TABLE_SEAT_COUNT} seats each)` : 'Per seat'
|
||||
}
|
||||
export function getBookingStatusLabel(value: BookingStatus | string, label?: string | null) {
|
||||
if (label) {
|
||||
return label
|
||||
}
|
||||
|
||||
export function getBookingStatusLabel(value: BookingStatus) {
|
||||
return value === 'confirmed' ? 'Confirmed' : 'Pending PIC confirmation'
|
||||
}
|
||||
|
||||
export function getSeatCount(bookingMode: BookingMode, quantity: number) {
|
||||
return bookingMode === 'table' ? quantity * TABLE_SEAT_COUNT : quantity
|
||||
export function getSeatCount(bookingMode: Pick<BookingModeOption, 'seatsPerUnit'> | null | undefined, quantity: number) {
|
||||
return quantity * (bookingMode?.seatsPerUnit ?? 1)
|
||||
}
|
||||
|
||||
export function getTicketCatalogItem(ticketType: TicketType) {
|
||||
return BOOKING_TICKET_CATALOG.find((ticket) => ticket.value === ticketType) ?? null
|
||||
export function getTicketLabel(ticket: Pick<TicketCatalogItem, 'label'> | null | undefined, ticketType: TicketType) {
|
||||
return ticket?.label || ticketType.toUpperCase()
|
||||
}
|
||||
|
||||
export function getBookingTicketLabel(booking: Pick<PublicBooking | ReceiptBooking, 'ticketLabel' | 'ticketType'>) {
|
||||
return booking.ticketLabel || booking.ticketType.toUpperCase()
|
||||
}
|
||||
|
||||
export function formatBookingCurrency(value: number) {
|
||||
|
||||
Reference in New Issue
Block a user