@burdenoff/microfe-movethewheels 2026.703.7 → 2026.704.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.
Files changed (117) hide show
  1. package/dist/{AIAssistantPage-B7FGmmKT.js → AIAssistantPage-MWJLAMN_.js} +5 -5
  2. package/dist/{AnalyticsPage-DXduj5KB.js → AnalyticsPage-900jpV2T.js} +9 -9
  3. package/dist/{CreateOrderPage-DuJJQ16b.js → CreateOrderPage-CXbhJLLz.js} +164 -139
  4. package/dist/{CustomerDetailsPage-C98SKw7Z.js → CustomerDetailsPage-CHzg75Q5.js} +16 -16
  5. package/dist/{CustomersPage-s8hK55JL.js → CustomersPage-CwtVspuq.js} +11 -11
  6. package/dist/{DashboardPage-BsEgcKEN.js → DashboardPage-BoswH3ec.js} +17 -17
  7. package/dist/{DataTable-Da7yHjM1.js → DataTable-CmdZd6iV.js} +7 -7
  8. package/dist/{DocumentsCard-CziaTJhP.js → DocumentsCard-y2ybnAlu.js} +4 -4
  9. package/dist/DriverDetailsPage-CPO0G8Ki.js +518 -0
  10. package/dist/{DriversPage-Duy_ITG6.js → DriversPage-6Z61dpDI.js} +11 -11
  11. package/dist/{FinancePage-BZtPTpmT.js → FinancePage-DYadOUzC.js} +9 -9
  12. package/dist/{FleetPage-DJfzoHbs.js → FleetPage-qWRj7WFO.js} +10 -10
  13. package/dist/{FormError-CWjGwOW8.js → FormError-D4T5FWZA.js} +1 -1
  14. package/dist/{ImportExportPage-DJ77GK_7.js → ImportExportPage-BhWGfbH3.js} +6 -6
  15. package/dist/{InventoryPage-Dpnwk_N3.js → InventoryPage-DbR4Z--d.js} +10 -10
  16. package/dist/{LiveTrackingPage-CLlvsP1B.js → LiveTrackingPage-5U68u3oA.js} +10 -10
  17. package/dist/{MarketplacePage-d99r-v6p.js → MarketplacePage-DMH0HNDH.js} +9 -9
  18. package/dist/{MetricCard-DA5ZqAVl.js → MetricCard-CqU5WsyW.js} +2 -2
  19. package/dist/{Notice-kkjV2rLR.js → Notice-BrXJ85dA.js} +2 -2
  20. package/dist/{OrderDetailsPage-BePtkyN0.js → OrderDetailsPage-DRYifXm9.js} +28 -24
  21. package/dist/{OrdersListPage-Csy_KScx.js → OrdersListPage-DvICveRv.js} +14 -14
  22. package/dist/{PageLayout-h_q9l8af.js → PageLayout-j9Zw3wBN.js} +579 -561
  23. package/dist/ProductDetailsPage-CWgcRqGR.js +375 -0
  24. package/dist/{ProductsPage-B4jWJ5bv.js → ProductsPage-CokwDOR2.js} +9 -9
  25. package/dist/{ReportsPage-ALRYfXtz.js → ReportsPage-MU8F9DQC.js} +9 -9
  26. package/dist/RouteDetailsPage-BHQbFgI9.js +642 -0
  27. package/dist/{RoutesPage-CQHCDInT.js → RoutesPage-Dqd5Don6.js} +7 -7
  28. package/dist/{SettingsPage-BYBRrlGe.js → SettingsPage-_6SIlcBX.js} +4 -4
  29. package/dist/{Sparkline-Q-6bJ24J.js → Sparkline-DTGdlxUH.js} +1 -1
  30. package/dist/{StatusBadge-1W2p8fEi.js → StatusBadge-BfiF-nNI.js} +1 -1
  31. package/dist/{TrackingPage-CFndTIyk.js → TrackingPage-Cg0tYGJe.js} +11 -11
  32. package/dist/VehicleDetailsPage-BbZlCFPo.js +417 -0
  33. package/dist/{VehiclesPage-ClkfFZxL.js → VehiclesPage-BfBEyXGs.js} +12 -12
  34. package/dist/WarehouseDetailsPage-DxNhiaoI.js +515 -0
  35. package/dist/{WarehousesPage-kNba6cr3.js → WarehousesPage-M3NaBT0z.js} +8 -8
  36. package/dist/{arrow-left-XOzFvBFD.js → arrow-left-1dVWv2dg.js} +1 -1
  37. package/dist/{arrow-right-CRrwlvFe.js → arrow-right-PUCNATnk.js} +1 -1
  38. package/dist/{arrow-up-ClFaLPly.js → arrow-up-VQuGMABN.js} +1 -1
  39. package/dist/{box-BX4WyCLv.js → box-DW1KNP8j.js} +1 -1
  40. package/dist/{boxes-R3nq5f0_.js → boxes-D6I00XqA.js} +1 -1
  41. package/dist/{chart-column-BgF8J9bx.js → chart-column-BwQ5H9ol.js} +1 -1
  42. package/dist/{charts-BS_OnWc6.js → charts-Dv_zxP7L.js} +2 -2
  43. package/dist/{chevron-left-D3hcLQ1g.js → chevron-left-DzNxoaBG.js} +1 -1
  44. package/dist/{chevron-right-BpfSrzQF.js → chevron-right-CKm3ZC1T.js} +1 -1
  45. package/dist/{circle-alert-CLqT8iCQ.js → circle-alert-CtBm6oIb.js} +1 -1
  46. package/dist/{circle-check-CWLAr_8f.js → circle-check-B_Y7FDyR.js} +1 -1
  47. package/dist/{circle-check-big-Dsuz8dJ2.js → circle-check-big-H5pLiMBW.js} +1 -1
  48. package/dist/{clock-DrZOQD8h.js → clock-B4-qjGuB.js} +1 -1
  49. package/dist/{dollar-sign-CJfKfYul.js → dollar-sign-CYGHy2oS.js} +1 -1
  50. package/dist/{download-C-yNvUiL.js → download-BJonq2ty.js} +1 -1
  51. package/dist/{file-text-m2SyQgBo.js → file-text-C-cZx58r.js} +1 -1
  52. package/dist/{filter-uA6govE9.js → filter-B_S6WOqr.js} +1 -1
  53. package/dist/{fuel-BroSIBo1.js → fuel-Cjt6IxKr.js} +1 -1
  54. package/dist/index.js +53 -33
  55. package/dist/{map-BzwQ__kD.js → map-CO0aWUae.js} +1 -1
  56. package/dist/{map-pin-BGbmEGn3.js → map-pin-BISLzLwb.js} +1 -1
  57. package/dist/{maps-DpIuSSmg.js → maps-CtfuCAmh.js} +1 -1
  58. package/dist/{navigation-CjKGLL9T.js → navigation-BFki5UJz.js} +1 -1
  59. package/dist/{package-D36cKFvu.js → package-UyaWiALC.js} +1 -1
  60. package/dist/{phone-mMZFHd1Z.js → phone-Bav_V7rR.js} +1 -1
  61. package/dist/{plus-DY5R-c1j.js → plus-Bk_G-lFC.js} +1 -1
  62. package/dist/{refresh-cw-DyOLYuDU.js → refresh-cw-C4-jecjy.js} +1 -1
  63. package/dist/{route-CuaZ1wU3.js → route-WpCP6F1x.js} +1 -1
  64. package/dist/{save-h9dzHwJE.js → save-CcfoIn2J.js} +1 -1
  65. package/dist/{search-stTGSec-.js → search-C3YRxW9e.js} +1 -1
  66. package/dist/{square-pen-DT5srCPD.js → square-pen-Dr1X-P2s.js} +1 -1
  67. package/dist/{star-gp4yO3PF.js → star-1Ixfuzai.js} +1 -1
  68. package/dist/{store-DJZDAvCY.js → store-BwU935v5.js} +1 -1
  69. package/dist/{trash-2-DwcjTxYE.js → trash-2-CC4KugEJ.js} +1 -1
  70. package/dist/{trending-up-2q2sPVsb.js → trending-up-1brglj1E.js} +1 -1
  71. package/dist/{triangle-alert-CU60xaOi.js → triangle-alert-Dh56Yu3-.js} +1 -1
  72. package/dist/{truck-ByZxqZhI.js → truck-DxhtLfkG.js} +1 -1
  73. package/dist/{upload-CWor1uj3.js → upload-CLf0JLXW.js} +1 -1
  74. package/dist/{useAssistant-uoc8wjr0.js → useAssistant-B9qBtRKm.js} +1 -1
  75. package/dist/{useCustomers-BPPhQXSH.js → useCustomers-BQ4AIEAT.js} +1 -1
  76. package/dist/{useFleet-CT_iW2V9.js → useFleet-BfA_-VUW.js} +101 -72
  77. package/dist/{useImportExport-BG0Cg_D9.js → useImportExport-7-O8noRJ.js} +1 -1
  78. package/dist/{useInventory-BtBGnOC2.js → useInventory-BO7TSgq_.js} +99 -73
  79. package/dist/{useOrders-BC-1mCc0.js → useOrders-B5FV0kcz.js} +1 -1
  80. package/dist/{useReports-Cx8blThR.js → useReports-hUXb8ryJ.js} +1 -1
  81. package/dist/{useRoutes-8fw8oO6y.js → useRoutes-C_ZCYJ_h.js} +1 -1
  82. package/dist/{useSettings-Do5WSUaI.js → useSettings-BNNzQs_B.js} +2 -2
  83. package/dist/{useTracking-Dogilo5C.js → useTracking-7nd_XOSL.js} +3 -3
  84. package/dist/{user-Dr_JS2FF.js → user-B1IjWJtn.js} +1 -1
  85. package/dist/{user-check-CBSiMH6I.js → user-check-Cx6fHHll.js} +1 -1
  86. package/dist/{users-BRV9-2nV.js → users-BKmsb79k.js} +1 -1
  87. package/dist/{warehouse-CmIAl0_w.js → warehouse-DE1irFyJ.js} +1 -1
  88. package/dist/{wrench-6U-BTSbg.js → wrench-DTJGFs17.js} +1 -1
  89. package/dist/{x-DX8sZrkd.js → x-CvsUGJy1.js} +1 -1
  90. package/package.json +1 -1
  91. package/src/movethewheels/MoveTheWheelsRoutes.tsx +5 -0
  92. package/src/movethewheels/components/GlassPanel.tsx +1 -3
  93. package/src/movethewheels/components/MetricCard.tsx +16 -3
  94. package/src/movethewheels/components/charts/StatusDonut.tsx +2 -6
  95. package/src/movethewheels/components/charts/TrendChart.tsx +2 -6
  96. package/src/movethewheels/components/maps/LogisticsMap.tsx +1 -4
  97. package/src/movethewheels/components/maps/RouteMap.tsx +3 -5
  98. package/src/movethewheels/components/maps/TrackingMap.tsx +11 -2
  99. package/src/movethewheels/hooks/useFleet.ts +113 -0
  100. package/src/movethewheels/hooks/useInventory.ts +86 -0
  101. package/src/movethewheels/hooks/useSettings.ts +8 -1
  102. package/src/movethewheels/pages/CreateOrderPage.tsx +48 -6
  103. package/src/movethewheels/pages/CustomerDetailsPage.tsx +3 -2
  104. package/src/movethewheels/pages/DriverDetailsPage.tsx +58 -20
  105. package/src/movethewheels/pages/OrderDetailsPage.tsx +2 -1
  106. package/src/movethewheels/pages/ProductDetailsPage.tsx +51 -15
  107. package/src/movethewheels/pages/RouteDetailsPage.tsx +51 -7
  108. package/src/movethewheels/pages/VehicleDetailsPage.tsx +53 -17
  109. package/src/movethewheels/pages/VehiclesPage.tsx +9 -1
  110. package/src/movethewheels/pages/WarehouseDetailsPage.tsx +49 -11
  111. package/src/movethewheels/types/index.ts +3 -0
  112. package/src/movethewheels/utils/graphqlClient.ts +51 -1
  113. package/dist/DriverDetailsPage-CBzAmM6s.js +0 -490
  114. package/dist/ProductDetailsPage-BMlRoR9E.js +0 -350
  115. package/dist/RouteDetailsPage-Btb1MSIS.js +0 -610
  116. package/dist/VehicleDetailsPage-DNLrMljV.js +0 -393
  117. package/dist/WarehouseDetailsPage-CAQFz7l_.js +0 -493
