@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.
- package/dist/generated/wspace-types.d.ts +1547 -64
- package/dist/hooks/useCustomers.js +3 -9
- package/dist/hooks/useFleet.js +146 -149
- package/dist/hooks/useInventory.js +178 -153
- package/dist/hooks/useRoutes.js +9 -9
- package/dist/hooks/useTracking.js +14 -16
- package/dist/movethewheels/hooks/useFleet.d.ts +9 -2
- package/dist/movethewheels/hooks/useInventory.d.ts +13 -12
- package/dist/movethewheels/hooks/useRoutes.d.ts +4 -1
- package/dist/movethewheels/types/index.d.ts +33 -23
- package/dist/movethewheels/utils/formatters.d.ts +1 -1
- package/dist/pages/CustomerDetailsPage.js +6 -6
- package/dist/pages/CustomersPage.js +2 -2
- package/dist/pages/DashboardPage.js +1 -1
- package/dist/pages/DriverDetailsPage.js +7 -3
- package/dist/pages/DriversPage.js +4 -4
- package/dist/pages/FleetPage.js +1 -1
- package/dist/pages/InventoryPage.js +11 -12
- package/dist/pages/LiveTrackingPage.js +4 -4
- package/dist/pages/ProductDetailsPage.js +408 -249
- package/dist/pages/ProductsPage.js +5 -5
- package/dist/pages/RouteDetailsPage.js +8 -10
- package/dist/pages/RoutesPage.js +2 -2
- package/dist/pages/TrackingPage.js +14 -8
- package/dist/pages/WarehouseDetailsPage.js +166 -163
- package/dist/pages/WarehousesPage.js +29 -21
- package/package.json +1 -1
- package/src/generated/wspace-types.ts +1762 -64
- package/src/movethewheels/hooks/useCustomers.ts +7 -9
- package/src/movethewheels/hooks/useFleet.ts +89 -31
- package/src/movethewheels/hooks/useInventory.ts +143 -49
- package/src/movethewheels/hooks/useRoutes.ts +36 -16
- package/src/movethewheels/hooks/useTracking.ts +23 -15
- package/src/movethewheels/pages/CustomerDetailsPage.tsx +14 -8
- package/src/movethewheels/pages/CustomersPage.tsx +5 -2
- package/src/movethewheels/pages/DashboardPage.tsx +3 -1
- package/src/movethewheels/pages/DriverDetailsPage.tsx +13 -3
- package/src/movethewheels/pages/DriversPage.tsx +1 -1
- package/src/movethewheels/pages/FleetPage.tsx +1 -1
- package/src/movethewheels/pages/InventoryPage.tsx +20 -16
- package/src/movethewheels/pages/LiveTrackingPage.tsx +6 -4
- package/src/movethewheels/pages/ProductDetailsPage.tsx +228 -49
- package/src/movethewheels/pages/ProductsPage.tsx +13 -5
- package/src/movethewheels/pages/RouteDetailsPage.tsx +16 -8
- package/src/movethewheels/pages/RoutesPage.tsx +4 -2
- package/src/movethewheels/pages/TrackingPage.tsx +25 -10
- package/src/movethewheels/pages/WarehouseDetailsPage.tsx +42 -29
- package/src/movethewheels/pages/WarehousesPage.tsx +23 -15
- package/src/movethewheels/types/index.ts +36 -24
- 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
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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 ??
|
|
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
|
-
|
|
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:
|
|
608
|
-
|
|
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 ??
|
|
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[] }>(
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
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
|
|
842
|
-
totalDrivers
|
|
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
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
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
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
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 ??
|
|
416
|
+
description: product.description ?? undefined,
|
|
417
|
+
category: product.category ?? undefined,
|
|
368
418
|
brand: product.brand ?? undefined,
|
|
369
|
-
dimensions:
|
|
370
|
-
|
|
371
|
-
|
|
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 =
|
|
391
|
-
|
|
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
|
-
|
|
408
|
-
|
|
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:
|
|
424
|
-
phone:
|
|
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
|
|
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
|
-
|
|
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
|
-
[
|
|
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
|
};
|