@burdenoff/microfe-movethewheels 2026.930.1 → 2026.930.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.
@@ -19,15 +19,16 @@ import { FormError as L } from "../components/FormError.js";
19
19
  import { Notice as R, useNotice as z } from "../components/Notice.js";
20
20
  import { formatDate as B, getInitials as V } from "../utils/formatters.js";
21
21
  import { DocumentsCard as H } from "../components/DocumentsCard.js";
22
- import { useRecordVehicleService as U } from "../hooks/useFleetIntelligence.js";
23
- import { VehicleServiceCard as W } from "../components/VehicleServiceCard.js";
22
+ import { useRefetchOnLeaveEdit as U } from "../hooks/useRefetchOnLeaveEdit.js";
23
+ import { useRecordVehicleService as W } from "../hooks/useFleetIntelligence.js";
24
+ import { VehicleServiceCard as ee } from "../components/VehicleServiceCard.js";
24
25
  import { useEffect as G, useState as K } from "react";
25
- import { useLocation as ee, useParams as te } from "react-router";
26
+ import { useLocation as te, useParams as ne } from "react-router";
26
27
  import { jsx as q, jsxs as J } from "react/jsx-runtime";
27
- import { useI18n as ne } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
28
- import { EmphasisPanel as Y, NextSteps as re, PagePurpose as X, ResponsiveDialog as Z, ResponsiveDialogContent as Q, ResponsiveDialogDescription as $, ResponsiveDialogFooter as ie, ResponsiveDialogHeader as ae, ResponsiveDialogTitle as oe } from "@burdenoff/fe-libs/ui";
28
+ import { useI18n as re } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
29
+ import { EmphasisPanel as Y, NextSteps as ie, PagePurpose as X, ResponsiveDialog as Z, ResponsiveDialogContent as Q, ResponsiveDialogDescription as $, ResponsiveDialogFooter as ae, ResponsiveDialogHeader as oe, ResponsiveDialogTitle as se } from "@burdenoff/fe-libs/ui";
29
30
  //#region src/movethewheels/pages/VehicleDetailsPage.tsx
