@burdenoff/microfe-movethewheels 2026.924.2 → 2026.925.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/OrderItemRow.js +195 -109
- package/dist/components/StatusBadge.js +31 -31
- package/dist/constants/index.js +11 -3
- package/dist/generated/wspace-operations.d.ts +7 -0
- package/dist/hooks/useFleet.js +85 -76
- package/dist/hooks/useOrders.js +8 -4
- package/dist/hooks/useTracking.js +127 -66
- package/dist/movethewheels/components/OrderItemRow.d.ts +1 -2
- package/dist/movethewheels/components/StatusBadge.d.ts +3 -1
- package/dist/movethewheels/constants/index.d.ts +1 -0
- package/dist/movethewheels/hooks/useFleet.d.ts +41 -1
- package/dist/movethewheels/hooks/useOrders.d.ts +1 -0
- package/dist/movethewheels/hooks/useTracking.d.ts +2 -2
- package/dist/movethewheels/types/index.d.ts +17 -39
- package/dist/node_modules/lucide-react/dist/esm/icons/sticky-note.js +11 -0
- package/dist/node_modules/lucide-react/dist/esm/icons/user-x.js +30 -0
- package/dist/pages/CreateOrderPage.js +597 -420
- package/dist/pages/DriverDetailsPage.js +702 -421
- package/dist/pages/DriversPage.js +106 -117
- package/dist/pages/FleetPage.js +4 -10
- package/dist/pages/OrderDetailsPage.js +290 -262
- package/dist/pages/OrdersListPage.js +4 -0
- package/dist/pages/RouteDetailsPage.js +264 -252
- package/dist/pages/TrackingPage.js +205 -195
- package/dist/pages/VehicleDetailsPage.js +403 -243
- package/dist/utils/mapMarkers.js +2 -1
- package/package.json +1 -1
- package/src/generated/wspace-operations.ts +10 -3
- package/src/movethewheels/components/OrderItemRow.tsx +162 -99
- package/src/movethewheels/components/StatusBadge.tsx +11 -2
- package/src/movethewheels/constants/index.ts +13 -0
- package/src/movethewheels/hooks/useFleet.ts +163 -44
- package/src/movethewheels/hooks/useOrders.ts +17 -3
- package/src/movethewheels/hooks/useTracking.ts +208 -27
- package/src/movethewheels/pages/CreateOrderPage.tsx +372 -263
- package/src/movethewheels/pages/DriverDetailsPage.tsx +516 -156
- package/src/movethewheels/pages/DriversPage.tsx +28 -11
- package/src/movethewheels/pages/FleetPage.tsx +7 -10
- package/src/movethewheels/pages/OrderDetailsPage.tsx +67 -30
- package/src/movethewheels/pages/OrdersListPage.tsx +1 -0
- package/src/movethewheels/pages/RouteDetailsPage.tsx +15 -7
- package/src/movethewheels/pages/TrackingPage.tsx +44 -14
- package/src/movethewheels/pages/VehicleDetailsPage.tsx +137 -15
- package/src/movethewheels/types/index.ts +17 -38
- package/src/movethewheels/utils/mapMarkers.ts +1 -0
- package/src/operations/wspace/movethewheels/queries/dashboard.graphql +5 -0
- package/src/operations/wspace/movethewheels/queries/logistics.graphql +2 -2
|
@@ -41,10 +41,23 @@ interface ApiShipment {
|
|
|
41
41
|
shipmentNumber: string;
|
|
42
42
|
deliveryJobId?: string | null;
|
|
43
43
|
status: string;
|
|
44
|
+
trackingNumber?: string | null;
|
|
44
45
|
estimatedDelivery?: string | null;
|
|
45
46
|
trackingEvents?: ApiTrackingEvent[];
|
|
46
47
|
}
|
|
47
48
|
|
|
49
|
+
interface LookupDeliveryJob {
|
|
50
|
+
id: string;
|
|
51
|
+
deliveryJobNumber: string;
|
|
52
|
+
trackingNumber?: string | null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
interface LookupShipment {
|
|
56
|
+
shipmentNumber: string;
|
|
57
|
+
deliveryJobId?: string | null;
|
|
58
|
+
trackingNumber?: string | null;
|
|
59
|
+
}
|
|
60
|
+
|
|
48
61
|
interface ApiActiveDelivery extends ApiShipment {
|
|
49
62
|
deliveryJob?:
|
|
50
63
|
| (ApiOrder & {
|
|
@@ -69,11 +82,30 @@ const SHIPMENTS_QUERY = `
|
|
|
69
82
|
shipmentNumber
|
|
70
83
|
deliveryJobId
|
|
71
84
|
status
|
|
85
|
+
trackingNumber
|
|
72
86
|
estimatedDelivery
|
|
73
87
|
}
|
|
74
88
|
}
|
|
75
89
|
`;
|
|
76
90
|
|
|
91
|
+
const LOOKUP_DELIVERY_JOB_QUERY = `
|
|
92
|
+
query LookupDeliveryJob($id: ID!) {
|
|
93
|
+
deliveryJob(id: $id) {
|
|
94
|
+
id
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
`;
|
|
98
|
+
|
|
99
|
+
const LOOKUP_DELIVERY_JOBS_QUERY = `
|
|
100
|
+
query LookupDeliveryJobs($filter: DeliveryJobFilterInput, $pagination: PaginationInput) {
|
|
101
|
+
deliveryJobs(filter: $filter, pagination: $pagination) {
|
|
102
|
+
id
|
|
103
|
+
deliveryJobNumber
|
|
104
|
+
trackingNumber
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
`;
|
|
108
|
+
|
|
77
109
|
const DELIVERY_JOB_TRACKING_QUERY = `
|
|
78
110
|
query GetDeliveryJobTracking($deliveryJobId: ID!, $pagination: PaginationInput) {
|
|
79
111
|
deliveryJobTracking(deliveryJobId: $deliveryJobId, pagination: $pagination) {
|
|
@@ -86,7 +118,6 @@ const DELIVERY_JOB_TRACKING_QUERY = `
|
|
|
86
118
|
}
|
|
87
119
|
}
|
|
88
120
|
`;
|
|
89
|
-
|
|
90
121
|
const ACTIVE_DELIVERIES_QUERY = `
|
|
91
122
|
query GetActiveDeliveries($pagination: PaginationInput) {
|
|
92
123
|
activeDeliveries(pagination: $pagination) {
|
|
@@ -178,25 +209,37 @@ const TRACKING_ALERTS_QUERY = `
|
|
|
178
209
|
}
|
|
179
210
|
`;
|
|
180
211
|
|
|
181
|
-
function
|
|
182
|
-
|
|
212
|
+
function mapTrackingEventToLocation(event: ApiTrackingEvent): Location {
|
|
213
|
+
return {
|
|
183
214
|
latitude: Number((event.location?.latitude as number | undefined) ?? 0),
|
|
184
215
|
longitude: Number((event.location?.longitude as number | undefined) ?? 0),
|
|
185
216
|
address: (event.location?.address as string | undefined) ?? undefined,
|
|
186
217
|
timestamp: new Date(event.timestamp),
|
|
187
218
|
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function mapTrackingEventsToTracking(
|
|
222
|
+
events: ApiTrackingEvent[],
|
|
223
|
+
entityId: string
|
|
224
|
+
): Tracking | null {
|
|
225
|
+
const latest = events[0];
|
|
226
|
+
if (!latest) return null;
|
|
227
|
+
|
|
228
|
+
const locationHistory = events.map(mapTrackingEventToLocation);
|
|
229
|
+
const currentLocation = locationHistory[0];
|
|
230
|
+
if (!currentLocation) return null;
|
|
188
231
|
|
|
189
232
|
return {
|
|
190
|
-
id:
|
|
233
|
+
id: latest.id,
|
|
191
234
|
entityId,
|
|
192
235
|
entityType: 'order',
|
|
193
|
-
currentLocation
|
|
194
|
-
locationHistory
|
|
195
|
-
status:
|
|
236
|
+
currentLocation,
|
|
237
|
+
locationHistory,
|
|
238
|
+
status: latest.eventType.toLowerCase(),
|
|
196
239
|
estimatedArrival: undefined,
|
|
197
|
-
nextCheckpoint:
|
|
240
|
+
nextCheckpoint: latest.details ?? undefined,
|
|
198
241
|
alerts: [],
|
|
199
|
-
lastUpdated: new Date(
|
|
242
|
+
lastUpdated: new Date(latest.timestamp),
|
|
200
243
|
};
|
|
201
244
|
}
|
|
202
245
|
|
|
@@ -216,8 +259,26 @@ function useGraphqlOptions() {
|
|
|
216
259
|
);
|
|
217
260
|
}
|
|
218
261
|
|
|
262
|
+
const TRACKING_POLL_INTERVAL_MS = 30_000;
|
|
263
|
+
|
|
264
|
+
function useVisibleTrackingPolling(load: () => void | Promise<void>, enabled: boolean): void {
|
|
265
|
+
useEffect(() => {
|
|
266
|
+
if (!enabled || typeof document === 'undefined') return;
|
|
267
|
+
|
|
268
|
+
const refreshWhenVisible = () => {
|
|
269
|
+
if (document.visibilityState === 'visible') void load();
|
|
270
|
+
};
|
|
271
|
+
const intervalId = window.setInterval(refreshWhenVisible, TRACKING_POLL_INTERVAL_MS);
|
|
272
|
+
document.addEventListener('visibilitychange', refreshWhenVisible);
|
|
273
|
+
return () => {
|
|
274
|
+
window.clearInterval(intervalId);
|
|
275
|
+
document.removeEventListener('visibilitychange', refreshWhenVisible);
|
|
276
|
+
};
|
|
277
|
+
}, [enabled, load]);
|
|
278
|
+
}
|
|
279
|
+
|
|
219
280
|
export function useTracking(options: UseTrackingOptions = {}): UseTrackingResult {
|
|
220
|
-
const { entityId, entityType } = options;
|
|
281
|
+
const { entityId, entityType, realtime = false } = options;
|
|
221
282
|
const graphQLOptions = useGraphqlOptions();
|
|
222
283
|
const [tracking, setTracking] = useState<Tracking[]>([]);
|
|
223
284
|
const [isLoading, setIsLoading] = useState(false);
|
|
@@ -274,6 +335,7 @@ export function useTracking(options: UseTrackingOptions = {}): UseTrackingResult
|
|
|
274
335
|
useEffect(() => {
|
|
275
336
|
void load();
|
|
276
337
|
}, [load]);
|
|
338
|
+
useVisibleTrackingPolling(load, realtime);
|
|
277
339
|
|
|
278
340
|
return {
|
|
279
341
|
tracking,
|
|
@@ -283,46 +345,165 @@ export function useTracking(options: UseTrackingOptions = {}): UseTrackingResult
|
|
|
283
345
|
};
|
|
284
346
|
}
|
|
285
347
|
|
|
286
|
-
|
|
348
|
+
async function resolveTrackingOrderId(
|
|
349
|
+
graphQLOptions: ReturnType<typeof useGraphqlOptions>,
|
|
350
|
+
rawLookup: string
|
|
351
|
+
): Promise<string | null> {
|
|
352
|
+
const lookup = rawLookup.trim();
|
|
353
|
+
const direct = await executeGraphQL<{ deliveryJob: { id: string } | null }>(
|
|
354
|
+
graphQLOptions,
|
|
355
|
+
LOOKUP_DELIVERY_JOB_QUERY,
|
|
356
|
+
{ id: lookup }
|
|
357
|
+
);
|
|
358
|
+
if (direct.deliveryJob?.id) return direct.deliveryJob.id;
|
|
359
|
+
|
|
360
|
+
const jobs = await executeGraphQL<{ deliveryJobs: LookupDeliveryJob[] }>(
|
|
361
|
+
graphQLOptions,
|
|
362
|
+
LOOKUP_DELIVERY_JOBS_QUERY,
|
|
363
|
+
{
|
|
364
|
+
filter: { search: lookup },
|
|
365
|
+
pagination: { limit: 50, offset: 0 },
|
|
366
|
+
}
|
|
367
|
+
);
|
|
368
|
+
const normalizedLookup = lookup.toLocaleLowerCase();
|
|
369
|
+
const matchingJobs = jobs.deliveryJobs.filter((job) =>
|
|
370
|
+
[job.id, job.deliveryJobNumber, job.trackingNumber]
|
|
371
|
+
.filter((value): value is string => Boolean(value))
|
|
372
|
+
.some((value) => value.toLocaleLowerCase() === normalizedLookup)
|
|
373
|
+
);
|
|
374
|
+
if (matchingJobs.length > 1) {
|
|
375
|
+
throw new Error('Multiple deliveries match that reference. Enter the full tracking number.');
|
|
376
|
+
}
|
|
377
|
+
if (matchingJobs[0]) return matchingJobs[0].id;
|
|
378
|
+
|
|
379
|
+
const shipments = await executeGraphQL<{ shipments: LookupShipment[] }>(
|
|
380
|
+
graphQLOptions,
|
|
381
|
+
SHIPMENTS_QUERY,
|
|
382
|
+
{
|
|
383
|
+
filter: { search: lookup },
|
|
384
|
+
pagination: { limit: 50, offset: 0 },
|
|
385
|
+
}
|
|
386
|
+
);
|
|
387
|
+
const matchingShipments = shipments.shipments.filter((shipment) =>
|
|
388
|
+
[shipment.trackingNumber, shipment.shipmentNumber]
|
|
389
|
+
.filter((value): value is string => Boolean(value))
|
|
390
|
+
.some((value) => value.toLocaleLowerCase() === normalizedLookup)
|
|
391
|
+
);
|
|
392
|
+
const matchingJobIds = [
|
|
393
|
+
...new Set(
|
|
394
|
+
matchingShipments
|
|
395
|
+
.map((shipment) => shipment.deliveryJobId)
|
|
396
|
+
.filter((value): value is string => Boolean(value))
|
|
397
|
+
),
|
|
398
|
+
];
|
|
399
|
+
if (matchingJobIds.length > 1) {
|
|
400
|
+
throw new Error('Multiple shipments match that reference. Enter the full tracking number.');
|
|
401
|
+
}
|
|
402
|
+
return matchingJobIds[0] ?? null;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
export function useOrderTracking(orderLookup: string | undefined) {
|
|
287
406
|
const graphQLOptions = useGraphqlOptions();
|
|
288
|
-
const
|
|
407
|
+
const [resolvedOrderId, setResolvedOrderId] = useState<string | undefined>();
|
|
408
|
+
const [lookupLoading, setLookupLoading] = useState(false);
|
|
409
|
+
const [lookupError, setLookupError] = useState<Error | null>(null);
|
|
410
|
+
const [lookupVersion, setLookupVersion] = useState(0);
|
|
411
|
+
const {
|
|
412
|
+
order,
|
|
413
|
+
isLoading: orderLoading,
|
|
414
|
+
error: orderError,
|
|
415
|
+
refetch: refetchOrder,
|
|
416
|
+
} = useOrder(resolvedOrderId);
|
|
289
417
|
const [tracking, setTracking] = useState<Tracking | null>(null);
|
|
290
|
-
const [
|
|
291
|
-
const [
|
|
418
|
+
const [trackingLoading, setTrackingLoading] = useState(false);
|
|
419
|
+
const [trackingError, setTrackingError] = useState<Error | null>(null);
|
|
420
|
+
|
|
421
|
+
useEffect(() => {
|
|
422
|
+
let cancelled = false;
|
|
423
|
+
const lookup = orderLookup?.trim() ?? '';
|
|
424
|
+
|
|
425
|
+
setResolvedOrderId(undefined);
|
|
426
|
+
setTracking(null);
|
|
427
|
+
setLookupError(null);
|
|
428
|
+
if (!lookup) {
|
|
429
|
+
setLookupLoading(false);
|
|
430
|
+
return () => {
|
|
431
|
+
cancelled = true;
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
setLookupLoading(true);
|
|
436
|
+
void resolveTrackingOrderId(graphQLOptions, lookup)
|
|
437
|
+
.then((id) => {
|
|
438
|
+
if (!cancelled) setResolvedOrderId(id ?? undefined);
|
|
439
|
+
})
|
|
440
|
+
.catch((fetchError: unknown) => {
|
|
441
|
+
if (!cancelled) {
|
|
442
|
+
setLookupError(
|
|
443
|
+
fetchError instanceof Error ? fetchError : new Error('Failed to find the delivery')
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
})
|
|
447
|
+
.finally(() => {
|
|
448
|
+
if (!cancelled) setLookupLoading(false);
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
return () => {
|
|
452
|
+
cancelled = true;
|
|
453
|
+
};
|
|
454
|
+
}, [graphQLOptions, lookupVersion, orderLookup]);
|
|
292
455
|
|
|
293
456
|
const load = useCallback(async () => {
|
|
294
|
-
if (!
|
|
457
|
+
if (!resolvedOrderId) {
|
|
295
458
|
setTracking(null);
|
|
459
|
+
setTrackingLoading(false);
|
|
296
460
|
return;
|
|
297
461
|
}
|
|
298
462
|
|
|
299
|
-
|
|
300
|
-
|
|
463
|
+
setTrackingLoading(true);
|
|
464
|
+
setTrackingError(null);
|
|
301
465
|
try {
|
|
302
466
|
const data = await executeGraphQL<{ deliveryJobTracking: ApiTrackingEvent[] }>(
|
|
303
467
|
graphQLOptions,
|
|
304
468
|
DELIVERY_JOB_TRACKING_QUERY,
|
|
305
469
|
{
|
|
306
|
-
deliveryJobId:
|
|
307
|
-
pagination: { limit:
|
|
470
|
+
deliveryJobId: resolvedOrderId,
|
|
471
|
+
pagination: { limit: 50, offset: 0 },
|
|
308
472
|
}
|
|
309
473
|
);
|
|
310
|
-
const
|
|
311
|
-
|
|
474
|
+
const events = [...data.deliveryJobTracking].sort(
|
|
475
|
+
(first, second) => Date.parse(second.timestamp) - Date.parse(first.timestamp)
|
|
476
|
+
);
|
|
477
|
+
setTracking(mapTrackingEventsToTracking(events, resolvedOrderId));
|
|
312
478
|
} catch (fetchError) {
|
|
313
|
-
|
|
479
|
+
setTrackingError(
|
|
314
480
|
fetchError instanceof Error ? fetchError : new Error('Failed to load order tracking')
|
|
315
481
|
);
|
|
316
482
|
} finally {
|
|
317
|
-
|
|
483
|
+
setTrackingLoading(false);
|
|
318
484
|
}
|
|
319
|
-
}, [graphQLOptions,
|
|
485
|
+
}, [graphQLOptions, resolvedOrderId]);
|
|
320
486
|
|
|
321
487
|
useEffect(() => {
|
|
322
488
|
void load();
|
|
323
489
|
}, [load]);
|
|
490
|
+
useVisibleTrackingPolling(load, Boolean(resolvedOrderId));
|
|
491
|
+
|
|
492
|
+
const refetch = () => {
|
|
493
|
+
setLookupVersion((version) => version + 1);
|
|
494
|
+
if (resolvedOrderId) {
|
|
495
|
+
void refetchOrder();
|
|
496
|
+
void load();
|
|
497
|
+
}
|
|
498
|
+
};
|
|
324
499
|
|
|
325
|
-
return {
|
|
500
|
+
return {
|
|
501
|
+
tracking,
|
|
502
|
+
order: resolvedOrderId ? order : null,
|
|
503
|
+
isLoading: lookupLoading || orderLoading || trackingLoading,
|
|
504
|
+
error: lookupError ?? (resolvedOrderId ? (orderError ?? trackingError) : null),
|
|
505
|
+
refetch,
|
|
506
|
+
};
|
|
326
507
|
}
|
|
327
508
|
|
|
328
509
|
export function useVehicleTracking(vehicleId: string | undefined) {
|
|
@@ -386,12 +567,11 @@ export function useLiveTracking() {
|
|
|
386
567
|
const deliveryJob = shipment.deliveryJob;
|
|
387
568
|
if (!deliveryJob) return null;
|
|
388
569
|
|
|
389
|
-
const latestEvent = shipment.trackingEvents?.[0];
|
|
390
570
|
return {
|
|
391
571
|
order: mapOrder(deliveryJob),
|
|
392
572
|
driver: deliveryJob.driver ? mapDriver(deliveryJob.driver) : null,
|
|
393
573
|
vehicle: deliveryJob.vehicle ? mapVehicle(deliveryJob.vehicle) : null,
|
|
394
|
-
tracking:
|
|
574
|
+
tracking: mapTrackingEventsToTracking(shipment.trackingEvents ?? [], deliveryJob.id),
|
|
395
575
|
estimatedArrival: shipment.estimatedDelivery
|
|
396
576
|
? new Date(shipment.estimatedDelivery)
|
|
397
577
|
: undefined,
|
|
@@ -412,6 +592,7 @@ export function useLiveTracking() {
|
|
|
412
592
|
useEffect(() => {
|
|
413
593
|
void load();
|
|
414
594
|
}, [load]);
|
|
595
|
+
useVisibleTrackingPolling(load, true);
|
|
415
596
|
|
|
416
597
|
return { activeDeliveries, isLoading, error, refetch: () => void load() };
|
|
417
598
|
}
|