No description
Find a file
Dave c8b7e1f51b feat(rep): visit check-in PWA — phone GPS, offline outbox
The rep app: open it on site, it finds who's around you, tick who you
saw, done. Third instance of the per-route-group PWA pattern already
running here (/m boss phone, /counter), so it reuses MobileAuthGate and
gets its own manifest + service worker.

Phone GPS is the primary position source, not the vehicle tracker. The
tracker knows where the bakkie is; the phone knows where the rep is,
and those diverge by a few hundred metres once the rep walks into the
plant. GPS also needs no network, which matters because mine shafts
have poor signal. The tracker is the secondary path
(GET /api/v1/visits/position) for when location is denied or
unavailable. The independently-collected vehicle trail corroborates
check-ins afterwards, so honesty doesn't depend on the phone.

Matching runs on the phone, not the server. The app caches a compact
customer snapshot and runs rankNearbyCustomers locally, so there's one
code path that behaves identically online and off — rather than a
server path plus an offline fallback that only gets exercised in the
places it's hardest to debug.

- (rep)/rep/            layout, page, check-in view, outbox
- visits-service.ts     getCustomerSnapshot, recordVisits
- POST /api/v1/visits           record a check-in
- GET  /api/v1/visits/customers snapshot for the offline cache
- GET  /api/v1/visits/position  tracker fallback
- public/manifest-rep.json, public/sw-rep.js, pickSwPath() branch

These routes authenticate with the browser session, not AGENT_API_KEY —
the caller is a logged-in rep. The rep id comes from the validated
session rather than the request body, so a client can't log a visit as
someone else.

The outbox uses localStorage rather than IndexedDB deliberately: the
payloads are tiny and localStorage is synchronous, which matters when
the rep hits Save and immediately pockets the phone. Entries are only
removed once the server acknowledges them, so a failed flush retries
instead of dropping a visit — duplicates are preferred over loss, and
surfaced rather than merged. Permanently-rejected entries (4xx other
than 401/408/429) are dropped so one bad row can't wedge the queue.

recordVisits writes each visit independently: a stale customer id
shouldn't cost the other four visits in a queued batch.

BLOCKED until one PB admin change: rep_visits.position_source does not
yet allow "phone" — verified against the live instance, PB returns
400 "Invalid value phone". Add it to the select. Separately,
accuracy_m is silently accepted and discarded (PB ignores unknown
fields), so GPS accuracy is thrown away until that number field is
added; not blocking.

