export type BookingMode = string export type TicketType = string export type BookingStatus = 'pending' | 'confirmed' export interface DinnerEvent { id: string title: string dateLabel: string timeLabel: string venue: string } export interface BookingModeOption { id: string value: BookingMode label: string quantityLabel: string seatsPerUnit: number sortOrder: number } export interface TicketCatalogItem { id: string value: TicketType label: string description: string price: number sortOrder: 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 } 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 } export interface PublicBookingSeat { id: string seatNumber: number seatToken: string recipientName: string | null recipientPhone: string | null sharedAt: string | null createdAt: string updatedAt: string } export interface PublicBookingSeatWithUrl extends PublicBookingSeat { seatUrl: string } export interface PublicBookingReceipt { booking: ReceiptBooking receiptUrl: string seats: PublicBookingSeatWithUrl[] } export interface PublicSeatReceipt { booking: ReceiptBooking seat: PublicBookingSeatWithUrl receiptUrl: string } export interface BookingCapacitySettings { totalSeats: number | null updatedAt: string | null } export interface BookingInventorySummary { totalSeats: number | null soldSeats: number pendingSeats: number leftSeats: number | null } export interface CreateBookingResponse { booking: PublicBooking confirmationUrl: string whatsappUrl: string } export interface WhatsAppDeliveryResult { sent: boolean skipped: boolean recipientPhone: string apiRecipientPhone: string messageId?: string error?: string } export interface ConfirmBookingResponse { booking: PublicBooking alreadyConfirmed: boolean ticketReceiptWhatsApp: WhatsAppDeliveryResult } export function isBookingStatus(value: string | null | undefined): value is BookingStatus { return value === 'pending' || value === 'confirmed' } export function getBookingStatusLabel(value: BookingStatus | string, label?: string | null) { if (label) { return label } return value === 'confirmed' ? 'Confirmed' : 'Pending PIC confirmation' } export function getSeatCount(bookingMode: Pick | null | undefined, quantity: number) { return quantity * (bookingMode?.seatsPerUnit ?? 1) } export function getTicketLabel(ticket: Pick | null | undefined, ticketType: TicketType) { return ticket?.label || ticketType.toUpperCase() } export function getBookingTicketLabel(booking: Pick) { return booking.ticketLabel || booking.ticketType.toUpperCase() } export function formatBookingCurrency(value: number) { return new Intl.NumberFormat('en-MY', { style: 'currency', currency: 'MYR', minimumFractionDigits: 0, maximumFractionDigits: 0 }).format(value) } export function getSeatLabel(seatNumber: number) { return `Seat ${seatNumber}` } export function calculateBookingInventorySummary( bookings: Pick[], settings: BookingCapacitySettings ): BookingInventorySummary { const soldSeats = bookings .filter((booking) => booking.status === 'confirmed') .reduce((total, booking) => total + booking.seatCount, 0) const pendingSeats = bookings .filter((booking) => booking.status === 'pending') .reduce((total, booking) => total + booking.seatCount, 0) const leftSeats = settings.totalSeats === null ? null : Math.max(settings.totalSeats - soldSeats, 0) return { totalSeats: settings.totalSeats, soldSeats, pendingSeats, leftSeats } }