@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.
- package/dist/{AIAssistantPage-C3cPsWt-.js → AIAssistantPage-D2gaEL1U.js} +16 -15
- package/dist/AnalyticsPage-DZMlEoBi.js +194 -0
- package/dist/CreateOrderPage-CWHx8yys.js +490 -0
- package/dist/CustomerDetailsPage-nR4oZ_ju.js +615 -0
- package/dist/CustomersPage-Bbvwvn8k.js +205 -0
- package/dist/DashboardPage-DrEhNf7N.js +542 -0
- package/dist/DataTable-CqMUD4er.js +340 -0
- package/dist/DocumentsCard-edDkgnJj.js +169 -0
- package/dist/DriverDetailsPage-whEMOWs9.js +490 -0
- package/dist/DriversPage-CHF2ZEzd.js +221 -0
- package/dist/FinancePage-Dn9GrW-1.js +175 -0
- package/dist/{FleetPage-DqkFi3s3.js → FleetPage-CrtGPy-p.js} +36 -35
- package/dist/{FormError-BlOjnXOq.js → FormError-BKcb_wT-.js} +1 -1
- package/dist/{ImportExportPage-DKJRCezl.js → ImportExportPage-WYdwZomZ.js} +52 -72
- package/dist/InventoryPage-l5A1f753.js +259 -0
- package/dist/LiveTrackingPage-DhEaNKZ4.js +429 -0
- package/dist/MarketplacePage-B9RYOb1Y.js +426 -0
- package/dist/MetricCard-y3bWHKUQ.js +191 -0
- package/dist/Notice-DOO-tjpV.js +46 -0
- package/dist/OrderDetailsPage-CwYHwk1K.js +584 -0
- package/dist/OrdersListPage-DhZJoQeI.js +386 -0
- package/dist/{PageLayout-D4LDHJJq.js → PageLayout-BniRJaQ6.js} +1 -1
- package/dist/{ProductDetailsPage-C9uOiYLp.js → ProductDetailsPage-1Gm9JKy0.js} +56 -56
- package/dist/ProductsPage-C8sn-1tN.js +186 -0
- package/dist/ReportsPage-D1tb0dcz.js +377 -0
- package/dist/RouteDetailsPage-CkMyR9b_.js +610 -0
- package/dist/{RoutesPage-DMfoicM1.js → RoutesPage-QkENzsE5.js} +58 -47
- package/dist/{SettingsPage-Bn-v0mbQ.js → SettingsPage-DV3_tU_X.js} +16 -38
- package/dist/Sparkline-K2RMJ58u.js +16729 -0
- package/dist/{StatusBadge-BZurYHMy.js → StatusBadge-BNHXYvZx.js} +7 -7
- package/dist/TrackingPage-BZrg4RCu.js +368 -0
- package/dist/VehicleDetailsPage-D-8XPZ2G.js +393 -0
- package/dist/VehiclesPage-D5BELbtM.js +184 -0
- package/dist/{WarehouseDetailsPage-L_6sWigM.js → WarehouseDetailsPage-Cmg-8Frn.js} +70 -70
- package/dist/WarehousesPage-C1Y1M867.js +182 -0
- package/dist/{arrow-left-n48Y-Wis.js → arrow-left-ClW4Isj7.js} +1 -1
- package/dist/arrow-right-CmKOmIkh.js +11 -0
- package/dist/arrow-up-BJijRLAZ.js +17 -0
- package/dist/{box-D9Pal4HM.js → box-BT3NsRQP.js} +1 -1
- package/dist/boxes-B_OGvQiW.js +54 -0
- package/dist/{chart-column-BFIJysDR.js → chart-column-DQTTdUPk.js} +1 -1
- package/dist/charts-Tj_PPTfA.js +2018 -0
- package/dist/chevron-left-B1rZyWWb.js +8 -0
- package/dist/{chevron-right-EMIIyyJ1.js → chevron-right-BIcwUJZq.js} +1 -1
- package/dist/chunk-BdsCSvBP.js +20 -0
- package/dist/{circle-alert-iY24lSlp.js → circle-alert-CNkuDNc6.js} +1 -1
- package/dist/circle-check-Cs1OVjtV.js +13 -0
- package/dist/{circle-check-big-w0TrLKMF.js → circle-check-big-C-p4eNwn.js} +1 -1
- package/dist/{clock-DJZtdl8u.js → clock-dRGG4rkF.js} +1 -1
- package/dist/{dollar-sign-CgKQRz_e.js → dollar-sign-B6RoYHHz.js} +1 -1
- package/dist/{download-DRXxoiQR.js → download-DP04iWTd.js} +1 -1
- package/dist/{file-text-DWWY6_f5.js → file-text-BuDx4hZp.js} +1 -1
- package/dist/{filter-DloLC2eE.js → filter-BD483_8g.js} +1 -1
- package/dist/{wrench-CANXWxF-.js → fuel-DLH1ag04.js} +3 -6
- package/dist/index.css +2 -0
- package/dist/index.js +150 -172
- package/dist/{map-CkJaULb2.js → map-DNQn9fyY.js} +1 -1
- package/dist/{map-pin-BGiCvyCs.js → map-pin-CqN1bbT1.js} +1 -1
- package/dist/mapMarkers-NCV9MRKR.js +28 -0
- package/dist/maps-yzLqiK_C.js +5070 -0
- package/dist/{navigation-D36prnAn.js → navigation-CgGUt2tR.js} +1 -1
- package/dist/{package-BChUzpJS.js → package-DmRF7iQD.js} +1 -1
- package/dist/{phone-Ac9s3mo_.js → phone-Cl32EXKW.js} +1 -1
- package/dist/{plus-Z76q-FZm.js → plus-DlANgOFx.js} +1 -1
- package/dist/{refresh-cw-DKt6f5T6.js → refresh-cw-rW59D4b7.js} +1 -1
- package/dist/route-CWjPrtAN.js +22 -0
- package/dist/{save-CojywNhG.js → save-UDlog7hg.js} +1 -1
- package/dist/{search-CDGefqb3.js → search-DNIG5Ck7.js} +1 -1
- package/dist/{square-pen-CopE9GmB.js → square-pen-BcfB3Grd.js} +1 -1
- package/dist/{star-nHlLjDKR.js → star-CVZHFwDx.js} +1 -1
- package/dist/{store-BR9Voi5E.js → store-bwnmCK9f.js} +1 -1
- package/dist/{trash-2-BKhar6Hs.js → trash-2-B80G12d4.js} +1 -1
- package/dist/{trending-up-Dohgd4t_.js → trending-up-CH4R4KOa.js} +1 -1
- package/dist/{triangle-alert-i-XQL9Rd.js → triangle-alert-CmS8AUU6.js} +1 -1
- package/dist/{truck-C0v_hbLR.js → truck-9rLDncav.js} +1 -1
- package/dist/upload-DanJY1Vo.js +21 -0
- package/dist/{useAssistant-D4v3oxD3.js → useAssistant-CnEwkTLm.js} +11 -28
- package/dist/{useCustomers-BmTNGxiI.js → useCustomers-ChJao1ET.js} +30 -27
- package/dist/{useFleet-DKqXZ16m.js → useFleet-g5Yri0G8.js} +82 -70
- package/dist/{useImportExport-DHr-SWSw.js → useImportExport-MYwIT3rE.js} +1 -1
- package/dist/{useInventory-DkyiVY_A.js → useInventory-BSOn-R1c.js} +84 -39
- package/dist/{useOrders-Cfk1GLiq.js → useOrders-BSiqo61w.js} +59 -48
- package/dist/{useReports-CTqZr-AB.js → useReports-fWESfsF9.js} +41 -35
- package/dist/{useRoutes-BQWO607N.js → useRoutes-zD8Yp323.js} +50 -42
- package/dist/{useSettings-CkDH_-gM.js → useSettings-B1lsWeZ1.js} +6 -6
- package/dist/{useTracking-B-va1kO3.js → useTracking-BKG0I2ln.js} +3 -3
- package/dist/{user-jyFU3lXk.js → user-DU51el3m.js} +1 -1
- package/dist/user-check-IMhk-qkX.js +20 -0
- package/dist/{users-DZ-ilS6B.js → users-B8KhJBGg.js} +1 -1
- package/dist/{warehouse-6m5kXMLu.js → warehouse-CcMMioeY.js} +1 -1
- package/dist/wrench-Dg-sgH2e.js +8 -0
- package/dist/x-C4ObLVEa.js +11 -0
- package/package.json +5 -1
- package/src/movethewheels/components/DataTable.tsx +252 -84
- package/src/movethewheels/components/DocumentsCard.tsx +102 -0
- package/src/movethewheels/components/GlassPanel.tsx +71 -0
- package/src/movethewheels/components/MetricCard.tsx +171 -73
- package/src/movethewheels/components/Notice.tsx +61 -0
- package/src/movethewheels/components/StatusBadge.tsx +7 -6
- package/src/movethewheels/components/charts/ChartShell.tsx +78 -0
- package/src/movethewheels/components/charts/Sparkline.tsx +85 -0
- package/src/movethewheels/components/charts/StatusDonut.tsx +115 -0
- package/src/movethewheels/components/charts/TrendChart.tsx +179 -0
- package/src/movethewheels/components/charts/VolumeBar.tsx +97 -0
- package/src/movethewheels/components/charts/chartTokens.ts +43 -0
- package/src/movethewheels/components/charts/index.ts +9 -0
- package/src/movethewheels/components/index.ts +51 -1
- package/src/movethewheels/components/maps/LogisticsMap.tsx +94 -0
- package/src/movethewheels/components/maps/MapCanvas.tsx +118 -0
- package/src/movethewheels/components/maps/RouteMap.tsx +71 -0
- package/src/movethewheels/components/maps/TrackingMap.tsx +102 -0
- package/src/movethewheels/components/maps/index.ts +16 -0
- package/src/movethewheels/components/maps/leaflet-setup.ts +124 -0
- package/src/movethewheels/components/maps/map-markers.css +121 -0
- package/src/movethewheels/hooks/index.ts +9 -0
- package/src/movethewheels/hooks/useAnalytics.ts +109 -4
- package/src/movethewheels/hooks/useCustomers.ts +11 -4
- package/src/movethewheels/hooks/useEntityDocuments.ts +132 -0
- package/src/movethewheels/hooks/useFleet.ts +26 -7
- package/src/movethewheels/hooks/useInventory.ts +70 -7
- package/src/movethewheels/hooks/useOrders.ts +32 -2
- package/src/movethewheels/hooks/useReports.ts +14 -4
- package/src/movethewheels/hooks/useRoutes.ts +20 -3
- package/src/movethewheels/pages/AnalyticsPage.tsx +93 -73
- package/src/movethewheels/pages/CreateOrderPage.tsx +48 -20
- package/src/movethewheels/pages/CustomerDetailsPage.tsx +239 -33
- package/src/movethewheels/pages/CustomersPage.tsx +53 -28
- package/src/movethewheels/pages/DashboardPage.tsx +238 -12
- package/src/movethewheels/pages/DriverDetailsPage.tsx +17 -31
- package/src/movethewheels/pages/DriversPage.tsx +59 -31
- package/src/movethewheels/pages/FinancePage.tsx +103 -82
- package/src/movethewheels/pages/InventoryPage.tsx +15 -3
- package/src/movethewheels/pages/LiveTrackingPage.tsx +124 -16
- package/src/movethewheels/pages/MarketplacePage.tsx +265 -50
- package/src/movethewheels/pages/OrderDetailsPage.tsx +214 -10
- package/src/movethewheels/pages/OrdersListPage.tsx +118 -47
- package/src/movethewheels/pages/ProductsPage.tsx +52 -28
- package/src/movethewheels/pages/ReportsPage.tsx +88 -4
- package/src/movethewheels/pages/RouteDetailsPage.tsx +119 -19
- package/src/movethewheels/pages/RoutesPage.tsx +22 -6
- package/src/movethewheels/pages/SettingsPage.tsx +5 -21
- package/src/movethewheels/pages/TrackingPage.tsx +76 -6
- package/src/movethewheels/pages/VehicleDetailsPage.tsx +4 -34
- package/src/movethewheels/pages/VehiclesPage.tsx +58 -27
- package/src/movethewheels/pages/WarehouseDetailsPage.tsx +1 -1
- package/src/movethewheels/pages/WarehousesPage.tsx +47 -36
- package/src/movethewheels/types/index.ts +9 -1
- package/src/movethewheels/utils/download.ts +62 -0
- package/src/movethewheels/utils/mapMarkers.ts +58 -0
- package/dist/AnalyticsPage-Dz6g2MvK.js +0 -215
- package/dist/CreateOrderPage-C5iwnjp4.js +0 -479
- package/dist/CustomerDetailsPage-B6nwFyLO.js +0 -406
- package/dist/CustomersPage-BKxMS1AV.js +0 -161
- package/dist/DashboardPage-BVjQi0EB.js +0 -387
- package/dist/DataTable-DmciIKKM.js +0 -249
- package/dist/DriverDetailsPage-CN3WQ0B9.js +0 -507
- package/dist/DriversPage-CH9rjQC-.js +0 -165
- package/dist/FinancePage-DEDykSOP.js +0 -158
- package/dist/InventoryPage-e0ze63iX.js +0 -253
- package/dist/LiveTrackingPage-DCX-K-LR.js +0 -379
- package/dist/MarketplacePage-CIQ97_zp.js +0 -247
- package/dist/MetricCard-BY0QrMD4.js +0 -146
- package/dist/OrderDetailsPage-DYiZ-9UK.js +0 -429
- package/dist/OrdersListPage-Cn90P1Xx.js +0 -307
- package/dist/ProductsPage-QJfHHpqB.js +0 -169
- package/dist/ReportsPage-Zp53q3_8.js +0 -284
- package/dist/RouteDetailsPage-DUStXSf9.js +0 -531
- package/dist/TrackingPage-W-jtdM3Y.js +0 -325
- package/dist/VehicleDetailsPage-Bceo5oz3.js +0 -410
- package/dist/VehiclesPage-ApKThYeG.js +0 -165
- package/dist/WarehousesPage-DCbXXXWE.js +0 -162
- package/dist/useAnalytics-DS8n0xgg.js +0 -297
- /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.
|
|
114
|
-
value={`${metrics.
|
|
115
|
-
icon={<
|
|
116
|
-
|
|
117
|
-
|
|
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.
|
|
134
|
-
value={`${metrics.
|
|
135
|
-
|
|
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
|
-
{/*
|
|
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} />
|
|
318
|
+
<FileText size={18} /> License
|
|
318
319
|
</CardTitle>
|
|
319
320
|
</CardHeader>
|
|
320
321
|
<CardContent>
|
|
321
|
-
<div className="
|
|
322
|
-
<div
|
|
323
|
-
<
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
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'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 {
|
|
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,
|
|
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
|
|
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
|
-
{
|
|
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 —
|
|
114
|
-
<
|
|
115
|
-
<p className="text-sm font-medium text-text-secondary">Workforce at a glance</p>
|
|
116
|
-
<
|
|
117
|
-
<
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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."
|