@burdenoff/microfe-movethewheels 2026.923.1 → 2026.924.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/CustomerPicker.js +94 -101
- package/dist/components/EntityCombobox.js +105 -0
- package/dist/components/OrderItemRow.js +124 -0
- package/dist/components/ui.js +2 -2
- package/dist/hooks/useFleet.js +53 -49
- package/dist/index.js +22 -22
- package/dist/movethewheels/components/CustomerPicker.d.ts +3 -2
- package/dist/movethewheels/components/EntityCombobox.d.ts +40 -0
- package/dist/movethewheels/components/OrderItemRow.d.ts +13 -0
- package/dist/movethewheels/components/ui.d.ts +1 -1
- package/dist/movethewheels/hooks/useFleet.d.ts +4 -0
- package/dist/node_modules/lucide-react/dist/esm/icons/chevrons-up-down.js +11 -0
- package/dist/pages/CreateOrderPage.js +288 -312
- package/dist/pages/OrderDetailsPage.js +291 -294
- package/dist/pages/RouteDetailsPage.js +298 -296
- package/package.json +1 -1
- package/src/movethewheels/components/CustomerPicker.tsx +26 -36
- package/src/movethewheels/components/EntityCombobox.tsx +237 -0
- package/src/movethewheels/components/OrderItemRow.tsx +172 -0
- package/src/movethewheels/components/ui.tsx +9 -0
- package/src/movethewheels/hooks/useFleet.ts +10 -4
- package/src/movethewheels/pages/CreateOrderPage.tsx +70 -88
- package/src/movethewheels/pages/OrderDetailsPage.tsx +61 -60
- package/src/movethewheels/pages/RouteDetailsPage.tsx +53 -53
|
@@ -11,7 +11,6 @@ import {
|
|
|
11
11
|
ArrowLeft,
|
|
12
12
|
Save,
|
|
13
13
|
Plus,
|
|
14
|
-
Trash2,
|
|
15
14
|
Users,
|
|
16
15
|
MapPin,
|
|
17
16
|
CalendarClock,
|
|
@@ -33,16 +32,17 @@ import {
|
|
|
33
32
|
SelectItem,
|
|
34
33
|
SelectTrigger,
|
|
35
34
|
SelectValue,
|
|
36
|
-
Separator,
|
|
37
35
|
} from '../components/ui';
|
|
38
36
|
import { EmphasisPanel, NextSteps, PagePurpose } from '@burdenoff/fe-libs/ui';
|
|
39
37
|
import { useOrder, useOrders } from '../hooks/useOrders';
|
|
40
38
|
import { useCustomer, useCustomers } from '../hooks/useCustomers';
|
|
41
|
-
import { useRoutes } from '../hooks/useRoutes';
|
|
42
|
-
import {
|
|
39
|
+
import { useRoute, useRoutes } from '../hooks/useRoutes';
|
|
40
|
+
import { useProducts } from '../hooks/useInventory';
|
|
43
41
|
import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
|
|
44
42
|
import { joinPath, buildCustomerCreatePath } from '../utils/navigation';
|
|
45
43
|
import { CustomerPicker } from '../components/CustomerPicker';
|
|
44
|
+
import { EntityCombobox } from '../components/EntityCombobox';
|
|
45
|
+
import { OrderItemRow } from '../components/OrderItemRow';
|
|
46
46
|
import type { Customer } from '../types';
|
|
47
47
|
|
|
48
48
|
/**
|
|
@@ -69,7 +69,6 @@ export default function CreateOrderPage() {
|
|
|
69
69
|
const { order } = useOrder(orderId);
|
|
70
70
|
const { createOrder, updateOrder } = useOrders();
|
|
71
71
|
const [customerSearch, setCustomerSearch] = useState('');
|
|
72
|
-
const debouncedCustomerSearch = useDebouncedValue(customerSearch);
|
|
73
72
|
const {
|
|
74
73
|
customers,
|
|
75
74
|
createCustomer,
|
|
@@ -78,15 +77,24 @@ export default function CreateOrderPage() {
|
|
|
78
77
|
refetch: refetchCustomers,
|
|
79
78
|
} = useCustomers({
|
|
80
79
|
limit: 20,
|
|
81
|
-
filters: { search:
|
|
80
|
+
filters: { search: customerSearch || undefined },
|
|
82
81
|
});
|
|
83
82
|
const { customer: linkedCustomer } = useCustomer(prefilledCustomerId ?? order?.customerId);
|
|
83
|
+
const [routeSearch, setRouteSearch] = useState('');
|
|
84
84
|
const {
|
|
85
85
|
routes,
|
|
86
86
|
isLoading: routesLoading,
|
|
87
87
|
error: routesError,
|
|
88
|
+
hasMore: routesHaveMore,
|
|
89
|
+
totalRoutes,
|
|
88
90
|
} = useRoutes({
|
|
89
91
|
status: 'planned',
|
|
92
|
+
search: routeSearch || undefined,
|
|
93
|
+
limit: 20,
|
|
94
|
+
});
|
|
95
|
+
const [productSearch, setProductSearch] = useState('');
|
|
96
|
+
const { products, isLoading: productsLoading } = useProducts({
|
|
97
|
+
search: productSearch || undefined,
|
|
90
98
|
limit: 20,
|
|
91
99
|
});
|
|
92
100
|
|
|
@@ -116,6 +124,13 @@ export default function CreateOrderPage() {
|
|
|
116
124
|
specialInstructions: order?.specialInstructions || '',
|
|
117
125
|
});
|
|
118
126
|
|
|
127
|
+
// Resolved independently of the (searchable, 20-row) `routes` page so the
|
|
128
|
+
// saved-route combobox's trigger still shows the right name even when the
|
|
129
|
+
// selection has scrolled out of the current search/page.
|
|
130
|
+
const { route: selectedRoute, isLoading: selectedRouteLoading } = useRoute(
|
|
131
|
+
formData.routeId || undefined
|
|
132
|
+
);
|
|
133
|
+
|
|
119
134
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
120
135
|
const [formError, setFormError] = useState<string | null>(null);
|
|
121
136
|
|
|
@@ -275,7 +290,7 @@ export default function CreateOrderPage() {
|
|
|
275
290
|
};
|
|
276
291
|
|
|
277
292
|
const selectSavedRoute = (value: string) => {
|
|
278
|
-
if (value
|
|
293
|
+
if (!value) {
|
|
279
294
|
updateField('routeId', '');
|
|
280
295
|
return;
|
|
281
296
|
}
|
|
@@ -431,7 +446,6 @@ export default function CreateOrderPage() {
|
|
|
431
446
|
onRetry={refetchCustomers}
|
|
432
447
|
onOpenCustomers={goToCustomerCreate}
|
|
433
448
|
selectedCustomer={selectedCustomer}
|
|
434
|
-
searchQuery={customerSearch}
|
|
435
449
|
onSearchQueryChange={setCustomerSearch}
|
|
436
450
|
/>
|
|
437
451
|
<div className="space-y-2">
|
|
@@ -464,19 +478,30 @@ export default function CreateOrderPage() {
|
|
|
464
478
|
</CardTitle>
|
|
465
479
|
</CardHeader>
|
|
466
480
|
<CardContent className="space-y-2">
|
|
467
|
-
<
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
481
|
+
<EntityCombobox
|
|
482
|
+
id="saved-route"
|
|
483
|
+
value={formData.routeId}
|
|
484
|
+
onChange={selectSavedRoute}
|
|
485
|
+
options={routes}
|
|
486
|
+
loading={routesLoading}
|
|
487
|
+
getId={(route) => route.id}
|
|
488
|
+
getLabel={(route) => `${route.name} · ${route.stops.length} stops`}
|
|
489
|
+
renderOption={(route) => (
|
|
490
|
+
<>
|
|
491
|
+
{route.name} · {route.stops.length} stops
|
|
492
|
+
</>
|
|
493
|
+
)}
|
|
494
|
+
selectedOption={selectedRoute}
|
|
495
|
+
selectedLoading={selectedRouteLoading}
|
|
496
|
+
placeholder="Choose a saved route"
|
|
497
|
+
searchPlaceholder="Search saved routes"
|
|
498
|
+
emptyText="No saved routes found."
|
|
499
|
+
noneLabel="No saved route"
|
|
500
|
+
error={routesError ? 'Saved routes are unavailable' : undefined}
|
|
501
|
+
onSearchChange={setRouteSearch}
|
|
502
|
+
hasMore={routesHaveMore}
|
|
503
|
+
totalCount={totalRoutes}
|
|
504
|
+
/>
|
|
480
505
|
<p className="text-xs text-text-secondary">
|
|
481
506
|
{routesLoading
|
|
482
507
|
? 'Loading planned routes…'
|
|
@@ -630,74 +655,31 @@ export default function CreateOrderPage() {
|
|
|
630
655
|
/>
|
|
631
656
|
) : (
|
|
632
657
|
<div className="space-y-4">
|
|
658
|
+
<div className="space-y-2">
|
|
659
|
+
<Label htmlFor="item-product-search">Search product catalog</Label>
|
|
660
|
+
<Input
|
|
661
|
+
id="item-product-search"
|
|
662
|
+
value={productSearch}
|
|
663
|
+
onChange={(e) => setProductSearch(e.target.value)}
|
|
664
|
+
placeholder="Search by name or SKU"
|
|
665
|
+
/>
|
|
666
|
+
<p className="text-xs text-text-secondary">
|
|
667
|
+
Narrows the "Catalog item" picker below for every row. Picking a catalog item
|
|
668
|
+
fills in name, SKU, weight, and value — you can still edit them, or skip the
|
|
669
|
+
picker and enter details manually.
|
|
670
|
+
</p>
|
|
671
|
+
</div>
|
|
633
672
|
{formData.items.map((item, index) => (
|
|
634
|
-
<
|
|
635
|
-
{
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
</div>
|
|
645
|
-
<div className="space-y-2">
|
|
646
|
-
<Label>SKU</Label>
|
|
647
|
-
<Input
|
|
648
|
-
value={item.sku}
|
|
649
|
-
onChange={(e) => updateItem(index, 'sku', e.target.value)}
|
|
650
|
-
placeholder="SKU-001"
|
|
651
|
-
/>
|
|
652
|
-
</div>
|
|
653
|
-
<div className="space-y-2">
|
|
654
|
-
<Label>Quantity</Label>
|
|
655
|
-
<Input
|
|
656
|
-
type="number"
|
|
657
|
-
min="1"
|
|
658
|
-
value={item.quantity}
|
|
659
|
-
onChange={(e) =>
|
|
660
|
-
updateItem(index, 'quantity', parseInt(e.target.value) || 1)
|
|
661
|
-
}
|
|
662
|
-
/>
|
|
663
|
-
</div>
|
|
664
|
-
<div className="space-y-2">
|
|
665
|
-
<Label>Weight (kg)</Label>
|
|
666
|
-
<Input
|
|
667
|
-
type="number"
|
|
668
|
-
min="0"
|
|
669
|
-
step="0.1"
|
|
670
|
-
value={item.weight}
|
|
671
|
-
onChange={(e) =>
|
|
672
|
-
updateItem(index, 'weight', parseFloat(e.target.value) || 0)
|
|
673
|
-
}
|
|
674
|
-
/>
|
|
675
|
-
</div>
|
|
676
|
-
<div className="space-y-2">
|
|
677
|
-
<Label>Value ($)</Label>
|
|
678
|
-
<Input
|
|
679
|
-
type="number"
|
|
680
|
-
min="0"
|
|
681
|
-
step="0.01"
|
|
682
|
-
value={item.value}
|
|
683
|
-
onChange={(e) =>
|
|
684
|
-
updateItem(index, 'value', parseFloat(e.target.value) || 0)
|
|
685
|
-
}
|
|
686
|
-
/>
|
|
687
|
-
</div>
|
|
688
|
-
<div className="flex items-end">
|
|
689
|
-
<Button
|
|
690
|
-
type="button"
|
|
691
|
-
variant="ghost"
|
|
692
|
-
size="icon"
|
|
693
|
-
className="text-status-error-text"
|
|
694
|
-
onClick={() => removeItem(index)}
|
|
695
|
-
>
|
|
696
|
-
<Trash2 size={16} />
|
|
697
|
-
</Button>
|
|
698
|
-
</div>
|
|
699
|
-
</div>
|
|
700
|
-
</div>
|
|
673
|
+
<OrderItemRow
|
|
674
|
+
key={item.id}
|
|
675
|
+
item={item}
|
|
676
|
+
index={index}
|
|
677
|
+
isFirst={index === 0}
|
|
678
|
+
products={products}
|
|
679
|
+
productsLoading={productsLoading}
|
|
680
|
+
onUpdate={updateItem}
|
|
681
|
+
onRemove={removeItem}
|
|
682
|
+
/>
|
|
701
683
|
))}
|
|
702
684
|
</div>
|
|
703
685
|
)}
|
|
@@ -36,11 +36,6 @@ import {
|
|
|
36
36
|
Separator,
|
|
37
37
|
Input,
|
|
38
38
|
Label,
|
|
39
|
-
Select,
|
|
40
|
-
SelectContent,
|
|
41
|
-
SelectItem,
|
|
42
|
-
SelectTrigger,
|
|
43
|
-
SelectValue,
|
|
44
39
|
Table,
|
|
45
40
|
TableBody,
|
|
46
41
|
TableCell,
|
|
@@ -48,9 +43,10 @@ import {
|
|
|
48
43
|
TableHeader,
|
|
49
44
|
TableRow,
|
|
50
45
|
} from '../components/ui';
|
|
46
|
+
import { EntityCombobox } from '../components/EntityCombobox';
|
|
51
47
|
import { EmphasisPanel, NextSteps, PagePurpose } from '@burdenoff/fe-libs/ui';
|
|
52
48
|
import { useOrder, useOrders } from '../hooks/useOrders';
|
|
53
|
-
import {
|
|
49
|
+
import { useVehicles, useVehicle, useDrivers, useDriver } from '../hooks/useFleet';
|
|
54
50
|
import { useDebouncedValue } from '../hooks/useDebouncedValue';
|
|
55
51
|
import { FormError } from '../components/FormError';
|
|
56
52
|
import { Notice, useNotice } from '../components/Notice';
|
|
@@ -105,20 +101,25 @@ export default function OrderDetailsPage() {
|
|
|
105
101
|
const [advancing, setAdvancing] = useState(false);
|
|
106
102
|
const [actionError, setActionError] = useState<string | null>(null);
|
|
107
103
|
const debouncedFleetSearch = useDebouncedValue(fleetSearch);
|
|
108
|
-
const { vehicles, isLoading: vehiclesLoading } =
|
|
104
|
+
const { vehicles, isLoading: vehiclesLoading } = useVehicles({
|
|
109
105
|
limit: 20,
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
driverId: selectedDriver || undefined,
|
|
113
|
-
},
|
|
106
|
+
search: debouncedFleetSearch || undefined,
|
|
107
|
+
driverId: selectedDriver || undefined,
|
|
114
108
|
});
|
|
115
|
-
const { drivers, isLoading: driversLoading } =
|
|
109
|
+
const { drivers, isLoading: driversLoading } = useDrivers({
|
|
116
110
|
limit: 20,
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
vehicleId: selectedVehicle || undefined,
|
|
120
|
-
},
|
|
111
|
+
search: debouncedFleetSearch || undefined,
|
|
112
|
+
vehicleId: selectedVehicle || undefined,
|
|
121
113
|
});
|
|
114
|
+
// Resolved independently of the (searched, 20-row) lists above so the
|
|
115
|
+
// trigger still shows the right label once a pick scrolls out of the
|
|
116
|
+
// current search results.
|
|
117
|
+
const { vehicle: selectedVehicleRecord, isLoading: selectedVehicleLoading } = useVehicle(
|
|
118
|
+
selectedVehicle || undefined
|
|
119
|
+
);
|
|
120
|
+
const { driver: selectedDriverRecord, isLoading: selectedDriverLoading } = useDriver(
|
|
121
|
+
selectedDriver || undefined
|
|
122
|
+
);
|
|
122
123
|
const { message: notice, notify } = useNotice();
|
|
123
124
|
const availableVehicles = vehicles.filter(
|
|
124
125
|
(vehicle) => vehicle.status === 'available' || vehicle.id === order?.vehicleId
|
|
@@ -564,54 +565,54 @@ export default function OrderDetailsPage() {
|
|
|
564
565
|
</div>
|
|
565
566
|
<div className="space-y-1.5">
|
|
566
567
|
<Label htmlFor="assign-vehicle">Vehicle</Label>
|
|
567
|
-
<
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
568
|
+
<EntityCombobox
|
|
569
|
+
id="assign-vehicle"
|
|
570
|
+
value={selectedVehicle}
|
|
571
|
+
onChange={setSelectedVehicle}
|
|
572
|
+
options={availableVehicles}
|
|
573
|
+
loading={vehiclesLoading}
|
|
574
|
+
getId={(vehicle) => vehicle.id}
|
|
575
|
+
getLabel={(vehicle) => `${vehicle.vehicleNumber} (${vehicle.type})`}
|
|
576
|
+
renderOption={(vehicle) => (
|
|
577
|
+
<>
|
|
578
|
+
{vehicle.vehicleNumber} ({vehicle.type})
|
|
579
|
+
</>
|
|
580
|
+
)}
|
|
581
|
+
selectedOption={selectedVehicleRecord}
|
|
582
|
+
selectedLoading={selectedVehicleLoading}
|
|
583
|
+
required
|
|
584
|
+
placeholder={
|
|
585
|
+
order.vehicleId ? `Current: ${order.vehicleId}` : 'Select a vehicle'
|
|
586
|
+
}
|
|
587
|
+
emptyText="No available vehicles."
|
|
588
|
+
// Search is driven by the shared "Search fleet" box above —
|
|
589
|
+
// no second input inside the popover.
|
|
590
|
+
/>
|
|
589
591
|
</div>
|
|
590
592
|
|
|
591
593
|
<div className="space-y-1.5">
|
|
592
594
|
<Label htmlFor="assign-driver">Driver</Label>
|
|
593
|
-
<
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
</Select>
|
|
595
|
+
<EntityCombobox
|
|
596
|
+
id="assign-driver"
|
|
597
|
+
value={selectedDriver}
|
|
598
|
+
onChange={setSelectedDriver}
|
|
599
|
+
options={availableDrivers}
|
|
600
|
+
loading={driversLoading}
|
|
601
|
+
getId={(driver) => driver.id}
|
|
602
|
+
getLabel={(driver) => `${driver.fullName} (${driver.employeeId})`}
|
|
603
|
+
renderOption={(driver) => (
|
|
604
|
+
<>
|
|
605
|
+
{driver.fullName} ({driver.employeeId})
|
|
606
|
+
</>
|
|
607
|
+
)}
|
|
608
|
+
selectedOption={selectedDriverRecord}
|
|
609
|
+
selectedLoading={selectedDriverLoading}
|
|
610
|
+
required
|
|
611
|
+
placeholder={
|
|
612
|
+
order.driverId ? `Current: ${order.driverId}` : 'Select a driver'
|
|
613
|
+
}
|
|
614
|
+
emptyText="No available drivers."
|
|
615
|
+
/>
|
|
615
616
|
</div>
|
|
616
617
|
|
|
617
618
|
<Button
|
|
@@ -39,8 +39,9 @@ import {
|
|
|
39
39
|
import { EmphasisPanel, NextSteps, PagePurpose } from '@burdenoff/fe-libs/ui';
|
|
40
40
|
import { RouteMap, type RouteStop as MapRouteStop } from '../components/maps';
|
|
41
41
|
import { firstRealLatLng } from '../utils/mapMarkers';
|
|
42
|
+
import { EntityCombobox } from '../components/EntityCombobox';
|
|
42
43
|
import { useRoute, useRoutes } from '../hooks/useRoutes';
|
|
43
|
-
import {
|
|
44
|
+
import { useVehicles, useVehicle, useDrivers, useDriver } from '../hooks/useFleet';
|
|
44
45
|
import { useAssignmentSuggestions } from '../hooks/useFleetIntelligence';
|
|
45
46
|
import { AssignmentSuggestions } from '../components/AssignmentSuggestions';
|
|
46
47
|
import { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';
|
|
@@ -51,15 +52,7 @@ import { joinPath } from '../utils/navigation';
|
|
|
51
52
|
import { ROUTE_STATUS_COLORS } from '../constants';
|
|
52
53
|
import { FormError } from '../components/FormError';
|
|
53
54
|
import { Notice, useNotice } from '../components/Notice';
|
|
54
|
-
import type { Route as RouteType, RouteStop
|
|
55
|
-
|
|
56
|
-
/** Keeps a currently selected fleet item visible even when it falls outside the current search page. */
|
|
57
|
-
function mergeSelected<T extends { id: string }>(items: T[], selected: T | null | undefined): T[] {
|
|
58
|
-
if (!selected || items.some((item) => item.id === selected.id)) {
|
|
59
|
-
return items;
|
|
60
|
-
}
|
|
61
|
-
return [selected, ...items];
|
|
62
|
-
}
|
|
55
|
+
import type { Route as RouteType, RouteStop } from '../types';
|
|
63
56
|
|
|
64
57
|
const ROUTE_STATUS_OPTIONS: { value: RouteType['status']; label: string }[] = [
|
|
65
58
|
{ value: 'planned', label: 'Planned' },
|
|
@@ -88,27 +81,25 @@ function NewRouteForm({ editId }: { editId?: string }) {
|
|
|
88
81
|
driverId: '',
|
|
89
82
|
stops: [{ address: '', type: 'delivery' }],
|
|
90
83
|
});
|
|
91
|
-
const { vehicles } =
|
|
84
|
+
const { vehicles, isLoading: vehiclesLoading } = useVehicles({
|
|
92
85
|
limit: 20,
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
driverId: form.driverId || undefined,
|
|
96
|
-
},
|
|
86
|
+
search: debouncedFleetSearch || undefined,
|
|
87
|
+
driverId: form.driverId || undefined,
|
|
97
88
|
});
|
|
98
|
-
const { drivers } =
|
|
89
|
+
const { drivers, isLoading: driversLoading } = useDrivers({
|
|
99
90
|
limit: 20,
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
vehicleId: form.vehicleId || undefined,
|
|
103
|
-
},
|
|
91
|
+
search: debouncedFleetSearch || undefined,
|
|
92
|
+
vehicleId: form.vehicleId || undefined,
|
|
104
93
|
});
|
|
105
94
|
// The selected vehicle/driver may fall outside the current search page (e.g. when
|
|
106
95
|
// editing a route or after the assignee no longer matches the search term); fetch
|
|
107
|
-
// them directly so the
|
|
108
|
-
const { vehicle: selectedVehicle } = useVehicle(
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const
|
|
96
|
+
// them directly so the combobox can still render the current selection's label.
|
|
97
|
+
const { vehicle: selectedVehicle, isLoading: selectedVehicleLoading } = useVehicle(
|
|
98
|
+
form.vehicleId || undefined
|
|
99
|
+
);
|
|
100
|
+
const { driver: selectedDriver, isLoading: selectedDriverLoading } = useDriver(
|
|
101
|
+
form.driverId || undefined
|
|
102
|
+
);
|
|
112
103
|
const [submitting, setSubmitting] = useState(false);
|
|
113
104
|
const [error, setError] = useState<string | null>(null);
|
|
114
105
|
|
|
@@ -296,39 +287,48 @@ function NewRouteForm({ editId }: { editId?: string }) {
|
|
|
296
287
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
297
288
|
<div className="space-y-2">
|
|
298
289
|
<Label htmlFor="vehicle">Vehicle (optional)</Label>
|
|
299
|
-
<
|
|
290
|
+
<EntityCombobox
|
|
291
|
+
id="vehicle"
|
|
300
292
|
value={form.vehicleId}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
293
|
+
onChange={(value) => setForm((prev) => ({ ...prev, vehicleId: value }))}
|
|
294
|
+
options={vehicles}
|
|
295
|
+
loading={vehiclesLoading}
|
|
296
|
+
getId={(vehicle) => vehicle.id}
|
|
297
|
+
getLabel={(vehicle) => `${vehicle.vehicleNumber} (${vehicle.type})`}
|
|
298
|
+
renderOption={(vehicle) => (
|
|
299
|
+
<>
|
|
300
|
+
{vehicle.vehicleNumber} ({vehicle.type})
|
|
301
|
+
</>
|
|
302
|
+
)}
|
|
303
|
+
selectedOption={selectedVehicle}
|
|
304
|
+
selectedLoading={selectedVehicleLoading}
|
|
305
|
+
required
|
|
306
|
+
placeholder="Assign later"
|
|
307
|
+
emptyText="No available vehicles."
|
|
308
|
+
// Search is driven by the shared "Search fleet" box above.
|
|
309
|
+
/>
|
|
314
310
|
</div>
|
|
315
311
|
<div className="space-y-2">
|
|
316
312
|
<Label htmlFor="driver">Driver (optional)</Label>
|
|
317
|
-
<
|
|
313
|
+
<EntityCombobox
|
|
314
|
+
id="driver"
|
|
318
315
|
value={form.driverId}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
316
|
+
onChange={(value) => setForm((prev) => ({ ...prev, driverId: value }))}
|
|
317
|
+
options={drivers}
|
|
318
|
+
loading={driversLoading}
|
|
319
|
+
getId={(driver) => driver.id}
|
|
320
|
+
getLabel={(driver) => `${driver.fullName} (${driver.employeeId})`}
|
|
321
|
+
renderOption={(driver) => (
|
|
322
|
+
<>
|
|
323
|
+
{driver.fullName} ({driver.employeeId})
|
|
324
|
+
</>
|
|
325
|
+
)}
|
|
326
|
+
selectedOption={selectedDriver}
|
|
327
|
+
selectedLoading={selectedDriverLoading}
|
|
328
|
+
required
|
|
329
|
+
placeholder="Assign later"
|
|
330
|
+
emptyText="No available drivers."
|
|
331
|
+
/>
|
|
332
332
|
</div>
|
|
333
333
|
</div>
|
|
334
334
|
</div>
|