map the selected rider and shop, drop the map tab

The separate Map tab is gone. "By rider" and "By store" now draw the map
where the stops table was, so picking a rider from the rail shows their
round on a map and picking a branch shows its day.

A round and a shop's day are both shapes — where the work is, in what order,
how far apart — and a table of addresses shows neither. You can read twenty
rows and still not see that a rider crossed the city twice.

Two views keep the table, on purpose: the waiting queue, whose rows are
ticked to assign them (a checkbox cannot live on a map pin, and nothing
there has been worked yet), and "By customer", where one customer's drops
are all at one address.

The map draws all three places a delivery has, not just the rider:

  shop   a square, one per branch — pickuplat/pickuplon, 500/500 rows
  drop   a circle per stop, by status — droplat/droplon, 500/500 rows
  rider  a hollow ring — riderslat/riderslon, 461/500 and 324/500

Shop and drop are on every row, so the map is never empty on a day with
work in it. The rider fix is real: on delivered rows it sits a median 134 m
from the drop, a reading taken at the customer's door.

The endpoints spell the columns differently — `pickuplon` on getdeliveries
against `pickuplong` on getorders, one letter, and reading the wrong one
puts every shop off the coast of Ghana. `droplat` is filled on deliveries
and empty on orders. `coordOf` takes every spelling so a waiting order maps
the same as a delivered one.

Also fixes the rail itself: it named each group from its first stop's
`ridername`, and that column holds a delivery status on more rows than a
name for two riders in five. The board was listing a phantom rider called
"delivered" carrying 100 of the day's stops, next to the real riders.
Grouping was never wrong — that is on userid — only the label. `isRealName`
now excludes the status vocabulary and both the rail and the map take the
most common name that survives.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JYEsb8PNZ19G9R8gUjTU7n
This commit is contained in:
2026-09-09 17:47:40 +05:30
parent 34bf7989f7
commit 35250717fc
11 changed files with 915 additions and 585 deletions

View File

@@ -37,6 +37,39 @@ export const DELIVERY_STATUS: Record<string, string> = {
cancelled: '#ef4444',
};
/**
* `waiting` is not in either map — it is the dispatch board's own word for work
* nobody has touched — but it is just as wrong as a name.
*/
const WAITING_WORD = 'waiting';
/**
* True when a value is a person's name and not a status wearing one.
*
* `deliveries.ridername` is not reliably a name. Every rider on tenant 916 has
* BOTH their name and a delivery status in that column, and for two of the five
* the status is the MORE common value (measured 2026-09-09):
*
* 883 { "Rajan": 21, "delivered": 42 }
* 897 { "Varun": 69, "delivered": 75 }
* 1111 { "Murali": 33, "delivered": 74, "cancelled": 1 }
* 1114 { "Tamilazhagan": 37, "delivered": 63 }
*
* So neither "first non-empty" nor "most common" finds the name on its own. The
* statuses have to be excluded first, and they are excluded by VOCABULARY — the
* two status maps above — rather than by a hand-written list, so a status added
* to the ladder is excluded here the same day.
*
* It lives beside the vocabularies rather than with either caller: the dispatch
* rail and the map both need it, and one importing it from the other would put
* a cycle between them.
*/
export function isRealName(value: string | undefined): boolean {
const name = value?.trim().toLowerCase();
if (!name) return false;
return !(name in DELIVERY_STATUS) && !(name in ORDER_STATUS) && name !== WAITING_WORD;
}
/** Unknown statuses fall to meta grey rather than to a colour that means something. */
export const statusColor = (map: Record<string, string>, status: string | undefined): string =>
map[(status ?? '').trim().toLowerCase()] ?? 'var(--color-ink-3)';