@burdenoff/microfe-movethewheels 2026.626.2 → 2026.703.1
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-AvsMbunM.js → AIAssistantPage-C3ovZ0Fh.js} +100 -73
- package/dist/AnalyticsPage-O8SpY-V4.js +215 -0
- package/dist/{CreateOrderPage-CjlnEtk3.js → CreateOrderPage-CB1_-_mN.js} +33 -35
- package/dist/{CustomerDetailsPage-I6cdDHUi.js → CustomerDetailsPage-BaeKEI1D.js} +42 -44
- package/dist/{CustomersPage-CGGfD4ki.js → CustomersPage-DRaLuHMC.js} +26 -28
- package/dist/{DashboardPage-D2HRqwg9.js → DashboardPage-DPjhNfKB.js} +85 -83
- package/dist/{DataTable-CD9ZhpLB.js → DataTable-DmciIKKM.js} +3 -3
- package/dist/{DriverDetailsPage-rg_pGNuP.js → DriverDetailsPage-CSSQTOzn.js} +52 -54
- package/dist/{DriversPage-DKiynISH.js → DriversPage-C2Bk0w4Q.js} +24 -26
- package/dist/{FinancePage-Vg8WPs38.js → FinancePage-lmp0Sdsw.js} +9 -9
- package/dist/FleetPage-DZn5pIqd.js +365 -0
- package/dist/ImportExportPage-BIUJZc7O.js +290 -0
- package/dist/InventoryPage-Cxh1zYJd.js +253 -0
- package/dist/{LiveTrackingPage-DQ1q4Id3.js → LiveTrackingPage-BY1yAkIt.js} +60 -56
- package/dist/MarketplacePage-RMuL17EA.js +247 -0
- package/dist/{MetricCard-B7yMyvMX.js → MetricCard-7aYgzZHK.js} +1 -1
- package/dist/{OrderDetailsPage-CFASgsYX.js → OrderDetailsPage-C2Mf4KSy.js} +83 -85
- package/dist/{OrdersListPage-BHeT8S5o.js → OrdersListPage-BrPBtnhp.js} +31 -33
- package/dist/{PageLayout-D3N8MCTm.js → PageLayout-D4LDHJJq.js} +483 -422
- package/dist/{ProductDetailsPage-CFGcbaHo.js → ProductDetailsPage-CezzzZcj.js} +27 -29
- package/dist/{ProductsPage--xEpbQ8A.js → ProductsPage-I5DilIoY.js} +21 -23
- package/dist/ReportsPage-BDXNmUng.js +284 -0
- package/dist/RouteDetailsPage-BcomL20M.js +263 -0
- package/dist/RoutesPage-lqX9ylHP.js +144 -0
- package/dist/SettingsPage-CQVIOaNs.js +421 -0
- package/dist/StatusBadge-NpTeHVlw.js +152 -0
- package/dist/{TrackingPage-CcRc_dva.js → TrackingPage-BXrjz7V8.js} +59 -61
- package/dist/{VehicleDetailsPage-3YDMPsPs.js → VehicleDetailsPage-DZjLcJgr.js} +40 -42
- package/dist/{VehiclesPage-UlxPWP9K.js → VehiclesPage-6W39XT5W.js} +19 -21
- package/dist/{WarehouseDetailsPage-Db7KeN-d.js → WarehouseDetailsPage-C10pgRGW.js} +38 -40
- package/dist/{WarehousesPage-CuTO-OPp.js → WarehousesPage-CHvBqNaQ.js} +15 -17
- package/dist/{arrow-left-CBpZKkpq.js → arrow-left-n48Y-Wis.js} +1 -1
- package/dist/{box-CAaiElsb.js → box-D9Pal4HM.js} +1 -1
- package/dist/{chart-column-D1Ym15-S.js → chart-column-BFIJysDR.js} +1 -1
- package/dist/{chevron-right-BIFGi0UA.js → chevron-right-EMIIyyJ1.js} +1 -1
- package/dist/{circle-alert-DjmNo5_L.js → circle-alert-iY24lSlp.js} +1 -1
- package/dist/{circle-check-big-CjuPVo3_.js → circle-check-big-w0TrLKMF.js} +1 -1
- package/dist/{clock-BjkdnddD.js → clock-DJZtdl8u.js} +1 -1
- package/dist/{dollar-sign-IB4O0wuQ.js → dollar-sign-CgKQRz_e.js} +1 -1
- package/dist/{download-Dpa9vqoG.js → download-DRXxoiQR.js} +1 -1
- package/dist/{file-text-C8Uioy11.js → file-text-DWWY6_f5.js} +1 -1
- package/dist/{filter-Da5OZaHI.js → filter-DloLC2eE.js} +1 -1
- package/dist/index.js +119 -111
- package/dist/{map-_Per4sT9.js → map-CkJaULb2.js} +1 -1
- package/dist/{map-pin-BSaf5IDL.js → map-pin-BGiCvyCs.js} +1 -1
- package/dist/{navigation-DaTbrhw2.js → navigation-D36prnAn.js} +1 -1
- package/dist/{package-BE7U0wQE.js → package-BChUzpJS.js} +1 -1
- package/dist/{phone-DV4StZiA.js → phone-Ac9s3mo_.js} +1 -1
- package/dist/{plus-Dl1rLeJR.js → plus-Z76q-FZm.js} +1 -1
- package/dist/{refresh-cw-CWfERAit.js → refresh-cw-DKt6f5T6.js} +1 -1
- package/dist/{save-XH8vydOn.js → save-CojywNhG.js} +1 -1
- package/dist/{search-OxoIiL_C.js → search-CDGefqb3.js} +1 -1
- package/dist/{square-pen-hLIbFQFV.js → square-pen-CopE9GmB.js} +1 -1
- package/dist/{star-YsVV3MNQ.js → star-nHlLjDKR.js} +1 -1
- package/dist/{store-RCBl7HB-.js → store-BR9Voi5E.js} +1 -1
- package/dist/{trending-up-C-85AwGT.js → trending-up-B7W-GL6P.js} +1 -1
- package/dist/{triangle-alert-BOhfGgmh.js → triangle-alert-BonHFFNz.js} +1 -1
- package/dist/{truck-CtDjNFbi.js → truck-DrlfSpRC.js} +1 -1
- package/dist/{useAnalytics-ph7eTIK6.js → useAnalytics-CgdzUfY_.js} +40 -40
- package/dist/useAssistant-D4v3oxD3.js +84 -0
- package/dist/{useCustomers-bS3a4ytk.js → useCustomers-QrlozybF.js} +24 -24
- package/dist/{useFleet-BdETplNE.js → useFleet-DiAC-IiH.js} +70 -67
- package/dist/useImportExport-BLMnAKCC.js +55 -0
- package/dist/{useInventory-Dwn18FPz.js → useInventory-DKoks37x.js} +33 -33
- package/dist/{useOrders-D_3_hGMp.js → useOrders-D3UZVy8Y.js} +42 -42
- package/dist/useReports-Puq4DCH9.js +78 -0
- package/dist/{useRoutes-v4aBaS-E.js → useRoutes-Do57LvRb.js} +31 -31
- package/dist/useSettings-CkDH_-gM.js +81 -0
- package/dist/{useTracking-De2KIUNu.js → useTracking-GJify4HN.js} +22 -22
- package/dist/{user-Dz5TJZFs.js → user-CjSoudeu.js} +1 -1
- package/dist/{users-g2N0eL-Z.js → users-BAM4gHTn.js} +1 -1
- package/dist/{warehouse-D3WWDeTX.js → warehouse-C7MSr47B.js} +1 -1
- package/dist/{wrench-CmxY1n4I.js → wrench-CANXWxF-.js} +1 -1
- package/package.json +1 -1
- package/src/movethewheels/MoveTheWheelsRoutes.tsx +27 -3
- package/src/movethewheels/components/StatusBadge.tsx +18 -37
- package/src/movethewheels/constants/index.ts +67 -26
- package/src/movethewheels/hooks/index.ts +12 -0
- package/src/movethewheels/hooks/useAnalytics.ts +20 -6
- package/src/movethewheels/hooks/useAssistant.ts +74 -0
- package/src/movethewheels/hooks/useFleet.ts +63 -59
- package/src/movethewheels/hooks/useImportExport.ts +101 -0
- package/src/movethewheels/hooks/useReports.ts +158 -0
- package/src/movethewheels/hooks/useSettings.ts +120 -0
- package/src/movethewheels/pages/AIAssistantPage.tsx +53 -15
- package/src/movethewheels/pages/AnalyticsPage.tsx +67 -64
- package/src/movethewheels/pages/CustomerDetailsPage.tsx +1 -1
- package/src/movethewheels/pages/CustomersPage.tsx +1 -1
- package/src/movethewheels/pages/DashboardPage.tsx +42 -22
- package/src/movethewheels/pages/DriverDetailsPage.tsx +1 -1
- package/src/movethewheels/pages/DriversPage.tsx +1 -1
- package/src/movethewheels/pages/FleetPage.tsx +96 -38
- package/src/movethewheels/pages/ImportExportPage.tsx +174 -68
- package/src/movethewheels/pages/InventoryPage.tsx +66 -28
- package/src/movethewheels/pages/LiveTrackingPage.tsx +12 -3
- package/src/movethewheels/pages/MarketplacePage.tsx +87 -45
- package/src/movethewheels/pages/OrderDetailsPage.tsx +2 -2
- package/src/movethewheels/pages/ReportsPage.tsx +194 -90
- package/src/movethewheels/pages/RouteDetailsPage.tsx +5 -11
- package/src/movethewheels/pages/RoutesPage.tsx +4 -9
- package/src/movethewheels/pages/SettingsPage.tsx +234 -56
- package/src/movethewheels/pages/TrackingPage.tsx +6 -6
- package/src/movethewheels/providers/MoveTheWheelsProvider.tsx +7 -6
- package/src/movethewheels/store/movethewheelsStore.ts +1 -1
- package/dist/AnalyticsPage-CCLfHgQZ.js +0 -212
- package/dist/FleetPage-DddXt8r8.js +0 -332
- package/dist/ImportExportPage-BW6qO3kc.js +0 -248
- package/dist/InventoryPage-rMGuaJN-.js +0 -222
- package/dist/MarketplacePage-DKrXAv62.js +0 -219
- package/dist/ReportsPage-Bb7_90rK.js +0 -235
- package/dist/RouteDetailsPage-CVtDVb1N.js +0 -282
- package/dist/RoutesPage-s57PtgcQ.js +0 -163
- package/dist/SettingsPage-BYNCBOpN.js +0 -298
- package/dist/StatusBadge-CijYQXtb.js +0 -209
- package/dist/graphqlClient-CdJyR_ed.js +0 -55
- package/dist/navigation-BgnOfsVd.js +0 -6
- package/dist/route-Dp9JiN_E.js +0 -51
- package/dist/settings-3Rw5NwFF.js +0 -28
- package/src/movethewheels/constants/mockData.ts +0 -1342
- /package/dist/{formatters-_vJlC-47.js → formatters-DhKZ-eA1.js} +0 -0
|
@@ -56,6 +56,13 @@ export default function DashboardPage() {
|
|
|
56
56
|
}
|
|
57
57
|
};
|
|
58
58
|
|
|
59
|
+
const onRowKeyDown = (e: React.KeyboardEvent, action: () => void) => {
|
|
60
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
61
|
+
e.preventDefault();
|
|
62
|
+
action();
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
59
66
|
return (
|
|
60
67
|
<PageLayout
|
|
61
68
|
hero
|
|
@@ -93,9 +100,6 @@ export default function DashboardPage() {
|
|
|
93
100
|
label={t('movethewheels.dashboard.metrics.totalOrders', 'Total Orders')}
|
|
94
101
|
value={metrics.orders.total}
|
|
95
102
|
icon={<Package size={20} />}
|
|
96
|
-
change={`+${metrics.orders.today} ${t('movethewheels.dashboard.metrics.today', 'today')}`}
|
|
97
|
-
changeType="positive"
|
|
98
|
-
trend="up"
|
|
99
103
|
onClick={() => goTo('orders')}
|
|
100
104
|
/>
|
|
101
105
|
<MetricCard
|
|
@@ -118,9 +122,6 @@ export default function DashboardPage() {
|
|
|
118
122
|
label={t('movethewheels.dashboard.metrics.revenue', 'Revenue')}
|
|
119
123
|
value={formatCurrency(metrics.revenue.total)}
|
|
120
124
|
icon={<DollarSign size={20} />}
|
|
121
|
-
change="+12.5%"
|
|
122
|
-
changeType="positive"
|
|
123
|
-
trend="up"
|
|
124
125
|
onClick={() => goTo('analytics')}
|
|
125
126
|
/>
|
|
126
127
|
</MetricGrid>
|
|
@@ -167,8 +168,12 @@ export default function DashboardPage() {
|
|
|
167
168
|
{orders.slice(0, 5).map((order) => (
|
|
168
169
|
<div
|
|
169
170
|
key={order.id}
|
|
170
|
-
|
|
171
|
+
role="button"
|
|
172
|
+
tabIndex={0}
|
|
173
|
+
aria-label={`View order ${order.orderNumber}`}
|
|
174
|
+
className="p-4 hover:bg-bg-sunken/50 cursor-pointer transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-action-primary-bg"
|
|
171
175
|
onClick={() => goTo(`orders/${order.id}`)}
|
|
176
|
+
onKeyDown={(e) => onRowKeyDown(e, () => goTo(`orders/${order.id}`))}
|
|
172
177
|
>
|
|
173
178
|
<div className="flex items-center justify-between">
|
|
174
179
|
<div>
|
|
@@ -211,8 +216,15 @@ export default function DashboardPage() {
|
|
|
211
216
|
delivery.order ? (
|
|
212
217
|
<div
|
|
213
218
|
key={delivery.order.id}
|
|
214
|
-
|
|
219
|
+
role="button"
|
|
220
|
+
tabIndex={0}
|
|
221
|
+
aria-label={`Track order ${delivery.order.orderNumber}`}
|
|
222
|
+
className="p-4 hover:bg-bg-sunken/50 cursor-pointer transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-action-primary-bg"
|
|
215
223
|
onClick={() => goTo(`tracking?order=${delivery.order.id}`)}
|
|
224
|
+
onKeyDown={(e) =>
|
|
225
|
+
delivery.order &&
|
|
226
|
+
onRowKeyDown(e, () => goTo(`tracking?order=${delivery.order.id}`))
|
|
227
|
+
}
|
|
216
228
|
>
|
|
217
229
|
<div className="flex items-center justify-between">
|
|
218
230
|
<div>
|
|
@@ -257,18 +269,26 @@ export default function DashboardPage() {
|
|
|
257
269
|
</h2>
|
|
258
270
|
<div className="space-y-3">
|
|
259
271
|
{[
|
|
260
|
-
{
|
|
272
|
+
{
|
|
273
|
+
status: 'pending',
|
|
274
|
+
count: orderStats.byStatus.pending,
|
|
275
|
+
color: 'bg-status-warning-text',
|
|
276
|
+
},
|
|
261
277
|
{
|
|
262
278
|
status: 'in_transit',
|
|
263
279
|
count: orderStats.byStatus.in_transit,
|
|
264
|
-
color: 'bg-
|
|
280
|
+
color: 'bg-status-info-text',
|
|
265
281
|
},
|
|
266
282
|
{
|
|
267
283
|
status: 'delivered',
|
|
268
284
|
count: orderStats.byStatus.delivered,
|
|
269
|
-
color: 'bg-
|
|
285
|
+
color: 'bg-status-success-text',
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
status: 'cancelled',
|
|
289
|
+
count: orderStats.byStatus.cancelled,
|
|
290
|
+
color: 'bg-status-error-text',
|
|
270
291
|
},
|
|
271
|
-
{ status: 'cancelled', count: orderStats.byStatus.cancelled, color: 'bg-red-500' },
|
|
272
292
|
].map(({ status, count, color }) => (
|
|
273
293
|
<div key={status} className="flex items-center justify-between">
|
|
274
294
|
<div className="flex items-center gap-2">
|
|
@@ -313,7 +333,7 @@ export default function DashboardPage() {
|
|
|
313
333
|
<p className="text-text-secondary">
|
|
314
334
|
{t('movethewheels.dashboard.fleetStatus.available', 'Available')}
|
|
315
335
|
</p>
|
|
316
|
-
<p className="text-lg font-semibold text-
|
|
336
|
+
<p className="text-lg font-semibold text-status-success-text">
|
|
317
337
|
{fleetStats.vehiclesByStatus.available}
|
|
318
338
|
</p>
|
|
319
339
|
</div>
|
|
@@ -321,7 +341,7 @@ export default function DashboardPage() {
|
|
|
321
341
|
<p className="text-text-secondary">
|
|
322
342
|
{t('movethewheels.dashboard.fleetStatus.inUse', 'In Use')}
|
|
323
343
|
</p>
|
|
324
|
-
<p className="text-lg font-semibold text-
|
|
344
|
+
<p className="text-lg font-semibold text-status-info-text">
|
|
325
345
|
{fleetStats.vehiclesByStatus.assigned}
|
|
326
346
|
</p>
|
|
327
347
|
</div>
|
|
@@ -329,7 +349,7 @@ export default function DashboardPage() {
|
|
|
329
349
|
<p className="text-text-secondary">
|
|
330
350
|
{t('movethewheels.dashboard.fleetStatus.maintenance', 'Maintenance')}
|
|
331
351
|
</p>
|
|
332
|
-
<p className="text-lg font-semibold text-
|
|
352
|
+
<p className="text-lg font-semibold text-status-warning-text">
|
|
333
353
|
{fleetStats.vehiclesByStatus.maintenance}
|
|
334
354
|
</p>
|
|
335
355
|
</div>
|
|
@@ -347,15 +367,15 @@ export default function DashboardPage() {
|
|
|
347
367
|
<Card>
|
|
348
368
|
<CardHeader>
|
|
349
369
|
<CardTitle className="text-base flex items-center gap-2">
|
|
350
|
-
<AlertTriangle size={16} className="text-
|
|
370
|
+
<AlertTriangle size={16} className="text-status-warning-text" />
|
|
351
371
|
{t('movethewheels.dashboard.alerts.title', 'Alerts')}
|
|
352
372
|
</CardTitle>
|
|
353
373
|
</CardHeader>
|
|
354
374
|
<CardContent>
|
|
355
375
|
<div className="space-y-3">
|
|
356
376
|
{orderStats.byStatus.pending > 5 && (
|
|
357
|
-
<div className="flex items-start gap-3 p-2 rounded bg-
|
|
358
|
-
<Clock size={16} className="text-
|
|
377
|
+
<div className="flex items-start gap-3 p-2 rounded bg-status-warning-bg-subtle">
|
|
378
|
+
<Clock size={16} className="text-status-warning-text mt-0.5" />
|
|
359
379
|
<div>
|
|
360
380
|
<p className="text-sm font-medium">
|
|
361
381
|
{t('movethewheels.dashboard.alerts.pendingOrders.title', 'Pending Orders')}
|
|
@@ -371,8 +391,8 @@ export default function DashboardPage() {
|
|
|
371
391
|
</div>
|
|
372
392
|
)}
|
|
373
393
|
{fleetStats.vehiclesByStatus.maintenance > 0 && (
|
|
374
|
-
<div className="flex items-start gap-3 p-2 rounded bg-
|
|
375
|
-
<Truck size={16} className="text-
|
|
394
|
+
<div className="flex items-start gap-3 p-2 rounded bg-status-warning-bg-subtle">
|
|
395
|
+
<Truck size={16} className="text-status-warning-text mt-0.5" />
|
|
376
396
|
<div>
|
|
377
397
|
<p className="text-sm font-medium">
|
|
378
398
|
{t(
|
|
@@ -391,8 +411,8 @@ export default function DashboardPage() {
|
|
|
391
411
|
</div>
|
|
392
412
|
)}
|
|
393
413
|
{fleetStats.driversByStatus.offline > 0 && (
|
|
394
|
-
<div className="flex items-start gap-3 p-2 rounded bg-
|
|
395
|
-
<Users size={16} className="text-
|
|
414
|
+
<div className="flex items-start gap-3 p-2 rounded bg-bg-sunken">
|
|
415
|
+
<Users size={16} className="text-text-secondary mt-0.5" />
|
|
396
416
|
<div>
|
|
397
417
|
<p className="text-sm font-medium">
|
|
398
418
|
{t('movethewheels.dashboard.alerts.offlineDrivers.title', 'Offline Drivers')}
|
|
@@ -51,7 +51,7 @@ export default function DriverDetailsPage() {
|
|
|
51
51
|
<div className="flex items-center gap-4">
|
|
52
52
|
<StatusBadge status={driver.status} type="driver" />
|
|
53
53
|
<div className="flex items-center gap-1">
|
|
54
|
-
<Star size={16} className="text-
|
|
54
|
+
<Star size={16} className="text-status-warning-text fill-current" />
|
|
55
55
|
<span className="font-medium">{driver.ratings.overall.toFixed(1)}</span>
|
|
56
56
|
<span className="text-text-secondary">({driver.ratings.totalRatings} ratings)</span>
|
|
57
57
|
</div>
|
|
@@ -53,7 +53,7 @@ export default function DriversPage() {
|
|
|
53
53
|
header: 'Rating',
|
|
54
54
|
cell: (row) => (
|
|
55
55
|
<div className="flex items-center gap-1">
|
|
56
|
-
<Star size={14} className="text-
|
|
56
|
+
<Star size={14} className="text-status-warning-text fill-current" />
|
|
57
57
|
<span className="font-medium">{row.ratings.overall.toFixed(1)}</span>
|
|
58
58
|
<span className="text-text-secondary text-sm">({row.ratings.totalRatings})</span>
|
|
59
59
|
</div>
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import { Truck, Users, Wrench, Fuel, Star, ChevronRight } from 'lucide-react';
|
|
8
8
|
import { PageLayout, PageSection } from '../components/PageLayout';
|
|
9
|
-
import { MetricCard, MetricGrid } from '../components/MetricCard';
|
|
9
|
+
import { MetricCard, MetricGrid, MetricCardSkeleton } from '../components/MetricCard';
|
|
10
10
|
import { StatusBadge, VehicleTypeBadge } from '../components/StatusBadge';
|
|
11
11
|
import {
|
|
12
12
|
Card,
|
|
@@ -31,9 +31,32 @@ export default function FleetPage() {
|
|
|
31
31
|
return value === key ? fallback : value;
|
|
32
32
|
};
|
|
33
33
|
const { basePath, navigate } = useMoveTheWheels();
|
|
34
|
-
const {
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
const {
|
|
35
|
+
stats,
|
|
36
|
+
isLoading: statsLoading,
|
|
37
|
+
error: statsError,
|
|
38
|
+
refetch: refetchStats,
|
|
39
|
+
} = useFleetStats();
|
|
40
|
+
const {
|
|
41
|
+
vehicles,
|
|
42
|
+
isLoading: vehiclesLoading,
|
|
43
|
+
error: vehiclesError,
|
|
44
|
+
refetch: refetchVehicles,
|
|
45
|
+
} = useVehicles();
|
|
46
|
+
const {
|
|
47
|
+
drivers,
|
|
48
|
+
isLoading: driversLoading,
|
|
49
|
+
error: driversError,
|
|
50
|
+
refetch: refetchDrivers,
|
|
51
|
+
} = useDrivers();
|
|
52
|
+
|
|
53
|
+
const isLoading = statsLoading || vehiclesLoading || driversLoading;
|
|
54
|
+
const error = statsError ?? vehiclesError ?? driversError;
|
|
55
|
+
const refetchAll = () => {
|
|
56
|
+
refetchStats();
|
|
57
|
+
refetchVehicles();
|
|
58
|
+
refetchDrivers();
|
|
59
|
+
};
|
|
37
60
|
|
|
38
61
|
const goTo = (path: string) => {
|
|
39
62
|
if (navigate) {
|
|
@@ -41,6 +64,13 @@ export default function FleetPage() {
|
|
|
41
64
|
}
|
|
42
65
|
};
|
|
43
66
|
|
|
67
|
+
const onRowKeyDown = (e: React.KeyboardEvent, action: () => void) => {
|
|
68
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
69
|
+
e.preventDefault();
|
|
70
|
+
action();
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
44
74
|
return (
|
|
45
75
|
<PageLayout
|
|
46
76
|
title={t('movethewheels.fleet.title', 'Fleet Management')}
|
|
@@ -107,37 +137,57 @@ export default function FleetPage() {
|
|
|
107
137
|
/>
|
|
108
138
|
|
|
109
139
|
{/* Key Metrics */}
|
|
110
|
-
|
|
111
|
-
<
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
140
|
+
{error && !isLoading ? (
|
|
141
|
+
<Card className="border-status-error-border">
|
|
142
|
+
<CardContent className="p-6 text-center space-y-3">
|
|
143
|
+
<p className="text-status-error-text font-medium">
|
|
144
|
+
{t('movethewheels.fleet.error.title', "Couldn't load fleet data")}
|
|
145
|
+
</p>
|
|
146
|
+
<p className="text-sm text-text-secondary">{error.message}</p>
|
|
147
|
+
<Button variant="outline" onClick={refetchAll}>
|
|
148
|
+
{t('movethewheels.fleet.error.retry', 'Retry')}
|
|
149
|
+
</Button>
|
|
150
|
+
</CardContent>
|
|
151
|
+
</Card>
|
|
152
|
+
) : isLoading ? (
|
|
153
|
+
<MetricGrid columns={4}>
|
|
154
|
+
{Array.from({ length: 4 }).map((_, i) => (
|
|
155
|
+
<MetricCardSkeleton key={i} />
|
|
156
|
+
))}
|
|
157
|
+
</MetricGrid>
|
|
158
|
+
) : (
|
|
159
|
+
<MetricGrid columns={4}>
|
|
160
|
+
<MetricCard
|
|
161
|
+
label={t('movethewheels.fleet.metrics.totalVehicles', 'Total Vehicles')}
|
|
162
|
+
value={stats.totalVehicles}
|
|
163
|
+
icon={<Truck size={20} />}
|
|
164
|
+
subtitle={`${stats.vehiclesByStatus.available} ${t('movethewheels.fleet.metrics.available', 'available')}`}
|
|
165
|
+
onClick={() => goTo('fleet/vehicles')}
|
|
166
|
+
/>
|
|
167
|
+
<MetricCard
|
|
168
|
+
label={t('movethewheels.fleet.metrics.totalDrivers', 'Total Drivers')}
|
|
169
|
+
value={stats.totalDrivers}
|
|
170
|
+
icon={<Users size={20} />}
|
|
171
|
+
subtitle={`${stats.driversByStatus.available} ${t('movethewheels.fleet.metrics.available', 'available')}`}
|
|
172
|
+
onClick={() => goTo('fleet/drivers')}
|
|
173
|
+
/>
|
|
174
|
+
<MetricCard
|
|
175
|
+
treatment="glass"
|
|
176
|
+
glow
|
|
177
|
+
label={t('movethewheels.fleet.metrics.utilization', 'Fleet Utilization')}
|
|
178
|
+
value={`${stats.utilization.toFixed(0)}%`}
|
|
179
|
+
icon={<Truck size={20} />}
|
|
180
|
+
changeType={stats.utilization >= 70 ? 'positive' : 'neutral'}
|
|
181
|
+
/>
|
|
182
|
+
<MetricCard
|
|
183
|
+
label={t('movethewheels.fleet.metrics.avgDriverRating', 'Avg Driver Rating')}
|
|
184
|
+
value={stats.avgDriverRating.toFixed(1)}
|
|
185
|
+
icon={<Star size={20} />}
|
|
186
|
+
subtitle={t('movethewheels.fleet.metrics.outOfFive', 'out of 5')}
|
|
187
|
+
changeType={stats.avgDriverRating >= 4.5 ? 'positive' : 'neutral'}
|
|
188
|
+
/>
|
|
189
|
+
</MetricGrid>
|
|
190
|
+
)}
|
|
141
191
|
|
|
142
192
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
143
193
|
{/* Vehicles Overview */}
|
|
@@ -155,8 +205,12 @@ export default function FleetPage() {
|
|
|
155
205
|
{vehicles.slice(0, 5).map((vehicle) => (
|
|
156
206
|
<div
|
|
157
207
|
key={vehicle.id}
|
|
158
|
-
|
|
208
|
+
role="button"
|
|
209
|
+
tabIndex={0}
|
|
210
|
+
aria-label={`View vehicle ${vehicle.vehicleNumber}`}
|
|
211
|
+
className="p-4 hover:bg-bg-sunken/50 cursor-pointer transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-action-primary-bg"
|
|
159
212
|
onClick={() => goTo(`fleet/vehicles/${vehicle.id}`)}
|
|
213
|
+
onKeyDown={(e) => onRowKeyDown(e, () => goTo(`fleet/vehicles/${vehicle.id}`))}
|
|
160
214
|
>
|
|
161
215
|
<div className="flex items-center justify-between">
|
|
162
216
|
<div className="flex items-center gap-3">
|
|
@@ -208,8 +262,12 @@ export default function FleetPage() {
|
|
|
208
262
|
{drivers.slice(0, 5).map((driver) => (
|
|
209
263
|
<div
|
|
210
264
|
key={driver.id}
|
|
211
|
-
|
|
265
|
+
role="button"
|
|
266
|
+
tabIndex={0}
|
|
267
|
+
aria-label={`View driver ${driver.fullName}`}
|
|
268
|
+
className="p-4 hover:bg-bg-sunken/50 cursor-pointer transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-action-primary-bg"
|
|
212
269
|
onClick={() => goTo(`fleet/drivers/${driver.id}`)}
|
|
270
|
+
onKeyDown={(e) => onRowKeyDown(e, () => goTo(`fleet/drivers/${driver.id}`))}
|
|
213
271
|
>
|
|
214
272
|
<div className="flex items-center justify-between">
|
|
215
273
|
<div className="flex items-center gap-3">
|
|
@@ -232,7 +290,7 @@ export default function FleetPage() {
|
|
|
232
290
|
<div className="text-right">
|
|
233
291
|
<StatusBadge status={driver.status} type="driver" size="sm" />
|
|
234
292
|
<div className="mt-1 flex items-center gap-1 justify-end">
|
|
235
|
-
<Star size={12} className="text-
|
|
293
|
+
<Star size={12} className="text-status-warning-text fill-current" />
|
|
236
294
|
<span className="text-sm font-medium">
|
|
237
295
|
{driver.ratings.overall.toFixed(1)}
|
|
238
296
|
</span>
|