@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
@@ -15,6 +15,7 @@ import {
15
15
  Warehouse,
16
16
  ChevronLeft,
17
17
  ChevronRight,
18
+ Trash2,
18
19
  } from 'lucide-react';
19
20
  import { PageLayout, LoadingState, ErrorState } from '../components/PageLayout';
20
21
  import {
@@ -26,8 +27,20 @@ import {
26
27
  Badge,
27
28
  Input,
28
29
  Label,
30
+ Textarea,
31
+ Checkbox,
29
32
  } from '../components/ui';
30
- import { EmphasisPanel, NextSteps, PagePurpose } from '@burdenoff/fe-libs/ui';
33
+ import {
34
+ EmphasisPanel,
35
+ NextSteps,
36
+ PagePurpose,
37
+ ResponsiveDialog,
38
+ ResponsiveDialogContent,
39
+ ResponsiveDialogHeader,
40
+ ResponsiveDialogFooter,
41
+ ResponsiveDialogTitle,
42
+ ResponsiveDialogDescription,
43
+ } from '@burdenoff/fe-libs/ui';
31
44
  import { useProduct, useInventory, useProductStockLevels } from '../hooks/useInventory';
32
45
  import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
33
46
  import { formatCurrency } from '../utils/formatters';
@@ -42,9 +55,18 @@ function NewProductForm({ editId }: { editId?: string }) {
42
55
  const [form, setForm] = useState({
43
56
  name: '',
44
57
  sku: '',
58
+ description: '',
45
59
  category: '',
46
60
  brand: '',
61
+ length: '',
62
+ width: '',
63
+ height: '',
64
+ dimensionUnit: '',
47
65
  weight: '',
66
+ value: '',
67
+ hazardous: false,
68
+ fragile: false,
69
+ temperatureControlled: false,
48
70
  });
49
71
  const [submitting, setSubmitting] = useState(false);
50
72
  const [error, setError] = useState<string | null>(null);
@@ -54,9 +76,18 @@ function NewProductForm({ editId }: { editId?: string }) {
54
76
  setForm({
55
77
  name: product.name ?? '',
56
78
  sku: product.sku ?? '',
79
+ description: product.description ?? '',
57
80
  category: product.category ?? '',
58
81
  brand: product.brand ?? '',
59
- weight: product.weight != null ? String(product.weight) : '',
82
+ length: product.dimensions?.length !== undefined ? String(product.dimensions.length) : '',
83
+ width: product.dimensions?.width !== undefined ? String(product.dimensions.width) : '',
84
+ height: product.dimensions?.height !== undefined ? String(product.dimensions.height) : '',
85
+ dimensionUnit: product.dimensions?.unit ?? '',
86
+ weight: product.weight !== undefined ? String(product.weight) : '',
87
+ value: product.value !== undefined ? String(product.value) : '',
88
+ hazardous: product.hazardous,
89
+ fragile: product.fragile,
90
+ temperatureControlled: product.temperatureControlled,
60
91
  });
61
92
  }
62
93
  }, [editId, product]);