30
- var se = [
31
+ var ce = [
31
32
  {
32
33
  value: "TRUCK",
33
34
  label: "Truck"
@@ -60,10 +61,10 @@ var se = [
60
61
  value: "DRONE",
61
62
  label: "Drone"
62
63
  }
63
- ], ce = Object.entries(P).map(([e, t]) => ({
64
+ ], le = Object.entries(P).map(([e, t]) => ({
64
65
  value: e,
65
66
  label: t
66
- })), le = [
67
+ })), ue = [
67
68
  {
68
69
  value: "DIESEL",
69
70
  label: "Diesel"
@@ -84,7 +85,7 @@ var se = [
84
85
  value: "HYBRID",
85
86
  label: "Hybrid"
86
87
  }
87
- ], ue = [
88
+ ], de = [
88
89
  {
89
90
  value: "owned",
90
91
  label: "Owned"
@@ -98,7 +99,7 @@ var se = [
98
99
  label: "Rented"
99
100
  }
100
101
  ];
101
- function de({ editId: t }) {
102
+ function fe({ editId: t }) {
102
103
  let { basePath: n, navigate: r } = d(), { createVehicle: i, updateVehicle: o, deleteVehicle: l } = p(), { vehicle: h } = m(t), g = !!t, [_, T] = K({
103
104
  vehicleNumber: "",
104
105
  type: "VAN",
@@ -240,7 +241,7 @@ function de({ editId: t }) {
240
241
  children: [/* @__PURE__ */ q(k, {
241
242
  id: "type",
242
243
  children: /* @__PURE__ */ q(A, {})
243
- }), /* @__PURE__ */ q(D, { children: se.map((e) => /* @__PURE__ */ q(O, {
244
+ }), /* @__PURE__ */ q(D, { children: ce.map((e) => /* @__PURE__ */ q(O, {
244
245
  value: e.value,
245
246
  children: e.label
246
247
  }, e.value)) })]
@@ -314,7 +315,7 @@ function de({ editId: t }) {
314
315
  children: [/* @__PURE__ */ q(k, {
315
316
  id: "ownershipType",
316
317
  children: /* @__PURE__ */ q(A, { placeholder: "Select ownership" })
317
- }), /* @__PURE__ */ q(D, { children: ue.map((e) => /* @__PURE__ */ q(O, {
318
+ }), /* @__PURE__ */ q(D, { children: de.map((e) => /* @__PURE__ */ q(O, {
318
319
  value: e.value,
319
320
  children: e.label
320
321
  }, e.value)) })]
@@ -352,7 +353,7 @@ function de({ editId: t }) {
352
353
  children: [/* @__PURE__ */ q(k, {
353
354
  id: "vehicleStatus",
354
355
  children: /* @__PURE__ */ q(A, {})
355
- }), /* @__PURE__ */ q(D, { children: ce.map((e) => /* @__PURE__ */ q(O, {
356
+ }), /* @__PURE__ */ q(D, { children: le.map((e) => /* @__PURE__ */ q(O, {
356
357
  value: e.value,
357
358
  children: e.label
358
359
  }, e.value)) })]
@@ -370,7 +371,7 @@ function de({ editId: t }) {
370
371
  }), /* @__PURE__ */ J(E, {
371
372
  value: _.fuelType || void 0,
372
373
  onValueChange: (e) => {
373
- let t = le.find((t) => t.value === e)?.value;
374
+ let t = ue.find((t) => t.value === e)?.value;
374
375
  T((e) => ({
375
376
  ...e,
376
377
  fuelType: t ?? ""
@@ -379,7 +380,7 @@ function de({ editId: t }) {
379
380
  children: [/* @__PURE__ */ q(k, {
380
381
  id: "fuelType",
381
382
  children: /* @__PURE__ */ q(A, { placeholder: "Select fuel type" })
382
- }), /* @__PURE__ */ q(D, { children: le.map((e) => /* @__PURE__ */ q(O, {
383
+ }), /* @__PURE__ */ q(D, { children: ue.map((e) => /* @__PURE__ */ q(O, {
383
384
  value: e.value,
384
385
  children: e.label
385
386
  }, e.value)) })]
@@ -484,7 +485,7 @@ function de({ editId: t }) {
484
485
  open: I,
485
486
  onOpenChange: R,
486
487
  children: /* @__PURE__ */ J(Q, { children: [
487
- /* @__PURE__ */ J(ae, { children: [/* @__PURE__ */ q(oe, { children: "Delete this vehicle?" }), /* @__PURE__ */ J($, { children: [
488
+ /* @__PURE__ */ J(oe, { children: [/* @__PURE__ */ q(se, { children: "Delete this vehicle?" }), /* @__PURE__ */ J($, { children: [
488
489
  "This removes ",
489
490
  _.vehicleNumber || "this vehicle",
490
491
  " from the active fleet. It cannot be undone from here."
@@ -493,7 +494,7 @@ function de({ editId: t }) {
493
494
  className: "px-1",
494
495
  children: /* @__PURE__ */ q(L, { message: V })
495
496
  }),
496
- /* @__PURE__ */ J(ie, { children: [/* @__PURE__ */ q(v, {
497
+ /* @__PURE__ */ J(ae, { children: [/* @__PURE__ */ q(v, {
497
498
  variant: "outline",
498
499
  onClick: () => R(!1),
499
500
  disabled: z,
@@ -520,12 +521,14 @@ function de({ editId: t }) {
520
521
  ]
521
522
  });
522
523
  }
523
- function fe() {
524
- let { vehicleId: a } = te(), { basePath: s, navigate: f } = d(), { vehicle: p, isLoading: C, error: w, refetch: E } = m(a), D = U(), { message: O, notify: k } = z(), { t: A } = ne(), P = (e, t) => {
524
+ function pe() {
525
+ let { vehicleId: a } = ne(), { basePath: s, navigate: f } = d(), { vehicle: p, isLoading: C, error: w, refetch: E } = m(a), D = W(), { message: O, notify: k } = z(), { t: A } = re(), P = (e, t) => {
525
526
  let n = A(e);
526
527
  return n === e ? t : n;
527
- }, L = ee().pathname.endsWith("/edit"), { drivers: G, isLoading: K, error: Z } = h(L ? void 0 : a), Q = () => f?.(u(s, "fleet/vehicles")), $ = (e) => f?.(u(s, e));
528
- return a ? L ? /* @__PURE__ */ q(de, { editId: a }) : C ? /* @__PURE__ */ q(M, { message: "Loading vehicle details..." }) : w || !p ? /* @__PURE__ */ q(j, {
528
+ }, L = te().pathname.endsWith("/edit");
529
+ U(L, E);
530
+ let { drivers: G, isLoading: K, error: Z } = h(L ? void 0 : a), Q = () => f?.(u(s, "fleet/vehicles")), $ = (e) => f?.(u(s, e));
531
+ return a ? L ? /* @__PURE__ */ q(fe, { editId: a }) : C ? /* @__PURE__ */ q(M, { message: "Loading vehicle details..." }) : w || !p ? /* @__PURE__ */ q(j, {
529
532
  title: "Vehicle not found",
530
533
  onRetry: E
531
534
  }) : /* @__PURE__ */ J(N, {
@@ -555,7 +558,7 @@ function fe() {
555
558
  icon: /* @__PURE__ */ q(c, { size: 18 }),
556
559
  children: "The full record for this vehicle — specs, capacity, fuel and mileage, who is driving it and where it is right now. Use it to confirm a vehicle is fuelled, crewed and the right size before dispatching it on a delivery."
557
560
  }),
558
- /* @__PURE__ */ q(re, {
561
+ /* @__PURE__ */ q(ie, {
559
562
  storageKey: "movethewheels-vehicle-details",
560
563
  steps: [{
561
564
  id: "assign",
@@ -773,7 +776,7 @@ function fe() {
773
776
  })]
774
777
  }, e.id))
775
778
  })] }),
776
- /* @__PURE__ */ q(W, {
779
+ /* @__PURE__ */ q(ee, {
777
780
  vehicle: p,
778
781
  t: P,
779
782
  onRecord: async (e) => {
@@ -794,7 +797,7 @@ function fe() {
794
797
  })]
795
798
  })
796
799
  ]
797
- }) : /* @__PURE__ */ q(de, {});
800
+ }) : /* @__PURE__ */ q(fe, {});
798
801
  }
799
802
  //#endregion
800
- export { fe as default };
803
+ export { pe as default };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-movethewheels",
3
- "version": "2026.930.1",
3
+ "version": "2026.930.3",
4
4
  "description": "Burdenoff MoveTheWheels Microfrontend - Logistics & Supply Chain Management Platform",
5
5
  "type": "module",
6
6
  "files": [
@@ -75,6 +75,7 @@ interface UseFleetResult {
75
75
  updateVehicle: (id: string, input: CreateVehicleInput) => Promise<Vehicle>;
76
76
  updateDriver: (id: string, input: UpdateDriverInput) => Promise<Driver>;
77
77
  deleteVehicle: (id: string) => Promise<void>;
78
+ deleteDriver: (id: string) => Promise<void>;
78
79
  }
79
80
 
80
81
  export interface ApiVehicle {
@@ -256,6 +257,12 @@ const GET_DRIVER_QUERY = `
256
257
  }
257
258
  `;
258
259
 
260
+ const DELETE_DRIVER_MUTATION = `
261
+ mutation DeleteDriver($id: ID!) {
262
+ deleteDriver(id: $id)
263
+ }
264
+ `;
265
+
259
266
  const DELETE_VEHICLE_MUTATION = `
260
267
  mutation DeleteVehicle($id: ID!) {
261
268
  deleteVehicle(id: $id)
@@ -891,6 +898,16 @@ export function useFleet(options: UseFleetOptions = {}): UseFleetResult {
891
898
  [graphQLOptions]
892
899
  );
893
900
 
901
+ const deleteDriver = useCallback(
902
+ async (id: string): Promise<void> => {
903
+ await executeGraphQL<{ deleteDriver: boolean }>(graphQLOptions, DELETE_DRIVER_MUTATION, {
904
+ id,
905
+ });
906
+ setDrivers((previous) => previous.filter((item) => item.id !== id));
907
+ },
908
+ [graphQLOptions]
909
+ );
910
+
894
911
  return {
895
912
  vehicles,
896
913
  drivers,
@@ -904,6 +921,7 @@ export function useFleet(options: UseFleetOptions = {}): UseFleetResult {
904
921
  updateVehicle,
905
922
  updateDriver,
906
923
  deleteVehicle,
924
+ deleteDriver,
907
925
  };
908
926
  }
909
927
 
@@ -0,0 +1,18 @@
1
+ import { useEffect, useRef } from 'react';
2
+
3
+ /**
4
+ * Detail pages render their `/edit` form from the same route component, so the
5
+ * component (and its record hook) stays mounted across edit → detail. Without a
6
+ * refetch the detail view keeps showing the pre-edit record. Call `refetch`
7
+ * exactly when the edit route is left, never on the initial mount.
8
+ */
9
+ export function useRefetchOnLeaveEdit(isEditRoute: boolean, refetch: () => void): void {
10
+ const wasEditing = useRef(isEditRoute);
11
+
12
+ useEffect(() => {
13
+ if (wasEditing.current && !isEditRoute) {
14
+ refetch();
15
+ }
16
+ wasEditing.current = isEditRoute;
17
+ }, [isEditRoute, refetch]);
18
+ }
@@ -15,6 +15,7 @@ import {
15
15
  MapPin,
16
16
  Save,
17
17
  UserX,
18
+ Trash2,
18
19
  Truck,
19
20
  StickyNote,
20
21
  } from 'lucide-react';
@@ -57,6 +58,7 @@ import {
57
58
  type UpdateDriverInput,
58
59
  } from '../hooks/useFleet';
59
60
  import { useDebouncedValue } from '../hooks/useDebouncedValue';
61
+ import { useRefetchOnLeaveEdit } from '../hooks/useRefetchOnLeaveEdit';
60
62
  import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
61
63
  import { formatCurrency, formatDate } from '../utils/formatters';
62
64
  import { joinPath } from '../utils/navigation';
@@ -119,7 +121,7 @@ const emptyForm: DriverFormState = {
119
121
 
120
122
  function NewDriverForm({ editId }: { editId?: string }) {
121
123
  const { basePath, navigate } = useMoveTheWheels();
122
- const { createDriver, updateDriver } = useFleet();
124
+ const { createDriver, updateDriver, deleteDriver } = useFleet();
123
125
  const { driver, refetch: refetchDriver } = useDriver(editId);
124
126
  const isEditing = !!editId;
125
127
  const [form, setForm] = useState<DriverFormState>(emptyForm);
@@ -129,6 +131,9 @@ function NewDriverForm({ editId }: { editId?: string }) {
129
131
  const [dismissReason, setDismissReason] = useState('');
130
132
  const [dismissing, setDismissing] = useState(false);
131
133
  const [dismissError, setDismissError] = useState<string | null>(null);
134
+ const [deleteOpen, setDeleteOpen] = useState(false);
135
+ const [deleting, setDeleting] = useState(false);
136
+ const [deleteError, setDeleteError] = useState<string | null>(null);
132
137
 
133
138
  const [vehicleSearch, setVehicleSearch] = useState('');
134
139
  const debouncedVehicleSearch = useDebouncedValue(vehicleSearch);
@@ -249,6 +254,21 @@ function NewDriverForm({ editId }: { editId?: string }) {
249
254
  }
250
255
  };
251
256
 
257
+ const handleDelete = async () => {
258
+ if (!editId) return;
259
+ setDeleting(true);
260
+ setDeleteError(null);
261
+ try {
262
+ await deleteDriver(editId);
263
+ setDeleteOpen(false);
264
+ navigate?.(joinPath(basePath, 'fleet/drivers'));
265
+ } catch (err) {
266
+ setDeleteError(err instanceof Error ? err.message : 'Failed to delete driver.');
267
+ } finally {
268
+ setDeleting(false);
269
+ }
270
+ };
271
+
252
272
  const alreadyDismissed = isEditing && Boolean(driver?.terminatedAt);
253
273
 
254
274
  return (
@@ -584,8 +604,60 @@ function NewDriverForm({ editId }: { editId?: string }) {
584
604
  </CardContent>
585
605
  </Card>
586
606
  )}
607
+
608
+ {isEditing && (
609
+ <Card className="border-status-error-border">
610
+ <CardHeader>
611
+ <CardTitle className="flex items-center gap-2 text-status-error-text">
612
+ <Trash2 size={18} /> Delete Driver
613
+ </CardTitle>
614
+ </CardHeader>
615
+ <CardContent>
616
+ <p className="mb-3 text-sm text-text-secondary">
617
+ Removes this driver record, for example one entered by mistake, and unlinks them
618
+ from any vehicle. To keep delivery history for someone who has left, dismiss them
619
+ instead. This cannot be undone from here.
620
+ </p>
621
+ <Button
622
+ type="button"
623
+ variant="outline"
624
+ className="border-status-error-border text-status-error-text hover:bg-status-error-bg-subtle"
625
+ onClick={() => setDeleteOpen(true)}
626
+ >
627
+ <Trash2 size={16} /> Delete this driver
628
+ </Button>
629
+ </CardContent>
630
+ </Card>
631
+ )}
587
632
  </form>
588
633
 
634
+ <ResponsiveDialog open={deleteOpen} onOpenChange={setDeleteOpen}>
635
+ <ResponsiveDialogContent>
636
+ <ResponsiveDialogHeader>
637
+ <ResponsiveDialogTitle>Delete this driver?</ResponsiveDialogTitle>
638
+ <ResponsiveDialogDescription>
639
+ This removes {`${form.firstName} ${form.lastName}`.trim() || 'this driver'} and
640
+ unlinks them from any vehicle. It cannot be undone from here.
641
+ </ResponsiveDialogDescription>
642
+ </ResponsiveDialogHeader>
643
+ <div className="px-1">
644
+ <FormError message={deleteError} />
645
+ </div>
646
+ <ResponsiveDialogFooter>
647
+ <Button variant="outline" onClick={() => setDeleteOpen(false)} disabled={deleting}>
648
+ Cancel
649
+ </Button>
650
+ <Button
651
+ className="bg-status-error-text text-text-inverse hover:opacity-90"
652
+ onClick={handleDelete}
653
+ disabled={deleting}
654
+ >
655
+ {deleting ? 'Deleting...' : 'Delete driver'}
656
+ </Button>
657
+ </ResponsiveDialogFooter>
658
+ </ResponsiveDialogContent>
659
+ </ResponsiveDialog>
660
+
589
661
  <ResponsiveDialog open={dismissOpen} onOpenChange={setDismissOpen}>
590
662
  <ResponsiveDialogContent>
591
663
  <ResponsiveDialogHeader>
@@ -631,6 +703,7 @@ export default function DriverDetailsPage() {
631
703
  const isEditRoute = useLocation().pathname.endsWith('/edit');
632
704
  const { basePath, navigate } = useMoveTheWheels();
633
705
  const { driver, isLoading, error, refetch } = useDriver(driverId);
706
+ useRefetchOnLeaveEdit(isEditRoute, refetch);
634
707
  const { vehicle: assignedVehicle } = useVehicle(driver?.assignedVehicle);
635
708
 
636
709
  const goBack = () => navigate?.(joinPath(basePath, 'fleet/drivers'));
@@ -42,6 +42,7 @@ import {
42
42
  ResponsiveDialogDescription,
43
43
  } from '@burdenoff/fe-libs/ui';
44
44
  import { useProduct, useInventory, useProductStockLevels } from '../hooks/useInventory';
45
+ import { useRefetchOnLeaveEdit } from '../hooks/useRefetchOnLeaveEdit';
45
46
  import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
46
47
  import { formatCurrency } from '../utils/formatters';
47
48
  import { joinPath } from '../utils/navigation';
@@ -326,6 +327,7 @@ export default function ProductDetailsPage() {
326
327
  const { product, isLoading, error, refetch } = useProduct(productId);
327
328
  const { deleteProduct } = useInventory();
328
329
  const isEditRoute = useLocation().pathname.endsWith('/edit');
330
+ useRefetchOnLeaveEdit(isEditRoute, refetch);
329
331
  const [deleteOpen, setDeleteOpen] = useState(false);
330
332
  const [deleting, setDeleting] = useState(false);
331
333
  const [deleteError, setDeleteError] = useState<string | null>(null);
@@ -53,6 +53,7 @@ import { EntityCombobox } from '../components/EntityCombobox';
53
53
  import { useRoute, useRoutes } from '../hooks/useRoutes';
54
54
  import { useVehicles, useVehicle, useDrivers, useDriver } from '../hooks/useFleet';
55
55
  import { useAssignmentSuggestions } from '../hooks/useFleetIntelligence';
56
+ import { useRefetchOnLeaveEdit } from '../hooks/useRefetchOnLeaveEdit';
56
57
  import { AssignmentSuggestions } from '../components/AssignmentSuggestions';
57
58
  import { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';
58
59
  import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
@@ -436,6 +437,7 @@ export default function RouteDetailsPage() {
436
437
  const isEditRoute = useLocation().pathname.endsWith('/edit');
437
438
  const { basePath, navigate } = useMoveTheWheels();
438
439
  const { route, isLoading, error, refetch } = useRoute(routeId);
440
+ useRefetchOnLeaveEdit(isEditRoute, refetch);
439
441
  const { optimizeRoute, updateRoute, deleteRoute } = useRoutes();
440
442
  const suggestions = useAssignmentSuggestions(routeId);
441
443
  const { t: translate } = useI18n();
@@ -50,6 +50,7 @@ import { useVehicle, useVehicleDrivers, useFleet, toApiVehicleStatus } from '../
50
50
  import type { VehicleFuelType, VehicleStatus } from '../types';
51
51
  import { VEHICLE_STATUS_LABELS } from '../constants';
52
52
  import { useRecordVehicleService } from '../hooks/useFleetIntelligence';
53
+ import { useRefetchOnLeaveEdit } from '../hooks/useRefetchOnLeaveEdit';
53
54
  import { VehicleServiceCard } from '../components/VehicleServiceCard';
54
55
  import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
55
56
  import { formatDate, getInitials } from '../utils/formatters';
@@ -494,6 +495,7 @@ export default function VehicleDetailsPage() {
494
495
  return value === key ? fallback : value;
495
496
  };
496
497
  const isEditRoute = useLocation().pathname.endsWith('/edit');
498
+ useRefetchOnLeaveEdit(isEditRoute, refetch);
497
499
  // Who is behind the wheel — resolved through DriverFilterInput.vehicleId, the
498
500
  // same link `assignDriverVehicle` writes.
499
501
  const {