@burdenoff/microfe-movethewheels 2026.822.1 → 2026.827.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.
Files changed (53) hide show
  1. package/dist/generated/wspace-operations.d.ts +168 -912
  2. package/dist/generated/wspace-types.d.ts +78432 -35831
  3. package/dist/hooks/useFleet.js +142 -74
  4. package/dist/hooks/useInventory.js +163 -70
  5. package/dist/hooks/useOrders.js +2 -2
  6. package/dist/hooks/useTracking.js +5 -5
  7. package/dist/index.js +33 -33
  8. package/dist/movethewheels/hooks/index.d.ts +2 -2
  9. package/dist/movethewheels/hooks/useFleet.d.ts +32 -1
  10. package/dist/movethewheels/hooks/useInventory.d.ts +67 -12
  11. package/dist/movethewheels/hooks/useOrders.d.ts +25 -2
  12. package/dist/movethewheels/types/index.d.ts +2 -0
  13. package/dist/node_modules/lucide-react/dist/esm/icons/arrow-down-right.js +11 -0
  14. package/dist/node_modules/lucide-react/dist/esm/icons/arrow-left-right.js +22 -0
  15. package/dist/node_modules/lucide-react/dist/esm/icons/arrow-up-right.js +11 -0
  16. package/dist/node_modules/lucide-react/dist/esm/icons/calendar-clock.js +32 -0
  17. package/dist/node_modules/lucide-react/dist/esm/icons/history.js +18 -0
  18. package/dist/node_modules/lucide-react/dist/esm/icons/lock.js +16 -0
  19. package/dist/node_modules/lucide-react/dist/esm/icons/user-round.js +13 -0
  20. package/dist/pages/CreateOrderPage.js +246 -195
  21. package/dist/pages/DeliveryCapturePage.js +91 -78
  22. package/dist/pages/DriversPage.js +202 -108
  23. package/dist/pages/InventoryPage.js +356 -131
  24. package/dist/pages/ProductDetailsPage.js +212 -156
  25. package/dist/pages/VehicleDetailsPage.js +257 -193
  26. package/dist/utils/formatters.js +4 -1
  27. package/package.json +1 -1
  28. package/src/generated/wspace-operations.ts +134 -929
  29. package/src/generated/wspace-types.ts +91730 -40059
  30. package/src/movethewheels/hooks/index.ts +9 -0
  31. package/src/movethewheels/hooks/useFleet.ts +133 -3
  32. package/src/movethewheels/hooks/useInventory.ts +311 -53
  33. package/src/movethewheels/hooks/useOrders.ts +31 -7
  34. package/src/movethewheels/hooks/useTracking.ts +14 -14
  35. package/src/movethewheels/pages/CreateOrderPage.tsx +67 -10
  36. package/src/movethewheels/pages/DeliveryCapturePage.tsx +8 -0
  37. package/src/movethewheels/pages/DriversPage.tsx +181 -31
  38. package/src/movethewheels/pages/InventoryPage.tsx +299 -19
  39. package/src/movethewheels/pages/ProductDetailsPage.tsx +68 -3
  40. package/src/movethewheels/pages/VehicleDetailsPage.tsx +86 -27
  41. package/src/movethewheels/types/index.ts +2 -0
  42. package/src/operations/wspace/movethewheels/fragments/core.graphql +23 -53
  43. package/src/operations/wspace/movethewheels/fragments/core.withdrawn.graphql +59 -0
  44. package/src/operations/wspace/movethewheels/mutations/entities.graphql +5 -5
  45. package/src/operations/wspace/movethewheels/mutations/marketplace-reports.graphql +18 -78
  46. package/src/operations/wspace/movethewheels/mutations/marketplace-reports.withdrawn.graphql +93 -0
  47. package/src/operations/wspace/movethewheels/mutations/orders.graphql +7 -7
  48. package/src/operations/wspace/movethewheels/queries/dashboard.graphql +3 -3
  49. package/src/operations/wspace/movethewheels/queries/entities.graphql +3 -3
  50. package/src/operations/wspace/movethewheels/queries/logistics.graphql +3 -3
  51. package/src/operations/wspace/movethewheels/queries/marketplace-reports.withdrawn.graphql +58 -0
  52. package/src/operations/wspace/movethewheels/queries/orders.graphql +3 -3
  53. package/src/operations/wspace/movethewheels/queries/marketplace-reports.graphql +0 -40
@@ -51,9 +51,15 @@ interface UseInventoryResult {
51
51
  updateWarehouse: (id: string, input: CreateWarehouseInput) => Promise<Warehouse>;
52
52
  }
