Skip to main content
RapidDev - Software Development Agency

How to Build HR management system with V0

Build an HR management system with V0 using Next.js, Supabase for employee records, and PostgreSQL RPC for atomic leave approval. You'll create an employee directory, leave request workflow, onboarding checklists, and department analytics — all in about 2-4 hours without touching a terminal.

What you'll build

  • Employee directory with search, department filtering, and status badges using Table and Avatar
  • Employee profile page with tabs for personal info, leave history, time entries, and documents
  • Leave request workflow with manager approval queue and automatic balance deduction via PostgreSQL RPC
  • Multi-step onboarding form for new employees with department assignment and task checklist
  • HR dashboard showing headcount trends, leave utilization, and department cost breakdown with Recharts
  • Document management for employee contracts, IDs, and certificates via Supabase Storage
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Advanced10 min read2-4 hoursV0 PremiumApril 2026RapidDev Engineering Team
TL;DR

Build an HR management system with V0 using Next.js, Supabase for employee records, and PostgreSQL RPC for atomic leave approval. You'll create an employee directory, leave request workflow, onboarding checklists, and department analytics — all in about 2-4 hours without touching a terminal.

What you're building

HR management covers the employee lifecycle — from hiring and onboarding through daily leave requests and time tracking to department analytics. A centralized system replaces scattered spreadsheets with role-based access and automated workflows.

V0 generates the multi-page architecture quickly: employee directory, profile pages, leave approval queues, and analytics dashboards. Supabase handles employee records, leave balances, document storage, and RLS-based access control.

The architecture uses Next.js App Router with Server Components for data-heavy pages, a client component for the multi-step onboarding form, Server Actions for CRUD operations, a PostgreSQL RPC function for atomic leave approval that prevents negative balances, and Recharts for headcount and leave charts.

Final result

An HR management system with employee directory, leave request workflow with balance enforcement, onboarding checklists, document management, and department analytics.

Tech stack

V0AI Code Generator
Next.jsFull-Stack Framework
Tailwind CSSStyling
shadcn/uiComponent Library
SupabaseDatabase, Auth & Storage
RechartsCharts

Prerequisites

  • A V0 account (Premium recommended for the multi-page architecture)
  • A Supabase project with Storage enabled for employee documents
  • Basic understanding of HR workflows (employees, departments, leave requests)
  • Familiarity with role-based access (HR admin, manager, employee)

Build steps

1

Set up the project and HR database schema

Open V0 and create a new project. Connect Supabase via the Connect panel. Create the schema for departments, employees, leave requests, leave balances, onboarding tasks, time entries, and documents.

prompt.txt
1// Paste this prompt into V0's AI chat:
2// Build an HR management system. Create a Supabase schema with:
3// 1. departments: id (uuid PK), name (text), head_id (uuid FK to employees nullable), parent_id (uuid FK to departments nullable), created_at (timestamptz)
4// 2. employees: id (uuid PK), user_id (uuid FK to auth.users), department_id (uuid FK to departments), first_name (text), last_name (text), email (text), phone (text), job_title (text), employment_type (text check in 'full_time','part_time','contract'), start_date (date), end_date (date nullable), salary_cents (bigint), manager_id (uuid FK to employees nullable), status (text default 'active' check in 'active','on_leave','terminated','onboarding'), avatar_url (text), created_at (timestamptz)
5// 3. leave_requests: id (uuid PK), employee_id (uuid FK to employees), type (text check in 'vacation','sick','personal','parental','unpaid'), start_date (date), end_date (date), days (decimal), reason (text), status (text default 'pending' check in 'pending','approved','rejected'), approved_by (uuid FK to employees nullable), created_at (timestamptz)
6// 4. leave_balances: id (uuid PK), employee_id (uuid FK to employees), type (text), year (int), total_days (decimal), used_days (decimal default 0), unique(employee_id, type, year)
7// 5. onboarding_tasks: id (uuid PK), employee_id (uuid FK to employees), title (text), description (text), assigned_to (uuid FK to employees), due_date (date), completed (boolean default false), position (int)
8// 6. time_entries: id (uuid PK), employee_id (uuid FK to employees), date (date), hours (decimal), project (text), notes (text), created_at (timestamptz)
9// 7. documents: id (uuid PK), employee_id (uuid FK to employees), title (text), file_url (text), type (text check in 'contract','id','certificate','other'), uploaded_at (timestamptz)
10// Add RLS: employees see their own data, managers see their reports, HR admins see all.

Pro tip: Connect to GitHub via the Git panel early — this system has many routes, so tracking changes with automatic branching helps manage the complex architecture.

Expected result: Database schema created with 7 tables, RLS policies for role-based access, and relationships between departments, employees, and their records.

2

Build the HR dashboard and employee directory

Create the main dashboard showing HR KPIs and the employee directory with search and filtering.