47 new tests. Verified on :4001: /rep serves 200 with the right
metadata, manifest-rep.json and sw-rep.js are served.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 21:09:27 +02:00
.agents/skills/wfdash-giellie feat: add driverless collection confirm and new order tooling 2026-08-12 01:13:46 +02:00
.audit-screenshots feat(tv-dashboard): enhance TV dashboard with driver stops and performance metrics 2026-08-19 10:16:49 +02:00
.claude chore: add dev-server launch config and mobile verification screenshots 2026-08-29 02:31:24 +02:00
.design-review fix, feat, style: standardize quote numbers, polish UI 2026-07-10 22:21:50 +02:00
.e2e-tmp feat(invoicing): add QuickBooks invoicing flow and print functionality 2026-07-09 09:44:55 +02:00
.freebuff feat(quotes): enhance quote creation with new fields and error handling 2026-08-26 23:55:30 +02:00
.kilo feat(delivery-notes): standardize delivery-note letterhead across PDFs 2026-08-18 11:53:25 +02:00
.mavis/plans fix, feat, style: standardize quote numbers, polish UI 2026-07-10 22:21:50 +02:00
.od-skills fix, feat, style: standardize quote numbers, polish UI 2026-07-10 22:21:50 +02:00
.opencode/openwork/inbox/chat-attachments fix: correct quote expiry handling for PocketBase's 0 default 2026-08-11 08:32:17 +02:00
.playwright-mcp feat(quotes): add "Include older" toggle for searching full quote history 2026-08-20 10:18:44 +02:00
.remember fix: resolve PO bug, add sku, improve webhooks 2026-08-06 13:13:57 +02:00
.superpowers/brainstorm/39145-1783449414 feat(collection-pool): add nearby supplier cluster sort mode 2026-07-07 21:56:29 +02:00
.tmp feat: implement discount functionality in POS system 2026-09-03 12:19:21 +02:00
.vscode
docs feat(rep-visits): FleetAI tracker adapter, nearby-customer matching, stop detection 2026-09-06 14:22:28 +02:00
e2e feat: collection-map updates, todays-stops, quote/approval rework, buyer-tasks, e2e 2026-08-03 09:33:45 +02:00
n8n_workfows chore(agent): baseline — uncommitted work from the local directory 2026-08-26 01:00:23 +02:00
pb-exports feat(visits): office pin tool for seeding customer coordinates 2026-09-06 16:58:07 +02:00
plugin-source feat: add tangerine theme, new docs and plugin stubs 2026-07-14 11:48:10 +02:00
public feat(rep): visit check-in PWA — phone GPS, offline outbox 2026-09-06 21:09:27 +02:00
reports feat: add driverless collection confirm and new order tooling 2026-08-12 01:13:46 +02:00
scripts feat(rep-visits): FleetAI tracker adapter, nearby-customer matching, stop detection 2026-09-06 14:22:28 +02:00
src feat(rep): visit check-in PWA — phone GPS, offline outbox 2026-09-06 21:09:27 +02:00
--full-page feat(deliveries): enhance delivery notes management and UI 2026-08-17 01:27:03 +02:00
.gitignore docs(pos): add design spec for the slip layout editor 2026-09-04 12:42:45 +02:00
.mcp.example.json
.tmp-inspect-agent-scan.mjs feat(collection-confirm): add stock item edits 2026-08-14 16:39:46 +02:00
AGENT_BRIEF_AUTOFILL_QUOTES.md feat(sprint): add new sprint pricing workflow and supporting code 2026-07-03 07:29:43 +02:00
AGENT_BRIEF_AUTOFILL_QUOTES.v1.long.md feat(sprint): add new sprint pricing workflow and supporting code 2026-07-03 07:29:43 +02:00
AGENTS.md chore: update AGENTS.md 2026-09-02 01:17:05 +02:00
APP_CAPABILITIES.md chore: add logos, opencode config and build assets 2026-07-27 08:21:20 +02:00
bulk_modal.png feat(invoicing): add QuickBooks invoicing flow and print functionality 2026-07-09 09:44:55 +02:00
CLAUDE.md
components.json
deno.lock chore: add logos, opencode config and build assets 2026-07-27 08:21:20 +02:00
Dockerfile
eslint.config.mjs
img1_mid2.png chore(scripts): one-time stock-order loaders + source screenshots 2026-08-03 17:33:28 +02:00
img1_skus_v.png chore(scripts): one-time stock-order loaders + source screenshots 2026-08-03 17:33:28 +02:00
img2_skus_v.png chore(scripts): one-time stock-order loaders + source screenshots 2026-08-03 17:33:28 +02:00
INTERACTION_REVIEW_2026-09-01.md chore: update package.json 2026-09-02 00:41:31 +02:00
invoicing-all.png feat(collection-confirm): add full collection confirm workflow 2026-07-08 01:35:00 +02:00
invoicing-invoiced.png feat(collection-confirm): add full collection confirm workflow 2026-07-08 01:35:00 +02:00
invoicing-ready.png feat(collection-confirm): add full collection confirm workflow 2026-07-08 01:35:00 +02:00
invoicing-search-order.png feat(collection-confirm): add full collection confirm workflow 2026-07-08 01:35:00 +02:00
invoicing-search-po.png feat(collection-confirm): add full collection confirm workflow 2026-07-08 01:35:00 +02:00
invoicing-search.png feat(collection-confirm): add full collection confirm workflow 2026-07-08 01:35:00 +02:00
invoicing_loaded.png feat(invoicing): add QuickBooks invoicing flow and print functionality 2026-07-09 09:44:55 +02:00
invoicing_page.png feat(invoicing): add QuickBooks invoicing flow and print functionality 2026-07-09 09:44:55 +02:00
modal-current.png fix: resolve PocketBase expand projection bug 2026-08-06 01:33:16 +02:00
modal-top-1785968700.png fix: resolve PocketBase expand projection bug 2026-08-06 01:33:16 +02:00
modal-top.png fix: resolve PocketBase expand projection bug 2026-08-06 01:33:16 +02:00
mr8xsu7z-quotes.pb.js refactor: switch hardcoded UI colors to semantic theme tokens 2026-07-07 17:22:03 +02:00
next.config.ts feat(tv): add TV dashboard + refine workflows 2026-08-13 20:55:22 +02:00
nixpacks.toml
nss-hex-nut-din934.xlsx feat: implement discount functionality in POS system 2026-09-03 12:19:21 +02:00
opencode.jsonc chore: add logos, opencode config and build assets 2026-07-27 08:21:20 +02:00
p1-classified.png chore: add stray p1-classified.png + p1-mask.png 2026-08-04 20:26:55 +02:00
p1-mask.png chore: add stray p1-classified.png + p1-mask.png 2026-08-04 20:26:55 +02:00
package-lock.json feat: implement discount functionality in POS system 2026-09-03 12:19:21 +02:00
package.json chore(pos): add pos_slip_layouts PB collection for the slip layout editor 2026-09-04 12:46:17 +02:00
page-overview.png feat(collection-confirm): add full collection confirm workflow 2026-07-08 01:35:00 +02:00
pb_quote_attachments.schema.json
pb_schema (1).json refactor: drop order-level status, shift to item-driven workflow 2026-06-29 13:06:01 +02:00
playwright.config.ts
postcss.config.mjs
README.md chore: add logos, opencode config and build assets 2026-07-27 08:21:20 +02:00
repairs-list-mobile.jpg feat(quotes): add "Include older" toggle for searching full quote history 2026-08-20 10:18:44 +02:00
Screenshot 2026-07-09 at 01.12.32.png feat(invoicing): add QuickBooks invoicing flow and print functionality 2026-07-09 09:44:55 +02:00
single_modal.png feat(invoicing): add QuickBooks invoicing flow and print functionality 2026-07-09 09:44:55 +02:00
sqlite_mcp_server.db refactor: switch hardcoded UI colors to semantic theme tokens 2026-07-07 17:22:03 +02:00
stock-flow-overview.html feat: implement discount functionality in POS system 2026-09-03 12:19:21 +02:00
stock-orders-after.png chore(scripts): one-time stock-order loaders + source screenshots 2026-08-03 17:33:28 +02:00
tsconfig.json feat(orders): enhance order handling with email_message_id deduplication and displayContactName utility 2026-08-21 19:18:02 +02:00
tv-board-1920-v2.jpeg feat(deliveries): enhance delivery notes management and UI 2026-08-17 01:27:03 +02:00
tv-board-1920.jpeg feat(deliveries): enhance delivery notes management and UI 2026-08-17 01:27:03 +02:00
tv-board-current.jpeg feat(deliveries): enhance delivery notes management and UI 2026-08-17 01:27:03 +02:00
tv-board-v3.jpeg feat(deliveries): enhance delivery notes management and UI 2026-08-17 01:27:03 +02:00
tv-board-v4.jpeg feat(deliveries): enhance delivery notes management and UI 2026-08-17 01:27:03 +02:00
vitest.config.ts feat(collection-confirm): add full collection confirm workflow 2026-07-08 01:35:00 +02:00
wfdash-stock-2026-09-02_4-hex-nuts.xlsx feat: implement discount functionality in POS system 2026-09-03 12:19:21 +02:00
WFDASH_NEXTJS.code-workspace

