@burdenoff/microfe-movethewheels 2026.925.1 → 2026.925.3

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 (47) hide show
  1. package/dist/components/StatusBadge.js +31 -31
  2. package/dist/constants/index.js +19 -5
  3. package/dist/hooks/useCustomers.js +0 -3
  4. package/dist/hooks/useFleet.js +197 -175
  5. package/dist/hooks/useInventory.js +144 -145
  6. package/dist/hooks/useRoutes.js +5 -14
  7. package/dist/movethewheels/components/StatusBadge.d.ts +3 -1
  8. package/dist/movethewheels/constants/index.d.ts +2 -0
  9. package/dist/movethewheels/hooks/useFleet.d.ts +53 -1
  10. package/dist/movethewheels/hooks/useInventory.d.ts +10 -0
  11. package/dist/movethewheels/types/index.d.ts +29 -108
  12. package/dist/node_modules/lucide-react/dist/esm/icons/sticky-note.js +11 -0
  13. package/dist/node_modules/lucide-react/dist/esm/icons/user-x.js +30 -0
  14. package/dist/pages/CustomerDetailsPage.js +666 -344
  15. package/dist/pages/CustomersPage.js +71 -80
  16. package/dist/pages/DriverDetailsPage.js +702 -421
  17. package/dist/pages/DriversPage.js +106 -117
  18. package/dist/pages/FleetPage.js +5 -13
  19. package/dist/pages/InventoryPage.js +4 -4
  20. package/dist/pages/OrderDetailsPage.js +279 -274
  21. package/dist/pages/RouteDetailsPage.js +300 -263
  22. package/dist/pages/RoutesPage.js +130 -67
  23. package/dist/pages/VehicleDetailsPage.js +562 -287
  24. package/dist/pages/VehiclesPage.js +119 -70
  25. package/dist/pages/WarehouseDetailsPage.js +410 -370
  26. package/dist/pages/WarehousesPage.js +131 -82
  27. package/package.json +1 -1
  28. package/src/movethewheels/components/StatusBadge.tsx +16 -2
  29. package/src/movethewheels/constants/index.ts +21 -0
  30. package/src/movethewheels/hooks/useCustomers.ts +0 -3
  31. package/src/movethewheels/hooks/useFleet.ts +236 -62
  32. package/src/movethewheels/hooks/useInventory.ts +76 -32
  33. package/src/movethewheels/hooks/useRoutes.ts +17 -12
  34. package/src/movethewheels/pages/CustomerDetailsPage.tsx +376 -109
  35. package/src/movethewheels/pages/CustomersPage.tsx +6 -15
  36. package/src/movethewheels/pages/DriverDetailsPage.tsx +516 -156
  37. package/src/movethewheels/pages/DriversPage.tsx +28 -11
  38. package/src/movethewheels/pages/FleetPage.tsx +9 -11
  39. package/src/movethewheels/pages/InventoryPage.tsx +14 -5
  40. package/src/movethewheels/pages/OrderDetailsPage.tsx +12 -7
  41. package/src/movethewheels/pages/RouteDetailsPage.tsx +89 -41
  42. package/src/movethewheels/pages/RoutesPage.tsx +66 -2
  43. package/src/movethewheels/pages/VehicleDetailsPage.tsx +282 -20
  44. package/src/movethewheels/pages/VehiclesPage.tsx +68 -3
  45. package/src/movethewheels/pages/WarehouseDetailsPage.tsx +213 -135
  46. package/src/movethewheels/pages/WarehousesPage.tsx +77 -16
  47. package/src/movethewheels/types/index.ts +30 -108
@@ -1,6 +1,6 @@
1
1
  import { useCallback, useEffect, useMemo, useState } from 'react';
2
2
  import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
3
- import type { Driver, DriverStatus, FleetFilter, Vehicle, VehicleStatus } from '../types';
3
+ import type { Driver, DriverStatus, FleetFilter, Location, Vehicle, VehicleStatus } from '../types';
4
4
  import { executeGraphQL } from '../utils/graphqlClient';
5
5
 
