Audited /dashboard, /dashboard/sites (+ detail), /dashboard/workforce/workers,
/dashboard/workforce/on-site, /dashboard/plant/vehicles (+ detail),
/dashboard/plant/equipment (+ detail), /dashboard/plant/fuel-logs,
/dashboard/financial/purchase-orders (+ detail),
/dashboard/financial/delivery-notes/inbox, /dashboard/financial/suppliers,
and /dashboard/chat at 375x812 and 768x1024 using a scroll-right test to
catch real horizontal page overflow (as opposed to a table's own,
intentional internal scroll).
Two repeating bug patterns accounted for every overflow found:
1. A page-specific `right={<div className="flex items-center gap-2">...}`
passed to Topbar can carry two or three labelled buttons (edit/import/
merge/back etc.). Without `flex-wrap` they don't fit a 375px screen in
one row and push the whole page sideways. Fixed on the vehicles,
equipment, and site detail pages, plus their list-page counterparts
(vehicles fuel-view, equipment plant-view) — all five instances of
this exact pattern. Same fix applied to a delivery-note card's button
cluster on the PO detail page, and to Topbar's own right-side
container as a general backstop.
2. `Intl.NumberFormat("en-ZA", ...)` joins currency/number groups with
non-breaking spaces, so a wide value like "R 24 988 663" can't wrap.
In a 3-up grid (site cards' Contract/Spent/Remaining) that overflowed
the column; fixed by stacking those to 1-up below `sm`. In a 2-up grid
(PO detail KPI tiles) added `min-w-0` so the tile clips instead of
growing the grid track past the viewport.
Also: the shared Dialog now caps at `max-h-[90dvh]` with internal
scroll (a dialog taller than the phone viewport had no way to reach
its close button or footer before), and the sidebar hamburger button
was overlapping the Topbar's title on every page below `lg` — reserved
space for it instead.
Tables already scroll horizontally inside their own card via the
shared `<Table>` component or an `overflow-auto` wrapper, and KPI
grids already collapse responsively — both were fine everywhere checked.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|---|---|---|
| .claude | ||
| .freebuff | ||
| .opencode/openwork/inbox/chat-attachments | ||
| .playwright-mcp | ||
| .superpowers/sdd/2026-09-18-hybrid-delivery-note-ingestion | ||
| docs | ||
| public | ||
| RAW_DOCS | ||
| scripts | ||
| src | ||
| .dockerignore | ||
| .env.coolify | ||
| .env.local.example | ||
| .gitignore | ||
| AGENTS.md | ||
| cedar_gate_fuel_logs_sep2026.csv | ||
| cedar_gate_summary_sep2026.csv | ||
| CLAUDE.md | ||
| cooler.env | ||
| COOLIFY.md | ||
| Copy of THE HILLS WAGES HRS AUGUST 2026.xlsx | ||
| deployment-vptid1uo0jnz2q4t1mcbqjj9-all-logs-2026-08-19-00-23-06.txt | ||
| docker-compose.yml | ||
| Dockerfile | ||
| eslint.config.mjs | ||
| fuel_logs_duplicates_2026-09-10.csv | ||
| next.config.js | ||
| package-lock.json | ||
| package.json | ||
| postcss.config.mjs | ||
| README.md | ||
| tsconfig.json | ||
| Vehicle Fuel Summary for Mega Civils- The Hills- 01_09_2026 to 22_09_2026.csv | ||
| vitest.config.ts | ||
MEGA INFRASTRUCTURE — Operations Dashboard
A Next.js 16 + TypeScript + Tailwind v4 operations dashboard for a South African civil construction company, backed by PocketBase. Tracks, per construction site: contracts, employees & timesheets, vehicles (fuel + plant), fuel logs, plant hour meters, fixed costs, income claims, purchase orders + line items, and delivery notes with OCR-driven receipting. Includes an LLM agent ("Ask") that reads the same data the dashboard uses, measures anything with free queries and its own charts, reads any file you attach, remembers lasting facts, changes data through confirm-first proposed actions with undo, and files its own PDF reports and app change requests — plus a read-only MCP server.
Default login PIN is
2468. ChangeNEXT_PUBLIC_APP_PINin production. There is no PIN bypass — the old?demo=1shortcut was removed on 2026-09-26.
Quick start
cp .env.local.example .env.local
# edit NEXT_PUBLIC_POCKETBASE_URL, POCKETBASE_ADMIN_*, N8N_*
npm install
npm run dev # next dev -H 0.0.0.0 -p 6004 --webpack
Production build / start:
npm run build # icons + og-image + next build --webpack
npm run start # next start -H 0.0.0.0 -p 3000
Docker / Coolify: see COOLIFY.md and the
Dockerfile.
MCP server (stdio, read-only):
npm run mcp # uses the PocketBase creds in .env.local
Modules
The sidebar (src/components/layout/sidebar.tsx) has five top-level
pages and three groups. Each is documented in
docs/modules.md.
| Path | Module | What it shows |
|---|---|---|
/dashboard |
Overview | KPI tiles, fuel-attention card grouped by site, 7-day rain outlook, every site |
/dashboard/sites, /dashboard/sites/[id] |
Sites | Per-site profitability, burn, P&L, payroll, weekly & monthly trends |
/dashboard/workforce |
Workforce | Landing: people cost this week |
/dashboard/workforce/workers[/[employeeId]] |
Workers | Every worker, hours, cost, printable QR badge |
/dashboard/workforce/timesheets |
Timesheets | Every entry from every source; spreadsheet import |
/dashboard/workforce/on-site |
On site now | Today's MEGA Clock attendance + Needs attention (missed scan-outs) |
/dashboard/plant |
Plant | Landing: fuel pumped, hours run, cost |
/dashboard/plant/vehicles[/[vehicleId]] |
Vehicles (L/km) | Fleet fuel efficiency, variance, dismissable attention |
/dashboard/plant/equipment[/[vehicleId]] |
Equipment (L/h) | Plant hour-meters, L/h, billable value |
/dashboard/plant/fuel-logs |
Fuel logs | Every fuel log across both buckets |
/dashboard/plant/map |
Map | Leaflet view of each vehicle's last scanned position |
/dashboard/financial |
Financial | Landing: open PO value, slips received, uploads waiting |
/dashboard/financial/purchase-orders[/[poId]] |
Purchase Orders | Every PO, fulfilment %, line editing, verify |
/dashboard/financial/delivery-notes (+ /inbox, /work-queue, /document/[id]) |
Delivery Notes | Upload → OCR → match → post pipeline and its review queues |
/dashboard/financial/suppliers[/[supplierId]] |
Suppliers | Ordered / delivered / outstanding per supplier |
/dashboard/chat |
Ask | Agent: free queries + charts, files, memory, proposed data changes with undo, PDF reports, images |
/dashboard/change-requests |
Change requests | App changes ("can we add this page/column/button?") Ask has logged for the developer |
/clock |
MEGA Clock | Installable phone PWA: worker badge scan in/out + vehicle hour-meter scan |
Old paths (/dashboard/employees, /dashboard/fuel[/id],
/dashboard/purchasing[/id], /dashboard/plant/[id]) still work — they
redirect to the pages above and keep ?site=.
Documentation
| Document | What's in it |
|---|---|
docs/architecture.md |
Runtime topology, the two PB clients, rendering model, auth model, build/deploy |
docs/data-model.md |
Every PocketBase collection, fields, relations, the project_code denormalisation, indexes, API rules |
docs/modules.md |
One section per dashboard module — what it reads, computes, lets the user do |
docs/data-flows.md |
End-to-end ingestion pipelines with sequence diagrams (timesheets, fuel, plant, PO, DN) |
docs/api-reference.md |
Every API route — method, auth, request/response, error cases |
docs/operations.md |
Env vars, scripts, tests, feature flags, deploy / smoke test |
docs/multi-site-assessment.md |
The main deliverable — will this app hold up at 50 sites? Where does it break, and what to fix in what order? |
docs/clock-daily-exceptions-rollout.md |
MEGA Clock new-day scans and the missed scan-out correction flow |
docs/pwa-install-verification.md |
Checking the dashboard / clock PWAs install correctly on iOS + Android |
docs/n8n/ |
The n8n PO-ingest and DN-OCR workflows |
docs/setup/env-and-mcp.md |
Local env + MCP client setup |
The docs/superpowers/ directory has older design notes; treat as
historical, not authoritative.
Tech stack
- Next.js 16 (App Router, modified build — see
AGENTS.md) - React 19 + TypeScript 5.7
- Tailwind v4 (via
@tailwindcss/postcss) - PocketBase 0.27 (PocketBase v0.22+ schema shape, see
scripts/pb-bootstrap-schema.py) - SheetJS (xlsx) + PapaParse (csv) for imports
- Recharts for charts
- Vitest for tests
@ducanh2912/next-pwa(Webpack-only —next dev/buildpass--webpackto support it)- Anthropic or MiniMax as the chat LLM provider
API surface (summary)
| Route group | Auth | Notes |
|---|---|---|
GET /api/health |
none | Docker healthcheck |
POST /api/sites, PATCH /api/sites/[id], POST /api/sites/[id]/fixed-costs |
server-only | Sites CRUD |
POST / PATCH / DELETE /api/vehicles[ /[id]] |
server-only | Vehicles CRUD (unified fuel + plant) |
POST /api/plant-hours/import, GET /api/plant-hours/template, GET /api/plant-hours/exception-report/[sheetId], DELETE /api/plant-hours/[id] |
server-only | Plant hours ingest + reporting |
POST /api/timesheets/import |
server-only | Timesheet ingest (three formats auto-detected) |
POST /api/fuel-logs/import |
server-only | Fuel log ingest |
POST /api/purchase-orders/upload, POST /api/purchase-orders/process-batch (n8n), GET /api/purchase-orders/by-number/[poNumber]/items, PATCH /api/purchase-orders/[id]/supplier |
server-only / x-ingest-secret |
PO ingest pipeline |
POST /api/delivery-notes/upload, GET /api/delivery-notes/[id]/slip (n8n), POST /api/delivery-notes/[id]/ocr-result (n8n), POST /api/delivery-notes/[id]/assign-po |
server-only / x-ingest-secret |
DN pipeline + manual fallback |
GET / POST /api/data-import-batches/[id]/status|retry |
server-only | Batch lifecycle |
POST /api/records/[collection]/[id]/verify |
server-only | Verify toggle for POs and DNs |
POST /api/purchase-orders/ingest, GET …/[id]/status, POST …/[id]/retry, GET …/[id]/document, GET …/[id]/pages/[n], POST …/[id]/items, PATCH / DELETE …/[id]/items/[itemId] |
server-only / x-ingest-secret (ingest) |
PO queue, source document, and manual line edits (blocked once verified) |
POST /api/dn-batches/tick (cron), …/[id]/rerun, …/[id]/dismiss, …/[id]/assembly, GET …/[id]/pages/[n], …/pages/[n]/dismiss |
x-ingest-secret (tick) / server-only |
Staged delivery-note batches |
PATCH /api/dn-batches/[id]/notes/[noteId], …/resolve-entities, …/resolve-item, …/post, …/dismiss, …/correct-receipt |
server-only / RECEIPT_CORRECTION_SECRET (correct-receipt) |
Correct, match, post, or stand down one candidate note |
POST /api/suppliers, PATCH /api/suppliers/[id] |
server-only | Supplier create / rename |
POST /api/vehicles/import-summary |
server-only | Sync the Buildsite Vehicle Fuel Summary CSV into vehicles |
POST /api/vehicles/[id]/merge |
server-only | Merge a duplicate vehicle into another (dry run unless confirm) |
GET /api/pb-files/[collection]/[recordId]/[...filename] |
server-only | Same-origin proxy for PocketBase files (PDF slips in iframes) |
POST /api/chat |
ENABLE_CHAT=true, x-ask-user identifies (no real auth yet) |
SSE chat stream — thread/action/chart/image/pdf events |
GET /api/ask/users, GET/POST /api/chat/threads[/[id]], POST /api/chat/attachments, GET/POST /api/ask/memories[/[id]] |
ENABLE_CHAT=true + x-ask-user |
Ask's picker, threads, attachments, memory |
GET /api/agent/actions/[id], POST …/{apply,reject,undo} |
ENABLE_CHAT=true + x-ask-user; confirm routes are same-origin only |
Propose → confirm → apply/undo, with a before/after snapshot |
GET/PATCH /api/ask/change-requests[/[id]] |
server-only | App change requests Ask filed for the developer |
GET /api/clock/employee-lookup, POST /api/clock/scan, POST /api/clock/vehicle-lookup, POST /api/clock/vehicle-scan |
server-only | MEGA Clock PWA: badge clock-in/out + vehicle hour-meter scan |
POST /api/clock/events/[id]/correct |
same-origin + x-app-pin = APP_PIN |
Record the real leaving time for a missed scan-out |
Full reference: docs/api-reference.md.
Tests
npm run typecheck # tsc --noEmit
npm run test:run # vitest run
npm run lint # eslint . — not yet clean, see docs/operations.md §4.1
The metric helpers under src/lib/metrics/*.test.ts pin every
computation the dashboard exposes. Run after any change to the
financial roll-up.
License
Proprietary. Internal use only.