Build a multi-level document approval system with customizable workflows, notifications, and audit trail
* Preview is for reference only. Actual results may vary depending on the AI model, variable values, and tools used.
You are an expert full-stack developer. Build a complete Document Approval Workflow System for "{{company_name}}" that replaces paper-based approval processes.
## Tech Stack
- Next.js 14 (App Router) + TypeScript
- Tailwind CSS + shadcn/ui
- Supabase (PostgreSQL + Auth + RLS + Storage)
- Resend (email notifications)
- Vercel deployment
## Database Schema
Create all tables in order with RLS policies enabled:
sql
-- Departments
create table departments (
id uuid primary key default gen_random_uuid(),
name text not null,
manager_id uuid references auth.users(id),
parent_id uuid references departments(id),
created_at timestamptz default now()
);
-- Employee profiles
create table employees (
id uuid primary key references auth.users(id),
full_name text not null,
email text not null,
department_id uuid references departments(id),
position text,
approver_level int default 0,
is_admin boolean default false,
created_at timestamptz default now()
);
create index on employees(department_id);
-- Document types
create table document_types (
id uuid primary key default gen_random_uuid(),
name text not null,
code text unique not null,
description text,
requires_attachment boolean default false,
is_active boolean default true,
created_at timestamptz default now()
);
-- Workflow configuration (amount-based routing rules)
create table approval_workflows (
id uuid primary key default gen_random_uuid(),
document_type_id uuid references document_types(id),
amount_min numeric default 0,
amount_max numeric,
levels int not null default 1,
level1_role text,
level2_role text,
level3_role text,
auto_approve_below numeric default 0,
escalation_hours int default 48,
is_active boolean default true,
created_at timestamptz default now()
);
create index on approval_workflows(document_type_id);
-- Approval requests (the actual documents)
create table approval_requests (
id uuid primary key default gen_random_uuid(),
request_no text unique not null,
document_type_id uuid references document_types(id),
requester_id uuid references employees(id),
title text not null,
description text,
amount numeric,
currency text default 'MYR',
priority text default 'normal' check (priority in ('low','normal','high','urgent')),
status text default 'pending' check (status in ('draft','pending','approved','rejected','cancelled','recalled')),
current_level int default 1,
total_levels int default 1,
workflow_id uuid references approval_workflows(id),
submitted_at timestamptz,
completed_at timestamptz,
rejection_reason text,
metadata jsonb default '{}',
created_at timestamptz default now(),
updated_at timestamptz default now()
);
create index on approval_requests(requester_id);
create index on approval_requests(status);
create index on approval_requests(document_type_id);
-- Approval steps (one row per level per request)
create table approval_steps (
id uuid primary key default gen_random_uuid(),
request_id uuid references approval_requests(id) on delete cascade,
level int not null,
approver_id uuid references employees(id),
delegate_id uuid references employees(id),
status text default 'pending' check (status in ('pending','approved','rejected','delegated','escalated','skipped')),
comments text,
acted_at timestamptz,
due_at timestamptz,
created_at timestamptz default now()
);
create index on approval_steps(request_id);
create index on approval_steps(approver_id);
create index on approval_steps(status);
-- Attachments
create table attachments (
id uuid primary key default gen_random_uuid(),
request_id uuid references approval_requests(id) on delete cascade,
file_name text not null,
file_url text not null,
file_size int,
mime_type text,
uploaded_by uuid references employees(id),
created_at timestamptz default now()
);
create index on attachments(request_id);
-- Comments and discussion thread
create table request_comments (
id uuid primary key default gen_random_uuid(),
request_id uuid references approval_requests(id) on delete cascade,
author_id uuid references employees(id),
content text not null,
is_internal boolean default false,
created_at timestamptz default now()
);
create index on request_comments(request_id);
-- In-app notifications
create table notifications (
id uuid primary key default gen_random_uuid(),
recipient_id uuid references employees(id),
request_id uuid references approval_requests(id),
type text not null,
title text not null,
body text,
is_read boolean default false,
sent_at timestamptz default now()
);
create index on notifications(recipient_id);
create index on notifications(is_read);
-- Full audit trail
create table audit_logs (
id uuid primary key default gen_random_uuid(),
request_id uuid references approval_requests(id),
actor_id uuid references employees(id),
action text not null,
old_data jsonb,
new_data jsonb,
ip_address text,
created_at timestamptz default now()
);
create index on audit_logs(request_id);
## Business Rules — Amount-Based Routing
Apply these routing rules automatically when a request is submitted:
- Amount < RM 5,000: 1 approval level (Department Head)
- Amount RM 5,000–50,000: 2 approval levels (Department Head → Finance Manager)
- Amount > RM 50,000: 3 approval levels (Department Head → Finance Manager → Director)
- Below auto-approve threshold: skip all levels, mark as approved immediately
- Document types: {{document_types}}
- Max approval levels: {{approval_levels}}
- Departments: {{departments}}
## Core Workflow Engine
Create `src/lib/workflow-engine.ts` with these functions:
1. `resolveWorkflow(documentTypeId, amount)` — query approval_workflows, match the correct tier, return config
2. `createApprovalSteps(requestId, workflow, submitterId)` — insert rows into approval_steps for each level, assign approvers by role, set due_at based on escalation_hours
3. `advanceWorkflow(requestId, stepId, action, comments, actorId)` — mark current step approved/rejected, if approved and more levels remain advance current_level and activate next step, if final level mark request approved, if rejected mark request rejected and notify requester
4. `recallRequest(requestId, requesterId)` — allow requester to recall a pending request before it is acted on
5. `resubmitRequest(requestId, requesterId, updates)` — after rejection, allow requester to edit and resubmit, reset all steps
## Features to Build
### Approval Inbox (`/inbox`)
- Split-pane layout: left list of pending items, right detail panel
- Each list row: document type badge, title, requester name, amount (RM), submitted date, priority badge
- Detail panel: full form data, attachments list, approval chain stepper, comment thread
- One-click Approve button (yellow) and Reject button (red, requires reason)
- Batch approve: select multiple rows and approve all at once
- Mobile-responsive: stack to single column on small screens so approvers can act from phone
### Approval Chain Stepper (`src/components/approval-stepper.tsx`)
- Vertical stepper showing all {{approval_levels}} levels
- Each node: approver avatar/initials, name, position, status icon, timestamp, comments
- Current active level highlighted with pulse animation
- Completed levels: green checkmark; rejected level: red X; pending levels: grey circle
### Request Submission Form (`src/components/request-form.tsx`)
- Document type selector triggers dynamic field rendering
- Amount field with RM prefix; below the field show real-time preview: "This request requires N approval levels"
- Multi-file upload to Supabase Storage (PDF, images, max 10 MB each)
- Priority selector: Normal / High / Urgent
- Auto-save draft to localStorage every 30 seconds
- Submit triggers workflow resolution and step creation via Server Action
### Delegation
- Employee settings page: delegate approval authority to a colleague for a date range
- When delegation is active, delegated requests appear in delegate's inbox with a "Delegating for [Name]" label
- Audit log records "Approved by [Delegate] on behalf of [Original Approver]"
### Auto-Escalation (Supabase Edge Function)
- Cron: every hour, check approval_steps where status = 'pending' and due_at < now()
- Mark overdue steps as 'escalated', send email to approver and their manager
- Dashboard surfaces escalated requests with an orange warning badge
### Notifications
Trigger in-app notification + email (via Resend) for:
- Submission → notify level 1 approver
- Each level approved → notify next level approver
- Final approval → notify requester
- Any rejection → notify requester with reason
- Resubmission → notify all approvers in chain that request has been revised
- Escalation → notify approver and their manager
### Dashboard (`/`)
Display summary cards:
- Pending My Approval (count + oldest waiting days)
- Submitted by Me In Progress (count + current level stuck on)
- Approved Today (count)
- Average Approval Time — last 30 days in hours
- Bottleneck Table: top 5 approvers by average hold time
Use Supabase RPC functions for aggregation queries.
### Admin — Workflow Configuration (`/admin/workflows`)
- Table listing all document types with their routing rules
- Edit modal: set amount tiers, assign approver roles per level, set escalation hours, set auto-approve threshold
- Changes take effect on new submissions only (existing requests keep original workflow snapshot)
### Admin — Reports (`/admin/reports`)
- Filter by date range, department, document type
- Metrics: total submitted, approval rate, average cycle time, rejection reasons breakdown
- Export to CSV
## Page Structure
/ Dashboard
/inbox Approver inbox
/requests My submitted requests
/requests/new Submit new request
/requests/[id] Request detail + audit trail
/admin/workflows Workflow configuration
/admin/reports Analytics and export
## Security & RLS
- Employees can only read their own requests or steps assigned to them
- Admins (is_admin = true) can read all records
- All mutations go through Server Actions (never expose service key to client)
- Wrap all RLS policy auth.uid() calls as (select auth.uid()) for performance
- Rate-limit the submission endpoint: max 20 requests per employee per day
## UI Design
- Dark navbar (#0F172A) + white content area
- Yellow (#FCD34D) for primary actions: Submit, Approve
- Status badges: Pending (blue), Approved (green), Rejected (red), Draft (grey), Escalated (orange)
- Priority badges: Normal (grey), High (orange), Urgent (red with pulse)
- Fonts: Inter (Latin) + Noto Sans SC (CJK) via next/font
- Company name "{{company_name}}" in navbar
- Currency: RM (Malaysian Ringgit) throughout
## Deliverables
1. Complete SQL migration file with all tables, indexes, and RLS policies
2. All pages and components with full TypeScript code
3. `src/lib/workflow-engine.ts` with all workflow functions
4. Supabase Edge Function for auto-escalation cron job
5. Server Actions for all mutations (submit, approve, reject, delegate, recall, resubmit)
6. `.env.local.example` with all required environment variables
7. Seed script with sample document types, workflows, departments, and employees for testing