@burdenoff/microfe-movethewheels 2026.624.1 → 2026.626.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.
- package/dist/{AIAssistantPage-BrHX2lQn.js → AIAssistantPage-AvsMbunM.js} +39 -33
- package/dist/{AnalyticsPage-DI6Wcaw5.js → AnalyticsPage-CCLfHgQZ.js} +44 -33
- package/dist/CreateOrderPage-CjlnEtk3.js +510 -0
- package/dist/CustomerDetailsPage-I6cdDHUi.js +265 -0
- package/dist/CustomersPage-CGGfD4ki.js +163 -0
- package/dist/{DashboardPage-3ZOnwg1a.js → DashboardPage-D2HRqwg9.js} +122 -111
- package/dist/DataTable-CD9ZhpLB.js +249 -0
- package/dist/DriverDetailsPage-rg_pGNuP.js +330 -0
- package/dist/DriversPage-DKiynISH.js +167 -0
- package/dist/FinancePage-Vg8WPs38.js +158 -0
- package/dist/{FleetPage-CPS7r4J3.js → FleetPage-DddXt8r8.js} +124 -85
- package/dist/ImportExportPage-BW6qO3kc.js +248 -0
- package/dist/InventoryPage-rMGuaJN-.js +222 -0
- package/dist/LiveTrackingPage-DQ1q4Id3.js +375 -0
- package/dist/MarketplacePage-DKrXAv62.js +219 -0
- package/dist/MetricCard-B7yMyvMX.js +146 -0
- package/dist/OrderDetailsPage-CFASgsYX.js +431 -0
- package/dist/OrdersListPage-BHeT8S5o.js +309 -0
- package/dist/{PageLayout-xDTiBTDS.js → PageLayout-D3N8MCTm.js} +213 -195
- package/dist/ProductDetailsPage-CFGcbaHo.js +195 -0
- package/dist/ProductsPage--xEpbQ8A.js +171 -0
- package/dist/{ReportsPage-B8OyWGzl.js → ReportsPage-Bb7_90rK.js} +57 -49
- package/dist/RouteDetailsPage-CVtDVb1N.js +282 -0
- package/dist/RoutesPage-s57PtgcQ.js +163 -0
- package/dist/SettingsPage-BYNCBOpN.js +298 -0
- package/dist/{StatusBadge-C--7NhoW.js → StatusBadge-CijYQXtb.js} +5 -2
- package/dist/{TrackingPage-BldUm_g7.js → TrackingPage-CcRc_dva.js} +88 -83
- package/dist/VehicleDetailsPage-3YDMPsPs.js +220 -0
- package/dist/VehiclesPage-UlxPWP9K.js +167 -0
- package/dist/WarehouseDetailsPage-Db7KeN-d.js +238 -0
- package/dist/WarehousesPage-CuTO-OPp.js +164 -0
- package/dist/{arrow-left-Bk5GsGNk.js → arrow-left-CBpZKkpq.js} +1 -1
- package/dist/{box-BAMv2-7M.js → box-CAaiElsb.js} +1 -1
- package/dist/{chart-column-CAfkf4o6.js → chart-column-D1Ym15-S.js} +1 -1
- package/dist/{chevron-right-Czrt_Qew.js → chevron-right-BIFGi0UA.js} +1 -1
- package/dist/{circle-alert-CEuV9Pij.js → circle-alert-DjmNo5_L.js} +1 -1
- package/dist/{circle-check-big-Cx8aO6B-.js → circle-check-big-CjuPVo3_.js} +1 -1
- package/dist/{clock-Be95bq7_.js → clock-BjkdnddD.js} +1 -1
- package/dist/{dollar-sign-kJqItPli.js → dollar-sign-IB4O0wuQ.js} +1 -1
- package/dist/{download-caab0D3V.js → download-Dpa9vqoG.js} +1 -1
- package/dist/{file-text-b1qhrJn1.js → file-text-C8Uioy11.js} +1 -1
- package/dist/{filter-BWmLTZB-.js → filter-Da5OZaHI.js} +1 -1
- package/dist/index.js +16 -16
- package/dist/{map-CS2aFgyO.js → map-_Per4sT9.js} +1 -1
- package/dist/{map-pin-96FeqL7H.js → map-pin-BSaf5IDL.js} +1 -1
- package/dist/{navigation-i6UXEjTC.js → navigation-DaTbrhw2.js} +1 -1
- package/dist/{package-KhSi3xaT.js → package-BE7U0wQE.js} +1 -1
- package/dist/{phone-B-h3tbsr.js → phone-DV4StZiA.js} +1 -1
- package/dist/{plus-D2BYAzMn.js → plus-Dl1rLeJR.js} +1 -1
- package/dist/{refresh-cw-D5Sy01s-.js → refresh-cw-CWfERAit.js} +1 -1
- package/dist/{route-CBDXuwH9.js → route-Dp9JiN_E.js} +1 -1
- package/dist/{save-CMc7h-Vi.js → save-XH8vydOn.js} +1 -1
- package/dist/{search-DVrUQbOg.js → search-OxoIiL_C.js} +1 -1
- package/dist/{settings-BwzIRYv3.js → settings-3Rw5NwFF.js} +1 -1
- package/dist/{square-pen-DmW36Bau.js → square-pen-hLIbFQFV.js} +1 -1
- package/dist/{star-Cv2MzxZL.js → star-YsVV3MNQ.js} +1 -1
- package/dist/{store-CLH957vl.js → store-RCBl7HB-.js} +1 -1
- package/dist/{trending-up-l8M0BO_w.js → trending-up-C-85AwGT.js} +1 -1
- package/dist/{triangle-alert-BI45s6bH.js → triangle-alert-BOhfGgmh.js} +1 -1
- package/dist/{truck-DX3lNQkz.js → truck-CtDjNFbi.js} +1 -1
- package/dist/{user-D8EY1uvt.js → user-Dz5TJZFs.js} +1 -1
- package/dist/{users-CfG0J8wP.js → users-g2N0eL-Z.js} +1 -1
- package/dist/{warehouse-Dt_bAr82.js → warehouse-D3WWDeTX.js} +1 -1
- package/dist/{wrench-C50eI_Iy.js → wrench-CmxY1n4I.js} +1 -1
- package/package.json +2 -2
- package/src/movethewheels/components/DataTable.tsx +41 -6
- package/src/movethewheels/components/MetricCard.tsx +51 -20
- package/src/movethewheels/components/PageLayout.tsx +60 -5
- package/src/movethewheels/components/StatusBadge.tsx +2 -1
- package/src/movethewheels/pages/AIAssistantPage.tsx +11 -3
- package/src/movethewheels/pages/AnalyticsPage.tsx +31 -25
- package/src/movethewheels/pages/CreateOrderPage.tsx +56 -17
- package/src/movethewheels/pages/CustomerDetailsPage.tsx +63 -38
- package/src/movethewheels/pages/CustomersPage.tsx +51 -12
- package/src/movethewheels/pages/DashboardPage.tsx +62 -48
- package/src/movethewheels/pages/DriverDetailsPage.tsx +57 -26
- package/src/movethewheels/pages/DriversPage.tsx +45 -12
- package/src/movethewheels/pages/FinancePage.tsx +45 -41
- package/src/movethewheels/pages/FleetPage.tsx +82 -34
- package/src/movethewheels/pages/ImportExportPage.tsx +46 -29
- package/src/movethewheels/pages/InventoryPage.tsx +37 -33
- package/src/movethewheels/pages/LiveTrackingPage.tsx +58 -20
- package/src/movethewheels/pages/MarketplacePage.tsx +37 -3
- package/src/movethewheels/pages/OrderDetailsPage.tsx +74 -46
- package/src/movethewheels/pages/OrdersListPage.tsx +81 -14
- package/src/movethewheels/pages/ProductDetailsPage.tsx +36 -11
- package/src/movethewheels/pages/ProductsPage.tsx +41 -12
- package/src/movethewheels/pages/ReportsPage.tsx +23 -19
- package/src/movethewheels/pages/RouteDetailsPage.tsx +71 -31
- package/src/movethewheels/pages/RoutesPage.tsx +48 -9
- package/src/movethewheels/pages/SettingsPage.tsx +68 -26
- package/src/movethewheels/pages/TrackingPage.tsx +52 -49
- package/src/movethewheels/pages/VehicleDetailsPage.tsx +49 -24
- package/src/movethewheels/pages/VehiclesPage.tsx +41 -15
- package/src/movethewheels/pages/WarehouseDetailsPage.tsx +55 -30
- package/src/movethewheels/pages/WarehousesPage.tsx +46 -9
- package/dist/CreateOrderPage-DfkSi6JF.js +0 -471
- package/dist/CustomerDetailsPage-DzAIu_tH.js +0 -239
- package/dist/CustomersPage-d21ixOYa.js +0 -119
- package/dist/DataTable-CZGz3SJu.js +0 -239
- package/dist/DriverDetailsPage-YuZSlPSV.js +0 -297
- package/dist/DriversPage-BOhqEBTv.js +0 -127
- package/dist/FinancePage-CnjUNXOb.js +0 -154
- package/dist/ImportExportPage-zNjs7xsV.js +0 -232
- package/dist/InventoryPage-C9g5d3SW.js +0 -223
- package/dist/LiveTrackingPage-DoBvtdBG.js +0 -332
- package/dist/MarketplacePage-BEj5hcBF.js +0 -192
- package/dist/MetricCard-DeHVeVwl.js +0 -135
- package/dist/OrderDetailsPage-D-9lDZYU.js +0 -398
- package/dist/OrdersListPage-CJzHbgY3.js +0 -257
- package/dist/ProductDetailsPage-3rKtzXpy.js +0 -168
- package/dist/ProductsPage-B0P_wjBJ.js +0 -131
- package/dist/RouteDetailsPage-DGjKTUMj.js +0 -240
- package/dist/RoutesPage-CpopY8xN.js +0 -116
- package/dist/SettingsPage-BvXSa3gi.js +0 -247
- package/dist/VehicleDetailsPage-Dqv--MVW.js +0 -194
- package/dist/VehiclesPage-CCBHRDHu.js +0 -127
- package/dist/WarehouseDetailsPage-ZObC_c7W.js +0 -215
- package/dist/WarehousesPage-CpgZEVW9.js +0 -121
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
import { Package, Plus, RefreshCw, Download } from 'lucide-react';
|
|
6
6
|
import { PageLayout } from '../components/PageLayout';
|
|
7
7
|
import { DataTable, type Column } from '../components/DataTable';
|
|
8
|
-
import {
|
|
8
|
+
import { Badge } from '../components/ui';
|
|
9
|
+
import { CTAOverflowMenu, EmphasisPanel, PagePurpose } from '@burdenoff/fe-libs/ui';
|
|
9
10
|
import { useProducts } from '../hooks/useInventory';
|
|
10
11
|
import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
|
|
11
12
|
import { formatCurrency } from '../utils/formatters';
|
|
@@ -96,19 +97,44 @@ export default function ProductsPage() {
|
|
|
96
97
|
subtitle={`${totalProducts} products`}
|
|
97
98
|
icon={<Package size={24} />}
|
|
98
99
|
headerActions={
|
|
99
|
-
<
|
|
100
|
-
<
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
<Button>
|
|
107
|
-
<Plus size={16} /> Add Product
|
|
108
|
-
</Button>
|
|
109
|
-
</div>
|
|
100
|
+
<CTAOverflowMenu
|
|
101
|
+
primary={{ label: 'Add Product', icon: <Plus size={16} />, onSelect: () => {} }}
|
|
102
|
+
actions={[
|
|
103
|
+
{ label: 'Refresh', icon: <RefreshCw size={16} />, onSelect: () => refetch() },
|
|
104
|
+
{ label: 'Export', icon: <Download size={16} />, onSelect: () => {} },
|
|
105
|
+
]}
|
|
106
|
+
/>
|
|
110
107
|
}
|
|
111
108
|
>
|
|
109
|
+
<PagePurpose icon={<Package size={18} />}>
|
|
110
|
+
The catalog of items you ship. Each product's dimensions, weight, value and handling
|
|
111
|
+
flags (fragile, hazardous, temperature-controlled) drive how it's packed, priced and
|
|
112
|
+
routed — keep them accurate so shipments are quoted and handled correctly.
|
|
113
|
+
</PagePurpose>
|
|
114
|
+
|
|
115
|
+
{/* Catalog at a glance — the page's single emphasis zone */}
|
|
116
|
+
<EmphasisPanel>
|
|
117
|
+
<p className="text-sm font-medium text-text-secondary">Catalog at a glance</p>
|
|
118
|
+
<div className="mt-3 grid grid-cols-3 gap-4">
|
|
119
|
+
<div>
|
|
120
|
+
<p className="text-2xl font-bold text-text-primary tabular-nums">{totalProducts}</p>
|
|
121
|
+
<p className="text-xs text-text-secondary">Total products</p>
|
|
122
|
+
</div>
|
|
123
|
+
<div>
|
|
124
|
+
<p className="text-2xl font-bold text-text-primary tabular-nums">
|
|
125
|
+
{products.filter((p) => p.fragile || p.hazardous).length}
|
|
126
|
+
</p>
|
|
127
|
+
<p className="text-xs text-text-secondary">Special handling</p>
|
|
128
|
+
</div>
|
|
129
|
+
<div>
|
|
130
|
+
<p className="text-2xl font-bold text-text-primary tabular-nums">
|
|
131
|
+
{products.filter((p) => p.temperatureControlled).length}
|
|
132
|
+
</p>
|
|
133
|
+
<p className="text-xs text-text-secondary">Temp-controlled</p>
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
</EmphasisPanel>
|
|
137
|
+
|
|
112
138
|
<DataTable
|
|
113
139
|
columns={columns}
|
|
114
140
|
data={products}
|
|
@@ -118,6 +144,9 @@ export default function ProductsPage() {
|
|
|
118
144
|
searchFields={['name', 'sku', 'category'] as (keyof Product)[]}
|
|
119
145
|
onRowClick={(row) => goTo(`inventory/products/${row.id}`)}
|
|
120
146
|
getRowId={(row) => row.id}
|
|
147
|
+
emptyIllustration="empty-files"
|
|
148
|
+
emptyTitle="No products yet"
|
|
149
|
+
emptyDescription="Add products to your catalog so they can be added to orders and stocked in warehouses."
|
|
121
150
|
/>
|
|
122
151
|
</PageLayout>
|
|
123
152
|
);
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
SelectTrigger,
|
|
18
18
|
SelectValue,
|
|
19
19
|
} from '../components/ui';
|
|
20
|
+
import { EmphasisPanel, PagePurpose } from '@burdenoff/fe-libs/ui';
|
|
20
21
|
|
|
21
22
|
export default function ReportsPage() {
|
|
22
23
|
const reports = [
|
|
@@ -87,6 +88,12 @@ export default function ReportsPage() {
|
|
|
87
88
|
</div>
|
|
88
89
|
}
|
|
89
90
|
>
|
|
91
|
+
<PagePurpose icon={<FileBarChart size={18} />}>
|
|
92
|
+
Turn your operational data into shareable summaries. Generate one-off reports on orders,
|
|
93
|
+
finance, fleet, customers or inventory, or schedule them to land in inboxes automatically —
|
|
94
|
+
so stakeholders always have the latest numbers without logging in.
|
|
95
|
+
</PagePurpose>
|
|
96
|
+
|
|
90
97
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
91
98
|
<div className="lg:col-span-2 space-y-4">
|
|
92
99
|
<h3 className="font-semibold">Available Reports</h3>
|
|
@@ -121,25 +128,22 @@ export default function ReportsPage() {
|
|
|
121
128
|
</div>
|
|
122
129
|
|
|
123
130
|
<div className="space-y-6">
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
<
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
</Button>
|
|
141
|
-
</CardContent>
|
|
142
|
-
</Card>
|
|
131
|
+
{/* Scheduled Reports — the page's single emphasis zone */}
|
|
132
|
+
<EmphasisPanel className="space-y-3">
|
|
133
|
+
<h2 className="flex items-center gap-2 text-base font-semibold text-text-primary">
|
|
134
|
+
<Clock size={18} /> Scheduled Reports
|
|
135
|
+
</h2>
|
|
136
|
+
{scheduledReports.map((report) => (
|
|
137
|
+
<div key={report.name} className="p-3 border border-border-seam rounded-lg">
|
|
138
|
+
<p className="font-medium">{report.name}</p>
|
|
139
|
+
<p className="text-sm text-text-secondary">Next: {report.nextRun}</p>
|
|
140
|
+
<p className="text-xs text-text-secondary">{report.recipients} recipients</p>
|
|
141
|
+
</div>
|
|
142
|
+
))}
|
|
143
|
+
<Button variant="outline" className="w-full">
|
|
144
|
+
Schedule New Report
|
|
145
|
+
</Button>
|
|
146
|
+
</EmphasisPanel>
|
|
143
147
|
|
|
144
148
|
<Card>
|
|
145
149
|
<CardHeader>
|
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
|
|
5
5
|
import { useParams } from 'react-router-dom';
|
|
6
6
|
import { Navigation, ArrowLeft, Edit, MapPin, Clock, Zap, CheckCircle } from 'lucide-react';
|
|
7
|
-
import { PageLayout, LoadingState, ErrorState } from '../components/PageLayout';
|
|
7
|
+
import { PageLayout, LoadingState, ErrorState, EmptyState } from '../components/PageLayout';
|
|
8
8
|
import { StatusBadge } from '../components/StatusBadge';
|
|
9
9
|
import { Card, CardContent, CardHeader, CardTitle, Button, Badge } from '../components/ui';
|
|
10
|
+
import { EmphasisPanel, NextSteps, PagePurpose } from '@burdenoff/fe-libs/ui';
|
|
10
11
|
import { useRoute, useRoutes } from '../hooks/useRoutes';
|
|
11
12
|
import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
|
|
12
13
|
import { formatDate, formatAddress } from '../utils/formatters';
|
|
@@ -19,6 +20,7 @@ export default function RouteDetailsPage() {
|
|
|
19
20
|
const { optimizeRoute } = useRoutes();
|
|
20
21
|
|
|
21
22
|
const goBack = () => navigate?.(joinPath(basePath, 'routes'));
|
|
23
|
+
const goTo = (path: string) => navigate?.(joinPath(basePath, path));
|
|
22
24
|
|
|
23
25
|
// Handle new route form
|
|
24
26
|
if (!routeId) {
|
|
@@ -33,11 +35,15 @@ export default function RouteDetailsPage() {
|
|
|
33
35
|
</Button>
|
|
34
36
|
}
|
|
35
37
|
>
|
|
36
|
-
<
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
+
/>
|
|
41
47
|
</PageLayout>
|
|
42
48
|
);
|
|
43
49
|
}
|
|
@@ -90,6 +96,43 @@ export default function RouteDetailsPage() {
|
|
|
90
96
|
</div>
|
|
91
97
|
}
|
|
92
98
|
>
|
|
99
|
+
<PagePurpose icon={<Navigation size={18} />}>
|
|
100
|
+
A single optimized driving plan that chains together this route's stops. Review the
|
|
101
|
+
sequence, distance and timing here, re-optimize if traffic or stops change, then hand the
|
|
102
|
+
itinerary to a driver.
|
|
103
|
+
</PagePurpose>
|
|
104
|
+
|
|
105
|
+
<NextSteps
|
|
106
|
+
storageKey="movethewheels-route-details"
|
|
107
|
+
steps={[
|
|
108
|
+
...(!route.optimized
|
|
109
|
+
? [
|
|
110
|
+
{
|
|
111
|
+
id: 'optimize',
|
|
112
|
+
label: 'Optimize this route',
|
|
113
|
+
description: 'Reorder stops to cut distance and driving time.',
|
|
114
|
+
icon: <Zap size={16} />,
|
|
115
|
+
onClick: handleOptimize,
|
|
116
|
+
},
|
|
117
|
+
]
|
|
118
|
+
: []),
|
|
119
|
+
{
|
|
120
|
+
id: 'edit',
|
|
121
|
+
label: 'Adjust stops',
|
|
122
|
+
description: 'Add, remove or reorder the delivery stops.',
|
|
123
|
+
icon: <Edit size={16} />,
|
|
124
|
+
onClick: () => {},
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
id: 'orders',
|
|
128
|
+
label: 'Review the orders on this route',
|
|
129
|
+
description: 'Check the deliveries bundled into this run.',
|
|
130
|
+
icon: <MapPin size={16} />,
|
|
131
|
+
onClick: () => goTo('orders'),
|
|
132
|
+
},
|
|
133
|
+
]}
|
|
134
|
+
/>
|
|
135
|
+
|
|
93
136
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
94
137
|
<div className="lg:col-span-2 space-y-6">
|
|
95
138
|
<Card>
|
|
@@ -161,36 +204,33 @@ export default function RouteDetailsPage() {
|
|
|
161
204
|
</div>
|
|
162
205
|
|
|
163
206
|
<div className="space-y-6">
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
<
|
|
170
|
-
|
|
171
|
-
<
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
</
|
|
207
|
+
{/* Route Info — the page's single emphasis zone */}
|
|
208
|
+
<EmphasisPanel className="space-y-4">
|
|
209
|
+
<h2 className="text-base font-semibold text-text-primary">Route Info</h2>
|
|
210
|
+
<div>
|
|
211
|
+
<p className="text-sm text-text-secondary">Total Distance</p>
|
|
212
|
+
<p className="text-2xl font-bold">
|
|
213
|
+
{route.distance.toFixed(1)}
|
|
214
|
+
<span className="text-sm font-normal"> km</span>
|
|
215
|
+
</p>
|
|
216
|
+
</div>
|
|
217
|
+
<div>
|
|
218
|
+
<p className="text-sm text-text-secondary">Est. Duration</p>
|
|
219
|
+
<p className="text-2xl font-bold">
|
|
220
|
+
{route.estimatedDuration}
|
|
221
|
+
<span className="text-sm font-normal"> min</span>
|
|
222
|
+
</p>
|
|
223
|
+
</div>
|
|
224
|
+
{route.actualDuration && (
|
|
176
225
|
<div>
|
|
177
|
-
<p className="text-sm text-text-secondary">
|
|
226
|
+
<p className="text-sm text-text-secondary">Actual Duration</p>
|
|
178
227
|
<p className="text-2xl font-bold">
|
|
179
|
-
{route.
|
|
228
|
+
{route.actualDuration}
|
|
180
229
|
<span className="text-sm font-normal"> min</span>
|
|
181
230
|
</p>
|
|
182
231
|
</div>
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
<p className="text-sm text-text-secondary">Actual Duration</p>
|
|
186
|
-
<p className="text-2xl font-bold">
|
|
187
|
-
{route.actualDuration}
|
|
188
|
-
<span className="text-sm font-normal"> min</span>
|
|
189
|
-
</p>
|
|
190
|
-
</div>
|
|
191
|
-
)}
|
|
192
|
-
</CardContent>
|
|
193
|
-
</Card>
|
|
232
|
+
)}
|
|
233
|
+
</EmphasisPanel>
|
|
194
234
|
|
|
195
235
|
<Card>
|
|
196
236
|
<CardHeader>
|
|
@@ -6,7 +6,8 @@ import { Plus, RefreshCw, Navigation } from 'lucide-react';
|
|
|
6
6
|
import { PageLayout } from '../components/PageLayout';
|
|
7
7
|
import { DataTable, type Column } from '../components/DataTable';
|
|
8
8
|
import { StatusBadge } from '../components/StatusBadge';
|
|
9
|
-
import {
|
|
9
|
+
import { Badge, Button } from '../components/ui';
|
|
10
|
+
import { CTAOverflowMenu, EmphasisPanel, PagePurpose } from '@burdenoff/fe-libs/ui';
|
|
10
11
|
import { useRoutes } from '../hooks/useRoutes';
|
|
11
12
|
import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
|
|
12
13
|
import { formatDate } from '../utils/formatters';
|
|
@@ -79,16 +80,45 @@ export default function RoutesPage() {
|
|
|
79
80
|
subtitle={`${totalRoutes} routes`}
|
|
80
81
|
icon={<Navigation size={24} />}
|
|
81
82
|
headerActions={
|
|
82
|
-
<
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
83
|
+
<CTAOverflowMenu
|
|
84
|
+
primary={{
|
|
85
|
+
label: 'Create Route',
|
|
86
|
+
icon: <Plus size={16} />,
|
|
87
|
+
onSelect: () => goTo('routes/new'),
|
|
88
|
+
}}
|
|
89
|
+
actions={[{ label: 'Refresh', icon: <RefreshCw size={16} />, onSelect: () => refetch() }]}
|
|
90
|
+
/>
|
|
90
91
|
}
|
|
91
92
|
>
|
|
93
|
+
<PagePurpose icon={<Navigation size={18} />}>
|
|
94
|
+
Routes group multiple delivery stops into an optimized driving plan. Build a route to batch
|
|
95
|
+
nearby drop-offs, cut distance and fuel, and give drivers a single ordered itinerary — then
|
|
96
|
+
open one to review stops, distance and optimization status.
|
|
97
|
+
</PagePurpose>
|
|
98
|
+
|
|
99
|
+
{/* Routes at a glance — the page's single emphasis zone */}
|
|
100
|
+
<EmphasisPanel>
|
|
101
|
+
<p className="text-sm font-medium text-text-secondary">Routes at a glance</p>
|
|
102
|
+
<div className="mt-3 grid grid-cols-3 gap-4">
|
|
103
|
+
<div>
|
|
104
|
+
<p className="text-2xl font-bold text-text-primary tabular-nums">{totalRoutes}</p>
|
|
105
|
+
<p className="text-xs text-text-secondary">Total routes</p>
|
|
106
|
+
</div>
|
|
107
|
+
<div>
|
|
108
|
+
<p className="text-2xl font-bold text-text-primary tabular-nums">
|
|
109
|
+
{routes.filter((r) => r.status === 'in_progress').length}
|
|
110
|
+
</p>
|
|
111
|
+
<p className="text-xs text-text-secondary">In progress</p>
|
|
112
|
+
</div>
|
|
113
|
+
<div>
|
|
114
|
+
<p className="text-2xl font-bold text-text-primary tabular-nums">
|
|
115
|
+
{routes.filter((r) => r.optimized).length}
|
|
116
|
+
</p>
|
|
117
|
+
<p className="text-xs text-text-secondary">Optimized</p>
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
120
|
+
</EmphasisPanel>
|
|
121
|
+
|
|
92
122
|
<DataTable
|
|
93
123
|
columns={columns}
|
|
94
124
|
data={routes}
|
|
@@ -98,6 +128,15 @@ export default function RoutesPage() {
|
|
|
98
128
|
searchFields={['name'] as (keyof Route)[]}
|
|
99
129
|
onRowClick={(row) => goTo(`routes/${row.id}`)}
|
|
100
130
|
getRowId={(row) => row.id}
|
|
131
|
+
emptyIllustration="empty-data"
|
|
132
|
+
emptyTitle="No routes yet"
|
|
133
|
+
emptyDescription="Create a route to batch deliveries into an optimized driving plan."
|
|
134
|
+
emptyAction={
|
|
135
|
+
<Button onClick={() => goTo('routes/new')}>
|
|
136
|
+
<Plus size={16} className="mr-1.5" />
|
|
137
|
+
Create Route
|
|
138
|
+
</Button>
|
|
139
|
+
}
|
|
101
140
|
/>
|
|
102
141
|
</PageLayout>
|
|
103
142
|
);
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Settings Page
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import { useState } from 'react';
|
|
5
6
|
import { Settings, User, Bell, Shield, Globe, Webhook, Save } from 'lucide-react';
|
|
6
7
|
import { PageLayout } from '../components/PageLayout';
|
|
7
8
|
import {
|
|
@@ -24,15 +25,59 @@ import {
|
|
|
24
25
|
TabsTrigger,
|
|
25
26
|
TabsContent,
|
|
26
27
|
} from '../components/ui';
|
|
28
|
+
import { EmphasisPanel, PagePurpose, NextSteps } from '@burdenoff/fe-libs/ui';
|
|
27
29
|
|
|
28
30
|
export default function SettingsPage() {
|
|
31
|
+
const [tab, setTab] = useState('general');
|
|
32
|
+
|
|
29
33
|
return (
|
|
30
34
|
<PageLayout
|
|
31
35
|
title="Settings"
|
|
32
36
|
subtitle="Manage your account and preferences"
|
|
33
37
|
icon={<Settings size={24} />}
|
|
34
38
|
>
|
|
35
|
-
<
|
|
39
|
+
<PagePurpose icon={<Settings size={18} />}>
|
|
40
|
+
Configure how MoveTheWheels works for your business — company details and localization,
|
|
41
|
+
which notifications you receive, account security, and the integrations (maps, payments,
|
|
42
|
+
messaging) that power deliveries. Set these once so the rest of the app behaves the way you
|
|
43
|
+
expect.
|
|
44
|
+
</PagePurpose>
|
|
45
|
+
|
|
46
|
+
<NextSteps
|
|
47
|
+
storageKey="movethewheels-settings"
|
|
48
|
+
steps={[
|
|
49
|
+
{
|
|
50
|
+
id: 'company',
|
|
51
|
+
label: 'Confirm company details & localization',
|
|
52
|
+
description: 'Set your name, contact info, timezone, currency and language.',
|
|
53
|
+
icon: <User size={16} />,
|
|
54
|
+
onClick: () => setTab('general'),
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
id: 'notifications',
|
|
58
|
+
label: 'Choose your notifications',
|
|
59
|
+
description: 'Decide which order, delivery and report emails you receive.',
|
|
60
|
+
icon: <Bell size={16} />,
|
|
61
|
+
onClick: () => setTab('notifications'),
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
id: 'security',
|
|
65
|
+
label: 'Secure your account',
|
|
66
|
+
description: 'Update your password and enable two-factor authentication.',
|
|
67
|
+
icon: <Shield size={16} />,
|
|
68
|
+
onClick: () => setTab('security'),
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
id: 'integrations',
|
|
72
|
+
label: 'Connect your tools',
|
|
73
|
+
description: 'Link maps, payments and messaging providers.',
|
|
74
|
+
icon: <Webhook size={16} />,
|
|
75
|
+
onClick: () => setTab('integrations'),
|
|
76
|
+
},
|
|
77
|
+
]}
|
|
78
|
+
/>
|
|
79
|
+
|
|
80
|
+
<Tabs value={tab} onValueChange={setTab}>
|
|
36
81
|
<TabsList className="mb-6">
|
|
37
82
|
<TabsTrigger value="general">
|
|
38
83
|
<User size={16} /> General
|
|
@@ -49,32 +94,29 @@ export default function SettingsPage() {
|
|
|
49
94
|
</TabsList>
|
|
50
95
|
|
|
51
96
|
<TabsContent value="general" className="space-y-6">
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
<
|
|
60
|
-
<Label>Company Name</Label>
|
|
61
|
-
<Input defaultValue="QuickLogistics Pro" />
|
|
62
|
-
</div>
|
|
63
|
-
<div>
|
|
64
|
-
<Label>Contact Email</Label>
|
|
65
|
-
<Input defaultValue="contact@quicklogistics.com" />
|
|
66
|
-
</div>
|
|
67
|
-
<div>
|
|
68
|
-
<Label>Phone</Label>
|
|
69
|
-
<Input defaultValue="+1-555-QUICK" />
|
|
70
|
-
</div>
|
|
71
|
-
<div>
|
|
72
|
-
<Label>Website</Label>
|
|
73
|
-
<Input defaultValue="https://quicklogistics.com" />
|
|
74
|
-
</div>
|
|
97
|
+
{/* Company Information — the page's single emphasis zone */}
|
|
98
|
+
<EmphasisPanel>
|
|
99
|
+
<h2 className="text-base font-semibold text-text-primary">Company Information</h2>
|
|
100
|
+
<p className="text-sm text-text-secondary mb-4">Basic company details</p>
|
|
101
|
+
<div className="grid grid-cols-2 gap-4">
|
|
102
|
+
<div>
|
|
103
|
+
<Label>Company Name</Label>
|
|
104
|
+
<Input defaultValue="QuickLogistics Pro" />
|
|
75
105
|
</div>
|
|
76
|
-
|
|
77
|
-
|
|
106
|
+
<div>
|
|
107
|
+
<Label>Contact Email</Label>
|
|
108
|
+
<Input defaultValue="contact@quicklogistics.com" />
|
|
109
|
+
</div>
|
|
110
|
+
<div>
|
|
111
|
+
<Label>Phone</Label>
|
|
112
|
+
<Input defaultValue="+1-555-QUICK" />
|
|
113
|
+
</div>
|
|
114
|
+
<div>
|
|
115
|
+
<Label>Website</Label>
|
|
116
|
+
<Input defaultValue="https://quicklogistics.com" />
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
</EmphasisPanel>
|
|
78
120
|
|
|
79
121
|
<Card>
|
|
80
122
|
<CardHeader>
|
|
@@ -10,6 +10,7 @@ import { MapPin, Search, Package, Truck, Clock, CheckCircle, AlertCircle } from
|
|
|
10
10
|
import { PageLayout, EmptyState } from '../components/PageLayout';
|
|
11
11
|
import { StatusBadge } from '../components/StatusBadge';
|
|
12
12
|
import { Card, CardContent, CardHeader, CardTitle, Button, Input } from '../components/ui';
|
|
13
|
+
import { EmphasisPanel, PagePurpose } from '@burdenoff/fe-libs/ui';
|
|
13
14
|
import { useOrderTracking } from '../hooks/useTracking';
|
|
14
15
|
import { formatDate, formatAddress } from '../utils/formatters';
|
|
15
16
|
import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
|
|
@@ -56,6 +57,13 @@ export default function TrackingPage() {
|
|
|
56
57
|
</Button>
|
|
57
58
|
}
|
|
58
59
|
>
|
|
60
|
+
<PagePurpose icon={<MapPin size={18} />}>
|
|
61
|
+
{t(
|
|
62
|
+
'movethewheels.tracking.purpose',
|
|
63
|
+
'Look up any single shipment by its tracking number or order ID to see exactly where it is, its progress through pickup and delivery, the current location and any alerts — the same view you can share with a customer.'
|
|
64
|
+
)}
|
|
65
|
+
</PagePurpose>
|
|
66
|
+
|
|
59
67
|
{/* Search Form */}
|
|
60
68
|
<Card>
|
|
61
69
|
<CardContent className="p-6">
|
|
@@ -106,7 +114,7 @@ export default function TrackingPage() {
|
|
|
106
114
|
{/* No Search Yet */}
|
|
107
115
|
{!searchedOrderId && !error && (
|
|
108
116
|
<EmptyState
|
|
109
|
-
|
|
117
|
+
illustration="onboarding"
|
|
110
118
|
title={t('movethewheels.tracking.empty.title', 'Enter a tracking number')}
|
|
111
119
|
description={t(
|
|
112
120
|
'movethewheels.tracking.empty.description',
|
|
@@ -120,55 +128,50 @@ export default function TrackingPage() {
|
|
|
120
128
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
121
129
|
{/* Main Tracking Info */}
|
|
122
130
|
<div className="lg:col-span-2 space-y-6">
|
|
123
|
-
{/* Status Card */}
|
|
124
|
-
<
|
|
125
|
-
<
|
|
126
|
-
<
|
|
127
|
-
<
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
131
|
+
{/* Status Card — the page's single emphasis zone */}
|
|
132
|
+
<EmphasisPanel>
|
|
133
|
+
<div className="flex items-center justify-between mb-4">
|
|
134
|
+
<h2 className="flex items-center gap-2 text-base font-semibold text-text-primary">
|
|
135
|
+
<Package size={20} />
|
|
136
|
+
{t('movethewheels.tracking.orderLabel', 'Order')} {order.orderNumber}
|
|
137
|
+
</h2>
|
|
138
|
+
<StatusBadge status={order.status} type="order" />
|
|
139
|
+
</div>
|
|
140
|
+
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
|
141
|
+
<div>
|
|
142
|
+
<p className="text-sm text-text-secondary">
|
|
143
|
+
{t('movethewheels.tracking.details.trackingNumber', 'Tracking Number')}
|
|
144
|
+
</p>
|
|
145
|
+
<p className="font-mono font-medium">{order.trackingNumber}</p>
|
|
132
146
|
</div>
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
{t('movethewheels.tracking.details.estimatedDelivery', 'Estimated Delivery')}
|
|
145
|
-
</p>
|
|
146
|
-
<p className="font-medium">
|
|
147
|
-
{tracking.estimatedArrival
|
|
148
|
-
? formatDate(tracking.estimatedArrival, 'full')
|
|
149
|
-
: order.scheduledDelivery
|
|
150
|
-
? formatDate(order.scheduledDelivery, 'full')
|
|
151
|
-
: t('movethewheels.tracking.details.tbd', 'TBD')}
|
|
152
|
-
</p>
|
|
153
|
-
</div>
|
|
154
|
-
<div>
|
|
155
|
-
<p className="text-sm text-text-secondary">
|
|
156
|
-
{t('movethewheels.tracking.details.items', 'Items')}
|
|
157
|
-
</p>
|
|
158
|
-
<p className="font-medium">
|
|
159
|
-
{order.items.length}{' '}
|
|
160
|
-
{t('movethewheels.tracking.details.itemsSuffix', 'items')}
|
|
161
|
-
</p>
|
|
162
|
-
</div>
|
|
163
|
-
<div>
|
|
164
|
-
<p className="text-sm text-text-secondary">
|
|
165
|
-
{t('movethewheels.tracking.details.lastUpdated', 'Last Updated')}
|
|
166
|
-
</p>
|
|
167
|
-
<p className="font-medium">{formatDate(tracking.lastUpdated, 'relative')}</p>
|
|
168
|
-
</div>
|
|
147
|
+
<div>
|
|
148
|
+
<p className="text-sm text-text-secondary">
|
|
149
|
+
{t('movethewheels.tracking.details.estimatedDelivery', 'Estimated Delivery')}
|
|
150
|
+
</p>
|
|
151
|
+
<p className="font-medium">
|
|
152
|
+
{tracking.estimatedArrival
|
|
153
|
+
? formatDate(tracking.estimatedArrival, 'full')
|
|
154
|
+
: order.scheduledDelivery
|
|
155
|
+
? formatDate(order.scheduledDelivery, 'full')
|
|
156
|
+
: t('movethewheels.tracking.details.tbd', 'TBD')}
|
|
157
|
+
</p>
|
|
169
158
|
</div>
|
|
170
|
-
|
|
171
|
-
|
|
159
|
+
<div>
|
|
160
|
+
<p className="text-sm text-text-secondary">
|
|
161
|
+
{t('movethewheels.tracking.details.items', 'Items')}
|
|
162
|
+
</p>
|
|
163
|
+
<p className="font-medium">
|
|
164
|
+
{order.items.length} {t('movethewheels.tracking.details.itemsSuffix', 'items')}
|
|
165
|
+
</p>
|
|
166
|
+
</div>
|
|
167
|
+
<div>
|
|
168
|
+
<p className="text-sm text-text-secondary">
|
|
169
|
+
{t('movethewheels.tracking.details.lastUpdated', 'Last Updated')}
|
|
170
|
+
</p>
|
|
171
|
+
<p className="font-medium">{formatDate(tracking.lastUpdated, 'relative')}</p>
|
|
172
|
+
</div>
|
|
173
|
+
</div>
|
|
174
|
+
</EmphasisPanel>
|
|
172
175
|
|
|
173
176
|
{/* Progress Timeline */}
|
|
174
177
|
<Card>
|
|
@@ -404,7 +407,7 @@ export default function TrackingPage() {
|
|
|
404
407
|
{/* No Results */}
|
|
405
408
|
{searchedOrderId && !order && !isLoading && !error && (
|
|
406
409
|
<EmptyState
|
|
407
|
-
|
|
410
|
+
illustration="empty-search"
|
|
408
411
|
title={t('movethewheels.tracking.notFound.title', 'Shipment not found')}
|
|
409
412
|
description={`${t('movethewheels.tracking.notFound.descriptionPrefix', 'No shipment found with tracking number')} "${searchedOrderId}"`}
|
|
410
413
|
action={
|