@burdenoff/microfe-movethewheels 2026.929.3 → 2026.930.1

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 (72) hide show
  1. package/dist/components/CustomerPicker.js +52 -48
  2. package/dist/components/EntityCombobox.js +25 -23
  3. package/dist/components/OrderItemRow.js +123 -78
  4. package/dist/generated/wspace-types.d.ts +1547 -64
  5. package/dist/hooks/useCustomers.js +55 -26
  6. package/dist/hooks/useFleet.js +236 -157
  7. package/dist/hooks/useInventory.js +214 -153
  8. package/dist/hooks/useMultiModalShipments.js +30 -26
  9. package/dist/hooks/useRoutes.js +64 -24
  10. package/dist/hooks/useTracking.js +14 -16
  11. package/dist/movethewheels/components/CustomerPicker.d.ts +5 -1
  12. package/dist/movethewheels/components/EntityCombobox.d.ts +6 -3
  13. package/dist/movethewheels/components/OrderItemRow.d.ts +7 -1
  14. package/dist/movethewheels/hooks/useCustomers.d.ts +3 -0
  15. package/dist/movethewheels/hooks/useFleet.d.ts +15 -2
  16. package/dist/movethewheels/hooks/useInventory.d.ts +15 -12
  17. package/dist/movethewheels/hooks/useMultiModalShipments.d.ts +1 -0
  18. package/dist/movethewheels/hooks/useRoutes.d.ts +7 -1
  19. package/dist/movethewheels/types/index.d.ts +33 -23
  20. package/dist/movethewheels/utils/formatters.d.ts +1 -1
  21. package/dist/movethewheels/utils/orderValidation.d.ts +11 -0
  22. package/dist/pages/CreateOrderPage.js +349 -297
  23. package/dist/pages/CustomerDetailsPage.js +6 -6
  24. package/dist/pages/CustomersPage.js +2 -2
  25. package/dist/pages/DashboardPage.js +1 -1
  26. package/dist/pages/DriverDetailsPage.js +340 -332
  27. package/dist/pages/DriversPage.js +7 -5
  28. package/dist/pages/FleetPage.js +1 -1
  29. package/dist/pages/InventoryPage.js +11 -12
  30. package/dist/pages/LiveTrackingPage.js +4 -4
  31. package/dist/pages/MultiModalShipmentPage.js +139 -125
  32. package/dist/pages/OrderDetailsPage.js +132 -140
  33. package/dist/pages/ProductDetailsPage.js +408 -249
  34. package/dist/pages/ProductsPage.js +5 -5
  35. package/dist/pages/RouteDetailsPage.js +208 -210
  36. package/dist/pages/RoutesPage.js +2 -2
  37. package/dist/pages/TrackingPage.js +16 -9
  38. package/dist/pages/WarehouseDetailsPage.js +166 -163
  39. package/dist/pages/WarehousesPage.js +29 -21
  40. package/dist/utils/orderValidation.js +18 -0
  41. package/package.json +1 -1
  42. package/src/generated/wspace-types.ts +1762 -64
  43. package/src/movethewheels/components/CustomerPicker.tsx +12 -0
  44. package/src/movethewheels/components/EntityCombobox.tsx +30 -11
  45. package/src/movethewheels/components/OrderItemRow.tsx +44 -0
  46. package/src/movethewheels/hooks/useCustomers.ts +45 -9
  47. package/src/movethewheels/hooks/useFleet.ts +214 -36
  48. package/src/movethewheels/hooks/useInventory.ts +185 -49
  49. package/src/movethewheels/hooks/useMultiModalShipments.ts +9 -7
  50. package/src/movethewheels/hooks/useRoutes.ts +87 -16
  51. package/src/movethewheels/hooks/useTracking.ts +23 -15
  52. package/src/movethewheels/pages/CreateOrderPage.tsx +66 -2
  53. package/src/movethewheels/pages/CustomerDetailsPage.tsx +14 -8
  54. package/src/movethewheels/pages/CustomersPage.tsx +5 -2
  55. package/src/movethewheels/pages/DashboardPage.tsx +3 -1
  56. package/src/movethewheels/pages/DriverDetailsPage.tsx +25 -4
  57. package/src/movethewheels/pages/DriversPage.tsx +8 -2
  58. package/src/movethewheels/pages/FleetPage.tsx +1 -1
  59. package/src/movethewheels/pages/InventoryPage.tsx +20 -16
  60. package/src/movethewheels/pages/LiveTrackingPage.tsx +6 -4
  61. package/src/movethewheels/pages/MultiModalShipmentPage.tsx +15 -1
  62. package/src/movethewheels/pages/OrderDetailsPage.tsx +25 -25
  63. package/src/movethewheels/pages/ProductDetailsPage.tsx +228 -49
  64. package/src/movethewheels/pages/ProductsPage.tsx +13 -5
  65. package/src/movethewheels/pages/RouteDetailsPage.tsx +24 -12
  66. package/src/movethewheels/pages/RoutesPage.tsx +4 -2
  67. package/src/movethewheels/pages/TrackingPage.tsx +32 -12
  68. package/src/movethewheels/pages/WarehouseDetailsPage.tsx +42 -29
  69. package/src/movethewheels/pages/WarehousesPage.tsx +23 -15
  70. package/src/movethewheels/types/index.ts +36 -24
  71. package/src/movethewheels/utils/formatters.ts +1 -1
  72. package/src/movethewheels/utils/orderValidation.ts +53 -0
