docs/modules.md §7.1: threads, memory, files, free queries/virtual datasets, the propose/confirm/apply/undo gateway, both PDF export paths (manual answer export and Ask-made reports via make_pdf_report), images, and change requests — with the actor/x-ask-user model and the three new PocketBase collections. docs/data-model.md §4.17: field-by-field ask_threads/ask_messages/ ask_log reference, cross-linked from modules.md via explicit anchors. docs/api-reference.md §12/§12b: /api/chat's new body shape and its thread/action/chart/image/pdf SSE events, plus every new agent-actions route (picker, threads, attachments, confirm-card actions, memories, change requests) and their auth-matrix entries; the pb-files proxy entry now notes its ask_messages admin-token path. README.md: the intro, the modules table (adds Change requests), and the API surface summary now describe an agent that acts, not just a read-only chat. Also marks Tasks 1–12 and 9b's implementation checkbox done in the plan doc (docs/superpowers/plans/2026-09-26-ask-agent-actions.md) — the remaining unchecked box is 9b's live check, skipped because ask_threads/ask_messages/ask_log aren't applied on the target PocketBase yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
165 lines
9.8 KiB
Markdown
165 lines
9.8 KiB
Markdown
# 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`.** Change `NEXT_PUBLIC_APP_PIN` in
|
|
> production. There is no PIN bypass — the old `?demo=1` shortcut was
|
|
> removed on 2026-09-26.
|
|
|
|
---
|
|
|
|
## Quick start
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
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`](./COOLIFY.md) and the
|
|
[`Dockerfile`](./Dockerfile).
|
|
|
|
MCP server (stdio, read-only):
|
|
|
|
```bash
|
|
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`](./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`](./docs/architecture.md) | Runtime topology, the two PB clients, rendering model, auth model, build/deploy |
|
|
| [`docs/data-model.md`](./docs/data-model.md) | Every PocketBase collection, fields, relations, the `project_code` denormalisation, indexes, API rules |
|
|
| [`docs/modules.md`](./docs/modules.md) | One section per dashboard module — what it reads, computes, lets the user do |
|
|
| [`docs/data-flows.md`](./docs/data-flows.md) | End-to-end ingestion pipelines with sequence diagrams (timesheets, fuel, plant, PO, DN) |
|
|
| [`docs/api-reference.md`](./docs/api-reference.md) | Every API route — method, auth, request/response, error cases |
|
|
| [`docs/operations.md`](./docs/operations.md) | Env vars, scripts, tests, feature flags, deploy / smoke test |
|
|
| [`docs/multi-site-assessment.md`](./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`](./docs/clock-daily-exceptions-rollout.md) | MEGA Clock new-day scans and the missed scan-out correction flow |
|
|
| [`docs/pwa-install-verification.md`](./docs/pwa-install-verification.md) | Checking the dashboard / clock PWAs install correctly on iOS + Android |
|
|
| [`docs/n8n/`](./docs/n8n/) | The n8n PO-ingest and DN-OCR workflows |
|
|
| [`docs/setup/env-and-mcp.md`](./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`/`build` pass
|
|
`--webpack` to 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`](./docs/api-reference.md).
|
|
|
|
---
|
|
|
|
## Tests
|
|
|
|
```bash
|
|
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.
|