From 2c4bd1c00ca4627f203b109d707d2d46e9057cdd Mon Sep 17 00:00:00 2001
From: OziinG <145884442+OziinG@users.noreply.github.com>
Date: Wed, 1 Jul 2026 11:09:44 +0900
Subject: [PATCH 1/2] Show cached OSRM route metrics in admin routes
Constraint: Routes overview/detail must use routeMetrics only and render blanks for missing cache.\nRejected: Falling back to legacy ETA/distance fields | hides stale or guessed values.\nConfidence: high\nScope-risk: moderate\nDirective: Keep parent summary totals leaf-row based when parent rows display child aggregates.\nTested: cd apps/shopify-app && node --test tests/routes-page.test.mjs; npm run typecheck; npm run build; node --test tests/*.test.mjs; npm run check:public-urls.\nNot-tested: Browser iframe smoke after backend deploy.
---
.../features/delivery/route-detail.server.js | 2 +-
.../app/routes/app.routes.$routeId.jsx | 4 +-
apps/shopify-app/app/routes/app.routes.jsx | 154 +++++++++---------
apps/shopify-app/tests/routes-page.test.mjs | 44 ++++-
4 files changed, 118 insertions(+), 86 deletions(-)
diff --git a/apps/shopify-app/app/features/delivery/route-detail.server.js b/apps/shopify-app/app/features/delivery/route-detail.server.js
index d2ea631..278a78c 100644
--- a/apps/shopify-app/app/features/delivery/route-detail.server.js
+++ b/apps/shopify-app/app/features/delivery/route-detail.server.js
@@ -79,7 +79,7 @@ export function buildRouteGroupChildDetails(routeGroup) {
const optimized = readRouteOptimizedSnapshot(child?.optimized ?? routePlan?.optimized);
return {
routeGeometry: child?.routeGeometry ?? routePlan?.routeGeometry ?? optimized?.routeGeometry ?? null,
- routeMetrics: child?.routeMetrics ?? routePlan?.routeMetrics ?? routePlan?.metrics ?? optimized?.metrics ?? null,
+ routeMetrics: child?.routeMetrics ?? routePlan?.routeMetrics ?? null,
routePlan: getRouteGroupChildRoutePlan(routeGroup, child, routePlanId, index, stops),
routePlanId,
routeStopPoints: firstArray(child?.routeStopPoints, routePlan?.routeStopPoints, optimized?.routeStopPoints),
diff --git a/apps/shopify-app/app/routes/app.routes.$routeId.jsx b/apps/shopify-app/app/routes/app.routes.$routeId.jsx
index 34884c9..ef57194 100644
--- a/apps/shopify-app/app/routes/app.routes.$routeId.jsx
+++ b/apps/shopify-app/app/routes/app.routes.$routeId.jsx
@@ -1387,8 +1387,8 @@ export default function RouteDetailPage() {
const routeDeliveredCount = countRouteStopsByStatus(orderedRouteStops, ["DELIVERED", "FULFILLED"]);
const routeAttemptedCount = countRouteStopsByStatus(orderedRouteStops, ["ATTEMPTED", "FAILED"]);
const routeTotalItems = getRouteTotalItems(effectiveRoutePlan, orderedRouteStops);
- const routeTotalDriveTime = getRouteMetricLabel(formatRouteDurationSeconds(routeMetrics?.durationSeconds), effectiveRoutePlan?.totalDriveTime, effectiveRoutePlan?.driveTime);
- const routeTotalDistance = getRouteMetricLabel(formatRouteDistanceMeters(routeMetrics?.distanceMeters), effectiveRoutePlan?.totalDistance, effectiveRoutePlan?.distance);
+ const routeTotalDriveTime = getRouteMetricLabel(formatRouteDurationSeconds(routeMetrics?.durationSeconds));
+ const routeTotalDistance = getRouteMetricLabel(formatRouteDistanceMeters(routeMetrics?.distanceMeters));
const routeTotalWeight = getRouteMetricLabel(effectiveRoutePlan?.totalWeight, effectiveRoutePlan?.weight);
const routeVehicleLabel = getRouteVehicleLabel(effectiveRoutePlan);
const routeCreatedLabel = getRouteCreatedLabel(effectiveRoutePlan);
diff --git a/apps/shopify-app/app/routes/app.routes.jsx b/apps/shopify-app/app/routes/app.routes.jsx
index 685038e..26bc00c 100644
--- a/apps/shopify-app/app/routes/app.routes.jsx
+++ b/apps/shopify-app/app/routes/app.routes.jsx
@@ -394,11 +394,19 @@ function sumOptionalNumbers(values) {
return hasValue ? total : null;
}
-function formatDriveTime(totalMinutes) {
- const minutes = numberOrNull(totalMinutes);
- if (minutes == null) return "-";
+function readRouteMetrics(routePlan) {
+ const routeMetrics = routePlan?.routeMetrics ?? null;
+ return {
+ distanceMeters: firstNumber(routeMetrics?.distanceMeters),
+ durationSeconds: firstNumber(routeMetrics?.durationSeconds),
+ };
+}
+
+function formatRouteDurationSeconds(totalSeconds) {
+ const seconds = numberOrNull(totalSeconds);
+ if (seconds == null) return "-";
- const roundedMinutes = Math.max(Math.round(minutes), 0);
+ const roundedMinutes = Math.max(Math.round(seconds / 60), 0);
const hours = Math.floor(roundedMinutes / 60);
const remainingMinutes = roundedMinutes % 60;
@@ -407,12 +415,13 @@ function formatDriveTime(totalMinutes) {
return `${hours} hr ${remainingMinutes} min`;
}
-function formatDistanceMiles(totalDistanceMiles) {
- const distanceMiles = numberOrNull(totalDistanceMiles);
- if (distanceMiles == null) return "-";
+function formatRouteDistanceMeters(totalDistanceMeters) {
+ const distanceMeters = numberOrNull(totalDistanceMeters);
+ if (distanceMeters == null) return "-";
- const roundedDistanceMiles = Math.round(distanceMiles * 10) / 10;
- return `${Number.isInteger(roundedDistanceMiles) ? roundedDistanceMiles : roundedDistanceMiles.toFixed(1)}mi`;
+ const kilometers = distanceMeters / 1000;
+ const roundedKilometers = Math.round(kilometers * 10) / 10;
+ return `${Number.isInteger(roundedKilometers) ? roundedKilometers : roundedKilometers.toFixed(1)} km`;
}
function formatRouteTableDate(routePlan) {
@@ -441,37 +450,12 @@ function buildRouteRows(routePlans, routeGroups = []) {
const standaloneRoutePlans = Array.isArray(routePlans)
? routePlans.filter((routePlan) => !childRoutePlanIds.has(routePlan.id))
: [];
- const routeGroupRows = safeRouteGroups.map((routeGroup) => {
- const childCount = getVisibleRouteGroupChildren(routeGroup).length;
- return {
- id: routeGroup.id,
- rowKey: `routeGroup:${routeGroup.id}`,
- routeGroupId: routeGroup.id,
- href: routeGroupPath(routeGroup.id),
- isClickable: true,
- isDeletable: true,
- isRouteGroup: true,
- deleteKey: getRouteDeleteKey({ ...routeGroup, isRouteGroup: true }),
- route: routeGroup.name ?? routeGroup.id,
- status: routeGroup.displayStatus ?? routeGroup.status ?? "DRAFT",
- orders: getRouteGroupTotalOrders(routeGroup),
- coordinates: "-",
- delivered: 0,
- attempted: 0,
- missingCoordinates: 0,
- date: formatRouteGroupDate(routeGroup),
- deliveryArea: "-",
- start: "Parent group",
- end: childCount > 0 ? `${childCount} child routes` : "No split",
- driver: "-",
- driverId: null,
- };
- });
const routeChildRows = safeRouteGroups.flatMap((routeGroup) =>
getVisibleRouteGroupChildren(routeGroup)
.map((child, index) => {
const routePlanId = getRouteGroupChildRoutePlanId(child);
const routePlan = child.routePlan ?? {};
+ const routeMetrics = readRouteMetrics({ ...routePlan, routeMetrics: child.routeMetrics ?? routePlan.routeMetrics });
const stopsCount = child.stopsCount ?? routePlan.stopsCount ?? 0;
const missingCoordinates = routePlan.missingCoordinates ?? 0;
const locatedCount = Math.max(stopsCount - missingCoordinates, 0);
@@ -493,27 +477,51 @@ function buildRouteRows(routePlans, routeGroups = []) {
missingCoordinates,
date: formatRouteTableDate(routePlan),
deliveryArea: formatRouteValues(routePlan.deliveryAreas),
- start: "Shopify departure location",
- end: "Loop back to start",
driver: formatRouteDriver({ displayName: child.driverName }),
driverId: child.driverId ?? routePlan.driverId ?? null,
- driveTimeMinutes: firstNumber(
- routePlan.driveTimeMinutes,
- routePlan.totalDriveTimeMinutes,
- routePlan.durationMinutes,
- routePlan.metrics?.driveTimeMinutes,
- routePlan.metrics?.totalDriveTimeMinutes,
- ),
- distanceMiles: firstNumber(
- routePlan.distanceMiles,
- routePlan.totalDistanceMiles,
- routePlan.distanceMi,
- routePlan.metrics?.distanceMiles,
- routePlan.metrics?.totalDistanceMiles,
- ),
+ driveTimeSeconds: routeMetrics.durationSeconds,
+ distanceMeters: routeMetrics.distanceMeters,
};
}),
);
+ const routeGroupMetricsById = new Map(
+ safeRouteGroups.map((routeGroup) => {
+ const childRows = routeChildRows.filter((routeRow) => routeRow.parentRouteGroupId === routeGroup.id);
+ return [
+ routeGroup.id,
+ {
+ distanceMeters: sumOptionalNumbers(childRows.map((routeRow) => routeRow.distanceMeters)),
+ durationSeconds: sumOptionalNumbers(childRows.map((routeRow) => routeRow.driveTimeSeconds)),
+ },
+ ];
+ }),
+ );
+ const routeGroupRows = safeRouteGroups.map((routeGroup) => {
+ const routeMetrics = routeGroupMetricsById.get(routeGroup.id) ?? {};
+ return {
+ id: routeGroup.id,
+ rowKey: `routeGroup:${routeGroup.id}`,
+ routeGroupId: routeGroup.id,
+ href: routeGroupPath(routeGroup.id),
+ isClickable: true,
+ isDeletable: true,
+ isRouteGroup: true,
+ deleteKey: getRouteDeleteKey({ ...routeGroup, isRouteGroup: true }),
+ route: routeGroup.name ?? routeGroup.id,
+ status: routeGroup.displayStatus ?? routeGroup.status ?? "DRAFT",
+ orders: getRouteGroupTotalOrders(routeGroup),
+ coordinates: "-",
+ delivered: 0,
+ attempted: 0,
+ missingCoordinates: 0,
+ date: formatRouteGroupDate(routeGroup),
+ deliveryArea: "-",
+ driver: "-",
+ driverId: null,
+ driveTimeSeconds: routeMetrics.durationSeconds ?? null,
+ distanceMeters: routeMetrics.distanceMeters ?? null,
+ };
+ });
if (standaloneRoutePlans.length === 0 && routeGroupRows.length === 0 && routeChildRows.length === 0) {
return [
@@ -526,15 +534,16 @@ function buildRouteRows(routePlans, routeGroups = []) {
orders: 0,
date: "-",
deliveryArea: "-",
- start: "Shopify departure location",
- end: "Loop back to start",
driver: "-",
driverId: null,
+ driveTimeSeconds: null,
+ distanceMeters: null,
},
];
}
const routePlanRows = standaloneRoutePlans.map((routePlan) => {
+ const routeMetrics = readRouteMetrics(routePlan);
const stopsCount = routePlan.stopsCount ?? 0;
const missingCoordinates = routePlan.missingCoordinates ?? 0;
const locatedCount = Math.max(stopsCount - missingCoordinates, 0);
@@ -567,24 +576,10 @@ function buildRouteRows(routePlans, routeGroups = []) {
missingCoordinates,
date: formatRouteTableDate(routePlan),
deliveryArea: formatRouteValues(routePlan.deliveryAreas),
- start: "Shopify departure location",
- end: "Loop back to start",
driver: formatRouteDriver(routePlan.driver),
driverId: routePlan.driverId ?? routePlan.driver?.id ?? null,
- driveTimeMinutes: firstNumber(
- routePlan.driveTimeMinutes,
- routePlan.totalDriveTimeMinutes,
- routePlan.durationMinutes,
- routePlan.metrics?.driveTimeMinutes,
- routePlan.metrics?.totalDriveTimeMinutes,
- ),
- distanceMiles: firstNumber(
- routePlan.distanceMiles,
- routePlan.totalDistanceMiles,
- routePlan.distanceMi,
- routePlan.metrics?.distanceMiles,
- routePlan.metrics?.totalDistanceMiles,
- ),
+ driveTimeSeconds: routeMetrics.durationSeconds,
+ distanceMeters: routeMetrics.distanceMeters,
};
});
return [...routeGroupRows, ...routeChildRows, ...routePlanRows];
@@ -599,19 +594,20 @@ function formatRouteGroupDate(routeGroup) {
function buildRoutesSummary(routeRows) {
const activeRouteRows = routeRows.filter((route) => route.isClickable);
+ const summaryRouteRows = activeRouteRows.filter((route) => !route.isRouteGroup);
return [
- { label: "Routes", value: String(activeRouteRows.length) },
- { label: "Stops", value: String(sumNumbers(activeRouteRows.map((route) => route.orders))) },
- { label: "Delivered", value: String(sumNumbers(activeRouteRows.map((route) => route.delivered))) },
- { label: "Attempted", value: String(sumNumbers(activeRouteRows.map((route) => route.attempted))) },
+ { label: "Routes", value: String(summaryRouteRows.length) },
+ { label: "Stops", value: String(sumNumbers(summaryRouteRows.map((route) => route.orders))) },
+ { label: "Delivered", value: String(sumNumbers(summaryRouteRows.map((route) => route.delivered))) },
+ { label: "Attempted", value: String(sumNumbers(summaryRouteRows.map((route) => route.attempted))) },
{
label: "Drive time",
- value: formatDriveTime(sumOptionalNumbers(activeRouteRows.map((route) => route.driveTimeMinutes))),
+ value: formatRouteDurationSeconds(sumOptionalNumbers(summaryRouteRows.map((route) => route.driveTimeSeconds))),
},
{
label: "Distance",
- value: formatDistanceMiles(sumOptionalNumbers(activeRouteRows.map((route) => route.distanceMiles))),
+ value: formatRouteDistanceMeters(sumOptionalNumbers(summaryRouteRows.map((route) => route.distanceMeters))),
},
];
}
@@ -841,8 +837,8 @@ export default function RoutesPage() {
Orders |
Area |
Driver |
- Start |
- End |
+ Total drive time |
+ Total distance |
@@ -875,8 +871,8 @@ export default function RoutesPage() {
{route.orders} |
{route.deliveryArea} |
{route.driver} |
- {route.start} |
- {route.end} |
+ {formatRouteDurationSeconds(route.driveTimeSeconds)} |
+ {formatRouteDistanceMeters(route.distanceMeters)} |
))}
diff --git a/apps/shopify-app/tests/routes-page.test.mjs b/apps/shopify-app/tests/routes-page.test.mjs
index 5f9a912..e78a468 100644
--- a/apps/shopify-app/tests/routes-page.test.mjs
+++ b/apps/shopify-app/tests/routes-page.test.mjs
@@ -160,8 +160,8 @@ test("Routes table uses aligned CLEVER planning columns", () => {
assert.match(routesPageSource, />Status<\/th>/);
assert.match(routesPageSource, />Orders<\/th>/);
assert.match(routesPageSource, />Area<\/th>/);
- assert.match(routesPageSource, />Start<\/th>/);
- assert.match(routesPageSource, />End<\/th>/);
+ assert.match(routesPageSource, />Total drive time<\/th>/);
+ assert.match(routesPageSource, />Total distance<\/th>/);
assert.match(routesPageSource, />Driver<\/th>/);
assert.doesNotMatch(routesPageSource, />Planned for<\/th>/);
assert.doesNotMatch(routesPageSource, />Delivery date<\/th>/);
@@ -175,8 +175,9 @@ test("Routes table uses aligned CLEVER planning columns", () => {
assert.match(routesPageSource, /return Number\(routeGroup\?\.totalOrders \?\? routeGroup\?\.ordersCount \?\? routeGroup\?\.assignments\?\.length \?\? 0\) \|\| 0/);
assert.doesNotMatch(routeHelpersSource, /return children\.length >= 2 \? children : \[\]/);
assert.match(routeHelpersSource, /rightRouteIdx = numberOrUndefined\(right\.child\?\.routeIdx\)/);
- assert.match(routesPageSource, /const childCount = getVisibleRouteGroupChildren\(routeGroup\)\.length/);
- assert.match(routesPageSource, /end: childCount > 0 \? `\$\{childCount\} child routes` : "No split"/);
+ assert.match(routesPageSource, /const routeGroupMetricsById = new Map/);
+ assert.match(routesPageSource, /distanceMeters: sumOptionalNumbers\(childRows\.map\(\(routeRow\) => routeRow\.distanceMeters\)\)/);
+ assert.match(routesPageSource, /durationSeconds: sumOptionalNumbers\(childRows\.map\(\(routeRow\) => routeRow\.driveTimeSeconds\)\)/);
assert.match(routesPageSource, /isRouteGroup: true/);
assert.match(routesPageSource, /isDeletable: false/);
assert.match(routesPageSource, /formatRouteGroupDate\(routeGroup\)/);
@@ -190,6 +191,29 @@ test("Routes table uses aligned CLEVER planning columns", () => {
assert.doesNotMatch(routesPageSource, />Delivery day<\/th>/);
});
+test("Routes table renders OSRM drive metrics instead of start and end placeholders", () => {
+ assert.match(routesPageSource, />Total drive time<\/th>/);
+ assert.match(routesPageSource, />Total distance<\/th>/);
+ assert.doesNotMatch(routesPageSource, />Start<\/th>/);
+ assert.doesNotMatch(routesPageSource, />End<\/th>/);
+ assert.match(routesPageSource, /function readRouteMetrics\(routePlan\) \{/);
+ assert.match(routesPageSource, /routeMetrics\?\.durationSeconds/);
+ assert.match(routesPageSource, /routeMetrics\?\.distanceMeters/);
+ assert.match(routesPageSource, /formatRouteDurationSeconds\(route\.driveTimeSeconds\)/);
+ assert.match(routesPageSource, /formatRouteDistanceMeters\(route\.distanceMeters\)/);
+ assert.match(routesPageSource, /const routeGroupMetricsById = new Map/);
+ assert.match(routesPageSource, /parentRouteGroupId: routeGroup\.id/);
+ assert.match(routesPageSource, /const summaryRouteRows = activeRouteRows\.filter\(\(route\) => !route\.isRouteGroup\)/);
+ assert.match(routesPageSource, /value: String\(summaryRouteRows\.length\)/);
+ assert.match(routesPageSource, /sumNumbers\(summaryRouteRows\.map\(\(route\) => route\.orders\)\)/);
+ assert.match(routesPageSource, /sumNumbers\(summaryRouteRows\.map\(\(route\) => route\.delivered\)\)/);
+ assert.match(routesPageSource, /sumNumbers\(summaryRouteRows\.map\(\(route\) => route\.attempted\)\)/);
+ assert.match(routesPageSource, /sumOptionalNumbers\(summaryRouteRows\.map\(\(route\) => route\.driveTimeSeconds\)\)/);
+ assert.match(routesPageSource, /sumOptionalNumbers\(summaryRouteRows\.map\(\(route\) => route\.distanceMeters\)\)/);
+ assert.doesNotMatch(routesPageSource, /driveTimeMinutes: firstNumber/);
+ assert.doesNotMatch(routesPageSource, /distanceMiles: firstNumber/);
+});
+
test("Routes page removes the previous copied detail and settings rules", () => {
assert.doesNotMatch(routesPageSource, /selectedRouteId/);
assert.doesNotMatch(routesPageSource, /const routeMetricsGridStyle = \{/);
@@ -278,6 +302,18 @@ test("Route detail loader reads server-saved drivers for route driver labels", (
assert.match(routeDetailServerSource, /driverData\.errors/);
});
+test("Route detail drive metrics are OSRM-only and use child DTO metrics", () => {
+ assert.match(routeDetailServerSource, /routeMetrics: child\?\.routeMetrics \?\? routePlan\?\.routeMetrics \?\? null/);
+ assert.match(routeDetailSource, /driveTimeLabel: getRouteMetricLabel\(formatRouteDurationSeconds\(detail\?\.routeMetrics\?\.durationSeconds\)\)/);
+ assert.match(routeDetailSource, /totalDistanceLabel: getRouteMetricLabel\(formatRouteDistanceMeters\(detail\?\.routeMetrics\?\.distanceMeters\)\)/);
+ assert.match(routeDetailSource, /const routeTotalDriveTime = getRouteMetricLabel\(formatRouteDurationSeconds\(routeMetrics\?\.durationSeconds\)\)/);
+ assert.match(routeDetailSource, /const routeTotalDistance = getRouteMetricLabel\(formatRouteDistanceMeters\(routeMetrics\?\.distanceMeters\)\)/);
+ assert.doesNotMatch(routeDetailSource, /effectiveRoutePlan\?\.totalDriveTime/);
+ assert.doesNotMatch(routeDetailSource, /effectiveRoutePlan\?\.driveTime/);
+ assert.doesNotMatch(routeDetailSource, /effectiveRoutePlan\?\.totalDistance/);
+ assert.doesNotMatch(routeDetailSource, /effectiveRoutePlan\?\.distance/);
+});
+
test("Route detail keeps the server driver action but removes the header assignment UI", () => {
assert.match(routeDetailServerSource, /assignDeliveryRoutePlanDriver/);
assert.match(routeDetailServerSource, /intent === "saveRouteDriver"/);
From 7c2b5c00deb6f0654018544683c59853f74a0208 Mon Sep 17 00:00:00 2001
From: OziinG <145884442+OziinG@users.noreply.github.com>
Date: Wed, 1 Jul 2026 14:48:35 +0900
Subject: [PATCH 2/2] Render route group rows from child metrics
Constraint: Route group detail rows must show child-route OSRM metrics and item totals after server draft-save cache generation.\nRejected: Reusing parent route summary values | group rows can hide missing child metrics and stale totals.\nConfidence: high\nScope-risk: narrow\nDirective: Keep routeIdx/#idx as server-owned identity and use childRoutePlan/childRouteMetrics for row totals.\nTested: node --test tests/routes-page.test.mjs tests/route-groups-helper.test.mjs; npm run typecheck; git diff --check\nNot-tested: Shopify app production deploy.
---
.../app/routes/app.routes.$routeId.jsx | 35 ++++++++++++-------
apps/shopify-app/tests/routes-page.test.mjs | 14 +++++---
2 files changed, 32 insertions(+), 17 deletions(-)
diff --git a/apps/shopify-app/app/routes/app.routes.$routeId.jsx b/apps/shopify-app/app/routes/app.routes.$routeId.jsx
index ef57194..425531a 100644
--- a/apps/shopify-app/app/routes/app.routes.$routeId.jsx
+++ b/apps/shopify-app/app/routes/app.routes.$routeId.jsx
@@ -783,6 +783,8 @@ function getRouteStartDateTimeValue(routePlan) {
if (value?.includes("T")) return value.slice(0, 16);
const date = textOrUndefined(routePlan?.routeScope?.deliveryDate ?? routePlan?.deliveryDate ?? routePlan?.planDate);
+ const departureTime = textOrUndefined(routePlan?.departureTime);
+ if (date && departureTime) return `${date}T${departureTime}`;
return date ? `${date}T09:00` : "";
}
@@ -807,7 +809,7 @@ function countRouteStopsByStatus(routeStops, statuses) {
}
function getRouteTotalItems(routePlan, routeStops) {
- const explicitTotal = numberOrUndefined(routePlan?.totalItems ?? routePlan?.itemsCount ?? routePlan?.itemCount);
+ const explicitTotal = numberOrUndefined(routePlan?.itemSummary?.totalQuantity ?? routePlan?.totalItems ?? routePlan?.itemsCount ?? routePlan?.itemCount);
const stopTotal = routeStops.reduce((total, stop) => total + (numberOrUndefined(stop.itemCount) ?? 0), 0);
return explicitTotal ?? (stopTotal > 0 ? stopTotal : ROUTE_EMPTY_LABEL);
@@ -918,6 +920,8 @@ function buildRouteStops(stops) {
const stopNumber = Number.isInteger(sequence) && sequence > 0
? sequence
: index + 1;
+ const itemCount = numberOrUndefined(stop.itemCount ?? stop.itemsCount ?? stop.totalItems)
+ ?? firstArray(stop.items).reduce((total, item) => total + (numberOrUndefined(item?.quantity) ?? 0), 0);
return {
id: stop.deliveryStopId ?? stop.shopifyOrderGid ?? `route-stop-${index + 1}`,
@@ -934,7 +938,7 @@ function buildRouteStops(stops) {
status: stop.fulfillmentStatus ?? stop.status ?? stop.assignmentStatus ?? "PENDING",
payment: stop.paymentStatus ?? stop.financialStatus ?? "—",
attributes: formatStopAttributes(stop.attributes),
- itemCount: numberOrUndefined(stop.itemCount ?? stop.itemsCount ?? stop.totalItems),
+ itemCount,
coordinatesLabel: coordinates != null ? "Yes" : "No",
coordinates,
hasCoordinates: coordinates != null,
@@ -1065,13 +1069,15 @@ function buildRouteGroupChildRows(routeGroup, childDetailsByRoutePlanId = new Ma
const routePlanId = getRouteGroupChildRoutePlanId(child);
const detail = childDetailsByRoutePlanId.get(routePlanId);
const detailStops = detail?.stops ?? [];
+ const childRoutePlan = detail?.routePlan ?? child?.routePlan ?? null;
+ const childRouteMetrics = detail?.routeMetrics ?? child?.routeMetrics ?? childRoutePlan?.routeMetrics ?? null;
const orderIds = getRouteGroupChildOrderIds(child, detailStops, routeStops);
const stopByOrderId = new Map(routeStops.map((stop) => [stop.orderId, stop]));
const stops = orderIds.length > 0
? orderIds.map((orderId) => detailStops.find((stop) => stop.orderId === orderId) ?? stopByOrderId.get(orderId)).filter(Boolean)
: detailStops;
const optimized = {
- metrics: detail?.routeMetrics ?? null,
+ metrics: childRouteMetrics,
routeGeometry: detail?.routeGeometry ?? null,
routeStopPoints: detail?.routeStopPoints ?? [],
};
@@ -1079,10 +1085,10 @@ function buildRouteGroupChildRows(routeGroup, childDetailsByRoutePlanId = new Ma
return {
attemptedCount: countRouteStopsByStatus(stops, ["ATTEMPTED", "FAILED", "NEEDS_REVIEW"]),
color: textOrUndefined(child?.color) ?? ROUTE_DEFAULT_COLORS[index % ROUTE_DEFAULT_COLORS.length] ?? MAP_MARKER_PALETTE.plannedOrder.color,
- createdLabel: textOrUndefined(detail?.routePlan?.createdAt)?.replace("T", " ").slice(0, 16) ?? ROUTE_EMPTY_LABEL,
+ createdLabel: getRouteCreatedLabel(childRoutePlan),
deliveredCount: countRouteStopsByStatus(stops, ["DELIVERED", "FULFILLED"]),
- driverLabel: textOrUndefined(child?.driverName ?? detail?.routePlan?.driver?.displayName) ?? "Unassigned",
- driveTimeLabel: getRouteMetricLabel(formatRouteDurationSeconds(detail?.routeMetrics?.durationSeconds)),
+ driverLabel: textOrUndefined(child?.driverName ?? childRoutePlan?.driver?.displayName) ?? "Unassigned",
+ driveTimeLabel: getRouteMetricLabel(formatRouteDurationSeconds(childRouteMetrics?.durationSeconds)),
id: routePlanId ?? `group-route-${index}`,
isCurrent: false,
optimized,
@@ -1091,14 +1097,15 @@ function buildRouteGroupChildRows(routeGroup, childDetailsByRoutePlanId = new Ma
routeKey: routePlanId ? `routePlan:${routePlanId}` : `routeIdx:${routeIndex}`,
routeIndex,
routePlanId: routePlanId ?? null,
- status: formatRouteStatus(detail?.routePlan?.status ?? child?.displayStatus ?? child?.status),
+ startTimeLabel: getRouteStartTimeLabel(getRouteStartDateTimeValue(childRoutePlan)),
+ status: formatRouteStatus(childRoutePlan?.status ?? child?.displayStatus ?? child?.status),
stops,
stopsCount: stops.length || orderIds.length,
- title: getRouteGroupChildRouteName(routeGroup, child, detail?.routePlan ?? child?.routePlan, index),
- totalDistanceLabel: getRouteMetricLabel(formatRouteDistanceMeters(detail?.routeMetrics?.distanceMeters)),
- totalItems: getRouteTotalItems(detail?.routePlan, stops),
- totalWeightLabel: getRouteMetricLabel(detail?.routePlan?.totalWeight, detail?.routePlan?.weight),
- vehicleLabel: getRouteVehicleLabel(detail?.routePlan),
+ title: getRouteGroupChildRouteName(routeGroup, child, childRoutePlan, index),
+ totalDistanceLabel: getRouteMetricLabel(formatRouteDistanceMeters(childRouteMetrics?.distanceMeters)),
+ totalItems: getRouteTotalItems(childRoutePlan, stops),
+ totalWeightLabel: getRouteMetricLabel(childRoutePlan?.totalWeight, childRoutePlan?.weight),
+ vehicleLabel: getRouteVehicleLabel(childRoutePlan),
};
}).filter((routeRow) => routeRow.routePlanId);
routeGroupChildRows.sort((first, second) => (
@@ -1459,6 +1466,7 @@ export default function RouteDetailPage() {
routeIndex: numberOrUndefined(currentRouteGroupChild?.routeIdx) ?? 1,
routeKey: `routePlan:${textOrUndefined(effectiveRoutePlan?.id) ?? currentRouteLineId}`,
routePlanId: textOrUndefined(effectiveRoutePlan?.id) ?? null,
+ startTimeLabel: routeStartTimeLabel,
status: formatRouteStatus(effectiveRoutePlan?.status),
stops: orderedRouteStops,
stopsCount: orderedRouteStops.length,
@@ -1868,6 +1876,7 @@ export default function RouteDetailPage() {
routeIdx: draft.routeIdx,
routeIndex: draft.routeIndex,
routePlanId: null,
+ startTimeLabel: ROUTE_EMPTY_LABEL,
stops: [],
stopsCount: 0,
tempId,
@@ -2585,7 +2594,7 @@ export default function RouteDetailPage() {
|
diff --git a/apps/shopify-app/tests/routes-page.test.mjs b/apps/shopify-app/tests/routes-page.test.mjs
index e78a468..7b470c4 100644
--- a/apps/shopify-app/tests/routes-page.test.mjs
+++ b/apps/shopify-app/tests/routes-page.test.mjs
@@ -304,8 +304,9 @@ test("Route detail loader reads server-saved drivers for route driver labels", (
test("Route detail drive metrics are OSRM-only and use child DTO metrics", () => {
assert.match(routeDetailServerSource, /routeMetrics: child\?\.routeMetrics \?\? routePlan\?\.routeMetrics \?\? null/);
- assert.match(routeDetailSource, /driveTimeLabel: getRouteMetricLabel\(formatRouteDurationSeconds\(detail\?\.routeMetrics\?\.durationSeconds\)\)/);
- assert.match(routeDetailSource, /totalDistanceLabel: getRouteMetricLabel\(formatRouteDistanceMeters\(detail\?\.routeMetrics\?\.distanceMeters\)\)/);
+ assert.match(routeDetailSource, /const childRouteMetrics = detail\?\.routeMetrics \?\? child\?\.routeMetrics \?\? childRoutePlan\?\.routeMetrics \?\? null/);
+ assert.match(routeDetailSource, /driveTimeLabel: getRouteMetricLabel\(formatRouteDurationSeconds\(childRouteMetrics\?\.durationSeconds\)\)/);
+ assert.match(routeDetailSource, /totalDistanceLabel: getRouteMetricLabel\(formatRouteDistanceMeters\(childRouteMetrics\?\.distanceMeters\)\)/);
assert.match(routeDetailSource, /const routeTotalDriveTime = getRouteMetricLabel\(formatRouteDurationSeconds\(routeMetrics\?\.durationSeconds\)\)/);
assert.match(routeDetailSource, /const routeTotalDistance = getRouteMetricLabel\(formatRouteDistanceMeters\(routeMetrics\?\.distanceMeters\)\)/);
assert.doesNotMatch(routeDetailSource, /effectiveRoutePlan\?\.totalDriveTime/);
@@ -456,7 +457,7 @@ test("Route detail separates group and child titles", () => {
assert.match(routeHelpersSource, /function getRouteGroupChildRouteName\(routeGroup, child, routePlan, index\) \{/);
assert.match(routeHelpersSource, /name\.startsWith\(`\$\{groupName\} — `\)/);
assert.match(routeDetailServerSource, /name: getRouteGroupChildRouteName\(routeGroup, child, routePlan, index\)/);
- assert.match(routeDetailSource, /title: getRouteGroupChildRouteName\(routeGroup, child, detail\?\.routePlan \?\? child\?\.routePlan, index\)/);
+ assert.match(routeDetailSource, /title: getRouteGroupChildRouteName\(routeGroup, child, childRoutePlan, index\)/);
assert.match(routeDetailServerSource, /routePlan: currentChildDetail\?\.routePlan \?\? routePlanData\.routePlan/);
assert.match(routeDetailSource, /\{routeDetailTitle\}<\/h1>/);
});
@@ -862,7 +863,12 @@ test("Route detail renders route lines and a stop timeline below the map", () =>
assert.match(routeDetailSource, />Total weight<\/th>/);
assert.match(routeDetailSource, />Created<\/th>/);
assert.match(routeDetailSource, /const defaultRouteCandidateTitle = isRouteGroupDetail \? "#1" : routeDetailTitle/);
- assert.match(routeDetailSource, /title: getRouteGroupChildRouteName\(routeGroup, child, detail\?\.routePlan \?\? child\?\.routePlan, index\)/);
+ assert.match(routeDetailSource, /const childRoutePlan = detail\?\.routePlan \?\? child\?\.routePlan \?\? null/);
+ assert.match(routeDetailSource, /title: getRouteGroupChildRouteName\(routeGroup, child, childRoutePlan, index\)/);
+ assert.match(routeDetailSource, /startTimeLabel: getRouteStartTimeLabel\(getRouteStartDateTimeValue\(childRoutePlan\)\)/);
+ assert.match(routeDetailSource, /totalItems: getRouteTotalItems\(childRoutePlan, stops\)/);
+ assert.match(routeDetailSource, /routePlan\?\.itemSummary\?\.totalQuantity \?\? routePlan\?\.totalItems/);
+ assert.match(routeDetailSource, /routeRow\.startTimeLabel \?\? routeStartTimeLabel/);
assert.match(routeDetailSource, /aria-label="Change route driver"/);
assert.match(routeDetailSource, /aria-label="Change route vehicle"/);
assert.match(routeDetailSource, /aria-label="Change route start time"/);