import type { AppLocale } from './i18n' export type BookingMode = string export type TicketType = string export type BookingStatus = 'pending' | 'confirmed' export type PaymentMethod = 'cash' | 'bank' 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 locale: AppLocale 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 paymentMethod: PaymentMethod transactionDocument: BookingTransactionDocument | null remark: string | null status: BookingStatus statusLabel: string createdAt: string confirmedAt: string | null } export interface BookingTransactionDocument { originalName: string mimeType: string size: number uploadedAt: string url: string } export interface ReceiptBooking { id: string receiptToken: string event: DinnerEvent customerName: string customerPhone: string locale: AppLocale 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 interface CancelBookingConfirmationResponse { booking: PublicBooking alreadyPending: boolean } export interface TransferBookingPicResponse { booking: PublicBooking } export interface UpdateBookingDetailsResponse { booking: PublicBooking } export interface DeleteBookingResponse { booking: PublicBooking } export function isBookingStatus(value: string | null | undefined): value is BookingStatus { return value === 'pending' || value === 'confirmed' } export function isPaymentMethod(value: string | null | undefined): value is PaymentMethod { return value === 'cash' || value === 'bank' } export function getBookingStatusLabel(value: BookingStatus | string, label?: string | null, locale: AppLocale = 'en') { if (label && locale !== 'zh') { return label } if (locale === 'zh') { return value === 'confirmed' ? '已确认' : '等待负责人确认' } 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, locale: AppLocale = 'en') { return new Intl.NumberFormat(locale === 'zh' ? 'zh-MY' : 'en-MY', { style: 'currency', currency: 'MYR', minimumFractionDigits: 0, maximumFractionDigits: 0 }).format(value) } export function getSeatLabel(seatNumber: number, locale: AppLocale = 'en') { return locale === 'zh' ? `座位 ${seatNumber}` : `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 } }