@burdenoff/microfe-movethewheels 2026.929.3 → 2026.929.4

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 (50) hide show
  1. package/dist/generated/wspace-types.d.ts +1547 -64
  2. package/dist/hooks/useCustomers.js +3 -9
  3. package/dist/hooks/useFleet.js +146 -149
  4. package/dist/hooks/useInventory.js +178 -153
  5. package/dist/hooks/useRoutes.js +9 -9
  6. package/dist/hooks/useTracking.js +14 -16
  7. package/dist/movethewheels/hooks/useFleet.d.ts +9 -2
  8. package/dist/movethewheels/hooks/useInventory.d.ts +13 -12
  9. package/dist/movethewheels/hooks/useRoutes.d.ts +4 -1
  10. package/dist/movethewheels/types/index.d.ts +33 -23
  11. package/dist/movethewheels/utils/formatters.d.ts +1 -1
  12. package/dist/pages/CustomerDetailsPage.js +6 -6
  13. package/dist/pages/CustomersPage.js +2 -2
  14. package/dist/pages/DashboardPage.js +1 -1
  15. package/dist/pages/DriverDetailsPage.js +7 -3
  16. package/dist/pages/DriversPage.js +4 -4
  17. package/dist/pages/FleetPage.js +1 -1
  18. package/dist/pages/InventoryPage.js +11 -12
  19. package/dist/pages/LiveTrackingPage.js +4 -4
  20. package/dist/pages/ProductDetailsPage.js +408 -249
  21. package/dist/pages/ProductsPage.js +5 -5
  22. package/dist/pages/RouteDetailsPage.js +8 -10
  23. package/dist/pages/RoutesPage.js +2 -2
  24. package/dist/pages/TrackingPage.js +14 -8
  25. package/dist/pages/WarehouseDetailsPage.js +166 -163
  26. package/dist/pages/WarehousesPage.js +29 -21
  27. package/package.json +1 -1
  28. package/src/generated/wspace-types.ts +1762 -64
  29. package/src/movethewheels/hooks/useCustomers.ts +7 -9
  30. package/src/movethewheels/hooks/useFleet.ts +89 -31
  31. package/src/movethewheels/hooks/useInventory.ts +143 -49
  32. package/src/movethewheels/hooks/useRoutes.ts +36 -16
  33. package/src/movethewheels/hooks/useTracking.ts +23 -15
  34. package/src/movethewheels/pages/CustomerDetailsPage.tsx +14 -8
  35. package/src/movethewheels/pages/CustomersPage.tsx +5 -2
  36. package/src/movethewheels/pages/DashboardPage.tsx +3 -1
  37. package/src/movethewheels/pages/DriverDetailsPage.tsx +13 -3
  38. package/src/movethewheels/pages/DriversPage.tsx +1 -1
  39. package/src/movethewheels/pages/FleetPage.tsx +1 -1
  40. package/src/movethewheels/pages/InventoryPage.tsx +20 -16
  41. package/src/movethewheels/pages/LiveTrackingPage.tsx +6 -4
  42. package/src/movethewheels/pages/ProductDetailsPage.tsx +228 -49
  43. package/src/movethewheels/pages/ProductsPage.tsx +13 -5
  44. package/src/movethewheels/pages/RouteDetailsPage.tsx +16 -8
  45. package/src/movethewheels/pages/RoutesPage.tsx +4 -2
  46. package/src/movethewheels/pages/TrackingPage.tsx +25 -10
  47. package/src/movethewheels/pages/WarehouseDetailsPage.tsx +42 -29
  48. package/src/movethewheels/pages/WarehousesPage.tsx +23 -15
  49. package/src/movethewheels/types/index.ts +36 -24
  50. package/src/movethewheels/utils/formatters.ts +1 -1
