Migrate console off jupiter.nearle.app to the Doormile Express API

Retires REACT_APP_URL/URL2/URL3 in favor of REACT_APP_DOORMILE_URL across
every page (orders, deliveries, riders, tenants, pricing, profile, reports,
dispatch). Fixes several field-mapping and envelope-check bugs found along
the way, most notably that /admin/milers/:id routes (block, assign-vehicle,
edit, notify) key off milerprofileid, not userid, and that a miler's real
fields are phone/availabilitystatus/displayname, not contactno/status/
firstname+lastname (confirmed against a live read-only session).

Also fixes several silent-failure bugs uncovered during that audit: order
creation and order cancellation showed a success toast but gave no feedback
at all on failure (createorder1.js had a dead notifyadmin() call that left
the loading spinner stuck forever on every failed submit), and Tenants.js's
pricing/profile updates never surfaced a failed response to the operator.

The AI dispatch optimiser (routes.workolik.com/routemate.workolik.com) and
its jupiter.nearle.app delivery-commit call remain untouched by design —
separate solver service with no equivalent in the new API.
This commit is contained in:
2026-08-06 19:26:23 +05:30
parent 0a36212ac8
commit 59f31c8adf
39 changed files with 7006 additions and 9170 deletions

View File

@@ -9,7 +9,6 @@ import 'leaflet/dist/leaflet.css';
import '../../../utils/leafletPolylineOffset';
import dayjs from 'dayjs';
import { useInfiniteQuery, useQueries, useQuery, useMutation } from '@tanstack/react-query';
import axios from 'axios';
import {
MdMap,
MdDirectionsBike,
@@ -58,6 +57,7 @@ import {
import ProfitabilitySection from './ProfitabilitySection';
import ActiveSection from './ActiveSection';
import { fetchDeliveries, fetchAppLocations, getRiderPeriodicLogs, fetchRidersLogs, fetchBatchEfficiency } from '../../api/api';
import { getConsignmentLogs } from 'pages/api/doormileApi';
import {
STATUS_STYLES,
getStatusStyle,
@@ -1071,9 +1071,11 @@ const Dispatch = ({
return () => window.removeEventListener('keydown', onKey);
}, [riderPositionModal]);
// TODO: wire to real tenant context once the standalone Dispatch screen
// surfaces it. 916 matches the example tenant in the API spec.
const ANALYSIS_TENANT_ID = 916;
// Was hardcoded to 916 (the example tenant in the API spec) — every
// operator's batch-efficiency analytics queried the same tenant regardless
// of who was actually logged in. Reads the real session tenant now, same
// as the rest of the app (login.js sets this on sign-in).
const ANALYSIS_TENANT_ID = localStorage.getItem('tenantid') || undefined;
const batchEfficiencyMutation = useMutation({
mutationFn: fetchBatchEfficiency,
@@ -1517,11 +1519,12 @@ const Dispatch = ({
plannedMapRendererRef.current = L.canvas({ padding: 1.5, tolerance: 5 });
}
// Pull the partners/getriderlogs feed for the currently selected hub + date.
// This endpoint returns the exact live GPS position for every rider at the
// hub (latitude/longitude/logdate/status). We render those positions as
// markers on the main dispatch map so the operator sees where each rider
// actually is — matching the Reports → Riders Logs page.
// Live rider position feed. Was dead for a while during the backend
// migration (fetchRidersLogs was a permanent [] stub) — now sourced from
// GET /admin/milers/summary (confirmed in jupiter2doormile.md), which
// carries each rider's currentlatitude/currentlongitude/lastpingat. We
// render those positions as markers on the main dispatch map so the
// operator sees where each rider actually is.
const RIDER_LOG_POLL_MS = 1000;
const { data: ridersLocationLogs } = useQuery({
queryKey: [selectedAppLocationId, selectedDate, ''],
@@ -2197,17 +2200,20 @@ const Dispatch = ({
queries: focusedRiderDeliveryIds.map((deliveryid) => ({
queryKey: ['deliveryLogs', deliveryid],
queryFn: async () => {
const res = await axios.get(
`${process.env.REACT_APP_URL3}/deliveries/getdeliverylogs/?deliveryid=${deliveryid}`
);
// Accept several possible response shapes — the live API has shipped
// {details:[…]}, plain arrays, and {data:[…]} variants over time, and
// any of those should produce a polyline. Pick the first non-empty
// array-like found.
const candidates = [res?.data?.details, res?.data?.data, res?.data, res];
const rows = candidates.find((c) => Array.isArray(c)) || [];
// Also accept lat/lng (alternate naming) in case the endpoint ever
// returns the same shape the front-end uses internally.
// GET /admin/consignments/:id/logs is confirmed (jupiter2doormile.md,
// Status: Done) as the GPS/status trail for one consignment — but
// this file's own `deliveryid` here has never been confirmed to
// actually BE a consignmentid (Dispatch.js's data source hasn't been
// audited against the new API the way orders.js/deliveries.js have).
// If it's really a bookingid, this 404s and rows stays empty — same
// degraded-but-not-crashing behaviour as before.
let rows = [];
try {
rows = (await getConsignmentLogs(deliveryid)) || [];
if (!Array.isArray(rows)) rows = [];
} catch (err) {
rows = [];
}
// Sort by logdate ascending so the polyline follows the rider's
// chronological path. The endpoint isn't guaranteed to return rows
// in order — without this, consecutive points can be out of sequence
@@ -5121,14 +5127,14 @@ const Dispatch = ({
{renderMarkers()}
{renderRoutes()}
{/* Live rider GPS markers from /partners/getriderlogs/. Mirrors the
Reports → Riders Logs map: green pin when the rider's last log
row is `active`, red otherwise, with the rider's username as a
{/* Live rider GPS markers from GET /admin/milers/summary (see
fetchRidersLogs in api.js). Green pin when the rider's status
is `active`, red otherwise, with the rider's username as a
label. Scoped to riders who actually have orders in the
currently selected slot — `riders` is derived from
filteredLiveRows so it already reflects the slot filter. A
rider with zero orders in the current slot is hidden, even if
getriderlogs still returns their GPS row. When a specific
the summary still returns their GPS row. When a specific
rider is focused, only that one is shown. */}
{liveRiderLocations
.filter((r) =>