@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
|
@@ -23,11 +23,7 @@ export type OrderWriteInput = Omit<Partial<Order>, 'scheduledDelivery'> & {
|
|
|
23
23
|
interface UseOrdersResult {
|
|
24
24
|
orders: Order[];
|
|
25
25
|
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
|
-
*/
|
|
26
|
+
/** True when the most recent fetch has another server page. */
|
|
31
27
|
hasMore: boolean;
|
|
32
28
|
isLoading: boolean;
|
|
33
29
|
error: Error | null;
|
|
@@ -42,7 +38,7 @@ interface UseOrdersResult {
|
|
|
42
38
|
// The `order` domain was renamed to `delivery_jobs`/MtwDeliveryJob on 2026-07-07 (commit
|
|
43
39
|
// 7e99ec5); `orderType` was dropped entirely in that rename with no replacement field, so it
|
|
44
40
|
// is intentionally absent here — see BOFF-4505.
|
|
45
|
-
interface ApiOrder {
|
|
41
|
+
export interface ApiOrder {
|
|
46
42
|
id: string;
|
|
47
43
|
deliveryJobNumber: string;
|
|
48
44
|
customerId: string;
|
|
@@ -111,6 +107,7 @@ const LIST_ORDERS_QUERY = `
|
|
|
111
107
|
deliveryJobs(filter: $filter, pagination: $pagination) {
|
|
112
108
|
${DELIVERY_JOB_FIELDS}
|
|
113
109
|
}
|
|
110
|
+
deliveryJobsCount(filter: $filter)
|
|
114
111
|
}
|
|
115
112
|
`;
|
|
116
113
|
|
|
@@ -266,7 +263,7 @@ function mapAddress(address: Record<string, unknown> | null | undefined): Order[
|
|
|
266
263
|
} as Order['pickupAddress'];
|
|
267
264
|
}
|
|
268
265
|
|
|
269
|
-
function mapOrder(order: ApiOrder): Order {
|
|
266
|
+
export function mapOrder(order: ApiOrder): Order {
|
|
270
267
|
return {
|
|
271
268
|
id: order.id,
|
|
272
269
|
orderNumber: order.deliveryJobNumber,
|
|
@@ -324,6 +321,7 @@ export function useOrders(options: UseOrdersOptions = {}): UseOrdersResult {
|
|
|
324
321
|
rawFilters?.priority,
|
|
325
322
|
rawFilters?.customerId,
|
|
326
323
|
rawFilters?.driverId,
|
|
324
|
+
rawFilters?.search,
|
|
327
325
|
rawFilters?.unassigned,
|
|
328
326
|
]
|
|
329
327
|
);
|
|
@@ -331,6 +329,7 @@ export function useOrders(options: UseOrdersOptions = {}): UseOrdersResult {
|
|
|
331
329
|
useMoveTheWheels();
|
|
332
330
|
|
|
333
331
|
const [orders, setOrders] = useState<Order[]>([]);
|
|
332
|
+
const [totalOrders, setTotalOrders] = useState(0);
|
|
334
333
|
const [hasMore, setHasMore] = useState(false);
|
|
335
334
|
const [isLoading, setIsLoading] = useState(false);
|
|
336
335
|
const [error, setError] = useState<Error | null>(null);
|
|
@@ -353,6 +352,7 @@ export function useOrders(options: UseOrdersOptions = {}): UseOrdersResult {
|
|
|
353
352
|
try {
|
|
354
353
|
const data = await executeGraphQL<{
|
|
355
354
|
deliveryJobs: ApiOrder[];
|
|
355
|
+
deliveryJobsCount: number;
|
|
356
356
|
}>(graphQLOptions, LIST_ORDERS_QUERY, {
|
|
357
357
|
filter: {
|
|
358
358
|
status: filters?.status ? toApiStatus(filters.status) : undefined,
|
|
@@ -361,17 +361,16 @@ export function useOrders(options: UseOrdersOptions = {}): UseOrdersResult {
|
|
|
361
361
|
: undefined,
|
|
362
362
|
customerId: filters?.customerId,
|
|
363
363
|
driverId: filters?.driverId,
|
|
364
|
+
unassigned: filters?.unassigned,
|
|
365
|
+
search: filters?.search,
|
|
364
366
|
},
|
|
365
367
|
pagination: { limit, offset },
|
|
366
368
|
});
|
|
367
369
|
|
|
368
370
|
const raw = data.deliveryJobs;
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
mapped = mapped.filter((item) => !item.driverId);
|
|
373
|
-
}
|
|
374
|
-
setOrders(mapped);
|
|
371
|
+
setTotalOrders(data.deliveryJobsCount);
|
|
372
|
+
setHasMore(offset + raw.length < data.deliveryJobsCount);
|
|
373
|
+
setOrders(raw.map(mapOrder));
|
|
375
374
|
} catch (fetchError) {
|
|
376
375
|
setError(fetchError instanceof Error ? fetchError : new Error('Failed to load orders'));
|
|
377
376
|
} finally {
|
|
@@ -481,7 +480,7 @@ export function useOrders(options: UseOrdersOptions = {}): UseOrdersResult {
|
|
|
481
480
|
|
|
482
481
|
return {
|
|
483
482
|
orders,
|
|
484
|
-
totalOrders
|
|
483
|
+
totalOrders,
|
|
485
484
|
hasMore,
|
|
486
485
|
isLoading,
|
|
487
486
|
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
|
|
|
@@ -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();
|