@burdenoff/microfe-movethewheels 2026.920.3 → 2026.921.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 (77) hide show
  1. package/dist/components/CarbonFootprintPanel.js +1 -1
  2. package/dist/components/CustomerPicker.js +76 -65
  3. package/dist/components/DataTable.js +140 -139
  4. package/dist/components/DocumentsCard.js +14 -8
  5. package/dist/generated/wspace-types.d.ts +1043 -5
  6. package/dist/hooks/useAnalytics.js +21 -1
  7. package/dist/hooks/useCustomers.js +94 -78
  8. package/dist/hooks/useDebouncedValue.js +11 -0
  9. package/dist/hooks/useEntityDocuments.js +28 -18
  10. package/dist/hooks/useEpod.js +24 -17
  11. package/dist/hooks/useFleet.js +146 -125
  12. package/dist/hooks/useInventory.js +255 -186
  13. package/dist/hooks/useMultiModalShipments.js +35 -27
  14. package/dist/hooks/useOrders.js +73 -73
  15. package/dist/hooks/useRoutes.js +97 -75
  16. package/dist/hooks/useTracking.js +100 -88
  17. package/dist/movethewheels/components/CustomerPicker.d.ts +3 -1
  18. package/dist/movethewheels/components/DataTable.d.ts +5 -1
  19. package/dist/movethewheels/hooks/useCustomers.d.ts +0 -1
  20. package/dist/movethewheels/hooks/useDebouncedValue.d.ts +1 -0
  21. package/dist/movethewheels/hooks/useEntityDocuments.d.ts +2 -0
  22. package/dist/movethewheels/hooks/useEpod.d.ts +6 -1
  23. package/dist/movethewheels/hooks/useFleet.d.ts +49 -5
  24. package/dist/movethewheels/hooks/useInventory.d.ts +24 -17
  25. package/dist/movethewheels/hooks/useMultiModalShipments.d.ts +6 -1
  26. package/dist/movethewheels/hooks/useOrders.d.ts +33 -5
  27. package/dist/movethewheels/hooks/useRoutes.d.ts +2 -1
  28. package/dist/movethewheels/hooks/useTracking.d.ts +12 -11
  29. package/dist/movethewheels/types/index.d.ts +7 -5
  30. package/dist/pages/CreateOrderPage.js +207 -201
  31. package/dist/pages/CustomersPage.js +61 -54
  32. package/dist/pages/DeliveryCapturePage.js +131 -109
  33. package/dist/pages/DriversPage.js +100 -92
  34. package/dist/pages/InventoryPage.js +239 -236
  35. package/dist/pages/MultiModalShipmentPage.js +163 -130
  36. package/dist/pages/OrderDetailsPage.js +224 -208
  37. package/dist/pages/OrdersListPage.js +66 -60
  38. package/dist/pages/ProductDetailsPage.js +241 -200
  39. package/dist/pages/ProductsPage.js +59 -52
  40. package/dist/pages/RouteDetailsPage.js +343 -321
  41. package/dist/pages/RoutesPage.js +55 -48
  42. package/dist/pages/VehiclesPage.js +63 -56
  43. package/dist/pages/WarehousesPage.js +60 -53
  44. package/package.json +1 -1
  45. package/src/generated/wspace-types.ts +1188 -5
  46. package/src/movethewheels/components/CarbonFootprintPanel.tsx +0 -1
  47. package/src/movethewheels/components/CustomerPicker.tsx +14 -0
  48. package/src/movethewheels/components/DataTable.tsx +34 -18
  49. package/src/movethewheels/components/DocumentsCard.tsx +6 -1
  50. package/src/movethewheels/components/maps/leaflet-setup.ts +1 -8
  51. package/src/movethewheels/hooks/useAnalytics.ts +27 -0
  52. package/src/movethewheels/hooks/useCustomers.ts +64 -24
  53. package/src/movethewheels/hooks/useDebouncedValue.ts +12 -0
  54. package/src/movethewheels/hooks/useEntityDocuments.ts +43 -25
  55. package/src/movethewheels/hooks/useEpod.ts +14 -7
  56. package/src/movethewheels/hooks/useFleet.ts +58 -37
  57. package/src/movethewheels/hooks/useFleetIntelligence.ts +1 -4
  58. package/src/movethewheels/hooks/useInventory.ts +211 -90
  59. package/src/movethewheels/hooks/useMultiModalShipments.ts +15 -11
  60. package/src/movethewheels/hooks/useOrders.ts +13 -14
  61. package/src/movethewheels/hooks/useRoutes.ts +80 -23
  62. package/src/movethewheels/hooks/useTracking.ts +130 -98
  63. package/src/movethewheels/pages/CreateOrderPage.tsx +9 -1
  64. package/src/movethewheels/pages/CustomersPage.tsx +11 -1
  65. package/src/movethewheels/pages/DeliveryCapturePage.tsx +31 -1
  66. package/src/movethewheels/pages/DriversPage.tsx +24 -12
  67. package/src/movethewheels/pages/InventoryPage.tsx +57 -32
  68. package/src/movethewheels/pages/MultiModalShipmentPage.tsx +52 -3
  69. package/src/movethewheels/pages/OrderDetailsPage.tsx +17 -1
  70. package/src/movethewheels/pages/OrdersListPage.tsx +20 -3
  71. package/src/movethewheels/pages/ProductDetailsPage.tsx +52 -3
  72. package/src/movethewheels/pages/ProductsPage.tsx +11 -1
  73. package/src/movethewheels/pages/RouteDetailsPage.tsx +35 -5
  74. package/src/movethewheels/pages/RoutesPage.tsx +11 -1
  75. package/src/movethewheels/pages/VehiclesPage.tsx +19 -2
  76. package/src/movethewheels/pages/WarehousesPage.tsx +11 -1
  77. package/src/movethewheels/types/index.ts +7 -5