@@ -72,24 +103,42 @@ function NewProductForm({ editId }: { editId?: string }) {
72
103
  setSubmitting(true);
73
104
  setError(null);
74
105
  try {
75
- const weight = form.weight.trim() ? Number(form.weight) : undefined;
106
+ const parseNumber = (value: string): number | undefined => {
107
+ if (!value.trim()) return undefined;
108
+ const parsed = Number(value);
109
+ return Number.isFinite(parsed) ? parsed : undefined;
110
+ };
111
+ const dimensions =
112
+ form.length.trim() || form.width.trim() || form.height.trim() || form.dimensionUnit.trim()
113
+ ? {
114
+ ...(parseNumber(form.length) !== undefined
115
+ ? { length: parseNumber(form.length) }
116
+ : {}),
117
+ ...(parseNumber(form.width) !== undefined ? { width: parseNumber(form.width) } : {}),
118
+ ...(parseNumber(form.height) !== undefined
119
+ ? { height: parseNumber(form.height) }
120
+ : {}),
121
+ ...(form.dimensionUnit.trim() ? { unit: form.dimensionUnit.trim() } : {}),
122
+ }
123
+ : undefined;
124
+ const input = {
125
+ name: form.name.trim(),
126
+ sku: form.sku.trim(),
127
+ description: form.description.trim() || (isEditing ? null : undefined),
128
+ category: form.category.trim() || (isEditing ? null : undefined),
129
+ brand: form.brand.trim() || (isEditing ? null : undefined),
130
+ dimensions: dimensions ?? (isEditing ? null : undefined),
131
+ weight: parseNumber(form.weight) ?? (isEditing ? null : undefined),
132
+ value: parseNumber(form.value) ?? (isEditing ? null : undefined),
133
+ hazardous: form.hazardous,
134
+ fragile: form.fragile,
135
+ temperatureControlled: form.temperatureControlled,
136
+ };
76
137
  if (isEditing && editId) {
77
- const updated = await updateProduct(editId, {
78
- name: form.name.trim(),
79
- sku: form.sku.trim(),
80
- category: form.category.trim() || undefined,
81
- brand: form.brand.trim() || undefined,
82
- weight: weight !== undefined && !Number.isNaN(weight) ? weight : undefined,
83
- });
138
+ const updated = await updateProduct(editId, input);
84
139
  navigate?.(joinPath(basePath, `inventory/products/${updated.id}`));
85
140
  } else {
86
- const created = await createProduct({
87
- name: form.name.trim(),
88
- sku: form.sku.trim(),
89
- category: form.category.trim() || undefined,
90
- brand: form.brand.trim() || undefined,
91
- weight: weight !== undefined && !Number.isNaN(weight) ? weight : undefined,
92
- });
141
+ const created = await createProduct(input);
93
142
  navigate?.(joinPath(basePath, `inventory/products/${created.id}`));
94
143
  }
95
144
  } catch (err) {
@@ -177,6 +226,61 @@ function NewProductForm({ editId }: { editId?: string }) {
177
226
  placeholder="Acme"
178
227
  />
179
228
  </div>
229
+ </div>
230
+ <div className="space-y-2">
231
+ <Label htmlFor="description">Description</Label>
232
+ <Textarea
233
+ id="description"
234
+ value={form.description}
235
+ onChange={(e) => setForm((prev) => ({ ...prev, description: e.target.value }))}
236
+ placeholder="Describe the item"
237
+ rows={3}
238
+ />
239
+ </div>
240
+ <div className="grid grid-cols-2 md:grid-cols-4 gap-4">
241
+ {(
242
+ [
243
+ ['length', 'Length'],
244
+ ['width', 'Width'],
245
+ ['height', 'Height'],
246
+ ] as const
247
+ ).map(([field, label]) => (
248
+ <div key={field} className="space-y-2">
249
+ <Label htmlFor={field}>{label}</Label>
250
+ <Input
251
+ id={field}
252
+ type="number"
253
+ min="0"
254
+ step="0.01"
255
+ value={form[field]}
256
+ onChange={(e) => setForm((prev) => ({ ...prev, [field]: e.target.value }))}
257
+ placeholder="Not provided"
258
+ />
259
+ </div>
260
+ ))}
261
+ <div className="space-y-2">
262
+ <Label htmlFor="dimensionUnit">Dimension unit</Label>
263
+ <Input
264
+ id="dimensionUnit"
265
+ value={form.dimensionUnit}
266
+ onChange={(e) => setForm((prev) => ({ ...prev, dimensionUnit: e.target.value }))}
267
+ placeholder="cm"
268
+ />
269
+ </div>
270
+ </div>
271
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
272
+ <div className="space-y-2">
273
+ <Label htmlFor="value">Unit value</Label>
274
+ <Input
275
+ id="value"
276
+ type="number"
277
+ min="0"
278
+ step="0.01"
279
+ value={form.value}
280
+ onChange={(e) => setForm((prev) => ({ ...prev, value: e.target.value }))}
281
+ placeholder="Not provided"
282
+ />
283
+ </div>
180
284
  <div className="space-y-2">
181
285
  <Label htmlFor="weight">Weight (kg)</Label>
182
286
  <Input
@@ -186,10 +290,29 @@ function NewProductForm({ editId }: { editId?: string }) {
186
290
  step="0.01"
187
291
  value={form.weight}
188
292
  onChange={(e) => setForm((prev) => ({ ...prev, weight: e.target.value }))}
189
- placeholder="0.5"
293
+ placeholder="Not provided"
190
294
  />
191
295
  </div>
192
296
  </div>
297
+ <div className="flex flex-wrap gap-4">
298
+ {(
299
+ [
300
+ ['fragile', 'Fragile'],
301
+ ['hazardous', 'Hazardous'],
302
+ ['temperatureControlled', 'Temperature controlled'],
303
+ ] as const
304
+ ).map(([field, label]) => (
305
+ <label key={field} className="flex items-center gap-2 text-sm">
306
+ <Checkbox
307
+ checked={form[field]}
308
+ onCheckedChange={(checked) =>
309
+ setForm((prev) => ({ ...prev, [field]: checked === true }))
310
+ }
311
+ />
312
+ {label}
313
+ </label>
314
+ ))}
315
+ </div>
193
316
  </div>
194
317
  </EmphasisPanel>
195
318
  </form>
@@ -201,7 +324,11 @@ export default function ProductDetailsPage() {
201
324
  const { productId } = useParams<{ productId: string }>();
202
325
  const { basePath, navigate } = useMoveTheWheels();
203
326
  const { product, isLoading, error, refetch } = useProduct(productId);
327
+ const { deleteProduct } = useInventory();
204
328
  const isEditRoute = useLocation().pathname.endsWith('/edit');
329
+ const [deleteOpen, setDeleteOpen] = useState(false);
330
+ const [deleting, setDeleting] = useState(false);
331
+ const [deleteError, setDeleteError] = useState<string | null>(null);
205
332
  // Where this item is actually stocked — inventoryItems(filter: { cargoItemId }).
206
333
  const [stockPage, setStockPage] = useState(0);
207
334
  const stockPageSize = 20;
@@ -223,6 +350,21 @@ export default function ProductDetailsPage() {
223
350
 
224
351
  const goBack = () => navigate?.(joinPath(basePath, 'inventory/products'));
225
352
  const goTo = (path: string) => navigate?.(joinPath(basePath, path));
353
+ const handleDelete = async () => {
354
+ if (!productId) return;
355
+ setDeleting(true);
356
+ setDeleteError(null);
357
+ try {
358
+ await deleteProduct(productId);
359
+ setDeleteOpen(false);
360
+ goBack();
361
+ } catch (deleteFailure) {
362
+ setDeleteError(
363
+ deleteFailure instanceof Error ? deleteFailure.message : 'Failed to delete product.'
364
+ );
365
+ setDeleting(false);
366
+ }
367
+ };
226
368
 
227
369
  if (!productId) return <NewProductForm />;
228
370
  if (isEditRoute) return <NewProductForm editId={productId} />;
@@ -248,7 +390,7 @@ export default function ProductDetailsPage() {
248
390
  }
249
391
  headerContent={
250
392
  <div className="flex gap-2">
251
- <Badge variant="outline">{product.category}</Badge>
393
+ <Badge variant="outline">{product.category ?? 'Not provided'}</Badge>
252
394
  {product.brand && <Badge variant="secondary">{product.brand}</Badge>}
253
395
  {product.fragile && <Badge variant="destructive">Fragile</Badge>}
254
396
  {product.hazardous && <Badge variant="destructive">Hazardous</Badge>}
@@ -289,7 +431,7 @@ export default function ProductDetailsPage() {
289
431
  <CardTitle>Description</CardTitle>
290
432
  </CardHeader>
291
433
  <CardContent>
292
- <p>{product.description}</p>
434
+ <p>{product.description ?? 'Not provided'}</p>
293
435
  </CardContent>
294
436
  </Card>
295
437
 
@@ -301,34 +443,24 @@ export default function ProductDetailsPage() {
301
443
  </CardHeader>
302
444
  <CardContent>
303
445
  <div className="grid grid-cols-2 md:grid-cols-4 gap-4">
304
- <div>
305
- <p className="text-sm text-text-secondary">Length</p>
306
- <p className="text-2xl font-bold">
307
- {product.dimensions.length}
308
- <span className="text-sm font-normal"> {product.dimensions.unit}</span>
309
- </p>
310
- </div>
311
- <div>
312
- <p className="text-sm text-text-secondary">Width</p>
313
- <p className="text-2xl font-bold">
314
- {product.dimensions.width}
315
- <span className="text-sm font-normal"> {product.dimensions.unit}</span>
316
- </p>
317
- </div>
318
- <div>
319
- <p className="text-sm text-text-secondary">Height</p>
320
- <p className="text-2xl font-bold">
321
- {product.dimensions.height}
322
- <span className="text-sm font-normal"> {product.dimensions.unit}</span>
323
- </p>
324
- </div>
325
- <div>
326
- <p className="text-sm text-text-secondary">Weight</p>
327
- <p className="text-2xl font-bold">
328
- {product.weight}
329
- <span className="text-sm font-normal"> kg</span>
330
- </p>
331
- </div>
446
+ {(
447
+ [
448
+ ['Length', product.dimensions?.length, product.dimensions?.unit],
449
+ ['Width', product.dimensions?.width, product.dimensions?.unit],
450
+ ['Height', product.dimensions?.height, product.dimensions?.unit],
451
+ ['Weight', product.weight, 'kg'],
452
+ ] as const
453
+ ).map(([label, value, unit]) => (
454
+ <div key={label}>
455
+ <p className="text-sm text-text-secondary">{label}</p>
456
+ <p className="text-2xl font-bold">
457
+ {value !== undefined ? value : 'Not provided'}
458
+ {value !== undefined && unit && (
459
+ <span className="text-sm font-normal"> {unit}</span>
460
+ )}
461
+ </p>
462
+ </div>
463
+ ))}
332
464
  </div>
333
465
  </CardContent>
334
466
  </Card>
@@ -360,7 +492,9 @@ export default function ProductDetailsPage() {
360
492
  <h2 className="flex items-center gap-2 text-base font-semibold text-text-primary mb-3">
361
493
  <DollarSign size={18} /> Pricing
362
494
  </h2>
363
- <p className="text-3xl font-bold">{formatCurrency(product.value)}</p>
495
+ <p className="text-3xl font-bold">
496
+ {product.value !== undefined ? formatCurrency(product.value) : 'Not provided'}
497
+ </p>
364
498
  <p className="text-sm text-text-secondary">Unit price</p>
365
499
  </EmphasisPanel>
366
500
 
@@ -466,8 +600,53 @@ export default function ProductDetailsPage() {
466
600
  )}
467
601
  </CardContent>
468
602
  </Card>
603
+
604
+ <Card className="border-status-error-border">
605
+ <CardHeader>
606
+ <CardTitle className="flex items-center gap-2 text-status-error-text">
607
+ <Trash2 size={18} /> Delete Product
608
+ </CardTitle>
609
+ </CardHeader>
610
+ <CardContent>
611
+ <p className="mb-3 text-sm text-text-secondary">
612
+ Removes this catalog item. Existing inventory records may prevent deletion.
613
+ </p>
614
+ <Button
615
+ variant="outline"
616
+ className="border-status-error-border text-status-error-text hover:bg-status-error-bg-subtle"
617
+ onClick={() => setDeleteOpen(true)}
618
+ >
619
+ <Trash2 size={16} /> Delete this product
620
+ </Button>
621
+ </CardContent>
622
+ </Card>
469
623
  </div>
470
624
  </div>
625
+ <ResponsiveDialog open={deleteOpen} onOpenChange={setDeleteOpen}>
626
+ <ResponsiveDialogContent>
627
+ <ResponsiveDialogHeader>
628
+ <ResponsiveDialogTitle>Delete this product?</ResponsiveDialogTitle>
629
+ <ResponsiveDialogDescription>
630
+ This removes {product.name} from the catalog and cannot be undone from here.
631
+ </ResponsiveDialogDescription>
632
+ </ResponsiveDialogHeader>
633
+ <div className="px-1">
634
+ <FormError message={deleteError} />
635
+ </div>
636
+ <ResponsiveDialogFooter>
637
+ <Button variant="outline" onClick={() => setDeleteOpen(false)} disabled={deleting}>
638
+ Cancel
639
+ </Button>
640
+ <Button
641
+ className="bg-status-error-text text-text-inverse hover:opacity-90"
642
+ onClick={handleDelete}
643
+ disabled={deleting}
644
+ >
645
+ {deleting ? 'Deleting...' : 'Delete product'}
646
+ </Button>
647
+ </ResponsiveDialogFooter>
648
+ </ResponsiveDialogContent>
649
+ </ResponsiveDialog>
471
650
  </PageLayout>
472
651
  );
473
652
  }
@@ -48,7 +48,9 @@ export default function ProductsPage() {
48
48
  cell: (row) => (
49
49
  <div>
50
50
  <p className="font-medium">{row.name}</p>
51
- <p className="text-sm text-text-secondary line-clamp-1">{row.description}</p>
51
+ <p className="text-sm text-text-secondary line-clamp-1">
52
+ {row.description ?? 'Not provided'}
53
+ </p>
52
54
  </div>
53
55
  ),
54
56
  },
@@ -57,27 +59,33 @@ export default function ProductsPage() {
57
59
  header: 'Category',
58
60
  accessorKey: 'category',
59
61
  sortable: true,
60
- cell: (row) => <Badge variant="outline">{row.category}</Badge>,
62
+ cell: (row) => <Badge variant="outline">{row.category ?? 'Not provided'}</Badge>,
61
63
  },
62
64
  {
63
65
  id: 'dimensions',
64
66
  header: 'Dimensions',
65
67
  cell: (row) =>
66
- `${row.dimensions.length}x${row.dimensions.width}x${row.dimensions.height} ${row.dimensions.unit}`,
68
+ row.dimensions
69
+ ? `${row.dimensions.length ?? 'Not provided'}x${row.dimensions.width ?? 'Not provided'}x${row.dimensions.height ?? 'Not provided'}${row.dimensions.unit ? ` ${row.dimensions.unit}` : ''}`
70
+ : 'Not provided',
67
71
  },
68
72
  {
69
73
  id: 'weight',
70
74
  header: 'Weight',
71
75
  accessorKey: 'weight',
72
76
  sortable: true,
73
- cell: (row) => `${row.weight} kg`,
77
+ cell: (row) => (row.weight !== undefined ? `${row.weight} kg` : 'Not provided'),
74
78
  },
75
79
  {
76
80
  id: 'value',
77
81
  header: 'Value',
78
82
  accessorKey: 'value',
79
83
  sortable: true,
80
- cell: (row) => <span className="font-medium">{formatCurrency(row.value)}</span>,
84
+ cell: (row) => (
85
+ <span className="font-medium">
86
+ {row.value !== undefined ? formatCurrency(row.value) : 'Not provided'}
87
+ </span>
88
+ ),
81
89
  },
82
90
  {
83
91
  id: 'flags',
@@ -95,20 +95,22 @@ function NewRouteForm({ editId }: { editId?: string }) {
95
95
  isLoading: vehiclesLoading,
96
96
  hasMore: vehiclesHaveMore,
97
97
  totalVehicles,
98
+ loadMore: loadMoreVehicles,
99
+ isLoadingMore: vehiclesLoadingMore,
98
100
  } = useVehicles({
99
101
  limit: 20,
100
102
  search: vehicleSearch || undefined,
101
- driverId: form.driverId || undefined,
102
103
  });
103
104
  const {
104
105
  drivers,
105
106
  isLoading: driversLoading,
106
107
  hasMore: driversHaveMore,
107
108
  totalDrivers,
109
+ loadMore: loadMoreDrivers,
110
+ isLoadingMore: driversLoadingMore,
108
111
  } = useDrivers({
109
112
  limit: 20,
110
113
  search: driverSearch || undefined,
111
- vehicleId: form.vehicleId || undefined,
112
114
  });
113
115
  // The selected vehicle/driver may fall outside the current search page (e.g. when
114
116
  // editing a route or after the assignee no longer matches the search term); fetch
@@ -195,16 +197,14 @@ function NewRouteForm({ editId }: { editId?: string }) {
195
197
  country: '',
196
198
  },
197
199
  type: stop.type,
198
- scheduledTime: new Date(),
199
- duration: 0,
200
200
  status: 'pending',
201
201
  }));
202
202
  if (isEditing) {
203
203
  const updated = await updateRoute(editId, {
204
204
  name: form.name.trim(),
205
205
  status: form.status,
206
- vehicleId: form.vehicleId || undefined,
207
- driverId: form.driverId || undefined,
206
+ vehicleId: form.vehicleId || null,
207
+ driverId: form.driverId || null,
208
208
  stops,
209
209
  });
210
210
  navigate?.(joinPath(basePath, `routes/${updated.id}`));
@@ -316,7 +316,8 @@ function NewRouteForm({ editId }: { editId?: string }) {
316
316
  searchPlaceholder="Search vehicle number or type…"
317
317
  hasMore={vehiclesHaveMore}
318
318
  totalCount={totalVehicles}
319
- required
319
+ onLoadMore={loadMoreVehicles}
320
+ loadingMore={vehiclesLoadingMore}
320
321
  placeholder="Assign later"
321
322
  emptyText="No available vehicles."
322
323
  />
@@ -342,7 +343,8 @@ function NewRouteForm({ editId }: { editId?: string }) {
342
343
  searchPlaceholder="Search driver name or employee ID…"
343
344
  hasMore={driversHaveMore}
344
345
  totalCount={totalDrivers}
345
- required
346
+ onLoadMore={loadMoreDrivers}
347
+ loadingMore={driversLoadingMore}
346
348
  placeholder="Assign later"
347
349
  emptyText="No available drivers."
348
350
  />
@@ -542,8 +544,12 @@ export default function RouteDetailsPage() {
542
544
  headerContent={
543
545
  <div className="flex gap-2">
544
546
  <StatusBadge status={route.status} customColors={ROUTE_STATUS_COLORS[route.status]} />
545
- <Badge variant={route.optimized ? 'default' : 'secondary'}>
546
- {route.optimized ? 'Optimized' : 'Not Optimized'}
547
+ <Badge variant={route.optimized === true ? 'default' : 'secondary'}>
548
+ {route.optimized === undefined
549
+ ? 'Optimization unknown'
550
+ : route.optimized
551
+ ? 'Optimized'
552
+ : 'Not Optimized'}
547
553
  </Badge>
548
554
  </div>
549
555
  }
@@ -668,14 +674,20 @@ export default function RouteDetailsPage() {
668
674
  </div>
669
675
  <div className="text-right">
670
676
  <p className="text-sm">
671
- Scheduled: {formatDate(stop.scheduledTime, 'time')}
677
+ {stop.scheduledTime
678
+ ? `Scheduled: ${formatDate(stop.scheduledTime, 'time')}`
679
+ : 'Scheduled: Not provided'}
672
680
  </p>
673
681
  {stop.actualTime && (
674
682
  <p className="text-sm text-status-success-text">
675
683
  Actual: {formatDate(stop.actualTime, 'time')}
676
684
  </p>
677
685
  )}
678
- <p className="text-xs text-text-secondary">{stop.duration} min stop</p>
686
+ <p className="text-xs text-text-secondary">
687
+ {stop.duration !== undefined
688
+ ? `${stop.duration} min stop`
689
+ : 'Duration not provided'}
690
+ </p>
679
691
  </div>
680
692
  </div>
681
693
  </div>
@@ -88,8 +88,10 @@ export default function RoutesPage() {
88
88
  id: 'optimized',
89
89
  header: 'Optimized',
90
90
  cell: (row) => (
91
- <Badge variant={row.optimized ? 'default' : 'outline'}>
92
- {row.optimized ? 'Yes' : 'No'}
91
+ <Badge
92
+ variant={row.optimized === undefined ? 'outline' : row.optimized ? 'default' : 'outline'}
93
+ >
94
+ {row.optimized === undefined ? 'Unknown' : row.optimized ? 'Yes' : 'No'}
93
95
  </Badge>
94
96
  ),
95
97
  },
@@ -67,7 +67,7 @@ export default function TrackingPage() {
67
67
  ? {
68
68
  lat: coord.lat,
69
69
  lng: coord.lng,
70
- timestamp: location.timestamp?.toISOString() ?? new Date().toISOString(),
70
+ timestamp: location.timestamp?.toISOString(),
71
71
  }
72
72
  : null;
73
73
  })
@@ -87,7 +87,7 @@ export default function TrackingPage() {
87
87
  const current: TrackEndpoint | null = currentCoord
88
88
  ? {
89
89
  ...currentCoord,
90
- label: tracking?.currentLocation.address || 'Current position',
90
+ label: tracking?.currentLocation?.address || 'Current position',
91
91
  }
92
92
  : null;
93
93
 
@@ -120,7 +120,7 @@ export default function TrackingPage() {
120
120
  {/* Search Form */}
121
121
  <Card>
122
122
  <CardContent className="p-6">
123
- <form onSubmit={handleSearch} className="flex gap-4">
123
+ <form onSubmit={handleSearch} className="flex flex-col gap-4 sm:flex-row">
124
124
  <div className="flex-1 relative">
125
125
  <Search className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-text-secondary" />
126
126
  <Input
@@ -133,7 +133,12 @@ export default function TrackingPage() {
133
133
  className="pl-10 h-12 text-lg"
134
134
  />
135
135
  </div>
136
- <Button type="submit" size="lg" disabled={!trackingInput || isLoading}>
136
+ <Button
137
+ type="submit"
138
+ size="lg"
139
+ className="w-full sm:w-auto"
140
+ disabled={!trackingInput || isLoading}
141
+ >
137
142
  {isLoading
138
143
  ? t('movethewheels.tracking.search.searching', 'Searching...')
139
144
  : t('movethewheels.tracking.search.track', 'Track')}
@@ -222,7 +227,7 @@ export default function TrackingPage() {
222
227
  {t('movethewheels.tracking.details.lastUpdated', 'Last Updated')}
223
228
  </p>
224
229
  <p className="font-medium">
225
- {tracking
230
+ {tracking?.lastUpdated
226
231
  ? formatDate(tracking.lastUpdated, 'relative')
227
232
  : t('movethewheels.tracking.details.noUpdates', 'No tracking updates yet')}
228
233
  </p>
@@ -416,7 +421,9 @@ export default function TrackingPage() {
416
421
  <div>
417
422
  <p className="text-sm font-medium">{alert.message}</p>
418
423
  <p className="text-xs text-text-secondary">
419
- {formatDate(alert.timestamp, 'relative')}
424
+ {alert.timestamp
425
+ ? formatDate(alert.timestamp, 'relative')
426
+ : t('movethewheels.tracking.alerts.timeUnknown', 'Time not recorded')}
420
427
  {alert.resolvedAt &&
421
428
  ` - ${t('movethewheels.tracking.alerts.resolved', 'Resolved')}`}
422
429
  </p>
@@ -456,18 +463,31 @@ export default function TrackingPage() {
456
463
  </p>
457
464
  </div>
458
465
  )}
459
- {tracking && (
466
+ {tracking?.currentLocation ? (
460
467
  <>
461
468
  <p className="text-sm">
462
469
  {tracking.currentLocation.address ||
463
470
  t('movethewheels.tracking.location.updating', 'Location updating...')}
464
471
  </p>
465
- <p className="text-xs text-text-secondary mt-1">
466
- {t('movethewheels.tracking.location.updated', 'Updated')}{' '}
467
- {formatDate(tracking.currentLocation.timestamp || new Date(), 'relative')}
468
- </p>
472
+ {tracking.currentLocation.timestamp ? (
473
+ <p className="text-xs text-text-secondary mt-1">
474
+ {t('movethewheels.tracking.location.updated', 'Updated')}{' '}
475
+ {formatDate(tracking.currentLocation.timestamp, 'relative')}
476
+ </p>
477
+ ) : (
478
+ <p className="text-xs text-text-secondary mt-1">
479
+ {t(
480
+ 'movethewheels.tracking.location.timeUnknown',
481
+ 'Update time not recorded'
482
+ )}
483
+ </p>
484
+ )}
469
485
  </>
470
- )}
486
+ ) : tracking ? (
487
+ <p className="text-sm text-text-secondary">
488
+ {t('movethewheels.tracking.location.unknown', 'Location not recorded')}
489
+ </p>
490
+ ) : null}
471
491
  </CardContent>
472
492
  </Card>
473
493