@@ -2,8 +2,8 @@
2
2
  * Product Details Page
3
3
  */
4
4
 
5
- import { useState } from 'react';
6
- import { useParams } from 'react-router-dom';
5
+ import { useEffect, useState } from 'react';
6
+ import { useLocation, useParams } from 'react-router-dom';
7
7
  import { Package, ArrowLeft, Edit, Box, DollarSign, Tag, Save } from 'lucide-react';
8
8
  import { PageLayout, LoadingState, ErrorState } from '../components/PageLayout';
9
9
  import {
@@ -23,9 +23,11 @@ import { formatCurrency } from '../utils/formatters';
23
23
  import { joinPath } from '../utils/navigation';
24
24
  import { FormError } from '../components/FormError';
25
25
 
26
- function NewProductForm() {
26
+ function NewProductForm({ editId }: { editId?: string }) {
27
27
  const { basePath, navigate } = useMoveTheWheels();
28
- const { createProduct } = useInventory();
28
+ const { createProduct, updateProduct } = useInventory();
29
+ const { product } = useProduct(editId);
30
+ const isEditing = !!editId;
29
31
  const [form, setForm] = useState({
30
32
  name: '',
31
33
  sku: '',
@@ -36,6 +38,18 @@ function NewProductForm() {
36
38
  const [submitting, setSubmitting] = useState(false);
37
39
  const [error, setError] = useState<string | null>(null);
38
40
 
41
+ useEffect(() => {
42
+ if (editId && product) {
43
+ setForm({
44
+ name: product.name ?? '',
45
+ sku: product.sku ?? '',
46
+ category: product.category ?? '',
47
+ brand: product.brand ?? '',
48
+ weight: product.weight != null ? String(product.weight) : '',
49
+ });
50
+ }
51
+ }, [editId, product]);
52
+
39
53
  const goBack = () => navigate?.(joinPath(basePath, 'inventory/products'));
40
54
 
41
55
  const handleSubmit = async (e: React.FormEvent) => {
@@ -48,14 +62,25 @@ function NewProductForm() {
48
62
  setError(null);
49
63
  try {
50
64
  const weight = form.weight.trim() ? Number(form.weight) : undefined;
51
- const created = await createProduct({
52
- name: form.name.trim(),
53
- sku: form.sku.trim(),
54
- category: form.category.trim() || undefined,
55
- brand: form.brand.trim() || undefined,
56
- weight: weight !== undefined && !Number.isNaN(weight) ? weight : undefined,
57
- });
58
- navigate?.(joinPath(basePath, `inventory/products/${created.id}`));
65
+ if (isEditing && editId) {
66
+ const updated = await updateProduct(editId, {
67
+ name: form.name.trim(),
68
+ sku: form.sku.trim(),
69
+ category: form.category.trim() || undefined,
70
+ brand: form.brand.trim() || undefined,
71
+ weight: weight !== undefined && !Number.isNaN(weight) ? weight : undefined,
72
+ });
73
+ navigate?.(joinPath(basePath, `inventory/products/${updated.id}`));
74
+ } else {
75
+ const created = await createProduct({
76
+ name: form.name.trim(),
77
+ sku: form.sku.trim(),
78
+ category: form.category.trim() || undefined,
79
+ brand: form.brand.trim() || undefined,
80
+ weight: weight !== undefined && !Number.isNaN(weight) ? weight : undefined,
81
+ });
82
+ navigate?.(joinPath(basePath, `inventory/products/${created.id}`));
83
+ }
59
84
  } catch (err) {
60
85
  console.error('Failed to create product:', err);
61
86
  setError(err instanceof Error ? err.message : 'Failed to create product. Please try again.');
@@ -65,7 +90,7 @@ function NewProductForm() {
65
90
 
66
91
  return (
67
92
  <PageLayout
68
- title="Add Product"
93
+ title={isEditing ? 'Edit Product' : 'Add Product'}
69
94
  subtitle="Add an item to your catalog"
70
95
  icon={<Package size={24} />}
71
96
  headerActions={
@@ -74,7 +99,14 @@ function NewProductForm() {
74
99
  <ArrowLeft size={16} /> Cancel
75
100
  </Button>
76
101
  <Button onClick={handleSubmit} disabled={submitting}>
77
- <Save size={16} /> {submitting ? 'Saving...' : 'Add Product'}
102
+ <Save size={16} />{' '}
103
+ {isEditing
104
+ ? submitting
105
+ ? 'Saving...'
106
+ : 'Save Changes'
107
+ : submitting
108
+ ? 'Saving...'
109
+ : 'Add Product'}
78
110
  </Button>
79
111
  </div>
80
112
  }
@@ -158,11 +190,13 @@ export default function ProductDetailsPage() {
158
190
  const { productId } = useParams<{ productId: string }>();
159
191
  const { basePath, navigate } = useMoveTheWheels();
160
192
  const { product, isLoading, error, refetch } = useProduct(productId);
193
+ const isEditRoute = useLocation().pathname.endsWith('/edit');
161
194
 
162
195
  const goBack = () => navigate?.(joinPath(basePath, 'inventory/products'));
163
196
  const goTo = (path: string) => navigate?.(joinPath(basePath, path));
164
197
 
165
198
  if (!productId) return <NewProductForm />;
199
+ if (isEditRoute) return <NewProductForm editId={productId} />;
166
200
  if (isLoading) return <LoadingState message="Loading product details..." />;
167
201
  if (error || !product) return <ErrorState title="Product not found" onRetry={refetch} />;
168
202
 
@@ -176,7 +210,9 @@ export default function ProductDetailsPage() {
176
210
  <Button variant="outline" onClick={goBack}>
177
211
  <ArrowLeft size={16} /> Back
178
212
  </Button>
179
- <Button>
213
+ <Button
214
+ onClick={() => navigate?.(joinPath(basePath, `inventory/products/${productId}/edit`))}
215
+ >
180
216
  <Edit size={16} /> Edit
181
217
  </Button>
182
218
  </div>
@@ -2,8 +2,8 @@
2
2
  * Route Details Page
3
3
  */
4
4
 
5
- import { useState } from 'react';
6
- import { useParams } from 'react-router-dom';
5
+ import { useEffect, useState } from 'react';
6
+ import { useParams, useLocation } from 'react-router-dom';
7
7
  import {
8
8
  Navigation,
9
9
  ArrowLeft,
@@ -56,10 +56,12 @@ const ROUTE_STATUS_OPTIONS: { value: RouteType['status']; label: string }[] = [
56
56
  { value: 'cancelled', label: 'Cancelled' },
57
57
  ];
58
58
 
59
- function NewRouteForm() {
59
+ function NewRouteForm({ editId }: { editId?: string }) {
60
60
  const { basePath, navigate } = useMoveTheWheels();
61
- const { createRoute } = useRoutes();
61
+ const { createRoute, updateRoute } = useRoutes();
62
+ const { route } = useRoute(editId);
62
63
  const { vehicles, drivers } = useFleet();
64
+ const isEditing = !!editId;
63
65
  const [form, setForm] = useState<{
64
66
  name: string;
65
67
  status: RouteType['status'];
@@ -76,6 +78,24 @@ function NewRouteForm() {
76
78
  const [submitting, setSubmitting] = useState(false);
77
79
  const [error, setError] = useState<string | null>(null);
78
80
 
81
+ // When editing, prefill the form from the loaded route once it resolves.
82
+ useEffect(() => {
83
+ if (!editId || !route) return;
84
+ setForm({
85
+ name: route.name || '',
86
+ status: route.status,
87
+ vehicleId: route.vehicleId || '',
88
+ driverId: route.driverId || '',
89
+ stops:
90
+ route.stops.length > 0
91
+ ? route.stops.map((stop) => ({
92
+ address: formatAddress(stop.address) || stop.address.street || '',
93
+ type: stop.type,
94
+ }))
95
+ : [{ address: '', type: 'delivery' }],
96
+ });
97
+ }, [editId, route]);
98
+
79
99
  const goBack = () => navigate?.(joinPath(basePath, 'routes'));
80
100
 
81
101
  const addStop = () =>
@@ -135,6 +155,17 @@ function NewRouteForm() {
135
155
  duration: 0,
136
156
  status: 'pending',
137
157
  }));
158
+ if (isEditing) {
159
+ const updated = await updateRoute(editId, {
160
+ name: form.name.trim(),
161
+ status: form.status,
162
+ vehicleId: form.vehicleId || undefined,
163
+ driverId: form.driverId || undefined,
164
+ stops,
165
+ });
166
+ navigate?.(joinPath(basePath, `routes/${updated.id}`));
167
+ return;
168
+ }
138
169
  const created = await createRoute({
139
170
  name: form.name.trim(),
140
171
  status: form.status,
@@ -152,7 +183,7 @@ function NewRouteForm() {
152
183
 
153
184
  return (
154
185
  <PageLayout
155
- title="Create Route"
186
+ title={isEditing ? 'Edit Route' : 'Create Route'}
156
187
  subtitle="Plan a new delivery route"
157
188
  icon={<Navigation size={24} />}
158
189
  headerActions={
@@ -161,7 +192,14 @@ function NewRouteForm() {
161
192
  <ArrowLeft size={16} /> Cancel
162
193
  </Button>
163
194
  <Button onClick={handleSubmit} disabled={submitting}>
164
- <Save size={16} /> {submitting ? 'Saving...' : 'Create Route'}
195
+ <Save size={16} />{' '}
196
+ {isEditing
197
+ ? submitting
198
+ ? 'Saving...'
199
+ : 'Save Changes'
200
+ : submitting
201
+ ? 'Saving...'
202
+ : 'Create Route'}
165
203
  </Button>
166
204
  </div>
167
205
  }
@@ -333,6 +371,7 @@ function NewRouteForm() {
333
371
 
334
372
  export default function RouteDetailsPage() {
335
373
  const { routeId } = useParams<{ routeId: string }>();
374
+ const isEditRoute = useLocation().pathname.endsWith('/edit');
336
375
  const { basePath, navigate } = useMoveTheWheels();
337
376
  const { route, isLoading, error, refetch } = useRoute(routeId);
338
377
  const { optimizeRoute } = useRoutes();
@@ -363,6 +402,11 @@ export default function RouteDetailsPage() {
363
402
  return <NewRouteForm />;
364
403
  }
365
404
 
405
+ // Handle edit route form
406
+ if (isEditRoute) {
407
+ return <NewRouteForm editId={routeId} />;
408
+ }
409
+
366
410
  if (isLoading) return <LoadingState message="Loading route details..." />;
367
411
  if (error || !route) return <ErrorState title="Route not found" onRetry={refetch} />;
368
412
 
@@ -397,7 +441,7 @@ export default function RouteDetailsPage() {
397
441
  <Zap size={16} /> {optimizing ? 'Optimizing…' : 'Optimize'}
398
442
  </Button>
399
443
  )}
400
- <Button>
444
+ <Button onClick={() => navigate?.(joinPath(basePath, `routes/${routeId}/edit`))}>
401
445
  <Edit size={16} /> Edit
402
446
  </Button>
403
447
  </div>
@@ -2,8 +2,8 @@
2
2
  * Vehicle Details Page
3
3
  */
4
4
 
5
- import { useState } from 'react';
6
- import { useParams } from 'react-router-dom';
5
+ import { useEffect, useState } from 'react';
6
+ import { useParams, useLocation } from 'react-router-dom';
7
7
  import { Truck, ArrowLeft, MapPin, Fuel, Wrench, Edit, Save } from 'lucide-react';
8
8
  import { PageLayout, LoadingState, ErrorState } from '../components/PageLayout';
9
9
  import { StatusBadge, VehicleTypeBadge } from '../components/StatusBadge';
@@ -41,9 +41,11 @@ const VEHICLE_TYPE_OPTIONS = [
41
41
  { value: 'DRONE', label: 'Drone' },
42
42
  ];
43
43
 
44
- function NewVehicleForm() {
44
+ function NewVehicleForm({ editId }: { editId?: string }) {
45
45
  const { basePath, navigate } = useMoveTheWheels();
46
- const { createVehicle } = useFleet();
46
+ const { createVehicle, updateVehicle } = useFleet();
47
+ const { vehicle } = useVehicle(editId);
48
+ const isEditing = !!editId;
47
49
  const [form, setForm] = useState({
48
50
  vehicleNumber: '',
49
51
  type: 'VAN',
@@ -54,6 +56,18 @@ function NewVehicleForm() {
54
56
  const [submitting, setSubmitting] = useState(false);
55
57
  const [error, setError] = useState<string | null>(null);
56
58
 
59
+ useEffect(() => {
60
+ if (editId && vehicle) {
61
+ setForm({
62
+ vehicleNumber: vehicle.vehicleNumber ?? '',
63
+ type: vehicle.type ? vehicle.type.toUpperCase() : 'VAN',
64
+ make: vehicle.make ?? '',
65
+ model: vehicle.model ?? '',
66
+ licensePlate: vehicle.licensePlate ?? '',
67
+ });
68
+ }
69
+ }, [editId, vehicle]);
70
+
57
71
  const goBack = () => navigate?.(joinPath(basePath, 'fleet/vehicles'));
58
72
 
59
73
  const handleSubmit = async (e: React.FormEvent) => {
@@ -65,14 +79,25 @@ function NewVehicleForm() {
65
79
  setSubmitting(true);
66
80
  setError(null);
67
81
  try {
68
- const created = await createVehicle({
69
- vehicleNumber: form.vehicleNumber.trim(),
70
- type: form.type,
71
- make: form.make.trim() || undefined,
72
- model: form.model.trim() || undefined,
73
- licensePlate: form.licensePlate.trim() || undefined,
74
- });
75
- navigate?.(joinPath(basePath, `fleet/vehicles/${created.id}`));
82
+ if (editId) {
83
+ const updated = await updateVehicle(editId, {
84
+ vehicleNumber: form.vehicleNumber.trim(),
85
+ type: form.type,
86
+ make: form.make.trim() || undefined,
87
+ model: form.model.trim() || undefined,
88
+ licensePlate: form.licensePlate.trim() || undefined,
89
+ });
90
+ navigate?.(joinPath(basePath, `fleet/vehicles/${updated.id}`));
91
+ } else {
92
+ const created = await createVehicle({
93
+ vehicleNumber: form.vehicleNumber.trim(),
94
+ type: form.type,
95
+ make: form.make.trim() || undefined,
96
+ model: form.model.trim() || undefined,
97
+ licensePlate: form.licensePlate.trim() || undefined,
98
+ });
99
+ navigate?.(joinPath(basePath, `fleet/vehicles/${created.id}`));
100
+ }
76
101
  } catch (err) {
77
102
  console.error('Failed to create vehicle:', err);
78
103
  setError(err instanceof Error ? err.message : 'Failed to create vehicle. Please try again.');
@@ -82,7 +107,7 @@ function NewVehicleForm() {
82
107
 
83
108
  return (
84
109
  <PageLayout
85
- title="Add Vehicle"
110
+ title={isEditing ? 'Edit Vehicle' : 'Add Vehicle'}
86
111
  subtitle="Register a new vehicle in your fleet"
87
112
  icon={<Truck size={24} />}
88
113
  headerActions={
@@ -91,14 +116,21 @@ function NewVehicleForm() {
91
116
  <ArrowLeft size={16} /> Cancel
92
117
  </Button>
93
118
  <Button onClick={handleSubmit} disabled={submitting}>
94
- <Save size={16} /> {submitting ? 'Saving...' : 'Add Vehicle'}
119
+ <Save size={16} />{' '}
120
+ {isEditing
121
+ ? submitting
122
+ ? 'Saving...'
123
+ : 'Save Changes'
124
+ : submitting
125
+ ? 'Saving...'
126
+ : 'Add Vehicle'}
95
127
  </Button>
96
128
  </div>
97
129
  }
98
130
  >
99
131
  <PagePurpose icon={<Truck size={18} />}>
100
- Add a vehicle so it can be assigned to deliveries and routes. Give it a fleet number and type
101
- to start — make, model and plate help identify it on the road.
132
+ Add a vehicle so it can be assigned to deliveries and routes. Give it a fleet number and
133
+ type to start — make, model and plate help identify it on the road.
102
134
  </PagePurpose>
103
135
 
104
136
  <form onSubmit={handleSubmit} className="max-w-2xl space-y-6">
@@ -180,11 +212,13 @@ export default function VehicleDetailsPage() {
180
212
  const { vehicleId } = useParams<{ vehicleId: string }>();
181
213
  const { basePath, navigate } = useMoveTheWheels();
182
214
  const { vehicle, isLoading, error, refetch } = useVehicle(vehicleId);
215
+ const isEditRoute = useLocation().pathname.endsWith('/edit');
183
216
 
184
217
  const goBack = () => navigate?.(joinPath(basePath, 'fleet/vehicles'));
185
218
  const goTo = (path: string) => navigate?.(joinPath(basePath, path));
186
219
 
187
220
  if (!vehicleId) return <NewVehicleForm />;
221
+ if (isEditRoute) return <NewVehicleForm editId={vehicleId} />;
188
222
  if (isLoading) return <LoadingState message="Loading vehicle details..." />;
189
223
  if (error || !vehicle) return <ErrorState title="Vehicle not found" onRetry={refetch} />;
190
224
 
@@ -198,7 +232,9 @@ export default function VehicleDetailsPage() {
198
232
  <Button variant="outline" onClick={goBack}>
199
233
  <ArrowLeft size={16} /> Back
200
234
  </Button>
201
- <Button>
235
+ <Button
236
+ onClick={() => navigate?.(joinPath(basePath, 'fleet/vehicles/' + vehicleId + '/edit'))}
237
+ >
202
238
  <Edit size={16} /> Edit
203
239
  </Button>
204
240
  </div>
@@ -3,7 +3,15 @@
3
3
  */
4
4
 
5
5
  import { useState } from 'react';
6
- import { Truck, Plus, RefreshCw, Download, CircleCheck, Route as RouteIcon, Wrench } from 'lucide-react';
6
+ import {
7
+ Truck,
8
+ Plus,
9
+ RefreshCw,
10
+ Download,
11
+ CircleCheck,
12
+ Route as RouteIcon,
13
+ Wrench,
14
+ } from 'lucide-react';
7
15
  import { PageLayout } from '../components/PageLayout';
8
16
  import { MetricCard, MetricGrid } from '../components/MetricCard';
9
17
  import { DataTable, type Column } from '../components/DataTable';
@@ -2,8 +2,8 @@
2
2
  * Warehouse Details Page
3
3
  */
4
4
 
5
- import { useState } from 'react';
6
- import { useParams } from 'react-router-dom';
5
+ import { useEffect, useState } from 'react';
6
+ import { useLocation, useParams } from 'react-router-dom';
7
7
  import { Warehouse, ArrowLeft, Edit, MapPin, Clock, Users, Box, Save } from 'lucide-react';
8
8
  import { PageLayout, LoadingState, ErrorState } from '../components/PageLayout';
9
9
  import {
@@ -37,9 +37,11 @@ const WAREHOUSE_TYPE_OPTIONS = [
37
37
  { value: 'RETAIL', label: 'Retail' },
38
38
  ];
39
39
 
40
- function NewWarehouseForm() {
40
+ function NewWarehouseForm({ editId }: { editId?: string }) {
41
41
  const { basePath, navigate } = useMoveTheWheels();
42
- const { createWarehouse } = useInventory();
42
+ const { createWarehouse, updateWarehouse } = useInventory();
43
+ const { warehouse } = useWarehouse(editId);
44
+ const isEditing = !!editId;
43
45
  const [form, setForm] = useState({
44
46
  code: '',
45
47
  name: '',
@@ -54,6 +56,21 @@ function NewWarehouseForm() {
54
56
  const [submitting, setSubmitting] = useState(false);
55
57
  const [error, setError] = useState<string | null>(null);
56
58
 
59
+ useEffect(() => {
60
+ if (!editId || !warehouse) return;
61
+ setForm({
62
+ code: warehouse.code || '',
63
+ name: warehouse.name || '',
64
+ type: warehouse.type ? warehouse.type.toUpperCase() : 'DISTRIBUTION',
65
+ street: warehouse.address.street || '',
66
+ city: warehouse.address.city || '',
67
+ state: warehouse.address.state || '',
68
+ postalCode: warehouse.address.postalCode || '',
69
+ country: warehouse.address.country || 'USA',
70
+ totalCapacity: warehouse.capacity.totalSpace ? String(warehouse.capacity.totalSpace) : '',
71
+ });
72
+ }, [editId, warehouse]);
73
+
57
74
  const goBack = () => navigate?.(joinPath(basePath, 'inventory/warehouses'));
58
75
 
59
76
  const update = (field: keyof typeof form, value: string) =>
@@ -73,7 +90,7 @@ function NewWarehouseForm() {
73
90
  setError(null);
74
91
  try {
75
92
  const totalCapacity = form.totalCapacity.trim() ? Number(form.totalCapacity) : undefined;
76
- const created = await createWarehouse({
93
+ const warehouseInput = {
77
94
  code: form.code.trim(),
78
95
  name: form.name.trim(),
79
96
  type: form.type,
@@ -86,18 +103,26 @@ function NewWarehouseForm() {
86
103
  },
87
104
  totalCapacity:
88
105
  totalCapacity !== undefined && !Number.isNaN(totalCapacity) ? totalCapacity : undefined,
89
- });
90
- navigate?.(joinPath(basePath, `inventory/warehouses/${created.id}`));
106
+ };
107
+ if (editId) {
108
+ await updateWarehouse(editId, warehouseInput);
109
+ navigate?.(joinPath(basePath, `inventory/warehouses/${editId}`));
110
+ } else {
111
+ const created = await createWarehouse(warehouseInput);
112
+ navigate?.(joinPath(basePath, `inventory/warehouses/${created.id}`));
113
+ }
91
114
  } catch (err) {
92
115
  console.error('Failed to create warehouse:', err);
93
- setError(err instanceof Error ? err.message : 'Failed to create warehouse. Please try again.');
116
+ setError(
117
+ err instanceof Error ? err.message : 'Failed to create warehouse. Please try again.'
118
+ );
94
119
  setSubmitting(false);
95
120
  }
96
121
  };
97
122
 
98
123
  return (
99
124
  <PageLayout
100
- title="Add Warehouse"
125
+ title={isEditing ? 'Edit Warehouse' : 'Add Warehouse'}
101
126
  subtitle="Register a storage and fulfilment site"
102
127
  icon={<Warehouse size={24} />}
103
128
  headerActions={
@@ -106,7 +131,14 @@ function NewWarehouseForm() {
106
131
  <ArrowLeft size={16} /> Cancel
107
132
  </Button>
108
133
  <Button onClick={handleSubmit} disabled={submitting}>
109
- <Save size={16} /> {submitting ? 'Saving...' : 'Add Warehouse'}
134
+ <Save size={16} />{' '}
135
+ {isEditing
136
+ ? submitting
137
+ ? 'Saving...'
138
+ : 'Save Changes'
139
+ : submitting
140
+ ? 'Saving...'
141
+ : 'Add Warehouse'}
110
142
  </Button>
111
143
  </div>
112
144
  }
@@ -244,12 +276,14 @@ function NewWarehouseForm() {
244
276
  export default function WarehouseDetailsPage() {
245
277
  const { warehouseId } = useParams<{ warehouseId: string }>();
246
278
  const { basePath, navigate } = useMoveTheWheels();
279
+ const isEditRoute = useLocation().pathname.endsWith('/edit');
247
280
  const { warehouse, isLoading, error, refetch } = useWarehouse(warehouseId);
248
281
 
249
282
  const goBack = () => navigate?.(joinPath(basePath, 'inventory/warehouses'));
250
283
  const goTo = (path: string) => navigate?.(joinPath(basePath, path));
251
284
 
252
285
  if (!warehouseId) return <NewWarehouseForm />;
286
+ if (isEditRoute) return <NewWarehouseForm editId={warehouseId} />;
253
287
  if (isLoading) return <LoadingState message="Loading warehouse details..." />;
254
288
  if (error || !warehouse) return <ErrorState title="Warehouse not found" onRetry={refetch} />;
255
289
 
@@ -265,7 +299,11 @@ export default function WarehouseDetailsPage() {
265
299
  <Button variant="outline" onClick={goBack}>
266
300
  <ArrowLeft size={16} /> Back
267
301
  </Button>
268
- <Button>
302
+ <Button
303
+ onClick={() =>
304
+ navigate?.(joinPath(basePath, `inventory/warehouses/${warehouseId}/edit`))
305
+ }
306
+ >
269
307
  <Edit size={16} /> Edit
270
308
  </Button>
271
309
  </div>
@@ -307,6 +307,7 @@ export interface Vehicle {
307
307
  type: VehicleType;
308
308
  make: string;
309
309
  model: string;
310
+ licensePlate: string;
310
311
  year: number;
311
312
  capacity: {
312
313
  weight: number;
@@ -339,6 +340,8 @@ export interface Driver {
339
340
  id: string;
340
341
  userId: string;
341
342
  fullName: string;
343
+ email: string;
344
+ phone: string;
342
345
  employeeId: string;
343
346
  licenseNumber: string;
344
347
  licenseType: string;
@@ -69,6 +69,40 @@ function isAbortError(error: unknown): boolean {
69
69
 
70
70
  const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
71
71
 
72
+ /**
73
+ * Global in-flight cap across every GraphQL call this MFE makes. The workspace
74
+ * service runs a single lean replica (no PgBouncer), and a page like the
75
+ * dashboard fans out ~7 queries at once; multiplied by the retry loop below a
76
+ * transient blip could balloon into dozens of simultaneous requests and tip the
77
+ * replica over. A small semaphore keeps the fan-out polite — requests beyond the
78
+ * limit queue for a moment instead of stampeding the service. 4 is high enough
79
+ * that normal navigation feels instant, low enough that a burst never floods.
80
+ */
81
+ const MAX_CONCURRENT = 4;
82
+ let activeRequests = 0;
83
+ const pendingAcquirers: Array<() => void> = [];
84
+
85
+ function acquireSlot(): Promise<void> {
86
+ return new Promise((resolve) => {
87
+ if (activeRequests < MAX_CONCURRENT) {
88
+ activeRequests += 1;
89
+ resolve();
90
+ } else {
91
+ pendingAcquirers.push(resolve);
92
+ }
93
+ });
94
+ }
95
+
96
+ function releaseSlot(): void {
97
+ const next = pendingAcquirers.shift();
98
+ if (next) {
99
+ // Hand the slot straight to the next waiter — activeRequests stays put.
100
+ next();
101
+ } else {
102
+ activeRequests -= 1;
103
+ }
104
+ }
105
+
72
106
  export async function executeGraphQL<TData>(
73
107
  options: GraphQLRequestOptions,
74
108
  query: string,
@@ -88,12 +122,28 @@ export async function executeGraphQL<TData>(
88
122
  };
89
123
  const body = JSON.stringify({ query, variables });
90
124
 
125
+ // Serialize past the global concurrency cap before doing any network work,
126
+ // and always release the slot when this call settles (success or failure).
127
+ await acquireSlot();
128
+ try {
129
+ return await runWithRetries<TData>(url, headers, body);
130
+ } finally {
131
+ releaseSlot();
132
+ }
133
+ }
134
+
135
+ async function runWithRetries<TData>(
136
+ url: string,
137
+ headers: Record<string, string>,
138
+ body: string
139
+ ): Promise<TData> {
91
140
  let lastError: Error = new Error('GraphQL request failed');
92
141
 
93
142
  for (let attempt = 0; attempt <= MAX_RETRIES; attempt += 1) {
94
143
  const canRetry = attempt < MAX_RETRIES;
95
144
  // Exponential backoff with jitter before each retry (not the first attempt).
96
- if (attempt > 0) await sleep(Math.min(250 * 2 ** (attempt - 1), 2000) + Math.floor(Math.random() * 150));
145
+ if (attempt > 0)
146
+ await sleep(Math.min(250 * 2 ** (attempt - 1), 2000) + Math.floor(Math.random() * 150));
97
147
 
98
148
  const controller = new AbortController();
99
149
  const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);