@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.
- package/dist/{AIAssistantPage-AvsMbunM.js → AIAssistantPage-C3cPsWt-.js} +100 -73
- package/dist/AnalyticsPage-Dz6g2MvK.js +215 -0
- package/dist/{CreateOrderPage-CjlnEtk3.js → CreateOrderPage-C5iwnjp4.js} +36 -67
- package/dist/CustomerDetailsPage-B6nwFyLO.js +406 -0
- package/dist/{CustomersPage-CGGfD4ki.js → CustomersPage-2Y05LVyh.js} +26 -28
- package/dist/{DashboardPage-D2HRqwg9.js → DashboardPage-BVjQi0EB.js} +85 -83
- package/dist/{DataTable-CD9ZhpLB.js → DataTable-DmciIKKM.js} +3 -3
- package/dist/DriverDetailsPage-CN3WQ0B9.js +507 -0
- package/dist/{DriversPage-DKiynISH.js → DriversPage-B0Zpx8OQ.js} +25 -27
- package/dist/{FinancePage-Vg8WPs38.js → FinancePage-DEDykSOP.js} +9 -9
- package/dist/FleetPage-DqkFi3s3.js +365 -0
- package/dist/FormError-BlOjnXOq.js +15 -0
- package/dist/ImportExportPage-DKJRCezl.js +290 -0
- package/dist/InventoryPage-DwfgCUQq.js +253 -0
- package/dist/{LiveTrackingPage-DQ1q4Id3.js → LiveTrackingPage-DCX-K-LR.js} +60 -56
- package/dist/MarketplacePage-CIQ97_zp.js +247 -0
- package/dist/{MetricCard-B7yMyvMX.js → MetricCard-BY0QrMD4.js} +1 -1
- package/dist/{OrderDetailsPage-CFASgsYX.js → OrderDetailsPage-DYiZ-9UK.js} +83 -85
- package/dist/{OrdersListPage-BHeT8S5o.js → OrdersListPage-BjDlCKJd.js} +31 -33
- package/dist/{PageLayout-D3N8MCTm.js → PageLayout-D4LDHJJq.js} +483 -422
- package/dist/ProductDetailsPage-C9uOiYLp.js +350 -0
- package/dist/{ProductsPage--xEpbQ8A.js → ProductsPage-C1G9h7Xd.js} +22 -24
- package/dist/ReportsPage-Zp53q3_8.js +284 -0
- package/dist/RouteDetailsPage-DUStXSf9.js +531 -0
- package/dist/RoutesPage-DMfoicM1.js +144 -0
- package/dist/SettingsPage-Bn-v0mbQ.js +421 -0
- package/dist/StatusBadge-BZurYHMy.js +152 -0
- package/dist/{TrackingPage-CcRc_dva.js → TrackingPage-W-jtdM3Y.js} +59 -61
- package/dist/VehicleDetailsPage-Bceo5oz3.js +410 -0
- package/dist/{VehiclesPage-UlxPWP9K.js → VehiclesPage-VBtOep91.js} +20 -22
- package/dist/WarehouseDetailsPage-L_6sWigM.js +493 -0
- package/dist/{WarehousesPage-CuTO-OPp.js → WarehousesPage-DCbXXXWE.js} +16 -18
- 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 +186 -162
- 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/trash-2-BKhar6Hs.js +32 -0
- package/dist/{trending-up-C-85AwGT.js → trending-up-Dohgd4t_.js} +1 -1
- package/dist/{triangle-alert-BOhfGgmh.js → triangle-alert-i-XQL9Rd.js} +1 -1
- package/dist/{truck-CtDjNFbi.js → truck-C0v_hbLR.js} +1 -1
- package/dist/{useAnalytics-ph7eTIK6.js → useAnalytics-DS8n0xgg.js} +40 -40
- package/dist/useAssistant-D4v3oxD3.js +84 -0
- package/dist/{useCustomers-bS3a4ytk.js → useCustomers-BmTNGxiI.js} +24 -24
- package/dist/{useFleet-BdETplNE.js → useFleet-DKqXZ16m.js} +133 -109
- package/dist/useImportExport-DHr-SWSw.js +55 -0
- package/dist/{useInventory-Dwn18FPz.js → useInventory-DkyiVY_A.js} +95 -75
- package/dist/{useOrders-D_3_hGMp.js → useOrders-Cfk1GLiq.js} +42 -42
- package/dist/useReports-CTqZr-AB.js +78 -0
- package/dist/{useRoutes-v4aBaS-E.js → useRoutes-BQWO607N.js} +31 -31
- package/dist/useSettings-CkDH_-gM.js +81 -0
- package/dist/{useTracking-De2KIUNu.js → useTracking-B-va1kO3.js} +22 -22
- package/dist/{user-Dz5TJZFs.js → user-jyFU3lXk.js} +1 -1
- package/dist/{users-g2N0eL-Z.js → users-DZ-ilS6B.js} +1 -1
- package/dist/{warehouse-D3WWDeTX.js → warehouse-6m5kXMLu.js} +1 -1
- package/dist/{wrench-CmxY1n4I.js → wrench-CANXWxF-.js} +1 -1
- package/package.json +1 -1
- package/src/movethewheels/MoveTheWheelsRoutes.tsx +31 -3
- package/src/movethewheels/components/FormError.tsx +25 -0
- 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 +170 -59
- package/src/movethewheels/hooks/useImportExport.ts +101 -0
- package/src/movethewheels/hooks/useInventory.ts +107 -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 +154 -21
- package/src/movethewheels/pages/CustomersPage.tsx +1 -1
- package/src/movethewheels/pages/DashboardPage.tsx +42 -22
- package/src/movethewheels/pages/DriverDetailsPage.tsx +160 -3
- package/src/movethewheels/pages/DriversPage.tsx +6 -2
- 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/ProductDetailsPage.tsx +146 -3
- package/src/movethewheels/pages/ProductsPage.tsx +5 -1
- package/src/movethewheels/pages/ReportsPage.tsx +194 -90
- package/src/movethewheels/pages/RouteDetailsPage.tsx +290 -36
- 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/pages/VehicleDetailsPage.tsx +158 -2
- package/src/movethewheels/pages/VehiclesPage.tsx +5 -1
- package/src/movethewheels/pages/WarehouseDetailsPage.tsx +224 -2
- package/src/movethewheels/pages/WarehousesPage.tsx +5 -1
- 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/CustomerDetailsPage-I6cdDHUi.js +0 -265
- package/dist/DriverDetailsPage-rg_pGNuP.js +0 -330
- 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/ProductDetailsPage-CFGcbaHo.js +0 -195
- 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/VehicleDetailsPage-3YDMPsPs.js +0 -220
- package/dist/WarehouseDetailsPage-Db7KeN-d.js +0 -238
- 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-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 {
|
|
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-
|
|
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-
|
|
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={{
|
|
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 {
|
|
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>
|