53
53
 
54
+ // Wire shape of MtwInventoryItem (wspace-movethewheels-svc:
55
+ // src/modules/inventory/schema/inventory.gql). The catalog domain was renamed
56
+ // Product -> MtwCargoItem on 2026-07-07 (commit 7e99ec5): the root fields are
57
+ // `cargoItems`/`cargoItem`/`createCargoItem`/`updateCargoItem` and the inventory
58
+ // link column is `cargoItemId`. The MFE keeps its own `Product`/`Inventory.productId`
59
+ // domain names; the rename is absorbed in the mappers below.
54
60
  interface ApiInventoryItem {
55
61
  id: string;
56
- productId: string;
62
+ cargoItemId: string;
57
63
  warehouseId: string;
58
64
  onHand: number;
59
65
  available: number;
@@ -96,7 +102,7 @@ const LIST_INVENTORY_QUERY = `
96
102
  query ListInventoryItems($filter: InventoryFilterInput, $pagination: PaginationInput) {
97
103
  inventoryItems(filter: $filter, pagination: $pagination) {
98
104
  id
99
- productId
105
+ cargoItemId
100
106
  warehouseId
101
107
  onHand
102
108
  available
@@ -110,8 +116,8 @@ const LIST_INVENTORY_QUERY = `
110
116
  `;
111
117
 
112
118
  const LIST_PRODUCTS_QUERY = `
113
- query ListProducts($filter: ProductFilterInput, $pagination: PaginationInput) {
114
- products(filter: $filter, pagination: $pagination) {
119
+ query ListCargoItems($filter: CargoItemFilterInput, $pagination: PaginationInput) {
120
+ cargoItems(filter: $filter, pagination: $pagination) {
115
121
  id
116
122
  sku
117
123
  name
@@ -146,8 +152,8 @@ const LIST_WAREHOUSES_QUERY = `
146
152
  `;
147
153
 
148
154
  const GET_PRODUCT_QUERY = `
149
- query GetProduct($id: ID!) {
150
- product(id: $id) {
155
+ query GetCargoItem($id: ID!) {
156
+ cargoItem(id: $id) {
151
157
  id
152
158
  sku
153
159
  name
@@ -190,12 +196,36 @@ const INVENTORY_STATS_QUERY = `
190
196
  totalDamaged
191
197
  lowStockCount
192
198
  }
199
+ cargoItemsCount
200
+ warehousesCount
201
+ }
202
+ `;
203
+
204
+ /**
205
+ * Stock ledger for the workspace, newest first (the service orders
206
+ * `inventoryTransactions` by `createdAt: desc`). `InventoryFilterInput` accepts
207
+ * `cargoItemId` / `warehouseId`, so the same query backs a per-item ledger.
208
+ */
209
+ const LIST_INVENTORY_TRANSACTIONS_QUERY = `
210
+ query ListInventoryTransactions($filter: InventoryFilterInput, $pagination: PaginationInput) {
211
+ inventoryTransactions(filter: $filter, pagination: $pagination) {
212
+ id
213
+ transactionNumber
214
+ type
215
+ quantity
216
+ cargoItemId
217
+ warehouseId
218
+ notes
219
+ referenceType
220
+ referenceId
221
+ createdAt
222
+ }
193
223
  }
194
224
  `;
195
225
 
196
226
  const CREATE_PRODUCT_MUTATION = `
197
- mutation CreateProduct($input: CreateProductInput!) {
198
- createProduct(input: $input) {
227
+ mutation CreateCargoItem($input: CreateCargoItemInput!) {
228
+ createCargoItem(input: $input) {
199
229
  id
200
230
  sku
201
231
  name
@@ -215,8 +245,8 @@ const CREATE_PRODUCT_MUTATION = `
215
245
  `;
216
246
 
217
247
  const UPDATE_PRODUCT_MUTATION = `
218
- mutation UpdateProduct($id: ID!, $input: UpdateProductInput!) {
219
- updateProduct(id: $id, input: $input) {
248
+ mutation UpdateCargoItem($id: ID!, $input: UpdateCargoItemInput!) {
249
+ updateCargoItem(id: $id, input: $input) {
220
250
  id
221
251
  sku
222
252
  name
@@ -269,7 +299,7 @@ const ADJUST_INVENTORY_MUTATION = `
269
299
  mutation AdjustInventoryStock($input: AdjustInventoryStockInput!) {
270
300
  adjustInventoryStock(input: $input) {
271
301
  id
272
- productId
302
+ cargoItemId
273
303
  warehouseId
274
304
  onHand
275
305
  available
@@ -355,7 +385,7 @@ function mapWarehouse(warehouse: ApiWarehouse): Warehouse {
355
385
  function mapInventory(item: ApiInventoryItem): Inventory {
356
386
  return {
357
387
  id: item.id,
358
- productId: item.productId,
388
+ productId: item.cargoItemId,
359
389
  warehouseId: item.warehouseId,
360
390
  zoneId: '',
361
391
  quantity: item.onHand,
@@ -419,12 +449,12 @@ export function useInventory(options: UseInventoryOptions = {}): UseInventoryRes
419
449
  {
420
450
  filter: {
421
451
  warehouseId: filters?.warehouseId,
422
- productId: filters?.productId,
452
+ cargoItemId: filters?.productId,
423
453
  },
424
454
  pagination: { limit: 500, offset: 0 },
425
455
  }
426
456
  ),
427
- executeGraphQL<{ products: ApiProduct[] }>(graphQLOptions, LIST_PRODUCTS_QUERY, {
457
+ executeGraphQL<{ cargoItems: ApiProduct[] }>(graphQLOptions, LIST_PRODUCTS_QUERY, {
428
458
  pagination: { limit: 500, offset: 0 },
429
459
  }),
430
460
  executeGraphQL<{ warehouses: ApiWarehouse[] }>(graphQLOptions, LIST_WAREHOUSES_QUERY, {
@@ -438,7 +468,7 @@ export function useInventory(options: UseInventoryOptions = {}): UseInventoryRes
438
468
  }
439
469
 
440
470
  setInventory(nextInventory);
441
- setProducts(productData.products.map(mapProduct));
471
+ setProducts(productData.cargoItems.map(mapProduct));
442
472
  setWarehouses(warehouseData.warehouses.map(mapWarehouse));
443
473
  } catch (fetchError) {
444
474
  setError(
@@ -515,7 +545,7 @@ export function useInventory(options: UseInventoryOptions = {}): UseInventoryRes
515
545
 
516
546
  const createProduct = useCallback(
517
547
  async (input: CreateProductInput): Promise<Product> => {
518
- const data = await executeGraphQL<{ createProduct: ApiProduct }>(
548
+ const data = await executeGraphQL<{ createCargoItem: ApiProduct }>(
519
549
  graphQLOptions,
520
550
  CREATE_PRODUCT_MUTATION,
521
551
  {
@@ -528,7 +558,7 @@ export function useInventory(options: UseInventoryOptions = {}): UseInventoryRes
528
558
  },
529
559
  }
530
560
  );
531
- const created = mapProduct(data.createProduct);
561
+ const created = mapProduct(data.createCargoItem);
532
562
  setProducts((previous) => [created, ...previous]);
533
563
  return created;
534
564
  },
@@ -559,7 +589,7 @@ export function useInventory(options: UseInventoryOptions = {}): UseInventoryRes
559
589
 
560
590
  const updateProduct = useCallback(
561
591
  async (id: string, input: CreateProductInput): Promise<Product> => {
562
- const data = await executeGraphQL<{ updateProduct: ApiProduct }>(
592
+ const data = await executeGraphQL<{ updateCargoItem: ApiProduct }>(
563
593
  graphQLOptions,
564
594
  UPDATE_PRODUCT_MUTATION,
565
595
  {
@@ -573,7 +603,7 @@ export function useInventory(options: UseInventoryOptions = {}): UseInventoryRes
573
603
  },
574
604
  }
575
605
  );
576
- const updated = mapProduct(data.updateProduct);
606
+ const updated = mapProduct(data.updateCargoItem);
577
607
  setProducts((previous) => previous.map((item) => (item.id === id ? updated : item)));
578
608
  return updated;
579
609
  },
@@ -637,15 +667,15 @@ export function useProducts(options: UseListOptions = {}) {
637
667
  setIsLoading(true);
638
668
  setError(null);
639
669
  try {
640
- const data = await executeGraphQL<{ products: ApiProduct[] }>(
670
+ const data = await executeGraphQL<{ cargoItems: ApiProduct[] }>(
641
671
  graphQLOptions,
642
672
  LIST_PRODUCTS_QUERY,
643
673
  {
644
674
  pagination: { limit, offset },
645
675
  }
646
676
  );
647
- setHasMore(data.products.length === limit);
648
- setProducts(data.products.map(mapProduct));
677
+ setHasMore(data.cargoItems.length === limit);
678
+ setProducts(data.cargoItems.map(mapProduct));
649
679
  } catch (fetchError) {
650
680
  setError(fetchError instanceof Error ? fetchError : new Error('Failed to load products'));
651
681
  } finally {
@@ -701,14 +731,14 @@ export function useProduct(productId: string | undefined) {
701
731
  setIsLoading(true);
702
732
  setError(null);
703
733
  try {
704
- const data = await executeGraphQL<{ product: ApiProduct | null }>(
734
+ const data = await executeGraphQL<{ cargoItem: ApiProduct | null }>(
705
735
  graphQLOptions,
706
736
  GET_PRODUCT_QUERY,
707
737
  {
708
738
  id: productId,
709
739
  }
710
740
  );
711
- setProduct(data.product ? mapProduct(data.product) : null);
741
+ setProduct(data.cargoItem ? mapProduct(data.cargoItem) : null);
712
742
  } catch (fetchError) {
713
743
  setError(fetchError instanceof Error ? fetchError : new Error('Failed to load product'));
714
744
  } finally {
@@ -827,15 +857,39 @@ export function useWarehouse(warehouseId: string | undefined) {
827
857
  return { warehouse, isLoading, error, refetch: () => void load() };
828
858
  }
829
859
 
860
+ export interface InventoryStats {
861
+ /** Units physically in the warehouses. */
862
+ totalOnHand: number;
863
+ /** Units free to promise on a new order. */
864
+ totalAvailable: number;
865
+ /** Units already committed to open orders. */
866
+ totalAllocated: number;
867
+ /** Units written off as damaged. */
868
+ totalDamaged: number;
869
+ /** Stock lines at or under their reorder point. */
870
+ lowStockCount: number;
871
+ /** Catalog size (`cargoItemsCount`), workspace-wide — not a page length. */
872
+ totalProducts: number;
873
+ /** Warehouse count (`warehousesCount`), workspace-wide. */
874
+ totalWarehouses: number;
875
+ }
876
+
877
+ /**
878
+ * Workspace-wide inventory aggregates, every figure served by the module:
879
+ * `inventoryStats` for the stock position, `cargoItemsCount`/`warehousesCount`
880
+ * for catalog and network size. There is no aggregate stock-value field on the
881
+ * subgraph, so no monetary total is reported here.
882
+ */
830
883
  export function useInventoryStats() {
831
884
  const graphQLOptions = useGraphqlOptions();
832
- const [stats, setStats] = useState({
833
- totalItems: 0,
834
- lowStockItems: 0,
835
- totalValue: 0,
885
+ const [stats, setStats] = useState<InventoryStats>({
886
+ totalOnHand: 0,
887
+ totalAvailable: 0,
888
+ totalAllocated: 0,
889
+ totalDamaged: 0,
890
+ lowStockCount: 0,
836
891
  totalProducts: 0,
837
892
  totalWarehouses: 0,
838
- warehouseUtilization: [] as Array<{ id: string; name: string; utilization: number }>,
839
893
  });
840
894
  const [isLoading, setIsLoading] = useState(false);
841
895
  const [error, setError] = useState<Error | null>(null);
@@ -845,32 +899,26 @@ export function useInventoryStats() {
845
899
  setIsLoading(true);
846
900
  setError(null);
847
901
  try {
848
- const [inventoryStats, warehouses] = await Promise.all([
849
- executeGraphQL<{
850
- inventoryStats: {
851
- totalOnHand: number;
852
- lowStockCount: number;
853
- };
854
- }>(graphQLOptions, INVENTORY_STATS_QUERY),
855
- executeGraphQL<{ warehouses: ApiWarehouse[] }>(graphQLOptions, LIST_WAREHOUSES_QUERY, {
856
- pagination: { limit: 500, offset: 0 },
857
- }),
858
- ]);
902
+ const data = await executeGraphQL<{
903
+ inventoryStats: {
904
+ totalOnHand: number;
905
+ totalAvailable: number;
906
+ totalAllocated: number;
907
+ totalDamaged: number;
908
+ lowStockCount: number;
909
+ };
910
+ cargoItemsCount: number;
911
+ warehousesCount: number;
912
+ }>(graphQLOptions, INVENTORY_STATS_QUERY);
859
913
 
860
914
  setStats({
861
- totalItems: inventoryStats.inventoryStats.totalOnHand,
862
- lowStockItems: inventoryStats.inventoryStats.lowStockCount,
863
- totalValue: 0,
864
- totalProducts: 0,
865
- totalWarehouses: warehouses.warehouses.length,
866
- warehouseUtilization: warehouses.warehouses.map((warehouse) => ({
867
- id: warehouse.id,
868
- name: warehouse.name,
869
- utilization:
870
- (warehouse.totalCapacity ?? 0) > 0
871
- ? ((warehouse.usedCapacity ?? 0) / (warehouse.totalCapacity ?? 1)) * 100
872
- : 0,
873
- })),
915
+ totalOnHand: data.inventoryStats.totalOnHand,
916
+ totalAvailable: data.inventoryStats.totalAvailable,
917
+ totalAllocated: data.inventoryStats.totalAllocated,
918
+ totalDamaged: data.inventoryStats.totalDamaged,
919
+ lowStockCount: data.inventoryStats.lowStockCount,
920
+ totalProducts: data.cargoItemsCount,
921
+ totalWarehouses: data.warehousesCount,
874
922
  });
875
923
  } catch (fetchError) {
876
924
  setError(
@@ -886,3 +934,213 @@ export function useInventoryStats() {
886
934
 
887
935
  return { stats, isLoading, error };
888
936
  }
937
+
938
+ /**
939
+ * Per-warehouse stock for one catalog item. Uses the nested
940
+ * `MtwInventoryItem.warehouse` resolver so the warehouse name comes back with
941
+ * the row instead of being joined against a separately-paged warehouse list.
942
+ */
943
+ const PRODUCT_STOCK_LEVELS_QUERY = `
944
+ query GetCargoItemStockLevels($cargoItemId: ID!) {
945
+ inventoryItems(filter: { cargoItemId: $cargoItemId }, pagination: { limit: 200, offset: 0 }) {
946
+ id
947
+ onHand
948
+ available
949
+ allocated
950
+ damaged
951
+ reorderPoint
952
+ warehouse {
953
+ id
954
+ name
955
+ code
956
+ }
957
+ }
958
+ }
959
+ `;
960
+
961
+ export interface ProductStockLevel {
962
+ inventoryItemId: string;
963
+ warehouseId: string;
964
+ warehouseName: string;
965
+ warehouseCode: string;
966
+ onHand: number;
967
+ available: number;
968
+ allocated: number;
969
+ damaged: number;
970
+ reorderPoint?: number;
971
+ }
972
+
973
+ interface ApiProductStockLevel {
974
+ id: string;
975
+ onHand: number;
976
+ available: number;
977
+ allocated: number;
978
+ damaged: number;
979
+ reorderPoint?: number | null;
980
+ warehouse?: { id: string; name: string; code: string } | null;
981
+ }
982
+
983
+ /** Where a catalog item is stocked, and how much of it is free to promise. */
984
+ export function useProductStockLevels(productId: string | undefined) {
985
+ const graphQLOptions = useGraphqlOptions();
986
+ const [levels, setLevels] = useState<ProductStockLevel[]>([]);
987
+ const [isLoading, setIsLoading] = useState(false);
988
+ const [error, setError] = useState<Error | null>(null);
989
+
990
+ const load = useCallback(async () => {
991
+ if (!productId) {
992
+ setLevels([]);
993
+ return;
994
+ }
995
+ setIsLoading(true);
996
+ setError(null);
997
+ try {
998
+ const data = await executeGraphQL<{ inventoryItems: ApiProductStockLevel[] }>(
999
+ graphQLOptions,
1000
+ PRODUCT_STOCK_LEVELS_QUERY,
1001
+ { cargoItemId: productId }
1002
+ );
1003
+ setLevels(
1004
+ data.inventoryItems.map((item) => ({
1005
+ inventoryItemId: item.id,
1006
+ warehouseId: item.warehouse?.id ?? '',
1007
+ warehouseName: item.warehouse?.name ?? 'Unknown warehouse',
1008
+ warehouseCode: item.warehouse?.code ?? '',
1009
+ onHand: item.onHand,
1010
+ available: item.available,
1011
+ allocated: item.allocated,
1012
+ damaged: item.damaged,
1013
+ reorderPoint: item.reorderPoint ?? undefined,
1014
+ }))
1015
+ );
1016
+ } catch (fetchError) {
1017
+ setError(fetchError instanceof Error ? fetchError : new Error('Failed to load stock levels'));
1018
+ } finally {
1019
+ setIsLoading(false);
1020
+ }
1021
+ }, [graphQLOptions, productId]);
1022
+
1023
+ useEffect(() => {
1024
+ void load();
1025
+ }, [load]);
1026
+
1027
+ const refetch = useCallback(() => {
1028
+ void load();
1029
+ }, [load]);
1030
+
1031
+ return { levels, isLoading, error, refetch };
1032
+ }
1033
+
1034
+ export type InventoryTransactionType = 'RECEIPT' | 'ISSUE' | 'TRANSFER' | 'ADJUSTMENT' | 'RETURN';
1035
+
1036
+ export interface InventoryTransaction {
1037
+ id: string;
1038
+ transactionNumber: string;
1039
+ type: InventoryTransactionType;
1040
+ /** Signed by `type`: receipts/returns add stock, issues remove it. */
1041
+ quantity: number;
1042
+ productId: string;
1043
+ warehouseId: string;
1044
+ notes?: string;
1045
+ referenceType?: string;
1046
+ referenceId?: string;
1047
+ createdAt: Date;
1048
+ }
1049
+
1050
+ interface ApiInventoryTransaction {
1051
+ id: string;
1052
+ transactionNumber: string;
1053
+ type: string;
1054
+ quantity: number;
1055
+ cargoItemId: string;
1056
+ warehouseId: string;
1057
+ notes?: string | null;
1058
+ referenceType?: string | null;
1059
+ referenceId?: string | null;
1060
+ createdAt: string;
1061
+ }
1062
+
1063
+ const TRANSACTION_TYPES: InventoryTransactionType[] = [
1064
+ 'RECEIPT',
1065
+ 'ISSUE',
1066
+ 'TRANSFER',
1067
+ 'ADJUSTMENT',
1068
+ 'RETURN',
1069
+ ];
1070
+
1071
+ /**
1072
+ * The service returns `InventoryTransactionType` in upper snake case, but the
1073
+ * value is normalised here rather than trusted, so an unexpected casing renders
1074
+ * as ADJUSTMENT-neutral text instead of a broken label.
1075
+ */
1076
+ function mapTransactionType(type: string): InventoryTransactionType {
1077
+ const normalized = type.toUpperCase();
1078
+ return (
1079
+ TRANSACTION_TYPES.find((candidate) => candidate === normalized) ??
1080
+ ('ADJUSTMENT' as InventoryTransactionType)
1081
+ );
1082
+ }
1083
+
1084
+ function mapTransaction(transaction: ApiInventoryTransaction): InventoryTransaction {
1085
+ return {
1086
+ id: transaction.id,
1087
+ transactionNumber: transaction.transactionNumber,
1088
+ type: mapTransactionType(transaction.type),
1089
+ quantity: transaction.quantity,
1090
+ productId: transaction.cargoItemId,
1091
+ warehouseId: transaction.warehouseId,
1092
+ notes: transaction.notes ?? undefined,
1093
+ referenceType: transaction.referenceType ?? undefined,
1094
+ referenceId: transaction.referenceId ?? undefined,
1095
+ createdAt: new Date(transaction.createdAt),
1096
+ };
1097
+ }
1098
+
1099
+ interface UseInventoryTransactionsOptions {
1100
+ limit?: number;
1101
+ productId?: string;
1102
+ warehouseId?: string;
1103
+ }
1104
+
1105
+ /** Recent stock movements from `Query.inventoryTransactions` (newest first). */
1106
+ export function useInventoryTransactions(options: UseInventoryTransactionsOptions = {}) {
1107
+ const { limit = 10, productId, warehouseId } = options;
1108
+ const graphQLOptions = useGraphqlOptions();
1109
+ const [transactions, setTransactions] = useState<InventoryTransaction[]>([]);
1110
+ const [isLoading, setIsLoading] = useState(false);
1111
+ const [error, setError] = useState<Error | null>(null);
1112
+
1113
+ const load = useCallback(async () => {
1114
+ setIsLoading(true);
1115
+ setError(null);
1116
+ try {
1117
+ const data = await executeGraphQL<{ inventoryTransactions: ApiInventoryTransaction[] }>(
1118
+ graphQLOptions,
1119
+ LIST_INVENTORY_TRANSACTIONS_QUERY,
1120
+ {
1121
+ filter: productId || warehouseId ? { cargoItemId: productId, warehouseId } : undefined,
1122
+ pagination: { limit, offset: 0 },
1123
+ }
1124
+ );
1125
+ setTransactions(data.inventoryTransactions.map(mapTransaction));
1126
+ } catch (fetchError) {
1127
+ setError(
1128
+ fetchError instanceof Error
1129
+ ? fetchError
1130
+ : new Error('Failed to load inventory transactions')
1131
+ );
1132
+ } finally {
1133
+ setIsLoading(false);
1134
+ }
1135
+ }, [graphQLOptions, limit, productId, warehouseId]);
1136
+
1137
+ useEffect(() => {
1138
+ void load();
1139
+ }, [load]);
1140
+
1141
+ const refetch = useCallback(() => {
1142
+ void load();
1143
+ }, [load]);
1144
+
1145
+ return { transactions, isLoading, error, refetch };
1146
+ }
@@ -9,6 +9,17 @@ interface UseOrdersOptions {
9
9
  offset?: number;
10
10
  }
11
11
 
12
+ /**
13
+ * Write shape for create/update. Identical to `Partial<Order>` except that
14
+ * `scheduledDelivery` also accepts `null`: `UpdateDeliveryJobInput.scheduledDelivery`
15
+ * is nullable on the service, so an explicit `null` clears a previously booked
16
+ * slot, while `undefined` leaves it untouched. Without the distinction, clearing
17
+ * the field in the form would silently keep the old date.
18
+ */
19
+ export type OrderWriteInput = Omit<Partial<Order>, 'scheduledDelivery'> & {
20
+ scheduledDelivery?: Date | null;
21
+ };
22
+
12
23
  interface UseOrdersResult {
13
24
  orders: Order[];
14
25
  totalOrders: number;
@@ -21,8 +32,8 @@ interface UseOrdersResult {
21
32
  isLoading: boolean;
22
33
  error: Error | null;
23
34
  refetch: () => void;
24
- createOrder: (order: Partial<Order>) => Promise<Order>;
25
- updateOrder: (id: string, updates: Partial<Order>) => Promise<Order>;
35
+ createOrder: (order: OrderWriteInput) => Promise<Order>;
36
+ updateOrder: (id: string, updates: OrderWriteInput) => Promise<Order>;
26
37
  deleteOrder: (id: string) => Promise<void>;
27
38
  updateOrderStatus: (id: string, status: OrderStatus) => Promise<Order>;
28
39
  }
@@ -160,7 +171,13 @@ const DELETE_ORDER_MUTATION = `
160
171
  }
161
172
  `;
162
173
 
163
- function mapOrderStatus(status: string): OrderStatus {
174
+ /**
175
+ * Normalises a service `DeliveryJobStatus` into the MFE's OrderStatus union.
176
+ * Exported so any surface rendering a raw job status badge normalises it the
177
+ * same way — the service casing is upper snake case and would miss the badge
178
+ * colour map otherwise.
179
+ */
180
+ export function mapOrderStatus(status: string): OrderStatus {
164
181
  switch (status) {
165
182
  case 'PENDING':
166
183
  return 'pending';
@@ -183,7 +200,12 @@ function mapOrderStatus(status: string): OrderStatus {
183
200
  }
184
201
  }
185
202
 
186
- function mapOrderPriority(priority: string): 'low' | 'normal' | 'high' | 'urgent' {
203
+ /**
204
+ * Normalises a service `DeliveryPriority` (LOW/MEDIUM/HIGH/URGENT/CRITICAL)
205
+ * into the four-level union PriorityBadge understands. MEDIUM and CRITICAL have
206
+ * no direct badge entry, so rendering the raw value would break the colour map.
207
+ */
208
+ export function mapOrderPriority(priority: string): 'low' | 'normal' | 'high' | 'urgent' {
187
209
  switch (priority) {
188
210
  case 'LOW':
189
211
  return 'low';
@@ -366,7 +388,7 @@ export function useOrders(options: UseOrdersOptions = {}): UseOrdersResult {
366
388
  }, [loadOrders]);
367
389
 
368
390
  const createOrder = useCallback(
369
- async (orderData: Partial<Order>): Promise<Order> => {
391
+ async (orderData: OrderWriteInput): Promise<Order> => {
370
392
  const deliveryJobNumber =
371
393
  orderData.orderNumber ??
372
394
  `ORD-${new Date().getFullYear()}-${Date.now().toString().slice(-6)}`;
@@ -399,7 +421,7 @@ export function useOrders(options: UseOrdersOptions = {}): UseOrdersResult {
399
421
  );
400
422
 
401
423
  const updateOrder = useCallback(
402
- async (id: string, updates: Partial<Order>): Promise<Order> => {
424
+ async (id: string, updates: OrderWriteInput): Promise<Order> => {
403
425
  const data = await executeGraphQL<{
404
426
  updateDeliveryJob: ApiOrder;
405
427
  }>(graphQLOptions, UPDATE_ORDER_MUTATION, {
@@ -414,7 +436,9 @@ export function useOrders(options: UseOrdersOptions = {}): UseOrdersResult {
414
436
  pricing: updates.pricing,
415
437
  specialInstructions: updates.specialInstructions,
416
438
  trackingNumber: updates.trackingNumber,
417
- scheduledDelivery: updates.scheduledDelivery?.toISOString(),
439
+ // `null` clears the booked slot; `undefined` leaves it untouched.
440
+ scheduledDelivery:
441
+ updates.scheduledDelivery === null ? null : updates.scheduledDelivery?.toISOString(),
418
442
  vehicleId: updates.vehicleId,
419
443
  driverId: updates.driverId,
420
444
  },
@@ -30,7 +30,7 @@ interface ApiTrackingEvent {
30
30
  interface ApiShipment {
31
31
  id: string;
32
32
  shipmentNumber: string;
33
- orderId: string;
33
+ deliveryJobId?: string | null;
34
34
  status: string;
35
35
  estimatedDelivery?: string | null;
36
36
  trackingEvents?: ApiTrackingEvent[];
@@ -41,7 +41,7 @@ const SHIPMENTS_QUERY = `
41
41
  shipments(filter: $filter, pagination: $pagination) {
42
42
  id
43
43
  shipmentNumber
44
- orderId
44
+ deliveryJobId
45
45
  status
46
46
  estimatedDelivery
47
47
  }
@@ -53,7 +53,7 @@ const SHIPMENT_QUERY = `
53
53
  shipment(id: $id) {
54
54
  id
55
55
  shipmentNumber
56
- orderId
56
+ deliveryJobId
57
57
  status
58
58
  estimatedDelivery
59
59
  trackingEvents {
@@ -68,9 +68,9 @@ const SHIPMENT_QUERY = `
68
68
  }
69
69
  `;
70
70
 
71
- const ORDER_TRACKING_QUERY = `
72
- query GetOrderTracking($orderId: ID!) {
73
- orderTracking(orderId: $orderId) {
71
+ const DELIVERY_JOB_TRACKING_QUERY = `
72
+ query GetDeliveryJobTracking($deliveryJobId: ID!) {
73
+ deliveryJobTracking(deliveryJobId: $deliveryJobId) {
74
74
  id
75
75
  shipmentId
76
76
  eventType
@@ -86,7 +86,7 @@ const ACTIVE_DELIVERIES_QUERY = `
86
86
  activeDeliveries {
87
87
  id
88
88
  shipmentNumber
89
- orderId
89
+ deliveryJobId
90
90
  status
91
91
  estimatedDelivery
92
92
  }
@@ -163,7 +163,7 @@ export function useTracking(options: UseTrackingOptions = {}): UseTrackingResult
163
163
 
164
164
  const mapped = data.shipments.map((shipment) => ({
165
165
  id: shipment.id,
166
- entityId: shipment.orderId,
166
+ entityId: shipment.deliveryJobId ?? shipment.id,
167
167
  entityType: 'order' as const,
168
168
  currentLocation: {
169
169
  latitude: 0,
@@ -224,14 +224,14 @@ export function useOrderTracking(orderId: string | undefined) {
224
224
  setIsLoading(true);
225
225
  setError(null);
226
226
  try {
227
- const data = await executeGraphQL<{ orderTracking: ApiTrackingEvent[] }>(
227
+ const data = await executeGraphQL<{ deliveryJobTracking: ApiTrackingEvent[] }>(
228
228
  graphQLOptions,
229
- ORDER_TRACKING_QUERY,
229
+ DELIVERY_JOB_TRACKING_QUERY,
230
230
  {
231
- orderId,
231
+ deliveryJobId: orderId,
232
232
  }
233
233
  );
234
- const latest = data.orderTracking[data.orderTracking.length - 1];
234
+ const latest = data.deliveryJobTracking[data.deliveryJobTracking.length - 1];
235
235
  setTracking(latest ? mapTrackingEventToTracking(latest, orderId) : null);
236
236
  } catch (fetchError) {
237
237
  setError(
@@ -327,7 +327,7 @@ export function useLiveTracking() {
327
327
 
328
328
  const shipmentEvents = shipmentData.shipment?.trackingEvents ?? [];
329
329
  const latestEvent = shipmentEvents[shipmentEvents.length - 1];
330
- const order = orders.find((item) => item.id === shipment.orderId);
330
+ const order = orders.find((item) => item.id === shipment.deliveryJobId);
331
331
  if (!order) {
332
332
  return null;
333
333
  }
@@ -346,7 +346,7 @@ export function useLiveTracking() {
346
346
  driver,
347
347
  vehicle,
348
348
  tracking: latestEvent
349
- ? mapTrackingEventToTracking(latestEvent, shipment.orderId)
349
+ ? mapTrackingEventToTracking(latestEvent, shipment.deliveryJobId ?? shipment.id)
350
350
  : null,
351
351
  estimatedArrival: shipment.estimatedDelivery
352
352
  ? new Date(shipment.estimatedDelivery)