@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
@@ -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',
@@ -195,16 +195,14 @@ function NewRouteForm({ editId }: { editId?: string }) {
195
195
  country: '',
196
196
  },
197
197
  type: stop.type,
198
- scheduledTime: new Date(),
199
- duration: 0,
200
198
  status: 'pending',
201
199
  }));
202
200
  if (isEditing) {
203
201
  const updated = await updateRoute(editId, {
204
202
  name: form.name.trim(),
205
203
  status: form.status,
206
- vehicleId: form.vehicleId || undefined,
207
- driverId: form.driverId || undefined,
204
+ vehicleId: form.vehicleId || null,
205
+ driverId: form.driverId || null,
208
206
  stops,
209
207
  });
210
208
  navigate?.(joinPath(basePath, `routes/${updated.id}`));
@@ -542,8 +540,12 @@ export default function RouteDetailsPage() {
542
540
  headerContent={
543
541
  <div className="flex gap-2">
544
542
  <StatusBadge status={route.status} customColors={ROUTE_STATUS_COLORS[route.status]} />
545
- <Badge variant={route.optimized ? 'default' : 'secondary'}>
546
- {route.optimized ? 'Optimized' : 'Not Optimized'}
543
+ <Badge variant={route.optimized === true ? 'default' : 'secondary'}>
544
+ {route.optimized === undefined
545
+ ? 'Optimization unknown'
546
+ : route.optimized
547
+ ? 'Optimized'
548
+ : 'Not Optimized'}
547
549
  </Badge>
548
550
  </div>
549
551
  }
@@ -668,14 +670,20 @@ export default function RouteDetailsPage() {
668
670
  </div>
669
671
  <div className="text-right">
670
672
  <p className="text-sm">
671
- Scheduled: {formatDate(stop.scheduledTime, 'time')}
673
+ {stop.scheduledTime
674
+ ? `Scheduled: ${formatDate(stop.scheduledTime, 'time')}`
675
+ : 'Scheduled: Not provided'}
672
676
  </p>
673
677
  {stop.actualTime && (
674
678
  <p className="text-sm text-status-success-text">
675
679
  Actual: {formatDate(stop.actualTime, 'time')}
676
680
  </p>
677
681
  )}
678
- <p className="text-xs text-text-secondary">{stop.duration} min stop</p>
682
+ <p className="text-xs text-text-secondary">
683
+ {stop.duration !== undefined
684
+ ? `${stop.duration} min stop`
685
+ : 'Duration not provided'}
686
+ </p>
679
687
  </div>
680
688
  </div>
681
689
  </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
 
@@ -222,7 +222,7 @@ export default function TrackingPage() {
222
222
  {t('movethewheels.tracking.details.lastUpdated', 'Last Updated')}
223
223
  </p>
224
224
  <p className="font-medium">
225
- {tracking
225
+ {tracking?.lastUpdated
226
226
  ? formatDate(tracking.lastUpdated, 'relative')
227
227
  : t('movethewheels.tracking.details.noUpdates', 'No tracking updates yet')}
228
228
  </p>
@@ -416,7 +416,9 @@ export default function TrackingPage() {
416
416
  <div>
417
417
  <p className="text-sm font-medium">{alert.message}</p>
418
418
  <p className="text-xs text-text-secondary">
419
- {formatDate(alert.timestamp, 'relative')}
419
+ {alert.timestamp
420
+ ? formatDate(alert.timestamp, 'relative')
421
+ : t('movethewheels.tracking.alerts.timeUnknown', 'Time not recorded')}
420
422
  {alert.resolvedAt &&
421
423
  ` - ${t('movethewheels.tracking.alerts.resolved', 'Resolved')}`}
422
424
  </p>
@@ -456,18 +458,31 @@ export default function TrackingPage() {
456
458
  </p>
457
459
  </div>
458
460
  )}
459
- {tracking && (
461
+ {tracking?.currentLocation ? (
460
462
  <>
461
463
  <p className="text-sm">
462
464
  {tracking.currentLocation.address ||
463
465
  t('movethewheels.tracking.location.updating', 'Location updating...')}
464
466
  </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>
467
+ {tracking.currentLocation.timestamp ? (
468
+ <p className="text-xs text-text-secondary mt-1">
469
+ {t('movethewheels.tracking.location.updated', 'Updated')}{' '}
470
+ {formatDate(tracking.currentLocation.timestamp, 'relative')}
471
+ </p>
472
+ ) : (
473
+ <p className="text-xs text-text-secondary mt-1">
474
+ {t(
475
+ 'movethewheels.tracking.location.timeUnknown',
476
+ 'Update time not recorded'
477
+ )}
478
+ </p>
479
+ )}
469
480
  </>
470
- )}
481
+ ) : tracking ? (
482
+ <p className="text-sm text-text-secondary">
483
+ {t('movethewheels.tracking.location.unknown', 'Location not recorded')}
484
+ </p>
485
+ ) : null}
471
486
  </CardContent>
472
487
  </Card>
473
488