@burdenoff/microfe-movethewheels 2026.925.1 → 2026.925.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/components/StatusBadge.js +31 -31
- package/dist/constants/index.js +10 -3
- package/dist/hooks/useFleet.js +85 -76
- package/dist/movethewheels/components/StatusBadge.d.ts +3 -1
- package/dist/movethewheels/constants/index.d.ts +1 -0
- package/dist/movethewheels/hooks/useFleet.d.ts +41 -1
- package/dist/movethewheels/types/index.d.ts +16 -38
- package/dist/node_modules/lucide-react/dist/esm/icons/sticky-note.js +11 -0
- package/dist/node_modules/lucide-react/dist/esm/icons/user-x.js +30 -0
- package/dist/pages/DriverDetailsPage.js +702 -421
- package/dist/pages/DriversPage.js +106 -117
- package/dist/pages/FleetPage.js +4 -10
- package/dist/pages/OrderDetailsPage.js +279 -274
- package/dist/pages/RouteDetailsPage.js +264 -252
- package/dist/pages/VehicleDetailsPage.js +403 -243
- package/package.json +1 -1
- package/src/movethewheels/components/StatusBadge.tsx +11 -2
- package/src/movethewheels/constants/index.ts +12 -0
- package/src/movethewheels/hooks/useFleet.ts +163 -44
- package/src/movethewheels/pages/DriverDetailsPage.tsx +516 -156
- package/src/movethewheels/pages/DriversPage.tsx +28 -11
- package/src/movethewheels/pages/FleetPage.tsx +7 -10
- package/src/movethewheels/pages/OrderDetailsPage.tsx +12 -7
- package/src/movethewheels/pages/RouteDetailsPage.tsx +15 -7
- package/src/movethewheels/pages/VehicleDetailsPage.tsx +137 -15
- package/src/movethewheels/types/index.ts +16 -38
|
@@ -37,14 +37,17 @@ import { useDebouncedValue } from '../hooks/useDebouncedValue';
|
|
|
37
37
|
import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
|
|
38
38
|
import { formatDate } from '../utils/formatters';
|
|
39
39
|
import { joinPath } from '../utils/navigation';
|
|
40
|
+
import { DRIVER_STATUS_LABELS } from '../constants';
|
|
40
41
|
import type { Driver, DriverStatus } from '../types';
|
|
41
42
|
|
|
43
|
+
// Sourced from the same DRIVER_STATUS_LABELS map the roster table's
|
|
44
|
+
// StatusBadge renders from, so this list can never drift from the table
|
|
45
|
+
// again (BOFF-7909).
|
|
42
46
|
const STATUS_OPTIONS: Array<{ value: DriverStatus | 'all'; label: string }> = [
|
|
43
47
|
{ value: 'all', label: 'All statuses' },
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
{ value: 'offline', label: 'Off duty' },
|
|
48
|
+
...(Object.entries(DRIVER_STATUS_LABELS) as Array<[DriverStatus, string]>).map(
|
|
49
|
+
([value, label]) => ({ value, label })
|
|
50
|
+
),
|
|
48
51
|
];
|
|
49
52
|
|
|
50
53
|
export default function DriversPage() {
|
|
@@ -74,8 +77,17 @@ export default function DriversPage() {
|
|
|
74
77
|
// Exact ON_BREAK / OFF_DUTY / INACTIVE counts — fleetStats cannot split these.
|
|
75
78
|
const { counts } = useDriverStatusCounts();
|
|
76
79
|
// Drivers carry only `vehicleId`; the roster shows the fleet number, so the
|
|
77
|
-
//
|
|
78
|
-
|
|
80
|
+
// exact vehicles referenced on this page are looked up by id — this stays
|
|
81
|
+
// correct regardless of total fleet size (a flat top-N page would silently
|
|
82
|
+
// miss drivers assigned to a vehicle outside the first N, BOFF-7909).
|
|
83
|
+
const assignedVehicleIds = useMemo(
|
|
84
|
+
() =>
|
|
85
|
+
Array.from(
|
|
86
|
+
new Set(drivers.map((driver) => driver.assignedVehicle).filter(Boolean))
|
|
87
|
+
) as string[],
|
|
88
|
+
[drivers]
|
|
89
|
+
);
|
|
90
|
+
const { vehicles } = useVehicles({ ids: assignedVehicleIds });
|
|
79
91
|
const vehicleNumberById = useMemo(
|
|
80
92
|
() => new Map(vehicles.map((vehicle) => [vehicle.id, vehicle.vehicleNumber])),
|
|
81
93
|
[vehicles]
|
|
@@ -154,10 +166,10 @@ export default function DriversPage() {
|
|
|
154
166
|
id: 'rating',
|
|
155
167
|
header: 'Rating',
|
|
156
168
|
cell: (row) =>
|
|
157
|
-
row.
|
|
169
|
+
row.rating > 0 ? (
|
|
158
170
|
<div className="flex items-center gap-1">
|
|
159
171
|
<Star size={14} className="text-status-warning-text fill-current" />
|
|
160
|
-
<span className="font-medium">{row.
|
|
172
|
+
<span className="font-medium">{row.rating.toFixed(1)}</span>
|
|
161
173
|
</div>
|
|
162
174
|
) : (
|
|
163
175
|
<span className="text-sm text-text-secondary">Not rated</span>
|
|
@@ -181,10 +193,15 @@ export default function DriversPage() {
|
|
|
181
193
|
},
|
|
182
194
|
{
|
|
183
195
|
id: 'joined',
|
|
184
|
-
header: '
|
|
185
|
-
accessorKey: '
|
|
196
|
+
header: 'Hire Date',
|
|
197
|
+
accessorKey: 'hireDate',
|
|
186
198
|
sortable: true,
|
|
187
|
-
cell: (row) =>
|
|
199
|
+
cell: (row) =>
|
|
200
|
+
row.hireDate ? (
|
|
201
|
+
formatDate(row.hireDate, 'short')
|
|
202
|
+
) : (
|
|
203
|
+
<span className="text-text-secondary">Not recorded</span>
|
|
204
|
+
),
|
|
188
205
|
},
|
|
189
206
|
];
|
|
190
207
|
|
|
@@ -302,20 +302,17 @@ export default function FleetPage() {
|
|
|
302
302
|
</Avatar>
|
|
303
303
|
<div>
|
|
304
304
|
<p className="font-medium">{driver.fullName}</p>
|
|
305
|
-
<p className="text-sm text-text-secondary">
|
|
306
|
-
{driver.employeeId} - {driver.completedOrders}{' '}
|
|
307
|
-
{t('movethewheels.fleet.drivers.deliveries', 'deliveries')}
|
|
308
|
-
</p>
|
|
305
|
+
<p className="text-sm text-text-secondary">{driver.employeeId}</p>
|
|
309
306
|
</div>
|
|
310
307
|
</div>
|
|
311
308
|
<div className="text-right">
|
|
312
309
|
<StatusBadge status={driver.status} type="driver" size="sm" />
|
|
313
|
-
|
|
314
|
-
<
|
|
315
|
-
|
|
316
|
-
{driver.
|
|
317
|
-
</
|
|
318
|
-
|
|
310
|
+
{driver.rating > 0 && (
|
|
311
|
+
<div className="mt-1 flex items-center gap-1 justify-end">
|
|
312
|
+
<Star size={12} className="text-status-warning-text fill-current" />
|
|
313
|
+
<span className="text-sm font-medium">{driver.rating.toFixed(1)}</span>
|
|
314
|
+
</div>
|
|
315
|
+
)}
|
|
319
316
|
</div>
|
|
320
317
|
</div>
|
|
321
318
|
</div>
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
ArrowRight,
|
|
24
24
|
XCircle,
|
|
25
25
|
UserCheck,
|
|
26
|
+
Search,
|
|
26
27
|
} from 'lucide-react';
|
|
27
28
|
import { PageLayout, LoadingState, ErrorState } from '../components/PageLayout';
|
|
28
29
|
import { StatusBadge, PriorityBadge, PaymentStatusBadge } from '../components/StatusBadge';
|
|
@@ -577,13 +578,17 @@ export default function OrderDetailsPage() {
|
|
|
577
578
|
|
|
578
579
|
<div className="space-y-3">
|
|
579
580
|
<div className="space-y-1.5">
|
|
580
|
-
<Label htmlFor="assign-fleet-search">Search
|
|
581
|
-
<
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
581
|
+
<Label htmlFor="assign-fleet-search">Search vehicles & drivers</Label>
|
|
582
|
+
<div className="relative">
|
|
583
|
+
<Search className="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary" />
|
|
584
|
+
<Input
|
|
585
|
+
id="assign-fleet-search"
|
|
586
|
+
value={fleetSearch}
|
|
587
|
+
onChange={(event) => setFleetSearch(event.target.value)}
|
|
588
|
+
placeholder="Vehicle number, driver name or employee ID"
|
|
589
|
+
className="pl-9"
|
|
590
|
+
/>
|
|
591
|
+
</div>
|
|
587
592
|
</div>
|
|
588
593
|
<div className="space-y-1.5">
|
|
589
594
|
<Label htmlFor="assign-vehicle">Vehicle</Label>
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
ArrowUp,
|
|
19
19
|
ArrowDown,
|
|
20
20
|
Fuel,
|
|
21
|
+
Search,
|
|
21
22
|
} from 'lucide-react';
|
|
22
23
|
import { PageLayout, LoadingState, ErrorState } from '../components/PageLayout';
|
|
23
24
|
import { StatusBadge } from '../components/StatusBadge';
|
|
@@ -276,13 +277,20 @@ function NewRouteForm({ editId }: { editId?: string }) {
|
|
|
276
277
|
</div>
|
|
277
278
|
</div>
|
|
278
279
|
<div className="space-y-2">
|
|
279
|
-
<Label htmlFor="fleet-search">Search
|
|
280
|
-
<
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
280
|
+
<Label htmlFor="fleet-search">Search vehicles & drivers</Label>
|
|
281
|
+
<div className="relative">
|
|
282
|
+
<Search className="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary" />
|
|
283
|
+
<Input
|
|
284
|
+
id="fleet-search"
|
|
285
|
+
value={fleetSearch}
|
|
286
|
+
onChange={(e) => setFleetSearch(e.target.value)}
|
|
287
|
+
placeholder="Vehicle number, driver name or employee ID"
|
|
288
|
+
className="pl-9"
|
|
289
|
+
/>
|
|
290
|
+
</div>
|
|
291
|
+
<p className="text-xs text-text-secondary">
|
|
292
|
+
Narrows both pickers below as you type — up to 20 matches at a time.
|
|
293
|
+
</p>
|
|
286
294
|
</div>
|
|
287
295
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
288
296
|
<div className="space-y-2">
|
|
@@ -25,7 +25,8 @@ import {
|
|
|
25
25
|
AvatarFallback,
|
|
26
26
|
} from '../components/ui';
|
|
27
27
|
import { EmphasisPanel, NextSteps, PagePurpose } from '@burdenoff/fe-libs/ui';
|
|
28
|
-
import { useVehicle, useVehicleDrivers, useFleet } from '../hooks/useFleet';
|
|
28
|
+
import { useVehicle, useVehicleDrivers, useFleet, toApiVehicleStatus } from '../hooks/useFleet';
|
|
29
|
+
import type { VehicleStatus } from '../types';
|
|
29
30
|
import { useRecordVehicleService } from '../hooks/useFleetIntelligence';
|
|
30
31
|
import { VehicleServiceCard } from '../components/VehicleServiceCard';
|
|
31
32
|
import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
|
|
@@ -47,6 +48,19 @@ const VEHICLE_TYPE_OPTIONS = [
|
|
|
47
48
|
{ value: 'DRONE', label: 'Drone' },
|
|
48
49
|
];
|
|
49
50
|
|
|
51
|
+
const VEHICLE_STATUS_OPTIONS: Array<{ value: VehicleStatus; label: string }> = [
|
|
52
|
+
{ value: 'available', label: 'Available' },
|
|
53
|
+
{ value: 'assigned', label: 'In Transit' },
|
|
54
|
+
{ value: 'maintenance', label: 'Maintenance' },
|
|
55
|
+
{ value: 'out_of_service', label: 'Out of Service' },
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
const OWNERSHIP_TYPE_OPTIONS = [
|
|
59
|
+
{ value: 'owned', label: 'Owned' },
|
|
60
|
+
{ value: 'leased', label: 'Leased' },
|
|
61
|
+
{ value: 'rented', label: 'Rented' },
|
|
62
|
+
];
|
|
63
|
+
|
|
50
64
|
function NewVehicleForm({ editId }: { editId?: string }) {
|
|
51
65
|
const { basePath, navigate } = useMoveTheWheels();
|
|
52
66
|
const { createVehicle, updateVehicle } = useFleet();
|
|
@@ -58,6 +72,11 @@ function NewVehicleForm({ editId }: { editId?: string }) {
|
|
|
58
72
|
make: '',
|
|
59
73
|
model: '',
|
|
60
74
|
licensePlate: '',
|
|
75
|
+
ownershipType: '',
|
|
76
|
+
year: '',
|
|
77
|
+
status: 'available' as VehicleStatus,
|
|
78
|
+
fuelType: '',
|
|
79
|
+
fuelEfficiencyLPer100Km: '',
|
|
61
80
|
});
|
|
62
81
|
const [submitting, setSubmitting] = useState(false);
|
|
63
82
|
const [error, setError] = useState<string | null>(null);
|
|
@@ -70,6 +89,14 @@ function NewVehicleForm({ editId }: { editId?: string }) {
|
|
|
70
89
|
make: vehicle.make ?? '',
|
|
71
90
|
model: vehicle.model ?? '',
|
|
72
91
|
licensePlate: vehicle.licensePlate ?? '',
|
|
92
|
+
ownershipType: vehicle.ownershipType ?? '',
|
|
93
|
+
year: vehicle.year ? String(vehicle.year) : '',
|
|
94
|
+
status: vehicle.status,
|
|
95
|
+
fuelType: vehicle.fuelType ?? '',
|
|
96
|
+
fuelEfficiencyLPer100Km:
|
|
97
|
+
vehicle.fuelEfficiencyLPer100Km !== undefined
|
|
98
|
+
? String(vehicle.fuelEfficiencyLPer100Km)
|
|
99
|
+
: '',
|
|
73
100
|
});
|
|
74
101
|
}
|
|
75
102
|
}, [editId, vehicle]);
|
|
@@ -85,23 +112,25 @@ function NewVehicleForm({ editId }: { editId?: string }) {
|
|
|
85
112
|
setSubmitting(true);
|
|
86
113
|
setError(null);
|
|
87
114
|
try {
|
|
115
|
+
const payload = {
|
|
116
|
+
vehicleNumber: form.vehicleNumber.trim(),
|
|
117
|
+
type: form.type,
|
|
118
|
+
make: form.make.trim() || undefined,
|
|
119
|
+
model: form.model.trim() || undefined,
|
|
120
|
+
licensePlate: form.licensePlate.trim() || undefined,
|
|
121
|
+
ownershipType: form.ownershipType || undefined,
|
|
122
|
+
year: form.year ? Number(form.year) : undefined,
|
|
123
|
+
status: toApiVehicleStatus(form.status),
|
|
124
|
+
fuelType: form.fuelType || undefined,
|
|
125
|
+
fuelEfficiencyLPer100Km: form.fuelEfficiencyLPer100Km
|
|
126
|
+
? Number(form.fuelEfficiencyLPer100Km)
|
|
127
|
+
: undefined,
|
|
128
|
+
};
|
|
88
129
|
if (editId) {
|
|
89
|
-
const updated = await updateVehicle(editId,
|
|
90
|
-
vehicleNumber: form.vehicleNumber.trim(),
|
|
91
|
-
type: form.type,
|
|
92
|
-
make: form.make.trim() || undefined,
|
|
93
|
-
model: form.model.trim() || undefined,
|
|
94
|
-
licensePlate: form.licensePlate.trim() || undefined,
|
|
95
|
-
});
|
|
130
|
+
const updated = await updateVehicle(editId, payload);
|
|
96
131
|
navigate?.(joinPath(basePath, `fleet/vehicles/${updated.id}`));
|
|
97
132
|
} else {
|
|
98
|
-
const created = await createVehicle(
|
|
99
|
-
vehicleNumber: form.vehicleNumber.trim(),
|
|
100
|
-
type: form.type,
|
|
101
|
-
make: form.make.trim() || undefined,
|
|
102
|
-
model: form.model.trim() || undefined,
|
|
103
|
-
licensePlate: form.licensePlate.trim() || undefined,
|
|
104
|
-
});
|
|
133
|
+
const created = await createVehicle(payload);
|
|
105
134
|
navigate?.(joinPath(basePath, `fleet/vehicles/${created.id}`));
|
|
106
135
|
}
|
|
107
136
|
} catch (err) {
|
|
@@ -207,6 +236,83 @@ function NewVehicleForm({ editId }: { editId?: string }) {
|
|
|
207
236
|
/>
|
|
208
237
|
</div>
|
|
209
238
|
</div>
|
|
239
|
+
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
240
|
+
<div className="space-y-2">
|
|
241
|
+
<Label htmlFor="ownershipType">Ownership</Label>
|
|
242
|
+
<Select
|
|
243
|
+
value={form.ownershipType || undefined}
|
|
244
|
+
onValueChange={(value) => setForm((prev) => ({ ...prev, ownershipType: value }))}
|
|
245
|
+
>
|
|
246
|
+
<SelectTrigger id="ownershipType">
|
|
247
|
+
<SelectValue placeholder="Select ownership" />
|
|
248
|
+
</SelectTrigger>
|
|
249
|
+
<SelectContent>
|
|
250
|
+
{OWNERSHIP_TYPE_OPTIONS.map((option) => (
|
|
251
|
+
<SelectItem key={option.value} value={option.value}>
|
|
252
|
+
{option.label}
|
|
253
|
+
</SelectItem>
|
|
254
|
+
))}
|
|
255
|
+
</SelectContent>
|
|
256
|
+
</Select>
|
|
257
|
+
</div>
|
|
258
|
+
<div className="space-y-2">
|
|
259
|
+
<Label htmlFor="year">Year</Label>
|
|
260
|
+
<Input
|
|
261
|
+
id="year"
|
|
262
|
+
type="number"
|
|
263
|
+
min={1980}
|
|
264
|
+
max={new Date().getFullYear() + 1}
|
|
265
|
+
value={form.year}
|
|
266
|
+
onChange={(e) => setForm((prev) => ({ ...prev, year: e.target.value }))}
|
|
267
|
+
placeholder={String(new Date().getFullYear())}
|
|
268
|
+
/>
|
|
269
|
+
</div>
|
|
270
|
+
<div className="space-y-2">
|
|
271
|
+
<Label htmlFor="vehicleStatus">Status</Label>
|
|
272
|
+
<Select
|
|
273
|
+
value={form.status}
|
|
274
|
+
onValueChange={(value) =>
|
|
275
|
+
setForm((prev) => ({ ...prev, status: value as VehicleStatus }))
|
|
276
|
+
}
|
|
277
|
+
>
|
|
278
|
+
<SelectTrigger id="vehicleStatus">
|
|
279
|
+
<SelectValue />
|
|
280
|
+
</SelectTrigger>
|
|
281
|
+
<SelectContent>
|
|
282
|
+
{VEHICLE_STATUS_OPTIONS.map((option) => (
|
|
283
|
+
<SelectItem key={option.value} value={option.value}>
|
|
284
|
+
{option.label}
|
|
285
|
+
</SelectItem>
|
|
286
|
+
))}
|
|
287
|
+
</SelectContent>
|
|
288
|
+
</Select>
|
|
289
|
+
</div>
|
|
290
|
+
</div>
|
|
291
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
292
|
+
<div className="space-y-2">
|
|
293
|
+
<Label htmlFor="fuelType">Fuel Type</Label>
|
|
294
|
+
<Input
|
|
295
|
+
id="fuelType"
|
|
296
|
+
value={form.fuelType}
|
|
297
|
+
onChange={(e) => setForm((prev) => ({ ...prev, fuelType: e.target.value }))}
|
|
298
|
+
placeholder="Diesel"
|
|
299
|
+
/>
|
|
300
|
+
</div>
|
|
301
|
+
<div className="space-y-2">
|
|
302
|
+
<Label htmlFor="fuelEfficiency">Fuel Efficiency (L/100km)</Label>
|
|
303
|
+
<Input
|
|
304
|
+
id="fuelEfficiency"
|
|
305
|
+
type="number"
|
|
306
|
+
min={0}
|
|
307
|
+
step={0.1}
|
|
308
|
+
value={form.fuelEfficiencyLPer100Km}
|
|
309
|
+
onChange={(e) =>
|
|
310
|
+
setForm((prev) => ({ ...prev, fuelEfficiencyLPer100Km: e.target.value }))
|
|
311
|
+
}
|
|
312
|
+
placeholder="12.5"
|
|
313
|
+
/>
|
|
314
|
+
</div>
|
|
315
|
+
</div>
|
|
210
316
|
</div>
|
|
211
317
|
</EmphasisPanel>
|
|
212
318
|
</form>
|
|
@@ -394,6 +500,22 @@ export default function VehicleDetailsPage() {
|
|
|
394
500
|
</p>
|
|
395
501
|
</div>
|
|
396
502
|
)}
|
|
503
|
+
{(vehicle.fuelType || vehicle.fuelEfficiencyLPer100Km !== undefined) && (
|
|
504
|
+
<div className="grid grid-cols-2 gap-4">
|
|
505
|
+
{vehicle.fuelType && (
|
|
506
|
+
<div>
|
|
507
|
+
<p className="text-sm text-text-secondary">Fuel Type</p>
|
|
508
|
+
<p className="font-medium">{vehicle.fuelType}</p>
|
|
509
|
+
</div>
|
|
510
|
+
)}
|
|
511
|
+
{vehicle.fuelEfficiencyLPer100Km !== undefined && (
|
|
512
|
+
<div>
|
|
513
|
+
<p className="text-sm text-text-secondary">Efficiency</p>
|
|
514
|
+
<p className="font-medium">{vehicle.fuelEfficiencyLPer100Km} L/100km</p>
|
|
515
|
+
</div>
|
|
516
|
+
)}
|
|
517
|
+
</div>
|
|
518
|
+
)}
|
|
397
519
|
</CardContent>
|
|
398
520
|
</Card>
|
|
399
521
|
|
|
@@ -346,6 +346,9 @@ export interface Vehicle {
|
|
|
346
346
|
nextMaintenance?: Date;
|
|
347
347
|
/** Odometer reading (km) at which the next service is due. */
|
|
348
348
|
nextMaintenanceMileage?: number;
|
|
349
|
+
fuelType?: string;
|
|
350
|
+
/** Fuel consumption, litres per 100km. */
|
|
351
|
+
fuelEfficiencyLPer100Km?: number;
|
|
349
352
|
fuelLevel?: number;
|
|
350
353
|
mileage?: number;
|
|
351
354
|
documents: VehicleDocument[];
|
|
@@ -353,7 +356,7 @@ export interface Vehicle {
|
|
|
353
356
|
}
|
|
354
357
|
|
|
355
358
|
// Driver type
|
|
356
|
-
export type DriverStatus = 'available' | 'busy' | 'offline' | 'on_break';
|
|
359
|
+
export type DriverStatus = 'available' | 'busy' | 'offline' | 'on_break' | 'inactive';
|
|
357
360
|
|
|
358
361
|
export interface DriverDocument {
|
|
359
362
|
id: string;
|
|
@@ -377,44 +380,19 @@ export interface Driver {
|
|
|
377
380
|
assignedVehicle?: string;
|
|
378
381
|
status: DriverStatus;
|
|
379
382
|
currentLocation?: Location;
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
currentHoursThisWeek: number;
|
|
393
|
-
};
|
|
394
|
-
ratings: {
|
|
395
|
-
overall: number;
|
|
396
|
-
punctuality: number;
|
|
397
|
-
communication: number;
|
|
398
|
-
carefulHandling: number;
|
|
399
|
-
professionalism: number;
|
|
400
|
-
onTimeDelivery: number;
|
|
401
|
-
totalRatings: number;
|
|
402
|
-
};
|
|
383
|
+
/** MtwDriver.rating — a single overall rating; the backend does not track per-category breakdowns. */
|
|
384
|
+
rating: number;
|
|
385
|
+
/** Free-text HR/performance notes. */
|
|
386
|
+
notes?: string;
|
|
387
|
+
hireDate?: Date;
|
|
388
|
+
baseSalary?: number;
|
|
389
|
+
bonusYtd?: number;
|
|
390
|
+
payCurrency?: string;
|
|
391
|
+
/** Set and in the future means the driver is currently on leave. */
|
|
392
|
+
onLeaveUntil?: Date;
|
|
393
|
+
terminatedAt?: Date;
|
|
394
|
+
terminationReason?: string;
|
|
403
395
|
documents: DriverDocument[];
|
|
404
|
-
earnings: {
|
|
405
|
-
totalEarnings: number;
|
|
406
|
-
weeklyEarnings: number;
|
|
407
|
-
monthlyEarnings: number;
|
|
408
|
-
pendingPayments: number;
|
|
409
|
-
completedDeliveries: number;
|
|
410
|
-
bonuses: number;
|
|
411
|
-
};
|
|
412
|
-
compliance: {
|
|
413
|
-
hoursOfService: boolean;
|
|
414
|
-
documentation: boolean;
|
|
415
|
-
vehicleInspection: boolean;
|
|
416
|
-
lastUpdate: Date;
|
|
417
|
-
};
|
|
418
396
|
createdAt: Date;
|
|
419
397
|
}
|
|
420
398
|
|