@burdenoff/microfe-movethewheels 2026.703.8 → 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 (37) hide show
  1. package/dist/{CreateOrderPage-V8aXfcpN.js → CreateOrderPage-CXbhJLLz.js} +163 -138
  2. package/dist/{DashboardPage-DiD-cWwD.js → DashboardPage-BoswH3ec.js} +2 -2
  3. package/dist/DriverDetailsPage-CPO0G8Ki.js +518 -0
  4. package/dist/{DriversPage-DW-FGPFW.js → DriversPage-6Z61dpDI.js} +1 -1
  5. package/dist/{FleetPage-P9k4zZqP.js → FleetPage-qWRj7WFO.js} +1 -1
  6. package/dist/{InventoryPage-BWikjZMW.js → InventoryPage-DbR4Z--d.js} +1 -1
  7. package/dist/{LiveTrackingPage-UQBWlthJ.js → LiveTrackingPage-5U68u3oA.js} +1 -1
  8. package/dist/{OrderDetailsPage-2J4ua0wd.js → OrderDetailsPage-DRYifXm9.js} +1 -1
  9. package/dist/ProductDetailsPage-CWgcRqGR.js +375 -0
  10. package/dist/{ProductsPage-Bn1U2jTj.js → ProductsPage-CokwDOR2.js} +1 -1
  11. package/dist/{RouteDetailsPage-CgSY1CTd.js → RouteDetailsPage-BHQbFgI9.js} +242 -210
  12. package/dist/{SettingsPage-2a05Sq2k.js → SettingsPage-_6SIlcBX.js} +1 -1
  13. package/dist/{TrackingPage-SnjW6qeD.js → TrackingPage-Cg0tYGJe.js} +1 -1
  14. package/dist/{VehicleDetailsPage-CvRMJ2zC.js → VehicleDetailsPage-BbZlCFPo.js} +159 -135
  15. package/dist/{VehiclesPage-qxf-1C1B.js → VehiclesPage-BfBEyXGs.js} +1 -1
  16. package/dist/WarehouseDetailsPage-DxNhiaoI.js +515 -0
  17. package/dist/{WarehousesPage-44Wj7AGT.js → WarehousesPage-M3NaBT0z.js} +1 -1
  18. package/dist/index.js +25 -5
  19. package/dist/{useFleet-BXSocBD_.js → useFleet-BfA_-VUW.js} +100 -71
  20. package/dist/{useInventory-DmZficcp.js → useInventory-BO7TSgq_.js} +98 -72
  21. package/dist/{useSettings-CteNasBE.js → useSettings-BNNzQs_B.js} +1 -1
  22. package/dist/{useTracking-B7ttexRe.js → useTracking-7nd_XOSL.js} +1 -1
  23. package/package.json +1 -1
  24. package/src/movethewheels/MoveTheWheelsRoutes.tsx +5 -0
  25. package/src/movethewheels/hooks/useFleet.ts +113 -0
  26. package/src/movethewheels/hooks/useInventory.ts +86 -0
  27. package/src/movethewheels/hooks/useSettings.ts +8 -1
  28. package/src/movethewheels/pages/CreateOrderPage.tsx +48 -6
  29. package/src/movethewheels/pages/DriverDetailsPage.tsx +56 -18
  30. package/src/movethewheels/pages/ProductDetailsPage.tsx +51 -15
  31. package/src/movethewheels/pages/RouteDetailsPage.tsx +51 -7
  32. package/src/movethewheels/pages/VehicleDetailsPage.tsx +51 -15
  33. package/src/movethewheels/pages/WarehouseDetailsPage.tsx +46 -10
  34. package/src/movethewheels/types/index.ts +3 -0
  35. package/dist/DriverDetailsPage-DjaXaATQ.js +0 -490
  36. package/dist/ProductDetailsPage-BSjWLrty.js +0 -350
  37. package/dist/WarehouseDetailsPage-D0Wyyj1Y.js +0 -493
