@burdenoff/microfe-movethewheels 2026.929.1 → 2026.929.3
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/hooks/useFleet.js +1 -1
- package/dist/movethewheels/hooks/useFleet.d.ts +2 -2
- package/dist/pages/DriverDetailsPage.js +61 -40
- package/dist/pages/RouteDetailsPage.js +353 -373
- package/package.json +1 -1
- package/src/movethewheels/hooks/useFleet.ts +3 -3
- package/src/movethewheels/pages/DriverDetailsPage.tsx +31 -9
- package/src/movethewheels/pages/RouteDetailsPage.tsx +24 -25
package/package.json
CHANGED
|
@@ -54,8 +54,8 @@ export interface CreateDriverInput {
|
|
|
54
54
|
payCurrency?: string;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
/**
|
|
58
|
-
export interface UpdateDriverInput extends CreateDriverInput {
|
|
57
|
+
/** Fields accepted when updating an existing driver; employee IDs are immutable. */
|
|
58
|
+
export interface UpdateDriverInput extends Omit<CreateDriverInput, 'employeeId'> {
|
|
59
59
|
onLeaveUntil?: string | null;
|
|
60
60
|
terminatedAt?: string;
|
|
61
61
|
terminationReason?: string;
|
|
@@ -602,7 +602,7 @@ export function mapDriver(driver: ApiDriver): Driver {
|
|
|
602
602
|
phone: driver.phone ?? '',
|
|
603
603
|
employeeId: driver.employeeId,
|
|
604
604
|
licenseNumber: driver.licenseNumber ?? '',
|
|
605
|
-
licenseType: driver.licenseClass ?? '
|
|
605
|
+
licenseType: driver.licenseClass ?? '',
|
|
606
606
|
licenseExpiry: driver.licenseExpiry ? new Date(driver.licenseExpiry) : undefined,
|
|
607
607
|
vehicleIds: driver.vehicleId ? [driver.vehicleId] : [],
|
|
608
608
|
assignedVehicle: driver.vehicleId ?? undefined,
|
|
@@ -53,6 +53,7 @@ import {
|
|
|
53
53
|
useVehicle,
|
|
54
54
|
useVehicles,
|
|
55
55
|
toApiDriverStatus,
|
|
56
|
+
type CreateDriverInput,
|
|
56
57
|
type UpdateDriverInput,
|
|
57
58
|
} from '../hooks/useFleet';
|
|
58
59
|
import { useDebouncedValue } from '../hooks/useDebouncedValue';
|
|
@@ -163,8 +164,7 @@ function NewDriverForm({ editId }: { editId?: string }) {
|
|
|
163
164
|
|
|
164
165
|
const goBack = () => navigate?.(joinPath(basePath, 'fleet/drivers'));
|
|
165
166
|
|
|
166
|
-
const buildPayload = (): UpdateDriverInput => ({
|
|
167
|
-
employeeId: form.employeeId.trim(),
|
|
167
|
+
const buildPayload = (includeLeaveFields = true): UpdateDriverInput => ({
|
|
168
168
|
firstName: form.firstName.trim(),
|
|
169
169
|
lastName: form.lastName.trim(),
|
|
170
170
|
email: form.email.trim() || undefined,
|
|
@@ -174,12 +174,18 @@ function NewDriverForm({ editId }: { editId?: string }) {
|
|
|
174
174
|
licenseExpiry: fromDateInput(form.licenseExpiry),
|
|
175
175
|
status: toApiDriverStatus(form.status),
|
|
176
176
|
vehicleId: form.vehicleId || undefined,
|
|
177
|
-
rating: form.rating ? Number(form.rating) : undefined,
|
|
177
|
+
rating: form.rating.trim() ? Number(form.rating) : undefined,
|
|
178
178
|
notes: form.notes.trim() || undefined,
|
|
179
179
|
hireDate: fromDateInput(form.hireDate),
|
|
180
|
-
baseSalary: form.baseSalary ? Number(form.baseSalary) : undefined,
|
|
181
|
-
bonusYtd: form.bonusYtd ? Number(form.bonusYtd) : undefined,
|
|
180
|
+
baseSalary: form.baseSalary.trim() ? Number(form.baseSalary) : undefined,
|
|
181
|
+
bonusYtd: form.bonusYtd.trim() ? Number(form.bonusYtd) : undefined,
|
|
182
182
|
payCurrency: form.payCurrency.trim() || undefined,
|
|
183
|
+
...(includeLeaveFields ? { onLeaveUntil: fromDateInput(form.onLeaveUntil) ?? null } : {}),
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
const buildCreatePayload = (): CreateDriverInput => ({
|
|
187
|
+
employeeId: form.employeeId.trim(),
|
|
188
|
+
...buildPayload(false),
|
|
183
189
|
});
|
|
184
190
|
|
|
185
191
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
@@ -199,7 +205,7 @@ function NewDriverForm({ editId }: { editId?: string }) {
|
|
|
199
205
|
const updated = await updateDriver(editId, buildPayload());
|
|
200
206
|
navigate?.(joinPath(basePath, `fleet/drivers/${updated.id}`));
|
|
201
207
|
} else {
|
|
202
|
-
const created = await createDriver(
|
|
208
|
+
const created = await createDriver(buildCreatePayload());
|
|
203
209
|
navigate?.(joinPath(basePath, `fleet/drivers/${created.id}`));
|
|
204
210
|
}
|
|
205
211
|
} catch (err) {
|
|
@@ -280,7 +286,8 @@ function NewDriverForm({ editId }: { editId?: string }) {
|
|
|
280
286
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
281
287
|
<div className="space-y-2">
|
|
282
288
|
<Label htmlFor="employeeId">
|
|
283
|
-
Employee ID
|
|
289
|
+
Employee ID {isEditing ? '(immutable)' : ''}{' '}
|
|
290
|
+
<span className="text-status-error-text">{!isEditing && '*'}</span>
|
|
284
291
|
</Label>
|
|
285
292
|
<Input
|
|
286
293
|
id="employeeId"
|
|
@@ -288,6 +295,7 @@ function NewDriverForm({ editId }: { editId?: string }) {
|
|
|
288
295
|
onChange={(e) => setForm((prev) => ({ ...prev, employeeId: e.target.value }))}
|
|
289
296
|
placeholder="EMP-001"
|
|
290
297
|
required
|
|
298
|
+
disabled={isEditing}
|
|
291
299
|
/>
|
|
292
300
|
</div>
|
|
293
301
|
<div className="space-y-2">
|
|
@@ -445,7 +453,19 @@ function NewDriverForm({ editId }: { editId?: string }) {
|
|
|
445
453
|
/>
|
|
446
454
|
</div>
|
|
447
455
|
<div className="space-y-2">
|
|
448
|
-
<
|
|
456
|
+
<div className="flex items-center justify-between gap-2">
|
|
457
|
+
<Label htmlFor="onLeaveUntil">On Leave Until</Label>
|
|
458
|
+
{form.onLeaveUntil && (
|
|
459
|
+
<Button
|
|
460
|
+
type="button"
|
|
461
|
+
variant="ghost"
|
|
462
|
+
size="sm"
|
|
463
|
+
onClick={() => setForm((prev) => ({ ...prev, onLeaveUntil: '' }))}
|
|
464
|
+
>
|
|
465
|
+
Clear
|
|
466
|
+
</Button>
|
|
467
|
+
)}
|
|
468
|
+
</div>
|
|
449
469
|
<Input
|
|
450
470
|
id="onLeaveUntil"
|
|
451
471
|
type="date"
|
|
@@ -823,7 +843,9 @@ export default function DriverDetailsPage() {
|
|
|
823
843
|
{driver.currentLocation.address || 'Location updating...'}
|
|
824
844
|
</p>
|
|
825
845
|
<p className="text-xs text-text-secondary mt-1">
|
|
826
|
-
|
|
846
|
+
{driver.currentLocation.timestamp
|
|
847
|
+
? `Updated ${formatDate(driver.currentLocation.timestamp, 'relative')}`
|
|
848
|
+
: 'Update time not recorded'}
|
|
827
849
|
</p>
|
|
828
850
|
</CardContent>
|
|
829
851
|
</Card>
|
|
@@ -18,7 +18,6 @@ import {
|
|
|
18
18
|
ArrowUp,
|
|
19
19
|
ArrowDown,
|
|
20
20
|
Fuel,
|
|
21
|
-
Search,
|
|
22
21
|
} from 'lucide-react';
|
|
23
22
|
import { PageLayout, LoadingState, ErrorState } from '../components/PageLayout';
|
|
24
23
|
import { StatusBadge } from '../components/StatusBadge';
|
|
@@ -57,7 +56,6 @@ import { useAssignmentSuggestions } from '../hooks/useFleetIntelligence';
|
|
|
57
56
|
import { AssignmentSuggestions } from '../components/AssignmentSuggestions';
|
|
58
57
|
import { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';
|
|
59
58
|
import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
|
|
60
|
-
import { useDebouncedValue } from '../hooks/useDebouncedValue';
|
|
61
59
|
import { formatDate, formatAddress, formatCurrency } from '../utils/formatters';
|
|
62
60
|
import { joinPath } from '../utils/navigation';
|
|
63
61
|
import { ROUTE_STATUS_COLORS } from '../constants';
|
|
@@ -76,8 +74,8 @@ function NewRouteForm({ editId }: { editId?: string }) {
|
|
|
76
74
|
const { basePath, navigate } = useMoveTheWheels();
|
|
77
75
|
const { createRoute, updateRoute } = useRoutes();
|
|
78
76
|
const { route } = useRoute(editId);
|
|
79
|
-
const [
|
|
80
|
-
const
|
|
77
|
+
const [vehicleSearch, setVehicleSearch] = useState('');
|
|
78
|
+
const [driverSearch, setDriverSearch] = useState('');
|
|
81
79
|
const isEditing = !!editId;
|
|
82
80
|
const [form, setForm] = useState<{
|
|
83
81
|
name: string;
|
|
@@ -92,14 +90,24 @@ function NewRouteForm({ editId }: { editId?: string }) {
|
|
|
92
90
|
driverId: '',
|
|
93
91
|
stops: [{ address: '', type: 'delivery' }],
|
|
94
92
|
});
|
|
95
|
-
const {
|
|
93
|
+
const {
|
|
94
|
+
vehicles,
|
|
95
|
+
isLoading: vehiclesLoading,
|
|
96
|
+
hasMore: vehiclesHaveMore,
|
|
97
|
+
totalVehicles,
|
|
98
|
+
} = useVehicles({
|
|
96
99
|
limit: 20,
|
|
97
|
-
search:
|
|
100
|
+
search: vehicleSearch || undefined,
|
|
98
101
|
driverId: form.driverId || undefined,
|
|
99
102
|
});
|
|
100
|
-
const {
|
|
103
|
+
const {
|
|
104
|
+
drivers,
|
|
105
|
+
isLoading: driversLoading,
|
|
106
|
+
hasMore: driversHaveMore,
|
|
107
|
+
totalDrivers,
|
|
108
|
+
} = useDrivers({
|
|
101
109
|
limit: 20,
|
|
102
|
-
search:
|
|
110
|
+
search: driverSearch || undefined,
|
|
103
111
|
vehicleId: form.vehicleId || undefined,
|
|
104
112
|
});
|
|
105
113
|
// The selected vehicle/driver may fall outside the current search page (e.g. when
|
|
@@ -286,22 +294,6 @@ function NewRouteForm({ editId }: { editId?: string }) {
|
|
|
286
294
|
</Select>
|
|
287
295
|
</div>
|
|
288
296
|
</div>
|
|
289
|
-
<div className="space-y-2">
|
|
290
|
-
<Label htmlFor="fleet-search">Search vehicles & drivers</Label>
|
|
291
|
-
<div className="relative">
|
|
292
|
-
<Search className="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary" />
|
|
293
|
-
<Input
|
|
294
|
-
id="fleet-search"
|
|
295
|
-
value={fleetSearch}
|
|
296
|
-
onChange={(e) => setFleetSearch(e.target.value)}
|
|
297
|
-
placeholder="Vehicle number, driver name or employee ID"
|
|
298
|
-
className="pl-9"
|
|
299
|
-
/>
|
|
300
|
-
</div>
|
|
301
|
-
<p className="text-xs text-text-secondary">
|
|
302
|
-
Narrows both pickers below as you type — up to 20 matches at a time.
|
|
303
|
-
</p>
|
|
304
|
-
</div>
|
|
305
297
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
306
298
|
<div className="space-y-2">
|
|
307
299
|
<Label htmlFor="vehicle">Vehicle (optional)</Label>
|
|
@@ -320,10 +312,13 @@ function NewRouteForm({ editId }: { editId?: string }) {
|
|
|
320
312
|
)}
|
|
321
313
|
selectedOption={selectedVehicle}
|
|
322
314
|
selectedLoading={selectedVehicleLoading}
|
|
315
|
+
onSearchChange={setVehicleSearch}
|
|
316
|
+
searchPlaceholder="Search vehicle number or type…"
|
|
317
|
+
hasMore={vehiclesHaveMore}
|
|
318
|
+
totalCount={totalVehicles}
|
|
323
319
|
required
|
|
324
320
|
placeholder="Assign later"
|
|
325
321
|
emptyText="No available vehicles."
|
|
326
|
-
// Search is driven by the shared "Search fleet" box above.
|
|
327
322
|
/>
|
|
328
323
|
</div>
|
|
329
324
|
<div className="space-y-2">
|
|
@@ -343,6 +338,10 @@ function NewRouteForm({ editId }: { editId?: string }) {
|
|
|
343
338
|
)}
|
|
344
339
|
selectedOption={selectedDriver}
|
|
345
340
|
selectedLoading={selectedDriverLoading}
|
|
341
|
+
onSearchChange={setDriverSearch}
|
|
342
|
+
searchPlaceholder="Search driver name or employee ID…"
|
|
343
|
+
hasMore={driversHaveMore}
|
|
344
|
+
totalCount={totalDrivers}
|
|
346
345
|
required
|
|
347
346
|
placeholder="Assign later"
|
|
348
347
|
emptyText="No available drivers."
|