@burdenoff/microfe-movethewheels 2026.924.2 → 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/OrderItemRow.js +195 -109
- package/dist/components/StatusBadge.js +31 -31
- package/dist/constants/index.js +11 -3
- package/dist/generated/wspace-operations.d.ts +7 -0
- package/dist/hooks/useFleet.js +85 -76
- package/dist/hooks/useOrders.js +8 -4
- package/dist/hooks/useTracking.js +127 -66
- package/dist/movethewheels/components/OrderItemRow.d.ts +1 -2
- 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/hooks/useOrders.d.ts +1 -0
- package/dist/movethewheels/hooks/useTracking.d.ts +2 -2
- package/dist/movethewheels/types/index.d.ts +17 -39
- 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/CreateOrderPage.js +597 -420
- 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 +290 -262
- package/dist/pages/OrdersListPage.js +4 -0
- package/dist/pages/RouteDetailsPage.js +264 -252
- package/dist/pages/TrackingPage.js +205 -195
- package/dist/pages/VehicleDetailsPage.js +403 -243
- package/dist/utils/mapMarkers.js +2 -1
- package/package.json +1 -1
- package/src/generated/wspace-operations.ts +10 -3
- package/src/movethewheels/components/OrderItemRow.tsx +162 -99
- package/src/movethewheels/components/StatusBadge.tsx +11 -2
- package/src/movethewheels/constants/index.ts +13 -0
- package/src/movethewheels/hooks/useFleet.ts +163 -44
- package/src/movethewheels/hooks/useOrders.ts +17 -3
- package/src/movethewheels/hooks/useTracking.ts +208 -27
- package/src/movethewheels/pages/CreateOrderPage.tsx +372 -263
- 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 +67 -30
- package/src/movethewheels/pages/OrdersListPage.tsx +1 -0
- package/src/movethewheels/pages/RouteDetailsPage.tsx +15 -7
- package/src/movethewheels/pages/TrackingPage.tsx +44 -14
- package/src/movethewheels/pages/VehicleDetailsPage.tsx +137 -15
- package/src/movethewheels/types/index.ts +17 -38
- package/src/movethewheels/utils/mapMarkers.ts +1 -0
- package/src/operations/wspace/movethewheels/queries/dashboard.graphql +5 -0
- package/src/operations/wspace/movethewheels/queries/logistics.graphql +2 -2
package/dist/utils/mapMarkers.js
CHANGED
|
@@ -6,7 +6,8 @@ function e(e) {
|
|
|
6
6
|
case "delivered": return "delivered";
|
|
7
7
|
case "failed":
|
|
8
8
|
case "delayed":
|
|
9
|
-
case "cancelled":
|
|
9
|
+
case "cancelled":
|
|
10
|
+
case "returned": return "delayed";
|
|
10
11
|
case "assigned":
|
|
11
12
|
case "picked_up":
|
|
12
13
|
case "confirmed": return "active";
|
package/package.json
CHANGED
|
@@ -292,7 +292,7 @@ export type DeleteMovethewheelsOrderMutation = { __typename?: 'Mutation', delete
|
|
|
292
292
|
export type GetMovethewheelsDashboardQueryVariables = Types.Exact<{ [key: string]: never; }>;
|
|
293
293
|
|
|
294
294
|
|
|
295
|
-
export type GetMovethewheelsDashboardQuery = { __typename?: 'Query', dashboardMetrics: { __typename?: 'MtwDashboardMetrics', totalDeliveryJobs: number, activeDeliveries: number, availableDrivers: number, activeVehicles: number, lowStockItems: number, totalRevenue: number, recentDeliveryJobs: Array<{ __typename?: 'MtwDeliveryJob', id: string, deliveryJobNumber: string, status: Types.DeliveryJobStatus, priority: Types.DeliveryPriority, customerId: string, trackingNumber?: string | null, pickupAddress: Record<string, unknown>, deliveryAddress: Record<string, unknown>, cargoItems: Record<string, unknown>, pricing?: Record<string, unknown> | null, scheduledDelivery?: string | null, vehicleId?: string | null, driverId?: string | null, createdAt: string, updatedAt: string }> }, deliveryJobStats: { __typename?: 'MtwDeliveryJobStats', total: number, pending: number, inTransit: number, delivered: number, cancelled: number, failed: number }, fleetStats: { __typename?: 'MtwFleetStats', totalVehicles: number, activeVehicles: number, inTransitVehicles: number, maintenanceVehicles: number, totalDrivers: number, availableDrivers: number, onDeliveryDrivers: number }, financeMetrics: { __typename?: 'MtwFinanceMetrics', fuelCost: number, openInvoices: number, revenue: { __typename?: 'MtwRevenueMetrics', totalRevenue: number, averageOrderValue: number, deliveredRevenue: number } }, analytics: { __typename?: 'MtwAnalyticsSeries', deliveryTrend: Array<{ __typename?: 'MtwAnalyticsPoint', label: string, value: number }>, orderTrend: Array<{ __typename?: 'MtwAnalyticsPoint', label: string, value: number }>, revenueTrend: Array<{ __typename?: 'MtwAnalyticsPoint', label: string, value: number }> } };
|
|
295
|
+
export type GetMovethewheelsDashboardQuery = { __typename?: 'Query', dashboardMetrics: { __typename?: 'MtwDashboardMetrics', totalDeliveryJobs: number, activeDeliveries: number, availableDrivers: number, activeVehicles: number, lowStockItems: number, totalRevenue: number, recentDeliveryJobs: Array<{ __typename?: 'MtwDeliveryJob', id: string, deliveryJobNumber: string, status: Types.DeliveryJobStatus, priority: Types.DeliveryPriority, customerId: string, trackingNumber?: string | null, pickupAddress: Record<string, unknown>, deliveryAddress: Record<string, unknown>, cargoItems: Record<string, unknown>, pricing?: Record<string, unknown> | null, scheduledDelivery?: string | null, vehicleId?: string | null, driverId?: string | null, createdAt: string, updatedAt: string }> }, deliveryJobStats: { __typename?: 'MtwDeliveryJobStats', total: number, pending: number, confirmed: number, assigned: number, pickedUp: number, inTransit: number, outForDelivery: number, delivered: number, cancelled: number, failed: number, returned: number }, fleetStats: { __typename?: 'MtwFleetStats', totalVehicles: number, activeVehicles: number, inTransitVehicles: number, maintenanceVehicles: number, totalDrivers: number, availableDrivers: number, onDeliveryDrivers: number }, financeMetrics: { __typename?: 'MtwFinanceMetrics', fuelCost: number, openInvoices: number, revenue: { __typename?: 'MtwRevenueMetrics', totalRevenue: number, averageOrderValue: number, deliveredRevenue: number } }, analytics: { __typename?: 'MtwAnalyticsSeries', deliveryTrend: Array<{ __typename?: 'MtwAnalyticsPoint', label: string, value: number }>, orderTrend: Array<{ __typename?: 'MtwAnalyticsPoint', label: string, value: number }>, revenueTrend: Array<{ __typename?: 'MtwAnalyticsPoint', label: string, value: number }> } };
|
|
296
296
|
|
|
297
297
|
export type ListCustomersQueryVariables = Types.Exact<{
|
|
298
298
|
filter?: Types.InputMaybe<Types.CustomerFilterInput>;
|
|
@@ -436,6 +436,7 @@ export type GetShipmentQuery = { __typename?: 'Query', shipment?: { __typename?:
|
|
|
436
436
|
|
|
437
437
|
export type GetDeliveryJobTrackingQueryVariables = Types.Exact<{
|
|
438
438
|
deliveryJobId: Types.Scalars['ID']['input'];
|
|
439
|
+
pagination?: Types.InputMaybe<Types.PaginationInput>;
|
|
439
440
|
}>;
|
|
440
441
|
|
|
441
442
|
|
|
@@ -1837,10 +1838,15 @@ export const GetMovethewheelsDashboardDocument = gql`
|
|
|
1837
1838
|
deliveryJobStats {
|
|
1838
1839
|
total
|
|
1839
1840
|
pending
|
|
1841
|
+
confirmed
|
|
1842
|
+
assigned
|
|
1843
|
+
pickedUp
|
|
1840
1844
|
inTransit
|
|
1845
|
+
outForDelivery
|
|
1841
1846
|
delivered
|
|
1842
1847
|
cancelled
|
|
1843
1848
|
failed
|
|
1849
|
+
returned
|
|
1844
1850
|
}
|
|
1845
1851
|
fleetStats {
|
|
1846
1852
|
totalVehicles
|
|
@@ -2809,8 +2815,8 @@ export type GetShipmentLazyQueryHookResult = ReturnType<typeof useGetShipmentLaz
|
|
|
2809
2815
|
export type GetShipmentSuspenseQueryHookResult = ReturnType<typeof useGetShipmentSuspenseQuery>;
|
|
2810
2816
|
export type GetShipmentQueryResult = Apollo.QueryResult<Types.GetShipmentQuery, Types.GetShipmentQueryVariables>;
|
|
2811
2817
|
export const GetDeliveryJobTrackingDocument = gql`
|
|
2812
|
-
query GetDeliveryJobTracking($deliveryJobId: ID
|
|
2813
|
-
deliveryJobTracking(deliveryJobId: $deliveryJobId) {
|
|
2818
|
+
query GetDeliveryJobTracking($deliveryJobId: ID!, $pagination: PaginationInput) {
|
|
2819
|
+
deliveryJobTracking(deliveryJobId: $deliveryJobId, pagination: $pagination) {
|
|
2814
2820
|
id
|
|
2815
2821
|
shipmentId
|
|
2816
2822
|
eventType
|
|
@@ -2834,6 +2840,7 @@ export const GetDeliveryJobTrackingDocument = gql`
|
|
|
2834
2840
|
* const { data, loading, error } = useGetDeliveryJobTrackingQuery({
|
|
2835
2841
|
* variables: {
|
|
2836
2842
|
* deliveryJobId: // value for 'deliveryJobId'
|
|
2843
|
+
* pagination: // value for 'pagination'
|
|
2837
2844
|
* },
|
|
2838
2845
|
* });
|
|
2839
2846
|
*/
|
|
@@ -1,23 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* OrderItemRow — one order-item line in CreateOrderPage's "
|
|
2
|
+
* OrderItemRow — one order-item line in CreateOrderPage's "Shipment contents" section.
|
|
3
3
|
*
|
|
4
4
|
* Its own component (not inlined in a `.map()`) because it needs one
|
|
5
|
-
* `useProduct`/`useProductStockLevels` hook call per row to resolve the
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* Hooks don't allow that from inside a loop in the parent.
|
|
5
|
+
* `useProduct`/`useProductStockLevels` hook call per row to resolve the selected
|
|
6
|
+
* catalog item's label and stock hint independently of the shared, search-filtered
|
|
7
|
+
* products list passed down from the page.
|
|
9
8
|
*/
|
|
10
9
|
import type { ChangeEvent } from 'react';
|
|
11
10
|
import { Trash2 } from 'lucide-react';
|
|
12
|
-
import { Button, Input, Label
|
|
11
|
+
import { Button, Input, Label } from './ui';
|
|
13
12
|
import { EntityCombobox } from './EntityCombobox';
|
|
14
13
|
import { useProduct, useProductStockLevels } from '../hooks/useInventory';
|
|
15
|
-
import type { OrderItem, Product } from '../types';
|
|
14
|
+
import type { Dimensions, OrderItem, Product } from '../types';
|
|
16
15
|
|
|
17
16
|
interface OrderItemRowProps {
|
|
18
17
|
item: OrderItem;
|
|
19
18
|
index: number;
|
|
20
|
-
isFirst: boolean;
|
|
21
19
|
products: Product[];
|
|
22
20
|
productsLoading: boolean;
|
|
23
21
|
onUpdate: (index: number, field: string, value: unknown) => void;
|
|
@@ -37,7 +35,6 @@ function stockHintText(totalAvailable: number, levels: { warehouseName: string }
|
|
|
37
35
|
export function OrderItemRow({
|
|
38
36
|
item,
|
|
39
37
|
index,
|
|
40
|
-
isFirst,
|
|
41
38
|
products,
|
|
42
39
|
productsLoading,
|
|
43
40
|
onUpdate,
|
|
@@ -55,6 +52,9 @@ export function OrderItemRow({
|
|
|
55
52
|
isLoading: stockLoading,
|
|
56
53
|
} = useProductStockLevels(item.productId || undefined);
|
|
57
54
|
|
|
55
|
+
const dimensions =
|
|
56
|
+
item.dimensions ?? ({ length: 0, width: 0, height: 0, unit: 'cm' } satisfies Dimensions);
|
|
57
|
+
|
|
58
58
|
const selectProduct = (productId: string) => {
|
|
59
59
|
if (!productId) {
|
|
60
60
|
onUpdate(index, 'productId', '');
|
|
@@ -69,103 +69,166 @@ export function OrderItemRow({
|
|
|
69
69
|
onUpdate(index, 'weight', product.weight);
|
|
70
70
|
onUpdate(index, 'value', product.value);
|
|
71
71
|
onUpdate(index, 'dimensions', product.dimensions);
|
|
72
|
-
// Quantity is never set from the catalog — there
|
|
73
|
-
// quantity on a cargo item
|
|
72
|
+
// Quantity is never set from the catalog — there is no per-unit default
|
|
73
|
+
// quantity on a cargo item; it belongs to this specific order line.
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const updateDimension = (field: keyof Omit<Dimensions, 'unit'>, value: string) => {
|
|
77
|
+
onUpdate(index, 'dimensions', {
|
|
78
|
+
...dimensions,
|
|
79
|
+
[field]: Number.parseFloat(value) || 0,
|
|
80
|
+
});
|
|
74
81
|
};
|
|
75
82
|
|
|
76
83
|
return (
|
|
77
|
-
<div
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
</>
|
|
93
|
-
)}
|
|
94
|
-
selectedOption={selectedProduct}
|
|
95
|
-
selectedLoading={selectedProductLoading}
|
|
96
|
-
placeholder="Pick from your product catalog"
|
|
97
|
-
emptyText="No products found."
|
|
98
|
-
noneLabel="Enter details manually"
|
|
99
|
-
// Search is driven by the shared "Search product catalog" box above
|
|
100
|
-
// this list — no second input inside the popover.
|
|
101
|
-
/>
|
|
102
|
-
{item.productId && !stockLoading && (
|
|
103
|
-
<p className="text-xs text-text-secondary">{stockHintText(totalAvailable, levels)}</p>
|
|
104
|
-
)}
|
|
105
|
-
</div>
|
|
106
|
-
<div className="grid grid-cols-1 gap-4 md:grid-cols-7">
|
|
107
|
-
<div className="space-y-2 md:col-span-2">
|
|
108
|
-
<Label>Item Name</Label>
|
|
109
|
-
<Input
|
|
110
|
-
value={item.name}
|
|
111
|
-
onChange={(e: ChangeEvent<HTMLInputElement>) => onUpdate(index, 'name', e.target.value)}
|
|
112
|
-
placeholder="Product name"
|
|
113
|
-
/>
|
|
114
|
-
</div>
|
|
115
|
-
<div className="space-y-2">
|
|
116
|
-
<Label>SKU</Label>
|
|
117
|
-
<Input
|
|
118
|
-
value={item.sku}
|
|
119
|
-
onChange={(e: ChangeEvent<HTMLInputElement>) => onUpdate(index, 'sku', e.target.value)}
|
|
120
|
-
placeholder="SKU-001"
|
|
121
|
-
/>
|
|
122
|
-
</div>
|
|
123
|
-
<div className="space-y-2">
|
|
124
|
-
<Label>Quantity</Label>
|
|
125
|
-
<Input
|
|
126
|
-
type="number"
|
|
127
|
-
min="1"
|
|
128
|
-
value={item.quantity}
|
|
129
|
-
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
|
130
|
-
onUpdate(index, 'quantity', parseInt(e.target.value) || 1)
|
|
131
|
-
}
|
|
132
|
-
/>
|
|
133
|
-
</div>
|
|
134
|
-
<div className="space-y-2">
|
|
135
|
-
<Label>Weight (kg)</Label>
|
|
136
|
-
<Input
|
|
137
|
-
type="number"
|
|
138
|
-
min="0"
|
|
139
|
-
step="0.1"
|
|
140
|
-
value={item.weight}
|
|
141
|
-
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
|
142
|
-
onUpdate(index, 'weight', parseFloat(e.target.value) || 0)
|
|
143
|
-
}
|
|
144
|
-
/>
|
|
84
|
+
<div
|
|
85
|
+
className="rounded-card border border-border-subtle bg-bg-surface p-4 shadow-elevation-1 sm:p-5"
|
|
86
|
+
aria-labelledby={`order-item-${item.id}-title`}
|
|
87
|
+
>
|
|
88
|
+
<div className="mb-4 flex items-start justify-between gap-4">
|
|
89
|
+
<div>
|
|
90
|
+
<h3
|
|
91
|
+
id={`order-item-${item.id}-title`}
|
|
92
|
+
className="text-sm font-semibold text-text-primary"
|
|
93
|
+
>
|
|
94
|
+
Item {index + 1}
|
|
95
|
+
</h3>
|
|
96
|
+
<p className="mt-1 text-xs text-text-secondary">
|
|
97
|
+
Select a catalog item to prefill details, or enter a one-off shipment manually.
|
|
98
|
+
</p>
|
|
145
99
|
</div>
|
|
146
|
-
<
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
100
|
+
<Button
|
|
101
|
+
type="button"
|
|
102
|
+
variant="ghost"
|
|
103
|
+
size="icon"
|
|
104
|
+
className="shrink-0 text-status-error-text"
|
|
105
|
+
onClick={() => onRemove(index)}
|
|
106
|
+
aria-label={`Remove item ${index + 1}`}
|
|
107
|
+
title={`Remove item ${index + 1}`}
|
|
108
|
+
>
|
|
109
|
+
<Trash2 size={16} aria-hidden="true" />
|
|
110
|
+
</Button>
|
|
111
|
+
</div>
|
|
112
|
+
|
|
113
|
+
<div className="space-y-4">
|
|
114
|
+
<div className="rounded-card border border-border-subtle bg-bg-sunken/40 p-3 sm:p-4">
|
|
115
|
+
<Label htmlFor={`item-product-${item.id}`}>Catalog item (optional)</Label>
|
|
116
|
+
<EntityCombobox
|
|
117
|
+
id={`item-product-${item.id}`}
|
|
118
|
+
value={item.productId}
|
|
119
|
+
onChange={selectProduct}
|
|
120
|
+
options={products}
|
|
121
|
+
loading={productsLoading}
|
|
122
|
+
getId={(product) => product.id}
|
|
123
|
+
getLabel={(product) => `${product.name} (${product.sku})`}
|
|
124
|
+
renderOption={(product) => (
|
|
125
|
+
<>
|
|
126
|
+
{product.name} <span className="text-text-tertiary">({product.sku})</span>
|
|
127
|
+
</>
|
|
128
|
+
)}
|
|
129
|
+
selectedOption={selectedProduct}
|
|
130
|
+
selectedLoading={selectedProductLoading}
|
|
131
|
+
placeholder="Pick from your product catalog"
|
|
132
|
+
emptyText="No products found."
|
|
133
|
+
noneLabel="Enter details manually"
|
|
156
134
|
/>
|
|
135
|
+
{item.productId && !stockLoading && (
|
|
136
|
+
<p className="mt-2 text-xs text-text-secondary" role="status">
|
|
137
|
+
{stockHintText(totalAvailable, levels)}
|
|
138
|
+
</p>
|
|
139
|
+
)}
|
|
157
140
|
</div>
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
141
|
+
|
|
142
|
+
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-12">
|
|
143
|
+
<div className="space-y-2 lg:col-span-4">
|
|
144
|
+
<Label htmlFor={`item-name-${item.id}`}>Item name</Label>
|
|
145
|
+
<Input
|
|
146
|
+
id={`item-name-${item.id}`}
|
|
147
|
+
value={item.name}
|
|
148
|
+
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
|
149
|
+
onUpdate(index, 'name', e.target.value)
|
|
150
|
+
}
|
|
151
|
+
placeholder="Product name"
|
|
152
|
+
/>
|
|
153
|
+
</div>
|
|
154
|
+
<div className="space-y-2 lg:col-span-3">
|
|
155
|
+
<Label htmlFor={`item-sku-${item.id}`}>SKU</Label>
|
|
156
|
+
<Input
|
|
157
|
+
id={`item-sku-${item.id}`}
|
|
158
|
+
value={item.sku}
|
|
159
|
+
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
|
160
|
+
onUpdate(index, 'sku', e.target.value)
|
|
161
|
+
}
|
|
162
|
+
placeholder="SKU-001"
|
|
163
|
+
/>
|
|
164
|
+
</div>
|
|
165
|
+
<div className="space-y-2 lg:col-span-2">
|
|
166
|
+
<Label htmlFor={`item-quantity-${item.id}`}>Quantity</Label>
|
|
167
|
+
<Input
|
|
168
|
+
id={`item-quantity-${item.id}`}
|
|
169
|
+
type="number"
|
|
170
|
+
min="1"
|
|
171
|
+
value={item.quantity}
|
|
172
|
+
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
|
173
|
+
onUpdate(index, 'quantity', parseInt(e.target.value, 10) || 1)
|
|
174
|
+
}
|
|
175
|
+
/>
|
|
176
|
+
</div>
|
|
177
|
+
<div className="space-y-2 lg:col-span-2">
|
|
178
|
+
<Label htmlFor={`item-weight-${item.id}`}>Weight (kg)</Label>
|
|
179
|
+
<Input
|
|
180
|
+
id={`item-weight-${item.id}`}
|
|
181
|
+
type="number"
|
|
182
|
+
min="0"
|
|
183
|
+
step="0.1"
|
|
184
|
+
value={item.weight}
|
|
185
|
+
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
|
186
|
+
onUpdate(index, 'weight', parseFloat(e.target.value) || 0)
|
|
187
|
+
}
|
|
188
|
+
/>
|
|
189
|
+
</div>
|
|
190
|
+
<div className="space-y-2 lg:col-span-1">
|
|
191
|
+
<Label htmlFor={`item-value-${item.id}`}>Value ($)</Label>
|
|
192
|
+
<Input
|
|
193
|
+
id={`item-value-${item.id}`}
|
|
194
|
+
type="number"
|
|
195
|
+
min="0"
|
|
196
|
+
step="0.01"
|
|
197
|
+
value={item.value}
|
|
198
|
+
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
|
199
|
+
onUpdate(index, 'value', parseFloat(e.target.value) || 0)
|
|
200
|
+
}
|
|
201
|
+
/>
|
|
202
|
+
</div>
|
|
168
203
|
</div>
|
|
204
|
+
|
|
205
|
+
<fieldset className="rounded-card border border-border-subtle bg-bg-sunken/40 p-3 sm:p-4">
|
|
206
|
+
<legend className="px-1 text-xs font-medium text-text-secondary">
|
|
207
|
+
Dimensions ({dimensions.unit})
|
|
208
|
+
</legend>
|
|
209
|
+
<div className="grid grid-cols-1 gap-4 sm:grid-cols-3">
|
|
210
|
+
{(['length', 'width', 'height'] as const).map((field) => (
|
|
211
|
+
<div key={field} className="space-y-2">
|
|
212
|
+
<Label htmlFor={`item-${field}-${item.id}`}>
|
|
213
|
+
{field.charAt(0).toUpperCase() + field.slice(1)}
|
|
214
|
+
</Label>
|
|
215
|
+
<Input
|
|
216
|
+
id={`item-${field}-${item.id}`}
|
|
217
|
+
type="number"
|
|
218
|
+
min="0"
|
|
219
|
+
step="0.1"
|
|
220
|
+
value={dimensions[field]}
|
|
221
|
+
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
|
222
|
+
updateDimension(field, e.target.value)
|
|
223
|
+
}
|
|
224
|
+
/>
|
|
225
|
+
</div>
|
|
226
|
+
))}
|
|
227
|
+
</div>
|
|
228
|
+
<p className="mt-2 text-xs text-text-secondary">
|
|
229
|
+
Catalog selections prefill these measurements, and you can adjust them for this order.
|
|
230
|
+
</p>
|
|
231
|
+
</fieldset>
|
|
169
232
|
</div>
|
|
170
233
|
</div>
|
|
171
234
|
);
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
ORDER_STATUS_COLORS,
|
|
6
6
|
VEHICLE_STATUS_COLORS,
|
|
7
7
|
DRIVER_STATUS_COLORS,
|
|
8
|
+
DRIVER_STATUS_LABELS,
|
|
8
9
|
PRIORITY_COLORS,
|
|
9
10
|
} from '../constants';
|
|
10
11
|
|
|
@@ -17,6 +18,8 @@ interface StatusBadgeProps {
|
|
|
17
18
|
bg: string;
|
|
18
19
|
text: string;
|
|
19
20
|
};
|
|
21
|
+
/** Override the auto-derived display text (title-cased raw status). */
|
|
22
|
+
label?: string;
|
|
20
23
|
size?: 'sm' | 'md' | 'lg';
|
|
21
24
|
showDot?: boolean;
|
|
22
25
|
className?: string;
|
|
@@ -35,6 +38,7 @@ export function StatusBadge({
|
|
|
35
38
|
status,
|
|
36
39
|
type = 'custom',
|
|
37
40
|
customColors,
|
|
41
|
+
label,
|
|
38
42
|
size = 'md',
|
|
39
43
|
showDot = true,
|
|
40
44
|
className,
|
|
@@ -59,8 +63,13 @@ export function StatusBadge({
|
|
|
59
63
|
}
|
|
60
64
|
}
|
|
61
65
|
|
|
62
|
-
// Format status for display
|
|
63
|
-
|
|
66
|
+
// Format status for display — an explicit label always wins; driver status
|
|
67
|
+
// otherwise renders from the same shared label map the filters use, so the
|
|
68
|
+
// table and the filter dropdown can never drift apart again (BOFF-7909).
|
|
69
|
+
const displayStatus =
|
|
70
|
+
label ??
|
|
71
|
+
(type === 'driver' ? DRIVER_STATUS_LABELS[status] : undefined) ??
|
|
72
|
+
status.replace(/_/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());
|
|
64
73
|
|
|
65
74
|
return (
|
|
66
75
|
<Badge
|
|
@@ -46,6 +46,7 @@ export const ORDER_STATUS_COLORS: Record<string, StatusColor> = {
|
|
|
46
46
|
in_transit: INFO,
|
|
47
47
|
out_for_delivery: WARNING,
|
|
48
48
|
delivered: SUCCESS,
|
|
49
|
+
returned: WARNING,
|
|
49
50
|
cancelled: ERROR,
|
|
50
51
|
failed: ERROR,
|
|
51
52
|
};
|
|
@@ -64,6 +65,18 @@ export const DRIVER_STATUS_COLORS: Record<string, StatusColor> = {
|
|
|
64
65
|
busy: INFO,
|
|
65
66
|
offline: NEUTRAL,
|
|
66
67
|
on_break: WARNING,
|
|
68
|
+
inactive: ERROR,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// Single source of truth for driver status display text — used by both the
|
|
72
|
+
// DriversPage status filter and the roster table's StatusBadge, so the two
|
|
73
|
+
// can never drift apart again (BOFF-7909).
|
|
74
|
+
export const DRIVER_STATUS_LABELS: Record<string, string> = {
|
|
75
|
+
available: 'Available',
|
|
76
|
+
busy: 'On Delivery',
|
|
77
|
+
offline: 'Off Duty',
|
|
78
|
+
on_break: 'On Break',
|
|
79
|
+
inactive: 'Inactive',
|
|
67
80
|
};
|
|
68
81
|
|
|
69
82
|
// Priority colors
|