@burdenoff/microfe-movethewheels 2026.925.2 → 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 (38) hide show
  1. package/dist/components/StatusBadge.js +30 -30
  2. package/dist/constants/index.js +12 -5
  3. package/dist/hooks/useCustomers.js +0 -3
  4. package/dist/hooks/useFleet.js +144 -131
  5. package/dist/hooks/useInventory.js +144 -145
  6. package/dist/hooks/useRoutes.js +5 -14
  7. package/dist/movethewheels/constants/index.d.ts +1 -0
  8. package/dist/movethewheels/hooks/useFleet.d.ts +13 -1
  9. package/dist/movethewheels/hooks/useInventory.d.ts +10 -0
  10. package/dist/movethewheels/types/index.d.ts +13 -70
  11. package/dist/pages/CustomerDetailsPage.js +666 -344
  12. package/dist/pages/CustomersPage.js +71 -80
  13. package/dist/pages/FleetPage.js +1 -3
  14. package/dist/pages/InventoryPage.js +4 -4
  15. package/dist/pages/RouteDetailsPage.js +273 -248
  16. package/dist/pages/RoutesPage.js +130 -67
  17. package/dist/pages/VehicleDetailsPage.js +506 -391
  18. package/dist/pages/VehiclesPage.js +119 -70
  19. package/dist/pages/WarehouseDetailsPage.js +410 -370
  20. package/dist/pages/WarehousesPage.js +131 -82
  21. package/package.json +1 -1
  22. package/src/movethewheels/components/StatusBadge.tsx +8 -3
  23. package/src/movethewheels/constants/index.ts +9 -0
  24. package/src/movethewheels/hooks/useCustomers.ts +0 -3
  25. package/src/movethewheels/hooks/useFleet.ts +74 -19
  26. package/src/movethewheels/hooks/useInventory.ts +76 -32
  27. package/src/movethewheels/hooks/useRoutes.ts +17 -12
  28. package/src/movethewheels/pages/CustomerDetailsPage.tsx +376 -109
  29. package/src/movethewheels/pages/CustomersPage.tsx +6 -15
  30. package/src/movethewheels/pages/FleetPage.tsx +2 -1
  31. package/src/movethewheels/pages/InventoryPage.tsx +14 -5
  32. package/src/movethewheels/pages/RouteDetailsPage.tsx +74 -34
  33. package/src/movethewheels/pages/RoutesPage.tsx +66 -2
  34. package/src/movethewheels/pages/VehicleDetailsPage.tsx +151 -11
  35. package/src/movethewheels/pages/VehiclesPage.tsx +68 -3
  36. package/src/movethewheels/pages/WarehouseDetailsPage.tsx +213 -135
  37. package/src/movethewheels/pages/WarehousesPage.tsx +77 -16
  38. package/src/movethewheels/types/index.ts +14 -70
@@ -1,85 +1,140 @@
1
1
  import { CircleCheck as e } from "../node_modules/lucide-react/dist/esm/icons/circle-check.js";
