This commit includes the temporary Next.js build cache files, public brand logos, and project configuration added in this change.
14 KiB
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.jsonengines) - 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.tscurrently 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.listorders.create_from_quotecollection_pool.listorder_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 databasesuppliers— supplier network (tags, coordinates, closing time)quotes,quote_items— quotes with line items and status workfloworders,purchase_orders— orders + per-supplier POsorder_items— legacy order itemsorder_items_v2— replacement schema (newitem_status+drv_statusmodel)collection_pool— driver work queuedelivery_notes,delivery_note_items— delivery recordsrepairs— repair requestsstock_items— minimal stock tracking (folded intoorder_itemsfor now)
Workflow rules and parity targets with the old Django app live in:
- docs/core-recheck-matrix.md — Quotes / Orders / Company / Customer recheck matrix
- docs/django-nextjs-parity-backlog.md — migration status per module
- docs/order-workflow-rules.md — status transitions
- docs/order-items-schema-standard.md — the v2 schema
- docs/orders-first-rollout.md — orders-first rollout
- docs/migration-audit.md — field-by-field audit
- docs/api-mcp-layer.md — API + MCP reference
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/callauth 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 devon port 3100 by default (override withE2E_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:
- Health —
curl http://localhost:3100/api/v1/health→{ ok: true, service: "wfdash-api" } - Auth —
/login, sign in, get redirected into(app). - Customers —
/customers— CRUD, search, paginate, CSV export. - Companies —
/companies→/companies/[id]— CRUD + relation on linked quotes/orders. - Suppliers —
/suppliers— CRUD with tags; verify on/supplier-map. - Supplier map —
/supplier-map— clustering, dark tiles, fullscreen, marker edit persists coords. - Quotes —
/quotes— create, line items, claim, process, email metadata persists; approvals. - Quote → Order (API) —
POST /api/v1/orders/from-quotewith{ quote_id }; new order + copiedorder_items; quote status unchanged (regression fromcore-recheck-matrix.md). - Orders —
/orders/[id]— per-supplier POs, item + drv status updates. - Order item status (API) —
PATCH /api/v1/order-items/{id}/status; bothdrv_statusanddriver_statusaccepted. - Collection pool —
/collection-pool+GET /api/v1/collection-pool; assignment updates stick. - Delivery notes —
/delivery-notes— basic CRUD; no parity guarantees yet (see parity backlog). - Repairs —
/repairs— placeholder (not migrated, per parity backlog). - MCP —
POST /api/mcpwith{"method":"tools/list"}returns 4 tools;tools/callworks for each. - Build & lint —
npm run lintclean;npm run buildcompletes 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.