Files
dailygrubs_console/CLAUDE.md

166 lines
8.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Commands
```bash
npm run start:staging # dev server (loads .env.staging via env-cmd)
npm run build:staging # production build — USE THIS, see "Environment" below
npm test # CRA/Jest, watch mode
CI=true npx react-scripts test --watchAll=false # single run
CI=true npx react-scripts test --watchAll=false -t "step ordering" # one test by name
CI=true npx react-scripts test --watchAll=false --testPathPattern=api # one file
```
Lint is not wired to a script. ESLint is deliberately disabled in the webpack pipeline
(`config-overrides.js` filters out `ESLintWebpackPlugin`; `.env` sets `DISABLE_ESLINT_PLUGIN`),
so **lint errors never fail a build** — run ESLint explicitly if you need it.
## Environment — read this before building
`REACT_APP_URL` (v1) and `REACT_APP_URL2` (v2) are the Jupiter API bases. They live in
`.env`, `.env.development`, and `.env.staging`. **All three files are gitignored**, so a
fresh clone or a CI/build host has none of them.
`npm run build` does *not* load the env-cmd files — only `build:dev` / `build:staging` do.
CRA auto-loads `.env` alone. A build missing `REACT_APP_URL` does not fail; it inlines
`undefined`, every request becomes the *relative* path `"undefined/orders/..."`, and those
silently resolve against whatever origin the console is served from. This shipped to
production once and looked like "the API base URL changed to the front-end's own host".
`config-overrides.js` now hard-fails a production build when `REACT_APP_URL`/`REACT_APP_URL2`
are missing. Add any new required var to `REQUIRED_PROD_ENV` there.
**`REACT_APP_URL3` is referenced but defined nowhere** (`Dispatch.js`, the
`getdeliverylogs` fetch). Actual-GPS trails and all Compare deltas silently render nothing
as a result. Whether that endpoint is v1 or v2 is unconfirmed.
## Architecture
CRA + `react-app-rewired` on the **Mantis** MUI template. The template's own pages/theme are
mostly inert scaffolding — all real work lives in `src/pages/nearle/`. Data fetching is
TanStack Query v5; maps are Leaflet + OSRM. Redux is vestigial — `store/reducers/index.js`
combines only `menu` and `snackbar` (the `auth.js` reducer file is not registered), so no
domain data flows through it. Server state belongs in TanStack Query.
### Auth is localStorage, and it is not enforced
`login.js` writes `tenantid`, `applocationid`, `locationid`, `userid`, `authname` etc. to
localStorage; everything else reads them from there. `AuthGuard`/`GuestGuard` exist but are
**commented out in both route files**, so every route is publicly reachable. `App.js` does a
soft redirect to `/login` when `authname` is absent and clears localStorage after a 1-hour
session window.
### The API layer has a module-load trap
`pages/nearle/api/api.js` reads `const tenid = localStorage.getItem('tenantid')` **at module
scope**. It is captured once on first import, so a login that happens afterwards leaves every
`tenid`-using query pinned to the stale value until a full reload. Prefer reading
localStorage inside the query function for anything new.
Several endpoints bypass the env vars and hardcode absolute hosts — the solver calls
(`routes.workolik.com`, `routemate.workolik.com`), `finalCreatedeliveries`, and
`fetchRidersLogs`. `finalCreatedeliveries` hardcodes the **live** Jupiter host, so a staging
build still commits deliveries to production.
### Dispatch.js has two personalities
`pages/nearle/dispatch/Dispatch.js` (~6k lines, plus a ~10k-line CSS file) is one component
serving two modes, switched by whether a `data` prop is passed:
| | standalone `/nearle/dispatch` | embedded (`data` passed) |
|---|---|---|
| source | live API polling | a solver response |
| `shouldFetchLive` | `true` | `false` |
| default `viewMode` | `riders` | `zones` |
| header / batch / date bar | shown | hidden |
Both paths converge on the same shape: `{ zones: [{ zone_name, riders: [{ rider_id,
rider_name, orders[] }] }], zone_summary[], details[] }`. The live path builds it in the
`liveData` memo by re-bucketing flat delivery rows by `deliverysuburb`; the embedded path
receives it already shaped. **Anything that produces data for Dispatch must match that
shape** — that is what `normaliseAssignResponse` in `api.js` exists to guarantee.
View modes: By Location / By Zone / By Rider / Active / Rider Info. "Active" is the live-ops
view — only riders whose latest GPS log is `active`/`pending`, collapsed to their single
in-progress delivery, polling deliveries every 15s.
Orders bucket into three fixed **batches** (Morning <08:00, Afternoon 09:00–12:30, Evening
16:00–19:00) on `assigntime`. Gaps between them are intentional. Layout persists under
`dispatch.slots.v8`; bump the key and add the old one to `LEGACY_SLOTS_STORAGE_KEYS` when the
shape changes. The operator-editable slot editor and the status-wise time-field dropdown are
both commented out in the JSX, not deleted.
Shared constants and pure helpers live in `dispatchShared.js` specifically to avoid a circular
import between `Dispatch.js` and its children (`CompareDataPanel`, `ActiveSection`).
### Assign flow: two clicks, two endpoints
```
/nearle/orders "Assign Orders (N)"
→ POST https://routes.workolik.com/api/v1/optimization/nagercoil/riderassign
(fixed-rider Nagercoil solver; body {deliveries:[...]}, or {} to let the server
pull created orders itself. Empty result → info snackbar, no navigation.)
→ navigate('/nearle/dispatch/preview', { state: { dispatchPreviewData, ... } })
/nearle/dispatch/preview "Assign Orders" ← this is the commit
→ POST https://jupiter.nearle.app/live/api/v1/deliveries/createdeliveries
→ navigate('/nearle/deliveries')
```
The first click writes nothing — it only produces a preview. `Preview.js` deliberately wipes
`history.state.usr` after consuming the response so a reload cannot resurrect a stale plan
(it bounces to `/nearle/orders` instead).
Two de-duplication rules matter here and were both written to fix real duplicate-delivery
bugs: `extractRiders` de-dupes by `orderid` across zones (one rider legitimately appears once
per delivery suburb), and `applyReconcileResponse` wipes a rider's orders from *every* zone
before re-placing them.
`normaliseAssignResponse` assigns `step` **per rider across the whole response**, before zone
bucketing. Numbering within each zone instead would restart the sequence at every suburb and
misrepresent the solver's ordering on map pins and route lines.
## Dead code that looks live
Several features are fully implemented but unreachable. Do not assume a feature works because
the code exists:
- **Compare mode** (`CompareDataPanel`, compare map, delta engine, Kalman/OSRM track
snapping) — `compareOpen` only becomes `true` via `pendingCompareRef.current`, which nothing
ever sets. There is no Compare button in the JSX.
- **Analysis view** (`#dispatch-analysis`, batch efficiency) — `setTopView` is never called,
so `topView` is permanently `'live'`. Its tenant is also hardcoded (`ANALYSIS_TENANT_ID = 916`).
- **Preview.js reconcile / re-optimise / CSV export / tuning** — `handleReconcile`,
`handleCreateDelivery`, `tabValue`, `csvExportData`, `tuningTypes` and their imports each
appear exactly once (their own declaration). The render is now just a header plus an
embedded `<Dispatch>`. `reconcileSteps` and `createOptimisationDeliveries` remain wired in
`api.js` but nothing invokes them.
- **`/nearle/orders/preview`** (`OrdersPreview.js`, ~770 lines) — routed, but nothing
navigates to it. Superseded by the dispatch Preview.
- **Rider push notification** — `Preview.js` only calls `notifyRider` when
`stateData.rider?.userfcmtoken` exists, and `orders.js` never passes a `rider` key. The
assigned rider is never notified.
## Tests
Two suites, 67 tests:
- `src/pages/nearle/api/api.test.js` — `normaliseAssignResponse` across every response shape,
`createNagercoilDeliveries` request wiring, and the `!data?.details?.length` contract that
`orders.js` branches on. `axios` is mocked with a module factory (CRA automock breaks on
`response.data`).
- `src/utils/route-guard/authState.test.js` — session validity, which gates every protected
route. Covers the 1-hour boundary, a missing start time (accepts, rather than logging the
user out) and storage throwing (fails closed).
Component tests do not exist. The large page components have heavy provider requirements
(react-query, notistack, router, theme), so prefer testing extracted pure logic in `api.js` /
`dispatchShared.js` over mounting them.
## Conventions
Prettier: single quotes, no trailing comma, 140 print width, 2-space indent. Files use CRLF —
multi-line string matching in scripts needs `\r?\n`.