Construction Management SaaS Case study

Projul

An end-to-end construction-management platform spanning CRM, estimates, signatures, scheduling, jobs, invoicing, payments, time tracking and reporting.

Construction Management SaaS

Projul Projul

Lead Engineer · App V2

System map · Transformation

Verified proof

  • Improved project performance by 80% The verified outcome for App V2
  • One data layer, two protocols Protocol choice never leaks into components

Refactor path

  1. Legacy frontend

    The first-generation application

  2. Component architecture

    Shared conventions, one state model

  3. Optimised data flow

    One data layer over GraphQL and REST

  4. Performance

    Rendering at real list sizes

What held it together

  • Shared component conventions
  • One state-management model
  • A single data-fetching layer
  • End-to-end tests over critical journeys
Product surface
The public Projul site, showing its all-in-one construction management product.

Engineering

React · React Query · Redux Toolkit · Web Workers · GraphQL · REST

Public product · Proprietary source code

Role
Lead Engineer · App V2
Disclosure
Public product · Proprietary source code

The problem

What the system had to solve.

An end-to-end construction-management product needed a scalable second-generation web application for complex operational workflows.

What I owned

The responsibility behind the work.

Led development of appv2.projul.com across its frontend and backend architecture.

Led development of Projul’s second-generation web application across frontend and backend architecture, building complex workflows for projects, customers, contractors, scheduling and operations.

Engineering approach

How it was built.

Developed workflows for projects, customers, contractors, scheduling and operational management, alongside performance and frontend architecture improvements.

Refactor path

  1. Legacy
  2. Component architecture
  3. Optimized data flow
  4. Performance

A second-generation application

Rebuilding a product people already depend on.

A construction-management platform is not one feature. It is a CRM, an estimating tool, a scheduler, a job tracker, an invoicing system and a reporting surface, all operating on the same customers, jobs and contractors, and all in daily use by businesses whose work does not pause for a rewrite.

The second-generation application had to carry that surface area while being faster than what it replaced. Users do not experience a rewrite as new architecture; they experience it as whether the screen they open fifty times a day got better or worse.

The operational surface

Commercial

  • CRM and customers
  • Estimates
  • Signatures
  • Invoicing and payments
  • Accounting sync

Delivery

  • Jobs and projects
  • Scheduling
  • Contractors
  • Time tracking
  • Reporting

Two ends of the same record

  • Projul’s lead pipeline: a stage bar running from new leads through contact, scheduling and estimating, above a list of leads that each show follow-up count, address, project type, estimated revenue and likelihood of sale, with the same pipeline on a tablet and the follow-up tasks it generates on a phone.

    Commercial — the lead pipeline, from a new lead through to an estimate waiting on the customer

  • Projul’s project view: several jobs stacked vertically, each with completion percentage, crew avatars, address and scheduled hours, against a band of days carrying individual task cards, with the current day’s tasks shown on a phone alongside.

    Delivery — the same work once it is sold, scheduled out day by day

One record seen twice. A lead becomes a job, and the job is what gets scheduled — which is why the rewrite could not be done a screen at a time.

Where the time actually went

Performance work on a data-dense application.

The slow parts of an operational product are rarely the network. They are long lists rendered in full, expensive derivations recomputed on every render, and the same data refetched by four components that each believed they owned it. The performance work targeted those three in order.

  • Virtualised lists

    Long schedules, job lists and customer tables render only what is on screen, so list length stops determining frame rate.

  • Memoised derivations

    Expensive computed values are cached against their inputs rather than recalculated on every render pass.

  • Server-state caching

    One cache for fetched data, shared across components, so the same record is not requested several times per screen.

  • Work off the main thread

    Heavy computation moved off the render thread, so a calculation cannot make the interface stop responding.

  • Local persistence

    An in-browser store for data that would otherwise be refetched on every navigation.

  • Deliberate state boundaries

    Server state and client state kept in separate systems rather than one global store holding both.

