@burdenoff/microfe-movethewheels 2026.703.1 → 2026.703.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 (72) hide show
  1. package/dist/{AIAssistantPage-C3ovZ0Fh.js → AIAssistantPage-C3cPsWt-.js} +1 -1
  2. package/dist/{AnalyticsPage-O8SpY-V4.js → AnalyticsPage-Dz6g2MvK.js} +6 -6
  3. package/dist/{CreateOrderPage-CB1_-_mN.js → CreateOrderPage-C5iwnjp4.js} +29 -58
  4. package/dist/CustomerDetailsPage-B6nwFyLO.js +406 -0
  5. package/dist/{CustomersPage-DRaLuHMC.js → CustomersPage-BKxMS1AV.js} +5 -5
  6. package/dist/{DashboardPage-DPjhNfKB.js → DashboardPage-BVjQi0EB.js} +11 -11
  7. package/dist/DriverDetailsPage-CN3WQ0B9.js +507 -0
  8. package/dist/{DriversPage-C2Bk0w4Q.js → DriversPage-CH9rjQC-.js} +6 -6
  9. package/dist/{FinancePage-lmp0Sdsw.js → FinancePage-DEDykSOP.js} +5 -5
  10. package/dist/{FleetPage-DZn5pIqd.js → FleetPage-DqkFi3s3.js} +5 -5
  11. package/dist/FormError-BlOjnXOq.js +15 -0
  12. package/dist/{ImportExportPage-BIUJZc7O.js → ImportExportPage-DKJRCezl.js} +1 -1
  13. package/dist/{InventoryPage-Cxh1zYJd.js → InventoryPage-e0ze63iX.js} +6 -6
  14. package/dist/{LiveTrackingPage-BY1yAkIt.js → LiveTrackingPage-DCX-K-LR.js} +5 -5
  15. package/dist/{MarketplacePage-RMuL17EA.js → MarketplacePage-CIQ97_zp.js} +1 -1
  16. package/dist/{OrderDetailsPage-C2Mf4KSy.js → OrderDetailsPage-DYiZ-9UK.js} +5 -5
  17. package/dist/{OrdersListPage-BrPBtnhp.js → OrdersListPage-Cn90P1Xx.js} +4 -4
  18. package/dist/ProductDetailsPage-C9uOiYLp.js +350 -0
  19. package/dist/{ProductsPage-I5DilIoY.js → ProductsPage-QJfHHpqB.js} +4 -4
  20. package/dist/{ReportsPage-BDXNmUng.js → ReportsPage-Zp53q3_8.js} +2 -2
  21. package/dist/RouteDetailsPage-DUStXSf9.js +531 -0
  22. package/dist/{RoutesPage-lqX9ylHP.js → RoutesPage-DMfoicM1.js} +3 -3
  23. package/dist/{SettingsPage-CQVIOaNs.js → SettingsPage-Bn-v0mbQ.js} +1 -1
  24. package/dist/{TrackingPage-BXrjz7V8.js → TrackingPage-W-jtdM3Y.js} +4 -4
  25. package/dist/VehicleDetailsPage-Bceo5oz3.js +410 -0
  26. package/dist/{VehiclesPage-6W39XT5W.js → VehiclesPage-ApKThYeG.js} +5 -5
  27. package/dist/WarehouseDetailsPage-L_6sWigM.js +493 -0
  28. package/dist/{WarehousesPage-CHvBqNaQ.js → WarehousesPage-DCbXXXWE.js} +3 -3
  29. package/dist/index.js +154 -138
  30. package/dist/trash-2-BKhar6Hs.js +32 -0
  31. package/dist/{useFleet-DiAC-IiH.js → useFleet-DKqXZ16m.js} +77 -56
  32. package/dist/{useInventory-DKoks37x.js → useInventory-DkyiVY_A.js} +73 -53
  33. package/dist/{useTracking-GJify4HN.js → useTracking-B-va1kO3.js} +2 -2
  34. package/package.json +1 -1
  35. package/src/movethewheels/MoveTheWheelsRoutes.tsx +4 -0
  36. package/src/movethewheels/components/FormError.tsx +25 -0
  37. package/src/movethewheels/hooks/useFleet.ts +107 -0
  38. package/src/movethewheels/hooks/useInventory.ts +107 -0
  39. package/src/movethewheels/pages/CustomerDetailsPage.tsx +153 -20
  40. package/src/movethewheels/pages/CustomersPage.tsx +1 -1
  41. package/src/movethewheels/pages/DriverDetailsPage.tsx +159 -2
  42. package/src/movethewheels/pages/DriversPage.tsx +6 -2
  43. package/src/movethewheels/pages/InventoryPage.tsx +1 -1
  44. package/src/movethewheels/pages/OrdersListPage.tsx +1 -1
  45. package/src/movethewheels/pages/ProductDetailsPage.tsx +146 -3
  46. package/src/movethewheels/pages/ProductsPage.tsx +6 -2
  47. package/src/movethewheels/pages/RouteDetailsPage.tsx +285 -25
  48. package/src/movethewheels/pages/VehicleDetailsPage.tsx +158 -2
  49. package/src/movethewheels/pages/VehiclesPage.tsx +6 -2
  50. package/src/movethewheels/pages/WarehouseDetailsPage.tsx +224 -2
  51. package/src/movethewheels/pages/WarehousesPage.tsx +5 -1
  52. package/dist/CustomerDetailsPage-BaeKEI1D.js +0 -263
  53. package/dist/DriverDetailsPage-CSSQTOzn.js +0 -328
  54. package/dist/ProductDetailsPage-CezzzZcj.js +0 -193
  55. package/dist/RouteDetailsPage-BcomL20M.js +0 -263
  56. package/dist/VehicleDetailsPage-DZjLcJgr.js +0 -218
  57. package/dist/WarehouseDetailsPage-C10pgRGW.js +0 -236
  58. /package/dist/{MetricCard-7aYgzZHK.js → MetricCard-BY0QrMD4.js} +0 -0
  59. /package/dist/{StatusBadge-NpTeHVlw.js → StatusBadge-BZurYHMy.js} +0 -0
  60. /package/dist/{formatters-DhKZ-eA1.js → formatters-CF2S2l1m.js} +0 -0
  61. /package/dist/{trending-up-B7W-GL6P.js → trending-up-Dohgd4t_.js} +0 -0
  62. /package/dist/{triangle-alert-BonHFFNz.js → triangle-alert-i-XQL9Rd.js} +0 -0
  63. /package/dist/{truck-DrlfSpRC.js → truck-C0v_hbLR.js} +0 -0
  64. /package/dist/{useAnalytics-CgdzUfY_.js → useAnalytics-DS8n0xgg.js} +0 -0
  65. /package/dist/{useCustomers-QrlozybF.js → useCustomers-BmTNGxiI.js} +0 -0
  66. /package/dist/{useImportExport-BLMnAKCC.js → useImportExport-DHr-SWSw.js} +0 -0
  67. /package/dist/{useOrders-D3UZVy8Y.js → useOrders-Cfk1GLiq.js} +0 -0
  68. /package/dist/{useReports-Puq4DCH9.js → useReports-CTqZr-AB.js} +0 -0
  69. /package/dist/{useRoutes-Do57LvRb.js → useRoutes-BQWO607N.js} +0 -0
  70. /package/dist/{user-CjSoudeu.js → user-jyFU3lXk.js} +0 -0
  71. /package/dist/{users-BAM4gHTn.js → users-DZ-ilS6B.js} +0 -0
  72. /package/dist/{warehouse-C7MSr47B.js → warehouse-6m5kXMLu.js} +0 -0