@@ -47,6 +47,8 @@ interface UseInventoryResult {
47
47
  ) => Promise<void>;
48
48
  createProduct: (input: CreateProductInput) => Promise<Product>;
49
49
  createWarehouse: (input: CreateWarehouseInput) => Promise<Warehouse>;
50
+ updateProduct: (id: string, input: CreateProductInput) => Promise<Product>;
51
+ updateWarehouse: (id: string, input: CreateWarehouseInput) => Promise<Warehouse>;
50
52
  }
51
53
 
52
54
  interface ApiInventoryItem {
@@ -212,6 +214,27 @@ const CREATE_PRODUCT_MUTATION = `
212
214
  }
213
215
  `;
214
216
 
217
+ const UPDATE_PRODUCT_MUTATION = `
218
+ mutation UpdateProduct($id: ID!, $input: UpdateProductInput!) {
219
+ updateProduct(id: $id, input: $input) {
220
+ id
221
+ sku
222
+ name
223
+ description
224
+ category
225
+ brand
226
+ dimensions
227
+ weight
228
+ value
229
+ hazardous
230
+ fragile
231
+ temperatureControlled
232
+ createdAt
233
+ updatedAt
234
+ }
235
+ }
236
+ `;
237
+
215
238
  const CREATE_WAREHOUSE_MUTATION = `
216
239
  mutation CreateWarehouse($input: CreateWarehouseInput!) {
217
240
  createWarehouse(input: $input) {
@@ -227,6 +250,21 @@ const CREATE_WAREHOUSE_MUTATION = `
227
250
  }
228
251
  `;
229
252
 
253
+ const UPDATE_WAREHOUSE_MUTATION = `
254
+ mutation UpdateWarehouse($id: ID!, $input: UpdateWarehouseInput!) {
255
+ updateWarehouse(id: $id, input: $input) {
256
+ id
257
+ name
258
+ code
259
+ type
260
+ address
261
+ totalCapacity
262
+ usedCapacity
263
+ createdAt
264
+ }
265
+ }
266
+ `;
267
+
230
268
  const ADJUST_INVENTORY_MUTATION = `
231
269
  mutation AdjustInventoryStock($input: AdjustInventoryStockInput!) {
232
270
  adjustInventoryStock(input: $input) {
@@ -519,6 +557,52 @@ export function useInventory(options: UseInventoryOptions = {}): UseInventoryRes
519
557
  [graphQLOptions]
520
558
  );
521
559
 
560
+ const updateProduct = useCallback(
561
+ async (id: string, input: CreateProductInput): Promise<Product> => {
562
+ const data = await executeGraphQL<{ updateProduct: ApiProduct }>(
563
+ graphQLOptions,
564
+ UPDATE_PRODUCT_MUTATION,
565
+ {
566
+ id,
567
+ input: {
568
+ name: input.name,
569
+ sku: input.sku,
570
+ category: input.category,
571
+ brand: input.brand,
572
+ weight: input.weight,
573
+ },
574
+ }
575
+ );
576
+ const updated = mapProduct(data.updateProduct);
577
+ setProducts((previous) => previous.map((item) => (item.id === id ? updated : item)));
578
+ return updated;
579
+ },
580
+ [graphQLOptions]
581
+ );
582
+
583
+ const updateWarehouse = useCallback(
584
+ async (id: string, input: CreateWarehouseInput): Promise<Warehouse> => {
585
+ const data = await executeGraphQL<{ updateWarehouse: ApiWarehouse }>(
586
+ graphQLOptions,
587
+ UPDATE_WAREHOUSE_MUTATION,
588
+ {
589
+ id,
590
+ input: {
591
+ code: input.code,
592
+ name: input.name,
593
+ type: input.type,
594
+ address: input.address,
595
+ totalCapacity: input.totalCapacity,
596
+ },
597
+ }
598
+ );
599
+ const updated = mapWarehouse(data.updateWarehouse);
600
+ setWarehouses((previous) => previous.map((item) => (item.id === id ? updated : item)));
601
+ return updated;
602
+ },
603
+ [graphQLOptions]
604
+ );
605
+
522
606
  return {
523
607
  inventory,
524
608
  products,
@@ -531,6 +615,8 @@ export function useInventory(options: UseInventoryOptions = {}): UseInventoryRes
531
615
  transferStock,
532
616
  createProduct,
533
617
  createWarehouse,
618
+ updateProduct,
619
+ updateWarehouse,
534
620
  };
535
621
  }