6
6
  interface UseFleetOptions {
@@ -15,6 +15,14 @@ export interface CreateVehicleInput {
15
15
  make?: string;
16
16
  model?: string;
17
17
  licensePlate?: string;
18
+ /** Free-text, e.g. "owned", "leased". */
19
+ ownershipType?: string;
20
+ year?: number;
21
+ /** VehicleStatus enum value, e.g. `ACTIVE`, `MAINTENANCE`. */
22
+ status?: string;
23
+ fuelType?: string;
24
+ fuelEfficiencyLPer100Km?: number;
25
+ capacity?: { weight?: number; volume?: number; pallets?: number };
18
26
  }
19
27
 
20
28
  export interface CreateDriverInput {
@@ -24,6 +32,24 @@ export interface CreateDriverInput {
24
32
  email?: string;
25
33
  phone?: string;
26
34
  licenseNumber?: string;
35
+ licenseClass?: string;
36
+ licenseExpiry?: string;
37
+ /** DriverStatus enum value, e.g. `AVAILABLE`, `OFF_DUTY`. */
38
+ status?: string;
39
+ vehicleId?: string;
40
+ rating?: number;
41
+ notes?: string;
42
+ hireDate?: string;
43
+ baseSalary?: number;
44
+ bonusYtd?: number;
45
+ payCurrency?: string;
46
+ }
47
+
48
+ /** Superset of `CreateDriverInput` with fields that only make sense on an existing driver. */
49
+ export interface UpdateDriverInput extends CreateDriverInput {
50
+ onLeaveUntil?: string | null;
51
+ terminatedAt?: string;
52
+ terminationReason?: string;
27
53
  }
28
54
 
29
55
  interface UseFleetResult {
@@ -37,7 +63,8 @@ interface UseFleetResult {
37
63
  createVehicle: (input: CreateVehicleInput) => Promise<Vehicle>;
38
64
  createDriver: (input: CreateDriverInput) => Promise<Driver>;
39
65
  updateVehicle: (id: string, input: CreateVehicleInput) => Promise<Vehicle>;
40
- updateDriver: (id: string, input: CreateDriverInput) => Promise<Driver>;
66
+ updateDriver: (id: string, input: UpdateDriverInput) => Promise<Driver>;
67
+ deleteVehicle: (id: string) => Promise<void>;
41
68
  }
42
69
 
43
70
  export interface ApiVehicle {
@@ -52,6 +79,8 @@ export interface ApiVehicle {
52
79
  capacity?: Record<string, unknown> | null;
53
80
  status: string;
54
81
  currentLocation?: Record<string, unknown> | null;
82
+ fuelType?: string | null;
83
+ fuelEfficiencyLPer100Km?: number | null;
55
84
  fuelLevel?: number | null;
56
85
  mileage?: number | null;
57
86
  lastServiceDate?: string | null;
@@ -76,6 +105,14 @@ export interface ApiDriver {
76
105
  currentLocation?: Record<string, unknown> | null;
77
106
  rating?: number | null;
78
107
  vehicleId?: string | null;
108
+ notes?: string | null;
109
+ hireDate?: string | null;
110
+ baseSalary?: number | null;
111
+ bonusYtd?: number | null;
112
+ payCurrency?: string | null;
113
+ onLeaveUntil?: string | null;
114
+ terminatedAt?: string | null;
115
+ terminationReason?: string | null;
79
116
  createdAt: string;
80
117
  }
81
118
 
@@ -93,6 +130,8 @@ const LIST_VEHICLES_QUERY = `
93
130
  capacity
94
131
  status
95
132
  currentLocation
133
+ fuelType
134
+ fuelEfficiencyLPer100Km
96
135
  fuelLevel
97
136
  mileage
98
137
  lastServiceDate
@@ -122,6 +161,14 @@ const LIST_DRIVERS_QUERY = `
122
161
  currentLocation
123
162
  rating
124
163
  vehicleId
164
+ notes
165
+ hireDate
166
+ baseSalary
167
+ bonusYtd
168
+ payCurrency
169
+ onLeaveUntil
170
+ terminatedAt
171
+ terminationReason
125
172
  createdAt
126
173
  }
127
174
  driversCount(filter: $filter)
@@ -142,6 +189,8 @@ const GET_VEHICLE_QUERY = `
142
189
  capacity
143
190
  status
144
191
  currentLocation
192
+ fuelType
193
+ fuelEfficiencyLPer100Km
145
194
  fuelLevel
146
195
  mileage
147
196
  lastServiceDate
@@ -170,11 +219,25 @@ const GET_DRIVER_QUERY = `
170
219
  currentLocation
171
220
  rating
172
221
  vehicleId
222
+ notes
223
+ hireDate
224
+ baseSalary
225
+ bonusYtd
226
+ payCurrency
227
+ onLeaveUntil
228
+ terminatedAt
229
+ terminationReason
173
230
  createdAt
174
231
  }
175
232
  }
176
233
  `;
177
234
 
235
+ const DELETE_VEHICLE_MUTATION = `
236
+ mutation DeleteVehicle($id: ID!) {
237
+ deleteVehicle(id: $id)
238
+ }
239
+ `;
240
+
178
241
  const UPDATE_VEHICLE_STATUS_MUTATION = `
179
242
  mutation UpdateVehicleStatus($id: ID!, $status: VehicleStatus!) {
180
243
  updateVehicleStatus(id: $id, status: $status) {
@@ -189,6 +252,8 @@ const UPDATE_VEHICLE_STATUS_MUTATION = `
189
252
  capacity
190
253
  status
191
254
  currentLocation
255
+ fuelType
256
+ fuelEfficiencyLPer100Km
192
257
  fuelLevel
193
258
  mileage
194
259
  createdAt
@@ -214,6 +279,14 @@ const ASSIGN_DRIVER_VEHICLE_MUTATION = `
214
279
  currentLocation
215
280
  rating
216
281
  vehicleId
282
+ notes
283
+ hireDate
284
+ baseSalary
285
+ bonusYtd
286
+ payCurrency
287
+ onLeaveUntil
288
+ terminatedAt
289
+ terminationReason
217
290
  createdAt
218
291
  }
219
292
  }
@@ -236,6 +309,14 @@ const UPDATE_DRIVER_STATUS_MUTATION = `
236
309
  currentLocation
237
310
  rating
238
311
  vehicleId
312
+ notes
313
+ hireDate
314
+ baseSalary
315
+ bonusYtd
316
+ payCurrency
317
+ onLeaveUntil
318
+ terminatedAt
319
+ terminationReason
239
320
  createdAt
240
321
  }
241
322
  }
@@ -255,6 +336,8 @@ const CREATE_VEHICLE_MUTATION = `
255
336
  capacity
256
337
  status
257
338
  currentLocation
339
+ fuelType
340
+ fuelEfficiencyLPer100Km
258
341
  fuelLevel
259
342
  mileage
260
343
  createdAt
@@ -280,6 +363,14 @@ const CREATE_DRIVER_MUTATION = `
280
363
  currentLocation
281
364
  rating
282
365
  vehicleId
366
+ notes
367
+ hireDate
368
+ baseSalary
369
+ bonusYtd
370
+ payCurrency
371
+ onLeaveUntil
372
+ terminatedAt
373
+ terminationReason
283
374
  createdAt
284
375
  }
285
376
  }
@@ -299,6 +390,8 @@ const UPDATE_VEHICLE_MUTATION = `
299
390
  capacity
300
391
  status
301
392
  currentLocation
393
+ fuelType
394
+ fuelEfficiencyLPer100Km
302
395
  fuelLevel
303
396
  mileage
304
397
  createdAt
@@ -324,6 +417,14 @@ const UPDATE_DRIVER_MUTATION = `
324
417
  currentLocation
325
418
  rating
326
419
  vehicleId
420
+ notes
421
+ hireDate
422
+ baseSalary
423
+ bonusYtd
424
+ payCurrency
425
+ onLeaveUntil
426
+ terminatedAt
427
+ terminationReason
327
428
  createdAt
328
429
  }
329
430
  }
@@ -367,12 +468,20 @@ function mapVehicleStatus(status: string): VehicleStatus {
367
468
  return 'assigned';
368
469
  case 'MAINTENANCE':
369
470
  return 'maintenance';
471
+ case 'DECOMMISSIONED':
472
+ return 'decommissioned';
370
473
  default:
371
474
  return 'out_of_service';
372
475
  }
373
476
  }
374
477
 
375
- function toApiVehicleStatus(status: VehicleStatus): string {
478
+ /**
479
+ * Exported so the vehicle edit form's status field can round-trip (BOFF-7909).
480
+ * `decommissioned` must stay distinct from `out_of_service` — collapsing them
481
+ * used to silently rewrite a DECOMMISSIONED vehicle to INACTIVE on any save
482
+ * that touched status, even without the user changing it.
483
+ */
484
+ export function toApiVehicleStatus(status: VehicleStatus): string {
376
485
  switch (status) {
377
486
  case 'available':
378
487
  return 'ACTIVE';
@@ -380,6 +489,8 @@ function toApiVehicleStatus(status: VehicleStatus): string {
380
489
  return 'IN_TRANSIT';
381
490
  case 'maintenance':
382
491
  return 'MAINTENANCE';
492
+ case 'decommissioned':
493
+ return 'DECOMMISSIONED';
383
494
  default:
384
495
  return 'INACTIVE';
385
496
  }
@@ -393,12 +504,15 @@ function mapDriverStatus(status: string): DriverStatus {
393
504
  return 'busy';
394
505
  case 'ON_BREAK':
395
506
  return 'on_break';
507
+ case 'INACTIVE':
508
+ return 'inactive';
396
509
  default:
397
510
  return 'offline';
398
511
  }
399
512
  }
400
513
 
401
- function toApiDriverStatus(status: DriverStatus): string {
514
+ /** Exported so the driver edit form's status field can round-trip losslessly (BOFF-7909). */
515
+ export function toApiDriverStatus(status: DriverStatus): string {
402
516
  switch (status) {
403
517
  case 'available':
404
518
  return 'AVAILABLE';
@@ -406,11 +520,40 @@ function toApiDriverStatus(status: DriverStatus): string {
406
520
  return 'ON_DELIVERY';
407
521
  case 'on_break':
408
522
  return 'ON_BREAK';
523
+ case 'inactive':
524
+ return 'INACTIVE';
409
525
  default:
410
526
  return 'OFF_DUTY';
411
527
  }
412
528
  }
413
529
 
530
+ /**
531
+ * `currentLocation` is an untyped JSON blob; every real writer (the IoT
532
+ * telemetry consumer — `toVehicleLocation()` in
533
+ * wspace-movethewheels-svc/src/events/iot-location-consumer.ts — and the
534
+ * `updateVehicleLocation`/`updateDriverLocation`-style manual mutations)
535
+ * stores `lat`/`lng`/`recordedAt`, never `latitude`/`longitude`. Reading the
536
+ * latter (as this used to) silently produced (0, 0) for every real fix and
537
+ * fabricated the "last updated" timestamp as "now" on every render. Parse
538
+ * the actual shape instead.
539
+ */
540
+ function parseCurrentLocation(
541
+ raw: Record<string, unknown> | null | undefined
542
+ ): Location | undefined {
543
+ if (!raw) return undefined;
544
+ const lat = raw.lat as number | undefined;
545
+ const lng = raw.lng as number | undefined;
546
+ if (typeof lat !== 'number' || typeof lng !== 'number') return undefined;
547
+ const recordedAt = raw.recordedAt as string | undefined;
548
+ return {
549
+ latitude: lat,
550
+ longitude: lng,
551
+ address: (raw.address as string | undefined) ?? undefined,
552
+ accuracy: typeof raw.accuracy === 'number' ? raw.accuracy : undefined,
553
+ timestamp: recordedAt ? new Date(recordedAt) : undefined,
554
+ };
555
+ }
556
+
414
557
  export function mapVehicle(vehicle: ApiVehicle): Vehicle {
415
558
  return {
416
559
  id: vehicle.id,
@@ -420,7 +563,7 @@ export function mapVehicle(vehicle: ApiVehicle): Vehicle {
420
563
  model: vehicle.model ?? '',
421
564
  licensePlate: vehicle.licensePlate ?? '',
422
565
  ownershipType: vehicle.ownershipType ?? undefined,
423
- year: vehicle.year ?? new Date().getFullYear(),
566
+ year: vehicle.year ?? undefined,
424
567
  capacity: {
425
568
  weight: 0,
426
569
  volume: 0,
@@ -428,17 +571,12 @@ export function mapVehicle(vehicle: ApiVehicle): Vehicle {
428
571
  },
429
572
  status: mapVehicleStatus(vehicle.status),
430
573
  driverId: undefined,
431
- currentLocation: vehicle.currentLocation
432
- ? {
433
- latitude: Number((vehicle.currentLocation.latitude as number | undefined) ?? 0),
434
- longitude: Number((vehicle.currentLocation.longitude as number | undefined) ?? 0),
435
- address: (vehicle.currentLocation.address as string | undefined) ?? undefined,
436
- timestamp: new Date(),
437
- }
438
- : undefined,
574
+ currentLocation: parseCurrentLocation(vehicle.currentLocation),
439
575
  lastMaintenance: vehicle.lastServiceDate ? new Date(vehicle.lastServiceDate) : undefined,
440
576
  nextMaintenance: vehicle.nextServiceDueDate ? new Date(vehicle.nextServiceDueDate) : undefined,
441
577
  nextMaintenanceMileage: vehicle.nextServiceDueMileage ?? undefined,
578
+ fuelType: vehicle.fuelType ?? undefined,
579
+ fuelEfficiencyLPer100Km: vehicle.fuelEfficiencyLPer100Km ?? undefined,
442
580
  fuelLevel: vehicle.fuelLevel ?? undefined,
443
581
  mileage: vehicle.mileage ?? undefined,
444
582
  documents: [],
@@ -460,52 +598,17 @@ export function mapDriver(driver: ApiDriver): Driver {
460
598
  vehicleIds: driver.vehicleId ? [driver.vehicleId] : [],
461
599
  assignedVehicle: driver.vehicleId ?? undefined,
462
600
  status: mapDriverStatus(driver.status),
463
- currentLocation: driver.currentLocation
464
- ? {
465
- latitude: Number((driver.currentLocation.latitude as number | undefined) ?? 0),
466
- longitude: Number((driver.currentLocation.longitude as number | undefined) ?? 0),
467
- address: (driver.currentLocation.address as string | undefined) ?? undefined,
468
- timestamp: new Date(),
469
- }
470
- : undefined,
471
- joinDate: new Date(driver.createdAt),
472
- baseSalary: 0,
473
- completedOrders: 0,
474
- todayOrders: 0,
475
- specializations: [],
476
- workingHours: {
477
- startTime: '09:00',
478
- endTime: '18:00',
479
- workingDays: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'],
480
- maxHoursPerDay: 8,
481
- maxHoursPerWeek: 40,
482
- currentHoursToday: 0,
483
- currentHoursThisWeek: 0,
484
- },
485
- ratings: {
486
- overall: driver.rating ?? 0,
487
- punctuality: driver.rating ?? 0,
488
- communication: driver.rating ?? 0,
489
- carefulHandling: driver.rating ?? 0,
490
- professionalism: driver.rating ?? 0,
491
- onTimeDelivery: driver.rating ?? 0,
492
- totalRatings: 0,
493
- },
601
+ currentLocation: parseCurrentLocation(driver.currentLocation),
602
+ rating: driver.rating ?? 0,
603
+ notes: driver.notes ?? undefined,
604
+ hireDate: driver.hireDate ? new Date(driver.hireDate) : undefined,
605
+ baseSalary: driver.baseSalary ?? undefined,
606
+ bonusYtd: driver.bonusYtd ?? undefined,
607
+ payCurrency: driver.payCurrency ?? undefined,
608
+ onLeaveUntil: driver.onLeaveUntil ? new Date(driver.onLeaveUntil) : undefined,
609
+ terminatedAt: driver.terminatedAt ? new Date(driver.terminatedAt) : undefined,
610
+ terminationReason: driver.terminationReason ?? undefined,
494
611
  documents: [],
495
- earnings: {
496
- totalEarnings: 0,
497
- weeklyEarnings: 0,
498
- monthlyEarnings: 0,
499
- pendingPayments: 0,
500
- completedDeliveries: 0,
501
- bonuses: 0,
502
- },
503
- compliance: {
504
- hoursOfService: true,
505
- documentation: true,
506
- vehicleInspection: true,
507
- lastUpdate: new Date(),
508
- },
509
612
  createdAt: new Date(driver.createdAt),
510
613
  };
511
614
  }
@@ -599,6 +702,12 @@ export function useFleet(options: UseFleetOptions = {}): UseFleetResult {
599
702
  make: input.make,
600
703
  model: input.model,
601
704
  licensePlate: input.licensePlate,
705
+ ownershipType: input.ownershipType,
706
+ year: input.year,
707
+ status: input.status,
708
+ fuelType: input.fuelType,
709
+ fuelEfficiencyLPer100Km: input.fuelEfficiencyLPer100Km,
710
+ capacity: input.capacity,
602
711
  },
603
712
  }
604
713
  );
@@ -622,6 +731,16 @@ export function useFleet(options: UseFleetOptions = {}): UseFleetResult {
622
731
  email: input.email,
623
732
  phone: input.phone,
624
733
  licenseNumber: input.licenseNumber,
734
+ licenseClass: input.licenseClass,
735
+ licenseExpiry: input.licenseExpiry,
736
+ status: input.status,
737
+ vehicleId: input.vehicleId,
738
+ rating: input.rating,
739
+ notes: input.notes,
740
+ hireDate: input.hireDate,
741
+ baseSalary: input.baseSalary,
742
+ bonusYtd: input.bonusYtd,
743
+ payCurrency: input.payCurrency,
625
744
  },
626
745
  }
627
746
  );
@@ -645,6 +764,12 @@ export function useFleet(options: UseFleetOptions = {}): UseFleetResult {
645
764
  make: input.make,
646
765
  model: input.model,
647
766
  licensePlate: input.licensePlate,
767
+ ownershipType: input.ownershipType,
768
+ year: input.year,
769
+ status: input.status,
770
+ fuelType: input.fuelType,
771
+ fuelEfficiencyLPer100Km: input.fuelEfficiencyLPer100Km,
772
+ capacity: input.capacity,
648
773
  },
649
774
  }
650
775
  );
@@ -656,7 +781,7 @@ export function useFleet(options: UseFleetOptions = {}): UseFleetResult {
656
781
  );
657
782
 
658
783
  const updateDriver = useCallback(
659
- async (id: string, input: CreateDriverInput): Promise<Driver> => {
784
+ async (id: string, input: UpdateDriverInput): Promise<Driver> => {
660
785
  const data = await executeGraphQL<{ updateDriver: ApiDriver }>(
661
786
  graphQLOptions,
662
787
  UPDATE_DRIVER_MUTATION,
@@ -668,6 +793,19 @@ export function useFleet(options: UseFleetOptions = {}): UseFleetResult {
668
793
  email: input.email,
669
794
  phone: input.phone,
670
795
  licenseNumber: input.licenseNumber,
796
+ licenseClass: input.licenseClass,
797
+ licenseExpiry: input.licenseExpiry,
798
+ status: input.status,
799
+ vehicleId: input.vehicleId,
800
+ rating: input.rating,
801
+ notes: input.notes,
802
+ hireDate: input.hireDate,
803
+ baseSalary: input.baseSalary,
804
+ bonusYtd: input.bonusYtd,
805
+ payCurrency: input.payCurrency,
806
+ onLeaveUntil: input.onLeaveUntil,
807
+ terminatedAt: input.terminatedAt,
808
+ terminationReason: input.terminationReason,
671
809
  },
672
810
  }
673
811
  );
@@ -678,6 +816,16 @@ export function useFleet(options: UseFleetOptions = {}): UseFleetResult {
678
816
  [graphQLOptions]
679
817
  );
680
818
 
819
+ const deleteVehicle = useCallback(
820
+ async (id: string): Promise<void> => {
821
+ await executeGraphQL<{ deleteVehicle: boolean }>(graphQLOptions, DELETE_VEHICLE_MUTATION, {
822
+ id,
823
+ });
824
+ setVehicles((previous) => previous.filter((item) => item.id !== id));
825
+ },
826
+ [graphQLOptions]
827
+ );
828
+
681
829
  return {
682
830
  vehicles,
683
831
  drivers,
@@ -690,6 +838,7 @@ export function useFleet(options: UseFleetOptions = {}): UseFleetResult {
690
838
  createDriver,
691
839
  updateVehicle,
692
840
  updateDriver,
841
+ deleteVehicle,
693
842
  };
694
843
  }
695
844
 
@@ -704,6 +853,8 @@ interface UseVehiclesOptions extends UseListOptions {
704
853
  vehicleType?: Vehicle['type'];
705
854
  /** Narrows to vehicles linked to this driver server-side (VehicleFilterInput.driverId). */
706
855
  driverId?: string;
856
+ /** Fetch exactly these vehicles by id, regardless of how many there are. */
857
+ ids?: readonly string[];
707
858
  }
708
859
 
709
860
  interface UseVehiclesResult {
@@ -716,18 +867,29 @@ interface UseVehiclesResult {
716
867
  refetch: () => void;
717
868
  updateVehicleStatus: (id: string, status: VehicleStatus) => Promise<Vehicle>;
718
869
  assignDriver: (vehicleId: string, driverId: string) => Promise<Vehicle>;
870
+ deleteVehicle: (id: string) => Promise<void>;
719
871
  }
720
872
 
721
873
  export function useVehicles(options: UseVehiclesOptions = {}): UseVehiclesResult {
722
- const { limit = 20, offset = 0, search, status, vehicleType, driverId } = options;
874
+ const { limit = 20, offset = 0, search, status, vehicleType, driverId, ids } = options;
723
875
  const graphQLOptions = useGraphqlOptions();
724
876
  const [vehicles, setVehicles] = useState<Vehicle[]>([]);
725
877
  const [totalVehicles, setTotalVehicles] = useState(0);
726
878
  const [hasMore, setHasMore] = useState(false);
727
879
  const [isLoading, setIsLoading] = useState(false);
728
880
  const [error, setError] = useState<Error | null>(null);
881
+ // Stable dependency key for the `ids` array — a caller passing a fresh array
882
+ // literal each render must not defeat the callback's memoization.
883
+ const idsKey = ids ? [...ids].sort().join(',') : '';
729
884
 
730
885
  const load = useCallback(async () => {
886
+ if (ids && ids.length === 0) {
887
+ // Nothing to look up — skip the request and clear stale results.
888
+ setVehicles([]);
889
+ setTotalVehicles(0);
890
+ setHasMore(false);
891
+ return;
892
+ }
731
893
  setIsLoading(true);
732
894
  setError(null);
733
895
  try {
@@ -740,8 +902,9 @@ export function useVehicles(options: UseVehiclesOptions = {}): UseVehiclesResult
740
902
  status: status ? toApiVehicleStatus(status) : undefined,
741
903
  type: vehicleType?.toUpperCase(),
742
904
  driverId: driverId || undefined,
905
+ ids: ids && ids.length > 0 ? [...ids] : undefined,
743
906
  },
744
- pagination: { limit, offset },
907
+ pagination: { limit: ids && ids.length > 0 ? ids.length : limit, offset: ids ? 0 : offset },
745
908
  });
746
909
  setHasMore(offset + data.vehicles.length < data.vehiclesCount);
747
910
  setTotalVehicles(data.vehiclesCount);
@@ -751,7 +914,7 @@ export function useVehicles(options: UseVehiclesOptions = {}): UseVehiclesResult
751
914
  } finally {
752
915
  setIsLoading(false);
753
916
  }
754
- }, [graphQLOptions, limit, offset, search, status, vehicleType, driverId]);
917
+ }, [graphQLOptions, limit, offset, search, status, vehicleType, driverId, idsKey]);
755
918
 
756
919
  useEffect(() => {
757
920
  void load();
@@ -799,6 +962,16 @@ export function useVehicles(options: UseVehiclesOptions = {}): UseVehiclesResult
799
962
  [graphQLOptions, vehicles]
800
963
  );
801
964
 
965
+ const deleteVehicle = useCallback(
966
+ async (id: string): Promise<void> => {
967
+ await executeGraphQL<{ deleteVehicle: boolean }>(graphQLOptions, DELETE_VEHICLE_MUTATION, {
968
+ id,
969
+ });
970
+ setVehicles((previous) => previous.filter((item) => item.id !== id));
971
+ },
972
+ [graphQLOptions]
973
+ );
974
+
802
975
  return {
803
976
  vehicles,
804
977
  totalVehicles,
@@ -808,6 +981,7 @@ export function useVehicles(options: UseVehiclesOptions = {}): UseVehiclesResult
808
981
  refetch,
809
982
  updateVehicleStatus,
810
983
  assignDriver,
984
+ deleteVehicle,
811
985
  };
812
986
  }
813
987