@@ -21,7 +21,13 @@ interface UseRoutesResult {
21
21
  error: Error | null;
22
22
  refetch: () => void;
23
23
  createRoute: (route: Partial<Route>) => Promise<Route>;
24
- updateRoute: (id: string, updates: Partial<Route>) => Promise<Route>;
24
+ updateRoute: (
25
+ id: string,
26
+ updates: Omit<Partial<Route>, 'vehicleId' | 'driverId'> & {
27
+ vehicleId?: string | null;
28
+ driverId?: string | null;
29
+ }
30
+ ) => Promise<Route>;
25
31
  deleteRoute: (id: string) => Promise<void>;
26
32
  optimizeRoute: (routeId: string) => Promise<Route>;
27
33
  updateStopStatus: (
@@ -205,13 +211,19 @@ function mapWaypoints(waypoints: unknown): RouteStop[] {
205
211
  state: String(addrObj.state ?? item.state ?? ''),
206
212
  postalCode: String(addrObj.postalCode ?? item.postalCode ?? ''),
207
213
  country: String(addrObj.country ?? item.country ?? ''),
208
- latitude: Number(addrObj.latitude ?? item.latitude ?? item.lat ?? 0),
209
- longitude: Number(addrObj.longitude ?? item.longitude ?? item.lng ?? 0),
214
+ latitude:
215
+ typeof (addrObj.latitude ?? item.latitude ?? item.lat) === 'number'
216
+ ? ((addrObj.latitude ?? item.latitude ?? item.lat) as number)
217
+ : undefined,
218
+ longitude:
219
+ typeof (addrObj.longitude ?? item.longitude ?? item.lng) === 'number'
220
+ ? ((addrObj.longitude ?? item.longitude ?? item.lng) as number)
221
+ : undefined,
210
222
  },
211
223
  type: item.type === 'pickup' ? 'pickup' : 'delivery',
212
- scheduledTime: item.scheduledTime ? new Date(String(item.scheduledTime)) : new Date(),
224
+ scheduledTime: item.scheduledTime ? new Date(String(item.scheduledTime)) : undefined,
213
225
  actualTime: item.actualTime ? new Date(String(item.actualTime)) : undefined,
214
- duration: Number(item.duration ?? 0),
226
+ duration: typeof item.duration === 'number' ? item.duration : undefined,
215
227
  status: (item.status as RouteStop['status']) ?? 'pending',
216
228
  notes: item.notes ? String(item.notes) : undefined,
217
229
  };
@@ -222,8 +234,8 @@ export function mapRoute(route: ApiRoute): Route {
222
234
  return {
223
235
  id: route.id,
224
236
  name: route.name,
225
- driverId: route.driverId ?? '',
226
- vehicleId: route.vehicleId ?? '',
237
+ driverId: route.driverId ?? undefined,
238
+ vehicleId: route.vehicleId ?? undefined,
227
239
  status: mapRouteStatus(route.status),
228
240
  stops: mapWaypoints(route.waypoints),
229
241
  // Prefer the real post-drive distance once it exists; the backend fetches
@@ -232,12 +244,10 @@ export function mapRoute(route: ApiRoute): Route {
232
244
  estimatedDuration: route.estimatedDuration ?? 0,
233
245
  fuelCost: route.fuelCost ?? undefined,
234
246
  actualDuration: route.actualDuration ?? undefined,
235
- // No `optimized` flag exists on the backend — `optimizeRoute` is the only
236
- // path that computes a real `estimatedDistance` (create/update never
237
- // collect one), so a positive value is a reasonably honest proxy. This
238
- // used to be hardcoded `false` and reset on every reload even for a
239
- // route that really had been optimized (BOFF-7909 follow-up).
240
- optimized: (route.estimatedDistance ?? 0) > 0,
247
+ // The route object has no optimization marker. `estimatedDistance` is a
248
+ // measurement, not proof that the optimize mutation ran, so leave this
249
+ // unknown rather than presenting an inferred Yes/No.
250
+ optimized: undefined,
241
251
  createdAt: new Date(route.createdAt),
242
252
  };
243
253
  }
@@ -334,7 +344,13 @@ export function useRoutes(options: UseRoutesOptions = {}): UseRoutesResult {
334
344
  );
335
345
 
336
346
  const updateRoute = useCallback(
337
- async (id: string, updates: Partial<Route>): Promise<Route> => {
347
+ async (
348
+ id: string,
349
+ updates: Omit<Partial<Route>, 'vehicleId' | 'driverId'> & {
350
+ vehicleId?: string | null;
351
+ driverId?: string | null;
352
+ }
353
+ ): Promise<Route> => {
338
354
  const data = await executeGraphQL<{ updateRoute: ApiRoute }>(
339
355
  graphQLOptions,
340
356
  UPDATE_ROUTE_MUTATION,
@@ -344,8 +360,12 @@ export function useRoutes(options: UseRoutesOptions = {}): UseRoutesResult {
344
360
  name: updates.name,
345
361
  status: toApiRouteStatus(updates.status),
346
362
  shipmentId: undefined,
347
- vehicleId: updates.vehicleId,
348
- driverId: updates.driverId,
363
+ vehicleId: Object.prototype.hasOwnProperty.call(updates, 'vehicleId')
364
+ ? (updates.vehicleId ?? null)
365
+ : undefined,
366
+ driverId: Object.prototype.hasOwnProperty.call(updates, 'driverId')
367
+ ? (updates.driverId ?? null)
368
+ : undefined,
349
369
  waypoints: updates.stops,
350
370
  // See createRoute — the edit form doesn't collect a distance
351
371
  // either, and `distance` must never masquerade as `actualDistance`.
@@ -209,11 +209,23 @@ const TRACKING_ALERTS_QUERY = `
209
209
  }
210
210
  `;
211
211
 
212
- function mapTrackingEventToLocation(event: ApiTrackingEvent): Location {
212
+ function mapTrackingEventToLocation(event: ApiTrackingEvent): Location | null {
213
+ const raw = event.location;
214
+ if (!raw) return null;
215
+ const latitude = raw.latitude ?? raw.lat;
216
+ const longitude = raw.longitude ?? raw.lng;
217
+ if (
218
+ typeof latitude !== 'number' ||
219
+ !Number.isFinite(latitude) ||
220
+ typeof longitude !== 'number' ||
221
+ !Number.isFinite(longitude)
222
+ ) {
223
+ return null;
224
+ }
213
225
  return {
214
- latitude: Number((event.location?.latitude as number | undefined) ?? 0),
215
- longitude: Number((event.location?.longitude as number | undefined) ?? 0),
216
- address: (event.location?.address as string | undefined) ?? undefined,
226
+ latitude,
227
+ longitude,
228
+ address: typeof raw.address === 'string' ? raw.address : undefined,
217
229
  timestamp: new Date(event.timestamp),
218
230
  };
219
231
  }
@@ -225,9 +237,10 @@ function mapTrackingEventsToTracking(
225
237
  const latest = events[0];
226
238
  if (!latest) return null;
227
239
 
228
- const locationHistory = events.map(mapTrackingEventToLocation);
240
+ const locationHistory = events
241
+ .map(mapTrackingEventToLocation)
242
+ .filter((location): location is Location => location !== null);
229
243
  const currentLocation = locationHistory[0];
230
- if (!currentLocation) return null;
231
244
 
232
245
  return {
233
246
  id: latest.id,
@@ -307,11 +320,7 @@ export function useTracking(options: UseTrackingOptions = {}): UseTrackingResult
307
320
  id: shipment.id,
308
321
  entityId: entityId ?? shipment.deliveryJobId ?? shipment.id,
309
322
  entityType: entityType ?? 'order',
310
- currentLocation: {
311
- latitude: 0,
312
- longitude: 0,
313
- timestamp: new Date(),
314
- },
323
+ currentLocation: undefined,
315
324
  locationHistory: [],
316
325
  status: shipment.status.toLowerCase(),
317
326
  estimatedArrival: shipment.estimatedDelivery
@@ -319,7 +328,7 @@ export function useTracking(options: UseTrackingOptions = {}): UseTrackingResult
319
328
  : undefined,
320
329
  nextCheckpoint: shipment.shipmentNumber,
321
330
  alerts: [],
322
- lastUpdated: new Date(),
331
+ lastUpdated: undefined,
323
332
  }));
324
333
 
325
334
  setTracking(mapped);
@@ -518,7 +527,7 @@ export function useVehicleTracking(vehicleId: string | undefined) {
518
527
  currentLocation: vehicle.currentLocation,
519
528
  locationHistory: [vehicle.currentLocation],
520
529
  status: vehicle.status,
521
- lastUpdated: vehicle.currentLocation.timestamp ?? new Date(),
530
+ lastUpdated: vehicle.currentLocation.timestamp,
522
531
  alerts: [],
523
532
  };
524
533
  }, [vehicle]);
@@ -538,7 +547,7 @@ export function useDriverTracking(driverId: string | undefined) {
538
547
  currentLocation: driver.currentLocation,
539
548
  locationHistory: [driver.currentLocation],
540
549
  status: driver.status,
541
- lastUpdated: driver.currentLocation.timestamp ?? new Date(),
550
+ lastUpdated: driver.currentLocation.timestamp,
542
551
  alerts: [],
543
552
  };
544
553
  }, [driver]);
@@ -622,7 +631,6 @@ export function useTrackingAlerts() {
622
631
  type: 'delay',
623
632
  severity: 'medium',
624
633
  message: `${alert.shipmentNumber}: ${alert.message}`,
625
- timestamp: new Date(),
626
634
  acknowledged: false,
627
635
  }))
628
636
  );