536
622
 
@@ -108,9 +108,16 @@ export function useModuleSettings(): UseModuleSettingsResult {
108
108
  [graphQLOptions]
109
109
  );
110
110
 
111
+ // Stabilize the preferences reference. Returning `settings?.preferences ?? {}`
112
+ // inline minted a fresh `{}` on every render while settings was null, which
113
+ // made SettingsPage's `useMemo(toForm, [preferences])` recompute every render
114
+ // and its `useEffect(setForm, [loadedForm])` fire in a loop → "Maximum update
115
+ // depth exceeded" → the page rendered blank behind the shell's error boundary.
116
+ const preferences = useMemo<ModulePreferences>(() => settings?.preferences ?? {}, [settings]);
117
+
111
118
  return {
112
119
  settings,
113
- preferences: settings?.preferences ?? {},
120
+ preferences,
114
121
  isLoading,
115
122
  isSaving,
116
123
  error,
@@ -4,7 +4,7 @@
4
4
  * Form for creating or editing orders.
5
5
  */
6
6
 
7
- import { useState } from 'react';
7
+ import { useEffect, useState } from 'react';
8
8
  import { useParams } from 'react-router-dom';
9
9
  import { Package, ArrowLeft, Save, Plus, Trash2, Users, MapPin } from 'lucide-react';
10
10
  import { PageLayout, EmptyState } from '../components/PageLayout';
@@ -35,7 +35,7 @@ export default function CreateOrderPage() {
35
35
  const { orderId } = useParams<{ orderId: string }>();
36
36
  const { basePath, navigate } = useMoveTheWheels();
37
37
  const { order } = useOrder(orderId);
38
- const { createOrder } = useOrders();
38
+ const { createOrder, updateOrder } = useOrders();
39
39
  const { customers } = useCustomers({ limit: 100 });
40
40
 
41
41
  const isEditing = !!orderId && !!order;
@@ -65,6 +65,36 @@ export default function CreateOrderPage() {
65
65
  const [isSubmitting, setIsSubmitting] = useState(false);
66
66
  const [formError, setFormError] = useState<string | null>(null);
67
67
 
68
+ // Prefill the form from the loaded order when editing. `order` arrives
69
+ // asynchronously after the initial render, so the useState initializers alone
70
+ // leave the fields blank until this syncs them.
71
+ useEffect(() => {
72
+ if (!order) {
73
+ return;
74
+ }
75
+ setFormData({
76
+ customerId: order.customerId || '',
77
+ orderType: order.orderType || 'b2c',
78
+ priority: order.priority || 'normal',
79
+ pickupAddress: {
80
+ street: order.pickupAddress.street || '',
81
+ city: order.pickupAddress.city || '',
82
+ state: order.pickupAddress.state || '',
83
+ postalCode: order.pickupAddress.postalCode || '',
84
+ country: order.pickupAddress.country || 'USA',
85
+ },
86
+ deliveryAddress: {
87
+ street: order.deliveryAddress.street || '',
88
+ city: order.deliveryAddress.city || '',
89
+ state: order.deliveryAddress.state || '',
90
+ postalCode: order.deliveryAddress.postalCode || '',
91
+ country: order.deliveryAddress.country || 'USA',
92
+ },
93
+ items: order.items || [],
94
+ specialInstructions: order.specialInstructions || '',
95
+ });
96
+ }, [order]);
97
+
68
98
  const goBack = () => {
69
99
  if (navigate) {
70
100
  navigate(joinPath(basePath, 'orders'));
@@ -106,7 +136,7 @@ export default function CreateOrderPage() {
106
136
 
107
137
  setIsSubmitting(true);
108
138
  try {
109
- await createOrder({
139
+ const input = {
110
140
  customerId: formData.customerId,
111
141
  customerInfo: {
112
142
  name: selectedCustomer.name,
@@ -120,12 +150,24 @@ export default function CreateOrderPage() {
120
150
  deliveryAddress: formData.deliveryAddress,
121
151
  items: formData.items,
122
152
  specialInstructions: formData.specialInstructions,
123
- });
153
+ };
124
154
 
125
- goBack();
155
+ if (isEditing && orderId) {
156
+ await updateOrder(orderId, input);
157
+ if (navigate) {
158
+ navigate(joinPath(basePath, `orders/${orderId}`));
159
+ }
160
+ } else {
161
+ await createOrder(input);
162
+ goBack();
163
+ }
126
164
  } catch (error) {
127
165
  setFormError(
128
- error instanceof Error ? error.message : 'Could not create the order. Please try again.'
166
+ error instanceof Error
167
+ ? error.message
168
+ : isEditing
169
+ ? 'Could not update the order. Please try again.'
170
+ : 'Could not create the order. Please try again.'
129
171
  );
130
172
  } finally {
131
173
  setIsSubmitting(false);
@@ -2,8 +2,8 @@
2
2
  * Driver 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 {
8
8
  Users,
9
9
  ArrowLeft,
@@ -36,9 +36,11 @@ import { joinPath } from '../utils/navigation';
36
36
  import { FormError } from '../components/FormError';
37
37
  import { DocumentsCard } from '../components/DocumentsCard';
38
38
 
39
- function NewDriverForm() {
39
+ function NewDriverForm({ editId }: { editId?: string }) {
40
40
  const { basePath, navigate } = useMoveTheWheels();
41
- const { createDriver } = useFleet();
41
+ const { createDriver, updateDriver } = useFleet();
42
+ const { driver } = useDriver(editId);
43
+ const isEditing = !!editId;
42
44
  const [form, setForm] = useState({
43
45
  employeeId: '',
44
46
  firstName: '',
@@ -50,6 +52,19 @@ function NewDriverForm() {
50
52
  const [submitting, setSubmitting] = useState(false);
51
53
  const [error, setError] = useState<string | null>(null);
52
54
 
55
+ useEffect(() => {
56
+ if (!editId || !driver) return;
57
+ const [firstName, ...rest] = driver.fullName.trim().split(/\s+/);
58
+ setForm({
59
+ employeeId: driver.employeeId || '',
60
+ firstName: firstName || '',
61
+ lastName: rest.join(' '),
62
+ email: driver.email || '',
63
+ phone: driver.phone || '',
64
+ licenseNumber: driver.licenseNumber || '',
65
+ });
66
+ }, [editId, driver]);
67
+
53
68
  const goBack = () => navigate?.(joinPath(basePath, 'fleet/drivers'));
54
69
 
55
70
  const handleSubmit = async (e: React.FormEvent) => {
@@ -61,25 +76,37 @@ function NewDriverForm() {
61
76
  setSubmitting(true);
62
77
  setError(null);
63
78
  try {
64
- const created = await createDriver({
65
- employeeId: form.employeeId.trim(),
66
- firstName: form.firstName.trim(),
67
- lastName: form.lastName.trim(),
68
- email: form.email.trim() || undefined,
69
- phone: form.phone.trim() || undefined,
70
- licenseNumber: form.licenseNumber.trim() || undefined,
71
- });
72
- navigate?.(joinPath(basePath, `fleet/drivers/${created.id}`));
79
+ if (editId) {
80
+ const updated = await updateDriver(editId, {
81
+ employeeId: form.employeeId.trim(),
82
+ firstName: form.firstName.trim(),
83
+ lastName: form.lastName.trim(),
84
+ email: form.email.trim() || undefined,
85
+ phone: form.phone.trim() || undefined,
86
+ licenseNumber: form.licenseNumber.trim() || undefined,
87
+ });
88
+ navigate?.(joinPath(basePath, `fleet/drivers/${updated.id}`));
89
+ } else {
90
+ const created = await createDriver({
91
+ employeeId: form.employeeId.trim(),
92
+ firstName: form.firstName.trim(),
93
+ lastName: form.lastName.trim(),
94
+ email: form.email.trim() || undefined,
95
+ phone: form.phone.trim() || undefined,
96
+ licenseNumber: form.licenseNumber.trim() || undefined,
97
+ });
98
+ navigate?.(joinPath(basePath, `fleet/drivers/${created.id}`));
99
+ }
73
100
  } catch (err) {
74
- console.error('Failed to create driver:', err);
75
- setError(err instanceof Error ? err.message : 'Failed to create driver. Please try again.');
101
+ console.error('Failed to save driver:', err);
102
+ setError(err instanceof Error ? err.message : 'Failed to save driver. Please try again.');
76
103
  setSubmitting(false);
77
104
  }
78
105
  };
79
106
 
80
107
  return (
81
108
  <PageLayout
82
- title="Add Driver"
109
+ title={isEditing ? 'Edit Driver' : 'Add Driver'}
83
110
  subtitle="Add a driver to your workforce"
84
111
  icon={<Users size={24} />}
85
112
  headerActions={
@@ -88,7 +115,14 @@ function NewDriverForm() {
88
115
  <ArrowLeft size={16} /> Cancel
89
116
  </Button>
90
117
  <Button onClick={handleSubmit} disabled={submitting}>
91
- <Save size={16} /> {submitting ? 'Saving...' : 'Add Driver'}
118
+ <Save size={16} />{' '}
119
+ {isEditing
120
+ ? submitting
121
+ ? 'Saving...'
122
+ : 'Save Changes'
123
+ : submitting
124
+ ? 'Saving...'
125
+ : 'Add Driver'}
92
126
  </Button>
93
127
  </div>
94
128
  }
@@ -180,6 +214,7 @@ function NewDriverForm() {
180
214
 
181
215
  export default function DriverDetailsPage() {
182
216
  const { driverId } = useParams<{ driverId: string }>();
217
+ const isEditRoute = useLocation().pathname.endsWith('/edit');
183
218
  const { basePath, navigate } = useMoveTheWheels();
184
219
  const { driver, isLoading, error, refetch } = useDriver(driverId);
185
220
 
@@ -187,6 +222,7 @@ export default function DriverDetailsPage() {
187
222
  const goTo = (path: string) => navigate?.(joinPath(basePath, path));
188
223
 
189
224
  if (!driverId) return <NewDriverForm />;
225
+ if (isEditRoute) return <NewDriverForm editId={driverId} />;
190
226
  if (isLoading) return <LoadingState message="Loading driver details..." />;
191
227
  if (error || !driver) return <ErrorState title="Driver not found" onRetry={refetch} />;
192
228
 
@@ -200,7 +236,9 @@ export default function DriverDetailsPage() {
200
236
  <Button variant="outline" onClick={goBack}>
201
237
  <ArrowLeft size={16} /> Back
202
238
  </Button>
203
- <Button>
239
+ <Button
240
+ onClick={() => navigate?.(joinPath(basePath, 'fleet/drivers/' + driverId + '/edit'))}
241
+ >
204
242
  <Edit size={16} /> Edit
205
243
  </Button>
206
244
  </div>
@@ -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>