prompt.txt
1// Paste this prompt into V0's AI chat:
2// Build an HR dashboard at app/page.tsx.
3// Requirements:
4// - Stats Cards row: total employees, open leave requests count, employees onboarding, departments count
5// - Recharts BarChart showing headcount by department (wrap in 'use client' component)
6// - PieChart showing leave type distribution for the current month
7// - Recent activity feed: latest leave requests, new hires, completed onboarding tasks
8// - Navigation to /employees, /leave, /reports
9//
10// Build employee directory at app/employees/page.tsx:
11// - Table with columns: Avatar + name, department, job title, status Badge, start date, actions
12// - Column sorting on name and start_date
13// - Search Input filtering by name or email
14// - Select filter for department
15// - Badge colors: active=green, on_leave=yellow, terminated=red, onboarding=blue
16// - Click row to navigate to /employees/[id]
17// - "Add Employee" Button linking to /employees/new

Expected result: The dashboard shows headcount charts, leave distribution, and recent activity. The directory lists all employees with search, department filter, and status badges.

3

Build the employee profile with tabs

Create the employee detail page with tabbed sections for personal info, leave history, time tracking, documents, and onboarding tasks.

prompt.txt
1// Paste this prompt into V0's AI chat:
2// Build employee profile at app/employees/[id]/page.tsx.
3// Requirements:
4// - Header: Avatar, full name, job title, department Badge, status Badge
5// - Tabs component with sections:
6// 1. Info tab: Card with personal details (email, phone, employment type, start date, salary), edit Button for HR admins
7// 2. Leave tab: Table of leave requests (type, dates, days, status Badge), "Request Leave" Button opening Dialog with DatePickerWithRange, Select for leave type, Textarea for reason
8// 3. Time tab: Table of time entries this month, total hours Card, "Log Time" Button
9// 4. Documents tab: list of uploaded documents with download links, "Upload" Button using file Input that uploads to Supabase Storage bucket 'employee-documents'
10// 5. Onboarding tab (visible only for status='onboarding'): Checkbox list of onboarding tasks with assigned person and due date
11// - Server Actions for leave request submission, time entry logging, document upload, onboarding task completion
12// - Zod validation on all form inputs

Expected result: Employee profile shows all HR data in organized tabs with forms for leave requests, time logging, document uploads, and onboarding task tracking.

4

Create the atomic leave approval system

Build the leave management page and the PostgreSQL RPC function that approves leave requests while atomically checking and deducting leave balances.

prompt.txt
1// Paste this prompt into V0's AI chat:
2// Build leave management at app/leave/page.tsx and create the approval RPC.
3// Requirements:
4// - Pending leave requests queue: Table showing employee name, leave type, dates, days requested, reason
5// - Approve/Reject buttons per row
6// - Create a PostgreSQL function approve_leave(request_id uuid, approver_id uuid):
7// BEGIN;
8// SELECT days, type, employee_id FROM leave_requests WHERE id = request_id AND status = 'pending' FOR UPDATE;
9// SELECT total_days - used_days AS remaining FROM leave_balances WHERE employee_id = emp AND type = leave_type AND year = current_year FOR UPDATE;
10// IF remaining < requested_days THEN RAISE EXCEPTION 'Insufficient leave balance';
11// UPDATE leave_balances SET used_days = used_days + requested_days;
12// UPDATE leave_requests SET status = 'approved', approved_by = approver_id;
13// COMMIT;
14// - API route at app/api/leave/approve/route.ts that calls this RPC
15// - Calendar overlay showing approved leave per team (visible to managers)
16// - Badge for leave status: pending=yellow, approved=green, rejected=red
17// - AlertDialog for rejection requiring a reason

Pro tip: The PostgreSQL RPC function uses FOR UPDATE row locks to prevent race conditions — even if two managers approve simultaneously, leave balances stay accurate.

Expected result: Managers can approve or reject leave requests from a queue. The RPC function atomically validates balances and prevents over-approval.

5

Build multi-step onboarding and reports

Create the new employee onboarding form and the HR reports page with headcount trends and leave analytics.

prompt.txt
1// Paste this prompt into V0's AI chat:
2// Build two pages:
3// 1. app/employees/new/page.tsx — multi-step onboarding form ('use client'):
4// - Step 1: Personal info (first_name, last_name, email, phone, avatar upload)
5// - Step 2: Employment details (department Select, job_title, employment_type Select, start_date, salary)
6// - Step 3: Manager assignment (Select from existing employees in the chosen department)
7// - Step 4: Onboarding tasks (pre-filled checklist with common tasks, ability to add custom tasks)
8// - Progress indicator showing current step
9// - Server Action to insert employee + leave_balances (auto-create vacation/sick/personal with defaults) + onboarding_tasks in one transaction
10//
11// 2. app/reports/page.tsx — HR analytics:
12// - Recharts LineChart: headcount trend over 12 months
13// - BarChart: leave utilization by department
14// - PieChart: employment type distribution
15// - Cards: average tenure, turnover rate this year, departments with highest leave usage
16// - Select for time period filtering
17// - Wrap all charts in 'use client' components

Expected result: HR admins can onboard new employees through a guided multi-step form. The reports page shows headcount trends, leave utilization, and workforce analytics.

Complete code

