@burdenoff/microfe-movethewheels 2026.925.3 → 2026.929.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -18,7 +18,6 @@ import {
18
18
  ArrowUp,
19
19
  ArrowDown,
20
20
  Fuel,
21
- Search,
22
21
  } from 'lucide-react';
23
22
  import { PageLayout, LoadingState, ErrorState } from '../components/PageLayout';
24
23
  import { StatusBadge } from '../components/StatusBadge';
@@ -57,7 +56,6 @@ import { useAssignmentSuggestions } from '../hooks/useFleetIntelligence';
57
56
  import { AssignmentSuggestions } from '../components/AssignmentSuggestions';
58
57
  import { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';
59
58
  import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
60
- import { useDebouncedValue } from '../hooks/useDebouncedValue';
61
59
  import { formatDate, formatAddress, formatCurrency } from '../utils/formatters';
62
60
  import { joinPath } from '../utils/navigation';
63
61
  import { ROUTE_STATUS_COLORS } from '../constants';
@@ -76,8 +74,8 @@ function NewRouteForm({ editId }: { editId?: string }) {
76
74
  const { basePath, navigate } = useMoveTheWheels();
77
75
  const { createRoute, updateRoute } = useRoutes();
78
76
  const { route } = useRoute(editId);
79
- const [fleetSearch, setFleetSearch] = useState('');
80
- const debouncedFleetSearch = useDebouncedValue(fleetSearch);
77
+ const [vehicleSearch, setVehicleSearch] = useState('');
78
+ const [driverSearch, setDriverSearch] = useState('');
81
79
  const isEditing = !!editId;
82
80
  const [form, setForm] = useState<{
83
81
  name: string;
@@ -92,14 +90,24 @@ function NewRouteForm({ editId }: { editId?: string }) {
92
90
  driverId: '',
93
91
  stops: [{ address: '', type: 'delivery' }],
94
92
  });
95
- const { vehicles, isLoading: vehiclesLoading } = useVehicles({
93
+ const {
94
+ vehicles,
95
+ isLoading: vehiclesLoading,
96
+ hasMore: vehiclesHaveMore,
97
+ totalVehicles,
98
+ } = useVehicles({
96
99
  limit: 20,
97
- search: debouncedFleetSearch || undefined,
100
+ search: vehicleSearch || undefined,
98
101
  driverId: form.driverId || undefined,
99
102
  });
100
- const { drivers, isLoading: driversLoading } = useDrivers({
103
+ const {
104
+ drivers,
105
+ isLoading: driversLoading,
106
+ hasMore: driversHaveMore,
107
+ totalDrivers,
108
+ } = useDrivers({
101
109
  limit: 20,
102
- search: debouncedFleetSearch || undefined,
110
+ search: driverSearch || undefined,
103
111
  vehicleId: form.vehicleId || undefined,
104
112
  });
105
113
  // The selected vehicle/driver may fall outside the current search page (e.g. when
@@ -286,22 +294,6 @@ function NewRouteForm({ editId }: { editId?: string }) {
286
294
  </Select>
287
295
  </div>
288
296
  </div>
289
- <div className="space-y-2">
290
- <Label htmlFor="fleet-search">Search vehicles &amp; drivers</Label>
291
- <div className="relative">
292
- <Search className="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary" />
293
- <Input
294
- id="fleet-search"
295
- value={fleetSearch}
296
- onChange={(e) => setFleetSearch(e.target.value)}
297
- placeholder="Vehicle number, driver name or employee ID"
298
- className="pl-9"
299
- />
300
- </div>
301
- <p className="text-xs text-text-secondary">
302
- Narrows both pickers below as you type — up to 20 matches at a time.
303
- </p>
304
- </div>
305
297
  <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
306
298
  <div className="space-y-2">
307
299
  <Label htmlFor="vehicle">Vehicle (optional)</Label>
@@ -320,10 +312,13 @@ function NewRouteForm({ editId }: { editId?: string }) {
320
312
  )}
321
313
  selectedOption={selectedVehicle}
322
314
  selectedLoading={selectedVehicleLoading}
315
+ onSearchChange={setVehicleSearch}
316
+ searchPlaceholder="Search vehicle number or type…"
317
+ hasMore={vehiclesHaveMore}
318
+ totalCount={totalVehicles}
323
319
  required
324
320
  placeholder="Assign later"
325
321
  emptyText="No available vehicles."
326
- // Search is driven by the shared "Search fleet" box above.
327
322
  />
328
323
  </div>
329
324
  <div className="space-y-2">
@@ -343,6 +338,10 @@ function NewRouteForm({ editId }: { editId?: string }) {
343
338
  )}
344
339
  selectedOption={selectedDriver}
345
340
  selectedLoading={selectedDriverLoading}
341
+ onSearchChange={setDriverSearch}
342
+ searchPlaceholder="Search driver name or employee ID…"
343
+ hasMore={driversHaveMore}
344
+ totalCount={totalDrivers}
346
345
  required
347
346
  placeholder="Assign later"
348
347
  emptyText="No available drivers."
@@ -47,7 +47,7 @@ import {
47
47
  ResponsiveDialogDescription,
48
48
  } from '@burdenoff/fe-libs/ui';
49
49
  import { useVehicle, useVehicleDrivers, useFleet, toApiVehicleStatus } from '../hooks/useFleet';
50
- import type { VehicleStatus } from '../types';
50
+ import type { VehicleFuelType, VehicleStatus } from '../types';
51
51
  import { VEHICLE_STATUS_LABELS } from '../constants';
52
52
  import { useRecordVehicleService } from '../hooks/useFleetIntelligence';
53
53
  import { VehicleServiceCard } from '../components/VehicleServiceCard';
@@ -74,6 +74,14 @@ const VEHICLE_STATUS_OPTIONS: Array<{ value: VehicleStatus; label: string }> = (
74
74
  Object.entries(VEHICLE_STATUS_LABELS) as Array<[VehicleStatus, string]>
75
75
  ).map(([value, label]) => ({ value, label }));
76
76
 
77
+ const FUEL_TYPE_OPTIONS: Array<{ value: VehicleFuelType; label: string }> = [
78
+ { value: 'DIESEL', label: 'Diesel' },
79
+ { value: 'PETROL', label: 'Petrol' },
80
+ { value: 'CNG', label: 'CNG' },
81
+ { value: 'ELECTRIC', label: 'Electric' },
82
+ { value: 'HYBRID', label: 'Hybrid' },
83
+ ];
84
+
77
85
  const OWNERSHIP_TYPE_OPTIONS = [
78
86
  { value: 'owned', label: 'Owned' },
79
87
  { value: 'leased', label: 'Leased' },
@@ -94,7 +102,7 @@ function NewVehicleForm({ editId }: { editId?: string }) {
94
102
  ownershipType: '',
95
103
  year: '',
96
104
  status: 'available' as VehicleStatus,
97
- fuelType: '',
105
+ fuelType: '' as VehicleFuelType | '',
98
106
  fuelEfficiencyLPer100Km: '',
99
107
  capacityWeight: '',
100
108
  capacityVolume: '',
@@ -342,12 +350,26 @@ function NewVehicleForm({ editId }: { editId?: string }) {
342
350
  <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
343
351
  <div className="space-y-2">
344
352
  <Label htmlFor="fuelType">Fuel Type</Label>
345
- <Input
346
- id="fuelType"
347
- value={form.fuelType}
348
- onChange={(e) => setForm((prev) => ({ ...prev, fuelType: e.target.value }))}
349
- placeholder="Diesel"
350
- />
353
+ <Select
354
+ value={form.fuelType || undefined}
355
+ onValueChange={(value) => {
356
+ const fuelType = FUEL_TYPE_OPTIONS.find(
357
+ (option) => option.value === value
358
+ )?.value;
359
+ setForm((prev) => ({ ...prev, fuelType: fuelType ?? '' }));
360
+ }}
361
+ >
362
+ <SelectTrigger id="fuelType">
363
+ <SelectValue placeholder="Select fuel type" />
364
+ </SelectTrigger>
365
+ <SelectContent>
366
+ {FUEL_TYPE_OPTIONS.map((option) => (
367
+ <SelectItem key={option.value} value={option.value}>
368
+ {option.label}
369
+ </SelectItem>
370
+ ))}
371
+ </SelectContent>
372
+ </Select>
351
373
  </div>
352
374
  <div className="space-y-2">
353
375
  <Label htmlFor="fuelEfficiency">Fuel Efficiency (L/100km)</Label>
@@ -590,15 +612,13 @@ export default function VehicleDetailsPage() {
590
612
  <div>
591
613
  <p className="text-sm text-text-secondary">Weight</p>
592
614
  <p className="text-2xl font-bold">
593
- {vehicle.capacity.weight}
594
- <span className="text-sm font-normal"> kg</span>
615
+ {vehicle.capacity.weight !== undefined ? `${vehicle.capacity.weight} kg` : '—'}
595
616
  </p>
596
617
  </div>
597
618
  <div>
598
619
  <p className="text-sm text-text-secondary">Volume</p>
599
620
  <p className="text-2xl font-bold">
600
- {vehicle.capacity.volume}
601
- <span className="text-sm font-normal"> m³</span>
621
+ {vehicle.capacity.volume !== undefined ? `${vehicle.capacity.volume} m³` : '—'}
602
622
  </p>
603
623
  </div>
604
624
  {vehicle.capacity.pallets && (
@@ -741,7 +761,9 @@ export default function VehicleDetailsPage() {
741
761
  {vehicle.currentLocation.address || 'Location updating...'}
742
762
  </p>
743
763
  <p className="text-xs text-text-secondary mt-1">
744
- Updated {formatDate(vehicle.currentLocation.timestamp || new Date(), 'relative')}
764
+ {vehicle.currentLocation.timestamp
765
+ ? `Updated ${formatDate(vehicle.currentLocation.timestamp, 'relative')}`
766
+ : 'Update time not recorded'}
745
767
  </p>
746
768
  </CardContent>
747
769
  </Card>
@@ -112,8 +112,10 @@ export default function VehiclesPage() {
112
112
  header: 'Capacity',
113
113
  cell: (row) => (
114
114
  <div className="text-sm">
115
- <p>{row.capacity.weight} kg</p>
116
- <p className="text-text-secondary">{row.capacity.volume} m³</p>
115
+ <p>{row.capacity.weight !== undefined ? `${row.capacity.weight} kg` : '—'}</p>
116
+ <p className="text-text-secondary">
117
+ {row.capacity.volume !== undefined ? `${row.capacity.volume} m³` : '—'}
118
+ </p>
117
119
  </div>
118
120
  ),
119
121
  },
@@ -52,7 +52,7 @@ const WAREHOUSE_STATUS_OPTIONS = [
52
52
  { value: 'INACTIVE', label: 'Inactive' },
53
53
  ];
54
54
 
55
- function NewWarehouseForm({ editId }: { editId?: string }) {
55
+ function NewWarehouseForm({ editId, onSaved }: { editId?: string; onSaved?: () => Promise<void> }) {
56
56
  const { basePath, navigate } = useMoveTheWheels();
57
57
  const { createWarehouse, updateWarehouse, deleteWarehouse } = useInventory();
58
58
  const { warehouse } = useWarehouse(editId);
@@ -143,6 +143,7 @@ function NewWarehouseForm({ editId }: { editId?: string }) {
143
143
  };
144
144
  if (editId) {
145
145
  await updateWarehouse(editId, warehouseInput);
146
+ await onSaved?.();
146
147
  navigate?.(joinPath(basePath, `inventory/warehouses/${editId}`));
147
148
  } else {
148
149
  const created = await createWarehouse(warehouseInput);
@@ -426,13 +427,13 @@ export default function WarehouseDetailsPage() {
426
427
  const { warehouseId } = useParams<{ warehouseId: string }>();
427
428
  const { basePath, navigate } = useMoveTheWheels();
428
429
  const isEditRoute = useLocation().pathname.endsWith('/edit');
429
- const { warehouse, isLoading, error, refetch } = useWarehouse(warehouseId);
430
+ const { warehouse, isLoading, error, refetch, reload } = useWarehouse(warehouseId);
430
431
 
431
432
  const goBack = () => navigate?.(joinPath(basePath, 'inventory/warehouses'));
432
433
  const goTo = (path: string) => navigate?.(joinPath(basePath, path));
433
434
 
434
435
  if (!warehouseId) return <NewWarehouseForm />;
435
- if (isEditRoute) return <NewWarehouseForm editId={warehouseId} />;
436
+ if (isEditRoute) return <NewWarehouseForm editId={warehouseId} onSaved={reload} />;
436
437
  if (isLoading) return <LoadingState message="Loading warehouse details..." />;
437
438
  if (error || !warehouse) return <ErrorState title="Warehouse not found" onRetry={refetch} />;
438
439
 
@@ -90,7 +90,10 @@ export default function WarehousesPage() {
90
90
  id: 'utilization',
91
91
  header: 'Utilization',
92
92
  cell: (row) => {
93
- const pct = (row.capacity.usedSpace / row.capacity.totalSpace) * 100;
93
+ const pct =
94
+ row.capacity.totalSpace > 0
95
+ ? (row.capacity.usedSpace / row.capacity.totalSpace) * 100
96
+ : 0;
94
97
  return (
95
98
  <div className="flex items-center gap-2">
96
99
  <Progress value={pct} className="w-16 h-2" />
@@ -308,7 +308,16 @@ export interface ProofOfDelivery {
308
308
  }
309
309
 
310
310
  // Vehicle type
311
- export type VehicleType = 'van' | 'truck' | 'motorcycle' | 'bicycle' | 'car';
311
+ export type VehicleType =
312
+ | 'van'
313
+ | 'truck'
314
+ | 'motorcycle'
315
+ | 'bicycle'
316
+ | 'flatbed'
317
+ | 'refrigerated'
318
+ | 'semi_trailer'
319
+ | 'drone';
320
+ export type VehicleFuelType = 'DIESEL' | 'PETROL' | 'CNG' | 'ELECTRIC' | 'HYBRID';
312
321
  export type VehicleStatus =
313
322
  'available' | 'assigned' | 'maintenance' | 'out_of_service' | 'decommissioned';
314
323
 
@@ -331,8 +340,8 @@ export interface Vehicle {
331
340
  ownershipType?: string;
332
341
  year?: number;
333
342
  capacity: {
334
- weight: number;
335
- volume: number;
343
+ weight?: number;
344
+ volume?: number;
336
345
  pallets?: number;
337
346
  };
338
347
  status: VehicleStatus;
@@ -342,7 +351,7 @@ export interface Vehicle {
342
351
  nextMaintenance?: Date;
343
352
  /** Odometer reading (km) at which the next service is due. */
344
353
  nextMaintenanceMileage?: number;
345
- fuelType?: string;
354
+ fuelType?: VehicleFuelType;
346
355
  /** Fuel consumption, litres per 100km. */
347
356
  fuelEfficiencyLPer100Km?: number;
348
357
  fuelLevel?: number;
@@ -371,7 +380,7 @@ export interface Driver {
371
380
  employeeId: string;
372
381
  licenseNumber: string;
373
382
  licenseType: string;
374
- licenseExpiry: Date;
383
+ licenseExpiry?: Date;
375
384
  vehicleIds: string[];
376
385
  assignedVehicle?: string;
377
386
  status: DriverStatus;