@burdenoff/microfe-movethewheels 2026.920.3 → 2026.923.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 +2719 -8
- package/dist/hooks/index.js +1 -1
- 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 +148 -125
- package/dist/hooks/useInventory.js +255 -186
- package/dist/hooks/useMultiModalShipments.js +35 -27
- package/dist/hooks/useOrders.js +139 -134
- package/dist/hooks/useRoutes.js +97 -75
- package/dist/hooks/useTracking.js +100 -88
- package/dist/index.js +6 -6
- 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 +48 -5
- package/dist/movethewheels/hooks/useRoutes.d.ts +18 -1
- package/dist/movethewheels/hooks/useTracking.d.ts +12 -11
- package/dist/movethewheels/types/index.d.ts +12 -5
- package/dist/pages/CreateOrderPage.js +191 -129
- 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 +190 -106
- 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 +324 -293
- 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 +3136 -8
- 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 +66 -38
- 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 +48 -14
- package/src/movethewheels/hooks/useRoutes.ts +81 -24
- package/src/movethewheels/hooks/useTracking.ts +130 -98
- package/src/movethewheels/pages/CreateOrderPage.tsx +93 -2
- 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 +91 -6
- 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 +45 -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 +12 -5
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
2
2
|
import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
|
|
3
3
|
import type { Order, OrderFilter, OrderStatus } from '../types';
|
|
4
|
+
import { mapRoute } from './useRoutes';
|
|
4
5
|
import { executeGraphQL } from '../utils/graphqlClient';
|
|
5
6
|
|
|
6
7
|
interface UseOrdersOptions {
|
|
@@ -23,11 +24,7 @@ export type OrderWriteInput = Omit<Partial<Order>, 'scheduledDelivery'> & {
|
|
|
23
24
|
interface UseOrdersResult {
|
|
24
25
|
orders: Order[];
|
|
25
26
|
totalOrders: number;
|
|
26
|
-
/**
|
|
27
|
-
* True when the most recent fetch returned a full page (rawCount === limit),
|
|
28
|
-
* i.e. there may be another page. Used for offset pagers where no grand
|
|
29
|
-
* total is available.
|
|
30
|
-
*/
|
|
27
|
+
/** True when the most recent fetch has another server page. */
|
|
31
28
|
hasMore: boolean;
|
|
32
29
|
isLoading: boolean;
|
|
33
30
|
error: Error | null;
|
|
@@ -42,7 +39,7 @@ interface UseOrdersResult {
|
|
|
42
39
|
// The `order` domain was renamed to `delivery_jobs`/MtwDeliveryJob on 2026-07-07 (commit
|
|
43
40
|
// 7e99ec5); `orderType` was dropped entirely in that rename with no replacement field, so it
|
|
44
41
|
// is intentionally absent here — see BOFF-4505.
|
|
45
|
-
interface ApiOrder {
|
|
42
|
+
export interface ApiOrder {
|
|
46
43
|
id: string;
|
|
47
44
|
deliveryJobNumber: string;
|
|
48
45
|
customerId: string;
|
|
@@ -57,6 +54,21 @@ interface ApiOrder {
|
|
|
57
54
|
scheduledDelivery?: string | null;
|
|
58
55
|
vehicleId?: string | null;
|
|
59
56
|
driverId?: string | null;
|
|
57
|
+
routeId?: string | null;
|
|
58
|
+
route?: {
|
|
59
|
+
id: string;
|
|
60
|
+
name: string;
|
|
61
|
+
status: string;
|
|
62
|
+
vehicleId?: string | null;
|
|
63
|
+
driverId?: string | null;
|
|
64
|
+
waypoints?: unknown;
|
|
65
|
+
estimatedDistance?: number | null;
|
|
66
|
+
estimatedDuration?: number | null;
|
|
67
|
+
fuelCost?: number | null;
|
|
68
|
+
actualDistance?: number | null;
|
|
69
|
+
actualDuration?: number | null;
|
|
70
|
+
createdAt: string;
|
|
71
|
+
} | null;
|
|
60
72
|
createdAt: string;
|
|
61
73
|
updatedAt: string;
|
|
62
74
|
customer?: {
|
|
@@ -89,6 +101,21 @@ const DELIVERY_JOB_FIELDS = `
|
|
|
89
101
|
scheduledDelivery
|
|
90
102
|
vehicleId
|
|
91
103
|
driverId
|
|
104
|
+
routeId
|
|
105
|
+
route {
|
|
106
|
+
id
|
|
107
|
+
name
|
|
108
|
+
status
|
|
109
|
+
vehicleId
|
|
110
|
+
driverId
|
|
111
|
+
waypoints
|
|
112
|
+
estimatedDistance
|
|
113
|
+
estimatedDuration
|
|
114
|
+
fuelCost
|
|
115
|
+
actualDistance
|
|
116
|
+
actualDuration
|
|
117
|
+
createdAt
|
|
118
|
+
}
|
|
92
119
|
createdAt
|
|
93
120
|
updatedAt
|
|
94
121
|
customer {
|
|
@@ -111,6 +138,7 @@ const LIST_ORDERS_QUERY = `
|
|
|
111
138
|
deliveryJobs(filter: $filter, pagination: $pagination) {
|
|
112
139
|
${DELIVERY_JOB_FIELDS}
|
|
113
140
|
}
|
|
141
|
+
deliveryJobsCount(filter: $filter)
|
|
114
142
|
}
|
|
115
143
|
`;
|
|
116
144
|
|
|
@@ -266,7 +294,7 @@ function mapAddress(address: Record<string, unknown> | null | undefined): Order[
|
|
|
266
294
|
} as Order['pickupAddress'];
|
|
267
295
|
}
|
|
268
296
|
|
|
269
|
-
function mapOrder(order: ApiOrder): Order {
|
|
297
|
+
export function mapOrder(order: ApiOrder): Order {
|
|
270
298
|
return {
|
|
271
299
|
id: order.id,
|
|
272
300
|
orderNumber: order.deliveryJobNumber,
|
|
@@ -305,6 +333,8 @@ function mapOrder(order: ApiOrder): Order {
|
|
|
305
333
|
scheduledDelivery: order.scheduledDelivery ? new Date(order.scheduledDelivery) : undefined,
|
|
306
334
|
vehicleId: order.vehicleId ?? undefined,
|
|
307
335
|
driverId: order.driverId ?? undefined,
|
|
336
|
+
routeId: order.routeId ?? undefined,
|
|
337
|
+
route: order.route ? mapRoute(order.route) : undefined,
|
|
308
338
|
createdAt: new Date(order.createdAt),
|
|
309
339
|
updatedAt: new Date(order.updatedAt),
|
|
310
340
|
};
|
|
@@ -324,6 +354,7 @@ export function useOrders(options: UseOrdersOptions = {}): UseOrdersResult {
|
|
|
324
354
|
rawFilters?.priority,
|
|
325
355
|
rawFilters?.customerId,
|
|
326
356
|
rawFilters?.driverId,
|
|
357
|
+
rawFilters?.search,
|
|
327
358
|
rawFilters?.unassigned,
|
|
328
359
|
]
|
|
329
360
|
);
|
|
@@ -331,6 +362,7 @@ export function useOrders(options: UseOrdersOptions = {}): UseOrdersResult {
|
|
|
331
362
|
useMoveTheWheels();
|
|
332
363
|
|
|
333
364
|
const [orders, setOrders] = useState<Order[]>([]);
|
|
365
|
+
const [totalOrders, setTotalOrders] = useState(0);
|
|
334
366
|
const [hasMore, setHasMore] = useState(false);
|
|
335
367
|
const [isLoading, setIsLoading] = useState(false);
|
|
336
368
|
const [error, setError] = useState<Error | null>(null);
|
|
@@ -353,6 +385,7 @@ export function useOrders(options: UseOrdersOptions = {}): UseOrdersResult {
|
|
|
353
385
|
try {
|
|
354
386
|
const data = await executeGraphQL<{
|
|
355
387
|
deliveryJobs: ApiOrder[];
|
|
388
|
+
deliveryJobsCount: number;
|
|
356
389
|
}>(graphQLOptions, LIST_ORDERS_QUERY, {
|
|
357
390
|
filter: {
|
|
358
391
|
status: filters?.status ? toApiStatus(filters.status) : undefined,
|
|
@@ -361,17 +394,16 @@ export function useOrders(options: UseOrdersOptions = {}): UseOrdersResult {
|
|
|
361
394
|
: undefined,
|
|
362
395
|
customerId: filters?.customerId,
|
|
363
396
|
driverId: filters?.driverId,
|
|
397
|
+
unassigned: filters?.unassigned,
|
|
398
|
+
search: filters?.search,
|
|
364
399
|
},
|
|
365
400
|
pagination: { limit, offset },
|
|
366
401
|
});
|
|
367
402
|
|
|
368
403
|
const raw = data.deliveryJobs;
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
mapped = mapped.filter((item) => !item.driverId);
|
|
373
|
-
}
|
|
374
|
-
setOrders(mapped);
|
|
404
|
+
setTotalOrders(data.deliveryJobsCount);
|
|
405
|
+
setHasMore(offset + raw.length < data.deliveryJobsCount);
|
|
406
|
+
setOrders(raw.map(mapOrder));
|
|
375
407
|
} catch (fetchError) {
|
|
376
408
|
setError(fetchError instanceof Error ? fetchError : new Error('Failed to load orders'));
|
|
377
409
|
} finally {
|
|
@@ -410,6 +442,7 @@ export function useOrders(options: UseOrdersOptions = {}): UseOrdersResult {
|
|
|
410
442
|
scheduledDelivery: orderData.scheduledDelivery?.toISOString(),
|
|
411
443
|
vehicleId: orderData.vehicleId,
|
|
412
444
|
driverId: orderData.driverId,
|
|
445
|
+
routeId: orderData.routeId,
|
|
413
446
|
},
|
|
414
447
|
});
|
|
415
448
|
|
|
@@ -441,6 +474,7 @@ export function useOrders(options: UseOrdersOptions = {}): UseOrdersResult {
|
|
|
441
474
|
updates.scheduledDelivery === null ? null : updates.scheduledDelivery?.toISOString(),
|
|
442
475
|
vehicleId: updates.vehicleId,
|
|
443
476
|
driverId: updates.driverId,
|
|
477
|
+
routeId: updates.routeId,
|
|
444
478
|
},
|
|
445
479
|
});
|
|
446
480
|
|
|
@@ -481,7 +515,7 @@ export function useOrders(options: UseOrdersOptions = {}): UseOrdersResult {
|
|
|
481
515
|
|
|
482
516
|
return {
|
|
483
517
|
orders,
|
|
484
|
-
totalOrders
|
|
518
|
+
totalOrders,
|
|
485
519
|
hasMore,
|
|
486
520
|
isLoading,
|
|
487
521
|
error,
|
|
@@ -7,6 +7,7 @@ interface UseRoutesOptions {
|
|
|
7
7
|
driverId?: string;
|
|
8
8
|
vehicleId?: string;
|
|
9
9
|
status?: 'planned' | 'in_progress' | 'completed' | 'cancelled';
|
|
10
|
+
search?: string;
|
|
10
11
|
limit?: number;
|
|
11
12
|
offset?: number;
|
|
12
13
|
}
|
|
@@ -14,7 +15,7 @@ interface UseRoutesOptions {
|
|
|
14
15
|
interface UseRoutesResult {
|
|
15
16
|
routes: Route[];
|
|
16
17
|
totalRoutes: number;
|
|
17
|
-
/** True when the last fetch
|
|
18
|
+
/** True when the last fetch has another server page. */
|
|
18
19
|
hasMore: boolean;
|
|
19
20
|
isLoading: boolean;
|
|
20
21
|
error: Error | null;
|
|
@@ -63,6 +64,7 @@ const LIST_ROUTES_QUERY = `
|
|
|
63
64
|
actualDuration
|
|
64
65
|
createdAt
|
|
65
66
|
}
|
|
67
|
+
routesCount(filter: $filter)
|
|
66
68
|
}
|
|
67
69
|
`;
|
|
68
70
|
|
|
@@ -216,7 +218,7 @@ function mapWaypoints(waypoints: unknown): RouteStop[] {
|
|
|
216
218
|
});
|
|
217
219
|
}
|
|
218
220
|
|
|
219
|
-
function mapRoute(route: ApiRoute): Route {
|
|
221
|
+
export function mapRoute(route: ApiRoute): Route {
|
|
220
222
|
return {
|
|
221
223
|
id: route.id,
|
|
222
224
|
name: route.name,
|
|
@@ -253,9 +255,10 @@ function useGraphqlOptions() {
|
|
|
253
255
|
}
|
|
254
256
|
|
|
255
257
|
export function useRoutes(options: UseRoutesOptions = {}): UseRoutesResult {
|
|
256
|
-
const { driverId, vehicleId, status, limit =
|
|
258
|
+
const { driverId, vehicleId, status, search, limit = 20, offset = 0 } = options;
|
|
257
259
|
const graphQLOptions = useGraphqlOptions();
|
|
258
260
|
const [routes, setRoutes] = useState<Route[]>([]);
|
|
261
|
+
const [totalRoutes, setTotalRoutes] = useState(0);
|
|
259
262
|
const [hasMore, setHasMore] = useState(false);
|
|
260
263
|
const [isLoading, setIsLoading] = useState(false);
|
|
261
264
|
const [error, setError] = useState<Error | null>(null);
|
|
@@ -264,23 +267,28 @@ export function useRoutes(options: UseRoutesOptions = {}): UseRoutesResult {
|
|
|
264
267
|
setIsLoading(true);
|
|
265
268
|
setError(null);
|
|
266
269
|
try {
|
|
267
|
-
const data = await executeGraphQL<{
|
|
270
|
+
const data = await executeGraphQL<{
|
|
271
|
+
routes: ApiRoute[];
|
|
272
|
+
routesCount: number;
|
|
273
|
+
}>(graphQLOptions, LIST_ROUTES_QUERY, {
|
|
268
274
|
filter: {
|
|
269
275
|
driverId,
|
|
270
276
|
vehicleId,
|
|
271
277
|
status: status ? toApiRouteStatus(status) : undefined,
|
|
278
|
+
search: search || undefined,
|
|
272
279
|
},
|
|
273
280
|
pagination: { limit, offset },
|
|
274
281
|
});
|
|
275
282
|
|
|
276
|
-
|
|
283
|
+
setTotalRoutes(data.routesCount);
|
|
284
|
+
setHasMore(offset + data.routes.length < data.routesCount);
|
|
277
285
|
setRoutes(data.routes.map(mapRoute));
|
|
278
286
|
} catch (fetchError) {
|
|
279
287
|
setError(fetchError instanceof Error ? fetchError : new Error('Failed to load routes'));
|
|
280
288
|
} finally {
|
|
281
289
|
setIsLoading(false);
|
|
282
290
|
}
|
|
283
|
-
}, [driverId, graphQLOptions, status, vehicleId, limit, offset]);
|
|
291
|
+
}, [driverId, graphQLOptions, search, status, vehicleId, limit, offset]);
|
|
284
292
|
|
|
285
293
|
useEffect(() => {
|
|
286
294
|
void load();
|
|
@@ -400,7 +408,7 @@ export function useRoutes(options: UseRoutesOptions = {}): UseRoutesResult {
|
|
|
400
408
|
|
|
401
409
|
return {
|
|
402
410
|
routes,
|
|
403
|
-
totalRoutes
|
|
411
|
+
totalRoutes,
|
|
404
412
|
hasMore,
|
|
405
413
|
isLoading,
|
|
406
414
|
error,
|
|
@@ -455,26 +463,75 @@ export function useRoute(routeId: string | undefined) {
|
|
|
455
463
|
};
|
|
456
464
|
}
|
|
457
465
|
|
|
458
|
-
|
|
459
|
-
|
|
466
|
+
const ROUTE_STATS_QUERY = `
|
|
467
|
+
query RouteStats {
|
|
468
|
+
routeStats {
|
|
469
|
+
total
|
|
470
|
+
totalDistance
|
|
471
|
+
totalDuration
|
|
472
|
+
completed
|
|
473
|
+
inProgress
|
|
474
|
+
planned
|
|
475
|
+
optimized
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
`;
|
|
460
479
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
480
|
+
export function useRouteStats() {
|
|
481
|
+
const graphQLOptions = useGraphqlOptions();
|
|
482
|
+
const [stats, setStats] = useState({
|
|
483
|
+
totalRoutes: 0,
|
|
484
|
+
totalDistance: 0,
|
|
485
|
+
totalDuration: 0,
|
|
486
|
+
avgDistance: 0,
|
|
487
|
+
avgDuration: 0,
|
|
488
|
+
completedRoutes: 0,
|
|
489
|
+
inProgressRoutes: 0,
|
|
490
|
+
plannedRoutes: 0,
|
|
491
|
+
optimizedRoutes: 0,
|
|
492
|
+
});
|
|
493
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
494
|
+
const [error, setError] = useState<Error | null>(null);
|
|
465
495
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
496
|
+
useEffect(() => {
|
|
497
|
+
const load = async () => {
|
|
498
|
+
setIsLoading(true);
|
|
499
|
+
setError(null);
|
|
500
|
+
try {
|
|
501
|
+
const data = await executeGraphQL<{
|
|
502
|
+
routeStats: {
|
|
503
|
+
total: number;
|
|
504
|
+
totalDistance: number;
|
|
505
|
+
totalDuration: number;
|
|
506
|
+
completed: number;
|
|
507
|
+
inProgress: number;
|
|
508
|
+
planned: number;
|
|
509
|
+
optimized: number;
|
|
510
|
+
};
|
|
511
|
+
}>(graphQLOptions, ROUTE_STATS_QUERY);
|
|
512
|
+
const routeStats = data.routeStats;
|
|
513
|
+
setStats({
|
|
514
|
+
totalRoutes: routeStats.total,
|
|
515
|
+
totalDistance: routeStats.totalDistance,
|
|
516
|
+
totalDuration: routeStats.totalDuration,
|
|
517
|
+
avgDistance: routeStats.total > 0 ? routeStats.totalDistance / routeStats.total : 0,
|
|
518
|
+
avgDuration: routeStats.total > 0 ? routeStats.totalDuration / routeStats.total : 0,
|
|
519
|
+
completedRoutes: routeStats.completed,
|
|
520
|
+
inProgressRoutes: routeStats.inProgress,
|
|
521
|
+
plannedRoutes: routeStats.planned,
|
|
522
|
+
optimizedRoutes: routeStats.optimized,
|
|
523
|
+
});
|
|
524
|
+
} catch (fetchError) {
|
|
525
|
+
setError(
|
|
526
|
+
fetchError instanceof Error ? fetchError : new Error('Failed to load route stats')
|
|
527
|
+
);
|
|
528
|
+
} finally {
|
|
529
|
+
setIsLoading(false);
|
|
530
|
+
}
|
|
476
531
|
};
|
|
477
|
-
|
|
532
|
+
|
|
533
|
+
void load();
|
|
534
|
+
}, [graphQLOptions]);
|
|
478
535
|
|
|
479
536
|
return { stats, isLoading, error };
|
|
480
537
|
}
|
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
2
2
|
import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
|
|
3
|
-
import type { Location, Tracking, TrackingAlert } from '../types';
|
|
3
|
+
import type { Location, Order, Tracking, TrackingAlert, Driver, Vehicle } from '../types';
|
|
4
4
|
import { executeGraphQL } from '../utils/graphqlClient';
|
|
5
|
-
import {
|
|
6
|
-
|
|
5
|
+
import {
|
|
6
|
+
mapDriver,
|
|
7
|
+
mapVehicle,
|
|
8
|
+
type ApiDriver,
|
|
9
|
+
type ApiVehicle,
|
|
10
|
+
useDriver,
|
|
11
|
+
useVehicle,
|
|
12
|
+
useVehicles,
|
|
13
|
+
useDrivers,
|
|
14
|
+
} from './useFleet';
|
|
15
|
+
import { mapOrder, type ApiOrder, useOrder } from './useOrders';
|
|
7
16
|
|
|
8
17
|
interface UseTrackingOptions {
|
|
9
18
|
entityId?: string;
|
|
@@ -36,6 +45,23 @@ interface ApiShipment {
|
|
|
36
45
|
trackingEvents?: ApiTrackingEvent[];
|
|
37
46
|
}
|
|
38
47
|
|
|
48
|
+
interface ApiActiveDelivery extends ApiShipment {
|
|
49
|
+
deliveryJob?:
|
|
50
|
+
| (ApiOrder & {
|
|
51
|
+
vehicle?: ApiVehicle | null;
|
|
52
|
+
driver?: ApiDriver | null;
|
|
53
|
+
})
|
|
54
|
+
| null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface LiveDelivery {
|
|
58
|
+
order: Order;
|
|
59
|
+
driver: Driver | null;
|
|
60
|
+
vehicle: Vehicle | null;
|
|
61
|
+
tracking: Tracking | null;
|
|
62
|
+
estimatedArrival: Date | undefined;
|
|
63
|
+
}
|
|
64
|
+
|
|
39
65
|
const SHIPMENTS_QUERY = `
|
|
40
66
|
query ListShipments($filter: ShipmentFilterInput, $pagination: PaginationInput) {
|
|
41
67
|
shipments(filter: $filter, pagination: $pagination) {
|
|
@@ -48,29 +74,9 @@ const SHIPMENTS_QUERY = `
|
|
|
48
74
|
}
|
|
49
75
|
`;
|
|
50
76
|
|
|
51
|
-
const SHIPMENT_QUERY = `
|
|
52
|
-
query GetShipment($id: ID!) {
|
|
53
|
-
shipment(id: $id) {
|
|
54
|
-
id
|
|
55
|
-
shipmentNumber
|
|
56
|
-
deliveryJobId
|
|
57
|
-
status
|
|
58
|
-
estimatedDelivery
|
|
59
|
-
trackingEvents {
|
|
60
|
-
id
|
|
61
|
-
shipmentId
|
|
62
|
-
eventType
|
|
63
|
-
location
|
|
64
|
-
details
|
|
65
|
-
timestamp
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
`;
|
|
70
|
-
|
|
71
77
|
const DELIVERY_JOB_TRACKING_QUERY = `
|
|
72
|
-
query GetDeliveryJobTracking($deliveryJobId: ID
|
|
73
|
-
deliveryJobTracking(deliveryJobId: $deliveryJobId) {
|
|
78
|
+
query GetDeliveryJobTracking($deliveryJobId: ID!, $pagination: PaginationInput) {
|
|
79
|
+
deliveryJobTracking(deliveryJobId: $deliveryJobId, pagination: $pagination) {
|
|
74
80
|
id
|
|
75
81
|
shipmentId
|
|
76
82
|
eventType
|
|
@@ -82,13 +88,81 @@ const DELIVERY_JOB_TRACKING_QUERY = `
|
|
|
82
88
|
`;
|
|
83
89
|
|
|
84
90
|
const ACTIVE_DELIVERIES_QUERY = `
|
|
85
|
-
query GetActiveDeliveries {
|
|
86
|
-
activeDeliveries {
|
|
91
|
+
query GetActiveDeliveries($pagination: PaginationInput) {
|
|
92
|
+
activeDeliveries(pagination: $pagination) {
|
|
87
93
|
id
|
|
88
94
|
shipmentNumber
|
|
89
95
|
deliveryJobId
|
|
90
96
|
status
|
|
91
97
|
estimatedDelivery
|
|
98
|
+
trackingEvents(pagination: { limit: 1, offset: 0 }) {
|
|
99
|
+
id
|
|
100
|
+
shipmentId
|
|
101
|
+
eventType
|
|
102
|
+
location
|
|
103
|
+
details
|
|
104
|
+
timestamp
|
|
105
|
+
}
|
|
106
|
+
deliveryJob {
|
|
107
|
+
id
|
|
108
|
+
deliveryJobNumber
|
|
109
|
+
customerId
|
|
110
|
+
status
|
|
111
|
+
priority
|
|
112
|
+
pickupAddress
|
|
113
|
+
deliveryAddress
|
|
114
|
+
cargoItems
|
|
115
|
+
pricing
|
|
116
|
+
trackingNumber
|
|
117
|
+
specialInstructions
|
|
118
|
+
scheduledDelivery
|
|
119
|
+
vehicleId
|
|
120
|
+
driverId
|
|
121
|
+
createdAt
|
|
122
|
+
updatedAt
|
|
123
|
+
customer {
|
|
124
|
+
name
|
|
125
|
+
email
|
|
126
|
+
phone
|
|
127
|
+
company
|
|
128
|
+
}
|
|
129
|
+
vehicle {
|
|
130
|
+
id
|
|
131
|
+
vehicleNumber
|
|
132
|
+
type
|
|
133
|
+
make
|
|
134
|
+
model
|
|
135
|
+
licensePlate
|
|
136
|
+
ownershipType
|
|
137
|
+
year
|
|
138
|
+
capacity
|
|
139
|
+
status
|
|
140
|
+
currentLocation
|
|
141
|
+
fuelLevel
|
|
142
|
+
mileage
|
|
143
|
+
lastServiceDate
|
|
144
|
+
nextServiceDueDate
|
|
145
|
+
nextServiceDueMileage
|
|
146
|
+
createdAt
|
|
147
|
+
}
|
|
148
|
+
driver {
|
|
149
|
+
id
|
|
150
|
+
userId
|
|
151
|
+
firstName
|
|
152
|
+
lastName
|
|
153
|
+
email
|
|
154
|
+
phone
|
|
155
|
+
employeeId
|
|
156
|
+
licenseNumber
|
|
157
|
+
licenseClass
|
|
158
|
+
licenseExpiry
|
|
159
|
+
status
|
|
160
|
+
currentLocation
|
|
161
|
+
rating
|
|
162
|
+
vehicleId
|
|
163
|
+
createdAt
|
|
164
|
+
}
|
|
165
|
+
}
|
|
92
166
|
}
|
|
93
167
|
}
|
|
94
168
|
`;
|
|
@@ -157,14 +231,21 @@ export function useTracking(options: UseTrackingOptions = {}): UseTrackingResult
|
|
|
157
231
|
graphQLOptions,
|
|
158
232
|
SHIPMENTS_QUERY,
|
|
159
233
|
{
|
|
160
|
-
|
|
234
|
+
filter: entityId
|
|
235
|
+
? {
|
|
236
|
+
...(entityType === 'order' ? { deliveryJobId: entityId } : {}),
|
|
237
|
+
...(entityType === 'vehicle' ? { vehicleId: entityId } : {}),
|
|
238
|
+
...(entityType === 'driver' ? { driverId: entityId } : {}),
|
|
239
|
+
}
|
|
240
|
+
: undefined,
|
|
241
|
+
pagination: { limit: 50, offset: 0 },
|
|
161
242
|
}
|
|
162
243
|
);
|
|
163
244
|
|
|
164
245
|
const mapped = data.shipments.map((shipment) => ({
|
|
165
246
|
id: shipment.id,
|
|
166
|
-
entityId: shipment.deliveryJobId ?? shipment.id,
|
|
167
|
-
entityType: 'order'
|
|
247
|
+
entityId: entityId ?? shipment.deliveryJobId ?? shipment.id,
|
|
248
|
+
entityType: entityType ?? 'order',
|
|
168
249
|
currentLocation: {
|
|
169
250
|
latitude: 0,
|
|
170
251
|
longitude: 0,
|
|
@@ -180,13 +261,7 @@ export function useTracking(options: UseTrackingOptions = {}): UseTrackingResult
|
|
|
180
261
|
lastUpdated: new Date(),
|
|
181
262
|
}));
|
|
182
263
|
|
|
183
|
-
|
|
184
|
-
if (entityId && item.entityId !== entityId) return false;
|
|
185
|
-
if (entityType && item.entityType !== entityType) return false;
|
|
186
|
-
return true;
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
setTracking(filtered);
|
|
264
|
+
setTracking(mapped);
|
|
190
265
|
} catch (fetchError) {
|
|
191
266
|
setError(
|
|
192
267
|
fetchError instanceof Error ? fetchError : new Error('Failed to load tracking data')
|
|
@@ -229,9 +304,10 @@ export function useOrderTracking(orderId: string | undefined) {
|
|
|
229
304
|
DELIVERY_JOB_TRACKING_QUERY,
|
|
230
305
|
{
|
|
231
306
|
deliveryJobId: orderId,
|
|
307
|
+
pagination: { limit: 1, offset: 0 },
|
|
232
308
|
}
|
|
233
309
|
);
|
|
234
|
-
const latest = data.deliveryJobTracking[
|
|
310
|
+
const latest = data.deliveryJobTracking[0];
|
|
235
311
|
setTracking(latest ? mapTrackingEventToTracking(latest, orderId) : null);
|
|
236
312
|
} catch (fetchError) {
|
|
237
313
|
setError(
|
|
@@ -291,18 +367,7 @@ export function useDriverTracking(driverId: string | undefined) {
|
|
|
291
367
|
|
|
292
368
|
export function useLiveTracking() {
|
|
293
369
|
const graphQLOptions = useGraphqlOptions();
|
|
294
|
-
const
|
|
295
|
-
const { vehicles } = useVehicles();
|
|
296
|
-
const { drivers } = useDrivers();
|
|
297
|
-
const [activeDeliveries, setActiveDeliveries] = useState<
|
|
298
|
-
Array<{
|
|
299
|
-
order: (typeof orders)[number];
|
|
300
|
-
driver: (typeof drivers)[number] | null;
|
|
301
|
-
vehicle: (typeof vehicles)[number] | null;
|
|
302
|
-
tracking: Tracking | null;
|
|
303
|
-
estimatedArrival: Date | undefined;
|
|
304
|
-
}>
|
|
305
|
-
>([]);
|
|
370
|
+
const [activeDeliveries, setActiveDeliveries] = useState<LiveDelivery[]>([]);
|
|
306
371
|
const [isLoading, setIsLoading] = useState(false);
|
|
307
372
|
const [error, setError] = useState<Error | null>(null);
|
|
308
373
|
|
|
@@ -310,64 +375,31 @@ export function useLiveTracking() {
|
|
|
310
375
|
setIsLoading(true);
|
|
311
376
|
setError(null);
|
|
312
377
|
try {
|
|
313
|
-
const data = await executeGraphQL<{ activeDeliveries:
|
|
378
|
+
const data = await executeGraphQL<{ activeDeliveries: ApiActiveDelivery[] }>(
|
|
314
379
|
graphQLOptions,
|
|
315
|
-
ACTIVE_DELIVERIES_QUERY
|
|
380
|
+
ACTIVE_DELIVERIES_QUERY,
|
|
381
|
+
{ pagination: { limit: 100, offset: 0 } }
|
|
316
382
|
);
|
|
317
383
|
|
|
318
|
-
const list =
|
|
319
|
-
|
|
320
|
-
const
|
|
321
|
-
|
|
322
|
-
SHIPMENT_QUERY,
|
|
323
|
-
{
|
|
324
|
-
id: shipment.id,
|
|
325
|
-
}
|
|
326
|
-
);
|
|
327
|
-
|
|
328
|
-
const shipmentEvents = shipmentData.shipment?.trackingEvents ?? [];
|
|
329
|
-
const latestEvent = shipmentEvents[shipmentEvents.length - 1];
|
|
330
|
-
const order = orders.find((item) => item.id === shipment.deliveryJobId);
|
|
331
|
-
if (!order) {
|
|
332
|
-
return null;
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
const driver = order.driverId
|
|
336
|
-
? (drivers.find(
|
|
337
|
-
(item) => item.id === order.driverId || item.userId === order.driverId
|
|
338
|
-
) ?? null)
|
|
339
|
-
: null;
|
|
340
|
-
const vehicle = order.vehicleId
|
|
341
|
-
? (vehicles.find((item) => item.id === order.vehicleId) ?? null)
|
|
342
|
-
: null;
|
|
384
|
+
const list = (data.activeDeliveries ?? [])
|
|
385
|
+
.map((shipment): LiveDelivery | null => {
|
|
386
|
+
const deliveryJob = shipment.deliveryJob;
|
|
387
|
+
if (!deliveryJob) return null;
|
|
343
388
|
|
|
389
|
+
const latestEvent = shipment.trackingEvents?.[0];
|
|
344
390
|
return {
|
|
345
|
-
order,
|
|
346
|
-
driver,
|
|
347
|
-
vehicle,
|
|
348
|
-
tracking: latestEvent
|
|
349
|
-
? mapTrackingEventToTracking(latestEvent, shipment.deliveryJobId ?? shipment.id)
|
|
350
|
-
: null,
|
|
391
|
+
order: mapOrder(deliveryJob),
|
|
392
|
+
driver: deliveryJob.driver ? mapDriver(deliveryJob.driver) : null,
|
|
393
|
+
vehicle: deliveryJob.vehicle ? mapVehicle(deliveryJob.vehicle) : null,
|
|
394
|
+
tracking: latestEvent ? mapTrackingEventToTracking(latestEvent, deliveryJob.id) : null,
|
|
351
395
|
estimatedArrival: shipment.estimatedDelivery
|
|
352
396
|
? new Date(shipment.estimatedDelivery)
|
|
353
397
|
: undefined,
|
|
354
398
|
};
|
|
355
399
|
})
|
|
356
|
-
|
|
400
|
+
.filter((item): item is LiveDelivery => item !== null);
|
|
357
401
|
|
|
358
|
-
setActiveDeliveries(
|
|
359
|
-
list.filter(
|
|
360
|
-
(
|
|
361
|
-
item
|
|
362
|
-
): item is {
|
|
363
|
-
order: (typeof orders)[number];
|
|
364
|
-
driver: (typeof drivers)[number] | null;
|
|
365
|
-
vehicle: (typeof vehicles)[number] | null;
|
|
366
|
-
tracking: Tracking | null;
|
|
367
|
-
estimatedArrival: Date | undefined;
|
|
368
|
-
} => item !== null
|
|
369
|
-
)
|
|
370
|
-
);
|
|
402
|
+
setActiveDeliveries(list);
|
|
371
403
|
} catch (fetchError) {
|
|
372
404
|
setError(
|
|
373
405
|
fetchError instanceof Error ? fetchError : new Error('Failed to load live tracking')
|
|
@@ -375,7 +407,7 @@ export function useLiveTracking() {
|
|
|
375
407
|
} finally {
|
|
376
408
|
setIsLoading(false);
|
|
377
409
|
}
|
|
378
|
-
}, [
|
|
410
|
+
}, [graphQLOptions]);
|
|
379
411
|
|
|
380
412
|
useEffect(() => {
|
|
381
413
|
void load();
|