@@ -168,7 +168,7 @@ export default function OrdersListPage() {
168
168
  {
169
169
  label: t('movethewheels.orders.actions.export', 'Export'),
170
170
  icon: <Download size={16} />,
171
- onSelect: () => {},
171
+ onSelect: () => goTo('import-export'),
172
172
  },
173
173
  ]}
174
174
  />
@@ -2,15 +2,157 @@
2
2
  * Product Details Page
3
3
  */
4
4
 
5
+ import { useState } from 'react';
5
6
  import { useParams } from 'react-router-dom';
6
- import { Package, ArrowLeft, Edit, Box, DollarSign, Tag } from 'lucide-react';
7
+ import { Package, ArrowLeft, Edit, Box, DollarSign, Tag, Save } from 'lucide-react';
7
8
  import { PageLayout, LoadingState, ErrorState } from '../components/PageLayout';
8
- import { Card, CardContent, CardHeader, CardTitle, Button, Badge } from '../components/ui';
9
+ import {
10
+ Card,
11
+ CardContent,
12
+ CardHeader,
13
+ CardTitle,
14
+ Button,
15
+ Badge,
16
+ Input,
17
+ Label,
18
+ } from '../components/ui';
9
19
  import { EmphasisPanel, NextSteps, PagePurpose } from '@burdenoff/fe-libs/ui';
