取代纸质病历和电话预约,为马来西亚私人诊所打造完整的数字化管理系统
* 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 developer proficient in Next.js 16, Tailwind CSS 4, and Supabase. Build a complete Clinic Appointment and Medical Records System for me.
## Clinic Information
- Clinic Name: {clinic_name}
- Clinic Type: {clinic_type} (e.g., General Practice / Specialist / Dental)
- Number of Doctors: {num_doctors}
- Consultation Fee: RM {consultation_fee}
- Operating Hours: {operating_hours} (e.g., Mon-Fri 9am-5pm, Sat 9am-1pm)
- Brand Primary Color: {primary_color}
## Tech Stack
- Frontend: Next.js 16 (App Router) + TypeScript + Tailwind CSS 4
- Backend/Database: Supabase (PostgreSQL + Auth + RLS + Realtime)
- Auth: Supabase Auth (doctors/staff use email login, patients use phone OTP)
- Deployment: Vercel
- PDF Generation: @react-pdf/renderer for MC and prescription labels
- Printing: Support 80mm thermal receipt printer
## Database Schema (Supabase PostgreSQL)
### Users and Permissions
```sql
-- Clinic information
create table clinics (
id uuid primary key default gen_random_uuid(),
name text not null,
clinic_type text not null,
registration_no text unique, -- MOH registration number
address text not null,
phone text not null,
email text,
logo_url text,
letterhead_config jsonb default '{}',
operating_hours jsonb not null,
consultation_fee decimal(10,2) default 35.00,
created_at timestamptz default now()
);
-- Staff table (doctors, nurses, receptionists)
create table staff (
id uuid primary key default gen_random_uuid(),
user_id uuid references auth.users(id),
clinic_id uuid references clinics(id),
full_name text not null,
role text not null check (role in ('doctor', 'nurse', 'receptionist', 'admin')),
mmc_no text, -- Malaysian Medical Council number (required for doctors)
specialty text,
phone text,
is_active boolean default true,
created_at timestamptz default now()
);
create index idx_staff_clinic on staff(clinic_id);
create index idx_staff_user on staff(user_id);
-- Patients table
create table patients (
id uuid primary key default gen_random_uuid(),
user_id uuid references auth.users(id),
clinic_id uuid references clinics(id),
ic_number text not null, -- MyKad IC number (e.g., 880515-14-5678)
full_name text not null,
name_zh text, -- Chinese name
gender text check (gender in ('male', 'female')),
date_of_birth date not null,
phone text not null,
email text,
address text,
emergency_contact_name text,
emergency_contact_phone text,
blood_type text check (blood_type in ('A+', 'A-', 'B+', 'B-', 'AB+', 'AB-', 'O+', 'O-', 'unknown')),
allergies text[] default '{}',
chronic_conditions text[] default '{}',
medical_notes text,
insurance_provider text,
insurance_policy_no text,
panel_company text,
panel_employee_id text,
is_active boolean default true,
created_at timestamptz default now(),
updated_at timestamptz default now()
);
create unique index idx_patients_ic_clinic on patients(ic_number, clinic_id);
create index idx_patients_clinic on patients(clinic_id);
create index idx_patients_phone on patients(phone);
```
### Appointment Management
```sql
-- Doctor schedules
create table doctor_schedules (
id uuid primary key default gen_random_uuid(),
staff_id uuid references staff(id),
clinic_id uuid references clinics(id),
day_of_week int not null check (day_of_week between 0 and 6),
start_time time not null,
end_time time not null,
slot_duration_minutes int default 15,
max_patients_per_slot int default 1,
is_active boolean default true
);
create index idx_schedules_staff on doctor_schedules(staff_id);
-- Schedule overrides (leave/overtime)
create table schedule_overrides (
id uuid primary key default gen_random_uuid(),
staff_id uuid references staff(id),
override_date date not null,
is_available boolean default false,
start_time time,
end_time time,
reason text
);
-- Appointments
create table appointments (
id uuid primary key default gen_random_uuid(),
clinic_id uuid references clinics(id),
patient_id uuid references patients(id),
doctor_id uuid references staff(id),
appointment_date date not null,
appointment_time time not null,
duration_minutes int default 15,
status text default 'booked' check (status in ('booked', 'confirmed', 'checked_in', 'in_consultation', 'completed', 'cancelled', 'no_show')),
visit_type text default 'walk_in' check (visit_type in ('walk_in', 'appointment', 'follow_up', 'emergency')),
chief_complaint text,
notes text,
queue_number int,
cancelled_at timestamptz,
cancel_reason text,
created_at timestamptz default now()
);
create index idx_appointments_date on appointments(clinic_id, appointment_date);
create index idx_appointments_patient on appointments(patient_id);
create index idx_appointments_doctor on appointments(doctor_id, appointment_date);
```
### Medical Records and Diagnosis
```sql
-- Consultation records
create table consultations (
id uuid primary key default gen_random_uuid(),
appointment_id uuid references appointments(id),
clinic_id uuid references clinics(id),
patient_id uuid references patients(id),
doctor_id uuid references staff(id),
consultation_date timestamptz default now(),
vital_signs jsonb default '{}',
subjective text,
objective text,
assessment text,
plan text,
diagnosis_codes text[] default '{}',
diagnosis_descriptions text[] default '{}',
follow_up_date date,
follow_up_notes text,
is_mc_issued boolean default false,
mc_days int default 0,
referral_to text,
referral_notes text,
created_at timestamptz default now(),
updated_at timestamptz default now()
);
create index idx_consultations_patient on consultations(patient_id);
create index idx_consultations_doctor on consultations(doctor_id);
create index idx_consultations_date on consultations(clinic_id, consultation_date);
-- Medical Certificates
create table medical_certificates (
id uuid primary key default gen_random_uuid(),
consultation_id uuid references consultations(id),
clinic_id uuid references clinics(id),
patient_id uuid references patients(id),
doctor_id uuid references staff(id),
mc_number text unique not null,
start_date date not null,
end_date date not null,
total_days int not null,
diagnosis text not null,
remarks text,
is_printed boolean default false,
printed_at timestamptz,
created_at timestamptz default now()
);
create index idx_mc_patient on medical_certificates(patient_id);
create index idx_mc_number on medical_certificates(mc_number);
```
### Drugs and Prescriptions
```sql
-- Drug database
create table drugs (
id uuid primary key default gen_random_uuid(),
clinic_id uuid references clinics(id),
generic_name text not null,
brand_name text,
name_zh text,
category text,
dosage_form text,
strength text,
unit text default 'tablet',
unit_price decimal(10,2) not null,
cost_price decimal(10,2),
stock_quantity int default 0,
reorder_level int default 50,
expiry_date date,
supplier text,
is_poison_a boolean default false,
is_poison_b boolean default false,
contraindications text[] default '{}',
interactions text[] default '{}',
common_dosage text,
storage_instructions text,
is_active boolean default true,
created_at timestamptz default now()
);
create index idx_drugs_clinic on drugs(clinic_id);
create index idx_drugs_generic on drugs(generic_name);
-- Prescription items
create table prescriptions (
id uuid primary key default gen_random_uuid(),
consultation_id uuid references consultations(id),
drug_id uuid references drugs(id),
patient_id uuid references patients(id),
dosage text not null,
frequency text not null,
duration_days int not null,
total_quantity int not null,
instructions text,
route text default 'oral',
is_dispensed boolean default false,
dispensed_at timestamptz,
dispensed_by uuid references staff(id),
created_at timestamptz default now()
);
create index idx_prescriptions_consultation on prescriptions(consultation_id);
create index idx_prescriptions_drug on prescriptions(drug_id);
-- Drug inventory log
create table drug_inventory_log (
id uuid primary key default gen_random_uuid(),
drug_id uuid references drugs(id),
clinic_id uuid references clinics(id),
change_type text check (change_type in ('purchase', 'dispensed', 'adjustment', 'expired', 'returned')),
quantity_change int not null,
reference_id uuid,
notes text,
performed_by uuid references staff(id),
created_at timestamptz default now()
);
create index idx_inventory_drug on drug_inventory_log(drug_id);
```
### Billing
```sql
-- Bills
create table bills (
id uuid primary key default gen_random_uuid(),
consultation_id uuid references consultations(id),
clinic_id uuid references clinics(id),
patient_id uuid references patients(id),
bill_number text unique not null,
bill_date timestamptz default now(),
subtotal decimal(10,2) default 0,
tax_amount decimal(10,2) default 0,
discount_amount decimal(10,2) default 0,
total_amount decimal(10,2) default 0,
payment_method text check (payment_method in ('cash', 'card', 'ewallet', 'panel', 'insurance', 'online_transfer')),
payment_status text default 'pending' check (payment_status in ('pending', 'paid', 'partial', 'panel_claim', 'void')),
panel_company text,
panel_claim_amount decimal(10,2) default 0,
patient_copay decimal(10,2) default 0,
paid_at timestamptz,
receipt_printed boolean default false,
notes text,
created_at timestamptz default now()
);
create index idx_bills_patient on bills(patient_id);
create index idx_bills_clinic_date on bills(clinic_id, bill_date);
-- Bill line items
create table bill_items (
id uuid primary key default gen_random_uuid(),
bill_id uuid references bills(id),
item_type text check (item_type in ('consultation', 'medication', 'procedure', 'injection', 'lab_test', 'mc_fee', 'other')),
description text not null,
quantity int default 1,
unit_price decimal(10,2) not null,
total_price decimal(10,2) not null,
drug_id uuid references drugs(id),
created_at timestamptz default now()
);
create index idx_bill_items_bill on bill_items(bill_id);
```
### RLS Policies
```sql
-- Patients can only see their own records
create policy patients_own_records on patients for select using (user_id = (select auth.uid()));
-- Staff can access all records for their clinic
create policy staff_clinic_access on patients for select using (
clinic_id in (select clinic_id from staff where user_id = (select auth.uid()))
);
-- Same pattern for appointments, consultations, prescriptions, bills, medical_certificates
```
## Business Logic Requirements
### Appointment Logic
1. Auto-generate available time slots from doctor_schedules, excluding schedule_overrides
2. Each slot has a capacity limit (max_patients_per_slot), fully booked slots are unavailable
3. Patients can book up to 30 days in advance, minimum 2 hours before appointment time
4. Cancellation requires 4 hours notice, otherwise marked as no_show
5. Walk-in patients get auto-assigned queue numbers (arrival order)
6. Status flow: booked -> confirmed -> checked_in -> in_consultation -> completed
7. Use Supabase Realtime for live waiting room updates
### MC Generation Rules
1. Auto-increment MC number: MC-{YYYY}-{000001}
2. MC must include: clinic letterhead (name, address, phone, MOH reg no), patient name, IC number, diagnosis, MC days, start/end dates, doctor signature, MMC number
3. MC day limit: GP clinic max 14 days, beyond requires specialist referral
4. One MC per consultation (prevent duplicates)
5. PDF generation for printing, A5 paper format
6. Diagnosis wording should be concise and professional (e.g., Upper Respiratory Tract Infection)
### Drug Management Logic
1. Auto-check patient allergy records when prescribing, show red warning on match
2. Drug interaction check: cross-reference interactions field within same prescription
3. Show yellow warning when stock is below reorder_level
4. Auto-deduct inventory on dispensing with inventory_log entry
5. Quick prescription templates for common conditions (e.g., Cold Pack = Paracetamol + Chlorpheniramine + Bromhexine)
6. Drug label printing: patient name, drug name, dosage instructions, expiry date
### Billing Logic
1. Auto-generate bill on consultation completion: consultation fee + medication + procedures
2. Panel patients: show company name, separate panel-covered amount and patient copay
3. Multiple payment methods: cash, card, e-wallet (Touch n Go, GrabPay), panel, insurance
4. Receipt number format: INV-{YYYY}-{000001}
5. Receipt supports thermal printing (80mm width) and A4 printing
6. Daily report auto-summary: total revenue, payment method breakdown, panel receivables
## Pages and UI Design
### 1. Patient Portal
**Booking Page /book**
- Select doctor (show photo, specialty, available slots)
- Calendar date picker, time slots in grid (green=available, gray=full, red=closed)
- Enter chief complaint (dropdown for common symptoms + free text)
- Confirmation with SMS/WhatsApp reminder
**My Records /my-records**
- Timeline view: all consultations in reverse chronological order
- Each record shows: date, doctor, diagnosis, prescription, MC (if any)
- Download MC PDF and receipt PDF
### 2. Receptionist Dashboard
**Today View /dashboard**
- Left: today appointment list (sorted by time), filterable by status
- Right: queue board (large font showing current number, waiting count)
- Top stats cards: total appointments, seen, waiting, cancelled
- Walk-in quick register: scan MyKad or manual IC input, auto-lookup or create patient
- One-click check-in button
**Patient Registration /patients/register**
- MyKad IC input auto-extracts: date of birth (first 6 digits), state (middle 2 digits), gender (last digit: odd=male, even=female)
- Allergy tags input (multi-select common allergens + custom)
- Chronic conditions multi-select: Diabetes, Hypertension, Asthma, Heart Disease, Kidney Disease, etc.
- Panel info: company name + employee ID
### 3. Doctor Consultation View
**Consultation /consultation/[id]**
- Three-column layout:
- Left (20%): patient info card, allergy warning (red banner), chronic conditions, last 3 consultations summary
- Center (50%): SOAP form (Subjective/Objective/Assessment/Plan sections), vital signs input
- Right (30%): prescribing panel (drug search, dosage/frequency/duration selection, auto-calculate total), prescription preview
- Bottom action bar: issue MC, generate bill, referral, schedule follow-up, complete consultation
- Quick templates: common diagnosis templates (URTI, Gastritis, HTN follow-up, etc.) that auto-fill SOAP + medications
- Drug search supports fuzzy matching on generic and brand names
- Allergy drug auto-block with confirmation dialog
### 4. Admin Panel
**Daily Report /admin/daily-report**
- Date picker
- Summary cards: total patients, total revenue, payment method distribution (pie chart), panel receivables
- Doctor workload: patients per doctor, average consultation duration
- Top 10 drugs dispensed
**Drug Inventory /admin/inventory**
- Table: drug name, stock, reorder level, expiry, status (normal/low/expired)
- Filters: low stock, expiring within 30 days
- Stock-in: select drug, quantity, batch number, expiry date, supplier
**Schedule Management /admin/schedule**
- Weekly calendar view: one row per doctor, drag to set time slots
- Date overrides: click date to add leave or overtime
- Show daily booking volume warning (yellow above 80% capacity)
## Malaysian Localization
1. MyKad IC format validation: XXXXXX-XX-XXXX (12 digits with hyphens)
2. Auto-extract from IC: date of birth (first 6 digits), state (middle 2 digits), gender (last digit odd=male, even=female)
3. Panel clinic workflow: company contracts, employee verification, monthly reconciliation
4. MC format compliant with Malaysian Employment Act requirements
5. Drug names display: Generic Name (Brand Name) - Chinese Name
6. Currency: RM (Malaysian Ringgit), 2 decimal places
7. Date format: DD/MM/YYYY (Malaysian convention)
8. Bilingual interface: Chinese primary, medical terms in English
9. Phone format: +60 prefix (e.g., +60 12-345 6789)
## Sample Data
Pre-populate the following test data:
### Doctors
- Dr. Tan Wei Ming (MMC: 45678) - General Practice
- Dr. Lim Siew Hua (MMC: 52341) - General Practice
### Patients
- Ahmad bin Ismail, IC: 850315-14-5623, Male, Blood Type O+, Allergy: Penicillin
- Tan Mei Ling, IC: 920728-10-5764, Female, Blood Type B+, Chronic: Diabetes
- Raj a/l Kumar, IC: 780102-08-5231, Male, Blood Type A+, Panel: Petronas (EMP-10234)
### Common Drugs (20 items)
- Paracetamol 500mg (Panadol) - RM 0.15/tab
- Amoxicillin 500mg (Amoxil) - RM 0.80/cap
- Chlorpheniramine 4mg (Piriton) - RM 0.10/tab
- Bromhexine 8mg (Bisolvon) - RM 0.25/tab
- Omeprazole 20mg (Losec) - RM 0.60/cap
- Metformin 500mg (Glucophage) - RM 0.20/tab
- Amlodipine 5mg (Norvasc) - RM 0.35/tab
- Cetirizine 10mg (Zyrtec) - RM 0.30/tab
- Prednisolone 5mg - RM 0.15/tab
- Diclofenac 50mg (Voltaren) - RM 0.25/tab
- Loperamide 2mg (Imodium) - RM 0.20/cap
- Hyoscine 10mg (Buscopan) - RM 0.40/tab
- Metoclopramide 10mg (Maxolon) - RM 0.15/tab
- Salbutamol inhaler (Ventolin) - RM 12.00/inhaler
- Mefenamic Acid 500mg (Ponstan) - RM 0.30/cap
- Doxycycline 100mg - RM 0.50/cap
- Clarithromycin 250mg (Klacid) - RM 1.20/tab
- Loratadine 10mg (Claritin) - RM 0.35/tab
- Atorvastatin 20mg (Lipitor) - RM 0.80/tab
- Losartan 50mg (Cozaar) - RM 0.55/tab
### Quick Diagnosis Templates
- URTI: Paracetamol + Chlorpheniramine + Bromhexine, MC 2 days
- Gastritis: Omeprazole + Hyoscine + Metoclopramide, MC 1 day
- Acute Gastroenteritis: Loperamide + ORS + Metoclopramide, MC 2 days
- Hypertension Follow-up: check BP, adjust medication, follow-up 1 month
- Diabetes Follow-up: check blood sugar, HbA1c, adjust Metformin dosage
## Key Feature Details
### Auto-Numbering System
- MC: MC-{YYYY}-{000001} annual increment
- Bills: INV-{YYYY}-{000001} annual increment
- Medical Records: MR-{000001} global increment
- Use PostgreSQL sequences for concurrency safety
### Security and Compliance
- Encrypt all medical record data at rest
- Audit log: who viewed/modified what record and when
- Doctors can only edit their own consultation records
- Medical record modifications retain version history (append-only corrections, no deletions)
- Password complexity requirements, session timeout (30 min idle auto-logout)
### Performance Optimization
- Patient search using Full-Text Search (IC number, name, phone)
- Appointment queries use composite indexes
- Drug search with fuzzy matching
- Historical data partitioned by year
Build the complete system following these specifications. Ensure all pages are functional, database schema is complete, and business logic is accurate. Start with database tables and seed data, then build each page incrementally.