@burdenoff/microfe-movethewheels 2026.703.4 → 2026.703.5
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-3YOnHO9Y.js → AIAssistantPage-D2gaEL1U.js} +16 -15
- package/dist/AnalyticsPage-DZMlEoBi.js +194 -0
- package/dist/{CreateOrderPage-DRAgGo9i.js → CreateOrderPage-CWHx8yys.js} +40 -40
- package/dist/{CustomerDetailsPage-BCeMmOFe.js → CustomerDetailsPage-nR4oZ_ju.js} +71 -71
- package/dist/CustomersPage-Bbvwvn8k.js +205 -0
- package/dist/DashboardPage-DrEhNf7N.js +542 -0
- package/dist/{DataTable-XvUcTjwi.js → DataTable-CqMUD4er.js} +31 -31
- package/dist/{DocumentsCard-U2lSe-cn.js → DocumentsCard-edDkgnJj.js} +25 -25
- package/dist/{DriverDetailsPage-BQrYd_Lp.js → DriverDetailsPage-whEMOWs9.js} +64 -64
- package/dist/DriversPage-CHF2ZEzd.js +221 -0
- package/dist/FinancePage-Dn9GrW-1.js +175 -0
- package/dist/{FleetPage-3ZHsB-TN.js → FleetPage-CrtGPy-p.js} +22 -22
- package/dist/{FormError-cuLV2jVD.js → FormError-BKcb_wT-.js} +1 -1
- package/dist/{ImportExportPage-C6f7DqhM.js → ImportExportPage-WYdwZomZ.js} +12 -12
- package/dist/InventoryPage-l5A1f753.js +259 -0
- package/dist/LiveTrackingPage-DhEaNKZ4.js +429 -0
- package/dist/{MarketplacePage-Bh_2ns4v.js → MarketplacePage-B9RYOb1Y.js} +116 -116
- package/dist/MetricCard-y3bWHKUQ.js +191 -0
- package/dist/{Notice-CSoDYKw9.js → Notice-DOO-tjpV.js} +2 -2
- package/dist/{OrderDetailsPage-zCj0lW0j.js → OrderDetailsPage-CwYHwk1K.js} +58 -58
- package/dist/OrdersListPage-DhZJoQeI.js +386 -0
- package/dist/{PageLayout-D4LDHJJq.js → PageLayout-BniRJaQ6.js} +1 -1
- package/dist/{ProductDetailsPage-wsxAQkA0.js → ProductDetailsPage-1Gm9JKy0.js} +55 -55
- package/dist/ProductsPage-C8sn-1tN.js +186 -0
- package/dist/ReportsPage-D1tb0dcz.js +377 -0
- package/dist/{RouteDetailsPage-DyMJE9AS.js → RouteDetailsPage-CkMyR9b_.js} +208 -181
- package/dist/{RoutesPage-Dtc669yc.js → RoutesPage-QkENzsE5.js} +12 -12
- package/dist/{SettingsPage-CpH9xhQg.js → SettingsPage-DV3_tU_X.js} +15 -15
- package/dist/Sparkline-K2RMJ58u.js +16729 -0
- package/dist/{StatusBadge-C87mDpDP.js → StatusBadge-BNHXYvZx.js} +7 -7
- package/dist/TrackingPage-BZrg4RCu.js +368 -0
- package/dist/{VehicleDetailsPage-DldYNsYV.js → VehicleDetailsPage-D-8XPZ2G.js} +53 -53
- package/dist/VehiclesPage-D5BELbtM.js +184 -0
- package/dist/{WarehouseDetailsPage-CMG7or6G.js → WarehouseDetailsPage-Cmg-8Frn.js} +64 -64
- package/dist/WarehousesPage-C1Y1M867.js +182 -0
- package/dist/{arrow-left-n48Y-Wis.js → arrow-left-ClW4Isj7.js} +1 -1
- package/dist/{arrow-right-Q3XQpTPR.js → arrow-right-CmKOmIkh.js} +1 -1
- package/dist/{arrow-up-xNF1zJUG.js → arrow-up-BJijRLAZ.js} +1 -1
- package/dist/{box-Eu05PXDh.js → box-BT3NsRQP.js} +1 -1
- package/dist/boxes-B_OGvQiW.js +54 -0
- package/dist/{chart-column-ouzgxIv_.js → chart-column-DQTTdUPk.js} +1 -1
- package/dist/charts-Tj_PPTfA.js +2018 -0
- package/dist/{chevron-left-B2_r0Elx.js → chevron-left-B1rZyWWb.js} +1 -1
- package/dist/{chevron-right-eV4LjaSi.js → chevron-right-BIcwUJZq.js} +1 -1
- package/dist/chunk-BdsCSvBP.js +20 -0
- package/dist/{circle-alert-Bzh2DBSF.js → circle-alert-CNkuDNc6.js} +1 -1
- package/dist/circle-check-Cs1OVjtV.js +13 -0
- package/dist/{circle-check-big-CGMSpQmD.js → circle-check-big-C-p4eNwn.js} +1 -1
- package/dist/{clock-Bl3ptexL.js → clock-dRGG4rkF.js} +1 -1
- package/dist/{dollar-sign-CAVU95SO.js → dollar-sign-B6RoYHHz.js} +1 -1
- package/dist/{download-C5NbvVDc.js → download-DP04iWTd.js} +1 -1
- package/dist/{file-text-D-1uvLZG.js → file-text-BuDx4hZp.js} +1 -1
- package/dist/{filter-DUfUg6BO.js → filter-BD483_8g.js} +1 -1
- package/dist/{fuel-B6_lnKDs.js → fuel-DLH1ag04.js} +1 -1
- package/dist/index.css +2 -0
- package/dist/index.js +214 -225
- package/dist/{map-C-M-FQLR.js → map-DNQn9fyY.js} +1 -1
- package/dist/{map-pin-Dznl5l40.js → map-pin-CqN1bbT1.js} +1 -1
- package/dist/mapMarkers-NCV9MRKR.js +28 -0
- package/dist/maps-yzLqiK_C.js +5070 -0
- package/dist/{navigation-BJSJl-VG.js → navigation-CgGUt2tR.js} +1 -1
- package/dist/{package-CcRttV2N.js → package-DmRF7iQD.js} +1 -1
- package/dist/{phone-DVSZHt_j.js → phone-Cl32EXKW.js} +1 -1
- package/dist/{plus-C_XZDwBz.js → plus-DlANgOFx.js} +1 -1
- package/dist/{refresh-cw-CfgN67l3.js → refresh-cw-rW59D4b7.js} +1 -1
- package/dist/route-CWjPrtAN.js +22 -0
- package/dist/{save-DJE8Fpdv.js → save-UDlog7hg.js} +1 -1
- package/dist/{search-D_VyGcZT.js → search-DNIG5Ck7.js} +1 -1
- package/dist/{square-pen-D-6JYrVQ.js → square-pen-BcfB3Grd.js} +1 -1
- package/dist/{star-BpRlUZuU.js → star-CVZHFwDx.js} +1 -1
- package/dist/{store-B-8HMuyZ.js → store-bwnmCK9f.js} +1 -1
- package/dist/{trash-2-8vmv-XV_.js → trash-2-B80G12d4.js} +1 -1
- package/dist/{trending-up-Dz6PqPRj.js → trending-up-CH4R4KOa.js} +1 -1
- package/dist/{triangle-alert-D7Y3J_4z.js → triangle-alert-CmS8AUU6.js} +1 -1
- package/dist/{truck-D-y2IDzv.js → truck-9rLDncav.js} +1 -1
- package/dist/{upload-CHZofOqb.js → upload-DanJY1Vo.js} +1 -1
- package/dist/{useAssistant-DVW4QgQb.js → useAssistant-CnEwkTLm.js} +11 -28
- package/dist/{useCustomers-BPW4__PR.js → useCustomers-ChJao1ET.js} +1 -1
- package/dist/{useFleet-ChBkxAd6.js → useFleet-g5Yri0G8.js} +1 -1
- package/dist/{useImportExport-DrPDHEDK.js → useImportExport-MYwIT3rE.js} +1 -1
- package/dist/{useInventory-CAAVMRp6.js → useInventory-BSOn-R1c.js} +1 -1
- package/dist/{useOrders-B465S2nt.js → useOrders-BSiqo61w.js} +1 -1
- package/dist/{useReports-jQ3GpNOm.js → useReports-fWESfsF9.js} +1 -1
- package/dist/{useRoutes-hS3d5-xr.js → useRoutes-zD8Yp323.js} +1 -1
- package/dist/{useSettings-BB8X2RLh.js → useSettings-B1lsWeZ1.js} +6 -6
- package/dist/{useTracking-RjGz9PZG.js → useTracking-BKG0I2ln.js} +3 -3
- package/dist/{user-DzfTn4dE.js → user-DU51el3m.js} +1 -1
- package/dist/{user-check-BEKRCi7v.js → user-check-IMhk-qkX.js} +1 -1
- package/dist/{users-CCrq-w0C.js → users-B8KhJBGg.js} +1 -1
- package/dist/{warehouse-CaqfmMKh.js → warehouse-CcMMioeY.js} +1 -1
- package/dist/{wrench-q_rgJfaP.js → wrench-Dg-sgH2e.js} +1 -1
- package/dist/{x-tMeDMcCw.js → x-C4ObLVEa.js} +1 -1
- package/package.json +5 -1
- package/src/movethewheels/components/GlassPanel.tsx +71 -0
- package/src/movethewheels/components/MetricCard.tsx +171 -73
- package/src/movethewheels/components/StatusBadge.tsx +7 -6
- package/src/movethewheels/components/charts/ChartShell.tsx +78 -0
- package/src/movethewheels/components/charts/Sparkline.tsx +85 -0
- package/src/movethewheels/components/charts/StatusDonut.tsx +115 -0
- package/src/movethewheels/components/charts/TrendChart.tsx +179 -0
- package/src/movethewheels/components/charts/VolumeBar.tsx +97 -0
- package/src/movethewheels/components/charts/chartTokens.ts +43 -0
- package/src/movethewheels/components/charts/index.ts +9 -0
- package/src/movethewheels/components/index.ts +44 -1
- package/src/movethewheels/components/maps/LogisticsMap.tsx +94 -0
- package/src/movethewheels/components/maps/MapCanvas.tsx +118 -0
- package/src/movethewheels/components/maps/RouteMap.tsx +71 -0
- package/src/movethewheels/components/maps/TrackingMap.tsx +102 -0
- package/src/movethewheels/components/maps/index.ts +16 -0
- package/src/movethewheels/components/maps/leaflet-setup.ts +124 -0
- package/src/movethewheels/components/maps/map-markers.css +121 -0
- package/src/movethewheels/hooks/useAnalytics.ts +14 -4
- package/src/movethewheels/pages/AnalyticsPage.tsx +93 -73
- package/src/movethewheels/pages/CreateOrderPage.tsx +4 -4
- package/src/movethewheels/pages/CustomersPage.tsx +29 -20
- package/src/movethewheels/pages/DashboardPage.tsx +238 -12
- package/src/movethewheels/pages/DriverDetailsPage.tsx +1 -1
- package/src/movethewheels/pages/DriversPage.tsx +35 -24
- package/src/movethewheels/pages/FinancePage.tsx +68 -30
- package/src/movethewheels/pages/InventoryPage.tsx +15 -3
- package/src/movethewheels/pages/LiveTrackingPage.tsx +124 -16
- package/src/movethewheels/pages/OrdersListPage.tsx +46 -38
- package/src/movethewheels/pages/ProductsPage.tsx +28 -24
- package/src/movethewheels/pages/ReportsPage.tsx +23 -0
- package/src/movethewheels/pages/RouteDetailsPage.tsx +46 -7
- package/src/movethewheels/pages/SettingsPage.tsx +2 -2
- package/src/movethewheels/pages/TrackingPage.tsx +76 -6
- package/src/movethewheels/pages/VehicleDetailsPage.tsx +1 -1
- package/src/movethewheels/pages/VehiclesPage.tsx +34 -24
- package/src/movethewheels/pages/WarehouseDetailsPage.tsx +1 -1
- package/src/movethewheels/pages/WarehousesPage.tsx +28 -22
- package/src/movethewheels/types/index.ts +1 -1
- package/src/movethewheels/utils/mapMarkers.ts +58 -0
- package/dist/AnalyticsPage-CMpI1070.js +0 -215
- package/dist/CustomersPage-CaNBMXmM.js +0 -172
- package/dist/DashboardPage-DIKlXde3.js +0 -387
- package/dist/DriversPage-PYmdFiby.js +0 -176
- package/dist/FinancePage-Cwh9Bw4M.js +0 -136
- package/dist/InventoryPage-DpiqVu1v.js +0 -253
- package/dist/LiveTrackingPage-FAzzlVnX.js +0 -379
- package/dist/MetricCard-CVBVpu7o.js +0 -146
- package/dist/OrdersListPage-CAMsuUck.js +0 -384
- package/dist/ProductsPage-DC5qDLfz.js +0 -180
- package/dist/ReportsPage-BwUJ37eO.js +0 -356
- package/dist/TrackingPage-DaNEFG_h.js +0 -325
- package/dist/VehiclesPage-BFw13iMt.js +0 -176
- package/dist/WarehousesPage-Wkytnt86.js +0 -173
- package/dist/useAnalytics-D7HW2XRb.js +0 -347
- /package/dist/{formatters-BQr-aR5W.js → formatters-FWJDwTFv.js} +0 -0
|
@@ -4,13 +4,15 @@
|
|
|
4
4
|
* Track orders by tracking number or order ID.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { useState } from 'react';
|
|
7
|
+
import { useMemo, useState } from 'react';
|
|
8
8
|
import { useSearchParams } from 'react-router-dom';
|
|
9
9
|
import { MapPin, Search, Package, Truck, Clock, CheckCircle, AlertCircle } from 'lucide-react';
|
|
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
13
|
import { EmphasisPanel, PagePurpose } from '@burdenoff/fe-libs/ui';
|
|
14
|
+
import { TrackingMap, type TrackEndpoint, type TrackPoint } from '../components/maps';
|
|
15
|
+
import { firstRealLatLng } from '../utils/mapMarkers';
|
|
14
16
|
import { useOrderTracking } from '../hooks/useTracking';
|
|
15
17
|
import { formatDate, formatAddress } from '../utils/formatters';
|
|
16
18
|
import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
|
|
@@ -42,6 +44,42 @@ export default function TrackingPage() {
|
|
|
42
44
|
}
|
|
43
45
|
};
|
|
44
46
|
|
|
47
|
+
// Real geographic view of the shipment: pickup → delivery endpoints, a
|
|
48
|
+
// breadcrumb trail from timeline events that carry coordinates, and the live
|
|
49
|
+
// current position. Everything is coordinate-filtered so a shipment with no
|
|
50
|
+
// GPS data honestly shows the map's empty state instead of a fake route.
|
|
51
|
+
const mapModel = useMemo(() => {
|
|
52
|
+
if (!order) return null;
|
|
53
|
+
const originCoord = firstRealLatLng(order.pickupAddress);
|
|
54
|
+
const destCoord = firstRealLatLng(order.deliveryAddress);
|
|
55
|
+
const origin: TrackEndpoint | null = originCoord
|
|
56
|
+
? { ...originCoord, label: `Pickup · ${order.pickupAddress.city || 'Origin'}` }
|
|
57
|
+
: null;
|
|
58
|
+
const destination: TrackEndpoint | null = destCoord
|
|
59
|
+
? { ...destCoord, label: `Delivery · ${order.deliveryAddress.city || 'Destination'}` }
|
|
60
|
+
: null;
|
|
61
|
+
|
|
62
|
+
const trail: TrackPoint[] = order.timeline
|
|
63
|
+
.map((event): TrackPoint | null => {
|
|
64
|
+
const coord = event.location ? firstRealLatLng(event.location) : null;
|
|
65
|
+
return coord
|
|
66
|
+
? { lat: coord.lat, lng: coord.lng, timestamp: new Date(event.timestamp).toISOString() }
|
|
67
|
+
: null;
|
|
68
|
+
})
|
|
69
|
+
.filter((point): point is TrackPoint => point !== null);
|
|
70
|
+
|
|
71
|
+
const currentCoord = tracking ? firstRealLatLng(tracking.currentLocation) : null;
|
|
72
|
+
const current: TrackEndpoint | null = currentCoord
|
|
73
|
+
? {
|
|
74
|
+
...currentCoord,
|
|
75
|
+
label: tracking?.currentLocation.address || 'Current position',
|
|
76
|
+
}
|
|
77
|
+
: null;
|
|
78
|
+
|
|
79
|
+
const hasAny = Boolean(origin || destination || current || trail.length > 0);
|
|
80
|
+
return { origin, destination, trail, current, hasAny };
|
|
81
|
+
}, [order, tracking]);
|
|
82
|
+
|
|
45
83
|
return (
|
|
46
84
|
<PageLayout
|
|
47
85
|
title={t('movethewheels.tracking.title', 'Track Shipment')}
|
|
@@ -173,6 +211,30 @@ export default function TrackingPage() {
|
|
|
173
211
|
</div>
|
|
174
212
|
</EmphasisPanel>
|
|
175
213
|
|
|
214
|
+
{/* Route Map — real origin/destination + tracking trail + live position */}
|
|
215
|
+
<Card>
|
|
216
|
+
<CardHeader>
|
|
217
|
+
<CardTitle className="flex items-center gap-2">
|
|
218
|
+
<MapPin size={18} />
|
|
219
|
+
{t('movethewheels.tracking.routeMap.title', 'Route map')}
|
|
220
|
+
</CardTitle>
|
|
221
|
+
</CardHeader>
|
|
222
|
+
<CardContent>
|
|
223
|
+
<TrackingMap
|
|
224
|
+
className="overflow-hidden rounded-lg border border-border-seam"
|
|
225
|
+
height={320}
|
|
226
|
+
origin={mapModel?.origin ?? null}
|
|
227
|
+
destination={mapModel?.destination ?? null}
|
|
228
|
+
trail={mapModel?.trail ?? []}
|
|
229
|
+
current={mapModel?.current ?? null}
|
|
230
|
+
emptyMessage={t(
|
|
231
|
+
'movethewheels.tracking.routeMap.empty',
|
|
232
|
+
'No location coordinates recorded for this shipment yet'
|
|
233
|
+
)}
|
|
234
|
+
/>
|
|
235
|
+
</CardContent>
|
|
236
|
+
</Card>
|
|
237
|
+
|
|
176
238
|
{/* Progress Timeline */}
|
|
177
239
|
<Card>
|
|
178
240
|
<CardHeader>
|
|
@@ -351,14 +413,22 @@ export default function TrackingPage() {
|
|
|
351
413
|
</CardTitle>
|
|
352
414
|
</CardHeader>
|
|
353
415
|
<CardContent>
|
|
354
|
-
|
|
355
|
-
<
|
|
356
|
-
|
|
416
|
+
{mapModel?.current ? (
|
|
417
|
+
<TrackingMap
|
|
418
|
+
className="mb-4 overflow-hidden rounded-lg border border-border-seam"
|
|
419
|
+
height={180}
|
|
420
|
+
current={mapModel.current}
|
|
421
|
+
destination={mapModel.destination ?? null}
|
|
422
|
+
trail={mapModel.trail}
|
|
423
|
+
/>
|
|
424
|
+
) : (
|
|
425
|
+
<div className="mb-4 rounded-lg bg-bg-sunken p-4 text-center text-text-secondary">
|
|
426
|
+
<MapPin size={28} className="mx-auto mb-2 opacity-60" />
|
|
357
427
|
<p className="text-sm">
|
|
358
|
-
{t('movethewheels.tracking.location.
|
|
428
|
+
{t('movethewheels.tracking.location.noFix', 'No live position fix yet')}
|
|
359
429
|
</p>
|
|
360
430
|
</div>
|
|
361
|
-
|
|
431
|
+
)}
|
|
362
432
|
<p className="text-sm">
|
|
363
433
|
{tracking.currentLocation.address ||
|
|
364
434
|
t('movethewheels.tracking.location.updating', 'Location updating...')}
|
|
@@ -266,7 +266,7 @@ export default function VehicleDetailsPage() {
|
|
|
266
266
|
<CardTitle>Capacity</CardTitle>
|
|
267
267
|
</CardHeader>
|
|
268
268
|
<CardContent>
|
|
269
|
-
<div className="grid grid-cols-3 gap-6">
|
|
269
|
+
<div className="grid grid-cols-1 sm:grid-cols-3 gap-6">
|
|
270
270
|
<div>
|
|
271
271
|
<p className="text-sm text-text-secondary">Weight</p>
|
|
272
272
|
<p className="text-2xl font-bold">
|
|
@@ -3,12 +3,13 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { useState } from 'react';
|
|
6
|
-
import { Truck, Plus, RefreshCw, Download } from 'lucide-react';
|
|
6
|
+
import { Truck, Plus, RefreshCw, Download, CircleCheck, Route as RouteIcon, Wrench } from 'lucide-react';
|
|
7
7
|
import { PageLayout } from '../components/PageLayout';
|
|
8
|
+
import { MetricCard, MetricGrid } from '../components/MetricCard';
|
|
8
9
|
import { DataTable, type Column } from '../components/DataTable';
|
|
9
10
|
import { StatusBadge, VehicleTypeBadge } from '../components/StatusBadge';
|
|
10
11
|
import { Progress } from '../components/ui';
|
|
11
|
-
import { CTAOverflowMenu,
|
|
12
|
+
import { CTAOverflowMenu, PagePurpose } from '@burdenoff/fe-libs/ui';
|
|
12
13
|
import { useVehicles, useFleetStats } from '../hooks/useFleet';
|
|
13
14
|
import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
|
|
14
15
|
import { joinPath } from '../utils/navigation';
|
|
@@ -126,28 +127,37 @@ export default function VehiclesPage() {
|
|
|
126
127
|
an order.
|
|
127
128
|
</PagePurpose>
|
|
128
129
|
|
|
129
|
-
{/* Fleet at a glance —
|
|
130
|
-
<
|
|
131
|
-
<p className="text-sm font-medium text-text-secondary">Fleet at a glance</p>
|
|
132
|
-
<
|
|
133
|
-
<
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
130
|
+
{/* Fleet at a glance — premium stat cards */}
|
|
131
|
+
<div>
|
|
132
|
+
<p className="mb-3 text-sm font-medium text-text-secondary">Fleet at a glance</p>
|
|
133
|
+
<MetricGrid columns={4}>
|
|
134
|
+
<MetricCard
|
|
135
|
+
label="Total vehicles"
|
|
136
|
+
value={totalVehicles}
|
|
137
|
+
icon={<Truck size={20} />}
|
|
138
|
+
accent="brand"
|
|
139
|
+
/>
|
|
140
|
+
<MetricCard
|
|
141
|
+
label="Available now"
|
|
142
|
+
value={stats.vehiclesByStatus.available}
|
|
143
|
+
icon={<CircleCheck size={20} />}
|
|
144
|
+
accent="success"
|
|
145
|
+
subtitle="Ready to dispatch"
|
|
146
|
+
/>
|
|
147
|
+
<MetricCard
|
|
148
|
+
label="On assignment"
|
|
149
|
+
value={stats.vehiclesByStatus.assigned}
|
|
150
|
+
icon={<RouteIcon size={20} />}
|
|
151
|
+
accent="blue"
|
|
152
|
+
/>
|
|
153
|
+
<MetricCard
|
|
154
|
+
label="In maintenance"
|
|
155
|
+
value={stats.vehiclesByStatus.maintenance}
|
|
156
|
+
icon={<Wrench size={20} />}
|
|
157
|
+
accent="warning"
|
|
158
|
+
/>
|
|
159
|
+
</MetricGrid>
|
|
160
|
+
</div>
|
|
151
161
|
|
|
152
162
|
<DataTable
|
|
153
163
|
columns={columns}
|
|
@@ -326,7 +326,7 @@ export default function WarehouseDetailsPage() {
|
|
|
326
326
|
{warehouse.capacity.totalSpace.toLocaleString()} sq ft
|
|
327
327
|
</p>
|
|
328
328
|
</div>
|
|
329
|
-
<div className="grid grid-cols-2 gap-4">
|
|
329
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
|
330
330
|
<div>
|
|
331
331
|
<p className="text-sm text-text-secondary">Pallets Used</p>
|
|
332
332
|
<p className="text-2xl font-bold">
|
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { useState } from 'react';
|
|
6
|
-
import { Warehouse, Plus, RefreshCw } from 'lucide-react';
|
|
6
|
+
import { Warehouse, Plus, RefreshCw, CircleCheck, Gauge } from 'lucide-react';
|
|
7
7
|
import { PageLayout } from '../components/PageLayout';
|
|
8
|
+
import { MetricCard, MetricGrid } from '../components/MetricCard';
|
|
8
9
|
import { DataTable, type Column } from '../components/DataTable';
|
|
9
10
|
import { Badge, Progress } from '../components/ui';
|
|
10
|
-
import { CTAOverflowMenu,
|
|
11
|
+
import { CTAOverflowMenu, PagePurpose } from '@burdenoff/fe-libs/ui';
|
|
11
12
|
import { useWarehouses, useWarehouseStats } from '../hooks/useInventory';
|
|
12
13
|
import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
|
|
13
14
|
import { joinPath } from '../utils/navigation';
|
|
@@ -121,26 +122,31 @@ export default function WarehousesPage() {
|
|
|
121
122
|
to balance stock, spot capacity crunches, and decide where to fulfil an order from.
|
|
122
123
|
</PagePurpose>
|
|
123
124
|
|
|
124
|
-
{/* Network at a glance —
|
|
125
|
-
<
|
|
126
|
-
<p className="text-sm font-medium text-text-secondary">Network at a glance</p>
|
|
127
|
-
<
|
|
128
|
-
<
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
<
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
125
|
+
{/* Network at a glance — premium stat cards */}
|
|
126
|
+
<div>
|
|
127
|
+
<p className="mb-3 text-sm font-medium text-text-secondary">Network at a glance</p>
|
|
128
|
+
<MetricGrid columns={3}>
|
|
129
|
+
<MetricCard
|
|
130
|
+
label="Total sites"
|
|
131
|
+
value={stats.total}
|
|
132
|
+
icon={<Warehouse size={20} />}
|
|
133
|
+
accent="brand"
|
|
134
|
+
/>
|
|
135
|
+
<MetricCard
|
|
136
|
+
label="Active"
|
|
137
|
+
value={stats.active}
|
|
138
|
+
icon={<CircleCheck size={20} />}
|
|
139
|
+
accent="success"
|
|
140
|
+
subtitle="Fulfilling orders"
|
|
141
|
+
/>
|
|
142
|
+
<MetricCard
|
|
143
|
+
label="Avg utilization"
|
|
144
|
+
value={stats.total > 0 ? `${Math.round(stats.avgUtilization * 100)}%` : '—'}
|
|
145
|
+
icon={<Gauge size={20} />}
|
|
146
|
+
accent="blue"
|
|
147
|
+
/>
|
|
148
|
+
</MetricGrid>
|
|
149
|
+
</div>
|
|
144
150
|
|
|
145
151
|
<DataTable
|
|
146
152
|
columns={columns}
|
|
@@ -691,7 +691,7 @@ export interface Analytics {
|
|
|
691
691
|
customerSatisfaction: number;
|
|
692
692
|
};
|
|
693
693
|
trends: {
|
|
694
|
-
dailyOrders: Array<{ date: string; orders: number }>;
|
|
694
|
+
dailyOrders: Array<{ date: string; orders: number; deliveries: number }>;
|
|
695
695
|
revenue: Array<{ month: string; revenue: number }>;
|
|
696
696
|
};
|
|
697
697
|
fleet: {
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helpers for turning domain records into map markers. Pure functions —
|
|
3
|
+
* no Leaflet import, so they can be reused by any page without pulling map CSS.
|
|
4
|
+
*/
|
|
5
|
+
import type { MapMarkerStatus } from '../components/maps';
|
|
6
|
+
import type { Address, Location } from '../types';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Map an order/shipment status onto the marker's visual status (drives pin color
|
|
10
|
+
* and glyph). Anything unrecognised falls back to a neutral pin.
|
|
11
|
+
*/
|
|
12
|
+
export function deliveryMarkerStatus(status: string | undefined): MapMarkerStatus {
|
|
13
|
+
switch ((status ?? '').toLowerCase()) {
|
|
14
|
+
case 'in_transit':
|
|
15
|
+
return 'in_transit';
|
|
16
|
+
case 'out_for_delivery':
|
|
17
|
+
return 'active';
|
|
18
|
+
case 'delivered':
|
|
19
|
+
return 'delivered';
|
|
20
|
+
case 'failed':
|
|
21
|
+
case 'delayed':
|
|
22
|
+
case 'cancelled':
|
|
23
|
+
return 'delayed';
|
|
24
|
+
case 'assigned':
|
|
25
|
+
case 'picked_up':
|
|
26
|
+
case 'confirmed':
|
|
27
|
+
return 'active';
|
|
28
|
+
default:
|
|
29
|
+
return 'default';
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* First location among the candidates that carries a real (non-null-island)
|
|
35
|
+
* coordinate, returned as a plain `{ lat, lng }`. Returns null when none do.
|
|
36
|
+
* Kept coordinate-shape-agnostic so callers pass whichever object they have.
|
|
37
|
+
*/
|
|
38
|
+
export function firstRealLatLng(
|
|
39
|
+
...candidates: Array<Location | Address | null | undefined>
|
|
40
|
+
): { lat: number; lng: number } | null {
|
|
41
|
+
for (const candidate of candidates) {
|
|
42
|
+
if (!candidate) continue;
|
|
43
|
+
const lat = candidate.latitude;
|
|
44
|
+
const lng = candidate.longitude;
|
|
45
|
+
if (
|
|
46
|
+
typeof lat === 'number' &&
|
|
47
|
+
typeof lng === 'number' &&
|
|
48
|
+
Number.isFinite(lat) &&
|
|
49
|
+
Number.isFinite(lng) &&
|
|
50
|
+
!(lat === 0 && lng === 0) &&
|
|
51
|
+
Math.abs(lat) <= 90 &&
|
|
52
|
+
Math.abs(lng) <= 180
|
|
53
|
+
) {
|
|
54
|
+
return { lat, lng };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
import { M as e, d as t, f as n, g as r, h as i, i as a, u as o } from "./PageLayout-D4LDHJJq.js";
|
|
2
|
-
import { t as s } from "./chart-column-ouzgxIv_.js";
|
|
3
|
-
import { t as c } from "./dollar-sign-CAVU95SO.js";
|
|
4
|
-
import { t as l } from "./package-CcRttV2N.js";
|
|
5
|
-
import { t as u } from "./trending-up-Dz6PqPRj.js";
|
|
6
|
-
import { t as d } from "./truck-D-y2IDzv.js";
|
|
7
|
-
import { t as f } from "./users-CCrq-w0C.js";
|
|
8
|
-
import { a as p, i as m, t as h } from "./useAnalytics-D7HW2XRb.js";
|
|
9
|
-
import { n as g, r as _, t as v } from "./MetricCard-CVBVpu7o.js";
|
|
10
|
-
import { n as y } from "./formatters-BQr-aR5W.js";
|
|
11
|
-
import { jsx as b, jsxs as x } from "react/jsx-runtime";
|
|
12
|
-
import { EmphasisPanel as S, PagePurpose as C } from "@burdenoff/fe-libs/ui";
|
|
13
|
-
//#region src/movethewheels/pages/AnalyticsPage.tsx
|
|
14
|
-
function w() {
|
|
15
|
-
let { analytics: w, isLoading: T, error: E, refetch: D } = h(), { orderData: O } = p(), { fleetData: k } = m();
|
|
16
|
-
return /* @__PURE__ */ x(a, {
|
|
17
|
-
title: "Analytics",
|
|
18
|
-
subtitle: "Business performance and insights",
|
|
19
|
-
icon: /* @__PURE__ */ b(s, { size: 24 }),
|
|
20
|
-
children: [
|
|
21
|
-
/* @__PURE__ */ b(C, {
|
|
22
|
-
icon: /* @__PURE__ */ b(s, { size: 18 }),
|
|
23
|
-
children: "See how the operation is really performing. Track order volume, revenue, on-time rate and customer satisfaction over time so you can spot trends, find bottlenecks, and decide where to invest — whether that's more vehicles, better routes, or new customers."
|
|
24
|
-
}),
|
|
25
|
-
E && !T ? /* @__PURE__ */ b(t, {
|
|
26
|
-
className: "border-status-error-border",
|
|
27
|
-
children: /* @__PURE__ */ x(n, {
|
|
28
|
-
className: "p-6 text-center space-y-3",
|
|
29
|
-
children: [
|
|
30
|
-
/* @__PURE__ */ b("p", {
|
|
31
|
-
className: "text-status-error-text font-medium",
|
|
32
|
-
children: "Couldn't load analytics"
|
|
33
|
-
}),
|
|
34
|
-
/* @__PURE__ */ b("p", {
|
|
35
|
-
className: "text-sm text-text-secondary",
|
|
36
|
-
children: E.message
|
|
37
|
-
}),
|
|
38
|
-
/* @__PURE__ */ b(o, {
|
|
39
|
-
variant: "outline",
|
|
40
|
-
onClick: D,
|
|
41
|
-
children: "Retry"
|
|
42
|
-
})
|
|
43
|
-
]
|
|
44
|
-
})
|
|
45
|
-
}) : T ? /* @__PURE__ */ b(_, {
|
|
46
|
-
columns: 5,
|
|
47
|
-
children: Array.from({ length: 5 }).map((e, t) => /* @__PURE__ */ b(g, {}, t))
|
|
48
|
-
}) : /* @__PURE__ */ x(_, {
|
|
49
|
-
columns: 5,
|
|
50
|
-
children: [
|
|
51
|
-
/* @__PURE__ */ b(v, {
|
|
52
|
-
label: "Total Orders",
|
|
53
|
-
value: w.overview.totalOrders.toLocaleString(),
|
|
54
|
-
icon: /* @__PURE__ */ b(l, { size: 20 })
|
|
55
|
-
}),
|
|
56
|
-
/* @__PURE__ */ b(v, {
|
|
57
|
-
treatment: "glass",
|
|
58
|
-
glow: !0,
|
|
59
|
-
label: "Revenue",
|
|
60
|
-
value: y(w.overview.totalRevenue),
|
|
61
|
-
icon: /* @__PURE__ */ b(c, { size: 20 })
|
|
62
|
-
}),
|
|
63
|
-
/* @__PURE__ */ b(v, {
|
|
64
|
-
label: "Active Vehicles",
|
|
65
|
-
value: w.overview.activeVehicles,
|
|
66
|
-
icon: /* @__PURE__ */ b(d, { size: 20 })
|
|
67
|
-
}),
|
|
68
|
-
/* @__PURE__ */ b(v, {
|
|
69
|
-
label: "On-Time Rate",
|
|
70
|
-
value: `${w.overview.onTimeDeliveryRate.toFixed(1)}%`,
|
|
71
|
-
icon: /* @__PURE__ */ b(u, { size: 20 }),
|
|
72
|
-
changeType: w.overview.onTimeDeliveryRate >= 90 ? "positive" : "neutral"
|
|
73
|
-
}),
|
|
74
|
-
/* @__PURE__ */ b(v, {
|
|
75
|
-
label: "Satisfaction",
|
|
76
|
-
value: w.overview.customerSatisfaction.toFixed(1),
|
|
77
|
-
icon: /* @__PURE__ */ b(f, { size: 20 }),
|
|
78
|
-
subtitle: "out of 5"
|
|
79
|
-
})
|
|
80
|
-
]
|
|
81
|
-
}),
|
|
82
|
-
/* @__PURE__ */ x("div", {
|
|
83
|
-
className: "grid grid-cols-1 lg:grid-cols-2 gap-6",
|
|
84
|
-
children: [/* @__PURE__ */ x(t, { children: [/* @__PURE__ */ b(i, { children: /* @__PURE__ */ b(r, { children: "Order Trends" }) }), /* @__PURE__ */ x(n, { children: [/* @__PURE__ */ b("div", {
|
|
85
|
-
className: "h-64 flex items-center justify-center bg-bg-sunken/50 rounded-lg",
|
|
86
|
-
children: /* @__PURE__ */ x("div", {
|
|
87
|
-
className: "text-center text-text-secondary",
|
|
88
|
-
children: [
|
|
89
|
-
/* @__PURE__ */ b(s, {
|
|
90
|
-
size: 48,
|
|
91
|
-
className: "mx-auto mb-2 opacity-50"
|
|
92
|
-
}),
|
|
93
|
-
/* @__PURE__ */ b("p", { children: "Order trend chart" }),
|
|
94
|
-
/* @__PURE__ */ b("p", {
|
|
95
|
-
className: "text-xs",
|
|
96
|
-
children: "(Chart library integration needed)"
|
|
97
|
-
})
|
|
98
|
-
]
|
|
99
|
-
})
|
|
100
|
-
}), /* @__PURE__ */ b("div", {
|
|
101
|
-
className: "mt-4 grid grid-cols-2 sm:grid-cols-4 gap-2",
|
|
102
|
-
children: O.trends.slice(-4).map((e) => /* @__PURE__ */ x("div", {
|
|
103
|
-
className: "text-center p-2 bg-bg-sunken/50 rounded",
|
|
104
|
-
children: [/* @__PURE__ */ b("p", {
|
|
105
|
-
className: "text-xs text-text-secondary",
|
|
106
|
-
children: e.date.split("-").slice(1).join("/")
|
|
107
|
-
}), /* @__PURE__ */ b("p", {
|
|
108
|
-
className: "font-medium",
|
|
109
|
-
children: e.orders
|
|
110
|
-
})]
|
|
111
|
-
}, e.date))
|
|
112
|
-
})] })] }), /* @__PURE__ */ x(t, { children: [/* @__PURE__ */ b(i, { children: /* @__PURE__ */ b(r, { children: "Revenue Trends" }) }), /* @__PURE__ */ x(n, { children: [/* @__PURE__ */ b("div", {
|
|
113
|
-
className: "h-64 flex items-center justify-center bg-bg-sunken/50 rounded-lg",
|
|
114
|
-
children: /* @__PURE__ */ x("div", {
|
|
115
|
-
className: "text-center text-text-secondary",
|
|
116
|
-
children: [/* @__PURE__ */ b(u, {
|
|
117
|
-
size: 48,
|
|
118
|
-
className: "mx-auto mb-2 opacity-50"
|
|
119
|
-
}), /* @__PURE__ */ b("p", { children: "Revenue trend chart" })]
|
|
120
|
-
})
|
|
121
|
-
}), /* @__PURE__ */ b("div", {
|
|
122
|
-
className: "mt-4 grid grid-cols-2 sm:grid-cols-5 gap-2",
|
|
123
|
-
children: w.trends.revenue.map((e) => /* @__PURE__ */ x("div", {
|
|
124
|
-
className: "text-center p-2 bg-bg-sunken/50 rounded",
|
|
125
|
-
children: [/* @__PURE__ */ b("p", {
|
|
126
|
-
className: "text-xs text-text-secondary",
|
|
127
|
-
children: e.month
|
|
128
|
-
}), /* @__PURE__ */ x("p", {
|
|
129
|
-
className: "font-medium text-sm",
|
|
130
|
-
children: [y(e.revenue / 1e3), "k"]
|
|
131
|
-
})]
|
|
132
|
-
}, e.month))
|
|
133
|
-
})] })] })]
|
|
134
|
-
}),
|
|
135
|
-
/* @__PURE__ */ x("div", {
|
|
136
|
-
className: "grid grid-cols-1 lg:grid-cols-3 gap-6",
|
|
137
|
-
children: [
|
|
138
|
-
/* @__PURE__ */ x(t, { children: [/* @__PURE__ */ b(i, { children: /* @__PURE__ */ b(r, { children: "Orders by Status" }) }), /* @__PURE__ */ b(n, {
|
|
139
|
-
className: "space-y-3",
|
|
140
|
-
children: O.byStatus.filter((e) => e.count > 0).map(({ status: t, count: n }) => /* @__PURE__ */ x("div", {
|
|
141
|
-
className: "flex items-center justify-between",
|
|
142
|
-
children: [/* @__PURE__ */ b("span", {
|
|
143
|
-
className: "text-sm capitalize",
|
|
144
|
-
children: t.replace("_", " ")
|
|
145
|
-
}), /* @__PURE__ */ x("div", {
|
|
146
|
-
className: "flex items-center gap-2",
|
|
147
|
-
children: [/* @__PURE__ */ b(e, {
|
|
148
|
-
value: n / w.overview.totalOrders * 100,
|
|
149
|
-
className: "w-20 h-2"
|
|
150
|
-
}), /* @__PURE__ */ b("span", {
|
|
151
|
-
className: "font-medium w-8 text-right",
|
|
152
|
-
children: n
|
|
153
|
-
})]
|
|
154
|
-
})]
|
|
155
|
-
}, t))
|
|
156
|
-
})] }),
|
|
157
|
-
/* @__PURE__ */ x(t, { children: [/* @__PURE__ */ b(i, { children: /* @__PURE__ */ b(r, { children: "Fleet Performance" }) }), /* @__PURE__ */ x(n, {
|
|
158
|
-
className: "space-y-4",
|
|
159
|
-
children: [/* @__PURE__ */ x("div", { children: [/* @__PURE__ */ x("div", {
|
|
160
|
-
className: "flex justify-between text-sm mb-1",
|
|
161
|
-
children: [/* @__PURE__ */ b("span", { children: "Vehicle Utilization" }), /* @__PURE__ */ x("span", { children: [k.vehicleUtilization.toFixed(0), "%"] })]
|
|
162
|
-
}), /* @__PURE__ */ b(e, { value: k.vehicleUtilization })] }), k.vehiclesByType.length > 0 && /* @__PURE__ */ x("div", {
|
|
163
|
-
className: "pt-2",
|
|
164
|
-
children: [/* @__PURE__ */ b("p", {
|
|
165
|
-
className: "text-sm text-text-secondary mb-2",
|
|
166
|
-
children: "Vehicles by Type"
|
|
167
|
-
}), k.vehiclesByType.map(({ type: e, count: t }) => /* @__PURE__ */ x("div", {
|
|
168
|
-
className: "flex justify-between text-sm py-1",
|
|
169
|
-
children: [/* @__PURE__ */ b("span", {
|
|
170
|
-
className: "capitalize",
|
|
171
|
-
children: e
|
|
172
|
-
}), /* @__PURE__ */ b("span", {
|
|
173
|
-
className: "font-medium",
|
|
174
|
-
children: t
|
|
175
|
-
})]
|
|
176
|
-
}, e))]
|
|
177
|
-
})]
|
|
178
|
-
})] }),
|
|
179
|
-
/* @__PURE__ */ x(S, {
|
|
180
|
-
className: "space-y-4",
|
|
181
|
-
children: [
|
|
182
|
-
/* @__PURE__ */ b("h2", {
|
|
183
|
-
className: "text-base font-semibold text-text-primary",
|
|
184
|
-
children: "Performance Metrics"
|
|
185
|
-
}),
|
|
186
|
-
/* @__PURE__ */ x("div", { children: [/* @__PURE__ */ b("p", {
|
|
187
|
-
className: "text-sm text-text-secondary",
|
|
188
|
-
children: "On-Time Delivery"
|
|
189
|
-
}), /* @__PURE__ */ x("p", {
|
|
190
|
-
className: "text-2xl font-bold",
|
|
191
|
-
children: [w.performance.onTimeDelivery.toFixed(1), "%"]
|
|
192
|
-
})] }),
|
|
193
|
-
/* @__PURE__ */ x("div", { children: [/* @__PURE__ */ b("p", {
|
|
194
|
-
className: "text-sm text-text-secondary",
|
|
195
|
-
children: "Customer Satisfaction"
|
|
196
|
-
}), /* @__PURE__ */ x("p", {
|
|
197
|
-
className: "text-2xl font-bold",
|
|
198
|
-
children: [w.overview.customerSatisfaction.toFixed(1), "/5"]
|
|
199
|
-
})] }),
|
|
200
|
-
/* @__PURE__ */ x("div", { children: [/* @__PURE__ */ b("p", {
|
|
201
|
-
className: "text-sm text-text-secondary",
|
|
202
|
-
children: "Driver Ratings"
|
|
203
|
-
}), /* @__PURE__ */ x("p", {
|
|
204
|
-
className: "text-2xl font-bold",
|
|
205
|
-
children: [w.performance.driverRatings.toFixed(1), "/5"]
|
|
206
|
-
})] })
|
|
207
|
-
]
|
|
208
|
-
})
|
|
209
|
-
]
|
|
210
|
-
})
|
|
211
|
-
]
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
//#endregion
|
|
215
|
-
export { w as default };
|