Paulo Ferri Souza
All case studies

Case management system for a law firm

A web system that took a law firm from paper files to a fully digital workflow: 3,500 clients, 6,000 cases, a Kanban board for case status, PDF petition generation and spreadsheet reports.

The firm works fully digital; six people use the system daily.

Role
Full-stack developer — sole developer
Period
Jan 2026 – present
Stack
  • Next.js
  • TypeScript
  • TanStack Query
  • Tailwind CSS
  • Hono
  • Cloudflare Workers
  • Drizzle ORM
  • Cloudflare D1 (SQLite)
Product walkthrough · 51 sec · Portuguese interface, no audio. Recorded with fictitious data.

The problem

The firm handles public-sector employment claims, with about 3,500 clients and 6,000 cases. Everything was kept on paper, in printed files stored in a cabinet, so any request (a client call, a payment update, a document to prepare) started with finding the right folder by hand.

What I built

My first step was moving the workflow into Trello: one list per case status, one card per case. It worked as an interim fix and showed how the firm actually moved cases between stages.

I then built a case management system as the only developer: a Next.js front end and a Hono API running on Cloudflare Workers, with Drizzle ORM and Cloudflare D1 (SQLite). The main parts are:

  • Case board. A Kanban board with the same columns the team used in Trello, grouped by stage (filing, proceedings, enforcement, payment, release of funds, closing).
  • Client and case records. Search, dynamic filters and bulk import from spreadsheets, with a line-by-line report of rows that failed or were duplicates.
  • Petition generation. Petitions for payment requests (RPV) and fund release (alvará) are generated as PDFs from the case and client data.
  • Reports. Spreadsheet exports, such as every case waiting on a payment request, with a second sheet listing each client's bank details and the blank cells that still need to be collected.
  • Reminders and notes attached to each case, with tasks grouped as overdue, today and upcoming, plus admin, lawyer and assistant user roles.

The walkthrough above follows client search, case details, a drag-and-drop board update, petition generation, reminders and the switch to dark mode. The gallery below shows the same application with English explanations and fictitious demo data.

Technical decisions

  • Keeping the Trello model. Case statuses are stored as the original Trello list names and matched exactly. Labels, order and grouping of columns come from configuration tables served by the API, so the firm can rename or reorganise columns without a code change. Cards move by drag and drop (dnd-kit) with optimistic updates in TanStack Query, and each column is virtualised (TanStack Virtual) because some hold hundreds of cases.
  • Petitions rendered in the browser. PDFs are built with pdfmake from the data already in the system. A shared base module holds what every petition has in common: letterhead, signatures, the date written out in full, and a signature block that never breaks across pages. Each template only defines its own content. Preview scripts render the templates to PDF so layout changes can be checked without opening the app.
  • Reading court documents. Dropping a filing receipt from the court system (eproc) fills in a new case: case number, parties, claim value, court and judge. Text is extracted with pdf.js. When the PDF is a scan, the app falls back to OCR with Tesseract in the browser and corrects common letter-for-digit misreads in case numbers.
  • Matching the official gazette. Payment lists published in the official gazette can be pasted in as plain text. Names are normalised (accents, ID numbers and table headers removed), matched against the client base in one request, and exported as a spreadsheet of clients to contact.

Outcome

The firm no longer relies on paper files. Cases, clients and petitions are handled in the system, and six people use it every day. Lists that used to require going through folders by hand, such as which clients have payments to receive, now come from a filter or a pasted gazette page.