@@ -48,6 +48,7 @@ import { CustomerPicker } from '../components/CustomerPicker';
48
48
  import { EntityCombobox } from '../components/EntityCombobox';
49
49
  import { OrderItemRow } from '../components/OrderItemRow';
50
50
  import type { Customer } from '../types';
51
+ import { validateOrderItems, type OrderItemFieldErrors } from '../utils/orderValidation';
51
52
 
52
53
  /**
53
54
  * `<input type="datetime-local">` needs a `YYYY-MM-DDTHH:mm` string in LOCAL
@@ -77,8 +78,12 @@ export default function CreateOrderPage() {
77
78
  customers,
78
79
  createCustomer,
79
80
  isLoading: customersLoading,
81
+ isLoadingMore: customersLoadingMore,
80
82
  error: customersError,
81
83
  refetch: refetchCustomers,
84
+ loadMore: loadMoreCustomers,
85
+ hasMore: customersHaveMore,
86
+ totalCustomers,
82
87
  } = useCustomers({
83
88
  limit: 20,
84
89
  filters: { search: customerSearch || undefined },
@@ -91,13 +96,22 @@ export default function CreateOrderPage() {
91
96
  error: routesError,
92
97
  hasMore: routesHaveMore,
93
98
  totalRoutes,
99
+ loadMore: loadMoreRoutes,
100
+ isLoadingMore: routesLoadingMore,
94
101
  } = useRoutes({
95
102
  status: 'planned',
96
103
  search: routeSearch || undefined,
97
104
  limit: 20,
98
105
  });
99
106
  const [productSearch, setProductSearch] = useState('');
100
- const { products, isLoading: productsLoading } = useProducts({
107
+ const {
108
+ products,
109
+ isLoading: productsLoading,
110
+ isLoadingMore: productsLoadingMore,
111
+ hasMore: productsHaveMore,
112
+ totalProducts,
113
+ loadMore: loadMoreProducts,
114
+ } = useProducts({
101
115
  search: productSearch || undefined,
102
116
  limit: 20,
103
117
  });
@@ -124,6 +138,7 @@ export default function CreateOrderPage() {
124
138
  country: order?.deliveryAddress.country || 'USA',
125
139
  },
126
140
  items: order?.items || [],
141
+ trackingNumber: order?.trackingNumber || '',
127
142
  scheduledDelivery: toDateTimeLocalValue(order?.scheduledDelivery),
128
143
  specialInstructions: order?.specialInstructions || '',
129
144
  });
@@ -137,6 +152,7 @@ export default function CreateOrderPage() {
137
152
 
138
153
  const [isSubmitting, setIsSubmitting] = useState(false);
139
154
  const [formError, setFormError] = useState<string | null>(null);
155
+ const [itemErrors, setItemErrors] = useState<Record<string, OrderItemFieldErrors>>({});
140
156
 
141
157
  useEffect(() => {
142
158
  if (!linkedCustomer) {
@@ -176,6 +192,7 @@ export default function CreateOrderPage() {
176
192
  country: order.deliveryAddress.country || 'USA',
177
193
  },
178
194
  items: order.items || [],
195
+ trackingNumber: order.trackingNumber || '',
179
196
  scheduledDelivery: toDateTimeLocalValue(order.scheduledDelivery),
180
197
  specialInstructions: order.specialInstructions || '',
181
198
  });
@@ -210,6 +227,11 @@ export default function CreateOrderPage() {
210
227
  if (formData.items.length === 0) {
211
228
  return 'Add at least one item to the order.';
212
229
  }
230
+ const nextItemErrors = validateOrderItems(formData.items);
231
+ setItemErrors(nextItemErrors);
232
+ if (Object.keys(nextItemErrors).length > 0) {
233
+ return 'Fix the highlighted shipment item fields before continuing.';
234
+ }
213
235
  if (formData.scheduledDelivery && Number.isNaN(Date.parse(formData.scheduledDelivery))) {
214
236
  return 'Enter a valid scheduled delivery date and time.';
215
237
  }
@@ -251,6 +273,7 @@ export default function CreateOrderPage() {
251
273
  deliveryAddress: formData.deliveryAddress,
252
274
  items: formData.items,
253
275
  // `null` clears an existing slot on edit; the service field is nullable.
276
+ trackingNumber: formData.trackingNumber.trim() || undefined,
254
277
  scheduledDelivery: formData.scheduledDelivery ? new Date(formData.scheduledDelivery) : null,
255
278
  routeId: formData.routeId || undefined,
256
279
  specialInstructions: formData.specialInstructions,
@@ -346,6 +369,14 @@ export default function CreateOrderPage() {
346
369
  };
347
370
 
348
371
  const removeItem = (index: number) => {
372
+ const itemId = formData.items[index]?.id;
373
+ if (itemId) {
374
+ setItemErrors((previous) => {
375
+ const next = { ...previous };
376
+ delete next[itemId];
377
+ return next;
378
+ });
379
+ }
349
380
  setFormData((prev) => ({
350
381
  ...prev,
351
382
  items: prev.items.filter((_, i) => i !== index),
@@ -353,6 +384,14 @@ export default function CreateOrderPage() {
353
384
  };
354
385
 
355
386
  const updateItem = (index: number, field: string, value: unknown) => {
387
+ const itemId = formData.items[index]?.id;
388
+ if (itemId && itemErrors[itemId]) {
389
+ setItemErrors((previous) => {
390
+ const next = { ...previous };
391
+ delete next[itemId];
392
+ return next;
393
+ });
394
+ }
356
395
  setFormData((prev) => ({
357
396
  ...prev,
358
397
  items: prev.items.map((item, i) => (i === index ? { ...item, [field]: value } : item)),
@@ -489,6 +528,10 @@ export default function CreateOrderPage() {
489
528
  onOpenCustomers={goToCustomerCreate}
490
529
  selectedCustomer={selectedCustomer}
491
530
  onSearchQueryChange={setCustomerSearch}
531
+ hasMore={customersHaveMore}
532
+ totalCount={totalCustomers}
533
+ onLoadMore={loadMoreCustomers}
534
+ isLoadingMore={customersLoadingMore}
492
535
  />
493
536
  <div className="space-y-2">
494
537
  <Label htmlFor="priority">Priority</Label>
@@ -555,6 +598,8 @@ export default function CreateOrderPage() {
555
598
  onSearchChange={setRouteSearch}
556
599
  hasMore={routesHaveMore}
557
600
  totalCount={totalRoutes}
601
+ onLoadMore={loadMoreRoutes}
602
+ loadingMore={routesLoadingMore}
558
603
  />
559
604
  <p className="mt-2 text-xs text-text-secondary" role="status">
560
605
  {routesLoading
@@ -782,6 +827,11 @@ export default function CreateOrderPage() {
782
827
  index={index}
783
828
  products={products}
784
829
  productsLoading={productsLoading}
830
+ productsLoadingMore={productsLoadingMore}
831
+ productsHasMore={productsHaveMore}
832
+ productsTotalCount={totalProducts}
833
+ onLoadMoreProducts={loadMoreProducts}
834
+ errors={itemErrors[item.id]}
785
835
  onUpdate={updateItem}
786
836
  onRemove={removeItem}
787
837
  />
@@ -803,6 +853,20 @@ export default function CreateOrderPage() {
803
853
  description="Optional delivery timing and instructions for the carrier."
804
854
  >
805
855
  <div className="grid grid-cols-1 gap-6 lg:grid-cols-3">
856
+ <div className="space-y-2">
857
+ <Label htmlFor="trackingNumber">Tracking / reference number</Label>
858
+ <Input
859
+ id="trackingNumber"
860
+ value={formData.trackingNumber}
861
+ onChange={(e: ChangeEvent<HTMLInputElement>) =>
862
+ updateField('trackingNumber', e.target.value)
863
+ }
864
+ placeholder="Optional carrier reference"
865
+ />
866
+ <p className="text-xs text-text-secondary">
867
+ Use this when a carrier or customer already supplied a tracking reference.
868
+ </p>
869
+ </div>
806
870
  <div className="space-y-2">
807
871
  <Label htmlFor="scheduledDelivery">Scheduled delivery</Label>
808
872
  <Input
@@ -817,7 +881,7 @@ export default function CreateOrderPage() {
817
881
  Leave blank to dispatch as soon as a driver is available.
818
882
  </p>
819
883
  </div>
820
- <div className="space-y-2 lg:col-span-2">
884
+ <div className="space-y-2 lg:col-span-1">
821
885
  <Label htmlFor="specialInstructions">Special instructions</Label>
822
886
  <Textarea
823
887
  id="specialInstructions"
@@ -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: '',
@@ -130,7 +132,14 @@ function NewDriverForm({ editId }: { editId?: string }) {
130
132
 
131
133
  const [vehicleSearch, setVehicleSearch] = useState('');
132
134
  const debouncedVehicleSearch = useDebouncedValue(vehicleSearch);
133
- const { vehicles, isLoading: vehiclesLoading } = useVehicles({
135
+ const {
136
+ vehicles,
137
+ isLoading: vehiclesLoading,
138
+ isLoadingMore: vehiclesLoadingMore,
139
+ hasMore: vehiclesHaveMore,
140
+ totalVehicles,
141
+ loadMore: loadMoreVehicles,
142
+ } = useVehicles({
134
143
  limit: 20,
135
144
  search: debouncedVehicleSearch || undefined,
136
145
  });
@@ -152,6 +161,7 @@ function NewDriverForm({ editId }: { editId?: string }) {
152
161
  licenseExpiry: toDateInput(driver.licenseExpiry),
153
162
  status: driver.status,
154
163
  vehicleId: driver.assignedVehicle || '',
164
+ vehicleIds: driver.vehicleIds,
155
165
  rating: driver.rating ? String(driver.rating) : '',
156
166
  notes: driver.notes || '',
157
167
  hireDate: toDateInput(driver.hireDate),
@@ -173,7 +183,8 @@ function NewDriverForm({ editId }: { editId?: string }) {
173
183
  licenseClass: form.licenseClass.trim() || undefined,
174
184
  licenseExpiry: fromDateInput(form.licenseExpiry),
175
185
  status: toApiDriverStatus(form.status),
176
- vehicleId: form.vehicleId || undefined,
186
+ vehicleId: form.vehicleId || null,
187
+ vehicleIds: form.vehicleIds,
177
188
  rating: form.rating.trim() ? Number(form.rating) : undefined,
178
189
  notes: form.notes.trim() || undefined,
179
190
  hireDate: fromDateInput(form.hireDate),
@@ -411,7 +422,13 @@ function NewDriverForm({ editId }: { editId?: string }) {
411
422
  <EntityCombobox
412
423
  id="vehicle"
413
424
  value={form.vehicleId}
414
- onChange={(value) => setForm((prev) => ({ ...prev, vehicleId: value }))}
425
+ onChange={(value) =>
426
+ setForm((prev) => ({
427
+ ...prev,
428
+ vehicleId: value,
429
+ vehicleIds: value ? [value] : [],
430
+ }))
431
+ }
415
432
  options={vehicles}
416
433
  loading={vehiclesLoading}
417
434
  getId={(vehicle) => vehicle.id}
@@ -424,6 +441,10 @@ function NewDriverForm({ editId }: { editId?: string }) {
424
441
  )}
425
442
  selectedOption={selectedVehicle}
426
443
  selectedLoading={selectedVehicleLoading}
444
+ hasMore={vehiclesHaveMore}
445
+ totalCount={totalVehicles}
446
+ onLoadMore={loadMoreVehicles}
447
+ loadingMore={vehiclesLoadingMore}
427
448
  placeholder="Unassigned"
428
449
  emptyText="No vehicles match that search."
429
450
  />
@@ -661,7 +682,7 @@ export default function DriverDetailsPage() {
661
682
  customColors={{ bg: 'bg-status-warning-bg-subtle', text: 'text-status-warning-text' }}
662
683
  />
663
684
  )}
664
- {driver.rating > 0 && (
685
+ {driver.rating !== undefined && (
665
686
  <div className="flex items-center gap-1">
666
687
  <Star size={16} className="text-status-warning-text fill-current" />
667
688
  <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>
@@ -299,7 +299,13 @@ export default function DriversPage() {
299
299
  ))}
300
300
  </SelectContent>
301
301
  </Select>
302
- <Select value={licenseClassFilter} onValueChange={setLicenseClassFilter}>
302
+ <Select
303
+ value={licenseClassFilter}
304
+ onValueChange={(value) => {
305
+ setLicenseClassFilter(value);
306
+ setPage(0);
307
+ }}
308
+ >
303
309
  <SelectTrigger className="w-[180px]">
304
310
  <SelectValue placeholder="License class" />
305
311
  </SelectTrigger>
@@ -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
  ))}
@@ -81,6 +81,8 @@ function formatMoney(
81
81
  export default function MultiModalShipmentPage() {
82
82
  const [pageIndex, setPageIndex] = useState(0);
83
83
  const pageSize = 10;
84
+ const [shipmentNumber, setShipmentNumber] = useState('');
85
+ const [shipmentSearch, setShipmentSearch] = useState('');
84
86
  const {
85
87
  shipments,
86
88
  totalShipments,
@@ -92,9 +94,9 @@ export default function MultiModalShipmentPage() {
92
94
  } = useMultiModalShipments({
93
95
  limit: pageSize,
94
96
  offset: pageIndex * pageSize,
97
+ search: shipmentSearch,
95
98
  });
96
99
 
97
- const [shipmentNumber, setShipmentNumber] = useState('');
98
100
  const [originAddress, setOriginAddress] = useState('');
99
101
  const [destinationAddress, setDestinationAddress] = useState('');
100
102
  const [legs, setLegs] = useState<ShipmentLegDraft[]>([emptyLeg()]);
@@ -529,6 +531,18 @@ export default function MultiModalShipmentPage() {
529
531
  </PageSection>
530
532
 
531
533
  <PageSection title="Planned Multi-Modal Shipments">
534
+ <div className="mb-4 max-w-xl space-y-2">
535
+ <Label htmlFor="planned-shipment-search">Search planned shipments</Label>
536
+ <Input
537
+ id="planned-shipment-search"
538
+ value={shipmentSearch}
539
+ onChange={(event) => {
540
+ setShipmentSearch(event.target.value);
541
+ setPageIndex(0);
542
+ }}
543
+ placeholder="Search by shipment or tracking number"
544
+ />
545
+ </div>
532
546
  {isLoading ? (
533
547
  <LoadingState message="Loading multi-modal shipments..." />
534
548
  ) : error ? (
@@ -4,7 +4,7 @@
4
4
  * Displays detailed information about a specific order.
5
5
  */
