@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.
- package/dist/components/CarbonFootprintPanel.js +1 -1
- package/dist/components/CustomerPicker.js +76 -65
- package/dist/components/DataTable.js +140 -139
- package/dist/components/DocumentsCard.js +14 -8
- package/dist/generated/wspace-types.d.ts +1043 -5
- package/dist/hooks/useAnalytics.js +21 -1
- package/dist/hooks/useCustomers.js +94 -78
- package/dist/hooks/useDebouncedValue.js +11 -0
- package/dist/hooks/useEntityDocuments.js +28 -18
- package/dist/hooks/useEpod.js +24 -17
- package/dist/hooks/useFleet.js +146 -125
- package/dist/hooks/useInventory.js +255 -186
- package/dist/hooks/useMultiModalShipments.js +35 -27
- package/dist/hooks/useOrders.js +73 -73
- package/dist/hooks/useRoutes.js +97 -75
- package/dist/hooks/useTracking.js +100 -88
- package/dist/movethewheels/components/CustomerPicker.d.ts +3 -1
- package/dist/movethewheels/components/DataTable.d.ts +5 -1
- package/dist/movethewheels/hooks/useCustomers.d.ts +0 -1
- package/dist/movethewheels/hooks/useDebouncedValue.d.ts +1 -0
- package/dist/movethewheels/hooks/useEntityDocuments.d.ts +2 -0
- package/dist/movethewheels/hooks/useEpod.d.ts +6 -1
- package/dist/movethewheels/hooks/useFleet.d.ts +49 -5
- package/dist/movethewheels/hooks/useInventory.d.ts +24 -17
- package/dist/movethewheels/hooks/useMultiModalShipments.d.ts +6 -1
- package/dist/movethewheels/hooks/useOrders.d.ts +33 -5
- package/dist/movethewheels/hooks/useRoutes.d.ts +2 -1
- package/dist/movethewheels/hooks/useTracking.d.ts +12 -11
- package/dist/movethewheels/types/index.d.ts +7 -5
- package/dist/pages/CreateOrderPage.js +207 -201
- package/dist/pages/CustomersPage.js +61 -54
- package/dist/pages/DeliveryCapturePage.js +131 -109
- package/dist/pages/DriversPage.js +100 -92
- package/dist/pages/InventoryPage.js +239 -236
- package/dist/pages/MultiModalShipmentPage.js +163 -130
- package/dist/pages/OrderDetailsPage.js +224 -208
- package/dist/pages/OrdersListPage.js +66 -60
- package/dist/pages/ProductDetailsPage.js +241 -200
- package/dist/pages/ProductsPage.js +59 -52
- package/dist/pages/RouteDetailsPage.js +343 -321
- package/dist/pages/RoutesPage.js +55 -48
- package/dist/pages/VehiclesPage.js +63 -56
- package/dist/pages/WarehousesPage.js +60 -53
- package/package.json +1 -1
- package/src/generated/wspace-types.ts +1188 -5
- package/src/movethewheels/components/CarbonFootprintPanel.tsx +0 -1
- package/src/movethewheels/components/CustomerPicker.tsx +14 -0
- package/src/movethewheels/components/DataTable.tsx +34 -18
- package/src/movethewheels/components/DocumentsCard.tsx +6 -1
- package/src/movethewheels/components/maps/leaflet-setup.ts +1 -8
- package/src/movethewheels/hooks/useAnalytics.ts +27 -0
- package/src/movethewheels/hooks/useCustomers.ts +64 -24
- package/src/movethewheels/hooks/useDebouncedValue.ts +12 -0
- package/src/movethewheels/hooks/useEntityDocuments.ts +43 -25
- package/src/movethewheels/hooks/useEpod.ts +14 -7
- package/src/movethewheels/hooks/useFleet.ts +58 -37
- package/src/movethewheels/hooks/useFleetIntelligence.ts +1 -4
- package/src/movethewheels/hooks/useInventory.ts +211 -90
- package/src/movethewheels/hooks/useMultiModalShipments.ts +15 -11
- package/src/movethewheels/hooks/useOrders.ts +13 -14
- package/src/movethewheels/hooks/useRoutes.ts +80 -23
- package/src/movethewheels/hooks/useTracking.ts +130 -98
- package/src/movethewheels/pages/CreateOrderPage.tsx +9 -1
- package/src/movethewheels/pages/CustomersPage.tsx +11 -1
- package/src/movethewheels/pages/DeliveryCapturePage.tsx +31 -1
- package/src/movethewheels/pages/DriversPage.tsx +24 -12
- package/src/movethewheels/pages/InventoryPage.tsx +57 -32
- package/src/movethewheels/pages/MultiModalShipmentPage.tsx +52 -3
- package/src/movethewheels/pages/OrderDetailsPage.tsx +17 -1
- package/src/movethewheels/pages/OrdersListPage.tsx +20 -3
- package/src/movethewheels/pages/ProductDetailsPage.tsx +52 -3
- package/src/movethewheels/pages/ProductsPage.tsx +11 -1
- package/src/movethewheels/pages/RouteDetailsPage.tsx +35 -5
- package/src/movethewheels/pages/RoutesPage.tsx +11 -1
- package/src/movethewheels/pages/VehiclesPage.tsx +19 -2
- package/src/movethewheels/pages/WarehousesPage.tsx +11 -1
- 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
|
|
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
|
-
{
|
|
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
|
-
{
|
|
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
|
|
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
|
|
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={
|
|
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
|
|
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
|
-
|
|
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
|
|