@burdenoff/microfe-movethewheels 2026.929.3 → 2026.929.4

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 (50) hide show
  1. package/dist/generated/wspace-types.d.ts +1547 -64
  2. package/dist/hooks/useCustomers.js +3 -9
  3. package/dist/hooks/useFleet.js +146 -149
  4. package/dist/hooks/useInventory.js +178 -153
  5. package/dist/hooks/useRoutes.js +9 -9
  6. package/dist/hooks/useTracking.js +14 -16
  7. package/dist/movethewheels/hooks/useFleet.d.ts +9 -2
  8. package/dist/movethewheels/hooks/useInventory.d.ts +13 -12
  9. package/dist/movethewheels/hooks/useRoutes.d.ts +4 -1
  10. package/dist/movethewheels/types/index.d.ts +33 -23
  11. package/dist/movethewheels/utils/formatters.d.ts +1 -1
  12. package/dist/pages/CustomerDetailsPage.js +6 -6
  13. package/dist/pages/CustomersPage.js +2 -2
  14. package/dist/pages/DashboardPage.js +1 -1
  15. package/dist/pages/DriverDetailsPage.js +7 -3
  16. package/dist/pages/DriversPage.js +4 -4
  17. package/dist/pages/FleetPage.js +1 -1
  18. package/dist/pages/InventoryPage.js +11 -12
  19. package/dist/pages/LiveTrackingPage.js +4 -4
  20. package/dist/pages/ProductDetailsPage.js +408 -249
  21. package/dist/pages/ProductsPage.js +5 -5
  22. package/dist/pages/RouteDetailsPage.js +8 -10
  23. package/dist/pages/RoutesPage.js +2 -2
  24. package/dist/pages/TrackingPage.js +14 -8
  25. package/dist/pages/WarehouseDetailsPage.js +166 -163
  26. package/dist/pages/WarehousesPage.js +29 -21
  27. package/package.json +1 -1
  28. package/src/generated/wspace-types.ts +1762 -64
  29. package/src/movethewheels/hooks/useCustomers.ts +7 -9
  30. package/src/movethewheels/hooks/useFleet.ts +89 -31
  31. package/src/movethewheels/hooks/useInventory.ts +143 -49
  32. package/src/movethewheels/hooks/useRoutes.ts +36 -16
  33. package/src/movethewheels/hooks/useTracking.ts +23 -15
  34. package/src/movethewheels/pages/CustomerDetailsPage.tsx +14 -8
  35. package/src/movethewheels/pages/CustomersPage.tsx +5 -2
  36. package/src/movethewheels/pages/DashboardPage.tsx +3 -1
  37. package/src/movethewheels/pages/DriverDetailsPage.tsx +13 -3
  38. package/src/movethewheels/pages/DriversPage.tsx +1 -1
  39. package/src/movethewheels/pages/FleetPage.tsx +1 -1
  40. package/src/movethewheels/pages/InventoryPage.tsx +20 -16
  41. package/src/movethewheels/pages/LiveTrackingPage.tsx +6 -4
  42. package/src/movethewheels/pages/ProductDetailsPage.tsx +228 -49
  43. package/src/movethewheels/pages/ProductsPage.tsx +13 -5
  44. package/src/movethewheels/pages/RouteDetailsPage.tsx +16 -8
  45. package/src/movethewheels/pages/RoutesPage.tsx +4 -2
  46. package/src/movethewheels/pages/TrackingPage.tsx +25 -10
  47. package/src/movethewheels/pages/WarehouseDetailsPage.tsx +42 -29
  48. package/src/movethewheels/pages/WarehousesPage.tsx +23 -15
  49. package/src/movethewheels/types/index.ts +36 -24
  50. package/src/movethewheels/utils/formatters.ts +1 -1
@@ -125,6 +125,7 @@ const DELETE_CUSTOMER_MUTATION = `
125
125
  `;
126
126
 
