@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
|
@@ -5,6 +5,9 @@ import { executeGraphQL } from '../utils/graphqlClient';
|
|
|
5
5
|
|
|
6
6
|
interface UseInventoryOptions {
|
|
7
7
|
filters?: InventoryFilter;
|
|
8
|
+
search?: string;
|
|
9
|
+
limit?: number;
|
|
10
|
+
offset?: number;
|
|
8
11
|
}
|
|
9
12
|
|
|
10
13
|
export interface CreateProductInput {
|
|
@@ -32,9 +35,11 @@ export interface CreateWarehouseInput {
|
|
|
32
35
|
|
|
33
36
|
interface UseInventoryResult {
|
|
34
37
|
inventory: Inventory[];
|
|
38
|
+
lowStockItems: Inventory[];
|
|
35
39
|
products: Product[];
|
|
36
40
|
warehouses: Warehouse[];
|
|
37
41
|
totalItems: number;
|
|
42
|
+
hasMore: boolean;
|
|
38
43
|
isLoading: boolean;
|
|
39
44
|
error: Error | null;
|
|
40
45
|
refetch: () => void;
|
|
@@ -68,6 +73,8 @@ interface ApiInventoryItem {
|
|
|
68
73
|
reorderPoint?: number | null;
|
|
69
74
|
createdAt: string;
|
|
70
75
|
updatedAt: string;
|
|
76
|
+
cargoItem?: { id: string; name: string; sku: string } | null;
|
|
77
|
+
warehouse?: { id: string; name: string; code: string } | null;
|
|
71
78
|
}
|
|
72
79
|
|
|
73
80
|
interface ApiProduct {
|
|
@@ -111,7 +118,18 @@ const LIST_INVENTORY_QUERY = `
|
|
|
111
118
|
reorderPoint
|
|
112
119
|
createdAt
|
|
113
120
|
updatedAt
|
|
121
|
+
cargoItem {
|
|
122
|
+
id
|
|
123
|
+
name
|
|
124
|
+
sku
|
|
125
|
+
}
|
|
126
|
+
warehouse {
|
|
127
|
+
id
|
|
128
|
+
name
|
|
129
|
+
code
|
|
130
|
+
}
|
|
114
131
|
}
|
|
132
|
+
inventoryItemsCount(filter: $filter)
|
|
115
133
|
}
|
|
116
134
|
`;
|
|
117
135
|
|
|
@@ -133,6 +151,7 @@ const LIST_PRODUCTS_QUERY = `
|
|
|
133
151
|
createdAt
|
|
134
152
|
updatedAt
|
|
135
153
|
}
|
|
154
|
+
cargoItemsCount(filter: $filter)
|
|
136
155
|
}
|
|
137
156
|
`;
|
|
138
157
|
|
|
@@ -148,6 +167,7 @@ const LIST_WAREHOUSES_QUERY = `
|
|
|
148
167
|
usedCapacity
|
|
149
168
|
createdAt
|
|
150
169
|
}
|
|
170
|
+
warehousesCount(filter: $filter)
|
|
151
171
|
}
|
|
152
172
|
`;
|
|
153
173
|
|
|
@@ -386,7 +406,10 @@ function mapInventory(item: ApiInventoryItem): Inventory {
|
|
|
386
406
|
return {
|
|
387
407
|
id: item.id,
|
|
388
408
|
productId: item.cargoItemId,
|
|
409
|
+
productName: item.cargoItem?.name,
|
|
410
|
+
productSku: item.cargoItem?.sku,
|
|
389
411
|
warehouseId: item.warehouseId,
|
|
412
|
+
warehouseName: item.warehouse?.name,
|
|
390
413
|
zoneId: '',
|
|
391
414
|
quantity: item.onHand,
|
|
392
415
|
reservedQuantity: item.allocated,
|
|
@@ -423,18 +446,20 @@ function useGraphqlOptions() {
|
|
|
423
446
|
}
|
|
424
447
|
|
|
425
448
|
export function useInventory(options: UseInventoryOptions = {}): UseInventoryResult {
|
|
426
|
-
const { filters: rawFilters } = options;
|
|
449
|
+
const { filters: rawFilters, search, limit = 10, offset = 0 } = options;
|
|
427
450
|
// Stabilize the filters reference by its primitive fields so React Compiler's
|
|
428
|
-
// inferred `filters` dependency stays stable
|
|
429
|
-
// refetched) every render when the caller passes an inline filters object.
|
|
451
|
+
// inferred `filters` dependency stays stable (callers commonly pass inline objects).
|
|
430
452
|
const filters = useMemo(
|
|
431
453
|
() => rawFilters,
|
|
432
|
-
[rawFilters?.lowStock, rawFilters?.productId, rawFilters?.warehouseId]
|
|
454
|
+
[rawFilters?.lowStock, rawFilters?.productId, rawFilters?.search, rawFilters?.warehouseId]
|
|
433
455
|
);
|
|
434
456
|
const graphQLOptions = useGraphqlOptions();
|
|
435
457
|
const [inventory, setInventory] = useState<Inventory[]>([]);
|
|
458
|
+
const [lowStockItems, setLowStockItems] = useState<Inventory[]>([]);
|
|
436
459
|
const [products, setProducts] = useState<Product[]>([]);
|
|
437
460
|
const [warehouses, setWarehouses] = useState<Warehouse[]>([]);
|
|
461
|
+
const [totalItems, setTotalItems] = useState(0);
|
|
462
|
+
const [hasMore, setHasMore] = useState(false);
|
|
438
463
|
const [isLoading, setIsLoading] = useState(false);
|
|
439
464
|
const [error, setError] = useState<Error | null>(null);
|
|
440
465
|
|
|
@@ -442,7 +467,21 @@ export function useInventory(options: UseInventoryOptions = {}): UseInventoryRes
|
|
|
442
467
|
setIsLoading(true);
|
|
443
468
|
setError(null);
|
|
444
469
|
try {
|
|
445
|
-
const
|
|
470
|
+
const inventoryFilter = {
|
|
471
|
+
warehouseId: filters?.warehouseId,
|
|
472
|
+
cargoItemId: filters?.productId,
|
|
473
|
+
search: search || filters?.search || undefined,
|
|
474
|
+
lowStock: filters?.lowStock || undefined,
|
|
475
|
+
};
|
|
476
|
+
const [inventoryData, lowStockData, productData, warehouseData] = await Promise.all([
|
|
477
|
+
executeGraphQL<{ inventoryItems: ApiInventoryItem[]; inventoryItemsCount: number }>(
|
|
478
|
+
graphQLOptions,
|
|
479
|
+
LIST_INVENTORY_QUERY,
|
|
480
|
+
{
|
|
481
|
+
filter: inventoryFilter,
|
|
482
|
+
pagination: { limit, offset },
|
|
483
|
+
}
|
|
484
|
+
),
|
|
446
485
|
executeGraphQL<{ inventoryItems: ApiInventoryItem[] }>(
|
|
447
486
|
graphQLOptions,
|
|
448
487
|
LIST_INVENTORY_QUERY,
|
|
@@ -450,24 +489,23 @@ export function useInventory(options: UseInventoryOptions = {}): UseInventoryRes
|
|
|
450
489
|
filter: {
|
|
451
490
|
warehouseId: filters?.warehouseId,
|
|
452
491
|
cargoItemId: filters?.productId,
|
|
492
|
+
lowStock: true,
|
|
453
493
|
},
|
|
454
|
-
pagination: { limit:
|
|
494
|
+
pagination: { limit: 5, offset: 0 },
|
|
455
495
|
}
|
|
456
496
|
),
|
|
457
497
|
executeGraphQL<{ cargoItems: ApiProduct[] }>(graphQLOptions, LIST_PRODUCTS_QUERY, {
|
|
458
|
-
pagination: { limit:
|
|
498
|
+
pagination: { limit: 5, offset: 0 },
|
|
459
499
|
}),
|
|
460
500
|
executeGraphQL<{ warehouses: ApiWarehouse[] }>(graphQLOptions, LIST_WAREHOUSES_QUERY, {
|
|
461
|
-
pagination: { limit:
|
|
501
|
+
pagination: { limit: 5, offset: 0 },
|
|
462
502
|
}),
|
|
463
503
|
]);
|
|
464
504
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
setInventory(nextInventory);
|
|
505
|
+
setInventory(inventoryData.inventoryItems.map(mapInventory));
|
|
506
|
+
setLowStockItems(lowStockData.inventoryItems.map(mapInventory));
|
|
507
|
+
setTotalItems(inventoryData.inventoryItemsCount);
|
|
508
|
+
setHasMore(offset + inventoryData.inventoryItems.length < inventoryData.inventoryItemsCount);
|
|
471
509
|
setProducts(productData.cargoItems.map(mapProduct));
|
|
472
510
|
setWarehouses(warehouseData.warehouses.map(mapWarehouse));
|
|
473
511
|
} catch (fetchError) {
|
|
@@ -477,7 +515,7 @@ export function useInventory(options: UseInventoryOptions = {}): UseInventoryRes
|
|
|
477
515
|
} finally {
|
|
478
516
|
setIsLoading(false);
|
|
479
517
|
}
|
|
480
|
-
}, [filters, graphQLOptions]);
|
|
518
|
+
}, [filters, graphQLOptions, limit, offset, search]);
|
|
481
519
|
|
|
482
520
|
useEffect(() => {
|
|
483
521
|
void load();
|
|
@@ -635,9 +673,11 @@ export function useInventory(options: UseInventoryOptions = {}): UseInventoryRes
|
|
|
635
673
|
|
|
636
674
|
return {
|
|
637
675
|
inventory,
|
|
676
|
+
lowStockItems,
|
|
638
677
|
products,
|
|
639
678
|
warehouses,
|
|
640
|
-
totalItems
|
|
679
|
+
totalItems,
|
|
680
|
+
hasMore,
|
|
641
681
|
isLoading,
|
|
642
682
|
error,
|
|
643
683
|
refetch,
|
|
@@ -655,10 +695,20 @@ interface UseListOptions {
|
|
|
655
695
|
offset?: number;
|
|
656
696
|
}
|
|
657
697
|
|
|
658
|
-
|
|
659
|
-
|
|
698
|
+
interface UseProductsOptions extends UseListOptions {
|
|
699
|
+
search?: string;
|
|
700
|
+
category?: string;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
interface UseWarehousesOptions extends UseListOptions {
|
|
704
|
+
search?: string;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
export function useProducts(options: UseProductsOptions = {}) {
|
|
708
|
+
const { limit = 20, offset = 0, search, category } = options;
|
|
660
709
|
const graphQLOptions = useGraphqlOptions();
|
|
661
710
|
const [products, setProducts] = useState<Product[]>([]);
|
|
711
|
+
const [totalProducts, setTotalProducts] = useState(0);
|
|
662
712
|
const [hasMore, setHasMore] = useState(false);
|
|
663
713
|
const [isLoading, setIsLoading] = useState(false);
|
|
664
714
|
const [error, setError] = useState<Error | null>(null);
|
|
@@ -667,21 +717,22 @@ export function useProducts(options: UseListOptions = {}) {
|
|
|
667
717
|
setIsLoading(true);
|
|
668
718
|
setError(null);
|
|
669
719
|
try {
|
|
670
|
-
const data = await executeGraphQL<{
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
}
|
|
676
|
-
);
|
|
677
|
-
setHasMore(data.cargoItems.length
|
|
720
|
+
const data = await executeGraphQL<{
|
|
721
|
+
cargoItems: ApiProduct[];
|
|
722
|
+
cargoItemsCount: number;
|
|
723
|
+
}>(graphQLOptions, LIST_PRODUCTS_QUERY, {
|
|
724
|
+
filter: { search: search || undefined, category: category || undefined },
|
|
725
|
+
pagination: { limit, offset },
|
|
726
|
+
});
|
|
727
|
+
setHasMore(offset + data.cargoItems.length < data.cargoItemsCount);
|
|
728
|
+
setTotalProducts(data.cargoItemsCount);
|
|
678
729
|
setProducts(data.cargoItems.map(mapProduct));
|
|
679
730
|
} catch (fetchError) {
|
|
680
731
|
setError(fetchError instanceof Error ? fetchError : new Error('Failed to load products'));
|
|
681
732
|
} finally {
|
|
682
733
|
setIsLoading(false);
|
|
683
734
|
}
|
|
684
|
-
}, [graphQLOptions, limit, offset]);
|
|
735
|
+
}, [graphQLOptions, limit, offset, search, category]);
|
|
685
736
|
|
|
686
737
|
useEffect(() => {
|
|
687
738
|
void load();
|
|
@@ -689,7 +740,7 @@ export function useProducts(options: UseListOptions = {}) {
|
|
|
689
740
|
|
|
690
741
|
return {
|
|
691
742
|
products,
|
|
692
|
-
totalProducts
|
|
743
|
+
totalProducts,
|
|
693
744
|
hasMore,
|
|
694
745
|
isLoading,
|
|
695
746
|
error,
|
|
@@ -697,22 +748,43 @@ export function useProducts(options: UseListOptions = {}) {
|
|
|
697
748
|
};
|
|
698
749
|
}
|
|
699
750
|
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
751
|
+
const PRODUCT_STATS_QUERY = `
|
|
752
|
+
query CargoItemStats {
|
|
753
|
+
cargoItemStats {
|
|
754
|
+
total
|
|
755
|
+
specialHandling
|
|
756
|
+
temperatureControlled
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
`;
|
|
760
|
+
|
|
761
|
+
export function useProductStats() {
|
|
762
|
+
const graphQLOptions = useGraphqlOptions();
|
|
763
|
+
const [stats, setStats] = useState({ total: 0, specialHandling: 0, temperatureControlled: 0 });
|
|
764
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
765
|
+
const [error, setError] = useState<Error | null>(null);
|
|
766
|
+
|
|
767
|
+
useEffect(() => {
|
|
768
|
+
const load = async () => {
|
|
769
|
+
setIsLoading(true);
|
|
770
|
+
setError(null);
|
|
771
|
+
try {
|
|
772
|
+
const data = await executeGraphQL<{ cargoItemStats: typeof stats }>(
|
|
773
|
+
graphQLOptions,
|
|
774
|
+
PRODUCT_STATS_QUERY
|
|
775
|
+
);
|
|
776
|
+
setStats(data.cargoItemStats);
|
|
777
|
+
} catch (fetchError) {
|
|
778
|
+
setError(
|
|
779
|
+
fetchError instanceof Error ? fetchError : new Error('Failed to load product stats')
|
|
780
|
+
);
|
|
781
|
+
} finally {
|
|
782
|
+
setIsLoading(false);
|
|
783
|
+
}
|
|
784
|
+
};
|
|
709
785
|
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
const specialHandling = products.filter((item) => item.fragile || item.hazardous).length;
|
|
713
|
-
const temperatureControlled = products.filter((item) => item.temperatureControlled).length;
|
|
714
|
-
return { total, specialHandling, temperatureControlled };
|
|
715
|
-
}, [products]);
|
|
786
|
+
void load();
|
|
787
|
+
}, [graphQLOptions]);
|
|
716
788
|
|
|
717
789
|
return { stats, isLoading, error };
|
|
718
790
|
}
|
|
@@ -753,10 +825,11 @@ export function useProduct(productId: string | undefined) {
|
|
|
753
825
|
return { product, isLoading, error, refetch: () => void load() };
|
|
754
826
|
}
|
|
755
827
|
|
|
756
|
-
export function useWarehouses(options:
|
|
757
|
-
const { limit =
|
|
828
|
+
export function useWarehouses(options: UseWarehousesOptions = {}) {
|
|
829
|
+
const { limit = 20, offset = 0, search } = options;
|
|
758
830
|
const graphQLOptions = useGraphqlOptions();
|
|
759
831
|
const [warehouses, setWarehouses] = useState<Warehouse[]>([]);
|
|
832
|
+
const [totalWarehouses, setTotalWarehouses] = useState(0);
|
|
760
833
|
const [hasMore, setHasMore] = useState(false);
|
|
761
834
|
const [isLoading, setIsLoading] = useState(false);
|
|
762
835
|
const [error, setError] = useState<Error | null>(null);
|
|
@@ -765,21 +838,22 @@ export function useWarehouses(options: UseListOptions = {}) {
|
|
|
765
838
|
setIsLoading(true);
|
|
766
839
|
setError(null);
|
|
767
840
|
try {
|
|
768
|
-
const data = await executeGraphQL<{
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
}
|
|
774
|
-
);
|
|
775
|
-
|
|
841
|
+
const data = await executeGraphQL<{
|
|
842
|
+
warehouses: ApiWarehouse[];
|
|
843
|
+
warehousesCount: number;
|
|
844
|
+
}>(graphQLOptions, LIST_WAREHOUSES_QUERY, {
|
|
845
|
+
filter: { search: search || undefined },
|
|
846
|
+
pagination: { limit, offset },
|
|
847
|
+
});
|
|
848
|
+
setTotalWarehouses(data.warehousesCount);
|
|
849
|
+
setHasMore(offset + data.warehouses.length < data.warehousesCount);
|
|
776
850
|
setWarehouses(data.warehouses.map(mapWarehouse));
|
|
777
851
|
} catch (fetchError) {
|
|
778
852
|
setError(fetchError instanceof Error ? fetchError : new Error('Failed to load warehouses'));
|
|
779
853
|
} finally {
|
|
780
854
|
setIsLoading(false);
|
|
781
855
|
}
|
|
782
|
-
}, [graphQLOptions, limit, offset]);
|
|
856
|
+
}, [graphQLOptions, limit, offset, search]);
|
|
783
857
|
|
|
784
858
|
useEffect(() => {
|
|
785
859
|
void load();
|
|
@@ -787,7 +861,7 @@ export function useWarehouses(options: UseListOptions = {}) {
|
|
|
787
861
|
|
|
788
862
|
return {
|
|
789
863
|
warehouses,
|
|
790
|
-
totalWarehouses
|
|
864
|
+
totalWarehouses,
|
|
791
865
|
hasMore,
|
|
792
866
|
isLoading,
|
|
793
867
|
error,
|
|
@@ -795,30 +869,43 @@ export function useWarehouses(options: UseListOptions = {}) {
|
|
|
795
869
|
};
|
|
796
870
|
}
|
|
797
871
|
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
:
|
|
820
|
-
|
|
821
|
-
|
|
872
|
+
const WAREHOUSE_STATS_QUERY = `
|
|
873
|
+
query WarehouseStats {
|
|
874
|
+
warehouseStats {
|
|
875
|
+
total
|
|
876
|
+
active
|
|
877
|
+
avgUtilization
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
`;
|
|
881
|
+
|
|
882
|
+
export function useWarehouseStats() {
|
|
883
|
+
const graphQLOptions = useGraphqlOptions();
|
|
884
|
+
const [stats, setStats] = useState({ total: 0, active: 0, avgUtilization: 0 });
|
|
885
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
886
|
+
const [error, setError] = useState<Error | null>(null);
|
|
887
|
+
|
|
888
|
+
useEffect(() => {
|
|
889
|
+
const load = async () => {
|
|
890
|
+
setIsLoading(true);
|
|
891
|
+
setError(null);
|
|
892
|
+
try {
|
|
893
|
+
const data = await executeGraphQL<{ warehouseStats: typeof stats }>(
|
|
894
|
+
graphQLOptions,
|
|
895
|
+
WAREHOUSE_STATS_QUERY
|
|
896
|
+
);
|
|
897
|
+
setStats(data.warehouseStats);
|
|
898
|
+
} catch (fetchError) {
|
|
899
|
+
setError(
|
|
900
|
+
fetchError instanceof Error ? fetchError : new Error('Failed to load warehouse stats')
|
|
901
|
+
);
|
|
902
|
+
} finally {
|
|
903
|
+
setIsLoading(false);
|
|
904
|
+
}
|
|
905
|
+
};
|
|
906
|
+
|
|
907
|
+
void load();
|
|
908
|
+
}, [graphQLOptions]);
|
|
822
909
|
|
|
823
910
|
return { stats, isLoading, error };
|
|
824
911
|
}
|
|
@@ -941,8 +1028,8 @@ export function useInventoryStats() {
|
|
|
941
1028
|
* the row instead of being joined against a separately-paged warehouse list.
|
|
942
1029
|
*/
|
|
943
1030
|
const PRODUCT_STOCK_LEVELS_QUERY = `
|
|
944
|
-
query GetCargoItemStockLevels($cargoItemId: ID
|
|
945
|
-
inventoryItems(filter: { cargoItemId: $cargoItemId }, pagination:
|
|
1031
|
+
query GetCargoItemStockLevels($cargoItemId: ID!, $pagination: PaginationInput) {
|
|
1032
|
+
inventoryItems(filter: { cargoItemId: $cargoItemId }, pagination: $pagination) {
|
|
946
1033
|
id
|
|
947
1034
|
onHand
|
|
948
1035
|
available
|
|
@@ -955,6 +1042,10 @@ const PRODUCT_STOCK_LEVELS_QUERY = `
|
|
|
955
1042
|
code
|
|
956
1043
|
}
|
|
957
1044
|
}
|
|
1045
|
+
inventoryItemsCount(filter: { cargoItemId: $cargoItemId })
|
|
1046
|
+
inventoryStats(filter: { cargoItemId: $cargoItemId }) {
|
|
1047
|
+
totalAvailable
|
|
1048
|
+
}
|
|
958
1049
|
}
|
|
959
1050
|
`;
|
|
960
1051
|
|
|
@@ -980,28 +1071,48 @@ interface ApiProductStockLevel {
|
|
|
980
1071
|
warehouse?: { id: string; name: string; code: string } | null;
|
|
981
1072
|
}
|
|
982
1073
|
|
|
1074
|
+
interface ApiProductStockSummary {
|
|
1075
|
+
totalAvailable: number;
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
interface ProductStockLevelOptions {
|
|
1079
|
+
limit?: number;
|
|
1080
|
+
offset?: number;
|
|
1081
|
+
}
|
|
1082
|
+
|
|
983
1083
|
/** Where a catalog item is stocked, and how much of it is free to promise. */
|
|
984
|
-
export function useProductStockLevels(
|
|
1084
|
+
export function useProductStockLevels(
|
|
1085
|
+
productId: string | undefined,
|
|
1086
|
+
options: ProductStockLevelOptions = {}
|
|
1087
|
+
) {
|
|
1088
|
+
const { limit = 20, offset = 0 } = options;
|
|
985
1089
|
const graphQLOptions = useGraphqlOptions();
|
|
986
1090
|
const [levels, setLevels] = useState<ProductStockLevel[]>([]);
|
|
1091
|
+
const [totalLevels, setTotalLevels] = useState(0);
|
|
1092
|
+
const [totalAvailable, setTotalAvailable] = useState(0);
|
|
987
1093
|
const [isLoading, setIsLoading] = useState(false);
|
|
988
1094
|
const [error, setError] = useState<Error | null>(null);
|
|
989
1095
|
|
|
990
1096
|
const load = useCallback(async () => {
|
|
991
1097
|
if (!productId) {
|
|
992
1098
|
setLevels([]);
|
|
1099
|
+
setTotalLevels(0);
|
|
1100
|
+
setTotalAvailable(0);
|
|
993
1101
|
return;
|
|
994
1102
|
}
|
|
995
1103
|
setIsLoading(true);
|
|
996
1104
|
setError(null);
|
|
997
1105
|
try {
|
|
998
|
-
const data = await executeGraphQL<{
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1106
|
+
const data = await executeGraphQL<{
|
|
1107
|
+
inventoryItems: ApiProductStockLevel[];
|
|
1108
|
+
inventoryItemsCount: number;
|
|
1109
|
+
inventoryStats: ApiProductStockSummary;
|
|
1110
|
+
}>(graphQLOptions, PRODUCT_STOCK_LEVELS_QUERY, {
|
|
1111
|
+
cargoItemId: productId,
|
|
1112
|
+
pagination: { limit, offset },
|
|
1113
|
+
});
|
|
1003
1114
|
setLevels(
|
|
1004
|
-
data.inventoryItems.map((item) => ({
|
|
1115
|
+
(data.inventoryItems ?? []).map((item) => ({
|
|
1005
1116
|
inventoryItemId: item.id,
|
|
1006
1117
|
warehouseId: item.warehouse?.id ?? '',
|
|
1007
1118
|
warehouseName: item.warehouse?.name ?? 'Unknown warehouse',
|
|
@@ -1013,12 +1124,14 @@ export function useProductStockLevels(productId: string | undefined) {
|
|
|
1013
1124
|
reorderPoint: item.reorderPoint ?? undefined,
|
|
1014
1125
|
}))
|
|
1015
1126
|
);
|
|
1127
|
+
setTotalLevels(data.inventoryItemsCount ?? 0);
|
|
1128
|
+
setTotalAvailable(data.inventoryStats?.totalAvailable ?? 0);
|
|
1016
1129
|
} catch (fetchError) {
|
|
1017
1130
|
setError(fetchError instanceof Error ? fetchError : new Error('Failed to load stock levels'));
|
|
1018
1131
|
} finally {
|
|
1019
1132
|
setIsLoading(false);
|
|
1020
1133
|
}
|
|
1021
|
-
}, [graphQLOptions, productId]);
|
|
1134
|
+
}, [graphQLOptions, limit, offset, productId]);
|
|
1022
1135
|
|
|
1023
1136
|
useEffect(() => {
|
|
1024
1137
|
void load();
|
|
@@ -1028,7 +1141,15 @@ export function useProductStockLevels(productId: string | undefined) {
|
|
|
1028
1141
|
void load();
|
|
1029
1142
|
}, [load]);
|
|
1030
1143
|
|
|
1031
|
-
return {
|
|
1144
|
+
return {
|
|
1145
|
+
levels,
|
|
1146
|
+
totalLevels,
|
|
1147
|
+
totalAvailable,
|
|
1148
|
+
hasMore: offset + levels.length < totalLevels,
|
|
1149
|
+
isLoading,
|
|
1150
|
+
error,
|
|
1151
|
+
refetch,
|
|
1152
|
+
};
|
|
1032
1153
|
}
|
|
1033
1154
|
|
|
1034
1155
|
export type InventoryTransactionType = 'RECEIPT' | 'ISSUE' | 'TRANSFER' | 'ADJUSTMENT' | 'RETURN';
|
|
@@ -12,12 +12,7 @@ import { executeGraphQL } from '../utils/graphqlClient';
|
|
|
12
12
|
|
|
13
13
|
export type TransportMode = 'ROAD' | 'AIR' | 'OCEAN' | 'RAIL';
|
|
14
14
|
export type ShipmentLegStatus =
|
|
15
|
-
| '
|
|
16
|
-
| 'BOOKED'
|
|
17
|
-
| 'IN_TRANSIT'
|
|
18
|
-
| 'ARRIVED'
|
|
19
|
-
| 'DELIVERED'
|
|
20
|
-
| 'CANCELLED';
|
|
15
|
+
'PLANNED' | 'BOOKED' | 'IN_TRANSIT' | 'ARRIVED' | 'DELIVERED' | 'CANCELLED';
|
|
21
16
|
export type OceanContainerType = 'LCL' | 'FCL_20FT' | 'FCL_40FT' | 'FCL_40FT_HC';
|
|
22
17
|
|
|
23
18
|
export interface FreightRateBreakdownLine {
|
|
@@ -139,7 +134,7 @@ const SHIPMENT_FIELDS = `
|
|
|
139
134
|
origin
|
|
140
135
|
destination
|
|
141
136
|
createdAt
|
|
142
|
-
legs {
|
|
137
|
+
legs(pagination: { limit: 20, offset: 0 }) {
|
|
143
138
|
${LEG_FIELDS}
|
|
144
139
|
}
|
|
145
140
|
`;
|
|
@@ -149,6 +144,7 @@ const MULTI_MODAL_SHIPMENTS_QUERY = `
|
|
|
149
144
|
shipments(filter: { isMultiModal: true }, pagination: $pagination) {
|
|
150
145
|
${SHIPMENT_FIELDS}
|
|
151
146
|
}
|
|
147
|
+
shipmentsCount(filter: { isMultiModal: true })
|
|
152
148
|
}
|
|
153
149
|
`;
|
|
154
150
|
|
|
@@ -269,9 +265,12 @@ function useGraphqlOptions() {
|
|
|
269
265
|
);
|
|
270
266
|
}
|
|
271
267
|
|
|
272
|
-
export function useMultiModalShipments() {
|
|
268
|
+
export function useMultiModalShipments(options: { limit?: number; offset?: number } = {}) {
|
|
269
|
+
const { limit = 10, offset = 0 } = options;
|
|
273
270
|
const graphQLOptions = useGraphqlOptions();
|
|
274
271
|
const [shipments, setShipments] = useState<MultiModalShipment[]>([]);
|
|
272
|
+
const [totalShipments, setTotalShipments] = useState(0);
|
|
273
|
+
const [hasMore, setHasMore] = useState(false);
|
|
275
274
|
const [isLoading, setIsLoading] = useState(false);
|
|
276
275
|
const [error, setError] = useState<Error | null>(null);
|
|
277
276
|
|
|
@@ -279,12 +278,14 @@ export function useMultiModalShipments() {
|
|
|
279
278
|
setIsLoading(true);
|
|
280
279
|
setError(null);
|
|
281
280
|
try {
|
|
282
|
-
const data = await executeGraphQL<{ shipments: ApiShipment[] }>(
|
|
281
|
+
const data = await executeGraphQL<{ shipments: ApiShipment[]; shipmentsCount: number }>(
|
|
283
282
|
graphQLOptions,
|
|
284
283
|
MULTI_MODAL_SHIPMENTS_QUERY,
|
|
285
|
-
{ pagination: { limit
|
|
284
|
+
{ pagination: { limit, offset } }
|
|
286
285
|
);
|
|
287
286
|
setShipments(data.shipments.map(mapShipment));
|
|
287
|
+
setTotalShipments(data.shipmentsCount);
|
|
288
|
+
setHasMore(offset + data.shipments.length < data.shipmentsCount);
|
|
288
289
|
} catch (fetchError) {
|
|
289
290
|
setError(
|
|
290
291
|
fetchError instanceof Error ? fetchError : new Error('Failed to load multi-modal shipments')
|
|
@@ -292,7 +293,7 @@ export function useMultiModalShipments() {
|
|
|
292
293
|
} finally {
|
|
293
294
|
setIsLoading(false);
|
|
294
295
|
}
|
|
295
|
-
}, [graphQLOptions]);
|
|
296
|
+
}, [graphQLOptions, limit, offset]);
|
|
296
297
|
|
|
297
298
|
useEffect(() => {
|
|
298
299
|
void load();
|
|
@@ -333,6 +334,7 @@ export function useMultiModalShipments() {
|
|
|
333
334
|
);
|
|
334
335
|
const created = mapShipment(data.planMultiModalShipment);
|
|
335
336
|
setShipments((previous) => [created, ...previous]);
|
|
337
|
+
setTotalShipments((previous) => previous + 1);
|
|
336
338
|
return created;
|
|
337
339
|
},
|
|
338
340
|
[graphQLOptions]
|
|
@@ -340,6 +342,8 @@ export function useMultiModalShipments() {
|
|
|
340
342
|
|
|
341
343
|
return {
|
|
342
344
|
shipments,
|
|
345
|
+
totalShipments,
|
|
346
|
+
hasMore,
|
|
343
347
|
isLoading,
|
|
344
348
|
error,
|
|
345
349
|
refetch: () => void load(),
|