2
- import { Gauge as t } from "../node_modules/lucide-react/dist/esm/icons/gauge.js";
3
- import { Plus as n } from "../node_modules/lucide-react/dist/esm/icons/plus.js";
4
- import { RefreshCw as r } from "../node_modules/lucide-react/dist/esm/icons/refresh-cw.js";
5
- import { Warehouse as i } from "../node_modules/lucide-react/dist/esm/icons/warehouse.js";
6
- import { joinPath as a } from "../utils/navigation.js";
7
- import { useMoveTheWheels as o } from "../providers/MoveTheWheelsProvider.js";
8
- import { useWarehouseStats as s, useWarehouses as c } from "../hooks/useInventory.js";
9
- import { Badge as l, Progress as u } from "../components/ui.js";
10
- import { PageLayout as d } from "../components/PageLayout.js";
11
- import { MetricCard as f, MetricGrid as p } from "../components/MetricCard.js";
12
- import { DataTable as m } from "../components/DataTable.js";
13
- import { useDebouncedValue as h } from "../hooks/useDebouncedValue.js";
14
- import { useState as g } from "react";
15
- import { jsx as _, jsxs as v } from "react/jsx-runtime";
16
- import { CTAOverflowMenu as y, PagePurpose as b } from "@burdenoff/fe-libs/ui";
2
+ import { Filter as t } from "../node_modules/lucide-react/dist/esm/icons/filter.js";
3
+ import { Gauge as n } from "../node_modules/lucide-react/dist/esm/icons/gauge.js";
4
+ import { Plus as r } from "../node_modules/lucide-react/dist/esm/icons/plus.js";
5
+ import { RefreshCw as i } from "../node_modules/lucide-react/dist/esm/icons/refresh-cw.js";
6
+ import { Warehouse as a } from "../node_modules/lucide-react/dist/esm/icons/warehouse.js";
7
+ import { joinPath as o } from "../utils/navigation.js";
8
+ import { useMoveTheWheels as s } from "../providers/MoveTheWheelsProvider.js";
9
+ import { useWarehouseStats as c, useWarehouses as l } from "../hooks/useInventory.js";
10
+ import { Badge as u, Button as d, Card as f, CardContent as p, Progress as m, Select as h, SelectContent as g, SelectItem as _, SelectTrigger as v, SelectValue as y } from "../components/ui.js";
11
+ import { PageLayout as b } from "../components/PageLayout.js";
12
+ import { MetricCard as x, MetricGrid as S } from "../components/MetricCard.js";
13
+ import { DataTable as C } from "../components/DataTable.js";
14
+ import { useDebouncedValue as w } from "../hooks/useDebouncedValue.js";
15
+ import { useState as T } from "react";
16
+ import { jsx as E, jsxs as D } from "react/jsx-runtime";
17
+ import { CTAOverflowMenu as O, PagePurpose as k } from "@burdenoff/fe-libs/ui";
17
18
  //#region src/movethewheels/pages/WarehousesPage.tsx
