@burdenoff/microfe-movethewheels 2026.929.3 → 2026.930.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/CustomerPicker.js +52 -48
- package/dist/components/EntityCombobox.js +25 -23
- package/dist/components/OrderItemRow.js +123 -78
- package/dist/generated/wspace-types.d.ts +1547 -64
- package/dist/hooks/useCustomers.js +55 -26
- package/dist/hooks/useFleet.js +236 -157
- package/dist/hooks/useInventory.js +214 -153
- package/dist/hooks/useMultiModalShipments.js +30 -26
- package/dist/hooks/useRoutes.js +64 -24
- package/dist/hooks/useTracking.js +14 -16
- package/dist/movethewheels/components/CustomerPicker.d.ts +5 -1
- package/dist/movethewheels/components/EntityCombobox.d.ts +6 -3
- package/dist/movethewheels/components/OrderItemRow.d.ts +7 -1
- package/dist/movethewheels/hooks/useCustomers.d.ts +3 -0
- package/dist/movethewheels/hooks/useFleet.d.ts +15 -2
- package/dist/movethewheels/hooks/useInventory.d.ts +15 -12
- package/dist/movethewheels/hooks/useMultiModalShipments.d.ts +1 -0
- package/dist/movethewheels/hooks/useRoutes.d.ts +7 -1
- package/dist/movethewheels/types/index.d.ts +33 -23
- package/dist/movethewheels/utils/formatters.d.ts +1 -1
- package/dist/movethewheels/utils/orderValidation.d.ts +11 -0
- package/dist/pages/CreateOrderPage.js +349 -297
- 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 +340 -332
- package/dist/pages/DriversPage.js +7 -5
- package/dist/pages/FleetPage.js +1 -1
- package/dist/pages/InventoryPage.js +11 -12
- package/dist/pages/LiveTrackingPage.js +4 -4
- package/dist/pages/MultiModalShipmentPage.js +139 -125
- package/dist/pages/OrderDetailsPage.js +132 -140
- package/dist/pages/ProductDetailsPage.js +408 -249
- package/dist/pages/ProductsPage.js +5 -5
- package/dist/pages/RouteDetailsPage.js +208 -210
- package/dist/pages/RoutesPage.js +2 -2
- package/dist/pages/TrackingPage.js +16 -9
- package/dist/pages/WarehouseDetailsPage.js +166 -163
- package/dist/pages/WarehousesPage.js +29 -21
- package/dist/utils/orderValidation.js +18 -0
- package/package.json +1 -1
- package/src/generated/wspace-types.ts +1762 -64
- package/src/movethewheels/components/CustomerPicker.tsx +12 -0
- package/src/movethewheels/components/EntityCombobox.tsx +30 -11
- package/src/movethewheels/components/OrderItemRow.tsx +44 -0
- package/src/movethewheels/hooks/useCustomers.ts +45 -9
- package/src/movethewheels/hooks/useFleet.ts +214 -36
- package/src/movethewheels/hooks/useInventory.ts +185 -49
- package/src/movethewheels/hooks/useMultiModalShipments.ts +9 -7
- package/src/movethewheels/hooks/useRoutes.ts +87 -16
- package/src/movethewheels/hooks/useTracking.ts +23 -15
- package/src/movethewheels/pages/CreateOrderPage.tsx +66 -2
- 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 +25 -4
- package/src/movethewheels/pages/DriversPage.tsx +8 -2
- 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/MultiModalShipmentPage.tsx +15 -1
- package/src/movethewheels/pages/OrderDetailsPage.tsx +25 -25
- package/src/movethewheels/pages/ProductDetailsPage.tsx +228 -49
- package/src/movethewheels/pages/ProductsPage.tsx +13 -5
- package/src/movethewheels/pages/RouteDetailsPage.tsx +24 -12
- package/src/movethewheels/pages/RoutesPage.tsx +4 -2
- package/src/movethewheels/pages/TrackingPage.tsx +32 -12
- 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
- package/src/movethewheels/utils/orderValidation.ts +53 -0
|
@@ -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,
|
|
@@ -872,8 +928,11 @@ interface UseVehiclesResult {
|
|
|
872
928
|
/** True when the last fetch returned a full page (rawCount === limit). */
|
|
873
929
|
hasMore: boolean;
|
|
874
930
|
isLoading: boolean;
|
|
931
|
+
/** True while an additional server page is appended. */
|
|
932
|
+
isLoadingMore: boolean;
|
|
875
933
|
error: Error | null;
|
|
876
934
|
refetch: () => void;
|
|
935
|
+
loadMore: () => Promise<void>;
|
|
877
936
|
updateVehicleStatus: (id: string, status: VehicleStatus) => Promise<Vehicle>;
|
|
878
937
|
assignDriver: (vehicleId: string, driverId: string) => Promise<Vehicle>;
|
|
879
938
|
deleteVehicle: (id: string) => Promise<void>;
|
|
@@ -886,13 +945,16 @@ export function useVehicles(options: UseVehiclesOptions = {}): UseVehiclesResult
|
|
|
886
945
|
const [totalVehicles, setTotalVehicles] = useState(0);
|
|
887
946
|
const [hasMore, setHasMore] = useState(false);
|
|
888
947
|
const [isLoading, setIsLoading] = useState(false);
|
|
948
|
+
const [isLoadingMore, setIsLoadingMore] = useState(false);
|
|
889
949
|
const [error, setError] = useState<Error | null>(null);
|
|
890
950
|
// Stable dependency key for the `ids` array — a caller passing a fresh array
|
|
891
951
|
// literal each render must not defeat the callback's memoization.
|
|
892
952
|
const idsKey = ids ? [...ids].sort().join(',') : '';
|
|
953
|
+
const idsFilterMode = ids === undefined ? 'none' : 'exact';
|
|
954
|
+
const exactIds = useMemo(() => (idsKey ? idsKey.split(',') : []), [idsKey]);
|
|
893
955
|
|
|
894
956
|
const load = useCallback(async () => {
|
|
895
|
-
if (
|
|
957
|
+
if (idsFilterMode === 'exact' && exactIds.length === 0) {
|
|
896
958
|
// Nothing to look up — skip the request and clear stale results.
|
|
897
959
|
setVehicles([]);
|
|
898
960
|
setTotalVehicles(0);
|
|
@@ -911,11 +973,14 @@ export function useVehicles(options: UseVehiclesOptions = {}): UseVehiclesResult
|
|
|
911
973
|
status: status ? toApiVehicleStatus(status) : undefined,
|
|
912
974
|
type: vehicleType?.toUpperCase(),
|
|
913
975
|
driverId: driverId || undefined,
|
|
914
|
-
ids:
|
|
976
|
+
ids: exactIds.length > 0 ? exactIds : undefined,
|
|
977
|
+
},
|
|
978
|
+
pagination: {
|
|
979
|
+
limit: idsFilterMode === 'exact' ? exactIds.length : limit,
|
|
980
|
+
offset: idsFilterMode === 'exact' ? 0 : offset,
|
|
915
981
|
},
|
|
916
|
-
pagination: { limit: ids && ids.length > 0 ? ids.length : limit, offset: ids ? 0 : offset },
|
|
917
982
|
});
|
|
918
|
-
setHasMore(offset + data.vehicles.length < data.vehiclesCount);
|
|
983
|
+
setHasMore(idsFilterMode === 'none' && offset + data.vehicles.length < data.vehiclesCount);
|
|
919
984
|
setTotalVehicles(data.vehiclesCount);
|
|
920
985
|
setVehicles(data.vehicles.map(mapVehicle));
|
|
921
986
|
} catch (fetchError) {
|
|
@@ -923,7 +988,65 @@ export function useVehicles(options: UseVehiclesOptions = {}): UseVehiclesResult
|
|
|
923
988
|
} finally {
|
|
924
989
|
setIsLoading(false);
|
|
925
990
|
}
|
|
926
|
-
}, [
|
|
991
|
+
}, [
|
|
992
|
+
graphQLOptions,
|
|
993
|
+
limit,
|
|
994
|
+
offset,
|
|
995
|
+
search,
|
|
996
|
+
status,
|
|
997
|
+
vehicleType,
|
|
998
|
+
driverId,
|
|
999
|
+
idsFilterMode,
|
|
1000
|
+
exactIds,
|
|
1001
|
+
]);
|
|
1002
|
+
|
|
1003
|
+
const loadMore = useCallback(async () => {
|
|
1004
|
+
if (idsFilterMode === 'exact' || !hasMore || isLoading || isLoadingMore) return;
|
|
1005
|
+
const nextOffset = offset + vehicles.length;
|
|
1006
|
+
setIsLoadingMore(true);
|
|
1007
|
+
setError(null);
|
|
1008
|
+
try {
|
|
1009
|
+
const data = await executeGraphQL<{ vehicles: ApiVehicle[]; vehiclesCount: number }>(
|
|
1010
|
+
graphQLOptions,
|
|
1011
|
+
LIST_VEHICLES_QUERY,
|
|
1012
|
+
{
|
|
1013
|
+
filter: {
|
|
1014
|
+
search: search || undefined,
|
|
1015
|
+
status: status ? toApiVehicleStatus(status) : undefined,
|
|
1016
|
+
type: vehicleType?.toUpperCase(),
|
|
1017
|
+
driverId: driverId || undefined,
|
|
1018
|
+
},
|
|
1019
|
+
pagination: { limit, offset: nextOffset },
|
|
1020
|
+
}
|
|
1021
|
+
);
|
|
1022
|
+
const mapped = data.vehicles.map(mapVehicle);
|
|
1023
|
+
setTotalVehicles(data.vehiclesCount);
|
|
1024
|
+
setHasMore(nextOffset + mapped.length < data.vehiclesCount);
|
|
1025
|
+
setVehicles((previous) => {
|
|
1026
|
+
const existingIds = new Set(previous.map((vehicle) => vehicle.id));
|
|
1027
|
+
return [...previous, ...mapped.filter((vehicle) => !existingIds.has(vehicle.id))];
|
|
1028
|
+
});
|
|
1029
|
+
} catch (fetchError) {
|
|
1030
|
+
setError(
|
|
1031
|
+
fetchError instanceof Error ? fetchError : new Error('Failed to load more vehicles')
|
|
1032
|
+
);
|
|
1033
|
+
} finally {
|
|
1034
|
+
setIsLoadingMore(false);
|
|
1035
|
+
}
|
|
1036
|
+
}, [
|
|
1037
|
+
driverId,
|
|
1038
|
+
graphQLOptions,
|
|
1039
|
+
hasMore,
|
|
1040
|
+
idsFilterMode,
|
|
1041
|
+
isLoading,
|
|
1042
|
+
isLoadingMore,
|
|
1043
|
+
limit,
|
|
1044
|
+
offset,
|
|
1045
|
+
search,
|
|
1046
|
+
status,
|
|
1047
|
+
vehicleType,
|
|
1048
|
+
vehicles.length,
|
|
1049
|
+
]);
|
|
927
1050
|
|
|
928
1051
|
useEffect(() => {
|
|
929
1052
|
void load();
|
|
@@ -960,15 +1083,17 @@ export function useVehicles(options: UseVehiclesOptions = {}): UseVehiclesResult
|
|
|
960
1083
|
vehicleId,
|
|
961
1084
|
}
|
|
962
1085
|
);
|
|
963
|
-
const
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
1086
|
+
const data = await executeGraphQL<{ vehicle: ApiVehicle | null }>(
|
|
1087
|
+
graphQLOptions,
|
|
1088
|
+
GET_VEHICLE_QUERY,
|
|
1089
|
+
{ id: vehicleId }
|
|
1090
|
+
);
|
|
1091
|
+
if (!data.vehicle) throw new Error('Vehicle not found');
|
|
1092
|
+
const updated = mapVehicle(data.vehicle);
|
|
968
1093
|
setVehicles((previous) => previous.map((item) => (item.id === vehicleId ? updated : item)));
|
|
969
1094
|
return updated;
|
|
970
1095
|
},
|
|
971
|
-
[graphQLOptions
|
|
1096
|
+
[graphQLOptions]
|
|
972
1097
|
);
|
|
973
1098
|
|
|
974
1099
|
const deleteVehicle = useCallback(
|
|
@@ -986,8 +1111,10 @@ export function useVehicles(options: UseVehiclesOptions = {}): UseVehiclesResult
|
|
|
986
1111
|
totalVehicles,
|
|
987
1112
|
hasMore,
|
|
988
1113
|
isLoading,
|
|
1114
|
+
isLoadingMore,
|
|
989
1115
|
error,
|
|
990
1116
|
refetch,
|
|
1117
|
+
loadMore,
|
|
991
1118
|
updateVehicleStatus,
|
|
992
1119
|
assignDriver,
|
|
993
1120
|
deleteVehicle,
|
|
@@ -1041,10 +1168,13 @@ interface UseDriversResult {
|
|
|
1041
1168
|
/** True when the last fetch returned a full page (rawCount === limit). */
|
|
1042
1169
|
hasMore: boolean;
|
|
1043
1170
|
isLoading: boolean;
|
|
1171
|
+
/** True while an additional server page is appended. */
|
|
1172
|
+
isLoadingMore: boolean;
|
|
1044
1173
|
error: Error | null;
|
|
1045
1174
|
refetch: () => void;
|
|
1175
|
+
loadMore: () => Promise<void>;
|
|
1046
1176
|
updateDriverStatus: (id: string, status: DriverStatus) => Promise<Driver>;
|
|
1047
|
-
assignVehicle: (driverId: string, vehicleId: string) => Promise<Driver>;
|
|
1177
|
+
assignVehicle: (driverId: string, vehicleId: string | null) => Promise<Driver>;
|
|
1048
1178
|
}
|
|
1049
1179
|
|
|
1050
1180
|
interface UseDriversOptions extends UseListOptions {
|
|
@@ -1063,6 +1193,7 @@ export function useDrivers(options: UseDriversOptions = {}): UseDriversResult {
|
|
|
1063
1193
|
const [totalDrivers, setTotalDrivers] = useState(0);
|
|
1064
1194
|
const [hasMore, setHasMore] = useState(false);
|
|
1065
1195
|
const [isLoading, setIsLoading] = useState(false);
|
|
1196
|
+
const [isLoadingMore, setIsLoadingMore] = useState(false);
|
|
1066
1197
|
const [error, setError] = useState<Error | null>(null);
|
|
1067
1198
|
|
|
1068
1199
|
const load = useCallback(async () => {
|
|
@@ -1091,6 +1222,51 @@ export function useDrivers(options: UseDriversOptions = {}): UseDriversResult {
|
|
|
1091
1222
|
}
|
|
1092
1223
|
}, [graphQLOptions, limit, offset, status, search, licenseClass, vehicleId]);
|
|
1093
1224
|
|
|
1225
|
+
const loadMore = useCallback(async () => {
|
|
1226
|
+
if (!hasMore || isLoading || isLoadingMore) return;
|
|
1227
|
+
const nextOffset = offset + drivers.length;
|
|
1228
|
+
setIsLoadingMore(true);
|
|
1229
|
+
setError(null);
|
|
1230
|
+
try {
|
|
1231
|
+
const data = await executeGraphQL<{ drivers: ApiDriver[]; driversCount: number }>(
|
|
1232
|
+
graphQLOptions,
|
|
1233
|
+
LIST_DRIVERS_QUERY,
|
|
1234
|
+
{
|
|
1235
|
+
filter: {
|
|
1236
|
+
status: status ? toApiDriverStatus(status) : undefined,
|
|
1237
|
+
search: search || undefined,
|
|
1238
|
+
licenseClass: licenseClass || undefined,
|
|
1239
|
+
vehicleId: vehicleId || undefined,
|
|
1240
|
+
},
|
|
1241
|
+
pagination: { limit, offset: nextOffset },
|
|
1242
|
+
}
|
|
1243
|
+
);
|
|
1244
|
+
const mapped = data.drivers.map(mapDriver);
|
|
1245
|
+
setTotalDrivers(data.driversCount);
|
|
1246
|
+
setHasMore(nextOffset + mapped.length < data.driversCount);
|
|
1247
|
+
setDrivers((previous) => {
|
|
1248
|
+
const existingIds = new Set(previous.map((driver) => driver.id));
|
|
1249
|
+
return [...previous, ...mapped.filter((driver) => !existingIds.has(driver.id))];
|
|
1250
|
+
});
|
|
1251
|
+
} catch (fetchError) {
|
|
1252
|
+
setError(fetchError instanceof Error ? fetchError : new Error('Failed to load more drivers'));
|
|
1253
|
+
} finally {
|
|
1254
|
+
setIsLoadingMore(false);
|
|
1255
|
+
}
|
|
1256
|
+
}, [
|
|
1257
|
+
drivers.length,
|
|
1258
|
+
graphQLOptions,
|
|
1259
|
+
hasMore,
|
|
1260
|
+
isLoading,
|
|
1261
|
+
isLoadingMore,
|
|
1262
|
+
limit,
|
|
1263
|
+
licenseClass,
|
|
1264
|
+
offset,
|
|
1265
|
+
search,
|
|
1266
|
+
status,
|
|
1267
|
+
vehicleId,
|
|
1268
|
+
]);
|
|
1269
|
+
|
|
1094
1270
|
useEffect(() => {
|
|
1095
1271
|
void load();
|
|
1096
1272
|
}, [load]);
|
|
@@ -1117,7 +1293,7 @@ export function useDrivers(options: UseDriversOptions = {}): UseDriversResult {
|
|
|
1117
1293
|
);
|
|
1118
1294
|
|
|
1119
1295
|
const assignVehicle = useCallback(
|
|
1120
|
-
async (driverId: string, vehicleId: string): Promise<Driver> => {
|
|
1296
|
+
async (driverId: string, vehicleId: string | null): Promise<Driver> => {
|
|
1121
1297
|
const data = await executeGraphQL<{ assignDriverVehicle: ApiDriver }>(
|
|
1122
1298
|
graphQLOptions,
|
|
1123
1299
|
ASSIGN_DRIVER_VEHICLE_MUTATION,
|
|
@@ -1138,8 +1314,10 @@ export function useDrivers(options: UseDriversOptions = {}): UseDriversResult {
|
|
|
1138
1314
|
totalDrivers,
|
|
1139
1315
|
hasMore,
|
|
1140
1316
|
isLoading,
|
|
1317
|
+
isLoadingMore,
|
|
1141
1318
|
error,
|
|
1142
1319
|
refetch,
|
|
1320
|
+
loadMore,
|
|
1143
1321
|
updateDriverStatus,
|
|
1144
1322
|
assignVehicle,
|
|
1145
1323
|
};
|