@burdenoff/microfe-movethewheels 2026.626.2 → 2026.703.2
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.
- package/dist/{AIAssistantPage-AvsMbunM.js → AIAssistantPage-C3cPsWt-.js} +100 -73
- package/dist/AnalyticsPage-Dz6g2MvK.js +215 -0
- package/dist/{CreateOrderPage-CjlnEtk3.js → CreateOrderPage-C5iwnjp4.js} +36 -67
- package/dist/CustomerDetailsPage-B6nwFyLO.js +406 -0
- package/dist/{CustomersPage-CGGfD4ki.js → CustomersPage-2Y05LVyh.js} +26 -28
- package/dist/{DashboardPage-D2HRqwg9.js → DashboardPage-BVjQi0EB.js} +85 -83
- package/dist/{DataTable-CD9ZhpLB.js → DataTable-DmciIKKM.js} +3 -3
- package/dist/DriverDetailsPage-CN3WQ0B9.js +507 -0
- package/dist/{DriversPage-DKiynISH.js → DriversPage-B0Zpx8OQ.js} +25 -27
- package/dist/{FinancePage-Vg8WPs38.js → FinancePage-DEDykSOP.js} +9 -9
- package/dist/FleetPage-DqkFi3s3.js +365 -0
- package/dist/FormError-BlOjnXOq.js +15 -0
- package/dist/ImportExportPage-DKJRCezl.js +290 -0
- package/dist/InventoryPage-DwfgCUQq.js +253 -0
- package/dist/{LiveTrackingPage-DQ1q4Id3.js → LiveTrackingPage-DCX-K-LR.js} +60 -56
- package/dist/MarketplacePage-CIQ97_zp.js +247 -0
- package/dist/{MetricCard-B7yMyvMX.js → MetricCard-BY0QrMD4.js} +1 -1
- package/dist/{OrderDetailsPage-CFASgsYX.js → OrderDetailsPage-DYiZ-9UK.js} +83 -85
- package/dist/{OrdersListPage-BHeT8S5o.js → OrdersListPage-BjDlCKJd.js} +31 -33
- package/dist/{PageLayout-D3N8MCTm.js → PageLayout-D4LDHJJq.js} +483 -422
- package/dist/ProductDetailsPage-C9uOiYLp.js +350 -0
- package/dist/{ProductsPage--xEpbQ8A.js → ProductsPage-C1G9h7Xd.js} +22 -24
- package/dist/ReportsPage-Zp53q3_8.js +284 -0
- package/dist/RouteDetailsPage-DUStXSf9.js +531 -0
- package/dist/RoutesPage-DMfoicM1.js +144 -0
- package/dist/SettingsPage-Bn-v0mbQ.js +421 -0
- package/dist/StatusBadge-BZurYHMy.js +152 -0
- package/dist/{TrackingPage-CcRc_dva.js → TrackingPage-W-jtdM3Y.js} +59 -61
- package/dist/VehicleDetailsPage-Bceo5oz3.js +410 -0
- package/dist/{VehiclesPage-UlxPWP9K.js → VehiclesPage-VBtOep91.js} +20 -22
- package/dist/WarehouseDetailsPage-L_6sWigM.js +493 -0
- package/dist/{WarehousesPage-CuTO-OPp.js → WarehousesPage-DCbXXXWE.js} +16 -18
- package/dist/{arrow-left-CBpZKkpq.js → arrow-left-n48Y-Wis.js} +1 -1
- package/dist/{box-CAaiElsb.js → box-D9Pal4HM.js} +1 -1
- package/dist/{chart-column-D1Ym15-S.js → chart-column-BFIJysDR.js} +1 -1
- package/dist/{chevron-right-BIFGi0UA.js → chevron-right-EMIIyyJ1.js} +1 -1
- package/dist/{circle-alert-DjmNo5_L.js → circle-alert-iY24lSlp.js} +1 -1
- package/dist/{circle-check-big-CjuPVo3_.js → circle-check-big-w0TrLKMF.js} +1 -1
- package/dist/{clock-BjkdnddD.js → clock-DJZtdl8u.js} +1 -1
- package/dist/{dollar-sign-IB4O0wuQ.js → dollar-sign-CgKQRz_e.js} +1 -1
- package/dist/{download-Dpa9vqoG.js → download-DRXxoiQR.js} +1 -1
- package/dist/{file-text-C8Uioy11.js → file-text-DWWY6_f5.js} +1 -1
- package/dist/{filter-Da5OZaHI.js → filter-DloLC2eE.js} +1 -1
- package/dist/index.js +186 -162
- package/dist/{map-_Per4sT9.js → map-CkJaULb2.js} +1 -1
- package/dist/{map-pin-BSaf5IDL.js → map-pin-BGiCvyCs.js} +1 -1
- package/dist/{navigation-DaTbrhw2.js → navigation-D36prnAn.js} +1 -1
- package/dist/{package-BE7U0wQE.js → package-BChUzpJS.js} +1 -1
- package/dist/{phone-DV4StZiA.js → phone-Ac9s3mo_.js} +1 -1
- package/dist/{plus-Dl1rLeJR.js → plus-Z76q-FZm.js} +1 -1
- package/dist/{refresh-cw-CWfERAit.js → refresh-cw-DKt6f5T6.js} +1 -1
- package/dist/{save-XH8vydOn.js → save-CojywNhG.js} +1 -1
- package/dist/{search-OxoIiL_C.js → search-CDGefqb3.js} +1 -1
- package/dist/{square-pen-hLIbFQFV.js → square-pen-CopE9GmB.js} +1 -1
- package/dist/{star-YsVV3MNQ.js → star-nHlLjDKR.js} +1 -1
- package/dist/{store-RCBl7HB-.js → store-BR9Voi5E.js} +1 -1
- package/dist/trash-2-BKhar6Hs.js +32 -0
- package/dist/{trending-up-C-85AwGT.js → trending-up-Dohgd4t_.js} +1 -1
- package/dist/{triangle-alert-BOhfGgmh.js → triangle-alert-i-XQL9Rd.js} +1 -1
- package/dist/{truck-CtDjNFbi.js → truck-C0v_hbLR.js} +1 -1
- package/dist/{useAnalytics-ph7eTIK6.js → useAnalytics-DS8n0xgg.js} +40 -40
- package/dist/useAssistant-D4v3oxD3.js +84 -0
- package/dist/{useCustomers-bS3a4ytk.js → useCustomers-BmTNGxiI.js} +24 -24
- package/dist/{useFleet-BdETplNE.js → useFleet-DKqXZ16m.js} +133 -109
- package/dist/useImportExport-DHr-SWSw.js +55 -0
- package/dist/{useInventory-Dwn18FPz.js → useInventory-DkyiVY_A.js} +95 -75
- package/dist/{useOrders-D_3_hGMp.js → useOrders-Cfk1GLiq.js} +42 -42
- package/dist/useReports-CTqZr-AB.js +78 -0
- package/dist/{useRoutes-v4aBaS-E.js → useRoutes-BQWO607N.js} +31 -31
- package/dist/useSettings-CkDH_-gM.js +81 -0
- package/dist/{useTracking-De2KIUNu.js → useTracking-B-va1kO3.js} +22 -22
- package/dist/{user-Dz5TJZFs.js → user-jyFU3lXk.js} +1 -1
- package/dist/{users-g2N0eL-Z.js → users-DZ-ilS6B.js} +1 -1
- package/dist/{warehouse-D3WWDeTX.js → warehouse-6m5kXMLu.js} +1 -1
- package/dist/{wrench-CmxY1n4I.js → wrench-CANXWxF-.js} +1 -1
- package/package.json +1 -1
- package/src/movethewheels/MoveTheWheelsRoutes.tsx +31 -3
- package/src/movethewheels/components/FormError.tsx +25 -0
- package/src/movethewheels/components/StatusBadge.tsx +18 -37
- package/src/movethewheels/constants/index.ts +67 -26
- package/src/movethewheels/hooks/index.ts +12 -0
- package/src/movethewheels/hooks/useAnalytics.ts +20 -6
- package/src/movethewheels/hooks/useAssistant.ts +74 -0
- package/src/movethewheels/hooks/useFleet.ts +170 -59
- package/src/movethewheels/hooks/useImportExport.ts +101 -0
- package/src/movethewheels/hooks/useInventory.ts +107 -0
- package/src/movethewheels/hooks/useReports.ts +158 -0
- package/src/movethewheels/hooks/useSettings.ts +120 -0
- package/src/movethewheels/pages/AIAssistantPage.tsx +53 -15
- package/src/movethewheels/pages/AnalyticsPage.tsx +67 -64
- package/src/movethewheels/pages/CustomerDetailsPage.tsx +154 -21
- package/src/movethewheels/pages/CustomersPage.tsx +1 -1
- package/src/movethewheels/pages/DashboardPage.tsx +42 -22
- package/src/movethewheels/pages/DriverDetailsPage.tsx +160 -3
- package/src/movethewheels/pages/DriversPage.tsx +6 -2
- package/src/movethewheels/pages/FleetPage.tsx +96 -38
- package/src/movethewheels/pages/ImportExportPage.tsx +174 -68
- package/src/movethewheels/pages/InventoryPage.tsx +66 -28
- package/src/movethewheels/pages/LiveTrackingPage.tsx +12 -3
- package/src/movethewheels/pages/MarketplacePage.tsx +87 -45
- package/src/movethewheels/pages/OrderDetailsPage.tsx +2 -2
- package/src/movethewheels/pages/ProductDetailsPage.tsx +146 -3
- package/src/movethewheels/pages/ProductsPage.tsx +5 -1
- package/src/movethewheels/pages/ReportsPage.tsx +194 -90
- package/src/movethewheels/pages/RouteDetailsPage.tsx +290 -36
- package/src/movethewheels/pages/RoutesPage.tsx +4 -9
- package/src/movethewheels/pages/SettingsPage.tsx +234 -56
- package/src/movethewheels/pages/TrackingPage.tsx +6 -6
- package/src/movethewheels/pages/VehicleDetailsPage.tsx +158 -2
- package/src/movethewheels/pages/VehiclesPage.tsx +5 -1
- package/src/movethewheels/pages/WarehouseDetailsPage.tsx +224 -2
- package/src/movethewheels/pages/WarehousesPage.tsx +5 -1
- package/src/movethewheels/providers/MoveTheWheelsProvider.tsx +7 -6
- package/src/movethewheels/store/movethewheelsStore.ts +1 -1
- package/dist/AnalyticsPage-CCLfHgQZ.js +0 -212
- package/dist/CustomerDetailsPage-I6cdDHUi.js +0 -265
- package/dist/DriverDetailsPage-rg_pGNuP.js +0 -330
- package/dist/FleetPage-DddXt8r8.js +0 -332
- package/dist/ImportExportPage-BW6qO3kc.js +0 -248
- package/dist/InventoryPage-rMGuaJN-.js +0 -222
- package/dist/MarketplacePage-DKrXAv62.js +0 -219
- package/dist/ProductDetailsPage-CFGcbaHo.js +0 -195
- package/dist/ReportsPage-Bb7_90rK.js +0 -235
- package/dist/RouteDetailsPage-CVtDVb1N.js +0 -282
- package/dist/RoutesPage-s57PtgcQ.js +0 -163
- package/dist/SettingsPage-BYNCBOpN.js +0 -298
- package/dist/StatusBadge-CijYQXtb.js +0 -209
- package/dist/VehicleDetailsPage-3YDMPsPs.js +0 -220
- package/dist/WarehouseDetailsPage-Db7KeN-d.js +0 -238
- package/dist/graphqlClient-CdJyR_ed.js +0 -55
- package/dist/navigation-BgnOfsVd.js +0 -6
- package/dist/route-Dp9JiN_E.js +0 -51
- package/dist/settings-3Rw5NwFF.js +0 -28
- package/src/movethewheels/constants/mockData.ts +0 -1342
- /package/dist/{formatters-_vJlC-47.js → formatters-CF2S2l1m.js} +0 -0
|
@@ -2,16 +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 {
|
|
7
|
-
|
|
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 {
|
|
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';
|
|
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
|
+
}
|
|
15
297
|
|
|
16
298
|
export default function RouteDetailsPage() {
|
|
17
299
|
const { routeId } = useParams<{ routeId: string }>();
|
|
@@ -24,28 +306,7 @@ export default function RouteDetailsPage() {
|
|
|
24
306
|
|
|
25
307
|
// Handle new route form
|
|
26
308
|
if (!routeId) {
|
|
27
|
-
return
|
|
28
|
-
<PageLayout
|
|
29
|
-
title="Create Route"
|
|
30
|
-
subtitle="Plan a new delivery route"
|
|
31
|
-
icon={<Navigation size={24} />}
|
|
32
|
-
headerActions={
|
|
33
|
-
<Button variant="outline" onClick={goBack}>
|
|
34
|
-
<ArrowLeft size={16} /> Back
|
|
35
|
-
</Button>
|
|
36
|
-
}
|
|
37
|
-
>
|
|
38
|
-
<PagePurpose icon={<Navigation size={18} />}>
|
|
39
|
-
Plan a new delivery route — pick the stops to visit, and the optimizer will sequence them
|
|
40
|
-
into the shortest driving plan so one driver can cover them in a single run.
|
|
41
|
-
</PagePurpose>
|
|
42
|
-
<EmptyState
|
|
43
|
-
illustration="onboarding"
|
|
44
|
-
title="Build your route"
|
|
45
|
-
description="The route planner lets you add stops, set the running order, and optimize for distance and time."
|
|
46
|
-
/>
|
|
47
|
-
</PageLayout>
|
|
48
|
-
);
|
|
309
|
+
return <NewRouteForm />;
|
|
49
310
|
}
|
|
50
311
|
|
|
51
312
|
if (isLoading) return <LoadingState message="Loading route details..." />;
|
|
@@ -60,13 +321,6 @@ export default function RouteDetailsPage() {
|
|
|
60
321
|
}
|
|
61
322
|
};
|
|
62
323
|
|
|
63
|
-
const statusColors = {
|
|
64
|
-
planned: { bg: 'bg-blue-100', text: 'text-blue-700' },
|
|
65
|
-
in_progress: { bg: 'bg-yellow-100', text: 'text-yellow-700' },
|
|
66
|
-
completed: { bg: 'bg-green-100', text: 'text-green-700' },
|
|
67
|
-
cancelled: { bg: 'bg-red-100', text: 'text-red-700' },
|
|
68
|
-
};
|
|
69
|
-
|
|
70
324
|
return (
|
|
71
325
|
<PageLayout
|
|
72
326
|
title={route.name}
|
|
@@ -89,7 +343,7 @@ export default function RouteDetailsPage() {
|
|
|
89
343
|
}
|
|
90
344
|
headerContent={
|
|
91
345
|
<div className="flex gap-2">
|
|
92
|
-
<StatusBadge status={route.status} customColors={
|
|
346
|
+
<StatusBadge status={route.status} customColors={ROUTE_STATUS_COLORS[route.status]} />
|
|
93
347
|
<Badge variant={route.optimized ? 'default' : 'secondary'}>
|
|
94
348
|
{route.optimized ? 'Optimized' : 'Not Optimized'}
|
|
95
349
|
</Badge>
|
|
@@ -159,7 +413,7 @@ export default function RouteDetailsPage() {
|
|
|
159
413
|
<div key={stop.id} className="flex gap-4">
|
|
160
414
|
<div className="relative">
|
|
161
415
|
<div
|
|
162
|
-
className={`w-8 h-8 rounded-full flex items-center justify-center ${stop.status === 'completed' ? 'bg-
|
|
416
|
+
className={`w-8 h-8 rounded-full flex items-center justify-center ${stop.status === 'completed' ? 'bg-status-success-text text-text-inverse' : 'bg-bg-sunken'}`}
|
|
163
417
|
>
|
|
164
418
|
{stop.status === 'completed' ? <CheckCircle size={16} /> : stop.sequence}
|
|
165
419
|
</div>
|
|
@@ -188,7 +442,7 @@ export default function RouteDetailsPage() {
|
|
|
188
442
|
Scheduled: {formatDate(stop.scheduledTime, 'time')}
|
|
189
443
|
</p>
|
|
190
444
|
{stop.actualTime && (
|
|
191
|
-
<p className="text-sm text-
|
|
445
|
+
<p className="text-sm text-status-success-text">
|
|
192
446
|
Actual: {formatDate(stop.actualTime, 'time')}
|
|
193
447
|
</p>
|
|
194
448
|
)}
|
|
@@ -267,7 +521,7 @@ export default function RouteDetailsPage() {
|
|
|
267
521
|
{route.trafficConditions.map((tc, i) => (
|
|
268
522
|
<div
|
|
269
523
|
key={i}
|
|
270
|
-
className={`p-2 rounded mb-2 ${tc.condition === 'severe' ? 'bg-
|
|
524
|
+
className={`p-2 rounded mb-2 ${tc.condition === 'severe' ? 'bg-status-error-bg-subtle' : tc.condition === 'heavy' ? 'bg-status-warning-bg-subtle' : 'bg-status-warning-bg-subtle'}`}
|
|
271
525
|
>
|
|
272
526
|
<p className="text-sm font-medium capitalize">{tc.condition} traffic</p>
|
|
273
527
|
<p className="text-xs text-text-secondary">
|
|
@@ -12,6 +12,7 @@ import { useRoutes } from '../hooks/useRoutes';
|
|
|
12
12
|
import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
|
|
13
13
|
import { formatDate } from '../utils/formatters';
|
|
14
14
|
import { joinPath } from '../utils/navigation';
|
|
15
|
+
import { ROUTE_STATUS_COLORS } from '../constants';
|
|
15
16
|
import type { Route } from '../types';
|
|
16
17
|
|
|
17
18
|
export default function RoutesPage() {
|
|
@@ -33,15 +34,9 @@ export default function RoutesPage() {
|
|
|
33
34
|
header: 'Status',
|
|
34
35
|
accessorKey: 'status',
|
|
35
36
|
sortable: true,
|
|
36
|
-
cell: (row) =>
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
in_progress: { bg: 'bg-yellow-100', text: 'text-yellow-700' },
|
|
40
|
-
completed: { bg: 'bg-green-100', text: 'text-green-700' },
|
|
41
|
-
cancelled: { bg: 'bg-red-100', text: 'text-red-700' },
|
|
42
|
-
};
|
|
43
|
-
return <StatusBadge status={row.status} customColors={colors[row.status]} />;
|
|
44
|
-
},
|
|
37
|
+
cell: (row) => (
|
|
38
|
+
<StatusBadge status={row.status} customColors={ROUTE_STATUS_COLORS[row.status]} />
|
|
39
|
+
),
|
|
45
40
|
},
|
|
46
41
|
{
|
|
47
42
|
id: 'stops',
|