updats on the dispatch page
This commit is contained in:
167
scripts/mapPreview.mjs
Normal file
167
scripts/mapPreview.mjs
Normal file
@@ -0,0 +1,167 @@
|
||||
/**
|
||||
* Build a standalone page that renders the REAL dispatch map with mock stops.
|
||||
*
|
||||
* ── Why this exists ─────────────────────────────────────────────────────────
|
||||
*
|
||||
* A leaflet map cannot be checked by anything else in this repo. The pure tests
|
||||
* never mount it, `renderToString` never runs the effect that builds it, and
|
||||
* the jsdom tests can only COUNT what it produced — none of them can tell you
|
||||
* whether the thing looks right. The map shipped twice on that basis and came
|
||||
* back wrong twice: once invisible behind a crash, once with its routes buried
|
||||
* under 966 pins.
|
||||
*
|
||||
* So this bundles the actual `GroupMap` — not a copy of it, not a sketch —
|
||||
* against invented stops, and writes one HTML file to open. What you see is
|
||||
* what the console draws.
|
||||
*
|
||||
* ── Why the mock data lives here and not in `src` ───────────────────────────
|
||||
*
|
||||
* `npm run verify:live` asserts there is no `src/demo`, and it is right to:
|
||||
* a fixture layer inside the app is how a screen ends up quietly rendering
|
||||
* invented numbers in production. This is a build tool. It imports from `src`
|
||||
* and nothing in `src` imports it, so the app has no path to this data.
|
||||
*
|
||||
* node scripts/mapPreview.mjs → writes scripts/.preview/map.html
|
||||
*/
|
||||
import { build } from 'esbuild';
|
||||
import { mkdirSync, writeFileSync } from 'node:fs';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const here = dirname(fileURLToPath(import.meta.url));
|
||||
const out = join(here, '.preview');
|
||||
|
||||
/* ── The mock day ─────────────────────────────────────────────────────────
|
||||
Shaped like the real thing rather than like a neat demo: one shop, two
|
||||
riders working outward in a loop, three orders stacked on one address, and
|
||||
one rider whose last reported position is nowhere near their last drop. Each
|
||||
of those is something the live data does and each has broken this map once. */
|
||||
const SHOP = { lat: 11.0168, lng: 76.9558 };
|
||||
|
||||
const ROUND_A = [
|
||||
[11.0245, 76.9601],
|
||||
[11.0298, 76.9662],
|
||||
[11.0331, 76.9754],
|
||||
[11.0288, 76.9823],
|
||||
[11.0201, 76.9788],
|
||||
// Three orders at one address — the repeat customer that stacked 379 pins.
|
||||
[11.0154, 76.9702],
|
||||
[11.0154, 76.9702],
|
||||
[11.0154, 76.9702],
|
||||
];
|
||||
|
||||
const ROUND_B = [
|
||||
[11.0102, 76.9481],
|
||||
[11.0044, 76.9412],
|
||||
[10.9981, 76.9377],
|
||||
[10.9932, 76.9455],
|
||||
[11.0011, 76.9521],
|
||||
];
|
||||
|
||||
function stopsFor(rider, userid, points, from) {
|
||||
return points.map((point, index) => ({
|
||||
kind: 'delivery',
|
||||
row: {
|
||||
deliveryid: userid * 100 + index,
|
||||
orderid: `916-${userid}${String(index + 1).padStart(2, '0')}`,
|
||||
userid,
|
||||
ridername: rider,
|
||||
// A couple left open, so the status colours are visible on the pins.
|
||||
orderstatus: index === points.length - 1 ? 'active' : 'delivered',
|
||||
assigntime: '2026-08-25 09:00:00',
|
||||
deliverytime: `2026-08-25 ${String(from + Math.floor(index / 2)).padStart(2, '0')}:${String((index * 17) % 60).padStart(2, '0')}:00`,
|
||||
pickuplat: String(SHOP.lat),
|
||||
pickuplon: String(SHOP.lng),
|
||||
droplat: String(point[0]),
|
||||
droplon: String(point[1]),
|
||||
// Only the last stop carries a rider fix, which is the shape the live
|
||||
// rows have — a position arrives when a job moves, not per stop.
|
||||
...(index === points.length - 1
|
||||
? { riderslat: String(point[0] + 0.004), riderslon: String(point[1] - 0.003) }
|
||||
: {}),
|
||||
deliveryamt: 30 + index * 5,
|
||||
deliverycustomer: `${rider}'s customer ${index + 1}`,
|
||||
deliveryaddress: `Stop ${index + 1}`,
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
const STOPS = [
|
||||
...stopsFor('Varun', 897, ROUND_A, 10),
|
||||
...stopsFor('Murali', 1111, ROUND_B, 11),
|
||||
];
|
||||
|
||||
const entry = join(out, 'entry.jsx');
|
||||
mkdirSync(out, { recursive: true });
|
||||
|
||||
writeFileSync(
|
||||
entry,
|
||||
`import { createRoot } from 'react-dom/client';
|
||||
import { GroupMap } from '../../src/features/store-admin/GroupMap';
|
||||
const STOPS = ${JSON.stringify(STOPS)};
|
||||
createRoot(document.getElementById('root')).render(
|
||||
<div style={{ padding: 16, maxWidth: 1100, margin: '0 auto' }}>
|
||||
<h1 style={{ font: '600 18px system-ui', margin: '0 0 4px' }}>Dispatch map — mock day</h1>
|
||||
<p style={{ font: '13px system-ui', color: '#5b6472', margin: '0 0 16px' }}>
|
||||
Two riders, one shop, ${STOPS.length} stops. Three of Varun's orders are at one address.
|
||||
This is the real GroupMap component with invented stops.
|
||||
</p>
|
||||
<GroupMap stops={STOPS} groupName="the mock day" />
|
||||
</div>,
|
||||
);
|
||||
`,
|
||||
);
|
||||
|
||||
await build({
|
||||
entryPoints: [entry],
|
||||
bundle: true,
|
||||
outfile: join(out, 'map.js'),
|
||||
jsx: 'automatic',
|
||||
format: 'iife',
|
||||
platform: 'browser',
|
||||
// Leaflet's stylesheet references sprite PNGs for controls we do not use;
|
||||
// inlined as data URIs so the page is a single self-contained file.
|
||||
loader: { '.css': 'css', '.png': 'dataurl', '.svg': 'dataurl' },
|
||||
// The real thing, minus the parts a static page has no business having.
|
||||
// `import.meta.env` is Vite's and does not exist here; the modules that read
|
||||
// it already optional-chain, so an empty object is enough.
|
||||
define: { 'import.meta.env': 'undefined', 'process.env.NODE_ENV': '"production"' },
|
||||
logLevel: 'warning',
|
||||
});
|
||||
|
||||
writeFileSync(
|
||||
join(out, 'map.html'),
|
||||
`<!doctype html>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Dispatch map preview</title>
|
||||
<link rel="stylesheet" href="./map.css">
|
||||
<style>
|
||||
/* The console's own tokens, so the map is styled as it is in the app. */
|
||||
:root {
|
||||
--color-brand: #662582; --color-brand-strong: #531b6e; --color-brand-tint: #f4eef8;
|
||||
--color-ink-1: #1e2530; --color-ink-2: #414a58; --color-ink-3: #5b6472; --color-ink-4: #97a1b0;
|
||||
--color-surface: #fff; --color-surface-subtle: #fafbfc; --color-surface-sunken: #f2f4f7;
|
||||
--color-border: #e3e7ec;
|
||||
}
|
||||
body { margin: 0; background: #fff; font-family: system-ui, sans-serif; }
|
||||
.pva-note { display:flex; gap:7px; align-items:flex-start; padding:8px 10px; border-radius:7px;
|
||||
background: var(--color-surface-subtle); font-size:11.5px; line-height:1.5; color: var(--color-ink-3); }
|
||||
.map-key { display:inline-flex; gap:5px; align-items:center; font-size:11px; color:var(--color-ink-4); }
|
||||
.map-key i { width:9px; height:9px; margin-left:6px; border-radius:50%; background:var(--color-ink-4); }
|
||||
.map-key i:first-child { margin-left:0 }
|
||||
.map-key i[data-key='shop'] { border-radius:2px; background:var(--color-brand) }
|
||||
.map-key i[data-key='drop'] { background:#10b981 }
|
||||
.map-key i[data-key='rider'] { background:transparent; border:2px solid var(--color-ink-3) }
|
||||
.rider-chip { display:inline-flex; gap:6px; align-items:center; padding:4px 10px; border:1px solid var(--color-border);
|
||||
border-radius:999px; background:#fff; font:inherit; font-size:12px; color:var(--color-ink-2); cursor:pointer }
|
||||
.rider-chip[data-active='true'] { border-color:var(--color-brand); background:var(--color-brand-tint); color:var(--color-ink-1) }
|
||||
.rider-chip i { width:8px; height:8px; border-radius:50% }
|
||||
</style>
|
||||
<div id="root"></div>
|
||||
<script src="./map.js"></script>
|
||||
`,
|
||||
);
|
||||
|
||||
console.log('preview written to', join(out, 'map.html'));
|
||||
console.log('open it in a browser to see the real map with mock stops');
|
||||
Reference in New Issue
Block a user