Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
39 changes: 24 additions & 15 deletions apps/shopify-app/app/routes/app.routes.$routeId.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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` : "";
}

Expand All @@ -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);
Expand Down Expand Up @@ -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}`,
Expand All @@ -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,
Expand Down Expand Up @@ -1065,24 +1069,26 @@ 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 ?? [],
};

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,
Expand All @@ -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) => (
Expand Down Expand Up @@ -1387,8 +1394,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);
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -1868,6 +1876,7 @@ export default function RouteDetailPage() {
routeIdx: draft.routeIdx,
routeIndex: draft.routeIndex,
routePlanId: null,
startTimeLabel: ROUTE_EMPTY_LABEL,
stops: [],
stopsCount: 0,
tempId,
Expand Down Expand Up @@ -2585,7 +2594,7 @@ export default function RouteDetailPage() {
</td>
<td style={routesDetailCellStyle}>
<button aria-label="Change route start time" style={routeEditableValueStyle} type="button">
<span style={routeEditableValueTextStyle}>{routeStartTimeLabel}</span>
<span style={routeEditableValueTextStyle}>{routeRow.startTimeLabel ?? routeStartTimeLabel}</span>
{renderRouteEditableChevron()}
</button>
</td>
Expand Down
154 changes: 75 additions & 79 deletions apps/shopify-app/app/routes/app.routes.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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) {
Expand Down Expand Up @@ -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);
Expand All @@ -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 [
Expand All @@ -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);
Expand Down Expand Up @@ -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];
Expand All @@ -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))),
},
];
}
Expand Down Expand Up @@ -841,8 +837,8 @@ export default function RoutesPage() {
<th style={routeNumberHeaderCellStyle}>Orders</th>
<th style={routeTableHeaderCellStyle}>Area</th>
<th style={routeTableHeaderCellStyle}>Driver</th>
<th style={routeTableHeaderCellStyle}>Start</th>
<th style={routeTableHeaderCellStyle}>End</th>
<th style={routeTableHeaderCellStyle}>Total drive time</th>
<th style={routeTableHeaderCellStyle}>Total distance</th>
</tr>
</thead>
<tbody>
Expand Down Expand Up @@ -875,8 +871,8 @@ export default function RoutesPage() {
<td style={routeNumberCellStyle}>{route.orders}</td>
<td style={routeTableCellStyle}>{route.deliveryArea}</td>
<td style={routeTableCellStyle}>{route.driver}</td>
<td style={routeTableCellStyle}>{route.start}</td>
<td style={routeTableCellStyle}>{route.end}</td>
<td style={routeTableCellStyle}>{formatRouteDurationSeconds(route.driveTimeSeconds)}</td>
<td style={routeTableCellStyle}>{formatRouteDistanceMeters(route.distanceMeters)}</td>
</tr>
))}
</tbody>
Expand Down
Loading