@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.
Files changed (47) hide show
  1. package/dist/components/OrderItemRow.js +195 -109
  2. package/dist/components/StatusBadge.js +31 -31
  3. package/dist/constants/index.js +11 -3
  4. package/dist/generated/wspace-operations.d.ts +7 -0
  5. package/dist/hooks/useFleet.js +85 -76
  6. package/dist/hooks/useOrders.js +8 -4
  7. package/dist/hooks/useTracking.js +127 -66
  8. package/dist/movethewheels/components/OrderItemRow.d.ts +1 -2
  9. package/dist/movethewheels/components/StatusBadge.d.ts +3 -1
  10. package/dist/movethewheels/constants/index.d.ts +1 -0
  11. package/dist/movethewheels/hooks/useFleet.d.ts +41 -1
  12. package/dist/movethewheels/hooks/useOrders.d.ts +1 -0
  13. package/dist/movethewheels/hooks/useTracking.d.ts +2 -2
  14. package/dist/movethewheels/types/index.d.ts +17 -39
  15. package/dist/node_modules/lucide-react/dist/esm/icons/sticky-note.js +11 -0
  16. package/dist/node_modules/lucide-react/dist/esm/icons/user-x.js +30 -0
  17. package/dist/pages/CreateOrderPage.js +597 -420
  18. package/dist/pages/DriverDetailsPage.js +702 -421
  19. package/dist/pages/DriversPage.js +106 -117
  20. package/dist/pages/FleetPage.js +4 -10
  21. package/dist/pages/OrderDetailsPage.js +290 -262
  22. package/dist/pages/OrdersListPage.js +4 -0
  23. package/dist/pages/RouteDetailsPage.js +264 -252
  24. package/dist/pages/TrackingPage.js +205 -195
  25. package/dist/pages/VehicleDetailsPage.js +403 -243
  26. package/dist/utils/mapMarkers.js +2 -1
  27. package/package.json +1 -1
  28. package/src/generated/wspace-operations.ts +10 -3
  29. package/src/movethewheels/components/OrderItemRow.tsx +162 -99
  30. package/src/movethewheels/components/StatusBadge.tsx +11 -2
  31. package/src/movethewheels/constants/index.ts +13 -0
  32. package/src/movethewheels/hooks/useFleet.ts +163 -44
  33. package/src/movethewheels/hooks/useOrders.ts +17 -3
  34. package/src/movethewheels/hooks/useTracking.ts +208 -27
  35. package/src/movethewheels/pages/CreateOrderPage.tsx +372 -263
  36. package/src/movethewheels/pages/DriverDetailsPage.tsx +516 -156
  37. package/src/movethewheels/pages/DriversPage.tsx +28 -11
  38. package/src/movethewheels/pages/FleetPage.tsx +7 -10
  39. package/src/movethewheels/pages/OrderDetailsPage.tsx +67 -30
  40. package/src/movethewheels/pages/OrdersListPage.tsx +1 -0
  41. package/src/movethewheels/pages/RouteDetailsPage.tsx +15 -7
  42. package/src/movethewheels/pages/TrackingPage.tsx +44 -14
  43. package/src/movethewheels/pages/VehicleDetailsPage.tsx +137 -15
  44. package/src/movethewheels/types/index.ts +17 -38
  45. package/src/movethewheels/utils/mapMarkers.ts +1 -0
  46. package/src/operations/wspace/movethewheels/queries/dashboard.graphql +5 -0
  47. 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 mapTrackingEventToTracking(event: ApiTrackingEvent, entityId: string): Tracking {
182
- const location = {
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: event.id,
233
+ id: latest.id,
191
234
  entityId,
192
235
  entityType: 'order',
193
- currentLocation: location,
194
- locationHistory: [location],
195
- status: event.eventType.toLowerCase(),
236
+ currentLocation,
237
+ locationHistory,
238
+ status: latest.eventType.toLowerCase(),
196
239
  estimatedArrival: undefined,
197
- nextCheckpoint: event.details ?? undefined,
240
+ nextCheckpoint: latest.details ?? undefined,
198
241
  alerts: [],
199
- lastUpdated: new Date(event.timestamp),
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
- export function useOrderTracking(orderId: string | undefined) {
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 { order } = useOrder(orderId);
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 [isLoading, setIsLoading] = useState(false);
291
- const [error, setError] = useState<Error | null>(null);
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 (!orderId) {
457
+ if (!resolvedOrderId) {
295
458
  setTracking(null);
459
+ setTrackingLoading(false);
296
460
  return;
297
461
  }
298
462
 
299
- setIsLoading(true);
300
- setError(null);
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: orderId,
307
- pagination: { limit: 1, offset: 0 },
470
+ deliveryJobId: resolvedOrderId,
471
+ pagination: { limit: 50, offset: 0 },
308
472
  }
309
473
  );
310
- const latest = data.deliveryJobTracking[0];
311
- setTracking(latest ? mapTrackingEventToTracking(latest, orderId) : null);
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
- setError(
479
+ setTrackingError(
314
480
  fetchError instanceof Error ? fetchError : new Error('Failed to load order tracking')
315
481
  );
316
482
  } finally {
317
- setIsLoading(false);
483
+ setTrackingLoading(false);
318
484
  }
319
- }, [graphQLOptions, orderId]);
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 { tracking, order, isLoading, error, refetch: () => void load() };
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: latestEvent ? mapTrackingEventToTracking(latestEvent, deliveryJob.id) : null,
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
  }