@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.
Files changed (149) hide show
  1. package/dist/{AIAssistantPage-3YOnHO9Y.js → AIAssistantPage-D2gaEL1U.js} +16 -15
  2. package/dist/AnalyticsPage-DZMlEoBi.js +194 -0
  3. package/dist/{CreateOrderPage-DRAgGo9i.js → CreateOrderPage-CWHx8yys.js} +40 -40
  4. package/dist/{CustomerDetailsPage-BCeMmOFe.js → CustomerDetailsPage-nR4oZ_ju.js} +71 -71
  5. package/dist/CustomersPage-Bbvwvn8k.js +205 -0
  6. package/dist/DashboardPage-DrEhNf7N.js +542 -0
  7. package/dist/{DataTable-XvUcTjwi.js → DataTable-CqMUD4er.js} +31 -31
  8. package/dist/{DocumentsCard-U2lSe-cn.js → DocumentsCard-edDkgnJj.js} +25 -25
  9. package/dist/{DriverDetailsPage-BQrYd_Lp.js → DriverDetailsPage-whEMOWs9.js} +64 -64
  10. package/dist/DriversPage-CHF2ZEzd.js +221 -0
  11. package/dist/FinancePage-Dn9GrW-1.js +175 -0
  12. package/dist/{FleetPage-3ZHsB-TN.js → FleetPage-CrtGPy-p.js} +22 -22
  13. package/dist/{FormError-cuLV2jVD.js → FormError-BKcb_wT-.js} +1 -1
  14. package/dist/{ImportExportPage-C6f7DqhM.js → ImportExportPage-WYdwZomZ.js} +12 -12
  15. package/dist/InventoryPage-l5A1f753.js +259 -0
  16. package/dist/LiveTrackingPage-DhEaNKZ4.js +429 -0
  17. package/dist/{MarketplacePage-Bh_2ns4v.js → MarketplacePage-B9RYOb1Y.js} +116 -116
  18. package/dist/MetricCard-y3bWHKUQ.js +191 -0
  19. package/dist/{Notice-CSoDYKw9.js → Notice-DOO-tjpV.js} +2 -2
  20. package/dist/{OrderDetailsPage-zCj0lW0j.js → OrderDetailsPage-CwYHwk1K.js} +58 -58
  21. package/dist/OrdersListPage-DhZJoQeI.js +386 -0
  22. package/dist/{PageLayout-D4LDHJJq.js → PageLayout-BniRJaQ6.js} +1 -1
  23. package/dist/{ProductDetailsPage-wsxAQkA0.js → ProductDetailsPage-1Gm9JKy0.js} +55 -55
  24. package/dist/ProductsPage-C8sn-1tN.js +186 -0
  25. package/dist/ReportsPage-D1tb0dcz.js +377 -0
  26. package/dist/{RouteDetailsPage-DyMJE9AS.js → RouteDetailsPage-CkMyR9b_.js} +208 -181
  27. package/dist/{RoutesPage-Dtc669yc.js → RoutesPage-QkENzsE5.js} +12 -12
  28. package/dist/{SettingsPage-CpH9xhQg.js → SettingsPage-DV3_tU_X.js} +15 -15
  29. package/dist/Sparkline-K2RMJ58u.js +16729 -0
  30. package/dist/{StatusBadge-C87mDpDP.js → StatusBadge-BNHXYvZx.js} +7 -7
  31. package/dist/TrackingPage-BZrg4RCu.js +368 -0
  32. package/dist/{VehicleDetailsPage-DldYNsYV.js → VehicleDetailsPage-D-8XPZ2G.js} +53 -53
  33. package/dist/VehiclesPage-D5BELbtM.js +184 -0
  34. package/dist/{WarehouseDetailsPage-CMG7or6G.js → WarehouseDetailsPage-Cmg-8Frn.js} +64 -64
  35. package/dist/WarehousesPage-C1Y1M867.js +182 -0
  36. package/dist/{arrow-left-n48Y-Wis.js → arrow-left-ClW4Isj7.js} +1 -1
  37. package/dist/{arrow-right-Q3XQpTPR.js → arrow-right-CmKOmIkh.js} +1 -1
  38. package/dist/{arrow-up-xNF1zJUG.js → arrow-up-BJijRLAZ.js} +1 -1
  39. package/dist/{box-Eu05PXDh.js → box-BT3NsRQP.js} +1 -1
  40. package/dist/boxes-B_OGvQiW.js +54 -0
  41. package/dist/{chart-column-ouzgxIv_.js → chart-column-DQTTdUPk.js} +1 -1
  42. package/dist/charts-Tj_PPTfA.js +2018 -0
  43. package/dist/{chevron-left-B2_r0Elx.js → chevron-left-B1rZyWWb.js} +1 -1
  44. package/dist/{chevron-right-eV4LjaSi.js → chevron-right-BIcwUJZq.js} +1 -1
  45. package/dist/chunk-BdsCSvBP.js +20 -0
  46. package/dist/{circle-alert-Bzh2DBSF.js → circle-alert-CNkuDNc6.js} +1 -1
  47. package/dist/circle-check-Cs1OVjtV.js +13 -0
  48. package/dist/{circle-check-big-CGMSpQmD.js → circle-check-big-C-p4eNwn.js} +1 -1
  49. package/dist/{clock-Bl3ptexL.js → clock-dRGG4rkF.js} +1 -1
  50. package/dist/{dollar-sign-CAVU95SO.js → dollar-sign-B6RoYHHz.js} +1 -1
  51. package/dist/{download-C5NbvVDc.js → download-DP04iWTd.js} +1 -1
  52. package/dist/{file-text-D-1uvLZG.js → file-text-BuDx4hZp.js} +1 -1
  53. package/dist/{filter-DUfUg6BO.js → filter-BD483_8g.js} +1 -1
  54. package/dist/{fuel-B6_lnKDs.js → fuel-DLH1ag04.js} +1 -1
  55. package/dist/index.css +2 -0
  56. package/dist/index.js +214 -225
  57. package/dist/{map-C-M-FQLR.js → map-DNQn9fyY.js} +1 -1
  58. package/dist/{map-pin-Dznl5l40.js → map-pin-CqN1bbT1.js} +1 -1
  59. package/dist/mapMarkers-NCV9MRKR.js +28 -0
  60. package/dist/maps-yzLqiK_C.js +5070 -0
  61. package/dist/{navigation-BJSJl-VG.js → navigation-CgGUt2tR.js} +1 -1
  62. package/dist/{package-CcRttV2N.js → package-DmRF7iQD.js} +1 -1
  63. package/dist/{phone-DVSZHt_j.js → phone-Cl32EXKW.js} +1 -1
  64. package/dist/{plus-C_XZDwBz.js → plus-DlANgOFx.js} +1 -1
  65. package/dist/{refresh-cw-CfgN67l3.js → refresh-cw-rW59D4b7.js} +1 -1
  66. package/dist/route-CWjPrtAN.js +22 -0
  67. package/dist/{save-DJE8Fpdv.js → save-UDlog7hg.js} +1 -1
  68. package/dist/{search-D_VyGcZT.js → search-DNIG5Ck7.js} +1 -1
  69. package/dist/{square-pen-D-6JYrVQ.js → square-pen-BcfB3Grd.js} +1 -1
  70. package/dist/{star-BpRlUZuU.js → star-CVZHFwDx.js} +1 -1
  71. package/dist/{store-B-8HMuyZ.js → store-bwnmCK9f.js} +1 -1
  72. package/dist/{trash-2-8vmv-XV_.js → trash-2-B80G12d4.js} +1 -1
  73. package/dist/{trending-up-Dz6PqPRj.js → trending-up-CH4R4KOa.js} +1 -1
  74. package/dist/{triangle-alert-D7Y3J_4z.js → triangle-alert-CmS8AUU6.js} +1 -1
  75. package/dist/{truck-D-y2IDzv.js → truck-9rLDncav.js} +1 -1
  76. package/dist/{upload-CHZofOqb.js → upload-DanJY1Vo.js} +1 -1
  77. package/dist/{useAssistant-DVW4QgQb.js → useAssistant-CnEwkTLm.js} +11 -28
  78. package/dist/{useCustomers-BPW4__PR.js → useCustomers-ChJao1ET.js} +1 -1
  79. package/dist/{useFleet-ChBkxAd6.js → useFleet-g5Yri0G8.js} +1 -1
  80. package/dist/{useImportExport-DrPDHEDK.js → useImportExport-MYwIT3rE.js} +1 -1
  81. package/dist/{useInventory-CAAVMRp6.js → useInventory-BSOn-R1c.js} +1 -1
  82. package/dist/{useOrders-B465S2nt.js → useOrders-BSiqo61w.js} +1 -1
  83. package/dist/{useReports-jQ3GpNOm.js → useReports-fWESfsF9.js} +1 -1
  84. package/dist/{useRoutes-hS3d5-xr.js → useRoutes-zD8Yp323.js} +1 -1
  85. package/dist/{useSettings-BB8X2RLh.js → useSettings-B1lsWeZ1.js} +6 -6
  86. package/dist/{useTracking-RjGz9PZG.js → useTracking-BKG0I2ln.js} +3 -3
  87. package/dist/{user-DzfTn4dE.js → user-DU51el3m.js} +1 -1
  88. package/dist/{user-check-BEKRCi7v.js → user-check-IMhk-qkX.js} +1 -1
  89. package/dist/{users-CCrq-w0C.js → users-B8KhJBGg.js} +1 -1
  90. package/dist/{warehouse-CaqfmMKh.js → warehouse-CcMMioeY.js} +1 -1
  91. package/dist/{wrench-q_rgJfaP.js → wrench-Dg-sgH2e.js} +1 -1
  92. package/dist/{x-tMeDMcCw.js → x-C4ObLVEa.js} +1 -1
  93. package/package.json +5 -1
  94. package/src/movethewheels/components/GlassPanel.tsx +71 -0
  95. package/src/movethewheels/components/MetricCard.tsx +171 -73
  96. package/src/movethewheels/components/StatusBadge.tsx +7 -6
  97. package/src/movethewheels/components/charts/ChartShell.tsx +78 -0
  98. package/src/movethewheels/components/charts/Sparkline.tsx +85 -0
  99. package/src/movethewheels/components/charts/StatusDonut.tsx +115 -0
  100. package/src/movethewheels/components/charts/TrendChart.tsx +179 -0
  101. package/src/movethewheels/components/charts/VolumeBar.tsx +97 -0
  102. package/src/movethewheels/components/charts/chartTokens.ts +43 -0
  103. package/src/movethewheels/components/charts/index.ts +9 -0
  104. package/src/movethewheels/components/index.ts +44 -1
  105. package/src/movethewheels/components/maps/LogisticsMap.tsx +94 -0
  106. package/src/movethewheels/components/maps/MapCanvas.tsx +118 -0
  107. package/src/movethewheels/components/maps/RouteMap.tsx +71 -0
  108. package/src/movethewheels/components/maps/TrackingMap.tsx +102 -0
  109. package/src/movethewheels/components/maps/index.ts +16 -0
  110. package/src/movethewheels/components/maps/leaflet-setup.ts +124 -0
  111. package/src/movethewheels/components/maps/map-markers.css +121 -0
  112. package/src/movethewheels/hooks/useAnalytics.ts +14 -4
  113. package/src/movethewheels/pages/AnalyticsPage.tsx +93 -73
  114. package/src/movethewheels/pages/CreateOrderPage.tsx +4 -4
  115. package/src/movethewheels/pages/CustomersPage.tsx +29 -20
  116. package/src/movethewheels/pages/DashboardPage.tsx +238 -12
  117. package/src/movethewheels/pages/DriverDetailsPage.tsx +1 -1
  118. package/src/movethewheels/pages/DriversPage.tsx +35 -24
  119. package/src/movethewheels/pages/FinancePage.tsx +68 -30
  120. package/src/movethewheels/pages/InventoryPage.tsx +15 -3
  121. package/src/movethewheels/pages/LiveTrackingPage.tsx +124 -16
  122. package/src/movethewheels/pages/OrdersListPage.tsx +46 -38
  123. package/src/movethewheels/pages/ProductsPage.tsx +28 -24
  124. package/src/movethewheels/pages/ReportsPage.tsx +23 -0
  125. package/src/movethewheels/pages/RouteDetailsPage.tsx +46 -7
  126. package/src/movethewheels/pages/SettingsPage.tsx +2 -2
  127. package/src/movethewheels/pages/TrackingPage.tsx +76 -6
  128. package/src/movethewheels/pages/VehicleDetailsPage.tsx +1 -1
  129. package/src/movethewheels/pages/VehiclesPage.tsx +34 -24
  130. package/src/movethewheels/pages/WarehouseDetailsPage.tsx +1 -1
  131. package/src/movethewheels/pages/WarehousesPage.tsx +28 -22
  132. package/src/movethewheels/types/index.ts +1 -1
  133. package/src/movethewheels/utils/mapMarkers.ts +58 -0
  134. package/dist/AnalyticsPage-CMpI1070.js +0 -215
  135. package/dist/CustomersPage-CaNBMXmM.js +0 -172
  136. package/dist/DashboardPage-DIKlXde3.js +0 -387
  137. package/dist/DriversPage-PYmdFiby.js +0 -176
  138. package/dist/FinancePage-Cwh9Bw4M.js +0 -136
  139. package/dist/InventoryPage-DpiqVu1v.js +0 -253
  140. package/dist/LiveTrackingPage-FAzzlVnX.js +0 -379
  141. package/dist/MetricCard-CVBVpu7o.js +0 -146
  142. package/dist/OrdersListPage-CAMsuUck.js +0 -384
  143. package/dist/ProductsPage-DC5qDLfz.js +0 -180
  144. package/dist/ReportsPage-BwUJ37eO.js +0 -356
  145. package/dist/TrackingPage-DaNEFG_h.js +0 -325
  146. package/dist/VehiclesPage-BFw13iMt.js +0 -176
  147. package/dist/WarehousesPage-Wkytnt86.js +0 -173
  148. package/dist/useAnalytics-D7HW2XRb.js +0 -347
  149. /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
