dispatch map, plan vs actual, and a fleet page
Three of the features the old console had and ours did not, built on what the data can actually support rather than on what the column names imply. Two findings changed the shape of the work: `riderlogs` is not a GPS trail. Every ping a rider sends carries the SAME coordinate — one rider's 2,404 pings on 14 August all read 11.052998, 76.929958, and the same holds on every day and region checked. Distance, speed and "time moving" cannot come from it. The Fleet page therefore reports presence only: who was online and for how long, inferred from the gaps between check-ins, because `login`, `logout` and `workhours` are empty on all 320,132 August rows. It says out loud that it cannot tell a rider parked all day from one who crossed the city. The delivery ladder is not the order its columns are in. `starttime` is later than `arrivaltime` on 316 of 316 rows, which looks corrupt and is not: `starttime` is per-DROP, stamped when the rider sets off for that address having finished the last one. Read as assign -> arrive -> pickup -> start -> deliver, every duration is positive. On tenant 916 that shows the bottleneck is not the riding: a median 69.5 minutes passes between handing an order to a rider and that rider reaching the shop, against 0.6 minutes at the counter. - Map tab on dispatch, fed by `deliveries.riderslat/lon` — the only rider positions that move (353 distinct across 461 rows). Tenant-scoped, so a shop sees its own rounds. The line joins stops in worked order and says it is not a route. - Plan vs actual tab: promised against delivered, and a step breakdown of where the hours go. `actualkms` is excluded — it equals the planned `kms` to the decimal on every delivered row, so it is a copy, not a measurement. - Fleet page in the platform console: a presence gantt and a map of where each rider is registered. - `ridername` holds a delivery status on more rows than it holds a name for two riders in five, so names are resolved by excluding the status vocabulary first. - leaflet, wrapped directly rather than via react-leaflet, lazy-loaded so only the pages with a map pay for it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JYEsb8PNZ19G9R8gUjTU7n
This commit is contained in:
@@ -216,6 +216,38 @@ export function usePartnerRiderCounts(partnerids: readonly number[]) {
|
||||
return counts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Every GPS ping a partner's riders sent over a window.
|
||||
*
|
||||
* ── Why the window is small by default ────────────────────────────────────
|
||||
*
|
||||
* The response is one row per ping and the riders ping constantly: August 2026
|
||||
* returned 320,132 rows for eight riders. A month is several megabytes of JSON
|
||||
* to fetch, parse and smooth, so the fleet view asks for a day or two and says
|
||||
* which day it is showing.
|
||||
*
|
||||
* Cached longer than `stable` allows because it is history: yesterday's pings
|
||||
* do not change, and a refetch on every focus would re-download the day.
|
||||
*/
|
||||
export function usePartnerRiderLogs(
|
||||
partnerid: number | undefined,
|
||||
range: { fromdate: string; todate: string },
|
||||
) {
|
||||
return useQuery({
|
||||
queryKey: queryKeys.partners.logs(partnerid ?? 0, range.fromdate, range.todate),
|
||||
queryFn: () =>
|
||||
partnersApi.riderLogs({
|
||||
partnerid: partnerid as number,
|
||||
fromdate: range.fromdate,
|
||||
todate: range.todate,
|
||||
}),
|
||||
enabled:
|
||||
typeof partnerid === 'number' && partnerid > 0 && Boolean(range.fromdate && range.todate),
|
||||
...stable,
|
||||
staleTime: 5 * 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
/** The regions one partner covers. */
|
||||
export function usePartnerLocations(partnerid: number | undefined) {
|
||||
return useQuery({
|
||||
|
||||
Reference in New Issue
Block a user