用平板取代昂贵的KDS硬件,实时管理厨房订单队列
* Preview is for reference only. Actual results may vary depending on the AI model, variable values, and tools used.
You are a full-stack development expert proficient in Next.js 16, Tailwind CSS 4, and Supabase. Help me build a 'Kitchen Display & Order Management System (KDS)' from scratch, designed for Malaysian F&B businesses to replace expensive dedicated KDS hardware with an affordable tablet solution.
## Project Details
- Restaurant name: {restaurant_name}
- Restaurant type: {restaurant_type}
- Number of kitchen stations: {num_stations}
- Alert threshold: {alert_threshold_minutes} minutes
- Brand primary color: {primary_color}
## Tech Stack
- Framework: Next.js 16 (App Router)
- Styling: Tailwind CSS 4
- Backend: Supabase (Postgres + Auth + Realtime + Edge Functions)
- Deployment: Vercel
- Real-time: Supabase Realtime (Broadcast + Postgres Changes)
- Audio: Web Audio API
## 1. Database Design (Supabase Postgres)
Create the following complete data model with all SQL migration files:
### 1.1 stations table (Kitchen stations)
```
id: uuid (PK, default gen_random_uuid())
name: text NOT NULL — Station name e.g. 'Hot Kitchen', 'Drinks', 'Grill'
slug: text UNIQUE NOT NULL
display_order: integer DEFAULT 0
categories: text[] — Menu categories this station handles e.g. ARRAY['mains','stir-fry','soups']
is_active: boolean DEFAULT true
created_at: timestamptz DEFAULT now()
```
### 1.2 menu_items table
```
id: uuid (PK, default gen_random_uuid())
name_zh: text NOT NULL — Chinese dish name
name_en: text — English dish name
category: text NOT NULL — 'mains','drinks','snacks','desserts','soups'
station_id: uuid REFERENCES stations(id)
default_prep_minutes: integer DEFAULT 10
is_active: boolean DEFAULT true
created_at: timestamptz DEFAULT now()
```
### 1.3 orders table
```
id: uuid (PK, default gen_random_uuid())
order_number: text UNIQUE NOT NULL — Display number e.g. 'A001'
source: text NOT NULL DEFAULT 'pos' — 'pos','grabfood','foodpanda','shopeefood','walk_in'
status: text NOT NULL DEFAULT 'new' — 'new','preparing','ready','served','cancelled'
priority: integer DEFAULT 0 — 0=normal, 1=rush
table_number: text
customer_name: text
notes: text — Order-level notes
total_items: integer DEFAULT 0
completed_items: integer DEFAULT 0
estimated_ready_at: timestamptz
created_at: timestamptz DEFAULT now()
accepted_at: timestamptz
completed_at: timestamptz
served_at: timestamptz
```
### 1.4 order_items table
```
id: uuid (PK, default gen_random_uuid())
order_id: uuid REFERENCES orders(id) ON DELETE CASCADE
menu_item_id: uuid REFERENCES menu_items(id)
item_name: text NOT NULL — Denormalized dish name
quantity: integer NOT NULL DEFAULT 1
status: text NOT NULL DEFAULT 'pending' — 'pending','cooking','done','cancelled'
station_id: uuid REFERENCES stations(id)
notes: text — Item-level notes e.g. 'no spice', 'extra rice'
started_at: timestamptz
completed_at: timestamptz
created_at: timestamptz DEFAULT now()
```
### 1.5 analytics_events table
```
id: uuid (PK, default gen_random_uuid())
event_type: text NOT NULL — 'order_created','order_completed','item_completed','order_cancelled'
order_id: uuid REFERENCES orders(id)
station_id: uuid REFERENCES stations(id)
wait_seconds: integer
prep_seconds: integer
created_at: timestamptz DEFAULT now()
```
### Required Indexes
```sql
CREATE INDEX idx_orders_status ON orders(status) WHERE status != 'served';
CREATE INDEX idx_orders_created ON orders(created_at DESC);
CREATE INDEX idx_order_items_order ON order_items(order_id);
CREATE INDEX idx_order_items_station ON order_items(station_id) WHERE status != 'done';
CREATE INDEX idx_order_items_status ON order_items(status);
CREATE INDEX idx_analytics_type ON analytics_events(event_type, created_at DESC);
CREATE INDEX idx_menu_items_station ON menu_items(station_id);
```
### RLS Policies
- All tables: authenticated users have full access
- orders and order_items: anon users can INSERT (supports POS/external system order creation)
- analytics_events: anon can INSERT, only authenticated can SELECT
- Wrap auth.uid() calls in (select auth.uid()) for performance optimization
### Enable Supabase Realtime
```sql
ALTER PUBLICATION supabase_realtime ADD TABLE orders;
ALTER PUBLICATION supabase_realtime ADD TABLE order_items;
```
## 2. Application Architecture (Next.js 16 App Router)
### File Structure
```
src/
├── app/
│ ├── layout.tsx # Root layout
│ ├── page.tsx # Login / station selection
│ ├── kitchen/
│ │ ├── layout.tsx # Full-screen kitchen layout (no header)
│ │ ├── page.tsx # All-stations kanban view
│ │ └── [station-slug]/
│ │ └── page.tsx # Single station view
│ ├── counter/
│ │ └── page.tsx # Counter pickup display
│ ├── analytics/
│ │ └── page.tsx # Analytics dashboard
│ └── api/
│ └── v1/
│ ├── orders/
│ │ └── route.ts # POST create, GET list orders
│ └── orders/[id]/
│ ├── route.ts # PATCH update order status
│ └── items/[itemId]/
│ └── route.ts # PATCH update item status
├── components/
│ ├── ui/ # shadcn/ui components
│ ├── kanban-board.tsx # Main kanban component
│ ├── order-card.tsx # Order card
│ ├── order-item-row.tsx # Individual item row within card
│ ├── station-filter.tsx # Station filter bar
│ ├── new-order-alert.tsx # New order audio + visual alert
│ ├── timer-badge.tsx # Wait time badge (color-coded)
│ ├── estimated-wait.tsx # Estimated wait time display
│ ├── analytics-cards.tsx # Analytics summary cards
│ └── analytics-charts.tsx # Analytics charts
├── hooks/
│ ├── use-realtime-orders.ts # Supabase Realtime subscription
│ ├── use-audio-alert.ts # Audio alert hook
│ └── use-timer.ts # Timer hook
├── lib/
│ ├── supabase/
│ │ ├── client.ts # Browser Supabase client
│ │ ├── server.ts # Server Supabase client
│ │ └── admin.ts # Service Role client
│ ├── order-logic.ts # Order business logic
│ ├── time-utils.ts # Time calculation utilities
│ └── sounds.ts # Sound management
└── types/
└── database.ts # Supabase generated types
```
## 3. Core Business Logic
### 3.1 Kanban State Transitions
```
New (new) → Preparing (preparing) → Ready (ready) → Served (served)
↘ Cancelled (cancelled)
```
Rules:
- When ANY order_item status changes to 'cooking', the order auto-transitions to 'preparing'
- When ALL order_items status become 'done', the order auto-transitions to 'ready'
- Completed orders in 'ready' column are dimmed after 5 minutes
- 'served' orders are removed from kanban, moved to history
### 3.2 Wait Time Color Coding
```
Green (normal): 0 to {alert_threshold_minutes}×0.5 minutes
Yellow (warning): {alert_threshold_minutes}×0.5 to {alert_threshold_minutes} minutes
Red (overdue): Over {alert_threshold_minutes} minutes
Red flashing (critical): Over {alert_threshold_minutes}×1.5 minutes
```
### 3.3 Estimated Wait Time Algorithm
```
Estimated time = Number of 'pending'+'cooking' orders at station × Station average completion time
Average completion time = Mean prep time of orders completed in last 2 hours
Fallback to menu_item.default_prep_minutes if no history
```
### 3.4 Audio Alerts
- New order arrives: Play a crisp 'ding-dong' notification sound
- Order overdue warning: Play an urgent alert sound
- Use Web Audio API to generate sounds (no external audio files needed)
- Provide mute toggle button
### 3.5 Auto Order Numbering
```
Format: Letter prefix + 3-digit number e.g. A001, A002 ... A999, B001 ...
Auto-resets daily (based on created_at date)
Generate via Postgres function:
CREATE OR REPLACE FUNCTION generate_order_number()
RETURNS text AS $$ ... $$ LANGUAGE plpgsql;
```
## 4. UI/UX Design Specifications
### Design Principles
- Tablet-first (1024×768 minimum resolution)
- Large buttons and text (kitchen environment — fingers may be wet/greasy)
- Minimum touch target: 48×48px
- High contrast colors (bright kitchen lighting)
- Key information visible without scrolling
### Kanban View Layout (/kitchen)
```
┌─────────────────────────────────────────────────────┐
│ {restaurant_name} KDS [Station Filter] [Mute] [Time] │
├──────────┬──────────┬──────────┬──────────────────────┤
│ New │ Preparing │ Ready │ Est. Wait: 12min │
│ (3) │ (5) │ (2) │ │
├──────────┼──────────┼──────────┤ │
│ ┌──────┐ │ ┌──────┐ │ ┌──────┐ │ Today Stats │
│ │A023 │ │ │A020 │ │ │A018 │ │ Total Orders: 145 │
│ │Tbl:5 │ │ │Delvry│ │ │Tbl:3 │ │ Avg Prep: 8min │
│ │ │ │ │12:05 │ │ │ │ │ Overdue: 5% │
│ │Nasi │ │ │ │ │ │Done │ │ │
│ │CKT │ │ │BKT │ │ │ │ │ │
│ │Teh x2│ │ │Chkn │ │ │ │ │ │
│ └──────┘ │ └──────┘ │ └──────┘ │ │
└──────────┴──────────┴──────────┴──────────────────────┘
```
### Order Card Design
- Top: Order number (large bold) + source icon (POS/GrabFood etc) + timer
- Middle: Item list with checkboxes to mark individual items done
- Bottom: Table number/customer name + notes (yellow highlight)
- Card border color changes with wait time (green→yellow→red)
- Rush orders: Red top border + flashing 'RUSH' badge
### Counter Display (/counter)
```
┌─────────────────────────────────┐
│ {restaurant_name} │
│ Please watch for your order │
├────────────────┬────────────────┤
│ Preparing │ Ready │
│ A023 │ ★ A018 │
│ A022 │ ★ A017 │
│ A020 │ │
│ │ │
│ Est. wait ~12min│ │
└────────────────┴────────────────┘
```
### Color Scheme
- Primary: {primary_color}
- Background: #F8FAFC (light gray-white)
- New orders column: #EFF6FF (light blue)
- Preparing column: #FFF7ED (light orange)
- Ready column: #F0FDF4 (light green)
- Overdue warning: #FEF2F2 (light red)
- Text: #0F172A (dark)
- Cards: White with 1px border #E2E8F0
### Typography
- Order number: 2rem, bold
- Dish names: 1.25rem
- Notes: 1rem, italic
- Timer: 1.5rem, monospace
- Stats numbers: 2.5rem, bold
## 5. Realtime Implementation
### use-realtime-orders.ts Hook
```typescript
// Subscribe to orders and order_items table changes via Supabase Realtime
// Trigger audio alert on new order INSERT
// Auto-update kanban on status changes
// Implement optimistic updates to avoid UI lag
// Auto-reconnect on disconnection
```
### Subscription Strategy
- Subscribe to orders table INSERT and UPDATE events
- Subscribe to order_items table UPDATE events
- Use Supabase channel filter to filter by station
- Implement heartbeat to detect connection status
## 6. API Design (Integration-Ready)
### POST /api/v1/orders — Create order
```json
{
'order': {
'source': 'grabfood',
'table_number': '5',
'customer_name': 'Mr. Zhang',
'notes': 'No spice',
'priority': 0,
'items': [
{ 'menu_item_id': 'uuid', 'quantity': 2, 'notes': 'Less sugar' },
{ 'menu_item_id': 'uuid', 'quantity': 1 }
]
}
}
```
### PATCH /api/v1/orders/[id] — Update order status
```json
{ 'status': 'preparing' }
```
### PATCH /api/v1/orders/[id]/items/[itemId] — Update item status
```json
{ 'status': 'done' }
```
### GET /api/v1/orders?status=new,preparing&station=hot-kitchen
Returns filtered order list
## 7. Sample Seed Data (Malaysian Dishes)
### Stations
```sql
INSERT INTO stations (name, slug, categories) VALUES
('Hot Kitchen', 'hot-kitchen', ARRAY['mains','stir-fry','soups']),
('Drinks Station', 'drinks', ARRAY['drinks','desserts']),
('Grill Station', 'grill', ARRAY['grill','snacks']);
```
### Menu Items
```sql
INSERT INTO menu_items (name_zh, name_en, category, station_id, default_prep_minutes) VALUES
('Nasi Lemak', 'Nasi Lemak', 'mains', (SELECT id FROM stations WHERE slug='hot-kitchen'), 8),
('Char Kway Teow', 'Char Kway Teow', 'stir-fry', (SELECT id FROM stations WHERE slug='hot-kitchen'), 10),
('Bak Kut Teh', 'Bak Kut Teh', 'soups', (SELECT id FROM stations WHERE slug='hot-kitchen'), 15),
('Hainanese Chicken Rice', 'Hainanese Chicken Rice', 'mains', (SELECT id FROM stations WHERE slug='hot-kitchen'), 8),
('Hokkien Mee', 'Hokkien Mee', 'stir-fry', (SELECT id FROM stations WHERE slug='hot-kitchen'), 12),
('Curry Mee', 'Curry Mee', 'soups', (SELECT id FROM stations WHERE slug='hot-kitchen'), 10),
('Pan Mee', 'Pan Mee', 'mains', (SELECT id FROM stations WHERE slug='hot-kitchen'), 10),
('Wonton Noodles', 'Wonton Noodles', 'mains', (SELECT id FROM stations WHERE slug='hot-kitchen'), 8),
('Teh Tarik', 'Teh Tarik', 'drinks', (SELECT id FROM stations WHERE slug='drinks'), 3),
('Milo Dinosaur', 'Milo Dinosaur', 'drinks', (SELECT id FROM stations WHERE slug='drinks'), 3),
('Barley Water', 'Barley Water', 'drinks', (SELECT id FROM stations WHERE slug='drinks'), 2),
('Iced Kopi', 'Iced Kopi', 'drinks', (SELECT id FROM stations WHERE slug='drinks'), 2),
('Bubble Tea', 'Bubble Tea', 'drinks', (SELECT id FROM stations WHERE slug='drinks'), 4),
('ABC Ice Kacang', 'ABC Ice Kacang', 'desserts', (SELECT id FROM stations WHERE slug='drinks'), 5),
('Chicken Satay', 'Chicken Satay', 'grill', (SELECT id FROM stations WHERE slug='grill'), 12),
('Beef Satay', 'Beef Satay', 'grill', (SELECT id FROM stations WHERE slug='grill'), 12),
('Grilled Chicken Wings', 'Grilled Chicken Wings', 'grill', (SELECT id FROM stations WHERE slug='grill'), 15),
('Prawn Fritters', 'Prawn Fritters', 'snacks', (SELECT id FROM stations WHERE slug='grill'), 6);
```
### Sample Orders
```sql
-- Order 1: Dine-in
INSERT INTO orders (order_number, source, status, table_number, total_items)
VALUES ('A001', 'pos', 'preparing', '5', 3);
-- Order 2: GrabFood delivery
INSERT INTO orders (order_number, source, status, customer_name, notes, total_items)
VALUES ('A002', 'grabfood', 'new', 'Ms. Lee', 'Extra peanut sauce please', 2);
-- Order 3: Rush order
INSERT INTO orders (order_number, source, status, table_number, priority, total_items)
VALUES ('A003', 'walk_in', 'new', 'VIP1', 1, 4);
```
## 8. Analytics Dashboard (/analytics)
Display the following data (with date range selector):
1. **Summary Cards**
- Total orders today
- Average prep time
- Overdue order percentage
- Busiest hour
2. **Charts**
- Orders per hour line chart
- Average prep time by station bar chart
- Order source pie chart (POS vs delivery platforms)
- Daily trend chart (past 7 days)
3. **Leaderboards**
- Most popular dishes Top 10
- Slowest prep dishes Top 5 (optimization needed)
## 9. Deployment & Environment Configuration
### Environment Variables
```
NEXT_PUBLIC_SUPABASE_URL=
NEXT_PUBLIC_SUPABASE_ANON_KEY=
SUPABASE_SERVICE_ROLE_KEY=
```
### Vercel Deployment Notes
- Enable Serverless Functions
- Set Region to Singapore (sin1) for lower latency
## 10. Implementation Steps
Please implement in this order:
1. Supabase database: Create all tables, indexes, RLS policies, functions
2. Seed data: Insert stations, menu items, sample orders
3. Next.js project init: Configure Tailwind CSS 4, fonts, layouts
4. Supabase client setup: client.ts, server.ts, admin.ts
5. Type definitions: database.ts type file
6. Kanban components: kanban-board, order-card, order-item-row
7. Realtime Hook: use-realtime-orders real-time subscription
8. Audio alerts: use-audio-alert Hook
9. Wait time: timer-badge, estimated-wait components
10. Station filter: station-filter component
11. API routes: orders CRUD
12. Counter display: counter page
13. Analytics dashboard: analytics page
14. Responsive optimization: Ensure perfect tablet view
15. Testing & optimization: Performance tuning, offline handling
Please start implementation now, beginning with the database design. Complete each module and wait for my confirmation before proceeding to the next.