@burdenoff/microfe-movethewheels 2026.703.3 → 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 (173) hide show
  1. package/dist/{AIAssistantPage-C3cPsWt-.js → AIAssistantPage-D2gaEL1U.js} +16 -15
  2. package/dist/AnalyticsPage-DZMlEoBi.js +194 -0
  3. package/dist/CreateOrderPage-CWHx8yys.js +490 -0
  4. package/dist/CustomerDetailsPage-nR4oZ_ju.js +615 -0
  5. package/dist/CustomersPage-Bbvwvn8k.js +205 -0
  6. package/dist/DashboardPage-DrEhNf7N.js +542 -0
  7. package/dist/DataTable-CqMUD4er.js +340 -0
  8. package/dist/DocumentsCard-edDkgnJj.js +169 -0
  9. package/dist/DriverDetailsPage-whEMOWs9.js +490 -0
  10. package/dist/DriversPage-CHF2ZEzd.js +221 -0
  11. package/dist/FinancePage-Dn9GrW-1.js +175 -0
  12. package/dist/{FleetPage-DqkFi3s3.js → FleetPage-CrtGPy-p.js} +36 -35
  13. package/dist/{FormError-BlOjnXOq.js → FormError-BKcb_wT-.js} +1 -1
  14. package/dist/{ImportExportPage-DKJRCezl.js → ImportExportPage-WYdwZomZ.js} +52 -72
  15. package/dist/InventoryPage-l5A1f753.js +259 -0
  16. package/dist/LiveTrackingPage-DhEaNKZ4.js +429 -0
  17. package/dist/MarketplacePage-B9RYOb1Y.js +426 -0
  18. package/dist/MetricCard-y3bWHKUQ.js +191 -0
  19. package/dist/Notice-DOO-tjpV.js +46 -0
  20. package/dist/OrderDetailsPage-CwYHwk1K.js +584 -0
  21. package/dist/OrdersListPage-DhZJoQeI.js +386 -0
  22. package/dist/{PageLayout-D4LDHJJq.js → PageLayout-BniRJaQ6.js} +1 -1
  23. package/dist/{ProductDetailsPage-C9uOiYLp.js → ProductDetailsPage-1Gm9JKy0.js} +56 -56
  24. package/dist/ProductsPage-C8sn-1tN.js +186 -0
  25. package/dist/ReportsPage-D1tb0dcz.js +377 -0
  26. package/dist/RouteDetailsPage-CkMyR9b_.js +610 -0
  27. package/dist/{RoutesPage-DMfoicM1.js → RoutesPage-QkENzsE5.js} +58 -47
  28. package/dist/{SettingsPage-Bn-v0mbQ.js → SettingsPage-DV3_tU_X.js} +16 -38
  29. package/dist/Sparkline-K2RMJ58u.js +16729 -0
  30. package/dist/{StatusBadge-BZurYHMy.js → StatusBadge-BNHXYvZx.js} +7 -7
  31. package/dist/TrackingPage-BZrg4RCu.js +368 -0
  32. package/dist/VehicleDetailsPage-D-8XPZ2G.js +393 -0
  33. package/dist/VehiclesPage-D5BELbtM.js +184 -0
  34. package/dist/{WarehouseDetailsPage-L_6sWigM.js → WarehouseDetailsPage-Cmg-8Frn.js} +70 -70
  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-CmKOmIkh.js +11 -0
  38. package/dist/arrow-up-BJijRLAZ.js +17 -0
  39. package/dist/{box-D9Pal4HM.js → box-BT3NsRQP.js} +1 -1
  40. package/dist/boxes-B_OGvQiW.js +54 -0
  41. package/dist/{chart-column-BFIJysDR.js → chart-column-DQTTdUPk.js} +1 -1
  42. package/dist/charts-Tj_PPTfA.js +2018 -0
  43. package/dist/chevron-left-B1rZyWWb.js +8 -0
  44. package/dist/{chevron-right-EMIIyyJ1.js → chevron-right-BIcwUJZq.js} +1 -1
  45. package/dist/chunk-BdsCSvBP.js +20 -0
  46. package/dist/{circle-alert-iY24lSlp.js → circle-alert-CNkuDNc6.js} +1 -1
  47. package/dist/circle-check-Cs1OVjtV.js +13 -0
  48. package/dist/{circle-check-big-w0TrLKMF.js → circle-check-big-C-p4eNwn.js} +1 -1
  49. package/dist/{clock-DJZtdl8u.js → clock-dRGG4rkF.js} +1 -1
  50. package/dist/{dollar-sign-CgKQRz_e.js → dollar-sign-B6RoYHHz.js} +1 -1
  51. package/dist/{download-DRXxoiQR.js → download-DP04iWTd.js} +1 -1
  52. package/dist/{file-text-DWWY6_f5.js → file-text-BuDx4hZp.js} +1 -1
  53. package/dist/{filter-DloLC2eE.js → filter-BD483_8g.js} +1 -1
  54. package/dist/{wrench-CANXWxF-.js → fuel-DLH1ag04.js} +3 -6
  55. package/dist/index.css +2 -0
  56. package/dist/index.js +150 -172
  57. package/dist/{map-CkJaULb2.js → map-DNQn9fyY.js} +1 -1
  58. package/dist/{map-pin-BGiCvyCs.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-D36prnAn.js → navigation-CgGUt2tR.js} +1 -1
  62. package/dist/{package-BChUzpJS.js → package-DmRF7iQD.js} +1 -1
  63. package/dist/{phone-Ac9s3mo_.js → phone-Cl32EXKW.js} +1 -1
  64. package/dist/{plus-Z76q-FZm.js → plus-DlANgOFx.js} +1 -1
  65. package/dist/{refresh-cw-DKt6f5T6.js → refresh-cw-rW59D4b7.js} +1 -1
  66. package/dist/route-CWjPrtAN.js +22 -0
  67. package/dist/{save-CojywNhG.js → save-UDlog7hg.js} +1 -1
  68. package/dist/{search-CDGefqb3.js → search-DNIG5Ck7.js} +1 -1
  69. package/dist/{square-pen-CopE9GmB.js → square-pen-BcfB3Grd.js} +1 -1
  70. package/dist/{star-nHlLjDKR.js → star-CVZHFwDx.js} +1 -1
  71. package/dist/{store-BR9Voi5E.js → store-bwnmCK9f.js} +1 -1
  72. package/dist/{trash-2-BKhar6Hs.js → trash-2-B80G12d4.js} +1 -1
  73. package/dist/{trending-up-Dohgd4t_.js → trending-up-CH4R4KOa.js} +1 -1
  74. package/dist/{triangle-alert-i-XQL9Rd.js → triangle-alert-CmS8AUU6.js} +1 -1
  75. package/dist/{truck-C0v_hbLR.js → truck-9rLDncav.js} +1 -1
  76. package/dist/upload-DanJY1Vo.js +21 -0
  77. package/dist/{useAssistant-D4v3oxD3.js → useAssistant-CnEwkTLm.js} +11 -28
  78. package/dist/{useCustomers-BmTNGxiI.js → useCustomers-ChJao1ET.js} +30 -27
  79. package/dist/{useFleet-DKqXZ16m.js → useFleet-g5Yri0G8.js} +82 -70
  80. package/dist/{useImportExport-DHr-SWSw.js → useImportExport-MYwIT3rE.js} +1 -1
  81. package/dist/{useInventory-DkyiVY_A.js → useInventory-BSOn-R1c.js} +84 -39
  82. package/dist/{useOrders-Cfk1GLiq.js → useOrders-BSiqo61w.js} +59 -48
  83. package/dist/{useReports-CTqZr-AB.js → useReports-fWESfsF9.js} +41 -35
  84. package/dist/{useRoutes-BQWO607N.js → useRoutes-zD8Yp323.js} +50 -42
  85. package/dist/{useSettings-CkDH_-gM.js → useSettings-B1lsWeZ1.js} +6 -6
  86. package/dist/{useTracking-B-va1kO3.js → useTracking-BKG0I2ln.js} +3 -3
  87. package/dist/{user-jyFU3lXk.js → user-DU51el3m.js} +1 -1
  88. package/dist/user-check-IMhk-qkX.js +20 -0
  89. package/dist/{users-DZ-ilS6B.js → users-B8KhJBGg.js} +1 -1
  90. package/dist/{warehouse-6m5kXMLu.js → warehouse-CcMMioeY.js} +1 -1
  91. package/dist/wrench-Dg-sgH2e.js +8 -0
  92. package/dist/x-C4ObLVEa.js +11 -0
  93. package/package.json +5 -1
  94. package/src/movethewheels/components/DataTable.tsx +252 -84
  95. package/src/movethewheels/components/DocumentsCard.tsx +102 -0
  96. package/src/movethewheels/components/GlassPanel.tsx +71 -0
  97. package/src/movethewheels/components/MetricCard.tsx +171 -73
  98. package/src/movethewheels/components/Notice.tsx +61 -0
  99. package/src/movethewheels/components/StatusBadge.tsx +7 -6
  100. package/src/movethewheels/components/charts/ChartShell.tsx +78 -0
  101. package/src/movethewheels/components/charts/Sparkline.tsx +85 -0
  102. package/src/movethewheels/components/charts/StatusDonut.tsx +115 -0
  103. package/src/movethewheels/components/charts/TrendChart.tsx +179 -0
  104. package/src/movethewheels/components/charts/VolumeBar.tsx +97 -0
  105. package/src/movethewheels/components/charts/chartTokens.ts +43 -0
  106. package/src/movethewheels/components/charts/index.ts +9 -0
  107. package/src/movethewheels/components/index.ts +51 -1
  108. package/src/movethewheels/components/maps/LogisticsMap.tsx +94 -0
  109. package/src/movethewheels/components/maps/MapCanvas.tsx +118 -0
  110. package/src/movethewheels/components/maps/RouteMap.tsx +71 -0
  111. package/src/movethewheels/components/maps/TrackingMap.tsx +102 -0
  112. package/src/movethewheels/components/maps/index.ts +16 -0
  113. package/src/movethewheels/components/maps/leaflet-setup.ts +124 -0
  114. package/src/movethewheels/components/maps/map-markers.css +121 -0
  115. package/src/movethewheels/hooks/index.ts +9 -0
  116. package/src/movethewheels/hooks/useAnalytics.ts +109 -4
  117. package/src/movethewheels/hooks/useCustomers.ts +11 -4
  118. package/src/movethewheels/hooks/useEntityDocuments.ts +132 -0
  119. package/src/movethewheels/hooks/useFleet.ts +26 -7
  120. package/src/movethewheels/hooks/useInventory.ts +70 -7
  121. package/src/movethewheels/hooks/useOrders.ts +32 -2
  122. package/src/movethewheels/hooks/useReports.ts +14 -4
  123. package/src/movethewheels/hooks/useRoutes.ts +20 -3
  124. package/src/movethewheels/pages/AnalyticsPage.tsx +93 -73
  125. package/src/movethewheels/pages/CreateOrderPage.tsx +48 -20
  126. package/src/movethewheels/pages/CustomerDetailsPage.tsx +239 -33
  127. package/src/movethewheels/pages/CustomersPage.tsx +53 -28
  128. package/src/movethewheels/pages/DashboardPage.tsx +238 -12
  129. package/src/movethewheels/pages/DriverDetailsPage.tsx +17 -31
  130. package/src/movethewheels/pages/DriversPage.tsx +59 -31
  131. package/src/movethewheels/pages/FinancePage.tsx +103 -82
  132. package/src/movethewheels/pages/InventoryPage.tsx +15 -3
  133. package/src/movethewheels/pages/LiveTrackingPage.tsx +124 -16
  134. package/src/movethewheels/pages/MarketplacePage.tsx +265 -50
  135. package/src/movethewheels/pages/OrderDetailsPage.tsx +214 -10
  136. package/src/movethewheels/pages/OrdersListPage.tsx +118 -47
  137. package/src/movethewheels/pages/ProductsPage.tsx +52 -28
  138. package/src/movethewheels/pages/ReportsPage.tsx +88 -4
  139. package/src/movethewheels/pages/RouteDetailsPage.tsx +119 -19
  140. package/src/movethewheels/pages/RoutesPage.tsx +22 -6
  141. package/src/movethewheels/pages/SettingsPage.tsx +5 -21
  142. package/src/movethewheels/pages/TrackingPage.tsx +76 -6
  143. package/src/movethewheels/pages/VehicleDetailsPage.tsx +4 -34
  144. package/src/movethewheels/pages/VehiclesPage.tsx +58 -27
  145. package/src/movethewheels/pages/WarehouseDetailsPage.tsx +1 -1
  146. package/src/movethewheels/pages/WarehousesPage.tsx +47 -36
  147. package/src/movethewheels/types/index.ts +9 -1
  148. package/src/movethewheels/utils/download.ts +62 -0
  149. package/src/movethewheels/utils/mapMarkers.ts +58 -0
  150. package/dist/AnalyticsPage-Dz6g2MvK.js +0 -215
  151. package/dist/CreateOrderPage-C5iwnjp4.js +0 -479
  152. package/dist/CustomerDetailsPage-B6nwFyLO.js +0 -406
  153. package/dist/CustomersPage-BKxMS1AV.js +0 -161
  154. package/dist/DashboardPage-BVjQi0EB.js +0 -387
  155. package/dist/DataTable-DmciIKKM.js +0 -249
  156. package/dist/DriverDetailsPage-CN3WQ0B9.js +0 -507
  157. package/dist/DriversPage-CH9rjQC-.js +0 -165
  158. package/dist/FinancePage-DEDykSOP.js +0 -158
  159. package/dist/InventoryPage-e0ze63iX.js +0 -253
  160. package/dist/LiveTrackingPage-DCX-K-LR.js +0 -379
  161. package/dist/MarketplacePage-CIQ97_zp.js +0 -247
  162. package/dist/MetricCard-BY0QrMD4.js +0 -146
  163. package/dist/OrderDetailsPage-DYiZ-9UK.js +0 -429
  164. package/dist/OrdersListPage-Cn90P1Xx.js +0 -307
  165. package/dist/ProductsPage-QJfHHpqB.js +0 -169
  166. package/dist/ReportsPage-Zp53q3_8.js +0 -284
  167. package/dist/RouteDetailsPage-DUStXSf9.js +0 -531
  168. package/dist/TrackingPage-W-jtdM3Y.js +0 -325
  169. package/dist/VehicleDetailsPage-Bceo5oz3.js +0 -410
  170. package/dist/VehiclesPage-ApKThYeG.js +0 -165
  171. package/dist/WarehousesPage-DCbXXXWE.js +0 -162
  172. package/dist/useAnalytics-DS8n0xgg.js +0 -297
  173. /package/dist/{formatters-CF2S2l1m.js → formatters-FWJDwTFv.js} +0 -0
