@burdenoff/microfe-movethewheels 2026.920.2 → 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 +271 -0
- 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 +19 -0
- 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/movethewheels/utils/navigation.d.ts +14 -0
- package/dist/pages/CreateOrderPage.js +228 -217
- package/dist/pages/CustomerDetailsPage.js +220 -220
- 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/dist/utils/navigation.js +22 -1
- 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 +321 -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 +92 -31
- package/src/movethewheels/pages/CustomerDetailsPage.tsx +15 -6
- 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
- package/src/movethewheels/utils/navigation.ts +32 -0
|
@@ -12,7 +12,6 @@ interface UseCustomersOptions {
|
|
|
12
12
|
interface UseCustomersResult {
|
|
13
13
|
customers: Customer[];
|
|
14
14
|
totalCustomers: number;
|
|
15
|
-
/** True when the last fetch returned a full page (rawCount === limit). */
|
|
16
15
|
hasMore: boolean;
|
|
17
16
|
isLoading: boolean;
|
|
18
17
|
error: Error | null;
|
|
@@ -55,6 +54,7 @@ const LIST_CUSTOMERS_QUERY = `
|
|
|
55
54
|
updatedAt
|
|
56
55
|
deletedAt
|
|
57
56
|
}
|
|
57
|
+
customersCount(filter: $filter)
|
|
58
58
|
}
|
|
59
59
|
`;
|
|
60
60
|
|
|
@@ -165,6 +165,7 @@ export function useCustomers(options: UseCustomersOptions = {}): UseCustomersRes
|
|
|
165
165
|
useMoveTheWheels();
|
|
166
166
|
|
|
167
167
|
const [customers, setCustomers] = useState<Customer[]>([]);
|
|
168
|
+
const [totalCustomers, setTotalCustomers] = useState(0);
|
|
168
169
|
const [hasMore, setHasMore] = useState(false);
|
|
169
170
|
const [isLoading, setIsLoading] = useState(false);
|
|
170
171
|
const [error, setError] = useState<Error | null>(null);
|
|
@@ -187,6 +188,7 @@ export function useCustomers(options: UseCustomersOptions = {}): UseCustomersRes
|
|
|
187
188
|
try {
|
|
188
189
|
const data = await executeGraphQL<{
|
|
189
190
|
customers: ApiCustomer[];
|
|
191
|
+
customersCount: number;
|
|
190
192
|
}>(graphQLOptions, LIST_CUSTOMERS_QUERY, {
|
|
191
193
|
filter: {
|
|
192
194
|
type: filters?.type,
|
|
@@ -196,11 +198,9 @@ export function useCustomers(options: UseCustomersOptions = {}): UseCustomersRes
|
|
|
196
198
|
});
|
|
197
199
|
|
|
198
200
|
const raw = data.customers;
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
mapped = mapped.filter((item) => item.isActive === filters.isActive);
|
|
203
|
-
}
|
|
201
|
+
setTotalCustomers(data.customersCount);
|
|
202
|
+
setHasMore(offset + raw.length < data.customersCount);
|
|
203
|
+
const mapped = raw.map(mapCustomer);
|
|
204
204
|
setCustomers(mapped);
|
|
205
205
|
} catch (fetchError) {
|
|
206
206
|
setError(fetchError instanceof Error ? fetchError : new Error('Failed to load customers'));
|
|
@@ -280,7 +280,7 @@ export function useCustomers(options: UseCustomersOptions = {}): UseCustomersRes
|
|
|
280
280
|
|
|
281
281
|
return {
|
|
282
282
|
customers,
|
|
283
|
-
totalCustomers
|
|
283
|
+
totalCustomers,
|
|
284
284
|
hasMore,
|
|
285
285
|
isLoading,
|
|
286
286
|
error,
|
|
@@ -342,25 +342,65 @@ export function useCustomer(customerId: string | undefined) {
|
|
|
342
342
|
return { customer, isLoading, error, refetch };
|
|
343
343
|
}
|
|
344
344
|
|
|
345
|
+
const CUSTOMER_STATS_QUERY = `
|
|
346
|
+
query CustomerStats {
|
|
347
|
+
customerStats {
|
|
348
|
+
total
|
|
349
|
+
b2b
|
|
350
|
+
b2c
|
|
351
|
+
active
|
|
352
|
+
inactive
|
|
353
|
+
avgRating
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
`;
|
|
357
|
+
|
|
345
358
|
export function useCustomerStats() {
|
|
346
|
-
const {
|
|
347
|
-
|
|
348
|
-
const
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
359
|
+
const { apiGatewayUrl, authToken, workspaceToken, workspaceId, tenantId, currentUser } =
|
|
360
|
+
useMoveTheWheels();
|
|
361
|
+
const graphQLOptions = useMemo(
|
|
362
|
+
() => ({
|
|
363
|
+
apiGatewayUrl,
|
|
364
|
+
authToken,
|
|
365
|
+
workspaceToken,
|
|
366
|
+
workspaceId,
|
|
367
|
+
tenantId,
|
|
368
|
+
actorId: currentUser?.id,
|
|
369
|
+
}),
|
|
370
|
+
[apiGatewayUrl, authToken, workspaceToken, workspaceId, tenantId, currentUser?.id]
|
|
371
|
+
);
|
|
372
|
+
const [stats, setStats] = useState({
|
|
373
|
+
total: 0,
|
|
374
|
+
b2b: 0,
|
|
375
|
+
b2c: 0,
|
|
376
|
+
active: 0,
|
|
377
|
+
inactive: 0,
|
|
378
|
+
avgRating: 0,
|
|
379
|
+
});
|
|
380
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
381
|
+
const [error, setError] = useState<Error | null>(null);
|
|
382
|
+
|
|
383
|
+
useEffect(() => {
|
|
384
|
+
const load = async () => {
|
|
385
|
+
setIsLoading(true);
|
|
386
|
+
setError(null);
|
|
387
|
+
try {
|
|
388
|
+
const data = await executeGraphQL<{ customerStats: typeof stats }>(
|
|
389
|
+
graphQLOptions,
|
|
390
|
+
CUSTOMER_STATS_QUERY
|
|
391
|
+
);
|
|
392
|
+
setStats(data.customerStats);
|
|
393
|
+
} catch (fetchError) {
|
|
394
|
+
setError(
|
|
395
|
+
fetchError instanceof Error ? fetchError : new Error('Failed to load customer stats')
|
|
396
|
+
);
|
|
397
|
+
} finally {
|
|
398
|
+
setIsLoading(false);
|
|
399
|
+
}
|
|
362
400
|
};
|
|
363
|
-
|
|
401
|
+
|
|
402
|
+
void load();
|
|
403
|
+
}, [graphQLOptions]);
|
|
364
404
|
|
|
365
405
|
return { stats, isLoading, error };
|
|
366
406
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
export function useDebouncedValue<T>(value: T, delay = 300): T {
|
|
4
|
+
const [debouncedValue, setDebouncedValue] = useState(value);
|
|
5
|
+
|
|
6
|
+
useEffect(() => {
|
|
7
|
+
const timer = window.setTimeout(() => setDebouncedValue(value), delay);
|
|
8
|
+
return () => window.clearTimeout(timer);
|
|
9
|
+
}, [value, delay]);
|
|
10
|
+
|
|
11
|
+
return debouncedValue;
|
|
12
|
+
}
|
|
@@ -55,9 +55,11 @@ interface ApiFile {
|
|
|
55
55
|
|
|
56
56
|
interface UseEntityDocumentsResult {
|
|
57
57
|
documents: EntityDocument[];
|
|
58
|
+
hasMore: boolean;
|
|
58
59
|
isLoading: boolean;
|
|
59
60
|
error: Error | null;
|
|
60
61
|
refetch: () => void;
|
|
62
|
+
loadMore: () => void;
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
const LIST_ENTITY_FILES_QUERY = `
|
|
@@ -71,6 +73,7 @@ const LIST_ENTITY_FILES_QUERY = `
|
|
|
71
73
|
createdAt
|
|
72
74
|
downloadUrl
|
|
73
75
|
}
|
|
76
|
+
hasMore
|
|
74
77
|
}
|
|
75
78
|
}
|
|
76
79
|
`;
|
|
@@ -93,6 +96,8 @@ export function useEntityDocuments(
|
|
|
93
96
|
const { apiGatewayUrl, authToken, workspaceToken, workspaceId, tenantId, currentUser } =
|
|
94
97
|
useMoveTheWheels();
|
|
95
98
|
const [documents, setDocuments] = useState<EntityDocument[]>([]);
|
|
99
|
+
const [hasMore, setHasMore] = useState(false);
|
|
100
|
+
const [offset, setOffset] = useState(0);
|
|
96
101
|
const [isLoading, setIsLoading] = useState(false);
|
|
97
102
|
const [error, setError] = useState<Error | null>(null);
|
|
98
103
|
|
|
@@ -108,43 +113,56 @@ export function useEntityDocuments(
|
|
|
108
113
|
[apiGatewayUrl, authToken, workspaceToken, workspaceId, tenantId, currentUser?.id]
|
|
109
114
|
);
|
|
110
115
|
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
116
|
+
const pageSize = 20;
|
|
117
|
+
|
|
118
|
+
const load = useCallback(
|
|
119
|
+
async (requestedOffset = 0, append = false) => {
|
|
120
|
+
if (!entityId) {
|
|
121
|
+
setDocuments([]);
|
|
122
|
+
setHasMore(false);
|
|
123
|
+
setOffset(0);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
setIsLoading(true);
|
|
127
|
+
setError(null);
|
|
128
|
+
try {
|
|
129
|
+
const data = await executeGraphQL<{
|
|
130
|
+
getFiles: { files: ApiFile[]; hasMore?: boolean | null };
|
|
131
|
+
}>(graphQLOptions, LIST_ENTITY_FILES_QUERY, {
|
|
123
132
|
filter: {
|
|
124
133
|
tags: [
|
|
125
134
|
{ key: 'mtwEntityType', label: entityType },
|
|
126
135
|
{ key: 'mtwEntityId', label: entityId },
|
|
127
136
|
],
|
|
128
137
|
},
|
|
129
|
-
pagination: { limit:
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
138
|
+
pagination: { limit: pageSize, offset: requestedOffset },
|
|
139
|
+
});
|
|
140
|
+
const nextDocuments = (data.getFiles?.files ?? []).map(mapFile);
|
|
141
|
+
setDocuments((previous) => (append ? [...previous, ...nextDocuments] : nextDocuments));
|
|
142
|
+
setHasMore(data.getFiles?.hasMore ?? nextDocuments.length === pageSize);
|
|
143
|
+
setOffset(requestedOffset);
|
|
144
|
+
} catch (fetchError) {
|
|
145
|
+
setError(fetchError instanceof Error ? fetchError : new Error('Failed to load documents'));
|
|
146
|
+
if (!append) setDocuments([]);
|
|
147
|
+
} finally {
|
|
148
|
+
setIsLoading(false);
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
[entityId, entityType, graphQLOptions]
|
|
152
|
+
);
|
|
140
153
|
|
|
141
154
|
useEffect(() => {
|
|
142
155
|
void load();
|
|
143
156
|
}, [load]);
|
|
144
157
|
|
|
145
158
|
const refetch = useCallback(() => {
|
|
146
|
-
void load();
|
|
159
|
+
void load(0, false);
|
|
147
160
|
}, [load]);
|
|
148
161
|
|
|
149
|
-
|
|
162
|
+
const loadMore = useCallback(() => {
|
|
163
|
+
if (!hasMore || isLoading) return;
|
|
164
|
+
void load(offset + pageSize, true);
|
|
165
|
+
}, [hasMore, isLoading, load, offset]);
|
|
166
|
+
|
|
167
|
+
return { documents, hasMore, isLoading, error, refetch, loadMore };
|
|
150
168
|
}
|
|
@@ -70,6 +70,7 @@ const MY_PENDING_EPOD_JOBS_QUERY = `
|
|
|
70
70
|
phone
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
|
+
myPendingEpodDeliveryJobsCount
|
|
73
74
|
}
|
|
74
75
|
`;
|
|
75
76
|
|
|
@@ -205,9 +206,12 @@ interface ApiPendingJob {
|
|
|
205
206
|
}
|
|
206
207
|
|
|
207
208
|
/** Type C self-list: the CALLING driver's own jobs still awaiting ePOD capture — feeds the capture flow's job picker. */
|
|
208
|
-
export function useMyPendingEpodDeliveryJobs() {
|
|
209
|
+
export function useMyPendingEpodDeliveryJobs(options: { limit?: number; offset?: number } = {}) {
|
|
210
|
+
const { limit = 20, offset = 0 } = options;
|
|
209
211
|
const graphQLOptions = useGraphQLOptions();
|
|
210
212
|
const [jobs, setJobs] = useState<PendingEpodDeliveryJob[]>([]);
|
|
213
|
+
const [totalJobs, setTotalJobs] = useState(0);
|
|
214
|
+
const [hasMore, setHasMore] = useState(false);
|
|
211
215
|
const [isLoading, setIsLoading] = useState(false);
|
|
212
216
|
const [error, setError] = useState<Error | null>(null);
|
|
213
217
|
|
|
@@ -215,10 +219,13 @@ export function useMyPendingEpodDeliveryJobs() {
|
|
|
215
219
|
setIsLoading(true);
|
|
216
220
|
setError(null);
|
|
217
221
|
try {
|
|
218
|
-
const data = await executeGraphQL<{
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
+
const data = await executeGraphQL<{
|
|
223
|
+
myPendingEpodDeliveryJobs: ApiPendingJob[];
|
|
224
|
+
myPendingEpodDeliveryJobsCount: number;
|
|
225
|
+
}>(graphQLOptions, MY_PENDING_EPOD_JOBS_QUERY, { pagination: { limit, offset } });
|
|
226
|
+
setTotalJobs(data.myPendingEpodDeliveryJobsCount ?? 0);
|
|
227
|
+
setHasMore(
|
|
228
|
+
offset + data.myPendingEpodDeliveryJobs.length < data.myPendingEpodDeliveryJobsCount
|
|
222
229
|
);
|
|
223
230
|
setJobs(
|
|
224
231
|
data.myPendingEpodDeliveryJobs.map((job) => ({
|
|
@@ -240,13 +247,13 @@ export function useMyPendingEpodDeliveryJobs() {
|
|
|
240
247
|
} finally {
|
|
241
248
|
setIsLoading(false);
|
|
242
249
|
}
|
|
243
|
-
}, [graphQLOptions]);
|
|
250
|
+
}, [graphQLOptions, limit, offset]);
|
|
244
251
|
|
|
245
252
|
useEffect(() => {
|
|
246
253
|
void load();
|
|
247
254
|
}, [load]);
|
|
248
255
|
|
|
249
|
-
return { jobs, isLoading, error, refetch: load };
|
|
256
|
+
return { jobs, totalJobs, hasMore, isLoading, error, refetch: load };
|
|
250
257
|
}
|
|
251
258
|
|
|
252
259
|
export interface CaptureEpodArgs {
|
|
@@ -5,6 +5,7 @@ import { executeGraphQL } from '../utils/graphqlClient';
|
|
|
5
5
|
|
|
6
6
|
interface UseFleetOptions {
|
|
7
7
|
filters?: FleetFilter;
|
|
8
|
+
limit?: number;
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
export interface CreateVehicleInput {
|
|
@@ -39,7 +40,7 @@ interface UseFleetResult {
|
|
|
39
40
|
updateDriver: (id: string, input: CreateDriverInput) => Promise<Driver>;
|
|
40
41
|
}
|
|
41
42
|
|
|
42
|
-
interface ApiVehicle {
|
|
43
|
+
export interface ApiVehicle {
|
|
43
44
|
id: string;
|
|
44
45
|
vehicleNumber: string;
|
|
45
46
|
type: string;
|
|
@@ -60,7 +61,7 @@ interface ApiVehicle {
|
|
|
60
61
|
updatedAt?: string;
|
|
61
62
|
}
|
|
62
63
|
|
|
63
|
-
interface ApiDriver {
|
|
64
|
+
export interface ApiDriver {
|
|
64
65
|
id: string;
|
|
65
66
|
userId?: string | null;
|
|
66
67
|
firstName: string;
|
|
@@ -100,6 +101,7 @@ const LIST_VEHICLES_QUERY = `
|
|
|
100
101
|
createdAt
|
|
101
102
|
updatedAt
|
|
102
103
|
}
|
|
104
|
+
vehiclesCount(filter: $filter)
|
|
103
105
|
}
|
|
104
106
|
`;
|
|
105
107
|
|
|
@@ -122,6 +124,7 @@ const LIST_DRIVERS_QUERY = `
|
|
|
122
124
|
vehicleId
|
|
123
125
|
createdAt
|
|
124
126
|
}
|
|
127
|
+
driversCount(filter: $filter)
|
|
125
128
|
}
|
|
126
129
|
`;
|
|
127
130
|
|
|
@@ -408,7 +411,7 @@ function toApiDriverStatus(status: DriverStatus): string {
|
|
|
408
411
|
}
|
|
409
412
|
}
|
|
410
413
|
|
|
411
|
-
function mapVehicle(vehicle: ApiVehicle): Vehicle {
|
|
414
|
+
export function mapVehicle(vehicle: ApiVehicle): Vehicle {
|
|
412
415
|
return {
|
|
413
416
|
id: vehicle.id,
|
|
414
417
|
vehicleNumber: vehicle.vehicleNumber,
|
|
@@ -443,7 +446,7 @@ function mapVehicle(vehicle: ApiVehicle): Vehicle {
|
|
|
443
446
|
};
|
|
444
447
|
}
|
|
445
448
|
|
|
446
|
-
function mapDriver(driver: ApiDriver): Driver {
|
|
449
|
+
export function mapDriver(driver: ApiDriver): Driver {
|
|
447
450
|
return {
|
|
448
451
|
id: driver.id,
|
|
449
452
|
userId: driver.userId ?? driver.id,
|
|
@@ -524,13 +527,13 @@ function useGraphqlOptions() {
|
|
|
524
527
|
}
|
|
525
528
|
|
|
526
529
|
export function useFleet(options: UseFleetOptions = {}): UseFleetResult {
|
|
527
|
-
const { filters: rawFilters } = options;
|
|
530
|
+
const { filters: rawFilters, limit = 20 } = options;
|
|
528
531
|
// Stabilize the filters reference by its primitive fields so React Compiler's
|
|
529
532
|
// inferred `filters` dependency stays stable and `load` is not recreated (and
|
|
530
533
|
// refetched) every render when the caller passes an inline filters object.
|
|
531
534
|
const filters = useMemo(
|
|
532
535
|
() => rawFilters,
|
|
533
|
-
[rawFilters?.driverId, rawFilters?.status, rawFilters?.vehicleType]
|
|
536
|
+
[rawFilters?.driverId, rawFilters?.search, rawFilters?.status, rawFilters?.vehicleType]
|
|
534
537
|
);
|
|
535
538
|
const graphQLOptions = useGraphqlOptions();
|
|
536
539
|
const [vehicles, setVehicles] = useState<Vehicle[]>([]);
|
|
@@ -545,16 +548,18 @@ export function useFleet(options: UseFleetOptions = {}): UseFleetResult {
|
|
|
545
548
|
const [vehicleData, driverData] = await Promise.all([
|
|
546
549
|
executeGraphQL<{ vehicles: ApiVehicle[] }>(graphQLOptions, LIST_VEHICLES_QUERY, {
|
|
547
550
|
filter: {
|
|
551
|
+
search: filters?.search,
|
|
548
552
|
type: filters?.vehicleType?.toUpperCase(),
|
|
549
553
|
status: filters?.status ? toApiVehicleStatus(filters.status) : undefined,
|
|
550
554
|
},
|
|
551
|
-
pagination: { limit
|
|
555
|
+
pagination: { limit, offset: 0 },
|
|
552
556
|
}),
|
|
553
557
|
executeGraphQL<{ drivers: ApiDriver[] }>(graphQLOptions, LIST_DRIVERS_QUERY, {
|
|
554
558
|
filter: {
|
|
559
|
+
search: filters?.search,
|
|
555
560
|
vehicleId: filters?.driverId,
|
|
556
561
|
},
|
|
557
|
-
pagination: { limit
|
|
562
|
+
pagination: { limit, offset: 0 },
|
|
558
563
|
}),
|
|
559
564
|
]);
|
|
560
565
|
|
|
@@ -565,7 +570,7 @@ export function useFleet(options: UseFleetOptions = {}): UseFleetResult {
|
|
|
565
570
|
} finally {
|
|
566
571
|
setIsLoading(false);
|
|
567
572
|
}
|
|
568
|
-
}, [filters, graphQLOptions]);
|
|
573
|
+
}, [filters, graphQLOptions, limit]);
|
|
569
574
|
|
|
570
575
|
useEffect(() => {
|
|
571
576
|
void load();
|
|
@@ -686,6 +691,12 @@ interface UseListOptions {
|
|
|
686
691
|
offset?: number;
|
|
687
692
|
}
|
|
688
693
|
|
|
694
|
+
interface UseVehiclesOptions extends UseListOptions {
|
|
695
|
+
search?: string;
|
|
696
|
+
status?: VehicleStatus;
|
|
697
|
+
vehicleType?: Vehicle['type'];
|
|
698
|
+
}
|
|
699
|
+
|
|
689
700
|
interface UseVehiclesResult {
|
|
690
701
|
vehicles: Vehicle[];
|
|
691
702
|
totalVehicles: number;
|
|
@@ -698,10 +709,11 @@ interface UseVehiclesResult {
|
|
|
698
709
|
assignDriver: (vehicleId: string, driverId: string) => Promise<Vehicle>;
|
|
699
710
|
}
|
|
700
711
|
|
|
701
|
-
export function useVehicles(options:
|
|
702
|
-
const { limit =
|
|
712
|
+
export function useVehicles(options: UseVehiclesOptions = {}): UseVehiclesResult {
|
|
713
|
+
const { limit = 20, offset = 0, search, status, vehicleType } = options;
|
|
703
714
|
const graphQLOptions = useGraphqlOptions();
|
|
704
715
|
const [vehicles, setVehicles] = useState<Vehicle[]>([]);
|
|
716
|
+
const [totalVehicles, setTotalVehicles] = useState(0);
|
|
705
717
|
const [hasMore, setHasMore] = useState(false);
|
|
706
718
|
const [isLoading, setIsLoading] = useState(false);
|
|
707
719
|
const [error, setError] = useState<Error | null>(null);
|
|
@@ -710,21 +722,26 @@ export function useVehicles(options: UseListOptions = {}): UseVehiclesResult {
|
|
|
710
722
|
setIsLoading(true);
|
|
711
723
|
setError(null);
|
|
712
724
|
try {
|
|
713
|
-
const data = await executeGraphQL<{
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
725
|
+
const data = await executeGraphQL<{
|
|
726
|
+
vehicles: ApiVehicle[];
|
|
727
|
+
vehiclesCount: number;
|
|
728
|
+
}>(graphQLOptions, LIST_VEHICLES_QUERY, {
|
|
729
|
+
filter: {
|
|
730
|
+
search: search || undefined,
|
|
731
|
+
status: status ? toApiVehicleStatus(status) : undefined,
|
|
732
|
+
type: vehicleType?.toUpperCase(),
|
|
733
|
+
},
|
|
734
|
+
pagination: { limit, offset },
|
|
735
|
+
});
|
|
736
|
+
setHasMore(offset + data.vehicles.length < data.vehiclesCount);
|
|
737
|
+
setTotalVehicles(data.vehiclesCount);
|
|
721
738
|
setVehicles(data.vehicles.map(mapVehicle));
|
|
722
739
|
} catch (fetchError) {
|
|
723
740
|
setError(fetchError instanceof Error ? fetchError : new Error('Failed to load vehicles'));
|
|
724
741
|
} finally {
|
|
725
742
|
setIsLoading(false);
|
|
726
743
|
}
|
|
727
|
-
}, [graphQLOptions, limit, offset]);
|
|
744
|
+
}, [graphQLOptions, limit, offset, search, status, vehicleType]);
|
|
728
745
|
|
|
729
746
|
useEffect(() => {
|
|
730
747
|
void load();
|
|
@@ -774,7 +791,7 @@ export function useVehicles(options: UseListOptions = {}): UseVehiclesResult {
|
|
|
774
791
|
|
|
775
792
|
return {
|
|
776
793
|
vehicles,
|
|
777
|
-
totalVehicles
|
|
794
|
+
totalVehicles,
|
|
778
795
|
hasMore,
|
|
779
796
|
isLoading,
|
|
780
797
|
error,
|
|
@@ -838,17 +855,17 @@ interface UseDriversResult {
|
|
|
838
855
|
}
|
|
839
856
|
|
|
840
857
|
interface UseDriversOptions extends UseListOptions {
|
|
841
|
-
/**
|
|
842
|
-
* Narrows the roster server-side via `DriverFilterInput.status` so the pager
|
|
843
|
-
* still counts whole result sets rather than filtering one fetched page.
|
|
844
|
-
*/
|
|
858
|
+
/** Narrows the roster server-side via DriverFilterInput.status. */
|
|
845
859
|
status?: DriverStatus;
|
|
860
|
+
search?: string;
|
|
861
|
+
licenseClass?: string;
|
|
846
862
|
}
|
|
847
863
|
|
|
848
864
|
export function useDrivers(options: UseDriversOptions = {}): UseDriversResult {
|
|
849
|
-
const { limit =
|
|
865
|
+
const { limit = 20, offset = 0, status, search, licenseClass } = options;
|
|
850
866
|
const graphQLOptions = useGraphqlOptions();
|
|
851
867
|
const [drivers, setDrivers] = useState<Driver[]>([]);
|
|
868
|
+
const [totalDrivers, setTotalDrivers] = useState(0);
|
|
852
869
|
const [hasMore, setHasMore] = useState(false);
|
|
853
870
|
const [isLoading, setIsLoading] = useState(false);
|
|
854
871
|
const [error, setError] = useState<Error | null>(null);
|
|
@@ -857,22 +874,26 @@ export function useDrivers(options: UseDriversOptions = {}): UseDriversResult {
|
|
|
857
874
|
setIsLoading(true);
|
|
858
875
|
setError(null);
|
|
859
876
|
try {
|
|
860
|
-
const data = await executeGraphQL<{
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
877
|
+
const data = await executeGraphQL<{
|
|
878
|
+
drivers: ApiDriver[];
|
|
879
|
+
driversCount: number;
|
|
880
|
+
}>(graphQLOptions, LIST_DRIVERS_QUERY, {
|
|
881
|
+
filter: {
|
|
882
|
+
status: status ? toApiDriverStatus(status) : undefined,
|
|
883
|
+
search: search || undefined,
|
|
884
|
+
licenseClass: licenseClass || undefined,
|
|
885
|
+
},
|
|
886
|
+
pagination: { limit, offset },
|
|
887
|
+
});
|
|
888
|
+
setHasMore(offset + data.drivers.length < data.driversCount);
|
|
889
|
+
setTotalDrivers(data.driversCount);
|
|
869
890
|
setDrivers(data.drivers.map(mapDriver));
|
|
870
891
|
} catch (fetchError) {
|
|
871
892
|
setError(fetchError instanceof Error ? fetchError : new Error('Failed to load drivers'));
|
|
872
893
|
} finally {
|
|
873
894
|
setIsLoading(false);
|
|
874
895
|
}
|
|
875
|
-
}, [graphQLOptions, limit, offset, status]);
|
|
896
|
+
}, [graphQLOptions, limit, offset, status, search, licenseClass]);
|
|
876
897
|
|
|
877
898
|
useEffect(() => {
|
|
878
899
|
void load();
|
|
@@ -918,7 +939,7 @@ export function useDrivers(options: UseDriversOptions = {}): UseDriversResult {
|
|
|
918
939
|
|
|
919
940
|
return {
|
|
920
941
|
drivers,
|
|
921
|
-
totalDrivers
|
|
942
|
+
totalDrivers,
|
|
922
943
|
hasMore,
|
|
923
944
|
isLoading,
|
|
924
945
|
error,
|
|
@@ -11,10 +11,7 @@ import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
|
|
|
11
11
|
import { executeGraphQL } from '../utils/graphqlClient';
|
|
12
12
|
|
|
13
13
|
export type FleetAlertType =
|
|
14
|
-
| '
|
|
15
|
-
| 'LICENSE_EXPIRING'
|
|
16
|
-
| 'MAINTENANCE_OVERDUE'
|
|
17
|
-
| 'MAINTENANCE_DUE_SOON';
|
|
14
|
+
'LICENSE_EXPIRED' | 'LICENSE_EXPIRING' | 'MAINTENANCE_OVERDUE' | 'MAINTENANCE_DUE_SOON';
|
|
18
15
|
export type FleetAlertSeverity = 'CRITICAL' | 'WARNING';
|
|
19
16
|
export type FleetAlertSubjectType = 'VEHICLE' | 'DRIVER';
|
|
20
17
|
|