127
127
  function mapCustomer(customer: ApiCustomer): Customer {
128
+ const address = customer.address ?? {};
128
129
  return {
129
130
  id: customer.id,
130
131
  name: customer.name,
@@ -132,17 +133,14 @@ function mapCustomer(customer: ApiCustomer): Customer {
132
133
  phone: customer.phone ?? '',
133
134
  type: customer.type.toLowerCase() === 'b2b' ? 'b2b' : 'b2c',
134
135
  company: customer.company ?? undefined,
135
- address: {
136
- street: '',
137
- city: '',
138
- state: '',
139
- postalCode: '',
140
- country: '',
141
- ...(customer.address ?? {}),
142
- },
136
+ address: Object.fromEntries(
137
+ Object.entries(address).filter(
138
+ ([, value]) => typeof value === 'string' && value.trim().length > 0
139
+ )
140
+ ),
143
141
  businessLicense: customer.businessLicense ?? undefined,
144
142
  taxId: customer.taxId ?? undefined,
145
- rating: customer.rating ?? 0,
143
+ rating: customer.rating ?? undefined,
146
144
  createdAt: new Date(customer.createdAt),
147
145
  updatedAt: new Date(customer.updatedAt),
148
146
  };
@@ -45,7 +45,8 @@ export interface CreateDriverInput {
45
45
  licenseExpiry?: string;
46
46
  /** DriverStatus enum value, e.g. `AVAILABLE`, `OFF_DUTY`. */
47
47
  status?: string;
48
- vehicleId?: string;
48
+ vehicleId?: string | null;
49
+ vehicleIds?: string[];
49
50
  rating?: number;
50
51
  notes?: string;
51
52
  hireDate?: string;
@@ -97,6 +98,7 @@ export interface ApiVehicle {
97
98
  nextServiceDueMileage?: number | null;
98
99
  createdAt: string;
99
100
  updatedAt?: string;
101
+ drivers?: Array<{ id: string }>;
100
102
  }
101
103
 
102
104
  export interface ApiDriver {
@@ -114,6 +116,7 @@ export interface ApiDriver {
114
116
  currentLocation?: Record<string, unknown> | null;
115
117
  rating?: number | null;
116
118
  vehicleId?: string | null;
119
+ vehicles?: Array<{ id: string }>;
117
120
  notes?: string | null;
118
121
  hireDate?: string | null;
119
122
  baseSalary?: number | null;
@@ -148,6 +151,9 @@ const LIST_VEHICLES_QUERY = `
148
151
  nextServiceDueMileage
149
152
  createdAt
150
153
  updatedAt
154
+ drivers {
155
+ id
156
+ }
151
157
  }
152
158
  vehiclesCount(filter: $filter)
153
159
  }
@@ -179,6 +185,9 @@ const LIST_DRIVERS_QUERY = `
179
185
  terminatedAt
180
186
  terminationReason
181
187
  createdAt
188
+ vehicles {
189
+ id
190
+ }
182
191
  }
183
192
  driversCount(filter: $filter)
184
193
  }
@@ -207,6 +216,9 @@ const GET_VEHICLE_QUERY = `
207
216
  nextServiceDueMileage
208
217
  createdAt
209
218
  updatedAt
219
+ drivers {
220
+ id
221
+ }
210
222
  }
211
223
  }
212
224
  `;
@@ -237,6 +249,9 @@ const GET_DRIVER_QUERY = `
237
249
  terminatedAt
238
250
  terminationReason
239
251
  createdAt
252
+ vehicles {
253
+ id
254
+ }
240
255
  }
241
256
  }
242
257
  `;
@@ -267,6 +282,9 @@ const UPDATE_VEHICLE_STATUS_MUTATION = `
267
282
  mileage
268
283
  createdAt
269
284
  updatedAt
285
+ drivers {
286
+ id
287
+ }
270
288
  }
271
289
  }
272
290
  `;
@@ -297,6 +315,9 @@ const ASSIGN_DRIVER_VEHICLE_MUTATION = `
297
315
  terminatedAt
298
316
  terminationReason
299
317
  createdAt
318
+ vehicles {
319
+ id
320
+ }
300
321
  }
301
322
  }
302
323
  `;
@@ -327,6 +348,9 @@ const UPDATE_DRIVER_STATUS_MUTATION = `
327
348
  terminatedAt
328
349
  terminationReason
329
350
  createdAt
351
+ vehicles {
352
+ id
353
+ }
330
354
  }
331
355
  }
332
356
  `;
@@ -351,6 +375,9 @@ const CREATE_VEHICLE_MUTATION = `
351
375
  mileage
352
376
  createdAt
353
377
  updatedAt
378
+ drivers {
379
+ id
380
+ }
354
381
  }
355
382
  }
356
383
  `;
@@ -381,6 +408,9 @@ const CREATE_DRIVER_MUTATION = `
381
408
  terminatedAt
382
409
  terminationReason
383
410
  createdAt
411
+ vehicles {
412
+ id
413
+ }
384
414
  }
385
415
  }
386
416
  `;
@@ -405,6 +435,9 @@ const UPDATE_VEHICLE_MUTATION = `
405
435
  mileage
406
436
  createdAt
407
437
  updatedAt
438
+ drivers {
439
+ id
440
+ }
408
441
  }
409
442
  }
410
443
  `;
@@ -435,6 +468,9 @@ const UPDATE_DRIVER_MUTATION = `
435
468
  terminatedAt
436
469
  terminationReason
437
470
  createdAt
471
+ vehicles {
472
+ id
473
+ }
438
474
  }
439
475
  }
440
476
  `;
@@ -579,7 +615,8 @@ export function mapVehicle(vehicle: ApiVehicle): Vehicle {
579
615
  pallets: typeof vehicle.capacity?.pallets === 'number' ? vehicle.capacity.pallets : undefined,
580
616
  },
581
617
  status: mapVehicleStatus(vehicle.status),
582
- driverId: undefined,
618
+ driverIds: (vehicle.drivers ?? []).map((driver) => driver.id),
619
+ driverId: vehicle.drivers?.[0]?.id,
583
620
  currentLocation: parseCurrentLocation(vehicle.currentLocation),
584
621
  lastMaintenance: vehicle.lastServiceDate ? new Date(vehicle.lastServiceDate) : undefined,
585
622
  nextMaintenance: vehicle.nextServiceDueDate ? new Date(vehicle.nextServiceDueDate) : undefined,
@@ -604,11 +641,16 @@ export function mapDriver(driver: ApiDriver): Driver {
604
641
  licenseNumber: driver.licenseNumber ?? '',
605
642
  licenseType: driver.licenseClass ?? '',
606
643
  licenseExpiry: driver.licenseExpiry ? new Date(driver.licenseExpiry) : undefined,
607
- vehicleIds: driver.vehicleId ? [driver.vehicleId] : [],
608
- assignedVehicle: driver.vehicleId ?? undefined,
644
+ vehicleIds:
645
+ driver.vehicles && driver.vehicles.length > 0
646
+ ? driver.vehicles.map((vehicle) => vehicle.id)
647
+ : driver.vehicleId
648
+ ? [driver.vehicleId]
649
+ : [],
650
+ assignedVehicle: driver.vehicles?.[0]?.id ?? driver.vehicleId ?? undefined,
609
651
  status: mapDriverStatus(driver.status),
610
652
  currentLocation: parseCurrentLocation(driver.currentLocation),
611
- rating: driver.rating ?? 0,
653
+ rating: driver.rating ?? undefined,
612
654
  notes: driver.notes ?? undefined,
613
655
  hireDate: driver.hireDate ? new Date(driver.hireDate) : undefined,
614
656
  baseSalary: driver.baseSalary ?? undefined,
@@ -656,6 +698,8 @@ export function useFleet(options: UseFleetOptions = {}): UseFleetResult {
656
698
  const graphQLOptions = useGraphqlOptions();
657
699
  const [vehicles, setVehicles] = useState<Vehicle[]>([]);
658
700
  const [drivers, setDrivers] = useState<Driver[]>([]);
701
+ const [totalVehicles, setTotalVehicles] = useState(0);
702
+ const [totalDrivers, setTotalDrivers] = useState(0);
659
703
  const [isLoading, setIsLoading] = useState(false);
660
704
  const [error, setError] = useState<Error | null>(null);
661
705
 
@@ -664,26 +708,36 @@ export function useFleet(options: UseFleetOptions = {}): UseFleetResult {
664
708
  setError(null);
665
709
  try {
666
710
  const [vehicleData, driverData] = await Promise.all([
667
- executeGraphQL<{ vehicles: ApiVehicle[] }>(graphQLOptions, LIST_VEHICLES_QUERY, {
668
- filter: {
669
- search: filters?.search,
670
- type: filters?.vehicleType?.toUpperCase(),
671
- status: filters?.status ? toApiVehicleStatus(filters.status) : undefined,
672
- driverId: filters?.driverId,
673
- },
674
- pagination: { limit, offset: 0 },
675
- }),
676
- executeGraphQL<{ drivers: ApiDriver[] }>(graphQLOptions, LIST_DRIVERS_QUERY, {
677
- filter: {
678
- search: filters?.search,
679
- vehicleId: filters?.vehicleId,
680
- },
681
- pagination: { limit, offset: 0 },
682
- }),
711
+ executeGraphQL<{ vehicles: ApiVehicle[]; vehiclesCount: number }>(
712
+ graphQLOptions,
713
+ LIST_VEHICLES_QUERY,
714
+ {
715
+ filter: {
716
+ search: filters?.search,
717
+ type: filters?.vehicleType?.toUpperCase(),
718
+ status: filters?.status ? toApiVehicleStatus(filters.status) : undefined,
719
+ driverId: filters?.driverId,
720
+ },
721
+ pagination: { limit, offset: 0 },
722
+ }
723
+ ),
724
+ executeGraphQL<{ drivers: ApiDriver[]; driversCount: number }>(
725
+ graphQLOptions,
726
+ LIST_DRIVERS_QUERY,
727
+ {
728
+ filter: {
729
+ search: filters?.search,
730
+ vehicleId: filters?.vehicleId,
731
+ },
732
+ pagination: { limit, offset: 0 },
733
+ }
734
+ ),
683
735
  ]);
684
736
 
685
737
  setVehicles(vehicleData.vehicles.map(mapVehicle));
686
738
  setDrivers(driverData.drivers.map(mapDriver));
739
+ setTotalVehicles(vehicleData.vehiclesCount);
740
+ setTotalDrivers(driverData.driversCount);
687
741
  } catch (fetchError) {
688
742
  setError(fetchError instanceof Error ? fetchError : new Error('Failed to load fleet data'));
689
743
  } finally {
@@ -744,6 +798,7 @@ export function useFleet(options: UseFleetOptions = {}): UseFleetResult {
744
798
  licenseExpiry: input.licenseExpiry,
745
799
  status: input.status,
746
800
  vehicleId: input.vehicleId,
801
+ vehicleIds: input.vehicleIds,
747
802
  rating: input.rating,
748
803
  notes: input.notes,
749
804
  hireDate: input.hireDate,
@@ -806,6 +861,7 @@ export function useFleet(options: UseFleetOptions = {}): UseFleetResult {
806
861
  licenseExpiry: input.licenseExpiry,
807
862
  status: input.status,
808
863
  vehicleId: input.vehicleId,
864
+ vehicleIds: input.vehicleIds,
809
865
  rating: input.rating,
810
866
  notes: input.notes,
811
867
  hireDate: input.hireDate,
@@ -838,8 +894,8 @@ export function useFleet(options: UseFleetOptions = {}): UseFleetResult {
838
894
  return {
839
895
  vehicles,
840
896
  drivers,
841
- totalVehicles: vehicles.length,
842
- totalDrivers: drivers.length,
897
+ totalVehicles,
898
+ totalDrivers,
843
899
  isLoading,
844
900
  error,
845
901
  refetch,
@@ -960,15 +1016,17 @@ export function useVehicles(options: UseVehiclesOptions = {}): UseVehiclesResult
960
1016
  vehicleId,
961
1017
  }
962
1018
  );
963
- const next = vehicles.find((item) => item.id === vehicleId);
964
- if (!next) {
965
- throw new Error('Vehicle not found');
966
- }
967
- const updated = { ...next, driverId, status: 'assigned' as VehicleStatus };
1019
+ const data = await executeGraphQL<{ vehicle: ApiVehicle | null }>(
1020
+ graphQLOptions,
1021
+ GET_VEHICLE_QUERY,
1022
+ { id: vehicleId }
1023
+ );
1024
+ if (!data.vehicle) throw new Error('Vehicle not found');
1025
+ const updated = mapVehicle(data.vehicle);
968
1026
  setVehicles((previous) => previous.map((item) => (item.id === vehicleId ? updated : item)));
969
1027
  return updated;
970
1028
  },
971
- [graphQLOptions, vehicles]
1029
+ [graphQLOptions]
972
1030
  );
973
1031
 
974
1032
  const deleteVehicle = useCallback(
@@ -1044,7 +1102,7 @@ interface UseDriversResult {
1044
1102
  error: Error | null;
1045
1103
  refetch: () => void;
1046
1104
  updateDriverStatus: (id: string, status: DriverStatus) => Promise<Driver>;
1047
- assignVehicle: (driverId: string, vehicleId: string) => Promise<Driver>;
1105
+ assignVehicle: (driverId: string, vehicleId: string | null) => Promise<Driver>;
1048
1106
  }
1049
1107
 
1050
1108
  interface UseDriversOptions extends UseListOptions {
@@ -1117,7 +1175,7 @@ export function useDrivers(options: UseDriversOptions = {}): UseDriversResult {
1117
1175
  );
1118
1176
 
1119
1177
  const assignVehicle = useCallback(
1120
- async (driverId: string, vehicleId: string): Promise<Driver> => {
1178
+ async (driverId: string, vehicleId: string | null): Promise<Driver> => {
1121
1179
  const data = await executeGraphQL<{ assignDriverVehicle: ApiDriver }>(
1122
1180
  graphQLOptions,
1123
1181
  ASSIGN_DRIVER_VEHICLE_MUTATION,
@@ -1,6 +1,6 @@
1
1
  import { useCallback, useEffect, useMemo, useState } from 'react';
2
2
  import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
3
- import type { Inventory, InventoryFilter, Product, Warehouse } from '../types';
3
+ import type { Inventory, InventoryFilter, Product, ProductDimensions, Warehouse } from '../types';
4
4
  import { executeGraphQL } from '../utils/graphqlClient';
5
5
 
6
6
  interface UseInventoryOptions {
@@ -13,9 +13,15 @@ interface UseInventoryOptions {
13
13
  export interface CreateProductInput {
14
14
  name: string;
15
15
  sku: string;
16
- category?: string;
17
- brand?: string;
18
- weight?: number;
16
+ description?: string | null;
17
+ category?: string | null;
18
+ brand?: string | null;
19
+ dimensions?: ProductDimensions | null;
20
+ weight?: number | null;
21
+ value?: number | null;
22
+ hazardous?: boolean;
23
+ fragile?: boolean;
24
+ temperatureControlled?: boolean;
19
25
  }
20
26
 
21
27
  export interface CreateWarehouseInput {
@@ -23,14 +29,8 @@ export interface CreateWarehouseInput {
23
29
  name: string;
24
30
  /** WarehouseType enum value, e.g. `DISTRIBUTION`, `FULFILLMENT`. */
25
31
  type: string;
26
- address: {
27
- street: string;
28
- city: string;
29
- state: string;
30
- postalCode: string;
31
- country: string;
32
- };
33
- /** Free-text on the backend, e.g. "ACTIVE", "INACTIVE". Defaults to ACTIVE. */
32
+ address: Record<string, string>;
33
+ /** Free-text on the backend, e.g. "ACTIVE", "INACTIVE". */
34
34
  status?: string;
35
35
  contactInfo?: { email?: string; phone?: string };
36
36
  totalCapacity?: number;
@@ -57,6 +57,7 @@ interface UseInventoryResult {
57
57
  createProduct: (input: CreateProductInput) => Promise<Product>;
58
58
  createWarehouse: (input: CreateWarehouseInput) => Promise<Warehouse>;
59
59
  updateProduct: (id: string, input: CreateProductInput) => Promise<Product>;
60
+ deleteProduct: (id: string) => Promise<void>;
60
61
  updateWarehouse: (id: string, input: CreateWarehouseInput) => Promise<Warehouse>;
61
62
  deleteWarehouse: (id: string) => Promise<void>;
62
63
  }
@@ -299,6 +300,12 @@ const UPDATE_PRODUCT_MUTATION = `
299
300
  }
300
301
  `;
301
302
 
303
+ const DELETE_PRODUCT_MUTATION = `
304
+ mutation DeleteCargoItem($id: ID!) {
305
+ deleteCargoItem(id: $id)
306
+ }
307
+ `;
308
+
302
309
  const CREATE_WAREHOUSE_MUTATION = `
303
310
  mutation CreateWarehouse($input: CreateWarehouseInput!) {
304
311
  createWarehouse(input: $input) {
@@ -357,24 +364,61 @@ const ADJUST_INVENTORY_MUTATION = `
357
364
  }
358
365
  }
359
366
  `;
367
+ const TRANSFER_INVENTORY_STOCK_MUTATION = `
368
+ mutation TransferInventoryStock($input: TransferInventoryStockInput!) {
369
+ transferInventoryStock(input: $input) {
370
+ source {
371
+ id
372
+ cargoItemId
373
+ warehouseId
374
+ onHand
375
+ available
376
+ allocated
377
+ damaged
378
+ reorderPoint
379
+ createdAt
380
+ updatedAt
381
+ }
382
+ destination {
383
+ id
384
+ cargoItemId
385
+ warehouseId
386
+ onHand
387
+ available
388
+ allocated
389
+ damaged
390
+ reorderPoint
391
+ createdAt
392
+ updatedAt
393
+ }
394
+ }
395
+ }
396
+ `;
397
+
398
+ function mapDimensions(
399
+ raw: Record<string, unknown> | null | undefined
400
+ ): ProductDimensions | undefined {
401
+ if (!raw) return undefined;
402
+ const dimensions: ProductDimensions = {};
403
+ for (const key of ['length', 'width', 'height'] as const) {
404
+ const value = raw[key];
405
+ if (typeof value === 'number' && Number.isFinite(value)) dimensions[key] = value;
406
+ }
407
+ if (typeof raw.unit === 'string' && raw.unit.trim()) dimensions.unit = raw.unit;
408
+ return Object.keys(dimensions).length > 0 ? dimensions : undefined;
409
+ }
360
410
 
361
411
  function mapProduct(product: ApiProduct): Product {
362
412
  return {
363
413
  id: product.id,
364
414
  sku: product.sku,
365
415
  name: product.name,
366
- description: product.description ?? '',
367
- category: product.category ?? 'general',
416
+ description: product.description ?? undefined,
417
+ category: product.category ?? undefined,
368
418
  brand: product.brand ?? undefined,
369
- dimensions: {
370
- length: 0,
371
- width: 0,
372
- height: 0,
373
- unit: 'cm',
374
- ...(product.dimensions ?? {}),
375
- } as Product['dimensions'],
376
- weight: product.weight ?? 0,
377
- value: product.value ?? 0,
419
+ dimensions: mapDimensions(product.dimensions),
420
+ weight: product.weight ?? undefined,
421
+ value: product.value ?? undefined,
378
422
  barcode: undefined,
379
423
  images: [],
380
424
  attributes: [],
@@ -386,9 +430,21 @@ function mapProduct(product: ApiProduct): Product {
386
430
  };
387
431
  }
388
432
 
433
+ function mapJsonStringRecord(
434
+ raw: Record<string, unknown> | null | undefined
435
+ ): Record<string, string> {
436
+ if (!raw) return {};
437
+ return Object.fromEntries(
438
+ Object.entries(raw).flatMap(([key, value]) =>
439
+ typeof value === 'string' && value.trim() ? [[key, value]] : []
440
+ )
441
+ );
442
+ }
443
+
389
444
  function mapWarehouse(warehouse: ApiWarehouse): Warehouse {
390
- const totalSpace = warehouse.totalCapacity ?? 0;
391
- const usedSpace = warehouse.usedCapacity ?? 0;
445
+ const totalSpace =
446
+ typeof warehouse.totalCapacity === 'number' ? warehouse.totalCapacity : undefined;
447
+ const usedSpace = typeof warehouse.usedCapacity === 'number' ? warehouse.usedCapacity : undefined;
392
448
  const operatingHoursRaw = warehouse.operatingHours ?? {};
393
449
  const operatingHours: Warehouse['operatingHours'] = {};
394
450
  for (const [day, hours] of Object.entries(operatingHoursRaw)) {
@@ -399,20 +455,17 @@ function mapWarehouse(warehouse: ApiWarehouse): Warehouse {
399
455
  }
400
456
  }
401
457
  const contactInfoRaw = warehouse.contactInfo ?? undefined;
458
+ const utilization =
459
+ totalSpace !== undefined && totalSpace > 0 && usedSpace !== undefined
460
+ ? (usedSpace / totalSpace) * 100
461
+ : undefined;
402
462
  return {
403
463
  id: warehouse.id,
404
464
  name: warehouse.name,
405
465
  code: warehouse.code,
406
- address: {
407
- street: '',
408
- city: '',
409
- state: '',
410
- postalCode: '',
411
- country: '',
412
- ...(warehouse.address ?? {}),
413
- },
414
- type: (warehouse.type?.toLowerCase() ?? 'distribution') as Warehouse['type'],
415
- status: warehouse.status ?? 'ACTIVE',
466
+ address: mapJsonStringRecord(warehouse.address),
467
+ type: warehouse.type.toLowerCase() as Warehouse['type'],
468
+ status: warehouse.status,
416
469
  capacity: {
417
470
  totalSpace,
418
471
  usedSpace,
@@ -420,11 +473,11 @@ function mapWarehouse(warehouse: ApiWarehouse): Warehouse {
420
473
  operatingHours,
421
474
  contactInfo: contactInfoRaw
422
475
  ? {
423
- email: (contactInfoRaw.email as string | undefined) ?? undefined,
424
- phone: (contactInfoRaw.phone as string | undefined) ?? undefined,
476
+ email: typeof contactInfoRaw.email === 'string' ? contactInfoRaw.email : undefined,
477
+ phone: typeof contactInfoRaw.phone === 'string' ? contactInfoRaw.phone : undefined,
425
478
  }
426
479
  : undefined,
427
- utilization: totalSpace > 0 ? (usedSpace / totalSpace) * 100 : 0,
480
+ utilization,
428
481
  createdAt: new Date(warehouse.createdAt),
429
482
  };
430
483
  }
@@ -585,23 +638,41 @@ export function useInventory(options: UseInventoryOptions = {}): UseInventoryRes
585
638
  productId: string,
586
639
  quantity: number
587
640
  ): Promise<void> => {
641
+ if (!Number.isInteger(quantity) || quantity <= 0) {
642
+ throw new Error('Transfer quantity must be a positive whole number.');
643
+ }
644
+ if (fromWarehouseId === toWarehouseId) {
645
+ throw new Error('Source and destination warehouses must be different.');
646
+ }
588
647
  const source = inventory.find(
589
648
  (item) => item.warehouseId === fromWarehouseId && item.productId === productId
590
649
  );
591
- if (!source) {
592
- throw new Error('Source inventory not found');
593
- }
594
-
595
- await updateStock(source.id, -Math.abs(quantity), 'transfer out');
596
-
650
+ if (!source) throw new Error('Source inventory not found');
597
651
  const destination = inventory.find(
598
652
  (item) => item.warehouseId === toWarehouseId && item.productId === productId
599
653
  );
600
- if (destination) {
601
- await updateStock(destination.id, Math.abs(quantity), 'transfer in');
602
- }
654
+ if (!destination) throw new Error('Destination inventory not found');
655
+
656
+ const data = await executeGraphQL<{
657
+ transferInventoryStock: { source: ApiInventoryItem; destination: ApiInventoryItem };
658
+ }>(graphQLOptions, TRANSFER_INVENTORY_STOCK_MUTATION, {
659
+ input: {
660
+ sourceInventoryItemId: source.id,
661
+ destinationInventoryItemId: destination.id,
662
+ quantity,
663
+ },
664
+ });
665
+ const updatedSource = mapInventory(data.transferInventoryStock.source);
666
+ const updatedDestination = mapInventory(data.transferInventoryStock.destination);
667
+ setInventory((previous) =>
668
+ previous.map((item) => {
669
+ if (item.id === updatedSource.id) return updatedSource;
670
+ if (item.id === updatedDestination.id) return updatedDestination;
671
+ return item;
672
+ })
673
+ );
603
674
  },
604
- [inventory, updateStock]
675
+ [graphQLOptions, inventory]
605
676
  );
606
677
 
607
678
  const createProduct = useCallback(
@@ -613,9 +684,15 @@ export function useInventory(options: UseInventoryOptions = {}): UseInventoryRes
613
684
  input: {
614
685
  name: input.name,
615
686
  sku: input.sku,
687
+ description: input.description,
616
688
  category: input.category,
617
689
  brand: input.brand,
690
+ dimensions: input.dimensions,
618
691
  weight: input.weight,
692
+ value: input.value,
693
+ hazardous: input.hazardous,
694
+ fragile: input.fragile,
695
+ temperatureControlled: input.temperatureControlled,
619
696
  },
620
697
  }
621
698
  );
@@ -661,9 +738,15 @@ export function useInventory(options: UseInventoryOptions = {}): UseInventoryRes
661
738
  input: {
662
739
  name: input.name,
663
740
  sku: input.sku,
741
+ description: input.description,
664
742
  category: input.category,
665
743
  brand: input.brand,
744
+ dimensions: input.dimensions,
666
745
  weight: input.weight,
746
+ value: input.value,
747
+ hazardous: input.hazardous,
748
+ fragile: input.fragile,
749
+ temperatureControlled: input.temperatureControlled,
667
750
  },
668
751
  }
669
752
  );
@@ -674,6 +757,16 @@ export function useInventory(options: UseInventoryOptions = {}): UseInventoryRes
674
757
  [graphQLOptions]
675
758
  );
676
759
 
760
+ const deleteProduct = useCallback(
761
+ async (id: string): Promise<void> => {
762
+ await executeGraphQL<{ deleteCargoItem: boolean }>(graphQLOptions, DELETE_PRODUCT_MUTATION, {
763
+ id,
764
+ });
765
+ setProducts((previous) => previous.filter((item) => item.id !== id));
766
+ },
767
+ [graphQLOptions]
768
+ );
769
+
677
770
  const updateWarehouse = useCallback(
678
771
  async (id: string, input: CreateWarehouseInput): Promise<Warehouse> => {
679
772
  const data = await executeGraphQL<{ updateWarehouse: ApiWarehouse }>(
@@ -725,8 +818,9 @@ export function useInventory(options: UseInventoryOptions = {}): UseInventoryRes
725
818
  updateStock,
726
819
  transferStock,
727
820
  createProduct,
728
- createWarehouse,
729
821
  updateProduct,
822
+ deleteProduct,
823
+ createWarehouse,
730
824
  updateWarehouse,
731
825
  deleteWarehouse,
732
826
  };