Samara Studio Management System
Replaced Excel with a production-grade, real-time studio operations platform.
At a Glance
The Problem
Samara Studio โ a photography, videography, and content-production studio โ was running its entire operation on Excel spreadsheets. Staff manually tracked bookings, room occupancy, equipment loans, client balances, and daily cash closings across disconnected sheets. Double-bookings happened when two staff members booked the same room simultaneously without syncing. Equipment was overcommitted because there was no real-time visibility of active reservations. Financial drift crept in as partial payments and deposits were manually tracked. And daily closing required summing rows in a spreadsheet with no audit trail.
The Solution
A real-time, role-based web application that centralizes every studio operation. Booking conflict prevention happens at three layers simultaneously: client-side validation, server-side API guards, and PostgreSQL exclusion constraints with row-level locking โ making double-booking structurally impossible. All billing logic lives in PostgreSQL RPCs (`end_booking_session`, `confirm_booking`, `cancel_booking`), so the critical financial path is transactional, lock-aware, and database-enforced regardless of which client triggers it.
Architecture
- โBrowser: Next.js 16 App Router with React Server Components for the calendar, booking forms, finance, and settings modules.
- โAPI Layer: Next.js Route Handlers (REST-style) serving all mutations and queries, backed by the Supabase JS SDK.
- โDatabase: Supabase-hosted PostgreSQL with 39 migration files covering schema, triggers, RLS policies, ACID RPCs, and 13 performance indexes on hot query paths.
- โAuth: Supabase Auth with JWT sessions and cookie-based SSR. Row Level Security on every table.
- โRealtime: Supabase Realtime subscriptions keep the FullCalendar resource-timegrid live across all open tabs.
- โState: React Query for all server state (targeted invalidation by key). Zustand for ephemeral UI state (booking form steps, drawer open/close, filter panels).
Key Features
- โThree-layer booking conflict prevention โ frontend check โ Next.js API guard โ PostgreSQL FOR UPDATE lock + exclusion constraint
- โOpen-session (walk-in) billing with whole-hour ceiling rounding and 15-minute short-session guard
- โProduction vs. Rental booking categories with camera operator fee splitting
- โDynamic per-room buffer time engine visualized on FullCalendar resource-timegrid
- โPre-paid bundle management โ group sessions sold at package price, individually schedulable
- โDouble-entry financial ledger โ every charge, payment, and refund is an immutable ledger row
- โDaily cash closing with drawer reconciliation and variance flagging
- โEquipment quantity-aware reservation system โ rejects bookings that exceed stock at API and DB level
- โExcel + PDF export pipeline for financial reports, booking lists, and client ledgers
- โE2E tests with Playwright + integration tests with Node.js native test runner
Screenshots
Code Highlight
Challenges & Solutions
๐ฏ Race conditions on concurrent bookings โ two staff booking the same room within milliseconds of each other
The `confirm_booking` RPC acquires a `FOR UPDATE` lock on the target room's active bookings before inserting. PostgreSQL serializes concurrent callers at the row-lock level. A PostgreSQL exclusion constraint on the `bookings` table using the `tstzrange` type additionally prevents overlapping time ranges from co-existing โ even if a bug bypasses the application layer.
๐ฏ Billing accuracy across multiple pricing models โ hourly, flat-rate, and camera-percentage variants
Migrated all billing math into the `end_booking_session` PostgreSQL function (migration 035). The function reads `camera_price_type`, applies `CEILING(ms / 3_600_000, 1)` for hourly items and uses the flat value for flat-fee items โ all inside a single `BEGIN/COMMIT` block. No partial state is possible.
๐ฏ Schema evolution across 39 migrations with real data present from early development
Every change was written as an incremental, idempotent SQL migration using `CREATE OR REPLACE`, `ALTER TABLE ... ADD COLUMN IF NOT EXISTS`, and `CREATE INDEX IF NOT EXISTS`. Destructive operations were avoided by design, giving a linear, auditable history of every schema decision.
Tech Stack
What I Learned
- ๐กPut critical logic in the database, not the app layer. Billing, conflict detection, and ledger updates that must not partially fail belong in PostgreSQL transactions โ the switch to RPCs eliminated an entire class of data-consistency bugs.
- ๐กReact Query's query key design deserves as much thought as the schema. Broad invalidations caused cascade re-fetches that hurt perceived performance; narrowing to specific keys (`['bookings', bookingId]`) made the UI feel significantly more responsive.
- ๐กSchema migrations are permanent decisions โ make them conservative. "Add, don't replace" prevented data loss but left some semantic debt. More upfront ERD planning before writing a single migration would pay dividends.
Links
Interested in a similar solution?
Let's discuss how I can build something like this for your business.











