@burdenoff/microfe-movethewheels 2026.703.4 → 2026.703.6

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 (150) hide show
  1. package/dist/{AIAssistantPage-3YOnHO9Y.js → AIAssistantPage-BmILdHgO.js} +16 -15
  2. package/dist/AnalyticsPage-BSlsiSDE.js +194 -0
  3. package/dist/{CreateOrderPage-DRAgGo9i.js → CreateOrderPage-DaCgVaO3.js} +40 -40
  4. package/dist/{CustomerDetailsPage-BCeMmOFe.js → CustomerDetailsPage-DErpbafW.js} +71 -71
  5. package/dist/CustomersPage-C1lRU6xs.js +205 -0
  6. package/dist/DashboardPage-Q5-5EKAU.js +542 -0
  7. package/dist/{DataTable-XvUcTjwi.js → DataTable-KjzbwnRL.js} +31 -31
  8. package/dist/{DocumentsCard-U2lSe-cn.js → DocumentsCard-T9y6cv4-.js} +25 -25
  9. package/dist/{DriverDetailsPage-BQrYd_Lp.js → DriverDetailsPage-DnHGkDC7.js} +64 -64
  10. package/dist/DriversPage-C0ujWVOn.js +221 -0
  11. package/dist/FinancePage-CA2XEgsQ.js +175 -0
  12. package/dist/{FleetPage-3ZHsB-TN.js → FleetPage-DrvXW7wK.js} +22 -22
  13. package/dist/{FormError-cuLV2jVD.js → FormError-CRsMP7TQ.js} +1 -1
  14. package/dist/{ImportExportPage-C6f7DqhM.js → ImportExportPage-Cac9HjFx.js} +12 -12
  15. package/dist/InventoryPage-Ezf4gqKI.js +259 -0
  16. package/dist/LiveTrackingPage-CzbVwDqK.js +429 -0
  17. package/dist/{MarketplacePage-Bh_2ns4v.js → MarketplacePage-BHDmSQmo.js} +116 -116
  18. package/dist/MetricCard-DQlDNqmK.js +191 -0
  19. package/dist/{Notice-CSoDYKw9.js → Notice-GmA7JDR3.js} +2 -2
  20. package/dist/{OrderDetailsPage-zCj0lW0j.js → OrderDetailsPage-CR0Wc1aN.js} +58 -58
  21. package/dist/OrdersListPage-C8Bu6wXM.js +386 -0
  22. package/dist/{PageLayout-D4LDHJJq.js → PageLayout-DX1Ou6V6.js} +164 -134
  23. package/dist/{ProductDetailsPage-wsxAQkA0.js → ProductDetailsPage-B_ns2MOe.js} +55 -55
  24. package/dist/ProductsPage-c92BsEoA.js +186 -0
  25. package/dist/ReportsPage-CPLXtLT2.js +377 -0
  26. package/dist/{RouteDetailsPage-DyMJE9AS.js → RouteDetailsPage-DOfcRiFv.js} +208 -181
  27. package/dist/{RoutesPage-Dtc669yc.js → RoutesPage-BZGublby.js} +12 -12
  28. package/dist/{SettingsPage-CpH9xhQg.js → SettingsPage-5cweX_xh.js} +15 -15
  29. package/dist/Sparkline-zSXvG6vA.js +16729 -0
  30. package/dist/{StatusBadge-C87mDpDP.js → StatusBadge-oZjownr1.js} +7 -7
  31. package/dist/TrackingPage-Ln9qVflK.js +368 -0
  32. package/dist/{VehicleDetailsPage-DldYNsYV.js → VehicleDetailsPage-CrkzE0dN.js} +53 -53
  33. package/dist/VehiclesPage-CTQWH7af.js +184 -0
  34. package/dist/{WarehouseDetailsPage-CMG7or6G.js → WarehouseDetailsPage-CwDLaG65.js} +64 -64
  35. package/dist/WarehousesPage-KW02DvZg.js +182 -0
  36. package/dist/{arrow-left-n48Y-Wis.js → arrow-left-DJDw_Yqv.js} +1 -1
  37. package/dist/{arrow-right-Q3XQpTPR.js → arrow-right-Cb2fLTPJ.js} +1 -1
  38. package/dist/{arrow-up-xNF1zJUG.js → arrow-up-PxaOIyiT.js} +1 -1
  39. package/dist/{box-Eu05PXDh.js → box-hSJVtvGf.js} +1 -1
  40. package/dist/boxes-DDnxEkT5.js +54 -0
  41. package/dist/{chart-column-ouzgxIv_.js → chart-column-xXgAZvRl.js} +1 -1
  42. package/dist/charts-CfCwQg6o.js +2018 -0
  43. package/dist/{chevron-left-B2_r0Elx.js → chevron-left-dhEGJYdh.js} +1 -1
  44. package/dist/{chevron-right-eV4LjaSi.js → chevron-right-Cud2NDvm.js} +1 -1
  45. package/dist/chunk-BdsCSvBP.js +20 -0
  46. package/dist/{circle-alert-Bzh2DBSF.js → circle-alert-Rx0p3D5w.js} +1 -1
  47. package/dist/circle-check-BCxRFdFo.js +13 -0
  48. package/dist/{circle-check-big-CGMSpQmD.js → circle-check-big-BcIJ8xjx.js} +1 -1
  49. package/dist/{clock-Bl3ptexL.js → clock-B2_YxJgB.js} +1 -1
  50. package/dist/{dollar-sign-CAVU95SO.js → dollar-sign-CQmAICv4.js} +1 -1
  51. package/dist/{download-C5NbvVDc.js → download-CG4Tponq.js} +1 -1
  52. package/dist/{file-text-D-1uvLZG.js → file-text-CzH-zMub.js} +1 -1
  53. package/dist/{filter-DUfUg6BO.js → filter-C0u2Lwfn.js} +1 -1
  54. package/dist/{fuel-B6_lnKDs.js → fuel-C2KELcM-.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-FuPu5aTw.js} +1 -1
  58. package/dist/{map-pin-Dznl5l40.js → map-pin-czpLv9uy.js} +1 -1
  59. package/dist/mapMarkers-NCV9MRKR.js +28 -0
  60. package/dist/maps-D9m7PKrs.js +5070 -0
  61. package/dist/{navigation-BJSJl-VG.js → navigation-D5_pdCSS.js} +1 -1
  62. package/dist/{package-CcRttV2N.js → package-B0sdi6lU.js} +1 -1
  63. package/dist/{phone-DVSZHt_j.js → phone-V2nuzPu9.js} +1 -1
  64. package/dist/{plus-C_XZDwBz.js → plus-BqQxkfoU.js} +1 -1
  65. package/dist/{refresh-cw-CfgN67l3.js → refresh-cw-CEOAPEcV.js} +1 -1
  66. package/dist/route-CZWcW-Dw.js +22 -0
  67. package/dist/{save-DJE8Fpdv.js → save-DDFvrxWl.js} +1 -1
  68. package/dist/{search-D_VyGcZT.js → search-D7Ya6jxj.js} +1 -1
  69. package/dist/{square-pen-D-6JYrVQ.js → square-pen-DoZZS4Lw.js} +1 -1
  70. package/dist/{star-BpRlUZuU.js → star-B7PgVu5j.js} +1 -1
  71. package/dist/{store-B-8HMuyZ.js → store-YVzcIG4A.js} +1 -1
  72. package/dist/{trash-2-8vmv-XV_.js → trash-2-BegnBzwI.js} +1 -1
  73. package/dist/{trending-up-Dz6PqPRj.js → trending-up-DBd3XNdb.js} +1 -1
  74. package/dist/{triangle-alert-D7Y3J_4z.js → triangle-alert-Bxk3Kgs6.js} +1 -1
  75. package/dist/{truck-D-y2IDzv.js → truck-Clrn0oHp.js} +1 -1
  76. package/dist/{upload-CHZofOqb.js → upload-Df5JmMwH.js} +1 -1
  77. package/dist/{useAssistant-DVW4QgQb.js → useAssistant-Cb8nnIVC.js} +11 -28
  78. package/dist/{useCustomers-BPW4__PR.js → useCustomers-Dq2IR2HQ.js} +1 -1
  79. package/dist/{useFleet-ChBkxAd6.js → useFleet-CapnWBcA.js} +1 -1
  80. package/dist/{useImportExport-DrPDHEDK.js → useImportExport-BoGzrlJy.js} +1 -1
  81. package/dist/{useInventory-CAAVMRp6.js → useInventory-Ci6M2d9f.js} +1 -1
  82. package/dist/{useOrders-B465S2nt.js → useOrders-BuQU1OHX.js} +1 -1
  83. package/dist/{useReports-jQ3GpNOm.js → useReports-DIx8W7CO.js} +1 -1
  84. package/dist/{useRoutes-hS3d5-xr.js → useRoutes-D7EvI_pm.js} +1 -1
  85. package/dist/{useSettings-BB8X2RLh.js → useSettings-BhX251UQ.js} +6 -6
  86. package/dist/{useTracking-RjGz9PZG.js → useTracking-C40jmZ6W.js} +3 -3
  87. package/dist/{user-DzfTn4dE.js → user-BlcJrbtE.js} +1 -1
  88. package/dist/{user-check-BEKRCi7v.js → user-check-DtnQmimP.js} +1 -1
  89. package/dist/{users-CCrq-w0C.js → users-Cvwko-Dx.js} +1 -1
  90. package/dist/{warehouse-CaqfmMKh.js → warehouse-LWLVTk6G.js} +1 -1
  91. package/dist/{wrench-q_rgJfaP.js → wrench-Cme0RbyR.js} +1 -1
  92. package/dist/{x-tMeDMcCw.js → x-D8C9Pg5V.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/graphqlClient.ts +81 -31
  134. package/src/movethewheels/utils/mapMarkers.ts +58 -0
  135. package/dist/AnalyticsPage-CMpI1070.js +0 -215
  136. package/dist/CustomersPage-CaNBMXmM.js +0 -172
  137. package/dist/DashboardPage-DIKlXde3.js +0 -387
  138. package/dist/DriversPage-PYmdFiby.js +0 -176
  139. package/dist/FinancePage-Cwh9Bw4M.js +0 -136
  140. package/dist/InventoryPage-DpiqVu1v.js +0 -253
  141. package/dist/LiveTrackingPage-FAzzlVnX.js +0 -379
  142. package/dist/MetricCard-CVBVpu7o.js +0 -146
  143. package/dist/OrdersListPage-CAMsuUck.js +0 -384
  144. package/dist/ProductsPage-DC5qDLfz.js +0 -180
  145. package/dist/ReportsPage-BwUJ37eO.js +0 -356
  146. package/dist/TrackingPage-DaNEFG_h.js +0 -325
  147. package/dist/VehiclesPage-BFw13iMt.js +0 -176
  148. package/dist/WarehousesPage-Wkytnt86.js +0 -173
  149. package/dist/useAnalytics-D7HW2XRb.js +0 -347
  150. /package/dist/{formatters-BQr-aR5W.js → formatters-FWJDwTFv.js} +0 -0
@@ -3,12 +3,13 @@
3
3
  */
4
4
 
5
5
  import { useState } from 'react';
6
- import { Users, Plus, RefreshCw, Download, Star } from 'lucide-react';
6
+ import { Users, Plus, RefreshCw, Download, Star, UserCheck, Building2 } 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 { TypeBadge } from '../components/StatusBadge';
10
11
  import { Badge, Avatar, AvatarFallback, Button } 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 { useCustomers, useCustomerStats } from '../hooks/useCustomers';
13
14
  import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
14
15
  import { formatDate } from '../utils/formatters';
@@ -119,24 +120,32 @@ export default function CustomersPage() {
119
120
  next shipment.
120
121
  </PagePurpose>
121
122
 
122
- {/* Customer base at a glance — the page's single emphasis zone */}
123
- <EmphasisPanel>
124
- <p className="text-sm font-medium text-text-secondary">Customer base at a glance</p>
125
- <div className="mt-3 grid grid-cols-3 gap-4">
126
- <div>
127
- <p className="text-2xl font-bold text-text-primary tabular-nums">{stats.total}</p>
128
- <p className="text-xs text-text-secondary">Total customers</p>
129
- </div>
130
- <div>
131
- <p className="text-2xl font-bold text-text-primary tabular-nums">{stats.active}</p>
132
- <p className="text-xs text-text-secondary">Active</p>
133
- </div>
134
- <div>
135
- <p className="text-2xl font-bold text-text-primary tabular-nums">{stats.b2b}</p>
136
- <p className="text-xs text-text-secondary">Business accounts</p>
137
- </div>
138
- </div>
139
- </EmphasisPanel>
123
+ {/* Customer base at a glance — premium stat cards */}
124
+ <div>
125
+ <p className="mb-3 text-sm font-medium text-text-secondary">Customer base at a glance</p>
126
+ <MetricGrid columns={3}>
127
+ <MetricCard
128
+ label="Total customers"
129
+ value={stats.total}
130
+ icon={<Users size={20} />}
131
+ accent="brand"
132
+ />
133
+ <MetricCard
134
+ label="Active"
135
+ value={stats.active}
136
+ icon={<UserCheck size={20} />}
137
+ accent="success"
138
+ subtitle="Currently transacting"
139
+ />
140
+ <MetricCard
141
+ label="Business accounts"
142
+ value={stats.b2b}
143
+ icon={<Building2 size={20} />}
144
+ accent="blue"
145
+ subtitle="B2B relationships"
146
+ />
147
+ </MetricGrid>
148
+ </div>
140
149
 
141
150
  <DataTable
142
151
  columns={columns}
@@ -4,6 +4,7 @@
4
4
  * Main overview page with key metrics and quick actions.
5
5
  */
6
6
 
7
+ import { useMemo } from 'react';
7
8
  import {
8
9
  Package,
9
10
  Truck,
@@ -14,10 +15,14 @@ import {
14
15
  TrendingUp,
15
16
  MapPin,
16
17
  ChevronRight,
18
+ Activity,
17
19
  } from 'lucide-react';
18
20
  import { PageLayout, PageSection } from '../components/PageLayout';
19
- import { MetricCard, MetricGrid } from '../components/MetricCard';
21
+ import { MetricCard, MetricGrid, type MetricDelta } from '../components/MetricCard';
20
22
  import { StatusBadge } from '../components/StatusBadge';
23
+ import { LogisticsMap, type LogisticsMarker } from '../components/maps';
24
+ import { VolumeBar } from '../components/charts';
25
+ import { deliveryMarkerStatus, firstRealLatLng } from '../utils/mapMarkers';
21
26
  import {
22
27
  Card,
23
28
  CardContent,
@@ -28,15 +33,26 @@ import {
28
33
  Progress,
29
34
  } from '../components/ui';
30
35
  import { CTAOverflowMenu, EmphasisPanel, PagePurpose } from '@burdenoff/fe-libs/ui';
31
- import { useDashboardMetrics } from '../hooks/useAnalytics';
36
+ import { useAnalytics, useDashboardMetrics } from '../hooks/useAnalytics';
32
37
  import { useOrders, useOrderStats } from '../hooks/useOrders';
33
38
  import { useFleetStats } from '../hooks/useFleet';
34
- import { useLiveTracking } from '../hooks/useTracking';
39
+ import { useLiveTracking, useTrackingAlerts } from '../hooks/useTracking';
35
40
  import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
36
41
  import { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';
37
42
  import { formatCurrency, formatDate } from '../utils/formatters';
38
43
  import { joinPath } from '../utils/navigation';
39
44
 
45
+ /** Percentage delta across a real series (first non-zero → last). */
46
+ function seriesDelta(values: number[], invert = false): MetricDelta | undefined {
47
+ if (values.length < 2) return undefined;
48
+ const first = values.find((v) => v > 0);
49
+ const last = values[values.length - 1];
50
+ if (first == null || first === 0) return undefined;
51
+ const pct = ((last - first) / Math.abs(first)) * 100;
52
+ const direction = pct > 0.5 ? 'up' : pct < -0.5 ? 'down' : 'flat';
53
+ return { value: `${pct >= 0 ? '+' : ''}${pct.toFixed(1)}%`, direction, invert };
54
+ }
55
+
40
56
  export default function DashboardPage() {
41
57
  const { t: translate } = useI18n();
42
58
  const t = (key: string, fallback: string): string => {
@@ -45,10 +61,69 @@ export default function DashboardPage() {
45
61
  };
46
62
  const { basePath, navigate } = useMoveTheWheels();
47
63
  const { metrics } = useDashboardMetrics();
64
+ const { analytics } = useAnalytics();
48
65
  const { orders } = useOrders({ limit: 5 });
49
66
  const { stats: orderStats } = useOrderStats();
50
67
  const { stats: fleetStats } = useFleetStats();
51
68
  const { activeDeliveries } = useLiveTracking();
69
+ const { unacknowledgedAlerts } = useTrackingAlerts();
70
+
71
+ const orderSpark = useMemo(
72
+ () => analytics.trends.dailyOrders.map((d) => d.orders),
73
+ [analytics.trends.dailyOrders]
74
+ );
75
+ const deliverySpark = useMemo(
76
+ () => analytics.trends.dailyOrders.map((d) => d.deliveries),
77
+ [analytics.trends.dailyOrders]
78
+ );
79
+ const revenueSpark = useMemo(
80
+ () => analytics.trends.revenue.map((r) => r.revenue),
81
+ [analytics.trends.revenue]
82
+ );
83
+
84
+ // Weekly order volume — last 7 real trend points (orders + deliveries).
85
+ const weeklyVolume = useMemo(
86
+ () => analytics.trends.dailyOrders.slice(-7),
87
+ [analytics.trends.dailyOrders]
88
+ );
89
+
90
+ // Live map markers built from the best real coordinate per active delivery.
91
+ const mapMarkers = useMemo<LogisticsMarker[]>(
92
+ () =>
93
+ activeDeliveries.flatMap((d) => {
94
+ if (!d.order) return [];
95
+ const coord = firstRealLatLng(
96
+ d.vehicle?.currentLocation,
97
+ d.driver?.currentLocation,
98
+ d.tracking?.currentLocation
99
+ );
100
+ if (!coord) return [];
101
+ return [
102
+ {
103
+ id: d.order.id,
104
+ lat: coord.lat,
105
+ lng: coord.lng,
106
+ label: d.order.orderNumber,
107
+ status: deliveryMarkerStatus(d.order.status),
108
+ sublabel: d.driver?.fullName || d.vehicle?.vehicleNumber || undefined,
109
+ },
110
+ ];
111
+ }),
112
+ [activeDeliveries]
113
+ );
114
+
115
+ // Recent activity — the latest orders as a real, timestamped feed.
116
+ const recentActivity = useMemo(
117
+ () =>
118
+ [...orders]
119
+ .sort(
120
+ (a, b) =>
121
+ new Date(b.updatedAt ?? b.createdAt).getTime() -
122
+ new Date(a.updatedAt ?? a.createdAt).getTime()
123
+ )
124
+ .slice(0, 5),
125
+ [orders]
126
+ );
52
127
 
53
128
  const goTo = (path: string) => {
54
129
  if (navigate) {
@@ -94,27 +169,38 @@ export default function DashboardPage() {
94
169
  )}
95
170
  </PagePurpose>
96
171
 
97
- {/* Key Metrics */}
172
+ {/* Key Metrics — premium control-tower stat cards with real trend deltas */}
98
173
  <MetricGrid columns={4}>
99
174
  <MetricCard
100
175
  label={t('movethewheels.dashboard.metrics.totalOrders', 'Total Orders')}
101
176
  value={metrics.orders.total}
102
177
  icon={<Package size={20} />}
178
+ accent="brand"
179
+ sparklineData={orderSpark.length >= 2 ? orderSpark : undefined}
180
+ delta={seriesDelta(orderSpark)}
103
181
  onClick={() => goTo('orders')}
104
182
  />
105
183
  <MetricCard
106
184
  label={t('movethewheels.dashboard.metrics.activeDeliveries', 'Active Deliveries')}
107
185
  value={metrics.orders.inTransit}
108
186
  icon={<Truck size={20} />}
187
+ accent="blue"
109
188
  subtitle={`${metrics.orders.pending} ${t('movethewheels.dashboard.metrics.pending', 'pending')}`}
189
+ sparklineData={deliverySpark.length >= 2 ? deliverySpark : undefined}
190
+ delta={seriesDelta(deliverySpark)}
110
191
  onClick={() => goTo('tracking')}
111
192
  />
112
193
  <MetricCard
113
- label={t('movethewheels.dashboard.metrics.fleetUtilization', 'Fleet Utilization')}
114
- value={`${metrics.fleet.activeVehicles}/${metrics.fleet.totalVehicles}`}
115
- icon={<Truck size={20} />}
116
- subtitle={`${metrics.fleet.availableDrivers} ${t('movethewheels.dashboard.metrics.driversAvailable', 'drivers available')}`}
117
- onClick={() => goTo('fleet')}
194
+ label={t('movethewheels.dashboard.metrics.onTimeDelivery', 'On-Time Delivery')}
195
+ value={`${metrics.performance.onTimeDeliveryRate.toFixed(1)}%`}
196
+ icon={<TrendingUp size={20} />}
197
+ accent="success"
198
+ delta={
199
+ metrics.performance.onTimeDeliveryRate >= 90
200
+ ? { value: 'On target', direction: 'up' }
201
+ : { value: 'Below 90%', direction: 'down' }
202
+ }
203
+ onClick={() => goTo('analytics')}
118
204
  />
119
205
  <MetricCard
120
206
  treatment="glass"
@@ -122,17 +208,157 @@ export default function DashboardPage() {
122
208
  label={t('movethewheels.dashboard.metrics.revenue', 'Revenue')}
123
209
  value={formatCurrency(metrics.revenue.total)}
124
210
  icon={<DollarSign size={20} />}
211
+ accent="success"
212
+ sparklineData={revenueSpark.length >= 2 ? revenueSpark : undefined}
213
+ delta={seriesDelta(revenueSpark)}
125
214
  onClick={() => goTo('analytics')}
126
215
  />
127
216
  </MetricGrid>
128
217
 
218
+ {/* Control Tower — live map + exceptions + recent activity */}
219
+ <div className="grid grid-cols-1 gap-6 lg:grid-cols-3">
220
+ <Card className="overflow-hidden lg:col-span-2">
221
+ <CardHeader className="flex flex-row items-center justify-between">
222
+ <CardTitle className="flex items-center gap-2">
223
+ <MapPin size={18} /> {t('movethewheels.dashboard.liveMap.title', 'Live Map')}
224
+ </CardTitle>
225
+ <Button variant="ghost" size="sm" onClick={() => goTo('tracking/live')}>
226
+ {t('movethewheels.dashboard.liveMap.open', 'Open live tracking')}
227
+ <ChevronRight size={16} />
228
+ </Button>
229
+ </CardHeader>
230
+ <CardContent className="p-0">
231
+ <LogisticsMap
232
+ markers={mapMarkers}
233
+ height={460}
234
+ emptyMessage={
235
+ activeDeliveries.length === 0
236
+ ? t('movethewheels.dashboard.liveMap.noActive', 'No active deliveries right now')
237
+ : t(
238
+ 'movethewheels.dashboard.liveMap.awaitingGps',
239
+ 'Awaiting live GPS positions for active deliveries'
240
+ )
241
+ }
242
+ />
243
+ </CardContent>
244
+ </Card>
245
+
246
+ <div className="space-y-6">
247
+ {/* Exception Alerts */}
248
+ <Card>
249
+ <CardHeader>
250
+ <CardTitle className="flex items-center gap-2">
251
+ <AlertTriangle size={18} className="text-status-warning-text" />
252
+ {t('movethewheels.dashboard.exceptions.title', 'Exception Alerts')}
253
+ {unacknowledgedAlerts.length > 0 && (
254
+ <Badge variant="destructive" className="ml-auto">
255
+ {unacknowledgedAlerts.length}
256
+ </Badge>
257
+ )}
258
+ </CardTitle>
259
+ </CardHeader>
260
+ <CardContent>
261
+ {unacknowledgedAlerts.length > 0 ? (
262
+ <div className="space-y-3">
263
+ {unacknowledgedAlerts.slice(0, 4).map((alert) => (
264
+ <div
265
+ key={alert.id}
266
+ className={`rounded-lg p-3 text-sm ${
267
+ alert.severity === 'critical' || alert.severity === 'high'
268
+ ? 'bg-status-error-bg-subtle'
269
+ : 'bg-status-warning-bg-subtle'
270
+ }`}
271
+ >
272
+ <p className="font-medium">{alert.message}</p>
273
+ <p className="mt-1 text-xs text-text-secondary">
274
+ {formatDate(alert.timestamp, 'relative')}
275
+ </p>
276
+ </div>
277
+ ))}
278
+ </div>
279
+ ) : (
280
+ <p className="py-4 text-center text-sm text-text-secondary">
281
+ {t(
282
+ 'movethewheels.dashboard.exceptions.none',
283
+ 'No exceptions — all deliveries on track'
284
+ )}
285
+ </p>
286
+ )}
287
+ </CardContent>
288
+ </Card>
289
+
290
+ {/* Recent Activity */}
291
+ <Card>
292
+ <CardHeader>
293
+ <CardTitle className="flex items-center gap-2">
294
+ <Activity size={18} />
295
+ {t('movethewheels.dashboard.activity.title', 'Recent Activity')}
296
+ </CardTitle>
297
+ </CardHeader>
298
+ <CardContent>
299
+ {recentActivity.length > 0 ? (
300
+ <ul className="space-y-3">
301
+ {recentActivity.map((order) => (
302
+ <li key={order.id} className="flex items-start gap-3">
303
+ <span
304
+ aria-hidden
305
+ className="mt-1.5 h-2 w-2 shrink-0 rounded-full bg-action-primary-bg"
306
+ />
307
+ <div className="min-w-0 flex-1">
308
+ <p className="truncate text-sm">
309
+ <span className="font-medium">{order.orderNumber}</span>{' '}
310
+ <span className="text-text-secondary capitalize">
311
+ {order.status.replace(/_/g, ' ')}
312
+ </span>
313
+ </p>
314
+ <p className="text-xs text-text-secondary">
315
+ {formatDate(order.updatedAt ?? order.createdAt, 'relative')}
316
+ </p>
317
+ </div>
318
+ </li>
319
+ ))}
320
+ </ul>
321
+ ) : (
322
+ <p className="py-4 text-center text-sm text-text-secondary">
323
+ {t('movethewheels.dashboard.activity.none', 'No recent activity yet')}
324
+ </p>
325
+ )}
326
+ </CardContent>
327
+ </Card>
328
+ </div>
329
+ </div>
330
+
331
+ {/* Weekly order volume */}
332
+ <Card>
333
+ <CardHeader>
334
+ <CardTitle>
335
+ {t('movethewheels.dashboard.weeklyVolume.title', 'Weekly order volume')}
336
+ </CardTitle>
337
+ </CardHeader>
338
+ <CardContent>
339
+ <VolumeBar
340
+ xKey="date"
341
+ height={240}
342
+ data={weeklyVolume}
343
+ series={[
344
+ { dataKey: 'orders', name: 'Orders' },
345
+ { dataKey: 'deliveries', name: 'Deliveries' },
346
+ ]}
347
+ emptyMessage={t(
348
+ 'movethewheels.dashboard.weeklyVolume.empty',
349
+ 'No order volume recorded yet'
350
+ )}
351
+ />
352
+ </CardContent>
353
+ </Card>
354
+
129
355
  {/* Quick Stats Row */}
130
356
  <MetricGrid columns={4}>
131
357
  <MetricCard
132
358
  size="sm"
133
- label={t('movethewheels.dashboard.stats.onTimeDelivery', 'On-Time Delivery')}
134
- value={`${metrics.performance.onTimeDeliveryRate.toFixed(1)}%`}
135
- changeType={metrics.performance.onTimeDeliveryRate >= 90 ? 'positive' : 'negative'}
359
+ label={t('movethewheels.dashboard.metrics.fleetUtilization', 'Fleet Utilization')}
360
+ value={`${metrics.fleet.activeVehicles}/${metrics.fleet.totalVehicles}`}
361
+ subtitle={`${metrics.fleet.availableDrivers} ${t('movethewheels.dashboard.metrics.driversAvailable', 'drivers available')}`}
136
362
  />
137
363
  <MetricCard
138
364
  size="sm"
@@ -374,7 +374,7 @@ export default function DriverDetailsPage() {
374
374
  {formatCurrency(driver.earnings.totalEarnings)}
375
375
  </p>
376
376
  </div>
377
- <div className="grid grid-cols-2 gap-3">
377
+ <div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
378
378
  <div>
379
379
  <p className="text-sm text-text-secondary">This Week</p>
380
380
  <p className="font-medium">{formatCurrency(driver.earnings.weeklyEarnings)}</p>
@@ -3,12 +3,13 @@
3
3
  */
4
4
 
5
5
  import { useState } from 'react';
6
- import { Users, Plus, RefreshCw, Download, Star } from 'lucide-react';
6
+ import { Users, Plus, RefreshCw, Download, Star, UserCheck, Coffee, UserMinus } 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 } from '../components/StatusBadge';
10
11
  import { Avatar, AvatarFallback } 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 { useDrivers, useFleetStats } from '../hooks/useFleet';
13
14
  import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
14
15
  import { formatDate } from '../utils/formatters';
@@ -123,28 +124,38 @@ export default function DriversPage() {
123
124
  assigning them to an order or route.
124
125
  </PagePurpose>
125
126
 
126
- {/* Workforce at a glance — the page's single emphasis zone */}
127
- <EmphasisPanel>
128
- <p className="text-sm font-medium text-text-secondary">Workforce at a glance</p>
129
- <div className="mt-3 grid grid-cols-3 gap-4">
130
- <div>
131
- <p className="text-2xl font-bold text-text-primary tabular-nums">{totalDrivers}</p>
132
- <p className="text-xs text-text-secondary">Total drivers</p>
133
- </div>
134
- <div>
135
- <p className="text-2xl font-bold text-text-primary tabular-nums">
136
- {stats.driversByStatus.available + stats.driversByStatus.busy}
137
- </p>
138
- <p className="text-xs text-text-secondary">On duty / available</p>
139
- </div>
140
- <div>
141
- <p className="text-2xl font-bold text-text-primary tabular-nums">
142
- {stats.avgDriverRating > 0 ? stats.avgDriverRating.toFixed(1) : '—'}
143
- </p>
144
- <p className="text-xs text-text-secondary">Avg rating</p>
145
- </div>
146
- </div>
147
- </EmphasisPanel>
127
+ {/* Workforce at a glance — premium stat cards */}
128
+ <div>
129
+ <p className="mb-3 text-sm font-medium text-text-secondary">Workforce at a glance</p>
130
+ <MetricGrid columns={4}>
131
+ <MetricCard
132
+ label="On duty"
133
+ value={stats.driversByStatus.available + stats.driversByStatus.busy}
134
+ icon={<UserCheck size={20} />}
135
+ accent="success"
136
+ subtitle="Available or delivering"
137
+ />
138
+ <MetricCard
139
+ label="On break"
140
+ value={stats.driversByStatus.on_break}
141
+ icon={<Coffee size={20} />}
142
+ accent="warning"
143
+ />
144
+ <MetricCard
145
+ label="Off duty"
146
+ value={stats.driversByStatus.offline}
147
+ icon={<UserMinus size={20} />}
148
+ accent="neutral"
149
+ />
150
+ <MetricCard
151
+ label="Avg rating"
152
+ value={stats.avgDriverRating > 0 ? stats.avgDriverRating.toFixed(1) : '—'}
153
+ icon={<Star size={20} />}
154
+ accent="brand"
155
+ subtitle={`${totalDrivers} drivers`}
156
+ />
157
+ </MetricGrid>
158
+ </div>
148
159
 
149
160
  <DataTable
150
161
  columns={columns}
@@ -1,16 +1,19 @@
1
1
  /**
2
2
  * Finance Page
3
3
  *
4
- * Shows only REAL figures returned by Query.financeMetrics (revenue, fuel cost,
5
- * open invoices) — no simulated paid/pending splits. Invoicing and settlement
6
- * are handled by the platform Billing module (global-billing-svc), which this
7
- * page deep-links to rather than re-implementing an Invoice model here.
4
+ * Shows only REAL figures returned by Query.financeMetrics (revenue, delivered
5
+ * revenue, fuel cost, open invoices) plus the real revenue trend series — no
6
+ * simulated paid/pending splits and no fabricated cost categories. Invoicing and
7
+ * settlement are handled by the platform Billing module, which this page
8
+ * deep-links to rather than re-implementing an Invoice ledger here.
8
9
  */
9
10
 
11
+ import { useMemo } from 'react';
10
12
  import { DollarSign, TrendingUp, CreditCard, FileText, Fuel, ArrowRight } from 'lucide-react';
11
13
  import { PageLayout } from '../components/PageLayout';
12
14
  import { MetricCard, MetricGrid } from '../components/MetricCard';
13
15
  import { Card, CardContent, CardHeader, CardTitle, Button } from '../components/ui';
16
+ import { TrendChart, VolumeBar, chartTokens } from '../components/charts';
14
17
  import { EmphasisPanel, PagePurpose } from '@burdenoff/fe-libs/ui';
15
18
  import { useAnalytics, useFinanceMetrics } from '../hooks/useAnalytics';
16
19
  import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
@@ -24,6 +27,24 @@ export default function FinancePage() {
24
27
  // Deep-link to the host Billing module (mounted at /billing in the app shell).
25
28
  const openBilling = () => navigate('/billing');
26
29
 
30
+ const revenueSeries = useMemo(
31
+ () => analytics.trends.revenue.map((r) => ({ month: r.month, revenue: r.revenue })),
32
+ [analytics.trends.revenue]
33
+ );
34
+
35
+ // Real dollar figures only — total revenue, money already collected on delivered
36
+ // orders, and fuel spend. No invented category breakdown.
37
+ const economics = useMemo(
38
+ () => [
39
+ { label: 'Total revenue', amount: metrics.totalRevenue },
40
+ { label: 'Collected', amount: metrics.deliveredRevenue },
41
+ { label: 'Fuel cost', amount: metrics.fuelCost },
42
+ ],
43
+ [metrics.totalRevenue, metrics.deliveredRevenue, metrics.fuelCost]
44
+ );
45
+
46
+ const hasEconomics = economics.some((e) => e.amount > 0);
47
+
27
48
  return (
28
49
  <PageLayout
29
50
  title="Finance"
@@ -31,7 +52,7 @@ export default function FinancePage() {
31
52
  icon={<DollarSign size={24} />}
32
53
  headerActions={
33
54
  <Button variant="outline" onClick={openBilling}>
34
- <FileText size={16} /> Invoices & Billing
55
+ <FileText size={16} /> Invoices &amp; Billing
35
56
  </Button>
36
57
  }
37
58
  >
@@ -48,59 +69,54 @@ export default function FinancePage() {
48
69
  label="Total Revenue"
49
70
  value={formatCurrency(metrics.totalRevenue)}
50
71
  icon={<DollarSign size={20} />}
72
+ accent="success"
51
73
  />
52
74
  <MetricCard
53
75
  label="Collected (Delivered)"
54
76
  value={formatCurrency(metrics.deliveredRevenue)}
55
77
  icon={<TrendingUp size={20} />}
56
- changeType="positive"
78
+ accent="brand"
57
79
  />
58
80
  <MetricCard
59
81
  label="Fuel Cost"
60
82
  value={formatCurrency(metrics.fuelCost)}
61
83
  icon={<Fuel size={20} />}
62
- changeType="neutral"
84
+ accent="warning"
63
85
  />
64
86
  <MetricCard
65
87
  label="Open Invoices"
66
88
  value={String(metrics.openInvoices)}
67
89
  icon={<CreditCard size={20} />}
90
+ accent="blue"
68
91
  />
69
92
  </MetricGrid>
70
93
 
71
- <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
72
- <Card>
94
+ <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
95
+ <Card className="lg:col-span-2">
73
96
  <CardHeader>
74
- <CardTitle>Revenue by Month</CardTitle>
97
+ <CardTitle>Revenue trend</CardTitle>
75
98
  </CardHeader>
76
99
  <CardContent>
77
- {analytics.trends.revenue.length > 0 ? (
78
- <div className="space-y-4">
79
- {analytics.trends.revenue.map((r) => (
80
- <div
81
- key={r.month}
82
- className="flex items-center justify-between p-3 bg-bg-sunken/50 rounded-lg"
83
- >
84
- <span className="font-medium">{r.month}</span>
85
- <span className="text-lg font-bold">{formatCurrency(r.revenue)}</span>
86
- </div>
87
- ))}
88
- </div>
89
- ) : (
90
- <p className="text-text-secondary text-center py-6">
91
- No revenue recorded yet. Revenue appears here as orders are delivered.
92
- </p>
93
- )}
100
+ <TrendChart
101
+ xKey="month"
102
+ variant="area"
103
+ height={280}
104
+ showLegend={false}
105
+ data={revenueSeries}
106
+ series={[{ dataKey: 'revenue', name: 'Revenue', color: chartTokens.brandBlue }]}
107
+ valueFormatter={(v) => formatCurrency(Number(v))}
108
+ emptyMessage="No revenue recorded yet. Revenue appears here as orders are delivered."
109
+ />
94
110
  </CardContent>
95
111
  </Card>
96
112
 
97
113
  {/* Invoices & Settlement — reuse of the Billing module */}
98
114
  <EmphasisPanel className="space-y-4">
99
- <h2 className="text-base font-semibold text-text-primary">Invoices & Settlement</h2>
115
+ <h2 className="text-base font-semibold text-text-primary">Invoices &amp; Settlement</h2>
100
116
  <p className="text-sm text-text-secondary">
101
117
  Invoicing, payments and settlement run through the platform Billing module — the single
102
- source of truth for what&apos;s been billed and paid. MoveTheWheels doesn&apos;t keep its
103
- own invoice ledger.
118
+ source of truth for what&apos;s been billed and paid. MoveTheWheels doesn&apos;t keep
119
+ its own invoice ledger.
104
120
  </p>
105
121
  <div className="flex items-center justify-between p-4 bg-status-warning-bg/10 rounded-lg">
106
122
  <div className="flex items-center gap-3">
@@ -123,6 +139,28 @@ export default function FinancePage() {
123
139
  </Button>
124
140
  </EmphasisPanel>
125
141
  </div>
142
+
143
+ <Card>
144
+ <CardHeader>
145
+ <CardTitle>Revenue vs cost</CardTitle>
146
+ </CardHeader>
147
+ <CardContent>
148
+ {hasEconomics ? (
149
+ <VolumeBar
150
+ xKey="label"
151
+ height={260}
152
+ showLegend={false}
153
+ data={economics}
154
+ series={[{ dataKey: 'amount', name: 'Amount' }]}
155
+ valueFormatter={(v) => formatCurrency(Number(v))}
156
+ />
157
+ ) : (
158
+ <p className="py-8 text-center text-sm text-text-secondary">
159
+ Revenue and cost figures appear here as orders are delivered.
160
+ </p>
161
+ )}
162
+ </CardContent>
163
+ </Card>
126
164
  </PageLayout>
127
165
  );
128
166
  }