← Back to projects

Custom CRM

Custom CRM
Next.jsTypeScriptQuickBooks APIClaude VisionPostgres/SupabaseVercel

The Problem

A handyman crew was running their entire business operations out of a single shared Google Doc. Every job, customer, and "don't forget the roofing nails" note lived in one ever-growing text file. It worked until it didn't. There was no way to pull up the day's schedule from a job site, nowhere to put job photos, and no unified structure for coordination.

They didn't need an expensive SaaS CRM with unnecessary features, they needed something shaped like the way they actually work. Mobile first, built around the job in front of them, and boring enough to trust every single day.

The solution

The app is built around the job, not the customer. The landing page is the job list above, grouped by priority and billing rate, so the crew walks in and sees what matters first.

Open a job and everything for it lives in one place: priority and status, who is assigned, the schedule, a running materials total with receipt photos, and a threaded notes feed the whole crew writes to through the day.

A job detail screen showing status, assigned crew, schedule, an auto-totaled materials list with receipt photos, and a thread of crew notes
A single job: assignment, scheduling, auto-totaled materials, and the crew notes feed

Zoom out from the single job and the schedule answers the question the crew kept asking: who is doing what this week. It is a day-by-day agenda you can filter by crew member, with the day's work sorted by priority and multi-day jobs carried across every day they span. Tap any card to jump straight to that job.

A weekly schedule view as a vertical day-by-day agenda, each day listing job cards with customer, assigned crew, and a time, with today's day highlighted and a multi-day job marked as a continuation
The weekly schedule: a filterable, day-by-day agenda the crew checks from the truck

Architecture

The architecture turns on a single tension in the data model. The crew thinks in jobs; QuickBooks, where the billing lives, thinks in customers. A conventional CRM resolves that by making the customer the central entity, which is the exact shape that cuts against the way the crew coordinates.

So the model inverts it: the job is the primary entity the crew works in, but a foreign-key constraint ties every job to a customer, and customers are created and linked straight to QuickBooks. The link is enforced at the database level, so nothing can exist outside it. The crew gets a job-first app, and billing gets a clean customer record behind every dollar with no reconciliation bolted on after the fact.

A few smaller modeling decisions follow from that one. The service address lives on the job rather than the customer, because a property manager might have work at a dozen buildings at once and one customer record would fight that. The stack stays deliberately plain: Next.js with a small Postgres schema on Supabase, every page a client component reading straight from the database. Heavier server-side rendering and abstractions were considered and skipped. For a small crew, reliability and readability beat cleverness.

Integrations

The integrations are where the system earns its keep. They fold the crew's existing tools into one workflow instead of three.

  • QuickBooks: customer records sync on save. The CRM is the source of truth and pushes downstream, so billing never drifts out of step.
  • TSheets: crew time tracking flows in through a separate OAuth integration, tying hours back to the job.
  • Claude Vision: snap a photo of a hardware-store receipt and it is read automatically. Line items get pulled out and totaled into the job's materials, no typing.

Where It Stands

It is in production and in daily use. The Google Doc is retired and real jobs flow through the app every day. It is intentionally scoped to a small crew: no feature bloat, no premature scale, just the workflow they need, built to stay out of the way.