@burdenoff/microfe-movethewheels 2026.626.2 → 2026.703.2

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 (135) hide show
  1. package/dist/{AIAssistantPage-AvsMbunM.js → AIAssistantPage-C3cPsWt-.js} +100 -73
  2. package/dist/AnalyticsPage-Dz6g2MvK.js +215 -0
  3. package/dist/{CreateOrderPage-CjlnEtk3.js → CreateOrderPage-C5iwnjp4.js} +36 -67
  4. package/dist/CustomerDetailsPage-B6nwFyLO.js +406 -0
  5. package/dist/{CustomersPage-CGGfD4ki.js → CustomersPage-2Y05LVyh.js} +26 -28
  6. package/dist/{DashboardPage-D2HRqwg9.js → DashboardPage-BVjQi0EB.js} +85 -83
  7. package/dist/{DataTable-CD9ZhpLB.js → DataTable-DmciIKKM.js} +3 -3
  8. package/dist/DriverDetailsPage-CN3WQ0B9.js +507 -0
  9. package/dist/{DriversPage-DKiynISH.js → DriversPage-B0Zpx8OQ.js} +25 -27
  10. package/dist/{FinancePage-Vg8WPs38.js → FinancePage-DEDykSOP.js} +9 -9
  11. package/dist/FleetPage-DqkFi3s3.js +365 -0
  12. package/dist/FormError-BlOjnXOq.js +15 -0
  13. package/dist/ImportExportPage-DKJRCezl.js +290 -0
  14. package/dist/InventoryPage-DwfgCUQq.js +253 -0
  15. package/dist/{LiveTrackingPage-DQ1q4Id3.js → LiveTrackingPage-DCX-K-LR.js} +60 -56
  16. package/dist/MarketplacePage-CIQ97_zp.js +247 -0
  17. package/dist/{MetricCard-B7yMyvMX.js → MetricCard-BY0QrMD4.js} +1 -1
  18. package/dist/{OrderDetailsPage-CFASgsYX.js → OrderDetailsPage-DYiZ-9UK.js} +83 -85
  19. package/dist/{OrdersListPage-BHeT8S5o.js → OrdersListPage-BjDlCKJd.js} +31 -33
  20. package/dist/{PageLayout-D3N8MCTm.js → PageLayout-D4LDHJJq.js} +483 -422
  21. package/dist/ProductDetailsPage-C9uOiYLp.js +350 -0
  22. package/dist/{ProductsPage--xEpbQ8A.js → ProductsPage-C1G9h7Xd.js} +22 -24
  23. package/dist/ReportsPage-Zp53q3_8.js +284 -0
  24. package/dist/RouteDetailsPage-DUStXSf9.js +531 -0
  25. package/dist/RoutesPage-DMfoicM1.js +144 -0
  26. package/dist/SettingsPage-Bn-v0mbQ.js +421 -0
  27. package/dist/StatusBadge-BZurYHMy.js +152 -0
  28. package/dist/{TrackingPage-CcRc_dva.js → TrackingPage-W-jtdM3Y.js} +59 -61
  29. package/dist/VehicleDetailsPage-Bceo5oz3.js +410 -0
  30. package/dist/{VehiclesPage-UlxPWP9K.js → VehiclesPage-VBtOep91.js} +20 -22
  31. package/dist/WarehouseDetailsPage-L_6sWigM.js +493 -0
  32. package/dist/{WarehousesPage-CuTO-OPp.js → WarehousesPage-DCbXXXWE.js} +16 -18
  33. package/dist/{arrow-left-CBpZKkpq.js → arrow-left-n48Y-Wis.js} +1 -1
  34. package/dist/{box-CAaiElsb.js → box-D9Pal4HM.js} +1 -1
  35. package/dist/{chart-column-D1Ym15-S.js → chart-column-BFIJysDR.js} +1 -1
  36. package/dist/{chevron-right-BIFGi0UA.js → chevron-right-EMIIyyJ1.js} +1 -1
  37. package/dist/{circle-alert-DjmNo5_L.js → circle-alert-iY24lSlp.js} +1 -1
  38. package/dist/{circle-check-big-CjuPVo3_.js → circle-check-big-w0TrLKMF.js} +1 -1
  39. package/dist/{clock-BjkdnddD.js → clock-DJZtdl8u.js} +1 -1
  40. package/dist/{dollar-sign-IB4O0wuQ.js → dollar-sign-CgKQRz_e.js} +1 -1
  41. package/dist/{download-Dpa9vqoG.js → download-DRXxoiQR.js} +1 -1
  42. package/dist/{file-text-C8Uioy11.js → file-text-DWWY6_f5.js} +1 -1
  43. package/dist/{filter-Da5OZaHI.js → filter-DloLC2eE.js} +1 -1
  44. package/dist/index.js +186 -162
  45. package/dist/{map-_Per4sT9.js → map-CkJaULb2.js} +1 -1
  46. package/dist/{map-pin-BSaf5IDL.js → map-pin-BGiCvyCs.js} +1 -1
  47. package/dist/{navigation-DaTbrhw2.js → navigation-D36prnAn.js} +1 -1
  48. package/dist/{package-BE7U0wQE.js → package-BChUzpJS.js} +1 -1
  49. package/dist/{phone-DV4StZiA.js → phone-Ac9s3mo_.js} +1 -1
  50. package/dist/{plus-Dl1rLeJR.js → plus-Z76q-FZm.js} +1 -1
  51. package/dist/{refresh-cw-CWfERAit.js → refresh-cw-DKt6f5T6.js} +1 -1
  52. package/dist/{save-XH8vydOn.js → save-CojywNhG.js} +1 -1
  53. package/dist/{search-OxoIiL_C.js → search-CDGefqb3.js} +1 -1
  54. package/dist/{square-pen-hLIbFQFV.js → square-pen-CopE9GmB.js} +1 -1
  55. package/dist/{star-YsVV3MNQ.js → star-nHlLjDKR.js} +1 -1
  56. package/dist/{store-RCBl7HB-.js → store-BR9Voi5E.js} +1 -1
  57. package/dist/trash-2-BKhar6Hs.js +32 -0
  58. package/dist/{trending-up-C-85AwGT.js → trending-up-Dohgd4t_.js} +1 -1
  59. package/dist/{triangle-alert-BOhfGgmh.js → triangle-alert-i-XQL9Rd.js} +1 -1
  60. package/dist/{truck-CtDjNFbi.js → truck-C0v_hbLR.js} +1 -1
  61. package/dist/{useAnalytics-ph7eTIK6.js → useAnalytics-DS8n0xgg.js} +40 -40
  62. package/dist/useAssistant-D4v3oxD3.js +84 -0
  63. package/dist/{useCustomers-bS3a4ytk.js → useCustomers-BmTNGxiI.js} +24 -24
  64. package/dist/{useFleet-BdETplNE.js → useFleet-DKqXZ16m.js} +133 -109
  65. package/dist/useImportExport-DHr-SWSw.js +55 -0
  66. package/dist/{useInventory-Dwn18FPz.js → useInventory-DkyiVY_A.js} +95 -75
  67. package/dist/{useOrders-D_3_hGMp.js → useOrders-Cfk1GLiq.js} +42 -42
  68. package/dist/useReports-CTqZr-AB.js +78 -0
  69. package/dist/{useRoutes-v4aBaS-E.js → useRoutes-BQWO607N.js} +31 -31
  70. package/dist/useSettings-CkDH_-gM.js +81 -0
  71. package/dist/{useTracking-De2KIUNu.js → useTracking-B-va1kO3.js} +22 -22
  72. package/dist/{user-Dz5TJZFs.js → user-jyFU3lXk.js} +1 -1
  73. package/dist/{users-g2N0eL-Z.js → users-DZ-ilS6B.js} +1 -1
  74. package/dist/{warehouse-D3WWDeTX.js → warehouse-6m5kXMLu.js} +1 -1
  75. package/dist/{wrench-CmxY1n4I.js → wrench-CANXWxF-.js} +1 -1
  76. package/package.json +1 -1
  77. package/src/movethewheels/MoveTheWheelsRoutes.tsx +31 -3
  78. package/src/movethewheels/components/FormError.tsx +25 -0
  79. package/src/movethewheels/components/StatusBadge.tsx +18 -37
  80. package/src/movethewheels/constants/index.ts +67 -26
  81. package/src/movethewheels/hooks/index.ts +12 -0
  82. package/src/movethewheels/hooks/useAnalytics.ts +20 -6
  83. package/src/movethewheels/hooks/useAssistant.ts +74 -0
  84. package/src/movethewheels/hooks/useFleet.ts +170 -59
  85. package/src/movethewheels/hooks/useImportExport.ts +101 -0
  86. package/src/movethewheels/hooks/useInventory.ts +107 -0
  87. package/src/movethewheels/hooks/useReports.ts +158 -0
  88. package/src/movethewheels/hooks/useSettings.ts +120 -0
  89. package/src/movethewheels/pages/AIAssistantPage.tsx +53 -15
  90. package/src/movethewheels/pages/AnalyticsPage.tsx +67 -64
  91. package/src/movethewheels/pages/CustomerDetailsPage.tsx +154 -21
  92. package/src/movethewheels/pages/CustomersPage.tsx +1 -1
  93. package/src/movethewheels/pages/DashboardPage.tsx +42 -22
  94. package/src/movethewheels/pages/DriverDetailsPage.tsx +160 -3
  95. package/src/movethewheels/pages/DriversPage.tsx +6 -2
  96. package/src/movethewheels/pages/FleetPage.tsx +96 -38
  97. package/src/movethewheels/pages/ImportExportPage.tsx +174 -68
  98. package/src/movethewheels/pages/InventoryPage.tsx +66 -28
  99. package/src/movethewheels/pages/LiveTrackingPage.tsx +12 -3
  100. package/src/movethewheels/pages/MarketplacePage.tsx +87 -45
  101. package/src/movethewheels/pages/OrderDetailsPage.tsx +2 -2
  102. package/src/movethewheels/pages/ProductDetailsPage.tsx +146 -3
  103. package/src/movethewheels/pages/ProductsPage.tsx +5 -1
  104. package/src/movethewheels/pages/ReportsPage.tsx +194 -90
  105. package/src/movethewheels/pages/RouteDetailsPage.tsx +290 -36
  106. package/src/movethewheels/pages/RoutesPage.tsx +4 -9
  107. package/src/movethewheels/pages/SettingsPage.tsx +234 -56
  108. package/src/movethewheels/pages/TrackingPage.tsx +6 -6
  109. package/src/movethewheels/pages/VehicleDetailsPage.tsx +158 -2
  110. package/src/movethewheels/pages/VehiclesPage.tsx +5 -1
  111. package/src/movethewheels/pages/WarehouseDetailsPage.tsx +224 -2
  112. package/src/movethewheels/pages/WarehousesPage.tsx +5 -1
  113. package/src/movethewheels/providers/MoveTheWheelsProvider.tsx +7 -6
  114. package/src/movethewheels/store/movethewheelsStore.ts +1 -1
  115. package/dist/AnalyticsPage-CCLfHgQZ.js +0 -212
  116. package/dist/CustomerDetailsPage-I6cdDHUi.js +0 -265
  117. package/dist/DriverDetailsPage-rg_pGNuP.js +0 -330
  118. package/dist/FleetPage-DddXt8r8.js +0 -332
  119. package/dist/ImportExportPage-BW6qO3kc.js +0 -248
  120. package/dist/InventoryPage-rMGuaJN-.js +0 -222
  121. package/dist/MarketplacePage-DKrXAv62.js +0 -219
  122. package/dist/ProductDetailsPage-CFGcbaHo.js +0 -195
  123. package/dist/ReportsPage-Bb7_90rK.js +0 -235
  124. package/dist/RouteDetailsPage-CVtDVb1N.js +0 -282
  125. package/dist/RoutesPage-s57PtgcQ.js +0 -163
  126. package/dist/SettingsPage-BYNCBOpN.js +0 -298
  127. package/dist/StatusBadge-CijYQXtb.js +0 -209
  128. package/dist/VehicleDetailsPage-3YDMPsPs.js +0 -220
  129. package/dist/WarehouseDetailsPage-Db7KeN-d.js +0 -238
  130. package/dist/graphqlClient-CdJyR_ed.js +0 -55
  131. package/dist/navigation-BgnOfsVd.js +0 -6
  132. package/dist/route-Dp9JiN_E.js +0 -51
  133. package/dist/settings-3Rw5NwFF.js +0 -28
  134. package/src/movethewheels/constants/mockData.ts +0 -1342
  135. /package/dist/{formatters-_vJlC-47.js → formatters-CF2S2l1m.js} +0 -0