@@ -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"
@@ -34,6 +34,7 @@ import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
34
34
  import { formatCurrency, formatDate } from '../utils/formatters';
35
35
  import { joinPath } from '../utils/navigation';
36
36
  import { FormError } from '../components/FormError';
37
+ import { DocumentsCard } from '../components/DocumentsCard';
37
38
 
38
39
  function NewDriverForm() {
39
40
  const { basePath, navigate } = useMoveTheWheels();
@@ -310,46 +311,31 @@ export default function DriverDetailsPage() {
310
311
  </CardContent>
311
312
  </Card>
312
313
 
313
- {/* Documents */}
314
+ {/* License — real driver credential fields */}
314
315
  <Card>
315
316
  <CardHeader>
316
317
  <CardTitle className="flex items-center gap-2">
317
- <FileText size={18} /> Documents
318
+ <FileText size={18} /> License
318
319
  </CardTitle>
319
320
  </CardHeader>
320
321
  <CardContent>
321
- <div className="space-y-3">
322
- <div className="flex items-center justify-between p-3 border rounded-lg">
323
- <div>
324
- <p className="font-medium">Driver's License</p>
325
- <p className="text-sm text-text-secondary">
326
- {driver.licenseNumber} - {driver.licenseType}
327
- </p>
328
- <p className="text-sm text-text-secondary">
329
- Expires: {formatDate(driver.licenseExpiry)}
330
- </p>
331
- </div>
332
- <Badge variant="default">Valid</Badge>
322
+ <div className="flex items-center justify-between p-3 border rounded-lg">
323
+ <div>
324
+ <p className="font-medium">Driver&apos;s License</p>
325
+ <p className="text-sm text-text-secondary">
326
+ {driver.licenseNumber || '—'}
327
+ {driver.licenseType ? ` - ${driver.licenseType}` : ''}
328
+ </p>
329
+ <p className="text-sm text-text-secondary">
330
+ Expires: {formatDate(driver.licenseExpiry)}
331
+ </p>
333
332
  </div>
334
- {driver.documents.map((doc) => (
335
- <div
336
- key={doc.id}
337
- className="flex items-center justify-between p-3 border rounded-lg"
338
- >
339
- <div>
340
- <p className="font-medium capitalize">{doc.type.replace('_', ' ')}</p>
341
- <p className="text-sm text-text-secondary">
342
- Expires: {formatDate(doc.expiryDate)}
343
- </p>
344
- </div>
345
- <Badge variant={doc.verified ? 'default' : 'secondary'}>
346
- {doc.verified ? 'Verified' : 'Pending'}
347
- </Badge>
348
- </div>
349
- ))}
350
333
  </div>
351
334
  </CardContent>
352
335
  </Card>
336
+
337
+ {/* Documents — real files via wspace-files-svc */}
338
+ <DocumentsCard entityType="driver" entityId={driver.id} />
353
339
  </div>
354
340
 
355
341
  <div className="space-y-6">
@@ -388,7 +374,7 @@ export default function DriverDetailsPage() {
388
374
  {formatCurrency(driver.earnings.totalEarnings)}
389
375
  </p>
390
376
  </div>
391
- <div className="grid grid-cols-2 gap-3">
377
+ <div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
392
378
  <div>
393
379
  <p className="text-sm text-text-secondary">This Week</p>
394
380
  <p className="font-medium">{formatCurrency(driver.earnings.weeklyEarnings)}</p>
@@ -2,13 +2,15 @@
2
2
  * Drivers Page - List of all drivers
3
3
  */
4
4
 
5
- import { Users, Plus, RefreshCw, Download, Star } from 'lucide-react';
5
+ import { useState } from 'react';
6
+ import { Users, Plus, RefreshCw, Download, Star, UserCheck, Coffee, UserMinus } from 'lucide-react';
6
7
  import { PageLayout } from '../components/PageLayout';
8
+ import { MetricCard, MetricGrid } from '../components/MetricCard';
7
9
  import { DataTable, type Column } from '../components/DataTable';
8
10
  import { StatusBadge } from '../components/StatusBadge';
9
11
  import { Avatar, AvatarFallback } from '../components/ui';
10
- import { CTAOverflowMenu, EmphasisPanel, PagePurpose } from '@burdenoff/fe-libs/ui';
11
- import { useDrivers } from '../hooks/useFleet';
12
+ import { CTAOverflowMenu, PagePurpose } from '@burdenoff/fe-libs/ui';
13
+ import { useDrivers, useFleetStats } from '../hooks/useFleet';
12
14
  import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
13
15
  import { formatDate } from '../utils/formatters';
14
16
  import { joinPath } from '../utils/navigation';
@@ -16,7 +18,15 @@ import type { Driver } from '../types';
16
18
 
17
19
  export default function DriversPage() {
18
20
  const { basePath, navigate } = useMoveTheWheels();
19
- const { drivers, totalDrivers, isLoading, refetch } = useDrivers();
21
+ const [page, setPage] = useState(0);
22
+ const [pageSize, setPageSize] = useState(25);
23
+ const { drivers, isLoading, refetch } = useDrivers({
24
+ limit: pageSize,
25
+ offset: page * pageSize,
26
+ });
27
+ // Workforce totals + tiles from the live aggregate, not the current page.
28
+ const { stats } = useFleetStats();
29
+ const totalDrivers = stats.totalDrivers;
20
30
 
21
31
  const goTo = (path: string) => navigate?.(joinPath(basePath, path));
22
32
 
@@ -99,7 +109,11 @@ export default function DriversPage() {
99
109
  }}
100
110
  actions={[
101
111
  { label: 'Refresh', icon: <RefreshCw size={16} />, onSelect: () => refetch() },
102
- { label: 'Export', icon: <Download size={16} />, onSelect: () => goTo('import-export') },
112
+ {
113
+ label: 'Export',
114
+ icon: <Download size={16} />,
115
+ onSelect: () => goTo('import-export'),
116
+ },
103
117
  ]}
104
118
  />
105
119
  }
@@ -110,32 +124,38 @@ export default function DriversPage() {
110
124
  assigning them to an order or route.
111
125
  </PagePurpose>
112
126
 
113
- {/* Workforce at a glance — the page's single emphasis zone */}
114
- <EmphasisPanel>
115
- <p className="text-sm font-medium text-text-secondary">Workforce at a glance</p>
116
- <div className="mt-3 grid grid-cols-3 gap-4">
117
- <div>
118
- <p className="text-2xl font-bold text-text-primary tabular-nums">{totalDrivers}</p>
119
- <p className="text-xs text-text-secondary">Total drivers</p>
120
- </div>
121
- <div>
122
- <p className="text-2xl font-bold text-text-primary tabular-nums">
123
- {drivers.filter((d) => d.status === 'available' || d.status === 'busy').length}
124
- </p>
125
- <p className="text-xs text-text-secondary">On duty / available</p>
126
- </div>
127
- <div>
128
- <p className="text-2xl font-bold text-text-primary tabular-nums">
129
- {drivers.length > 0
130
- ? (drivers.reduce((sum, d) => sum + d.ratings.overall, 0) / drivers.length).toFixed(
131
- 1
132
- )
133
- : '—'}
134
- </p>
135
- <p className="text-xs text-text-secondary">Avg rating</p>
136
- </div>
137
- </div>
138
- </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>
139
159
 
140
160
  <DataTable
141
161
  columns={columns}
@@ -146,6 +166,14 @@ export default function DriversPage() {
146
166
  searchFields={['fullName', 'employeeId'] as (keyof Driver)[]}
147
167
  onRowClick={(row) => goTo(`fleet/drivers/${row.id}`)}
148
168
  getRowId={(row) => row.id}
169
+ pageIndex={page}
170
+ pageSize={pageSize}
171
+ onPageChange={setPage}
172
+ onPageSizeChange={(size) => {
173
+ setPageSize(size);
174
+ setPage(0);
175
+ }}
176
+ totalItems={totalDrivers}
149
177
  emptyIllustration="empty-people"
150
178
  emptyTitle="No drivers yet"
151
179
  emptyDescription="Add drivers to start assigning deliveries and routes."