18
- function x() {
19
- let { basePath: x, navigate: S } = o(), [C, w] = g(0), [T, E] = g(25), [D, O] = g(""), k = h(D), { warehouses: A, totalWarehouses: j, hasMore: M, isLoading: N, error: P, refetch: F } = c({
20
- limit: T,
21
- offset: C * T,
22
- search: k
23
- }), { stats: I } = s(), L = (e) => S?.(a(x, e));
24
- return /* @__PURE__ */ v(d, {
19
+ var A = [
20
+ {
21
+ value: "all",
22
+ label: "All statuses"
23
+ },
24
+ {
25
+ value: "ACTIVE",
26
+ label: "Active"
27
+ },
28
+ {
29
+ value: "INACTIVE",
30
+ label: "Inactive"
31
+ }
32
+ ];
33
+ function j() {
34
+ let { basePath: j, navigate: M } = s(), [N, P] = T(0), [F, I] = T(25), [L, R] = T(""), [z, B] = T("all"), V = w(L), { warehouses: H, totalWarehouses: U, hasMore: W, isLoading: G, error: K, refetch: q } = l({
35
+ limit: F,
36
+ offset: N * F,
37
+ search: V,
38
+ status: z === "all" ? void 0 : z
39
+ }), J = z !== "all" || L.length > 0, { stats: Y } = c(), X = (e) => M?.(o(j, e));
40
+ return /* @__PURE__ */ D(b, {
25
41
  title: "Warehouses",
26
- subtitle: `${I.total} warehouses`,
27
- icon: /* @__PURE__ */ _(i, { size: 24 }),
28
- headerActions: /* @__PURE__ */ _(y, {
42
+ subtitle: `${Y.total} warehouses`,
43
+ icon: /* @__PURE__ */ E(a, { size: 24 }),
44
+ headerActions: /* @__PURE__ */ E(O, {
29
45
  primary: {
30
46
  label: "Add Warehouse",
31
- icon: /* @__PURE__ */ _(n, { size: 16 }),
32
- onSelect: () => L("inventory/warehouses/new")
47
+ icon: /* @__PURE__ */ E(r, { size: 16 }),
48
+ onSelect: () => X("inventory/warehouses/new")
33
49
  },
34
50
  actions: [{
35
51
  label: "Refresh",
36
- icon: /* @__PURE__ */ _(r, { size: 16 }),
37
- onSelect: () => F()
52
+ icon: /* @__PURE__ */ E(i, { size: 16 }),
53
+ onSelect: () => q()
38
54
  }]
39
55
  }),
40
56
  children: [
41
- /* @__PURE__ */ _(b, {
42
- icon: /* @__PURE__ */ _(i, { size: 18 }),
57
+ /* @__PURE__ */ E(k, {
58
+ icon: /* @__PURE__ */ E(a, { size: 18 }),
43
59
  children: "Your storage and fulfilment sites. Watch space and pallet utilization across each warehouse to balance stock, spot capacity crunches, and decide where to fulfil an order from."
44
60
  }),
45
- /* @__PURE__ */ v("div", { children: [/* @__PURE__ */ _("p", {
61
+ /* @__PURE__ */ D("div", { children: [/* @__PURE__ */ E("p", {
46
62
  className: "mb-3 text-sm font-medium text-text-secondary",
47
63
  children: "Network at a glance"
48
- }), /* @__PURE__ */ v(p, {
64
+ }), /* @__PURE__ */ D(S, {
49
65
  columns: 3,
50
66
  children: [
51
- /* @__PURE__ */ _(f, {
67
+ /* @__PURE__ */ E(x, {
52
68
  label: "Total sites",
53
- value: I.total,
54
- icon: /* @__PURE__ */ _(i, { size: 20 }),
69
+ value: Y.total,
70
+ icon: /* @__PURE__ */ E(a, { size: 20 }),
55
71
  accent: "brand"
56
72
  }),
57
- /* @__PURE__ */ _(f, {
73
+ /* @__PURE__ */ E(x, {
58
74
  label: "Active",
59
- value: I.active,
60
- icon: /* @__PURE__ */ _(e, { size: 20 }),
75
+ value: Y.active,
76
+ icon: /* @__PURE__ */ E(e, { size: 20 }),
61
77
  accent: "success",
62
78
  subtitle: "Fulfilling orders"
63
79
  }),
64
- /* @__PURE__ */ _(f, {
80
+ /* @__PURE__ */ E(x, {
65
81
  label: "Avg utilization",
66
- value: I.total > 0 ? `${Math.round(I.avgUtilization * 100)}%` : "—",
67
- icon: /* @__PURE__ */ _(t, { size: 20 }),
82
+ value: Y.total > 0 ? `${Math.round(Y.avgUtilization * 100)}%` : "—",
83
+ icon: /* @__PURE__ */ E(n, { size: 20 }),
68
84
  accent: "blue"
69
85
  })
70
86
  ]
71
87
  })] }),
72
- /* @__PURE__ */ _(m, {
88
+ /* @__PURE__ */ E(f, { children: /* @__PURE__ */ E(p, {
89
+ className: "p-4",
90
+ children: /* @__PURE__ */ D("div", {
91
+ className: "flex flex-wrap items-center gap-4",
92
+ children: [
93
+ /* @__PURE__ */ D("div", {
94
+ className: "flex items-center gap-2",
95
+ children: [/* @__PURE__ */ E(t, {
96
+ size: 16,
97
+ className: "text-text-secondary"
98
+ }), /* @__PURE__ */ E("span", {
99
+ className: "text-sm font-medium",
100
+ children: "Filters:"
101
+ })]
102
+ }),
103
+ /* @__PURE__ */ D(h, {
104
+ value: z,
105
+ onValueChange: (e) => {
106
+ B(e), P(0);
107
+ },
108
+ children: [/* @__PURE__ */ E(v, {
109
+ className: "w-[180px]",
110
+ children: /* @__PURE__ */ E(y, { placeholder: "Status" })
111
+ }), /* @__PURE__ */ E(g, { children: A.map((e) => /* @__PURE__ */ E(_, {
112
+ value: e.value,
113
+ children: e.label
114
+ }, e.value)) })]
115
+ }),
116
+ J && /* @__PURE__ */ E(d, {
117
+ variant: "ghost",
118
+ size: "sm",
119
+ onClick: () => {
120
+ B("all"), R(""), P(0);
121
+ },
122
+ children: "Clear filters"
123
+ })
124
+ ]
125
+ })
126
+ }) }),
127
+ /* @__PURE__ */ E(C, {
73
128
  columns: [
74
129
  {
75
130
  id: "name",
76
131
  header: "Warehouse",
77
132
  accessorKey: "name",
78
133
  sortable: !0,
79
- cell: (e) => /* @__PURE__ */ v("div", { children: [/* @__PURE__ */ _("p", {
134
+ cell: (e) => /* @__PURE__ */ D("div", { children: [/* @__PURE__ */ E("p", {
80
135
  className: "font-medium",
81
136
  children: e.name
82
- }), /* @__PURE__ */ _("p", {
137
+ }), /* @__PURE__ */ E("p", {
83
138
  className: "text-sm text-text-secondary",
84
139
  children: e.code
85
140
  })] })
@@ -87,11 +142,11 @@ function x() {
87
142
  {
88
143
  id: "location",
89
144
  header: "Location",
90
- cell: (e) => /* @__PURE__ */ v("div", { children: [/* @__PURE__ */ v("p", { children: [
145
+ cell: (e) => /* @__PURE__ */ D("div", { children: [/* @__PURE__ */ D("p", { children: [
91
146
  e.address.city,
92
147
  ", ",
93
148
  e.address.state
94
- ] }), /* @__PURE__ */ _("p", {
149
+ ] }), /* @__PURE__ */ E("p", {
95
150
  className: "text-sm text-text-secondary",
96
151
  children: e.address.country
97
152
  })] })
@@ -100,7 +155,7 @@ function x() {
100
155
  id: "type",
101
156
  header: "Type",
102
157
  accessorKey: "type",
103
- cell: (e) => /* @__PURE__ */ _(l, {
158
+ cell: (e) => /* @__PURE__ */ E(u, {
104
159
  variant: "outline",
105
160
  className: "capitalize",
106
161
  children: e.type.replace("_", " ")
@@ -111,12 +166,12 @@ function x() {
111
166
  header: "Utilization",
112
167
  cell: (e) => {
113
168
  let t = e.capacity.usedSpace / e.capacity.totalSpace * 100;
114
- return /* @__PURE__ */ v("div", {
169
+ return /* @__PURE__ */ D("div", {
115
170
  className: "flex items-center gap-2",
116
- children: [/* @__PURE__ */ _(u, {
171
+ children: [/* @__PURE__ */ E(m, {
117
172
  value: t,
118
173
  className: "w-16 h-2"
119
- }), /* @__PURE__ */ v("span", {
174
+ }), /* @__PURE__ */ D("span", {
120
175
  className: "text-sm",
121
176
  children: [t.toFixed(0), "%"]
122
177
  })]
@@ -126,55 +181,49 @@ function x() {
126
181
  {
127
182
  id: "capacity",
128
183
  header: "Capacity",
129
- cell: (e) => /* @__PURE__ */ v("div", {
184
+ cell: (e) => /* @__PURE__ */ D("p", {
130
185
  className: "text-sm",
131
- children: [/* @__PURE__ */ v("p", { children: [
186
+ children: [
132
187
  e.capacity.usedSpace.toLocaleString(),
133
188
  " / ",
134
189
  e.capacity.totalSpace.toLocaleString(),
135
- " ",
136
- "sq ft"
137
- ] }), /* @__PURE__ */ v("p", {
138
- className: "text-text-secondary",
139
- children: [
140
- e.capacity.usedPallets,
141
- " / ",
142
- e.capacity.totalPallets,
143
- " pallets"
144
- ]
145
- })]
190
+ " sq ft"
191
+ ]
146
192
  })
147
193
  },
148
194
  {
149
195
  id: "status",
150
196
  header: "Status",
151
- cell: (e) => /* @__PURE__ */ _(l, {
152
- variant: e.isActive ? "default" : "secondary",
153
- children: e.isActive ? "Active" : "Inactive"
154
- })
197
+ cell: (e) => {
198
+ let t = e.status.toUpperCase() === "ACTIVE";
199
+ return /* @__PURE__ */ E(u, {
200
+ variant: t ? "default" : "secondary",
201
+ children: t ? "Active" : "Inactive"
202
+ });
203
+ }
155
204
  }
156
205
  ],
157
- data: A,
158
- isLoading: N,
159
- error: P,
160
- onRetry: F,
206
+ data: H,
207
+ isLoading: G,
208
+ error: K,
209
+ onRetry: q,
161
210
  searchable: !0,
162
211
  searchPlaceholder: "Search warehouses...",
163
212
  searchFields: ["name", "code"],
164
- searchQuery: D,
213
+ searchQuery: L,
165
214
  onSearchQueryChange: (e) => {
166
- O(e), w(0);
215
+ R(e), P(0);
167
216
  },
168
- onRowClick: (e) => L(`inventory/warehouses/${e.id}`),
217
+ onRowClick: (e) => X(`inventory/warehouses/${e.id}`),
169
218
  getRowId: (e) => e.id,
170
- pageIndex: C,
171
- pageSize: T,
172
- onPageChange: w,
219
+ pageIndex: N,
220
+ pageSize: F,
221
+ onPageChange: P,
173
222
  onPageSizeChange: (e) => {
174
- E(e), w(0);
223
+ I(e), P(0);
175
224
  },
176
- totalItems: j,
177
- hasNextPage: M,
225
+ totalItems: U,
226
+ hasNextPage: W,
178
227
  emptyIllustration: "empty-data",
179
228
  emptyTitle: "No warehouses yet",
180
229
  emptyDescription: "Add a warehouse to store inventory and fulfil orders from it."
@@ -183,4 +232,4 @@ function x() {
183
232
  });
184
233
  }
185
234
  //#endregion
186
- export { x as default };
235
+ export { j as default };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-movethewheels",
3
- "version": "2026.925.2",
3
+ "version": "2026.925.3",
4
4
  "description": "Burdenoff MoveTheWheels Microfrontend - Logistics & Supply Chain Management Platform",
5
5
  "type": "module",
6
6
  "files": [
@@ -4,6 +4,7 @@ import type { OrderStatus, VehicleStatus, DriverStatus } from '../types';
4
4
  import {
5
5
  ORDER_STATUS_COLORS,
6
6
  VEHICLE_STATUS_COLORS,
7
+ VEHICLE_STATUS_LABELS,
7
8
  DRIVER_STATUS_COLORS,
8
9
  DRIVER_STATUS_LABELS,
9
10
  PRIORITY_COLORS,
@@ -66,10 +67,14 @@ export function StatusBadge({
66
67
  // Format status for display — an explicit label always wins; driver status
67
68
  // otherwise renders from the same shared label map the filters use, so the
68
69
  // table and the filter dropdown can never drift apart again (BOFF-7909).
70
+ const sharedLabel =
71
+ type === 'driver'
72
+ ? DRIVER_STATUS_LABELS[status]
73
+ : type === 'vehicle'
74
+ ? VEHICLE_STATUS_LABELS[status]
75
+ : undefined;
69
76
  const displayStatus =
70
- label ??
71
- (type === 'driver' ? DRIVER_STATUS_LABELS[status] : undefined) ??
72
- status.replace(/_/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());
77
+ label ?? sharedLabel ?? status.replace(/_/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());
73
78
 
74
79
  return (
75
80
  <Badge
@@ -57,6 +57,15 @@ export const VEHICLE_STATUS_COLORS: Record<string, StatusColor> = {
57
57
  assigned: INFO,
58
58
  maintenance: WARNING,
59
59
  out_of_service: ERROR,
60
+ decommissioned: NEUTRAL,
61
+ };
62
+
63
+ export const VEHICLE_STATUS_LABELS: Record<string, string> = {
64
+ available: 'Available',
65
+ assigned: 'In Transit',
66
+ maintenance: 'Maintenance',
67
+ out_of_service: 'Out of Service',
68
+ decommissioned: 'Decommissioned',
60
69
  };
61
70
 
62
71
  // Driver status colors
@@ -142,9 +142,6 @@ function mapCustomer(customer: ApiCustomer): Customer {
142
142
  },
143
143
  businessLicense: customer.businessLicense ?? undefined,
144
144
  taxId: customer.taxId ?? undefined,
145
- preferredContactMethod: 'email',
146
- marketingOptIn: false,
147
- isActive: !customer.deletedAt,
148
145
  rating: customer.rating ?? 0,
149
146
  createdAt: new Date(customer.createdAt),
150
147
  updatedAt: new Date(customer.updatedAt),
@@ -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 {
@@ -22,6 +22,7 @@ export interface CreateVehicleInput {
22
22
  status?: string;
23
23
  fuelType?: string;
24
24
  fuelEfficiencyLPer100Km?: number;
25
+ capacity?: { weight?: number; volume?: number; pallets?: number };
25
26
  }
26
27
 
27
28
  export interface CreateDriverInput {
@@ -63,6 +64,7 @@ interface UseFleetResult {
63
64
  createDriver: (input: CreateDriverInput) => Promise<Driver>;
64
65
  updateVehicle: (id: string, input: CreateVehicleInput) => Promise<Vehicle>;
65
66
  updateDriver: (id: string, input: UpdateDriverInput) => Promise<Driver>;
67
+ deleteVehicle: (id: string) => Promise<void>;
66
68
  }
67
69
 
68
70
  export interface ApiVehicle {
@@ -230,6 +232,12 @@ const GET_DRIVER_QUERY = `
230
232
  }
231
233
  `;
232
234
 
235
+ const DELETE_VEHICLE_MUTATION = `
236
+ mutation DeleteVehicle($id: ID!) {
237
+ deleteVehicle(id: $id)
238
+ }
239
+ `;
240
+
233
241
  const UPDATE_VEHICLE_STATUS_MUTATION = `
234
242
  mutation UpdateVehicleStatus($id: ID!, $status: VehicleStatus!) {
235
243
  updateVehicleStatus(id: $id, status: $status) {
@@ -460,12 +468,19 @@ function mapVehicleStatus(status: string): VehicleStatus {
460
468
  return 'assigned';
461
469
  case 'MAINTENANCE':
462
470
  return 'maintenance';
471
+ case 'DECOMMISSIONED':
472
+ return 'decommissioned';
463
473
  default:
464
474
  return 'out_of_service';
465
475
  }
466
476
  }
467
477
 
468
- /** Exported so the vehicle edit form's status field can round-trip (BOFF-7909). */
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
+ */
469
484
  export function toApiVehicleStatus(status: VehicleStatus): string {
470
485
  switch (status) {
471
486
  case 'available':
@@ -474,6 +489,8 @@ export function toApiVehicleStatus(status: VehicleStatus): string {
474
489
  return 'IN_TRANSIT';
475
490
  case 'maintenance':
476
491
  return 'MAINTENANCE';
492
+ case 'decommissioned':
493
+ return 'DECOMMISSIONED';
477
494
  default:
478
495
  return 'INACTIVE';
479
496
  }
@@ -510,6 +527,33 @@ export function toApiDriverStatus(status: DriverStatus): string {
510
527
  }
511
528
  }
512
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
+
513
557
  export function mapVehicle(vehicle: ApiVehicle): Vehicle {
514
558
  return {
515
559
  id: vehicle.id,
@@ -519,7 +563,7 @@ export function mapVehicle(vehicle: ApiVehicle): Vehicle {
519
563
  model: vehicle.model ?? '',
520
564
  licensePlate: vehicle.licensePlate ?? '',
521
565
  ownershipType: vehicle.ownershipType ?? undefined,
522
- year: vehicle.year ?? new Date().getFullYear(),
566
+ year: vehicle.year ?? undefined,
523
567
  capacity: {
524
568
  weight: 0,
525
569
  volume: 0,
@@ -527,14 +571,7 @@ export function mapVehicle(vehicle: ApiVehicle): Vehicle {
527
571
  },
528
572
  status: mapVehicleStatus(vehicle.status),
529
573
  driverId: undefined,
530
- currentLocation: vehicle.currentLocation
531
- ? {
532
- latitude: Number((vehicle.currentLocation.latitude as number | undefined) ?? 0),
533
- longitude: Number((vehicle.currentLocation.longitude as number | undefined) ?? 0),
534
- address: (vehicle.currentLocation.address as string | undefined) ?? undefined,
535
- timestamp: new Date(),
536
- }
537
- : undefined,
574
+ currentLocation: parseCurrentLocation(vehicle.currentLocation),
538
575
  lastMaintenance: vehicle.lastServiceDate ? new Date(vehicle.lastServiceDate) : undefined,
539
576
  nextMaintenance: vehicle.nextServiceDueDate ? new Date(vehicle.nextServiceDueDate) : undefined,
540
577
  nextMaintenanceMileage: vehicle.nextServiceDueMileage ?? undefined,
@@ -561,14 +598,7 @@ export function mapDriver(driver: ApiDriver): Driver {
561
598
  vehicleIds: driver.vehicleId ? [driver.vehicleId] : [],
562
599
  assignedVehicle: driver.vehicleId ?? undefined,
563
600
  status: mapDriverStatus(driver.status),
564
- currentLocation: driver.currentLocation
565
- ? {
566
- latitude: Number((driver.currentLocation.latitude as number | undefined) ?? 0),
567
- longitude: Number((driver.currentLocation.longitude as number | undefined) ?? 0),
568
- address: (driver.currentLocation.address as string | undefined) ?? undefined,
569
- timestamp: new Date(),
570
- }
571
- : undefined,
601
+ currentLocation: parseCurrentLocation(driver.currentLocation),
572
602
  rating: driver.rating ?? 0,
573
603
  notes: driver.notes ?? undefined,
574
604
  hireDate: driver.hireDate ? new Date(driver.hireDate) : undefined,
@@ -677,6 +707,7 @@ export function useFleet(options: UseFleetOptions = {}): UseFleetResult {
677
707
  status: input.status,
678
708
  fuelType: input.fuelType,
679
709
  fuelEfficiencyLPer100Km: input.fuelEfficiencyLPer100Km,
710
+ capacity: input.capacity,
680
711
  },
681
712
  }
682
713
  );
@@ -738,6 +769,7 @@ export function useFleet(options: UseFleetOptions = {}): UseFleetResult {
738
769
  status: input.status,
739
770
  fuelType: input.fuelType,
740
771
  fuelEfficiencyLPer100Km: input.fuelEfficiencyLPer100Km,
772
+ capacity: input.capacity,
741
773
  },
742
774
  }
743
775
  );
@@ -784,6 +816,16 @@ export function useFleet(options: UseFleetOptions = {}): UseFleetResult {
784
816
  [graphQLOptions]
785
817
  );
786
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
+
787
829
  return {
788
830
  vehicles,
789
831
  drivers,
@@ -796,6 +838,7 @@ export function useFleet(options: UseFleetOptions = {}): UseFleetResult {
796
838
  createDriver,
797
839
  updateVehicle,
798
840
  updateDriver,
841
+ deleteVehicle,
799
842
  };
800
843
  }
801
844
 
@@ -824,6 +867,7 @@ interface UseVehiclesResult {
824
867
  refetch: () => void;
825
868
  updateVehicleStatus: (id: string, status: VehicleStatus) => Promise<Vehicle>;
826
869
  assignDriver: (vehicleId: string, driverId: string) => Promise<Vehicle>;
870
+ deleteVehicle: (id: string) => Promise<void>;
827
871
  }
828
872
 
829
873
  export function useVehicles(options: UseVehiclesOptions = {}): UseVehiclesResult {
@@ -918,6 +962,16 @@ export function useVehicles(options: UseVehiclesOptions = {}): UseVehiclesResult
918
962
  [graphQLOptions, vehicles]
919
963
  );
920
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
+
921
975
  return {
922
976
  vehicles,
923
977
  totalVehicles,
@@ -927,6 +981,7 @@ export function useVehicles(options: UseVehiclesOptions = {}): UseVehiclesResult
927
981
  refetch,
928
982
  updateVehicleStatus,
929
983
  assignDriver,
984
+ deleteVehicle,
930
985
  };
931
986
  }
932
987