- <div className="aspect-video bg-bg-sunken rounded-lg flex items-center justify-center mb-4">
355
- <div className="text-center text-text-secondary">
356
- <MapPin size={32} className="mx-auto mb-2" />
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.mapView', 'Map View')}
428
+ {t('movethewheels.tracking.location.noFix', 'No live position fix yet')}
359
429
  </p>
360
430
  </div>
361
- </div>
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, EmphasisPanel, PagePurpose } from '@burdenoff/fe-libs/ui';
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 — the page's single emphasis zone */}
130
- <EmphasisPanel>
131
- <p className="text-sm font-medium text-text-secondary">Fleet at a glance</p>
132
- <div className="mt-3 grid grid-cols-3 gap-4">
133
- <div>
134
- <p className="text-2xl font-bold text-text-primary tabular-nums">{totalVehicles}</p>
135
- <p className="text-xs text-text-secondary">Total vehicles</p>
136
- </div>
137
- <div>
138
- <p className="text-2xl font-bold text-text-primary tabular-nums">
139
- {stats.vehiclesByStatus.available}
140
- </p>
141
- <p className="text-xs text-text-secondary">Available now</p>
142
- </div>
143
- <div>
144
- <p className="text-2xl font-bold text-text-primary tabular-nums">
145
- {stats.vehiclesByStatus.maintenance}
146
- </p>
147
- <p className="text-xs text-text-secondary">In maintenance</p>
148
- </div>
149
- </div>
150
- </EmphasisPanel>
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, EmphasisPanel, PagePurpose } from '@burdenoff/fe-libs/ui';
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 — the page's single emphasis zone */}
125
- <EmphasisPanel>
126
- <p className="text-sm font-medium text-text-secondary">Network at a glance</p>
127
- <div className="mt-3 grid grid-cols-3 gap-4">
128
- <div>
129
- <p className="text-2xl font-bold text-text-primary tabular-nums">{stats.total}</p>
130
- <p className="text-xs text-text-secondary">Total sites</p>
131
- </div>
132
- <div>
133
- <p className="text-2xl font-bold text-text-primary tabular-nums">{stats.active}</p>
134
- <p className="text-xs text-text-secondary">Active</p>
135
- </div>
136
- <div>
137
- <p className="text-2xl font-bold text-text-primary tabular-nums">
138
- {stats.total > 0 ? `${Math.round(stats.avgUtilization * 100)}%` : '—'}
139
- </p>
140
- <p className="text-xs text-text-secondary">Avg utilization</p>
141
- </div>
142
- </div>
143
- </EmphasisPanel>
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 };