@@ -40,17 +40,26 @@ import { EmphasisPanel, NextSteps, PagePurpose } from '@burdenoff/fe-libs/ui';
40
40
  import { RouteMap, type RouteStop as MapRouteStop } from '../components/maps';
41
41
  import { firstRealLatLng } from '../utils/mapMarkers';
42
42
  import { useRoute, useRoutes } from '../hooks/useRoutes';
43
- import { useFleet } from '../hooks/useFleet';
43
+ import { useFleet, useVehicle, useDriver } from '../hooks/useFleet';
44
44
  import { useAssignmentSuggestions } from '../hooks/useFleetIntelligence';
45
45
  import { AssignmentSuggestions } from '../components/AssignmentSuggestions';
46
46
  import { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';
47
47
  import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
48
+ import { useDebouncedValue } from '../hooks/useDebouncedValue';
48
49
  import { formatDate, formatAddress, formatCurrency } from '../utils/formatters';
49
50
  import { joinPath } from '../utils/navigation';
50
51
  import { ROUTE_STATUS_COLORS } from '../constants';
51
52
  import { FormError } from '../components/FormError';
52
53
  import { Notice, useNotice } from '../components/Notice';
53
- import type { Route as RouteType, RouteStop } from '../types';
54
+ import type { Route as RouteType, RouteStop, Vehicle, Driver } from '../types';
55
+
56
+ /** Keeps a currently selected fleet item visible even when it falls outside the current search page. */
57
+ function mergeSelected<T extends { id: string }>(items: T[], selected: T | null | undefined): T[] {
58
+ if (!selected || items.some((item) => item.id === selected.id)) {
59
+ return items;
60
+ }
61
+ return [selected, ...items];
62
+ }
54
63
 
55
64
  const ROUTE_STATUS_OPTIONS: { value: RouteType['status']; label: string }[] = [
56
65
  { value: 'planned', label: 'Planned' },
@@ -63,7 +72,12 @@ function NewRouteForm({ editId }: { editId?: string }) {
63
72
  const { basePath, navigate } = useMoveTheWheels();
64
73
  const { createRoute, updateRoute } = useRoutes();
65
74
  const { route } = useRoute(editId);
66
- const { vehicles, drivers } = useFleet();
75
+ const [fleetSearch, setFleetSearch] = useState('');
76
+ const debouncedFleetSearch = useDebouncedValue(fleetSearch);
77
+ const { vehicles, drivers } = useFleet({
78
+ limit: 20,
79
+ filters: { search: debouncedFleetSearch || undefined },
80
+ });
67
81
  const isEditing = !!editId;
68
82
  const [form, setForm] = useState<{
69
83
  name: string;
@@ -78,6 +92,13 @@ function NewRouteForm({ editId }: { editId?: string }) {
78
92
  driverId: '',
79
93
  stops: [{ address: '', type: 'delivery' }],
80
94
  });
95
+ // The selected vehicle/driver may fall outside the current search page (e.g. when
96
+ // editing a route or after the assignee no longer matches the search term); fetch
97
+ // them directly so the Select can still render the current selection.
98
+ const { vehicle: selectedVehicle } = useVehicle(form.vehicleId || undefined);
99
+ const { driver: selectedDriver } = useDriver(form.driverId || undefined);
100
+ const vehicleOptions: Vehicle[] = mergeSelected(vehicles, selectedVehicle);
101
+ const driverOptions: Driver[] = mergeSelected(drivers, selectedDriver);
81
102
  const [submitting, setSubmitting] = useState(false);
82
103
  const [error, setError] = useState<string | null>(null);
83
104
 
@@ -253,6 +274,15 @@ function NewRouteForm({ editId }: { editId?: string }) {
253
274
  </Select>
254
275
  </div>
255
276
  </div>
277
+ <div className="space-y-2">
278
+ <Label htmlFor="fleet-search">Search fleet</Label>
279
+ <Input
280
+ id="fleet-search"
281
+ value={fleetSearch}
282
+ onChange={(e) => setFleetSearch(e.target.value)}
283
+ placeholder="Vehicle number, driver name or ID"
284
+ />
285
+ </div>
256
286
  <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
257
287
  <div className="space-y-2">
258
288
  <Label htmlFor="vehicle">Vehicle (optional)</Label>
@@ -264,7 +294,7 @@ function NewRouteForm({ editId }: { editId?: string }) {
264
294
  <SelectValue placeholder="Assign later" />
265
295
  </SelectTrigger>
266
296
  <SelectContent>
267
- {vehicles.map((vehicle) => (
297
+ {vehicleOptions.map((vehicle) => (
268
298
  <SelectItem key={vehicle.id} value={vehicle.id}>
269
299
  {vehicle.vehicleNumber} ({vehicle.type})
270
300
  </SelectItem>
@@ -282,7 +312,7 @@ function NewRouteForm({ editId }: { editId?: string }) {
282
312
  <SelectValue placeholder="Assign later" />
283
313
  </SelectTrigger>
284
314
  <SelectContent>
285
- {drivers.map((driver) => (
315
+ {driverOptions.map((driver) => (
286
316
  <SelectItem key={driver.id} value={driver.id}>
287
317
  {driver.fullName} ({driver.employeeId})
288
318
  </SelectItem>
@@ -10,6 +10,7 @@ import { StatusBadge } from '../components/StatusBadge';
10
10
  import { Badge, Button } from '../components/ui';
11
11
  import { CTAOverflowMenu, EmphasisPanel, PagePurpose } from '@burdenoff/fe-libs/ui';
12
12
  import { useRoutes, useRouteStats } from '../hooks/useRoutes';
13
+ import { useDebouncedValue } from '../hooks/useDebouncedValue';
13
14
  import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
14
15
  import { formatDate } from '../utils/formatters';
15
16
  import { joinPath } from '../utils/navigation';
@@ -20,9 +21,12 @@ export default function RoutesPage() {
20
21
  const { basePath, navigate } = useMoveTheWheels();
21
22
  const [page, setPage] = useState(0);
22
23
  const [pageSize, setPageSize] = useState(25);
23
- const { routes, hasMore, isLoading, error, refetch } = useRoutes({
24
+ const [search, setSearch] = useState('');
25
+ const debouncedSearch = useDebouncedValue(search);
26
+ const { routes, totalRoutes, hasMore, isLoading, error, refetch } = useRoutes({
24
27
  limit: pageSize,
25
28
  offset: page * pageSize,
29
+ search: debouncedSearch || undefined,
26
30
  });
27
31
  // Bounded aggregate for the tiles + subtitle — not the truncated page.
28
32
  const { stats } = useRouteStats();
@@ -131,6 +135,11 @@ export default function RoutesPage() {
131
135
  searchable
132
136
  searchPlaceholder="Search routes..."
133
137
  searchFields={['name'] as (keyof Route)[]}
138
+ searchQuery={search}
139
+ onSearchQueryChange={(query) => {
140
+ setSearch(query);
141
+ setPage(0);
142
+ }}
134
143
  onRowClick={(row) => goTo(`routes/${row.id}`)}
135
144
  getRowId={(row) => row.id}
136
145
  pageIndex={page}
@@ -140,6 +149,7 @@ export default function RoutesPage() {
140
149
  setPageSize(size);
141
150
  setPage(0);
142
151
  }}
152
+ totalItems={totalRoutes}
143
153
  hasNextPage={hasMore}
144
154
  emptyIllustration="empty-data"
145
155
  emptyTitle="No routes yet"
@@ -19,6 +19,7 @@ import { StatusBadge, VehicleTypeBadge } from '../components/StatusBadge';
19
19
  import { Progress } from '../components/ui';
20
20
  import { CTAOverflowMenu, PagePurpose } from '@burdenoff/fe-libs/ui';
21
21
  import { useVehicles, useFleetStats } from '../hooks/useFleet';
22
+ import { useDebouncedValue } from '../hooks/useDebouncedValue';
22
23
  import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
23
24
  import { joinPath } from '../utils/navigation';
24
25
  import type { Vehicle } from '../types';
@@ -27,9 +28,19 @@ export default function VehiclesPage() {
27
28
  const { basePath, navigate } = useMoveTheWheels();
28
29
  const [page, setPage] = useState(0);
29
30
  const [pageSize, setPageSize] = useState(25);
30
- const { vehicles, isLoading, error, refetch } = useVehicles({
31
+ const [search, setSearch] = useState('');
32
+ const debouncedSearch = useDebouncedValue(search);
33
+ const {
34
+ vehicles,
35
+ totalVehicles: filteredVehicleCount,
36
+ hasMore,
37
+ isLoading,
38
+ error,
39
+ refetch,
40
+ } = useVehicles({
31
41
  limit: pageSize,
32
42
  offset: page * pageSize,
43
+ search: debouncedSearch || undefined,
33
44
  });
34
45
  // Fleet totals + tiles from the live aggregate, not the current page.
35
46
  const { stats } = useFleetStats();
@@ -176,6 +187,11 @@ export default function VehiclesPage() {
176
187
  searchable
177
188
  searchPlaceholder="Search vehicles..."
178
189
  searchFields={['vehicleNumber', 'make', 'model'] as (keyof Vehicle)[]}
190
+ searchQuery={search}
191
+ onSearchQueryChange={(query) => {
192
+ setSearch(query);
193
+ setPage(0);
194
+ }}
179
195
  onRowClick={(row) => goTo(`fleet/vehicles/${row.id}`)}
180
196
  getRowId={(row) => row.id}
181
197
  pageIndex={page}
@@ -185,7 +201,8 @@ export default function VehiclesPage() {
185
201
  setPageSize(size);
186
202
  setPage(0);
187
203
  }}
188
- totalItems={totalVehicles}
204
+ totalItems={filteredVehicleCount}
205
+ hasNextPage={hasMore}
189
206
  emptyIllustration="empty-data"
190
207
  emptyTitle="No vehicles yet"
191
208
  emptyDescription="Add vehicles to your fleet to start assigning them to deliveries."
@@ -10,6 +10,7 @@ import { DataTable, type Column } from '../components/DataTable';
10
10
  import { Badge, Progress } from '../components/ui';
11
11
  import { CTAOverflowMenu, PagePurpose } from '@burdenoff/fe-libs/ui';
12
12
  import { useWarehouses, useWarehouseStats } from '../hooks/useInventory';
13
+ import { useDebouncedValue } from '../hooks/useDebouncedValue';
13
14
  import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
14
15
  import { joinPath } from '../utils/navigation';
15
16
  import type { Warehouse as WarehouseType } from '../types';
@@ -18,9 +19,12 @@ export default function WarehousesPage() {
18
19
  const { basePath, navigate } = useMoveTheWheels();
19
20
  const [page, setPage] = useState(0);
20
21
  const [pageSize, setPageSize] = useState(25);
21
- const { warehouses, hasMore, isLoading, error, refetch } = useWarehouses({
22
+ const [search, setSearch] = useState('');
23
+ const debouncedSearch = useDebouncedValue(search);
24
+ const { warehouses, totalWarehouses, hasMore, isLoading, error, refetch } = useWarehouses({
22
25
  limit: pageSize,
23
26
  offset: page * pageSize,
27
+ search: debouncedSearch,
24
28
  });
25
29
  // Bounded aggregate for the tiles + subtitle — not the truncated page.
26
30
  const { stats } = useWarehouseStats();
@@ -157,6 +161,11 @@ export default function WarehousesPage() {
157
161
  searchable
158
162
  searchPlaceholder="Search warehouses..."
159
163
  searchFields={['name', 'code'] as (keyof WarehouseType)[]}
164
+ searchQuery={search}
165
+ onSearchQueryChange={(query) => {
166
+ setSearch(query);
167
+ setPage(0);
168
+ }}
160
169
  onRowClick={(row) => goTo(`inventory/warehouses/${row.id}`)}
161
170
  getRowId={(row) => row.id}
162
171
  pageIndex={page}
@@ -166,6 +175,7 @@ export default function WarehousesPage() {
166
175
  setPageSize(size);
167
176
  setPage(0);
168
177
  }}
178
+ totalItems={totalWarehouses}
169
179
  hasNextPage={hasMore}
170
180
  emptyIllustration="empty-data"
171
181
  emptyTitle="No warehouses yet"
@@ -502,7 +502,10 @@ export interface InventoryMovement {
502
502
  export interface Inventory {
503
503
  id: string;
504
504
  productId: string;
505
+ productName?: string;
506
+ productSku?: string;
505
507
  warehouseId: string;
508
+ warehouseName?: string;
506
509
  zoneId: string;
507
510
  quantity: number;
508
511
  reservedQuantity: number;
@@ -746,11 +749,8 @@ export interface OrderFilter {
746
749
  dateFrom?: Date;
747
750
  dateTo?: Date;
748
751
  priority?: string;
749
- /**
750
- * Client-side quick filter for orders that still need a driver assigned
751
- * (driverId is null). The backend DeliveryJobFilterInput has no null-driver
752
- * predicate, so this is applied to the fetched page after mapping.
753
- */
752
+ search?: string;
753
+ /** Server-side predicate for orders that still need a driver assigned. */
754
754
  unassigned?: boolean;
755
755
  }
756
756
 
@@ -758,11 +758,13 @@ export interface FleetFilter {
758
758
  vehicleType?: VehicleType;
759
759
  status?: VehicleStatus;
760
760
  driverId?: string;
761
+ search?: string;
761
762
  }
762
763
 
763
764
  export interface InventoryFilter {
764
765
  warehouseId?: string;
765
766
  productId?: string;
767
+ search?: string;
766
768
  lowStock?: boolean;
767
769
  }
768
770