6
6
 
7
- import { useEffect, useState } from 'react';
7
+ import { useState } from 'react';
8
8
  import { useParams } from 'react-router';
9
9
  import {
10
10
  Package,
@@ -105,15 +105,27 @@ export default function OrderDetailsPage() {
105
105
  const [advancing, setAdvancing] = useState(false);
106
106
  const [actionError, setActionError] = useState<string | null>(null);
107
107
  const debouncedFleetSearch = useDebouncedValue(fleetSearch);
108
- const { vehicles, isLoading: vehiclesLoading } = useVehicles({
108
+ const {
109
+ vehicles,
110
+ isLoading: vehiclesLoading,
111
+ isLoadingMore: vehiclesLoadingMore,
112
+ hasMore: vehiclesHaveMore,
113
+ totalVehicles,
114
+ loadMore: loadMoreVehicles,
115
+ } = useVehicles({
109
116
  limit: 20,
110
117
  search: debouncedFleetSearch || undefined,
111
- driverId: selectedDriver || undefined,
112
118
  });
113
- const { drivers, isLoading: driversLoading } = useDrivers({
119
+ const {
120
+ drivers,
121
+ isLoading: driversLoading,
122
+ isLoadingMore: driversLoadingMore,
123
+ hasMore: driversHaveMore,
124
+ totalDrivers,
125
+ loadMore: loadMoreDrivers,
126
+ } = useDrivers({
114
127
  limit: 20,
115
128
  search: debouncedFleetSearch || undefined,
116
- vehicleId: selectedVehicle || undefined,
117
129
  });
118
130
  // Resolved independently of the (searched, 20-row) lists above so the
119
131
  // trigger still shows the right label once a pick scrolls out of the
@@ -132,26 +144,6 @@ export default function OrderDetailsPage() {
132
144
  (driver) => driver.status === 'available' || driver.id === order?.driverId
133
145
  );
134
146
 
135
- useEffect(() => {
136
- if (
137
- !driversLoading &&
138
- selectedDriver &&
139
- !drivers.some((driver) => driver.id === selectedDriver)
140
- ) {
141
- setSelectedDriver('');
142
- }
143
- }, [drivers, driversLoading, selectedDriver]);
144
-
145
- useEffect(() => {
146
- if (
147
- !vehiclesLoading &&
148
- selectedVehicle &&
149
- !vehicles.some((vehicle) => vehicle.id === selectedVehicle)
150
- ) {
151
- setSelectedVehicle('');
152
- }
153
- }, [selectedVehicle, vehicles, vehiclesLoading]);
154
-
155
147
  const handleAssign = async () => {
156
148
  if (!orderId || !order) {
157
149
  setActionError('This order is no longer available. Refresh and try again.');
@@ -607,6 +599,10 @@ export default function OrderDetailsPage() {
607
599
  )}
608
600
  selectedOption={selectedVehicleRecord}
609
601
  selectedLoading={selectedVehicleLoading}
602
+ hasMore={vehiclesHaveMore}
603
+ totalCount={totalVehicles}
604
+ onLoadMore={loadMoreVehicles}
605
+ loadingMore={vehiclesLoadingMore}
610
606
  required
611
607
  placeholder={
612
608
  order.vehicleId ? `Current: ${order.vehicleId}` : 'Select a vehicle'
@@ -634,6 +630,10 @@ export default function OrderDetailsPage() {
634
630
  )}
635
631
  selectedOption={selectedDriverRecord}
636
632
  selectedLoading={selectedDriverLoading}
633
+ hasMore={driversHaveMore}
634
+ totalCount={totalDrivers}
635
+ onLoadMore={loadMoreDrivers}
636
+ loadingMore={driversLoadingMore}
637
637
  required
638
638
  placeholder={
639
639
  order.driverId ? `Current: ${order.driverId}` : 'Select a driver'