Adds /dashboard/plant/map: a Leaflet view of every vehicle scanned from the MEGA Clock kiosk, plotted at its last known position. Schema - vehicles.last_latitude / last_longitude / last_scanned_at / last_location_accuracy_m denormalised off fuel_logs so the map page renders every vehicle in a single query. Same best-effort pattern as attention_dismissed_at; updated by /api/clock/vehicle-scan after every successful scan. PB init defaults new number cols to 0 (not null) — filter uses 'last_latitude > 0 || last_latitude < 0' to mean 'real fix'. - Indexed via idx_vehicles_last_scanned. Map UI - Custom SVG markers per unit_type: car silhouette for fuel, tractor silhouette for plant, 24x24, drop-shadowed. - Clustering via react-leaflet-cluster over Leaflet.markercluster. Cluster icons colour-ramp with size (green->sky->amber->rose) and scale 32->54px. - Three-button base-layer switcher: Street (OSM), Dark (ESRI Dark Gray Canvas — CartoDB watermarks tiles without a key), Satellite (ESRI World Imagery). Last choice persists in localStorage. - Site pins: small purple map-marker divIcon, click flies the map to zoom level 15. - Empty-state is a floating chip on the map rather than a full-page card so the map is always visible. APIs - /api/clock/vehicle-scan: stores the GPS fix on the fuel_logs row AND denormalises onto the vehicle when present; missing-coord scan leaves last_* untouched. Sidebar: new Map entry under Plant group.
131 lines
5.8 KiB
Markdown
131 lines
5.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 chat ("Ask") with a read-only
|
|
tool layer over the same data, and a read-only MCP server.
|
|
|
|
> **Default login PIN is `2468`.** Change `NEXT_PUBLIC_APP_PIN` in
|
|
> production. Append `?demo=1` to any URL to bypass the PIN during
|
|
> development.
|
|
|
|
---
|
|
|
|
## 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 [`docs/COOLIFY.md`](./docs/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 lists seven modules. Each is documented in
|
|
[`docs/modules.md`](./docs/modules.md).
|
|
|
|
| Path | Module | What it shows |
|
|
| --- | --- | --- |
|
|
| `/dashboard` | Overview | KPI tiles, fuel-attention rail grouped by site, every site summary |
|
|
| `/dashboard/sites` and `/dashboard/sites/[id]` | Sites | Per-site profitability, burn, P&L, payroll, weekly & monthly trends |
|
|
| `/dashboard/employees` | Workforce | Per-employee hours, rates, weekly trend, casual-matrix import |
|
|
| `/dashboard/plant` and `/dashboard/plant/[vehicleId]` | Plant & Equipment | Hour-meter history, weekly logs, billable rate |
|
|
| `/dashboard/plant/map` | Fleet Map | Live Leaflet view of every vehicle's last scanned position |
|
|
| `/dashboard/fuel` and `/dashboard/fuel/[vehicleId]` | Fleet & Fuel | Variance per log, over-limit flags, dismissable attention |
|
|
| `/dashboard/purchasing` and `/dashboard/purchasing/[poId]` | Purchasing | Every PO, DN fulfilment %, OCR exception inbox |
|
|
| `/dashboard/chat` | Ask | Read-only LLM chat with the same metrics the dashboard uses |
|
|
|
|
---
|
|
|
|
## 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? |
|
|
|
|
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/[batchId]`, `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/chat` | `ENABLE_CHAT=true` (no user auth) | SSE chat stream |
|
|
| `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 |
|
|
|
|
Full reference: [`docs/api-reference.md`](./docs/api-reference.md).
|
|
|
|
---
|
|
|
|
## Tests
|
|
|
|
```bash
|
|
npm run typecheck # tsc --noEmit
|
|
npm run test:run # vitest run
|
|
```
|
|
|
|
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.
|