Skip to main content

UI Patterns Guide

RoundTrip Dispatcher UI — Full Feature Set

Version: 1.0 | Date: March 2026 | Status: Approved


Overview

M8 builds three feature areas on top of the M7 foundation: client management, inventory management, and billing. Every pattern needed already exists in the codebase — M8 is about applying them consistently rather than inventing new ones.


Component Reuse Inventory

Every M8 screen should use these shared components rather than building inline equivalents:

ComponentLocationUsed in M8 for
PageHeadercomponents/common/Every page title + subtitle + action button
SurfacePanelcomponents/common/Every card/panel wrapper
SectionHeadercomponents/common/Every panel header row
CountBadgecomponents/common/Counts in section headers
StatusBadgecomponents/common/Ticket status in service history
PriorityBadgecomponents/common/Ticket priority in service history
FieldLabelcomponents/common/All form field labels
MetaRowcomponents/common/Client detail sidebar rows

Established Patterns — Apply Consistently

List Page Pattern (from TicketListPage)

  • PageHeader with title, subtitle (total count), and primary action button
  • Filter bar in a SurfacePanel — search input + select dropdowns + clear button
  • Data table in a SurfacePanel — header row + skeleton rows + empty state + data rows
  • Priority left-border coloring on rows where priority is relevant
  • Pagination below the table when totalPages > 1
  • Every row navigates to detail on click

Detail Page Pattern (from TicketDetailPage)

  • Back button (navigate(-1)) with ArrowLeft icon
  • Page title from primary identifier (client name, item name, invoice number)
  • Two-column grid: main content left (variable width), metadata sidebar right (320px fixed)
  • SurfacePanel + SectionHeader for every content section
  • MetaRow for sidebar metadata fields
  • Empty states within sections (no notes, no attachments etc.)

Slide-Over Modal Pattern (from CreateTicketModal)

  • Dark navy header with Barlow Condensed title
  • Scrollable body with SectionDivider groupings
  • FieldLabel for every field
  • Footer with Cancel + primary action button (loading/success states)
  • Closes and resets state on success, navigates to created resource

Hook Pattern (from features/tickets/hooks/)

  • One hook file per major operation: useClientList, useClientDetail, useCreateClient etc.
  • All hooks live in features/<name>/hooks/
  • TanStack Query for all server state
  • useDebounce for search inputs (300ms)
  • Mutation hooks accept an onSuccess callback for navigation

API Module Pattern (from lib/api/)

  • One file per feature domain: clientApi.ts, inventoryApi.ts, billingApi.ts
  • All types co-located in the same file
  • Named export object: export const clientApi = { ... }

M8 Feature Breakdown

TRA-94 — Client Management

Pages/components:

  • ClientListPage — list with search, commercial/archived filters, "New Client" button
  • ClientDetailPage — two-column detail with service addresses, contact info, service history tab
  • CreateClientModal — slide-over form (name, contact, primary address)
  • EditClientModal — slide-over form (contact info only — address managed separately)
  • AddAddressModal — slide-over form (add service address to existing client)

New patterns introduced:

  • Tab navigation within a detail page — service history is a separate API call loaded on tab switch, not on page load. Prevents loading all history on every detail view.
  • Archived state — archived clients show a muted visual treatment with an "Archived" badge. The ArchiveClient action is TenantAdmin only.

API endpoints used:

  • GET /v1/clients — list with filters
  • GET /v1/clients/{id} — detail with addresses
  • GET /v1/clients/{id}/service-history — paginated history (loaded on tab switch)
  • POST /v1/clients — create
  • PATCH /v1/clients/{id} — update contact info
  • POST /v1/clients/{id}/addresses — add service address
  • PATCH /v1/clients/{id}/archive — archive (TenantAdmin only)

TRA-95 — Inventory Management

Pages/components:

  • InventoryListPage — list with search, category filter, low-stock filter toggle
  • InventoryDetailPage — two-column detail with stock history movements
  • CreateInventoryItemModal — slide-over form
  • AdjustStockModal — slide-over form (positive delta = receive, negative = consume, reason required)

New patterns introduced:

  • Low stock indicator — items at or below reorder threshold get a red/amber visual treatment in the list. A "Low Stock" filter toggle shows only those items.
  • Stock adjustment — the adjustment form handles both receiving stock (positive number) and manual consumption (negative number). The API takes a delta, not an absolute value.

API endpoints used:

  • GET /v1/inventory — list with filters
  • GET /v1/inventory/{id} — detail with stock movement history
  • POST /v1/inventory — create item
  • PATCH /v1/inventory/{id} — update item
  • POST /v1/inventory/{id}/adjust — stock adjustment
  • PATCH /v1/inventory/{id}/archive — archive

TRA-96 — Billing

Pages/components:

  • InvoiceListPage — list with status filter (Draft, Sent, Paid, Void)
  • InvoiceDetailPage — full detail with line items, totals, payment status
  • GenerateInvoiceModal — select completed ticket, preview line items, confirm generation
  • SendInvoiceModal — confirm send (email/SMS), shows recipient details

New patterns introduced:

  • PDF download — invoice detail has a "Download PDF" button calling GET /v1/invoices/{id}/pdf
  • Status workflow — invoices move through Draft → Sent → Paid (or Void). Transition buttons appear based on current status.
  • Generate from ticket — the generate modal shows a ticket picker (completed tickets only) and previews the auto-populated line items before creating.

API endpoints used:

  • GET /v1/invoices — list with status filter
  • GET /v1/invoices/{id} — detail with line items
  • GET /v1/invoices/{id}/pdf — download PDF
  • POST /v1/invoices/generate — generate from completed ticket
  • POST /v1/invoices/{id}/send — send via email/SMS
  • POST /v1/invoices/{id}/record-payment — mark as paid
  • POST /v1/invoices/{id}/void — void invoice

Directory Structure for M8

src/features/
├── clients/
│ ├── hooks/
│ │ ├── useClientList.ts
│ │ ├── useClientDetail.ts
│ │ ├── useClientServiceHistory.ts
│ │ ├── useCreateClient.ts
│ │ ├── useUpdateClient.ts
│ │ └── useAddServiceAddress.ts
│ ├── ClientListPage.tsx
│ ├── ClientDetailPage.tsx
│ ├── CreateClientModal.tsx
│ ├── EditClientModal.tsx
│ └── AddAddressModal.tsx
├── inventory/
│ ├── hooks/
│ │ ├── useInventoryList.ts
│ │ ├── useInventoryDetail.ts
│ │ ├── useCreateInventoryItem.ts
│ │ └── useAdjustStock.ts
│ ├── InventoryListPage.tsx
│ ├── InventoryDetailPage.tsx
│ ├── CreateInventoryItemModal.tsx
│ └── AdjustStockModal.tsx
└── billing/
├── hooks/
│ ├── useInvoiceList.ts
│ ├── useInvoiceDetail.ts
│ ├── useGenerateInvoice.ts
│ └── useSendInvoice.ts
├── InvoiceListPage.tsx
├── InvoiceDetailPage.tsx
├── GenerateInvoiceModal.tsx
└── SendInvoiceModal.tsx

Build Order

Start with clients (TRA-94) — most complete API, familiar patterns, no new UI paradigms except the tab. Then inventory (TRA-95) — straightforward, introduces the stock adjustment pattern. Then billing (TRA-96) — most complex, benefits from patterns established in clients and inventory.

Within each feature, always build in this order:

  1. API module + types (clientApi.ts)
  2. List hook + list page
  3. Detail hook + detail page
  4. Create/edit modal + mutation hook