替代 Eatigo/OpenTable 的马来西亚本地化餐厅预约管理系统
* Preview is for reference only. Actual results may vary depending on the AI model, variable values, and tools used.
You are a senior full-stack engineer. Build a complete **Table Reservation System** designed for Malaysian F&B businesses, replacing Eatigo/OpenTable.
## Project Overview
Restaurant name: {restaurant_name}
Restaurant type: {restaurant_type}
Number of tables: {num_tables}
Max party size: {max_party_size}
Operating hours: {operating_hours}
Brand primary color: {primary_color}
Booking lead days: {booking_lead_days}
## Tech Stack
- **Frontend**: Next.js 16 (App Router) + Tailwind CSS 4
- **Backend/Database**: Supabase (Postgres + Auth + Realtime + Edge Functions)
- **Deployment**: Vercel
- **SMS/WhatsApp**: Simulated (console.log + toast notifications, with API interface stub)
## Data Model (Supabase Postgres)
### restaurants table
- id (uuid, PK), name (text), type (text), phone (text), address (text)
- operating_hours (jsonb) — format: {"mon": {"open": "11:00", "close": "22:00"}, ...}
- default_slot_duration (int, default 90 min), primary_color (text)
- created_at (timestamptz)
### tables table
- id (uuid, PK), restaurant_id (FK → restaurants), table_number (text)
- min_capacity (int), max_capacity (int), zone (text, e.g. "indoor"/"patio"/"private room")
- is_active (boolean, default true)
- Indexes: restaurant_id, zone
### time_slots table
- id (uuid, PK), restaurant_id (FK), date (date), start_time (time), end_time (time)
- total_capacity (int), booked_count (int, default 0)
- is_blocked (boolean, default false), block_reason (text)
- Indexes: (restaurant_id, date, start_time) UNIQUE
### reservations table
- id (uuid, PK), restaurant_id (FK), table_id (FK → tables, nullable)
- customer_name (text), customer_phone (text, +60 format)
- customer_email (text, nullable), party_size (int)
- date (date), start_time (time), end_time (time)
- status (enum: 'pending' → 'confirmed' → 'seated' → 'completed' | 'cancelled' | 'no_show')
- special_requests (text), source (enum: 'online' | 'phone' | 'walk_in')
- confirmation_code (text, 6-char alphanumeric uppercase), confirmed_at (timestamptz)
- no_show_at (timestamptz), created_at (timestamptz)
- Indexes: (restaurant_id, date, status), customer_phone, confirmation_code UNIQUE
### waitlist table
- id (uuid, PK), restaurant_id (FK), customer_name (text)
- customer_phone (text), party_size (int)
- estimated_wait_minutes (int), status (enum: 'waiting' → 'notified' → 'seated' | 'left')
- queue_position (int), joined_at (timestamptz), seated_at (timestamptz)
- Indexes: (restaurant_id, status, queue_position)
### blocked_dates table
- id (uuid, PK), restaurant_id (FK), date (date)
- reason (text, e.g. "Chinese New Year closure" / "Private event")
- Indexes: (restaurant_id, date) UNIQUE
### no_show_records table
- id (uuid, PK), customer_phone (text), restaurant_id (FK)
- reservation_id (FK → reservations), recorded_at (timestamptz)
- View: aggregate no_show_count by customer_phone
## RLS Security Policies
- **Public read**: restaurants (basic info), time_slots (availability)
- **Public write**: reservations (INSERT only, customer bookings), waitlist (INSERT only)
- **Admin full access**: All tables CRUD, via auth.uid() matching admins table
- Wrap auth calls with `(select auth.uid())` to avoid per-row function evaluation
## Business Logic
### Overbooking Prevention
1. Before booking, verify time_slots.booked_count < time_slots.total_capacity
2. Use Postgres transaction + SELECT FOR UPDATE to lock the time slot row
3. After inserting reservation, auto-increment booked_count via trigger
4. On cancellation, decrement booked_count
### Confirmation Flow
1. Customer submits booking → status='pending' → generate confirmation_code
2. Simulate WhatsApp confirmation message (with code, date, time, party size)
3. Customer clicks confirmation link → status='confirmed'
4. Send reminder 2 hours before reservation (simulated)
5. If 30 min late → admin can mark as no_show
### No-Show Policy
- 3 cumulative no-shows → show warning at booking time
- 5 cumulative no-shows → require deposit (show UI prompt, actual payment not implemented)
- Admin can manually clear no-show records
### Walk-in Waitlist Management
- Walk-in guests join waitlist → auto-assign queue_position
- When a table opens → notify first in queue (simulated WhatsApp)
- If no response within 10 min → auto-skip, notify next person
- Use Supabase Realtime to subscribe to waitlist changes, update admin view live
## Frontend Pages (Customer — Mobile-First)
### /book — Booking Page
- Step-by-step form (Steps 1→4):
1. Select date (calendar widget, disable blocked_dates, max {booking_lead_days} days ahead)
2. Select time slot (grid buttons: grey=full, green=available, show remaining count)
3. Enter party size + special requests (allergies, high chair, birthday cake, etc.)
4. Enter contact info (name, +60 phone number, optional email)
- Fixed bottom "Confirm Booking" button in {primary_color}
- On success: show confirmation code + simulated WhatsApp message preview
### /book/status/[code] — Booking Status Page
- Look up reservation by confirmation code
- Display: date, time, party size, status badge
- Allow cancellation (if more than 2 hours before reservation)
### /waitlist — Walk-in Waitlist Page
- Enter name, phone, party size
- After joining: show queue position + estimated wait time (real-time updates)
- Supabase Realtime subscription for position changes
## Frontend Pages (Admin — Desktop-First)
### /admin/dashboard — Dashboard
- Today's overview cards: total bookings, confirmed, seated, no-shows
- Current waitlist count + average wait time
- Weekly booking trend chart (simple SVG or chart component)
### /admin/reservations — Reservation Management
- Table view with date filter and status filter
- Columns: confirmation code, name, phone, party size, time, status, actions
- Actions: confirm, mark seated, mark completed, mark no-show, cancel
- Click row to expand details (special requests, historical no-show count)
### /admin/floor-plan — Floor Management
- Table list (table-based, not graphical)
- Per table: table number, capacity, zone, current status (vacant/occupied/reserved)
- Simple drag-to-assign tables to reservations
### /admin/settings — Settings
- Operating hours editor (per day of week)
- Time slot capacity config (e.g. lunch 30 pax, dinner 40 pax)
- Blocked dates management (add/remove blocked_dates)
- Notification template editor (WhatsApp message templates)
## Malaysian Localization
- Bilingual UI: Simplified Chinese primary, English toggle
- Phone format: +60 prefix, validate Malaysian phone numbers
- Currency: MYR (if deposit display is needed)
- Timezone: Asia/Kuala_Lumpur (UTC+8)
- Date format: YYYY年MM月DD日 / DD/MM/YYYY
- Holiday awareness: Chinese New Year, Hari Raya, Deepavali, etc.
## Sample Data
Generate the following test data:
- Restaurant: {restaurant_name} ({restaurant_type}), address: Jalan Alor, Bukit Bintang, KL
- Tables: 12 total (4x 2-pax, 6x 4-pax, 2x 8-pax private room)
- Today's reservations: 8 bookings (mixed statuses)
- Waitlist: 3 people currently waiting
- Historical no-show records: 2 entries
- This week's blocked_date: 1 day ("Private event booking")
## Supabase Realtime Configuration
- Subscribe to reservations table changes → live-update admin reservation list
- Subscribe to waitlist table changes → live-update customer queue position
- Subscribe to time_slots table changes → live-update booking page availability
## Key Requirements
1. Mobile customer experience must be smooth (< 3s load, large tap targets, clear steps)
2. Admin interface supports tablet and desktop
3. All database operations use transactions to prevent race conditions
4. Code comments in bilingual Chinese/English
5. Use Tailwind CSS 4 + shadcn/ui components
6. Deploy to Vercel, environment variables via .env.local