10
- import { useProduct } from '../hooks/useInventory';
20
+ import { useProduct, useInventory } from '../hooks/useInventory';
11
21
  import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
12
22
  import { formatCurrency } from '../utils/formatters';
13
23
  import { joinPath } from '../utils/navigation';
24
+ import { FormError } from '../components/FormError';
25
+
26
+ function NewProductForm() {
27
+ const { basePath, navigate } = useMoveTheWheels();
28
+ const { createProduct } = useInventory();
29
+ const [form, setForm] = useState({
30
+ name: '',
31
+ sku: '',
32
+ category: '',
33
+ brand: '',
34
+ weight: '',
35
+ });
36
+ const [submitting, setSubmitting] = useState(false);
37
+ const [error, setError] = useState<string | null>(null);
38
+
39
+ const goBack = () => navigate?.(joinPath(basePath, 'inventory/products'));
40
+
41
+ const handleSubmit = async (e: React.FormEvent) => {
42
+ e.preventDefault();
43
+ if (!form.name.trim() || !form.sku.trim()) {
44
+ setError('Product name and SKU are required.');
45
+ return;
46
+ }
47
+ setSubmitting(true);
48
+ setError(null);
49
+ try {
50
+ 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}`));
59
+ } catch (err) {
60
+ console.error('Failed to create product:', err);
61
+ setError(err instanceof Error ? err.message : 'Failed to create product. Please try again.');
62
+ setSubmitting(false);
63
+ }
64
+ };
65
+
66
+ return (
67
+ <PageLayout
68
+ title="Add Product"
69
+ subtitle="Add an item to your catalog"
70
+ icon={<Package size={24} />}
71
+ headerActions={
72
+ <div className="flex gap-2">
73
+ <Button variant="outline" onClick={goBack} disabled={submitting}>
74
+ <ArrowLeft size={16} /> Cancel
75
+ </Button>
76
+ <Button onClick={handleSubmit} disabled={submitting}>
77
+ <Save size={16} /> {submitting ? 'Saving...' : 'Add Product'}
78
+ </Button>
79
+ </div>
80
+ }
81
+ >
82
+ <PagePurpose icon={<Package size={18} />}>
83
+ Add a product so it can be added to orders and stocked in warehouses. A name and SKU are all
84
+ it takes to start — category, brand and weight help with picking, pricing and routing.
85
+ </PagePurpose>
86
+
87
+ <form onSubmit={handleSubmit} className="max-w-2xl space-y-6">
88
+ <FormError message={error} />
89
+ <EmphasisPanel>
90
+ <h2 className="text-base font-semibold text-text-primary mb-4">Product Details</h2>
91
+ <div className="space-y-4">
92
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
93
+ <div className="space-y-2">
94
+ <Label htmlFor="name">
95
+ Name <span className="text-status-error-text">*</span>
96
+ </Label>
97
+ <Input
98
+ id="name"
99
+ value={form.name}
100
+ onChange={(e) => setForm((prev) => ({ ...prev, name: e.target.value }))}
101
+ placeholder="Wireless Headphones"
102
+ required
103
+ />
104
+ </div>
105
+ <div className="space-y-2">
106
+ <Label htmlFor="sku">
107
+ SKU <span className="text-status-error-text">*</span>
108
+ </Label>
109
+ <Input
110
+ id="sku"
111
+ value={form.sku}
112
+ onChange={(e) => setForm((prev) => ({ ...prev, sku: e.target.value }))}
113
+ placeholder="SKU-001"
114
+ required
115
+ />
116
+ </div>
117
+ </div>
118
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
119
+ <div className="space-y-2">
120
+ <Label htmlFor="category">Category</Label>
121
+ <Input
122
+ id="category"
123
+ value={form.category}
124
+ onChange={(e) => setForm((prev) => ({ ...prev, category: e.target.value }))}
125
+ placeholder="Electronics"
126
+ />
127
+ </div>
128
+ <div className="space-y-2">
129
+ <Label htmlFor="brand">Brand</Label>
130
+ <Input
131
+ id="brand"
132
+ value={form.brand}
133
+ onChange={(e) => setForm((prev) => ({ ...prev, brand: e.target.value }))}
134
+ placeholder="Acme"
135
+ />
136
+ </div>
137
+ <div className="space-y-2">
138
+ <Label htmlFor="weight">Weight (kg)</Label>
139
+ <Input
140
+ id="weight"
141
+ type="number"
142
+ min="0"
143
+ step="0.01"
144
+ value={form.weight}
145
+ onChange={(e) => setForm((prev) => ({ ...prev, weight: e.target.value }))}
146
+ placeholder="0.5"
147
+ />
148
+ </div>
149
+ </div>
150
+ </div>
151
+ </EmphasisPanel>
152
+ </form>
153
+ </PageLayout>
154
+ );
155
+ }
14
156
 
15
157
  export default function ProductDetailsPage() {
16
158
  const { productId } = useParams<{ productId: string }>();
@@ -20,6 +162,7 @@ export default function ProductDetailsPage() {
20
162
  const goBack = () => navigate?.(joinPath(basePath, 'inventory/products'));
21
163
  const goTo = (path: string) => navigate?.(joinPath(basePath, path));
22
164
 
165
+ if (!productId) return <NewProductForm />;
23
166
  if (isLoading) return <LoadingState message="Loading product details..." />;
24
167
  if (error || !product) return <ErrorState title="Product not found" onRetry={refetch} />;
25
168
 
@@ -98,10 +98,14 @@ export default function ProductsPage() {
98
98
  icon={<Package size={24} />}
99
99
  headerActions={
100
100
  <CTAOverflowMenu
101
- primary={{ label: 'Add Product', icon: <Plus size={16} />, onSelect: () => {} }}
101
+ primary={{
102
+ label: 'Add Product',
103
+ icon: <Plus size={16} />,
104
+ onSelect: () => goTo('inventory/products/new'),
105
+ }}
102
106
  actions={[
103
107
  { label: 'Refresh', icon: <RefreshCw size={16} />, onSelect: () => refetch() },
104
- { label: 'Export', icon: <Download size={16} />, onSelect: () => {} },
108
+ { label: 'Export', icon: <Download size={16} />, onSelect: () => goTo('import-export') },
105
109
  ]}
106
110
  />
107
111
  }
@@ -2,17 +2,298 @@
2
2
  * Route Details Page
3
3
  */
4
4
 
5
+ import { useState } from 'react';
5
6
  import { useParams } from 'react-router-dom';
6
- import { Navigation, ArrowLeft, Edit, MapPin, Clock, Zap, CheckCircle } from 'lucide-react';
7
- import { PageLayout, LoadingState, ErrorState, EmptyState } from '../components/PageLayout';
7
+ import {
8
+ Navigation,
9
+ ArrowLeft,
10
+ Edit,
11
+ MapPin,
12
+ Clock,
13
+ Zap,
14
+ CheckCircle,
15
+ Plus,
16
+ Trash2,
17
+ Save,
18
+ } from 'lucide-react';
19
+ import { PageLayout, LoadingState, ErrorState } from '../components/PageLayout';
8
20
  import { StatusBadge } from '../components/StatusBadge';
9
- import { Card, CardContent, CardHeader, CardTitle, Button, Badge } from '../components/ui';
21
+ import {
22
+ Card,
23
+ CardContent,
24
+ CardHeader,
25
+ CardTitle,
26
+ Button,
27
+ Badge,
28
+ Input,
29
+ Label,
30
+ Select,
31
+ SelectContent,
32
+ SelectItem,
33
+ SelectTrigger,
34
+ SelectValue,
35
+ } from '../components/ui';
10
36
  import { EmphasisPanel, NextSteps, PagePurpose } from '@burdenoff/fe-libs/ui';
11
37
  import { useRoute, useRoutes } from '../hooks/useRoutes';
38
+ import { useFleet } from '../hooks/useFleet';
12
39
  import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
13
40
  import { formatDate, formatAddress } from '../utils/formatters';
14
41
  import { joinPath } from '../utils/navigation';
15
42
  import { ROUTE_STATUS_COLORS } from '../constants';
43
+ import { FormError } from '../components/FormError';
44
+ import type { Route as RouteType, RouteStop } from '../types';
45
+
46
+ const ROUTE_STATUS_OPTIONS: { value: RouteType['status']; label: string }[] = [
47
+ { value: 'planned', label: 'Planned' },
48
+ { value: 'in_progress', label: 'In Progress' },
49
+ { value: 'completed', label: 'Completed' },
50
+ { value: 'cancelled', label: 'Cancelled' },
51
+ ];
52
+
53
+ function NewRouteForm() {
54
+ const { basePath, navigate } = useMoveTheWheels();
55
+ const { createRoute } = useRoutes();
56
+ const { vehicles, drivers } = useFleet();
57
+ const [form, setForm] = useState<{
58
+ name: string;
59
+ status: RouteType['status'];
60
+ vehicleId: string;
61
+ driverId: string;
62
+ stops: { address: string; type: 'pickup' | 'delivery' }[];
63
+ }>({
64
+ name: '',
65
+ status: 'planned',
66
+ vehicleId: '',
67
+ driverId: '',
68
+ stops: [{ address: '', type: 'delivery' }],
69
+ });
70
+ const [submitting, setSubmitting] = useState(false);
71
+ const [error, setError] = useState<string | null>(null);
72
+
73
+ const goBack = () => navigate?.(joinPath(basePath, 'routes'));
74
+
75
+ const addStop = () =>
76
+ setForm((prev) => ({
77
+ ...prev,
78
+ stops: [...prev.stops, { address: '', type: 'delivery' }],
79
+ }));
80
+
81
+ const removeStop = (index: number) =>
82
+ setForm((prev) => ({
83
+ ...prev,
84
+ stops: prev.stops.filter((_, i) => i !== index),
85
+ }));
86
+
87
+ const updateStop = (index: number, field: 'address' | 'type', value: string) =>
88
+ setForm((prev) => ({
89
+ ...prev,
90
+ stops: prev.stops.map((stop, i) => (i === index ? { ...stop, [field]: value } : stop)),
91
+ }));
92
+
93
+ const handleSubmit = async (e: React.FormEvent) => {
94
+ e.preventDefault();
95
+ if (!form.name.trim()) {
96
+ setError('Route name is required.');
97
+ return;
98
+ }
99
+ const filledStops = form.stops.filter((stop) => stop.address.trim());
100
+ if (filledStops.length === 0) {
101
+ setError('Add at least one stop with an address.');
102
+ return;
103
+ }
104
+ setSubmitting(true);
105
+ setError(null);
106
+ try {
107
+ const stops: RouteStop[] = filledStops.map((stop, index) => ({
108
+ id: `stop-${index + 1}`,
109
+ orderId: '',
110
+ sequence: index + 1,
111
+ address: {
112
+ street: stop.address.trim(),
113
+ city: '',
114
+ state: '',
115
+ postalCode: '',
116
+ country: '',
117
+ },
118
+ type: stop.type,
119
+ scheduledTime: new Date(),
120
+ duration: 0,
121
+ status: 'pending',
122
+ }));
123
+ const created = await createRoute({
124
+ name: form.name.trim(),
125
+ status: form.status,
126
+ vehicleId: form.vehicleId || undefined,
127
+ driverId: form.driverId || undefined,
128
+ stops,
129
+ });
130
+ navigate?.(joinPath(basePath, `routes/${created.id}`));
131
+ } catch (err) {
132
+ console.error('Failed to create route:', err);
133
+ setError(err instanceof Error ? err.message : 'Failed to create route. Please try again.');
134
+ setSubmitting(false);
135
+ }
136
+ };
137
+
138
+ return (
139
+ <PageLayout
140
+ title="Create Route"
141
+ subtitle="Plan a new delivery route"
142
+ icon={<Navigation size={24} />}
143
+ headerActions={
144
+ <div className="flex gap-2">
145
+ <Button variant="outline" onClick={goBack} disabled={submitting}>
146
+ <ArrowLeft size={16} /> Cancel
147
+ </Button>
148
+ <Button onClick={handleSubmit} disabled={submitting}>
149
+ <Save size={16} /> {submitting ? 'Saving...' : 'Create Route'}
150
+ </Button>
151
+ </div>
152
+ }
153
+ >
154
+ <PagePurpose icon={<Navigation size={18} />}>
155
+ Plan a new delivery route — name it, list the stops to visit, and optionally assign a vehicle
156
+ and driver. Once created you can optimize it to sequence the stops into the shortest run.
157
+ </PagePurpose>
158
+
159
+ <form onSubmit={handleSubmit} className="max-w-3xl space-y-6">
160
+ <FormError message={error} />
161
+
162
+ <EmphasisPanel>
163
+ <h2 className="text-base font-semibold text-text-primary mb-4">Route Details</h2>
164
+ <div className="space-y-4">
165
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
166
+ <div className="space-y-2">
167
+ <Label htmlFor="name">
168
+ Route Name <span className="text-status-error-text">*</span>
169
+ </Label>
170
+ <Input
171
+ id="name"
172
+ value={form.name}
173
+ onChange={(e) => setForm((prev) => ({ ...prev, name: e.target.value }))}
174
+ placeholder="Downtown morning run"
175
+ required
176
+ />
177
+ </div>
178
+ <div className="space-y-2">
179
+ <Label htmlFor="status">Status</Label>
180
+ <Select
181
+ value={form.status}
182
+ onValueChange={(value) =>
183
+ setForm((prev) => ({ ...prev, status: value as RouteType['status'] }))
184
+ }
185
+ >
186
+ <SelectTrigger id="status">
187
+ <SelectValue />
188
+ </SelectTrigger>
189
+ <SelectContent>
190
+ {ROUTE_STATUS_OPTIONS.map((option) => (
191
+ <SelectItem key={option.value} value={option.value}>
192
+ {option.label}
193
+ </SelectItem>
194
+ ))}
195
+ </SelectContent>
196
+ </Select>
197
+ </div>
198
+ </div>
199
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
200
+ <div className="space-y-2">
201
+ <Label htmlFor="vehicle">Vehicle (optional)</Label>
202
+ <Select
203
+ value={form.vehicleId}
204
+ onValueChange={(value) => setForm((prev) => ({ ...prev, vehicleId: value }))}
205
+ >
206
+ <SelectTrigger id="vehicle">
207
+ <SelectValue placeholder="Assign later" />
208
+ </SelectTrigger>
209
+ <SelectContent>
210
+ {vehicles.map((vehicle) => (
211
+ <SelectItem key={vehicle.id} value={vehicle.id}>
212
+ {vehicle.vehicleNumber} ({vehicle.type})
213
+ </SelectItem>
214
+ ))}
215
+ </SelectContent>
216
+ </Select>
217
+ </div>
218
+ <div className="space-y-2">
219
+ <Label htmlFor="driver">Driver (optional)</Label>
220
+ <Select
221
+ value={form.driverId}
222
+ onValueChange={(value) => setForm((prev) => ({ ...prev, driverId: value }))}
223
+ >
224
+ <SelectTrigger id="driver">
225
+ <SelectValue placeholder="Assign later" />
226
+ </SelectTrigger>
227
+ <SelectContent>
228
+ {drivers.map((driver) => (
229
+ <SelectItem key={driver.id} value={driver.id}>
230
+ {driver.fullName} ({driver.employeeId})
231
+ </SelectItem>
232
+ ))}
233
+ </SelectContent>
234
+ </Select>
235
+ </div>
236
+ </div>
237
+ </div>
238
+ </EmphasisPanel>
239
+
240
+ <Card>
241
+ <CardHeader className="flex flex-row items-center justify-between">
242
+ <CardTitle>
243
+ Stops <span className="text-status-error-text">*</span>
244
+ </CardTitle>
245
+ <Button type="button" variant="outline" size="sm" onClick={addStop}>
246
+ <Plus size={16} /> Add Stop
247
+ </Button>
248
+ </CardHeader>
249
+ <CardContent className="space-y-4">
250
+ {form.stops.map((stop, index) => (
251
+ <div key={index} className="grid grid-cols-1 md:grid-cols-12 gap-3 items-end">
252
+ <div className="md:col-span-7 space-y-2">
253
+ <Label htmlFor={`stop-address-${index}`}>Stop {index + 1} address</Label>
254
+ <Input
255
+ id={`stop-address-${index}`}
256
+ value={stop.address}
257
+ onChange={(e) => updateStop(index, 'address', e.target.value)}
258
+ placeholder="123 Main St, San Francisco, CA"
259
+ />
260
+ </div>
261
+ <div className="md:col-span-4 space-y-2">
262
+ <Label htmlFor={`stop-type-${index}`}>Type</Label>
263
+ <Select
264
+ value={stop.type}
265
+ onValueChange={(value) => updateStop(index, 'type', value)}
266
+ >
267
+ <SelectTrigger id={`stop-type-${index}`}>
268
+ <SelectValue />
269
+ </SelectTrigger>
270
+ <SelectContent>
271
+ <SelectItem value="pickup">Pickup</SelectItem>
272
+ <SelectItem value="delivery">Delivery</SelectItem>
273
+ </SelectContent>
274
+ </Select>
275
+ </div>
276
+ <div className="md:col-span-1 flex justify-end">
277
+ <Button
278
+ type="button"
279
+ variant="ghost"
280
+ size="icon"
281
+ className="text-status-error-text"
282
+ onClick={() => removeStop(index)}
283
+ disabled={form.stops.length === 1}
284
+ aria-label={`Remove stop ${index + 1}`}
285
+ >
286
+ <Trash2 size={16} />
287
+ </Button>
288
+ </div>
289
+ </div>
290
+ ))}
291
+ </CardContent>
292
+ </Card>
293
+ </form>
294
+ </PageLayout>
295
+ );
296
+ }
16
297
 
17
298
  export default function RouteDetailsPage() {
18
299
  const { routeId } = useParams<{ routeId: string }>();
@@ -25,28 +306,7 @@ export default function RouteDetailsPage() {
25
306
 
26
307
  // Handle new route form
27
308
  if (!routeId) {
28
- return (
29
- <PageLayout
30
- title="Create Route"
31
- subtitle="Plan a new delivery route"
32
- icon={<Navigation size={24} />}
33
- headerActions={
34
- <Button variant="outline" onClick={goBack}>
35
- <ArrowLeft size={16} /> Back
36
- </Button>
37
- }
38
- >
39
- <PagePurpose icon={<Navigation size={18} />}>
40
- Plan a new delivery route — pick the stops to visit, and the optimizer will sequence them
41
- into the shortest driving plan so one driver can cover them in a single run.
42
- </PagePurpose>
43
- <EmptyState
44
- illustration="onboarding"
45
- title="Build your route"
46
- description="The route planner lets you add stops, set the running order, and optimize for distance and time."
47
- />
48
- </PageLayout>
49
- );
309
+ return <NewRouteForm />;
50
310
  }
51
311
 
52
312
  if (isLoading) return <LoadingState message="Loading route details..." />;