What the derivations are for

  • Projul’s reporting screen: a bar chart of closing ratio by lead source against the company’s overall ratio, a doughnut chart breaking sales down by outcome, a long bar chart of closing ratio for every zip code, and a flow diagram tracing lead source types through to the project types they became.

    Reporting — closing ratio by lead source, sales ratio across every zip code the business works in, and lead source flowing through to project type

A reporting surface is the honest test of the state boundary. Every mark here is derived from records the rest of the application is already holding, and none of it is worth recomputing on a render pass.

The distinction that made the most difference: server state is a cache of something that lives elsewhere, and client state is owned by the interface. Treating them as one thing is what makes a large front end slow and hard to reason about.

Scheduling and jobs

The screen the business actually runs on.

Scheduling is where a construction business is coordinated: crews, jobs, days, and the constant reshuffling that happens when a delivery slips. It needs multiple calendar perspectives, direct manipulation, and time handling that survives users in different time zones from the jobs they are scheduling.

What the scheduling surface required

  • Day, week, list and timeline perspectives
  • Drag-and-drop rescheduling
  • Time-zone-correct handling
  • Interaction at list sizes that defeat naive rendering

The same schedule, three ways

  • Projul’s scheduling views: a month timeline listing every active project as a row of coloured hour blocks across two months, the same schedule filtered to a single project as a month calendar on a tablet, and a phone showing one day’s tasks with their addresses, gate codes and durations.

    A month across every active job on the desktop, one job as a calendar on a tablet, and one day in the crew’s hand

The perspectives are not three features. They are the office, the project manager and the crew reading one schedule at the distance each of them works at.

Two data protocols, one client

Talking to a backend mid-transition.

The application consumes both GraphQL and REST. That is the normal condition of a second-generation client built against a backend that is itself evolving: some surfaces have moved, others have not, and the client cannot wait for the migration to finish before shipping.

Keeping that manageable

Contained

  • Fetching isolated behind a data layer
  • One caching model over both protocols
  • Components consume data, not transports

Deliberately avoided

  • Protocol choice leaking into components
  • Two caching strategies in one application
  • Migration state visible in feature code
The mixture is contained at the data layer, so feature code does not fork on which protocol its data happens to arrive over.

Integrations and the surrounding surface

What a construction product has to talk to.

An operational product of this kind is judged partly on what it connects to. The application carries enterprise identity providers, rich document editing for estimates and scopes, mapping for job locations, data import for onboarding a business’s existing records, drawn signature capture, error monitoring so production problems are seen rather than reported, and the accounting service the business already keeps its books in.

Integration surface

  • Accounting sync
  • Enterprise identity providers
  • Rich text editing
  • Mapping and location
  • Bulk data import
  • Signature capture
  • Charting and reporting
  • Error monitoring
  • Internationalisation

All but one of these are things the application draws with. The accounting service is not one of them, and the chapter that follows is about the difference.

QuickBooks

Integrating with a system of record you do not own.

A construction business does not keep its books in its project software. It keeps them in QuickBooks, and it has kept them there since before it bought anything else. That makes the accounting integration unlike every other one in the product: the rest are components the application draws with, and this one is a second system of record that already holds the customer, already holds the invoice, and is the copy the business’s accountant will believe.

Projul syncs customers, estimates, change orders and invoices out to QuickBooks, and reads payments back. The engineering problem was never the API call. It is that two systems now hold the same commercial record, they are able to disagree, and the person who finds out is a contractor trying to invoice a finished job.

Two QuickBooks, two different integrations

QuickBooks Online

A hosted service the company authorises once, and one the application can reach whenever it has something to send.

  • Connected by the company from its own settings
  • Sync runs against a service that is always up
  • Payments recorded in QuickBooks come back by webhook

QuickBooks Desktop

A company file on a machine in the office, reached through a connector installed next to it.

  • A sync manager is installed alongside QuickBooks itself
  • The company file grants that connector standing access
  • The office machine has to be left running for a sync to land
Both are chosen from the same setting and produce the same synced record, so the rest of the product is written against one accounting service rather than two.

