
I Built a Full-Stack HRMS in 8 Hours for the Odoo Hackathon — Here’s What Actually Happened
The problem statement dropped at 8:30 AM. By 5:00 PM, I had a live, deployed, fully functional Human Resource Management System running on Vercel. Eight hours. One team. Zero sleep the night before. This is the honest story of how that happened — what I planned, what broke, what I learned, and what I’d do differently.
The Morning Before the Clock Started
The hackathon was virtual. That sounds easier. It’s not. When you’re in a room together, energy is contagious — someone gets excited, everyone gets excited. At home, you’re just a person staring at a terminal, fighting off the urge to refresh Twitter.
I had my setup ready the night before:
- Next.js 15 project scaffolded
- Supabase project created
- Tailwind CSS v4 + shadcn/ui initialized
.env.localpopulated- Folder structure committed to GitHub.
At 8:30 AM, the problem statement unlocked: Human Resource Management System.
My first thought? Good. I’ve thought about systems like this before. My second thought? Eight hours is nothing for an HRMS.
What the Problem Statement Actually Asked For
The PDF was detailed. Here’s what Odoo wanted — not just “build an HR app” — but a specific, thoughtful system:
- Secure auth — Sign up with Employee ID, Email, Password, Role. Email verification. Role-based redirects.
- Two user types — Admin/HR Officer vs Employee. Completely different experiences.
- Employee dashboard — Profile, Attendance, Leave Requests, quick-access cards.
- Admin dashboard — Employee list, attendance records, leave approvals, ability to switch between employees.
- Attendance — Check-in/check-out, daily and weekly views, status types (Present, Absent, Half-day, Leave).
- Leave management — Calendar-based date selection, leave types (Paid/Sick/Unpaid), approval workflow with admin comments.
- Payroll — Read-only for employees, editable salary structure for admins.
They also provided an Excalidraw design reference — a dark-themed, card-based UI with a navbar, sidebar, clean table views, and a monthly calendar with color-coded attendance markers.
That image became my north star for the next eight hours.
The Architecture Decision I Made in 15 Minutes
At 9:00 AM sharp, coding started.
The first 30 minutes weren’t about writing a single line of UI. They were about getting the foundation right — because if the database schema or the RLS policies are wrong, everything built on top of them is wrong too.
I chose Supabase as the single source of truth for everything:
- Auth — Supabase’s built-in email/password auth handled signup, login, email verification, and session management. No custom JWT juggling, no session headaches.
- Database — PostgreSQL via Supabase. Four tables:
profiles,attendance,leaves,payroll. - Row-Level Security — This was the critical architectural move. Instead of writing role checks in every API route, I wrote RLS policies directly at the database level. An employee literally cannot query another employee’s attendance record — the database refuses the request before it ever reaches application code.
- Realtime — Supabase Realtime subscriptions for attendance updates, so the admin dashboard reflects check-ins in real time without manual refresh.
The schema I finalized at 9:15 AM:
1profiles → id, employee_id, full_name, phone, address, job_title, department, avatar_url, role
2attendance → id, user_id, date, check_in, check_out, status
3leaves → id, user_id, type, start_date, end_date, remarks, status, admin_comment
4payroll → id, user_id, month, basic, deductions, net_salary (computed column)One thing I added that wasn’t in the original spec: auto-generated Login IDs in the format COMPANY_CODE + YEAR + SERIAL. Every employee gets a unique ID the moment the admin creates their account — no email invite chain needed. This came from realizing during planning that “email verification for every new employee” is painful to demo, and even more painful in a real company context. This was cleaner, faster, and more realistic.
The Challenge That Nearly Broke Me: Role-Based Routing
At 10:30 AM, I hit the first real wall.
Role-based routing in Next.js 15 App Router sounds simple. It isn’t. The question is: where do you check the role?
If you check in the page component, you get a flash of the wrong dashboard before the redirect fires. If you check in proxy.ts (Next.js’s middleware layer), you need the session server-side — which means cookies, which means Supabase’s createServerClient, a completely different instance from the browser client.
I ended up maintaining two Supabase clients:
1// lib/supabase/client.ts — for browser/client components
2// lib/supabase/server.ts — for server components and proxy.tsThe proxy.ts reads the session cookie, fetches the user’s role from profiles, and redirects:
- Unauthenticated →
/login - Employee →
/dashboard/profile - Admin →
/dashboard/admin/employees
Once this worked, every page downstream was clean. No component needed to think about role checking — the routing layer handled it entirely.
Building the Dashboards: Speed Over Perfection
From 11:00 AM to 1:00 PM, I was in pure build mode.
The employee dashboard had four quick-access cards exactly as specced: Profile, Attendance, Leave Requests, Logout. Each card was a shadcn/ui Card with a lucide-react icon, a label, and a short description. Done in 20 minutes.
The admin dashboard was harder. It needed a live employee list with the ability to “switch between employees” — view any employee’s records without navigating away. I implemented this as a selected-employee context: an admin picks a name from a dropdown, and all the data views below filter to that employee’s records. No page navigation, no full reload. Clean.
Attendance came next. Check-in writes a row to attendance with today’s date, current time, and status present. Check-out updates the same row. If you’ve already checked in today, the button automatically flips to “Check Out.” The weekly view is a 7-column table. The daily view is a single-row summary card. Both are server-fetched with no client-side spinners.
The Leave Calendar: The Hardest 90 Minutes
1:30 PM to 3:00 PM. Post-lunch. Energy dropping. Leave management staring at me.
The spec asked for calendar-based date range selection, leave type dropdown, remarks input, a monthly calendar with Present/Absent markers, and an admin approval workflow with comments.
The shadcn/ui Calendar component handles date range selection out of the box via react-day-picker. But the Present/Absent markers required custom DayContent rendering. I fetched that month’s attendance records, built a lookup map (date → status), and passed it as modifiers to the calendar. Each status got a colored dot under the date number.
1const modifiers = {
2 present: records.filter(d => d.status === 'present').map(d => new Date(d.date)),
3 absent: records.filter(d => d.status === 'absent').map(d => new Date(d.date)),
4 leave: records.filter(d => d.status === 'leave').map(d => new Date(d.date)),
5}The admin approval side was a table of all pending leave requests, each row with Approve/Reject buttons and an expandable comment input. Clicking either updates the row status in leaves and writes admin_comment. Because of RLS, the update only succeeds if the requesting user is confirmed as admin at the database level — not just the application level.
Payroll: The Module I Almost Skipped
3:00 PM to 4:00 PM.
One hour left before Polish time. Payroll had to be fast.
The data model made it possible. The payroll table has basic and deductions. net_salary is a PostgreSQL generated column:
1net_salary numeric GENERATED ALWAYS AS (basic - deductions) STOREDThe database computes it. I never calculate salary in application code — one less place a bug can live. Employees see a read-only card. Admins see an editable table.
What I added beyond the spec: configurable salary components — Basic, HRA, and allowances as percentage-based fields. Admin sets the percentages; the system computes each component’s value. This turned a boring static table into something that felt like actual payroll software rather than a college project.
The Last Hour: Polish, Validation, Panic
4:00 PM to 5:00 PM.
This hour is where hackathons are won or lost.
I went through every form and added proper validation:
- Email format checks on signup
- Password minimum 8 characters with at least one number
- End date cannot be before the start date on leave applications.
- Duplicate check-in prevention — can’t check in twice on the same day.
- Leave overlap detection — can’t apply for dates already applied or approved.
Then responsive testing. The app was built desktop-first. Odoo’s evaluation criteria explicitly called out responsive UI. I spent 20 minutes adding mobile breakpoints: sidebar collapsed to a bottom nav on small screens, attendance tables got horizontal scroll, dashboard cards stacked single-column on mobile.
At 4:47 PM, final commit. Every team member had their own commits in the history. The evaluator had already been added as a collaborator at 10:00 AM sharp.
At 4:55 PM, deployed to Vercel. One git push. Live.
What the Deployed Product Became
The live HRMS at hr-management-system-tan.vercel.app ended up being a genuinely usable product: app
- Company onboarding — sign up once, instantly become the admin, get a generated company Login ID.
- Employee management — add employees from the admin directory; each gets a unique Login ID.
- Full attendance lifecycle — check-in, check-out, automatic work hours calculation, monthly logs
- Leave management with balance tracking — applied leaves deduct from available balance in real-time.
- Payroll with percentage-based components — not just a static number but an actual salary structure
The tagline I wrote for the landing page: “Manage your people, not paperwork.”
That’s exactly what it does.
What I Learned
Schema design IS the project.
The 30 minutes I spent on the database schema saved me hours of refactoring later. Get this wrong and everything built on top is wrong. Get it right, and the application almost writes itself.
RLS is a superpower most developers ignore.
Role-based access at the database level means you can’t accidentally expose data through a buggy route. Security by default — not security by remembering to add a WHERE user_id = ? clause somewhere.
Generated columns are underrated.
Let the database compute derived values. One less place for logic bugs, one less thing to test, one less thing to explain.
Build boring parts first.
Auth and routing are unglamorous. But if they’re shaky, the flashy features on top fall apart during the demo. Evaluators notice when a Sign Up button doesn’t work.
Time-box every module ruthlessly.
I gave each module a hard deadline. When it hit, I shipped what I had and moved on. A working half-feature beats a broken full-feature in a hackathon every time.
The wireframe is a contract, not a suggestion.
Following the Excalidraw design reference strictly meant the UI looked intentional rather than improvised. Evaluators can tell the difference.
What I’d Do Differently
Start with seed data. A demo is dramatically better when the evaluator logs in and sees real records — employees, attendance logs, payroll entries — not an empty database. I should have had a seed script ready from minute one.
Document API contracts before building. Even with Supabase handling the backend, writing down “this page fetches X from Y table with Z filters” before touching any component would have cut debug time significantly.
Record the demo earlier. I recorded with 8 minutes left. My voice was rushed. The product deserved a calmer, more confident walkthrough.
The Honest Takeaway
Eight hours. A real, deployed, production-quality HRMS. Something I’m genuinely proud of — not because it’s perfect, but because it works. It handles real use cases that real companies deal with every day, and it handles them cleanly.
If you’re a developer sitting on the fence about hackathons: enter one. Not for the prizes. Do it because there is no situation in normal development life that forces you to make fast, committed architectural decisions the way a countdown timer does.
The pressure isn’t a bug in the hackathon format.
It’s the feature.
Built with Next.js 15, TypeScript, Tailwind CSS v4, shadcn/ui, and Supabase.
8 hours. Odoo Hackathon 2026.
Live: https://hr-management-system-tan.vercel.app/
GitHub: https://github.com/ronisarkar-official/hr-management-system