app/api/leave/approve/route.ts
1import { createClient } from '@/lib/supabase/server'
2import { NextRequest, NextResponse } from 'next/server'
3import { z } from 'zod'
4
5const schema = z.object({
6 requestId: z.string().uuid(),
7})
8
9export async function POST(request: NextRequest) {
10 const supabase = await createClient()
11 const { data: { user } } = await supabase.auth.getUser()
12 if (!user) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
13
14 const body = await request.json()
15 const { requestId } = schema.parse(body)
16
17 const { data, error } = await supabase.rpc('approve_leave', {
18 request_id: requestId,
19 approver_id: user.id,
20 })
21
22 if (error) {
23 if (error.message.includes('Insufficient leave balance')) {
24 return NextResponse.json(
25 { error: 'Insufficient leave balance' },
26 { status: 422 }
27 )
28 }
29 return NextResponse.json({ error: error.message }, { status: 500 })
30 }
31
32 return NextResponse.json({ success: true })
33}

Customization ideas

Add payroll integration

Connect time entries to a payroll calculator that computes gross pay based on salary, overtime hours, and leave deductions.

Add performance reviews

Build a quarterly review system with rating forms, manager feedback, and goal tracking per employee.

Add org chart visualization

Render the department and manager hierarchy as an interactive tree chart using the parent_id relationships.

Add employee self-service portal

Let employees update their own contact info, view pay stubs, and download tax documents from a dedicated portal.

Add automated leave balance rollover

Build a Vercel Cron job that runs January 1st to calculate remaining vacation days and roll over a configurable percentage to the new year.

Common pitfalls

Pitfall: Approving leave without checking the balance atomically

How to avoid: Use a PostgreSQL RPC function with FOR UPDATE row locks that checks the balance and updates both tables in a single transaction.

Pitfall: Storing salary as a float instead of integer cents

How to avoid: Store salary as bigint in cents (salary_cents) and format for display client-side by dividing by 100.

Pitfall: Not restricting document access with Storage RLS

How to avoid: Configure Supabase Storage bucket RLS to restrict file reads to the employee themselves and users with the HR admin role.

Pitfall: Building the manager hierarchy without null checks

How to avoid: Make manager_id nullable and add null checks in approval workflows — requests from top-level employees go directly to HR admin.

Best practices

  • Use a PostgreSQL RPC function for leave approval to enforce balance constraints at the database level, preventing race conditions.
  • Store all monetary values (salary, budgets) as integer cents (bigint) to avoid floating-point rounding errors.
  • Configure Supabase Storage bucket RLS to restrict document access to the employee and HR admins only.
  • Connect to GitHub via V0's Git panel early — the multi-page HR system benefits from version-controlled incremental development.
  • Seed leave_balances automatically when creating employees — use a PostgreSQL trigger or include balance creation in the onboarding Server Action.
  • Use Clerk custom claims for role-based access (hr_admin, manager, employee) rather than building a custom role system.
  • Validate all form inputs with Zod in Server Actions — enforce that leave end_date >= start_date and salary > 0.

AI prompts to try

Copy these prompts to build this project faster.

ChatGPT Prompt

I'm building an HR management system with Next.js and Supabase. I need a PostgreSQL function that atomically approves a leave request: checks that the employee has sufficient leave balance, deducts the days, and updates the request status — all in one transaction with row-level locking. Show me the function SQL, how to call it via supabase.rpc(), and how to handle the insufficient balance error in the API route.

Build Prompt

Build the leave approval queue for an HR system. Create a Server Component page that fetches pending leave requests with the employee name and leave balance. Each request row shows a Table with type, dates, days, reason, and Approve/Reject buttons. Approve calls a PostgreSQL RPC function that checks the balance and deducts days atomically. Reject opens an AlertDialog requiring a reason. Use Badge for status colors and Card for the balance summary.

Frequently asked questions

Can I build this with the free V0 plan?

The core pages fit within the free tier, but V0 Premium is recommended for the multi-page architecture. Use Design Mode (Option+D) for free visual tweaks across all plans.

How does the leave approval prevent over-spending balances?

A PostgreSQL RPC function uses FOR UPDATE row locks to atomically check the balance and deduct days in a single transaction. Even concurrent approvals cannot overdraw the balance.

What authentication should I use?

Clerk is recommended for the role-based access. Set custom claims (hr_admin, manager, employee) in the Clerk Dashboard metadata, then check roles in middleware.ts and Server Actions.

Can I add payroll features?

Yes. Use the time_entries and salary_cents data to calculate gross pay. For full payroll, integrate with a payroll API like Gusto or ADP through a Server Action.

How do I handle document storage?

Create a Supabase Storage bucket called employee-documents with RLS restricting access to the employee and HR admins. Upload via Server Action with FormData, store the file_url in the documents table.

How do I deploy?

Publish via V0's Share menu. Set SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY, and Clerk credentials in the Vars tab. Configure the Storage bucket and RLS policies in Supabase Dashboard.

Can RapidDev help build a custom HR platform?

Yes. RapidDev has built 600+ apps including HR platforms with payroll integration, performance reviews, and compliance tracking. Book a free consultation to discuss your requirements.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help building your app?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.