Both ends of the sync

  • Projul’s estimates board: a card for each customer estimate showing its total, a progress ring for how much has been invoiced and paid, and its approval state; an open estimate editor itemising labour and material lines with costs, markup and customer pricing; and an invoice on a phone showing the share of the estimate total it covers.

    The records that cross — estimates, their change orders, and the invoices raised against them

  • A QuickBooks Desktop application-certificate dialog, in which an application calling itself Projul Sync Manager requests permission to read and modify the company file, with the option to allow access even when QuickBooks is not running selected.

    The desktop connector asking a company file for standing access

In the estimate editor, the note above the pricing is marked as not syncing to QuickBooks. The integration states what it will not carry, in the place where someone is typing it.

What crosses the boundary

  • Customers
  • Estimates
  • Change orders
  • Invoices
  • Line items and costs
  • Payments, coming back the other way
  • Sync state belongs to the record

    Every synced record carries its own status, service and last attempt, so the interface says “last synced” or “failed sync” on the invoice that failed rather than raising one banner over the whole company.

  • Order is a real dependency

    An invoice cannot exist in the accounting service before its customer does. That dependency is surfaced where the record is edited, so the failure is explained at the point where somebody can act on it.

  • Their constraints become our validation

    QuickBooks limits how long a document number, a name, a SKU or a custom field may be. Those limits are enforced in Projul’s own forms, so a name is refused while it is being typed rather than at sync time, hours later.

  • One shape, argued per company

    Estimates sync itemised or non-itemised — set once for the company, or left to be decided by each estimate, because both kinds of business are real.

  • Not everything should cross

    Fields that are the product’s and not the ledger’s — the covering note at the head of an estimate, among others — are marked in the editor as not syncing, rather than quietly going missing.

  • Payments can be handed back

    Payments entered in QuickBooks flow into Projul on a webhook, and a company that would rather keep the two ledgers apart can turn that off without giving up the rest of the sync.

The rule the whole integration is built on: fail on the record, not on the company. A broken sync should tell one person which invoice to fix, not tell an office that accounting is down.

Leading the work

Four people, one application, no pause in service.

The front-end team was three React developers and a UX designer. On an application this wide, the leadership problem is consistency: four people can each build a correct screen and still produce four different products if the component and state conventions are not held.

What held it together

  • Shared component conventions
  • One state-management model, applied consistently
  • A single data-fetching layer
  • End-to-end tests over the critical journeys
  • Error monitoring in production

Engineering scope

What I worked on.

  1. Second-generation web application architecture
  2. Project and customer workflows
  3. Contractor and scheduling operations
  4. Frontend performance and state management
  5. GraphQL, REST and end-to-end testing

Why these decisions mattered

The trade-offs behind the rebuild.

  • Separate server and client state

    Decision
    Fetched data and interface state live in different systems.
    Why
    Treating a cache of remote data as application state is the root of most large-front-end performance problems.
  • Virtualise before optimising

    Decision
    Render only visible rows in long lists.
    Why
    No amount of component tuning fixes rendering several thousand rows nobody is looking at.
  • Heavy work off the render thread

    Decision
    Expensive computation moved off the main thread.
    Why
    An unresponsive interface reads as a broken product, whatever the computation was for.
  • Contain the protocol mixture

    Decision
    GraphQL and REST both consumed, behind one data layer.
    Why
    A client cannot wait for a backend migration, but it also should not be shaped by one.
  • Sync state on the record

    Decision
    Every record synced to QuickBooks carries its own status and last attempt.
    Why
    A company-wide banner tells an office that something is wrong; a status on the invoice tells one person which invoice to fix.
  • End-to-end tests on the critical paths

    Decision
    Automated coverage of the journeys the business runs on.
    Why
    A rewrite is judged on whether the daily paths still work, not on whether the architecture is nicer.

Impact

The verified result.

Improved project performance by 80%.

Technology

The technical surface.

  • React
  • React Query
  • Redux Toolkit
  • Web Workers
  • GraphQL
  • REST
  • QuickBooks API
  • Selenium

Next: Confidential Fitness & Wellbeing Platform

Back to all work Start a conversation