@burdenoff/microfe-movethewheels 2026.925.3 → 2026.929.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 +4 -1
- package/dist/hooks/useFleet.js +4 -4
- package/dist/hooks/useInventory.js +2 -1
- package/dist/movethewheels/components/StatusBadge.d.ts +3 -1
- package/dist/movethewheels/hooks/useFleet.d.ts +4 -3
- package/dist/movethewheels/hooks/useInventory.d.ts +1 -0
- package/dist/movethewheels/types/index.d.ts +6 -5
- package/dist/pages/DriverDetailsPage.js +2 -2
- package/dist/pages/DriversPage.js +2 -2
- package/dist/pages/InventoryPage.js +1 -1
- package/dist/pages/RouteDetailsPage.js +353 -373
- package/dist/pages/VehicleDetailsPage.js +49 -26
- package/dist/pages/VehiclesPage.js +2 -2
- package/dist/pages/WarehouseDetailsPage.js +73 -70
- package/dist/pages/WarehousesPage.js +1 -1
- package/package.json +1 -1
- package/src/movethewheels/components/StatusBadge.tsx +7 -4
- package/src/movethewheels/hooks/useFleet.ts +16 -7
- package/src/movethewheels/hooks/useInventory.ts +1 -1
- package/src/movethewheels/pages/DriverDetailsPage.tsx +3 -1
- package/src/movethewheels/pages/DriversPage.tsx +5 -1
- package/src/movethewheels/pages/InventoryPage.tsx +4 -1
- package/src/movethewheels/pages/RouteDetailsPage.tsx +24 -25
- package/src/movethewheels/pages/VehicleDetailsPage.tsx +35 -13
- package/src/movethewheels/pages/VehiclesPage.tsx +4 -2
- package/src/movethewheels/pages/WarehouseDetailsPage.tsx +4 -3
- package/src/movethewheels/pages/WarehousesPage.tsx +4 -1
- package/src/movethewheels/types/index.ts +14 -5
|
@@ -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."
|
|
@@ -47,7 +47,7 @@ import {
|
|
|
47
47
|
ResponsiveDialogDescription,
|
|
48
48
|
} from '@burdenoff/fe-libs/ui';
|
|
49
49
|
import { useVehicle, useVehicleDrivers, useFleet, toApiVehicleStatus } from '../hooks/useFleet';
|
|
50
|
-
import type { VehicleStatus } from '../types';
|
|
50
|
+
import type { VehicleFuelType, VehicleStatus } from '../types';
|
|
51
51
|
import { VEHICLE_STATUS_LABELS } from '../constants';
|
|
52
52
|
import { useRecordVehicleService } from '../hooks/useFleetIntelligence';
|
|
53
53
|
import { VehicleServiceCard } from '../components/VehicleServiceCard';
|
|
@@ -74,6 +74,14 @@ const VEHICLE_STATUS_OPTIONS: Array<{ value: VehicleStatus; label: string }> = (
|
|
|
74
74
|
Object.entries(VEHICLE_STATUS_LABELS) as Array<[VehicleStatus, string]>
|
|
75
75
|
).map(([value, label]) => ({ value, label }));
|
|
76
76
|
|
|
77
|
+
const FUEL_TYPE_OPTIONS: Array<{ value: VehicleFuelType; label: string }> = [
|
|
78
|
+
{ value: 'DIESEL', label: 'Diesel' },
|
|
79
|
+
{ value: 'PETROL', label: 'Petrol' },
|
|
80
|
+
{ value: 'CNG', label: 'CNG' },
|
|
81
|
+
{ value: 'ELECTRIC', label: 'Electric' },
|
|
82
|
+
{ value: 'HYBRID', label: 'Hybrid' },
|
|
83
|
+
];
|
|
84
|
+
|
|
77
85
|
const OWNERSHIP_TYPE_OPTIONS = [
|
|
78
86
|
{ value: 'owned', label: 'Owned' },
|
|
79
87
|
{ value: 'leased', label: 'Leased' },
|
|
@@ -94,7 +102,7 @@ function NewVehicleForm({ editId }: { editId?: string }) {
|
|
|
94
102
|
ownershipType: '',
|
|
95
103
|
year: '',
|
|
96
104
|
status: 'available' as VehicleStatus,
|
|
97
|
-
fuelType: '',
|
|
105
|
+
fuelType: '' as VehicleFuelType | '',
|
|
98
106
|
fuelEfficiencyLPer100Km: '',
|
|
99
107
|
capacityWeight: '',
|
|
100
108
|
capacityVolume: '',
|
|
@@ -342,12 +350,26 @@ function NewVehicleForm({ editId }: { editId?: string }) {
|
|
|
342
350
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
343
351
|
<div className="space-y-2">
|
|
344
352
|
<Label htmlFor="fuelType">Fuel Type</Label>
|
|
345
|
-
<
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
353
|
+
<Select
|
|
354
|
+
value={form.fuelType || undefined}
|
|
355
|
+
onValueChange={(value) => {
|
|
356
|
+
const fuelType = FUEL_TYPE_OPTIONS.find(
|
|
357
|
+
(option) => option.value === value
|
|
358
|
+
)?.value;
|
|
359
|
+
setForm((prev) => ({ ...prev, fuelType: fuelType ?? '' }));
|
|
360
|
+
}}
|
|
361
|
+
>
|
|
362
|
+
<SelectTrigger id="fuelType">
|
|
363
|
+
<SelectValue placeholder="Select fuel type" />
|
|
364
|
+
</SelectTrigger>
|
|
365
|
+
<SelectContent>
|
|
366
|
+
{FUEL_TYPE_OPTIONS.map((option) => (
|
|
367
|
+
<SelectItem key={option.value} value={option.value}>
|
|
368
|
+
{option.label}
|
|
369
|
+
</SelectItem>
|
|
370
|
+
))}
|
|
371
|
+
</SelectContent>
|
|
372
|
+
</Select>
|
|
351
373
|
</div>
|
|
352
374
|
<div className="space-y-2">
|
|
353
375
|
<Label htmlFor="fuelEfficiency">Fuel Efficiency (L/100km)</Label>
|
|
@@ -590,15 +612,13 @@ export default function VehicleDetailsPage() {
|
|
|
590
612
|
<div>
|
|
591
613
|
<p className="text-sm text-text-secondary">Weight</p>
|
|
592
614
|
<p className="text-2xl font-bold">
|
|
593
|
-
{vehicle.capacity.weight}
|
|
594
|
-
<span className="text-sm font-normal"> kg</span>
|
|
615
|
+
{vehicle.capacity.weight !== undefined ? `${vehicle.capacity.weight} kg` : '—'}
|
|
595
616
|
</p>
|
|
596
617
|
</div>
|
|
597
618
|
<div>
|
|
598
619
|
<p className="text-sm text-text-secondary">Volume</p>
|
|
599
620
|
<p className="text-2xl font-bold">
|
|
600
|
-
{vehicle.capacity.volume}
|
|
601
|
-
<span className="text-sm font-normal"> m³</span>
|
|
621
|
+
{vehicle.capacity.volume !== undefined ? `${vehicle.capacity.volume} m³` : '—'}
|
|
602
622
|
</p>
|
|
603
623
|
</div>
|
|
604
624
|
{vehicle.capacity.pallets && (
|
|
@@ -741,7 +761,9 @@ export default function VehicleDetailsPage() {
|
|
|
741
761
|
{vehicle.currentLocation.address || 'Location updating...'}
|
|
742
762
|
</p>
|
|
743
763
|
<p className="text-xs text-text-secondary mt-1">
|
|
744
|
-
|
|
764
|
+
{vehicle.currentLocation.timestamp
|
|
765
|
+
? `Updated ${formatDate(vehicle.currentLocation.timestamp, 'relative')}`
|
|
766
|
+
: 'Update time not recorded'}
|
|
745
767
|
</p>
|
|
746
768
|
</CardContent>
|
|
747
769
|
</Card>
|
|
@@ -112,8 +112,10 @@ export default function VehiclesPage() {
|
|
|
112
112
|
header: 'Capacity',
|
|
113
113
|
cell: (row) => (
|
|
114
114
|
<div className="text-sm">
|
|
115
|
-
<p>{row.capacity.weight} kg</p>
|
|
116
|
-
<p className="text-text-secondary">
|
|
115
|
+
<p>{row.capacity.weight !== undefined ? `${row.capacity.weight} kg` : '—'}</p>
|
|
116
|
+
<p className="text-text-secondary">
|
|
117
|
+
{row.capacity.volume !== undefined ? `${row.capacity.volume} m³` : '—'}
|
|
118
|
+
</p>
|
|
117
119
|
</div>
|
|
118
120
|
),
|
|
119
121
|
},
|
|
@@ -52,7 +52,7 @@ const WAREHOUSE_STATUS_OPTIONS = [
|
|
|
52
52
|
{ value: 'INACTIVE', label: 'Inactive' },
|
|
53
53
|
];
|
|
54
54
|
|
|
55
|
-
function NewWarehouseForm({ editId }: { editId?: string }) {
|
|
55
|
+
function NewWarehouseForm({ editId, onSaved }: { editId?: string; onSaved?: () => Promise<void> }) {
|
|
56
56
|
const { basePath, navigate } = useMoveTheWheels();
|
|
57
57
|
const { createWarehouse, updateWarehouse, deleteWarehouse } = useInventory();
|
|
58
58
|
const { warehouse } = useWarehouse(editId);
|
|
@@ -143,6 +143,7 @@ function NewWarehouseForm({ editId }: { editId?: string }) {
|
|
|
143
143
|
};
|
|
144
144
|
if (editId) {
|
|
145
145
|
await updateWarehouse(editId, warehouseInput);
|
|
146
|
+
await onSaved?.();
|
|
146
147
|
navigate?.(joinPath(basePath, `inventory/warehouses/${editId}`));
|
|
147
148
|
} else {
|
|
148
149
|
const created = await createWarehouse(warehouseInput);
|
|
@@ -426,13 +427,13 @@ export default function WarehouseDetailsPage() {
|
|
|
426
427
|
const { warehouseId } = useParams<{ warehouseId: string }>();
|
|
427
428
|
const { basePath, navigate } = useMoveTheWheels();
|
|
428
429
|
const isEditRoute = useLocation().pathname.endsWith('/edit');
|
|
429
|
-
const { warehouse, isLoading, error, refetch } = useWarehouse(warehouseId);
|
|
430
|
+
const { warehouse, isLoading, error, refetch, reload } = useWarehouse(warehouseId);
|
|
430
431
|
|
|
431
432
|
const goBack = () => navigate?.(joinPath(basePath, 'inventory/warehouses'));
|
|
432
433
|
const goTo = (path: string) => navigate?.(joinPath(basePath, path));
|
|
433
434
|
|
|
434
435
|
if (!warehouseId) return <NewWarehouseForm />;
|
|
435
|
-
if (isEditRoute) return <NewWarehouseForm editId={warehouseId} />;
|
|
436
|
+
if (isEditRoute) return <NewWarehouseForm editId={warehouseId} onSaved={reload} />;
|
|
436
437
|
if (isLoading) return <LoadingState message="Loading warehouse details..." />;
|
|
437
438
|
if (error || !warehouse) return <ErrorState title="Warehouse not found" onRetry={refetch} />;
|
|
438
439
|
|
|
@@ -90,7 +90,10 @@ export default function WarehousesPage() {
|
|
|
90
90
|
id: 'utilization',
|
|
91
91
|
header: 'Utilization',
|
|
92
92
|
cell: (row) => {
|
|
93
|
-
const pct =
|
|
93
|
+
const pct =
|
|
94
|
+
row.capacity.totalSpace > 0
|
|
95
|
+
? (row.capacity.usedSpace / row.capacity.totalSpace) * 100
|
|
96
|
+
: 0;
|
|
94
97
|
return (
|
|
95
98
|
<div className="flex items-center gap-2">
|
|
96
99
|
<Progress value={pct} className="w-16 h-2" />
|
|
@@ -308,7 +308,16 @@ export interface ProofOfDelivery {
|
|
|
308
308
|
}
|
|
309
309
|
|
|
310
310
|
// Vehicle type
|
|
311
|
-
export type VehicleType =
|
|
311
|
+
export type VehicleType =
|
|
312
|
+
| 'van'
|
|
313
|
+
| 'truck'
|
|
314
|
+
| 'motorcycle'
|
|
315
|
+
| 'bicycle'
|
|
316
|
+
| 'flatbed'
|
|
317
|
+
| 'refrigerated'
|
|
318
|
+
| 'semi_trailer'
|
|
319
|
+
| 'drone';
|
|
320
|
+
export type VehicleFuelType = 'DIESEL' | 'PETROL' | 'CNG' | 'ELECTRIC' | 'HYBRID';
|
|
312
321
|
export type VehicleStatus =
|
|
313
322
|
'available' | 'assigned' | 'maintenance' | 'out_of_service' | 'decommissioned';
|
|
314
323
|
|
|
@@ -331,8 +340,8 @@ export interface Vehicle {
|
|
|
331
340
|
ownershipType?: string;
|
|
332
341
|
year?: number;
|
|
333
342
|
capacity: {
|
|
334
|
-
weight
|
|
335
|
-
volume
|
|
343
|
+
weight?: number;
|
|
344
|
+
volume?: number;
|
|
336
345
|
pallets?: number;
|
|
337
346
|
};
|
|
338
347
|
status: VehicleStatus;
|
|
@@ -342,7 +351,7 @@ export interface Vehicle {
|
|
|
342
351
|
nextMaintenance?: Date;
|
|
343
352
|
/** Odometer reading (km) at which the next service is due. */
|
|
344
353
|
nextMaintenanceMileage?: number;
|
|
345
|
-
fuelType?:
|
|
354
|
+
fuelType?: VehicleFuelType;
|
|
346
355
|
/** Fuel consumption, litres per 100km. */
|
|
347
356
|
fuelEfficiencyLPer100Km?: number;
|
|
348
357
|
fuelLevel?: number;
|
|
@@ -371,7 +380,7 @@ export interface Driver {
|
|
|
371
380
|
employeeId: string;
|
|
372
381
|
licenseNumber: string;
|
|
373
382
|
licenseType: string;
|
|
374
|
-
licenseExpiry
|
|
383
|
+
licenseExpiry?: Date;
|
|
375
384
|
vehicleIds: string[];
|
|
376
385
|
assignedVehicle?: string;
|
|
377
386
|
status: DriverStatus;
|