@@ -2,8 +2,19 @@
2
2
  * Driver Details Page
3
3
  */
4
4
 
5
+ import { useState } from 'react';
5
6
  import { useParams } from 'react-router-dom';
6
- import { Users, ArrowLeft, Star, DollarSign, Clock, FileText, Edit, MapPin } from 'lucide-react';
7
+ import {
8
+ Users,
9
+ ArrowLeft,
10
+ Star,
11
+ DollarSign,
12
+ Clock,
13
+ FileText,
14
+ Edit,
15
+ MapPin,
16
+ Save,
17
+ } from 'lucide-react';
7
18
  import { PageLayout, LoadingState, ErrorState } from '../components/PageLayout';
8
19
  import { StatusBadge } from '../components/StatusBadge';
9
20
  import {
@@ -14,12 +25,157 @@ import {
14
25
  Button,
15
26
  Badge,
16
27
  Progress,
28
+ Input,
29
+ Label,
17
30
  } from '../components/ui';
18
31
  import { EmphasisPanel, NextSteps, PagePurpose } from '@burdenoff/fe-libs/ui';
19
- import { useDriver } from '../hooks/useFleet';
32
+ import { useDriver, useFleet } from '../hooks/useFleet';
20
33
  import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
21
34
  import { formatCurrency, formatDate } from '../utils/formatters';
22
35
  import { joinPath } from '../utils/navigation';
36
+ import { FormError } from '../components/FormError';
37
+
38
+ function NewDriverForm() {
39
+ const { basePath, navigate } = useMoveTheWheels();
40
+ const { createDriver } = useFleet();
41
+ const [form, setForm] = useState({
42
+ employeeId: '',
43
+ firstName: '',
44
+ lastName: '',
45
+ email: '',
46
+ phone: '',
47
+ licenseNumber: '',
48
+ });
49
+ const [submitting, setSubmitting] = useState(false);
50
+ const [error, setError] = useState<string | null>(null);
51
+
52
+ const goBack = () => navigate?.(joinPath(basePath, 'fleet/drivers'));
53
+
54
+ const handleSubmit = async (e: React.FormEvent) => {
55
+ e.preventDefault();
56
+ if (!form.employeeId.trim() || !form.firstName.trim() || !form.lastName.trim()) {
57
+ setError('Employee ID, first name and last name are required.');
58
+ return;
59
+ }
60
+ setSubmitting(true);
61
+ setError(null);
62
+ try {
63
+ const created = await createDriver({
64
+ employeeId: form.employeeId.trim(),
65
+ firstName: form.firstName.trim(),
66
+ lastName: form.lastName.trim(),
67
+ email: form.email.trim() || undefined,
68
+ phone: form.phone.trim() || undefined,
69
+ licenseNumber: form.licenseNumber.trim() || undefined,
70
+ });
71
+ navigate?.(joinPath(basePath, `fleet/drivers/${created.id}`));
72
+ } catch (err) {
73
+ console.error('Failed to create driver:', err);
74
+ setError(err instanceof Error ? err.message : 'Failed to create driver. Please try again.');
75
+ setSubmitting(false);
76
+ }
77
+ };
78
+
79
+ return (
80
+ <PageLayout
81
+ title="Add Driver"
82
+ subtitle="Add a driver to your workforce"
83
+ icon={<Users size={24} />}
84
+ headerActions={
85
+ <div className="flex gap-2">
86
+ <Button variant="outline" onClick={goBack} disabled={submitting}>
87
+ <ArrowLeft size={16} /> Cancel
88
+ </Button>
89
+ <Button onClick={handleSubmit} disabled={submitting}>
90
+ <Save size={16} /> {submitting ? 'Saving...' : 'Add Driver'}
91
+ </Button>
92
+ </div>
93
+ }
94
+ >
95
+ <PagePurpose icon={<Users size={18} />}>
96
+ Add a driver so they can be assigned to orders and routes. An employee ID and name get them on
97
+ the roster — add contact details and license number to keep the record complete.
98
+ </PagePurpose>
99
+
100
+ <form onSubmit={handleSubmit} className="max-w-2xl space-y-6">
101
+ <FormError message={error} />
102
+ <EmphasisPanel>
103
+ <h2 className="text-base font-semibold text-text-primary mb-4">Driver Details</h2>
104
+ <div className="space-y-4">
105
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
106
+ <div className="space-y-2">
107
+ <Label htmlFor="employeeId">
108
+ Employee ID <span className="text-status-error-text">*</span>
109
+ </Label>
110
+ <Input
111
+ id="employeeId"
112
+ value={form.employeeId}
113
+ onChange={(e) => setForm((prev) => ({ ...prev, employeeId: e.target.value }))}
114
+ placeholder="EMP-001"
115
+ required
116
+ />
117
+ </div>
118
+ <div className="space-y-2">
119
+ <Label htmlFor="firstName">
120
+ First Name <span className="text-status-error-text">*</span>
121
+ </Label>
122
+ <Input
123
+ id="firstName"
124
+ value={form.firstName}
125
+ onChange={(e) => setForm((prev) => ({ ...prev, firstName: e.target.value }))}
126
+ placeholder="Jane"
127
+ required
128
+ />
129
+ </div>
130
+ <div className="space-y-2">
131
+ <Label htmlFor="lastName">
132
+ Last Name <span className="text-status-error-text">*</span>
133
+ </Label>
134
+ <Input
135
+ id="lastName"
136
+ value={form.lastName}
137
+ onChange={(e) => setForm((prev) => ({ ...prev, lastName: e.target.value }))}
138
+ placeholder="Doe"
139
+ required
140
+ />
141
+ </div>
142
+ </div>
143
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
144
+ <div className="space-y-2">
145
+ <Label htmlFor="email">Email</Label>
146
+ <Input
147
+ id="email"
148
+ type="email"
149
+ value={form.email}
150
+ onChange={(e) => setForm((prev) => ({ ...prev, email: e.target.value }))}
151
+ placeholder="jane.doe@example.com"
152
+ />
153
+ </div>
154
+ <div className="space-y-2">
155
+ <Label htmlFor="phone">Phone</Label>
156
+ <Input
157
+ id="phone"
158
+ value={form.phone}
159
+ onChange={(e) => setForm((prev) => ({ ...prev, phone: e.target.value }))}
160
+ placeholder="+1-555-0100"
161
+ />
162
+ </div>
163
+ <div className="space-y-2">
164
+ <Label htmlFor="licenseNumber">License Number</Label>
165
+ <Input
166
+ id="licenseNumber"
167
+ value={form.licenseNumber}
168
+ onChange={(e) => setForm((prev) => ({ ...prev, licenseNumber: e.target.value }))}
169
+ placeholder="D1234567"
170
+ />
171
+ </div>
172
+ </div>
173
+ </div>
174
+ </EmphasisPanel>
175
+ </form>
176
+ </PageLayout>
177
+ );
178
+ }
23
179
 
24
180
  export default function DriverDetailsPage() {
25
181
  const { driverId } = useParams<{ driverId: string }>();
@@ -29,6 +185,7 @@ export default function DriverDetailsPage() {
29
185
  const goBack = () => navigate?.(joinPath(basePath, 'fleet/drivers'));
30
186
  const goTo = (path: string) => navigate?.(joinPath(basePath, path));
31
187
 
188
+ if (!driverId) return <NewDriverForm />;
32
189
  if (isLoading) return <LoadingState message="Loading driver details..." />;
33
190
  if (error || !driver) return <ErrorState title="Driver not found" onRetry={refetch} />;
34
191
 
@@ -51,7 +208,7 @@ export default function DriverDetailsPage() {
51
208
  <div className="flex items-center gap-4">
52
209
  <StatusBadge status={driver.status} type="driver" />
53
210
  <div className="flex items-center gap-1">
54
- <Star size={16} className="text-yellow-500 fill-yellow-500" />
211
+ <Star size={16} className="text-status-warning-text fill-current" />
55
212
  <span className="font-medium">{driver.ratings.overall.toFixed(1)}</span>
56
213
  <span className="text-text-secondary">({driver.ratings.totalRatings} ratings)</span>
57
214
  </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-yellow-500 fill-yellow-500" />
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>
@@ -92,7 +92,11 @@ export default function DriversPage() {
92
92
  icon={<Users size={24} />}
93
93
  headerActions={
94
94
  <CTAOverflowMenu
95
- primary={{ label: 'Add Driver', icon: <Plus size={16} />, onSelect: () => {} }}
95
+ primary={{
96
+ label: 'Add Driver',
97
+ icon: <Plus size={16} />,
98
+ onSelect: () => goTo('fleet/drivers/new'),
99
+ }}
96
100
  actions={[
97
101
  { label: 'Refresh', icon: <RefreshCw size={16} />, onSelect: () => refetch() },
98
102
  { label: 'Export', icon: <Download size={16} />, onSelect: () => {} },
@@ -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 { stats } = useFleetStats();
35
- const { vehicles } = useVehicles();
36
- const { drivers } = useDrivers();
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
- <MetricGrid columns={4}>
111
- <MetricCard
112
- label={t('movethewheels.fleet.metrics.totalVehicles', 'Total Vehicles')}
113
- value={stats.totalVehicles}
114
- icon={<Truck size={20} />}
115
- subtitle={`${stats.vehiclesByStatus.available} ${t('movethewheels.fleet.metrics.available', 'available')}`}
116
- onClick={() => goTo('fleet/vehicles')}
117
- />
118
- <MetricCard
119
- label={t('movethewheels.fleet.metrics.totalDrivers', 'Total Drivers')}
120
- value={stats.totalDrivers}
121
- icon={<Users size={20} />}
122
- subtitle={`${stats.driversByStatus.available} ${t('movethewheels.fleet.metrics.available', 'available')}`}
123
- onClick={() => goTo('fleet/drivers')}
124
- />
125
- <MetricCard
126
- treatment="glass"
127
- glow
128
- label={t('movethewheels.fleet.metrics.utilization', 'Fleet Utilization')}
129
- value={`${stats.utilization.toFixed(0)}%`}
130
- icon={<Truck size={20} />}
131
- changeType={stats.utilization >= 70 ? 'positive' : 'neutral'}
132
- />
133
- <MetricCard
134
- label={t('movethewheels.fleet.metrics.avgDriverRating', 'Avg Driver Rating')}
135
- value={stats.avgDriverRating.toFixed(1)}
136
- icon={<Star size={20} />}
137
- subtitle={t('movethewheels.fleet.metrics.outOfFive', 'out of 5')}
138
- changeType={stats.avgDriverRating >= 4.5 ? 'positive' : 'neutral'}
139
- />
140
- </MetricGrid>
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
- className="p-4 hover:bg-bg-sunken/50 cursor-pointer transition-colors"
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
- className="p-4 hover:bg-bg-sunken/50 cursor-pointer transition-colors"
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-yellow-500 fill-yellow-500" />
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>