WFDASH — Business CRM

A modern business CRM (customers, companies, suppliers, quotes, orders, driver collection pool, delivery notes, repairs) built as a Next.js 16 / PocketBase re-platform of the legacy Django app.

  • Frontend: Next.js 16 (App Router) + React 19 + TypeScript
  • UI: shadcn/ui (base-nova style), Tailwind CSS 4, Lucide icons, Sonner toasts
  • Maps: Leaflet + react-leaflet (clustering, marker edit, fullscreen, dark tiles)
  • Backend: PocketBase (database, auth, file storage, realtime)
  • MCP: in-app Model Context Protocol endpoint for n8n / external agents

Note

: This is not the Next.js from older training data. APIs, conventions, and file structure differ. Read the relevant guide in node_modules/next/dist/docs/ before writing code, and watch for deprecation notices.


Prerequisites

  • Node.js >= 20.9.0 (enforced by package.json engines)
  • PocketBase binary (the setup script connects to http://127.0.0.1:8090)but in my .env.local we have added the cloud url that its connecting to now at the moment

Quick Start

# 1. Install deps
npm install

# 2. Start PocketBase (in another terminal)
./pocketbase serve

# 3. Configure env (see "Environment Variables" below)
cp .env.local.example .env.local   # or create manually

# 4. Create collections in PocketBase
npm run setup                              # base collections (customers, companies, suppliers, quotes, orders, …)
npm run pb:migrate:orders-v2               # new order_items_v2 + collection_pool collections
npm run pb:migrate:order-status-in-stock   # switch orders.status to the operational enum + add order_items.in_stock

# 5. Run the app
npm run dev

App runs on http://localhost:3000.


Environment Variables

Create .env.local (gitignored) with:

# Public — exposed to the browser
NEXT_PUBLIC_POCKETBASE_URL=http://127.0.0.1:8090


# Server-only — admin used by setup / migration scripts and agent API
PB_ADMIN_EMAIL=admin@example.com
PB_ADMIN_PASSWORD=your_password

# Agent / MCP API auth (see "API & MCP" below)
AGENT_API_KEY=replace_with_secure_token

# Switch which collection the order-items service writes to
# Set to `order_items_v2` after running the migration
ORDER_ITEMS_COLLECTION=order_items

scripts/setup-pocketbase.ts currently has the admin email/password hard-coded — edit it before running, or set the matching env vars there.


NPM Scripts

Script Purpose
npm run dev Start Next.js dev server (webpack)
npm run build Production build
npm run start Run the production build
npm run lint ESLint over the project
npm run setup Create base PocketBase collections from scripts/setup-pocketbase.ts
npm run pb:migrate:orders-v2 Create order_items_v2 + collection_pool collections
npm test Vitest in watch mode
npm run test:run Vitest single run (CI-friendly)
npm run test:coverage Vitest with V8 coverage
npm run e2e Playwright E2E suite (boots next dev on port 3100)
npm run e2e:ui Playwright UI mode
npm run e2e:install One-time: install Playwright browsers + OS deps

See Testing below.


Features / Routes

All routes are file-based under src/app/.

Public (auth)

  • /login — sign in

Protected ((app))

  • /dashboard — overview stats + recent activity
  • /customers, /customers/[id] — CRUD, search, pagination, CSV export
  • /companies, /companies/[id] — CRUD with detail
  • /suppliers — CRUD with tag support
  • /supplier-map — Leaflet map: clustering, marker editing, fullscreen, dark tiles
  • /quotes — list with claim/process workflow
    • /quotes/[id] — detail
    • /quotes/[id]/process — process view
    • /quotes/approvals — approval queue
  • /orders, /orders/[id] — list + detail, PO generation by supplier
  • /collection-pool — driver work queue (pool of items to collect/deliver)
  • /delivery-notes — delivery note management
  • /repairs — repair requests

Cross-cutting

  • Light/dark theme switcher
  • Collapsible sidebar, mobile card views
  • Service worker for offline (registered client-side)

API & MCP

All agent endpoints require Authorization: Bearer <AGENT_API_KEY>.

Method Path Notes
GET /api/v1/health Liveness probe (no auth)
GET /api/v1/orders?page=1&perPage=50&status=new List orders (expands company,customer,quote)
POST /api/v1/orders/from-quote Body: { "quote_id": "..." } — creates an order + copies quote_items into the configured order_items collection; sets item_status=pending, drv_status=unassigned
GET /api/v1/collection-pool?page=1&perPage=50&status=queued List pool records
PATCH /api/v1/order-items/[id]/status Body: { item_status, drv_status, notes, problem_notes } (also accepts driver_status for backwards compat)
POST /api/mcp JSON-RPC style. Methods: tools/list, tools/call

MCP tools exposed

  • orders.list
  • orders.create_from_quote
  • collection_pool.list
  • order_items.update_status

See docs/api-mcp-layer.md for full payload details.


PocketBase Collections

Created by the setup / migration scripts and tracked in docs/pocketbase/:

  • customers — customer database (incl. alternative_email, buyer_email, coordinates)
  • companies — company database
  • suppliers — supplier network (tags, coordinates, closing time)
  • quotes, quote_items — quotes with line items and status workflow
  • orders, purchase_orders — orders + per-supplier POs
  • order_items — legacy order items
  • order_items_v2 — replacement schema (new item_status + drv_status model)
  • collection_pool — driver work queue
  • delivery_notes, delivery_note_items — delivery records
  • repairs — repair requests
  • stock_items — minimal stock tracking (folded into order_items for now)

Workflow rules and parity targets with the old Django app live in:


Project Structure

src/
├── app/
│   ├── (auth)/login/         # Login page
│   ├── (app)/                # Protected app pages
│   │   ├── dashboard/
│   │   ├── customers/  (+[id])
│   │   ├── companies/  (+[id])
│   │   ├── suppliers/
│   │   ├── supplier-map/
│   │   ├── quotes/           # + [id], [id]/process, approvals
│   │   ├── orders/    (+[id])
│   │   ├── collection-pool/
│   │   ├── delivery-notes/
│   │   └── repairs/
│   ├── api/
│   │   ├── mcp/route.ts                # MCP endpoint
│   │   └── v1/                         # Agent REST endpoints
│   │       ├── health/
│   │       ├── orders/                 # + from-quote
│   │       ├── order-items/[id]/status/
│   │       └── collection-pool/
│   ├── globals.css
│   ├── layout.tsx
│   └── page.tsx
├── components/
│   ├── ui/                   # shadcn/ui (base-nova)
│   ├── sidebar.tsx
│   ├── auth-provider.tsx
│   ├── theme-provider.tsx
│   ├── data-table.tsx
│   ├── line-items-table.tsx
│   ├── customer-form.tsx
│   ├── company-form.tsx
│   ├── supplier-form.tsx
│   ├── quote-form.tsx
│   ├── order-form.tsx
│   ├── delete-dialog.tsx
│   ├── supplier-map.tsx
│   └── service-worker-registration.tsx
└── lib/
    ├── pocketbase.ts         # Client (singleton) + server instances
    ├── pb-helpers.ts
    ├── types.ts              # TypeScript types for all collections
    ├── utils.ts
    ├── quote-status.ts
    └── server/
        ├── agent-auth.ts
        ├── pocketbase-admin.ts
        ├── orders-service.ts
        └── order-item-status.ts

scripts/
├── setup-pocketbase.ts                       # base collections
├── import-data.ts
└── migrations/
    └── create-order-items-v2-and-collection-pool.ts

e2e/                          # Playwright E2E tests (see Testing)
docs/                         # parity matrix, rollout plans, MCP, schema standards
vitest.config.ts              # Vitest config (jsdom, native tsconfig paths)
playwright.config.ts          # Playwright config (boots next dev on :3100)
Dockerfile                    # multi-stage Node 20 alpine build
nixpacks.toml                 # sets Nixpacks Node version to 20

Testing

The project ships with two test suites, both wired into npm scripts.

Vitest (unit + API route tests)

  • Framework: Vitest + jsdom + Testing Library
  • Config: vitest.config.ts — path aliases match tsconfig.json
  • Test files live next to the code they cover: src/**/*.{test,spec}.ts(x)

What gets covered:

File What it tests
src/lib/utils.test.ts cn() (clsx + tailwind-merge)
src/lib/quote-status.test.ts Status class lookup + format helper
src/lib/server/order-item-status.test.ts normalizeOrderItemStatusInput (every item/drv status, the legacy driver_status alias, invalid inputs)
src/lib/server/agent-auth.test.ts assertAgentAuth (bearer token validation, missing env, status code tagging)
src/lib/server/orders-service.test.ts createOrderFromQuote (idempotency, no-items error, payload shape, sequence increment, env-driven collection switch) and listCollectionPool (filter + pagination forwarding). PocketBase is mocked.
src/lib/server/pocketbase-admin.test.ts getAdminPocketBase env validation + auth call
src/app/api/v1/health/route.test.ts Health endpoint shape
src/app/api/v1/orders/route.test.ts Auth, status filter, pagination, error mapping
src/app/api/v1/orders/from-quote/route.test.ts Auth, 400 on missing quote_id, 201 vs 200 (idempotent), upstream error
src/app/api/v1/order-items/[id]/status/route.test.ts Auth, empty/invalid input, normalisation integration, env-driven collection
src/app/api/v1/collection-pool/route.test.ts Auth, param forwarding, error mapping
src/app/api/mcp/route.test.ts tools/list, every tools/call tool, missing-id guard, unknown-tool error, method-not-found

Run:

npm run test:run          # one-shot, CI-friendly
npm test                  # watch mode
npm run test:coverage     # with v8 coverage

The MCP tools/call auth path currently routes auth failures through the JSON-RPC catch (returns 500). The dedicated /api/v1/* endpoints properly return 401. Both are exercised in the tests.

Playwright (E2E)

  • Framework: Playwright
  • Config: playwright.config.ts — boots next dev on port 3100 by default (override with E2E_BASE_URL).
  • Tests live in e2e/*.spec.ts

What's covered:

File Specs
e2e/health.spec.ts GET /api/v1/health returns the right JSON shape — runs with no infra
e2e/agent-api.spec.ts Agent auth (401 without bearer, 200 with valid token) — skips unless E2E_AGENT_API_KEY is set
e2e/auth.spec.ts Login form: happy path + wrong-password — skips unless E2E_TEST_EMAIL + E2E_TEST_PASSWORD are set

The skip behaviour is intentional: with no env vars, the suite reports a clean 1 passed, N skipped so you can run it on a fresh checkout. Once you stand up PocketBase + seed a user, set:

export E2E_BASE_URL=http://localhost:3100
export E2E_POCKETBASE_URL=http://127.0.0.1:8090
export E2E_TEST_EMAIL=ci@example.com
export E2E_TEST_PASSWORD=correct-horse-battery-staple
export E2E_AGENT_API_KEY=$AGENT_API_KEY   # must match what next dev sees
npm run e2e

First-time setup: npm run e2e:install (downloads Chromium + OS deps).

Manual smoke-test checklist

For things that aren't (yet) covered by automated tests, walk through this against a running dev server + PocketBase:

  1. Health — curl http://localhost:3100/api/v1/health → { ok: true, service: "wfdash-api" }
  2. Auth — /login, sign in, get redirected into (app).
  3. Customers — /customers — CRUD, search, paginate, CSV export.
  4. Companies — /companies → /companies/[id] — CRUD + relation on linked quotes/orders.
  5. Suppliers — /suppliers — CRUD with tags; verify on /supplier-map.
  6. Supplier map — /supplier-map — clustering, dark tiles, fullscreen, marker edit persists coords.
  7. Quotes — /quotes — create, line items, claim, process, email metadata persists; approvals.
  8. Quote → Order (API) — POST /api/v1/orders/from-quote with { quote_id }; new order + copied order_items; quote status unchanged (regression from core-recheck-matrix.md).
  9. Orders — /orders/[id] — per-supplier POs, item + drv status updates.
  10. Order item status (API) — PATCH /api/v1/order-items/{id}/status; both drv_status and driver_status accepted.
  11. Collection pool — /collection-pool + GET /api/v1/collection-pool; assignment updates stick.
  12. Delivery notes — /delivery-notes — basic CRUD; no parity guarantees yet (see parity backlog).
  13. Repairs — /repairs — placeholder (not migrated, per parity backlog).
  14. MCP — POST /api/mcp with {"method":"tools/list"} returns 4 tools; tools/call works for each.
  15. Build & lint — npm run lint clean; npm run build completes without type errors.

Deployment

A multi-stage Dockerfile is included (Node 20 alpine, non-root nextjs user, standalone output). A nixpacks.toml pins the Node version for platforms like Railway/Render.

# Docker
docker build -t wfdash .
docker run --env-file .env.local -p 3000:3000 wfdash

# Nixpacks / Railway / Render — just push; nixpacks.toml sets Node 20.