@@ -333,10 +333,16 @@ function customerToForm(customer: {
333
333
  phone: string;
334
334
  company?: string;
335
335
  type: 'b2b' | 'b2c';
336
- address: { street: string; city: string; state: string; postalCode: string; country: string };
336
+ address: Partial<{
337
+ street: string;
338
+ city: string;
339
+ state: string;
340
+ postalCode: string;
341
+ country: string;
342
+ }>;
337
343
  businessLicense?: string;
338
344
  taxId?: string;
339
- rating: number;
345
+ rating?: number;
340
346
  }): CustomerFormState {
341
347
  return {
342
348
  name: customer.name,
@@ -344,11 +350,11 @@ function customerToForm(customer: {
344
350
  phone: customer.phone,
345
351
  company: customer.company ?? '',
346
352
  type: customer.type,
347
- street: customer.address.street,
348
- city: customer.address.city,
349
- state: customer.address.state,
350
- postalCode: customer.address.postalCode,
351
- country: customer.address.country,
353
+ street: customer.address.street ?? '',
354
+ city: customer.address.city ?? '',
355
+ state: customer.address.state ?? '',
356
+ postalCode: customer.address.postalCode ?? '',
357
+ country: customer.address.country ?? '',
352
358
  businessLicense: customer.businessLicense ?? '',
353
359
  taxId: customer.taxId ?? '',
354
360
  rating: customer.rating ? String(customer.rating) : '',
@@ -480,7 +486,7 @@ export default function CustomerDetailsPage() {
480
486
  headerContent={
481
487
  <div className="flex items-center gap-4">
482
488
  <TypeBadge type={customer.type} />
483
- {customer.rating > 0 && (
489
+ {customer.rating !== undefined && (
484
490
  <div className="flex items-center gap-1">
485
491
  <Star size={16} className="text-status-warning-text fill-current" />
486
492
  <span className="font-medium">{customer.rating.toFixed(1)}</span>
@@ -65,7 +65,10 @@ export default function CustomersPage() {
65
65
  {
66
66
  id: 'location',
67
67
  header: 'Location',
68
- cell: (row) => `${row.address.city}, ${row.address.state}`,
68
+ cell: (row) => {
69
+ const location = [row.address.city, row.address.state].filter(Boolean).join(', ');
70
+ return location || 'Not provided';
71
+ },
69
72
  },
70
73
  {
71
74
  id: 'rating',
@@ -73,7 +76,7 @@ export default function CustomersPage() {
73
76
  cell: (row) => (
74
77
  <div className="flex items-center gap-1">
75
78
  <Star size={14} className="text-status-warning-text fill-current" />
76
- <span>{row.rating.toFixed(1)}</span>
79
+ <span>{row.rating !== undefined ? row.rating.toFixed(1) : 'Not provided'}</span>
77
80
  </div>
78
81
  ),
79
82
  },
@@ -277,7 +277,9 @@ export default function DashboardPage() {
277
277
  >
278
278
  <p className="font-medium">{alert.message}</p>
279
279
  <p className="mt-1 text-xs text-text-secondary">
280
- {formatDate(alert.timestamp, 'relative')}
280
+ {alert.timestamp
281
+ ? formatDate(alert.timestamp, 'relative')
282
+ : 'Time not recorded'}
281
283
  </p>
282
284
  </div>
283
285
  ))}
@@ -86,6 +86,7 @@ interface DriverFormState {
86
86
  licenseExpiry: string;
87
87
  status: DriverStatus;
88
88
  vehicleId: string;
89
+ vehicleIds: string[];
89
90
  rating: string;
90
91
  notes: string;
91
92
  hireDate: string;
@@ -106,6 +107,7 @@ const emptyForm: DriverFormState = {
106
107
  licenseExpiry: '',
107
108
  status: 'available',
108
109
  vehicleId: '',
110
+ vehicleIds: [],
109
111
  rating: '',
110
112
  notes: '',
111
113
  hireDate: '',
@@ -152,6 +154,7 @@ function NewDriverForm({ editId }: { editId?: string }) {
152
154
  licenseExpiry: toDateInput(driver.licenseExpiry),
153
155
  status: driver.status,
154
156
  vehicleId: driver.assignedVehicle || '',
157
+ vehicleIds: driver.vehicleIds,
155
158
  rating: driver.rating ? String(driver.rating) : '',
156
159
  notes: driver.notes || '',
157
160
  hireDate: toDateInput(driver.hireDate),
@@ -173,7 +176,8 @@ function NewDriverForm({ editId }: { editId?: string }) {
173
176
  licenseClass: form.licenseClass.trim() || undefined,
174
177
  licenseExpiry: fromDateInput(form.licenseExpiry),
175
178
  status: toApiDriverStatus(form.status),
176
- vehicleId: form.vehicleId || undefined,
179
+ vehicleId: form.vehicleId || null,
180
+ vehicleIds: form.vehicleIds,
177
181
  rating: form.rating.trim() ? Number(form.rating) : undefined,
178
182
  notes: form.notes.trim() || undefined,
179
183
  hireDate: fromDateInput(form.hireDate),
@@ -411,7 +415,13 @@ function NewDriverForm({ editId }: { editId?: string }) {
411
415
  <EntityCombobox
412
416
  id="vehicle"
413
417
  value={form.vehicleId}
414
- onChange={(value) => setForm((prev) => ({ ...prev, vehicleId: value }))}
418
+ onChange={(value) =>
419
+ setForm((prev) => ({
420
+ ...prev,
421
+ vehicleId: value,
422
+ vehicleIds: value ? [value] : [],
423
+ }))
424
+ }
415
425
  options={vehicles}
416
426
  loading={vehiclesLoading}
417
427
  getId={(vehicle) => vehicle.id}
@@ -661,7 +671,7 @@ export default function DriverDetailsPage() {
661
671
  customColors={{ bg: 'bg-status-warning-bg-subtle', text: 'text-status-warning-text' }}
662
672
  />
663
673
  )}
664
- {driver.rating > 0 && (
674
+ {driver.rating !== undefined && (
665
675
  <div className="flex items-center gap-1">
666
676
  <Star size={16} className="text-status-warning-text fill-current" />
667
677
  <span className="font-medium">{driver.rating.toFixed(1)}</span>
@@ -170,7 +170,7 @@ export default function DriversPage() {
170
170
  id: 'rating',
171
171
  header: 'Rating',
172
172
  cell: (row) =>
173
- row.rating > 0 ? (
173
+ row.rating !== undefined ? (
174
174
  <div className="flex items-center gap-1">
175
175
  <Star size={14} className="text-status-warning-text fill-current" />
176
176
  <span className="font-medium">{row.rating.toFixed(1)}</span>
@@ -308,7 +308,7 @@ export default function FleetPage() {
308
308
  </div>
309
309
  <div className="text-right">
310
310
  <StatusBadge status={driver.status} type="driver" size="sm" />
311
- {driver.rating > 0 && (
311
+ {driver.rating !== undefined && (
312
312
  <div className="mt-1 flex items-center gap-1 justify-end">
313
313
  <Star size={12} className="text-status-warning-text fill-current" />
314
314
  <span className="text-sm font-medium">{driver.rating.toFixed(1)}</span>
@@ -502,32 +502,32 @@ export default function InventoryPage() {
502
502
  <div>
503
503
  <p className="font-medium">{warehouse.name}</p>
504
504
  <p className="text-sm text-text-secondary">
505
- {warehouse.address.city}, {warehouse.address.state}
505
+ {[warehouse.address.city, warehouse.address.state]
506
+ .filter(Boolean)
507
+ .join(', ') || 'Not provided'}
506
508
  </p>
507
509
  </div>
508
510
  <div className="text-right">
509
511
  <Badge
510
512
  variant={
511
- warehouse.status.toUpperCase() === 'ACTIVE' ? 'default' : 'secondary'
513
+ warehouse.status?.toUpperCase() === 'ACTIVE' ? 'default' : 'secondary'
512
514
  }
513
515
  >
514
- {warehouse.status.toUpperCase() === 'ACTIVE'
515
- ? t('movethewheels.inventory.warehouses.active', 'Active')
516
- : t('movethewheels.inventory.warehouses.inactive', 'Inactive')}
516
+ {warehouse.status
517
+ ? warehouse.status.toUpperCase() === 'ACTIVE'
518
+ ? t('movethewheels.inventory.warehouses.active', 'Active')
519
+ : t('movethewheels.inventory.warehouses.inactive', 'Inactive')
520
+ : 'Not provided'}
517
521
  </Badge>
518
522
  <div className="mt-2">
519
523
  <p className="text-xs text-text-secondary">
520
524
  {t('movethewheels.inventory.warehouses.utilization', 'Utilization')}
521
525
  </p>
522
- <Progress
523
- value={
524
- warehouse.capacity.totalSpace > 0
525
- ? (warehouse.capacity.usedSpace / warehouse.capacity.totalSpace) *
526
- 100
527
- : 0
528
- }
529
- className="w-24 h-2"
530
- />
526
+ {warehouse.utilization !== undefined ? (
527
+ <Progress value={warehouse.utilization} className="w-24 h-2" />
528
+ ) : (
529
+ <p className="text-xs text-text-tertiary">Not provided</p>
530
+ )}
531
531
  </div>
532
532
  </div>
533
533
  </div>
@@ -567,8 +567,12 @@ export default function InventoryPage() {
567
567
  <p className="text-sm text-text-secondary">{product.sku}</p>
568
568
  </div>
569
569
  <div className="text-right">
570
- <p className="font-medium">{formatCurrency(product.value)}</p>
571
- <Badge variant="outline">{product.category}</Badge>
570
+ <p className="font-medium">
571
+ {product.value !== undefined
572
+ ? formatCurrency(product.value)
573
+ : 'Not provided'}
574
+ </p>
575
+ <Badge variant="outline">{product.category ?? 'Not provided'}</Badge>
572
576
  </div>
573
577
  </div>
574
578
  </div>
@@ -83,7 +83,7 @@ export default function LiveTrackingPage() {
83
83
  sublabel:
84
84
  d.driver?.fullName ||
85
85
  d.vehicle?.vehicleNumber ||
86
- d.tracking?.currentLocation.address ||
86
+ d.tracking?.currentLocation?.address ||
87
87
  undefined,
88
88
  },
89
89
  ];
@@ -292,7 +292,7 @@ export default function LiveTrackingPage() {
292
292
  : 'N/A'}
293
293
  </p>
294
294
  <p className="text-xs text-text-secondary">
295
- {delivery.tracking?.currentLocation.address ||
295
+ {delivery.tracking?.currentLocation?.address ||
296
296
  'Location updating...'}
297
297
  </p>
298
298
  </div>
@@ -352,7 +352,7 @@ export default function LiveTrackingPage() {
352
352
  <div>
353
353
  <p className="text-sm text-text-secondary">Current Location</p>
354
354
  <p className="text-sm">
355
- {selected.tracking?.currentLocation.address || 'Updating...'}
355
+ {selected.tracking?.currentLocation?.address || 'Updating...'}
356
356
  </p>
357
357
  {(() => {
358
358
  const coord = firstRealLatLng(
@@ -440,7 +440,9 @@ export default function LiveTrackingPage() {
440
440
  >
441
441
  <p className="font-medium">{alert.message}</p>
442
442
  <p className="text-xs text-text-secondary mt-1">
443
- {formatDate(alert.timestamp, 'relative')}
443
+ {alert.timestamp
444
+ ? formatDate(alert.timestamp, 'relative')
445
+ : 'Time not recorded'}
444
446
  </p>
445
447
  </div>
446
448
  ))}