@burdenoff/microfe-movethewheels 2026.703.3 → 2026.703.5
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/{AIAssistantPage-C3cPsWt-.js → AIAssistantPage-D2gaEL1U.js} +16 -15
- package/dist/AnalyticsPage-DZMlEoBi.js +194 -0
- package/dist/CreateOrderPage-CWHx8yys.js +490 -0
- package/dist/CustomerDetailsPage-nR4oZ_ju.js +615 -0
- package/dist/CustomersPage-Bbvwvn8k.js +205 -0
- package/dist/DashboardPage-DrEhNf7N.js +542 -0
- package/dist/DataTable-CqMUD4er.js +340 -0
- package/dist/DocumentsCard-edDkgnJj.js +169 -0
- package/dist/DriverDetailsPage-whEMOWs9.js +490 -0
- package/dist/DriversPage-CHF2ZEzd.js +221 -0
- package/dist/FinancePage-Dn9GrW-1.js +175 -0
- package/dist/{FleetPage-DqkFi3s3.js → FleetPage-CrtGPy-p.js} +36 -35
- package/dist/{FormError-BlOjnXOq.js → FormError-BKcb_wT-.js} +1 -1
- package/dist/{ImportExportPage-DKJRCezl.js → ImportExportPage-WYdwZomZ.js} +52 -72
- package/dist/InventoryPage-l5A1f753.js +259 -0
- package/dist/LiveTrackingPage-DhEaNKZ4.js +429 -0
- package/dist/MarketplacePage-B9RYOb1Y.js +426 -0
- package/dist/MetricCard-y3bWHKUQ.js +191 -0
- package/dist/Notice-DOO-tjpV.js +46 -0
- package/dist/OrderDetailsPage-CwYHwk1K.js +584 -0
- package/dist/OrdersListPage-DhZJoQeI.js +386 -0
- package/dist/{PageLayout-D4LDHJJq.js → PageLayout-BniRJaQ6.js} +1 -1
- package/dist/{ProductDetailsPage-C9uOiYLp.js → ProductDetailsPage-1Gm9JKy0.js} +56 -56
- package/dist/ProductsPage-C8sn-1tN.js +186 -0
- package/dist/ReportsPage-D1tb0dcz.js +377 -0
- package/dist/RouteDetailsPage-CkMyR9b_.js +610 -0
- package/dist/{RoutesPage-DMfoicM1.js → RoutesPage-QkENzsE5.js} +58 -47
- package/dist/{SettingsPage-Bn-v0mbQ.js → SettingsPage-DV3_tU_X.js} +16 -38
- package/dist/Sparkline-K2RMJ58u.js +16729 -0
- package/dist/{StatusBadge-BZurYHMy.js → StatusBadge-BNHXYvZx.js} +7 -7
- package/dist/TrackingPage-BZrg4RCu.js +368 -0
- package/dist/VehicleDetailsPage-D-8XPZ2G.js +393 -0
- package/dist/VehiclesPage-D5BELbtM.js +184 -0
- package/dist/{WarehouseDetailsPage-L_6sWigM.js → WarehouseDetailsPage-Cmg-8Frn.js} +70 -70
- package/dist/WarehousesPage-C1Y1M867.js +182 -0
- package/dist/{arrow-left-n48Y-Wis.js → arrow-left-ClW4Isj7.js} +1 -1
- package/dist/arrow-right-CmKOmIkh.js +11 -0
- package/dist/arrow-up-BJijRLAZ.js +17 -0
- package/dist/{box-D9Pal4HM.js → box-BT3NsRQP.js} +1 -1
- package/dist/boxes-B_OGvQiW.js +54 -0
- package/dist/{chart-column-BFIJysDR.js → chart-column-DQTTdUPk.js} +1 -1
- package/dist/charts-Tj_PPTfA.js +2018 -0
- package/dist/chevron-left-B1rZyWWb.js +8 -0
- package/dist/{chevron-right-EMIIyyJ1.js → chevron-right-BIcwUJZq.js} +1 -1
- package/dist/chunk-BdsCSvBP.js +20 -0
- package/dist/{circle-alert-iY24lSlp.js → circle-alert-CNkuDNc6.js} +1 -1
- package/dist/circle-check-Cs1OVjtV.js +13 -0
- package/dist/{circle-check-big-w0TrLKMF.js → circle-check-big-C-p4eNwn.js} +1 -1
- package/dist/{clock-DJZtdl8u.js → clock-dRGG4rkF.js} +1 -1
- package/dist/{dollar-sign-CgKQRz_e.js → dollar-sign-B6RoYHHz.js} +1 -1
- package/dist/{download-DRXxoiQR.js → download-DP04iWTd.js} +1 -1
- package/dist/{file-text-DWWY6_f5.js → file-text-BuDx4hZp.js} +1 -1
- package/dist/{filter-DloLC2eE.js → filter-BD483_8g.js} +1 -1
- package/dist/{wrench-CANXWxF-.js → fuel-DLH1ag04.js} +3 -6
- package/dist/index.css +2 -0
- package/dist/index.js +150 -172
- package/dist/{map-CkJaULb2.js → map-DNQn9fyY.js} +1 -1
- package/dist/{map-pin-BGiCvyCs.js → map-pin-CqN1bbT1.js} +1 -1
- package/dist/mapMarkers-NCV9MRKR.js +28 -0
- package/dist/maps-yzLqiK_C.js +5070 -0
- package/dist/{navigation-D36prnAn.js → navigation-CgGUt2tR.js} +1 -1
- package/dist/{package-BChUzpJS.js → package-DmRF7iQD.js} +1 -1
- package/dist/{phone-Ac9s3mo_.js → phone-Cl32EXKW.js} +1 -1
- package/dist/{plus-Z76q-FZm.js → plus-DlANgOFx.js} +1 -1
- package/dist/{refresh-cw-DKt6f5T6.js → refresh-cw-rW59D4b7.js} +1 -1
- package/dist/route-CWjPrtAN.js +22 -0
- package/dist/{save-CojywNhG.js → save-UDlog7hg.js} +1 -1
- package/dist/{search-CDGefqb3.js → search-DNIG5Ck7.js} +1 -1
- package/dist/{square-pen-CopE9GmB.js → square-pen-BcfB3Grd.js} +1 -1
- package/dist/{star-nHlLjDKR.js → star-CVZHFwDx.js} +1 -1
- package/dist/{store-BR9Voi5E.js → store-bwnmCK9f.js} +1 -1
- package/dist/{trash-2-BKhar6Hs.js → trash-2-B80G12d4.js} +1 -1
- package/dist/{trending-up-Dohgd4t_.js → trending-up-CH4R4KOa.js} +1 -1
- package/dist/{triangle-alert-i-XQL9Rd.js → triangle-alert-CmS8AUU6.js} +1 -1
- package/dist/{truck-C0v_hbLR.js → truck-9rLDncav.js} +1 -1
- package/dist/upload-DanJY1Vo.js +21 -0
- package/dist/{useAssistant-D4v3oxD3.js → useAssistant-CnEwkTLm.js} +11 -28
- package/dist/{useCustomers-BmTNGxiI.js → useCustomers-ChJao1ET.js} +30 -27
- package/dist/{useFleet-DKqXZ16m.js → useFleet-g5Yri0G8.js} +82 -70
- package/dist/{useImportExport-DHr-SWSw.js → useImportExport-MYwIT3rE.js} +1 -1
- package/dist/{useInventory-DkyiVY_A.js → useInventory-BSOn-R1c.js} +84 -39
- package/dist/{useOrders-Cfk1GLiq.js → useOrders-BSiqo61w.js} +59 -48
- package/dist/{useReports-CTqZr-AB.js → useReports-fWESfsF9.js} +41 -35
- package/dist/{useRoutes-BQWO607N.js → useRoutes-zD8Yp323.js} +50 -42
- package/dist/{useSettings-CkDH_-gM.js → useSettings-B1lsWeZ1.js} +6 -6
- package/dist/{useTracking-B-va1kO3.js → useTracking-BKG0I2ln.js} +3 -3
- package/dist/{user-jyFU3lXk.js → user-DU51el3m.js} +1 -1
- package/dist/user-check-IMhk-qkX.js +20 -0
- package/dist/{users-DZ-ilS6B.js → users-B8KhJBGg.js} +1 -1
- package/dist/{warehouse-6m5kXMLu.js → warehouse-CcMMioeY.js} +1 -1
- package/dist/wrench-Dg-sgH2e.js +8 -0
- package/dist/x-C4ObLVEa.js +11 -0
- package/package.json +5 -1
- package/src/movethewheels/components/DataTable.tsx +252 -84
- package/src/movethewheels/components/DocumentsCard.tsx +102 -0
- package/src/movethewheels/components/GlassPanel.tsx +71 -0
- package/src/movethewheels/components/MetricCard.tsx +171 -73
- package/src/movethewheels/components/Notice.tsx +61 -0
- package/src/movethewheels/components/StatusBadge.tsx +7 -6
- package/src/movethewheels/components/charts/ChartShell.tsx +78 -0
- package/src/movethewheels/components/charts/Sparkline.tsx +85 -0
- package/src/movethewheels/components/charts/StatusDonut.tsx +115 -0
- package/src/movethewheels/components/charts/TrendChart.tsx +179 -0
- package/src/movethewheels/components/charts/VolumeBar.tsx +97 -0
- package/src/movethewheels/components/charts/chartTokens.ts +43 -0
- package/src/movethewheels/components/charts/index.ts +9 -0
- package/src/movethewheels/components/index.ts +51 -1
- package/src/movethewheels/components/maps/LogisticsMap.tsx +94 -0
- package/src/movethewheels/components/maps/MapCanvas.tsx +118 -0
- package/src/movethewheels/components/maps/RouteMap.tsx +71 -0
- package/src/movethewheels/components/maps/TrackingMap.tsx +102 -0
- package/src/movethewheels/components/maps/index.ts +16 -0
- package/src/movethewheels/components/maps/leaflet-setup.ts +124 -0
- package/src/movethewheels/components/maps/map-markers.css +121 -0
- package/src/movethewheels/hooks/index.ts +9 -0
- package/src/movethewheels/hooks/useAnalytics.ts +109 -4
- package/src/movethewheels/hooks/useCustomers.ts +11 -4
- package/src/movethewheels/hooks/useEntityDocuments.ts +132 -0
- package/src/movethewheels/hooks/useFleet.ts +26 -7
- package/src/movethewheels/hooks/useInventory.ts +70 -7
- package/src/movethewheels/hooks/useOrders.ts +32 -2
- package/src/movethewheels/hooks/useReports.ts +14 -4
- package/src/movethewheels/hooks/useRoutes.ts +20 -3
- package/src/movethewheels/pages/AnalyticsPage.tsx +93 -73
- package/src/movethewheels/pages/CreateOrderPage.tsx +48 -20
- package/src/movethewheels/pages/CustomerDetailsPage.tsx +239 -33
- package/src/movethewheels/pages/CustomersPage.tsx +53 -28
- package/src/movethewheels/pages/DashboardPage.tsx +238 -12
- package/src/movethewheels/pages/DriverDetailsPage.tsx +17 -31
- package/src/movethewheels/pages/DriversPage.tsx +59 -31
- package/src/movethewheels/pages/FinancePage.tsx +103 -82
- package/src/movethewheels/pages/InventoryPage.tsx +15 -3
- package/src/movethewheels/pages/LiveTrackingPage.tsx +124 -16
- package/src/movethewheels/pages/MarketplacePage.tsx +265 -50
- package/src/movethewheels/pages/OrderDetailsPage.tsx +214 -10
- package/src/movethewheels/pages/OrdersListPage.tsx +118 -47
- package/src/movethewheels/pages/ProductsPage.tsx +52 -28
- package/src/movethewheels/pages/ReportsPage.tsx +88 -4
- package/src/movethewheels/pages/RouteDetailsPage.tsx +119 -19
- package/src/movethewheels/pages/RoutesPage.tsx +22 -6
- package/src/movethewheels/pages/SettingsPage.tsx +5 -21
- package/src/movethewheels/pages/TrackingPage.tsx +76 -6
- package/src/movethewheels/pages/VehicleDetailsPage.tsx +4 -34
- package/src/movethewheels/pages/VehiclesPage.tsx +58 -27
- package/src/movethewheels/pages/WarehouseDetailsPage.tsx +1 -1
- package/src/movethewheels/pages/WarehousesPage.tsx +47 -36
- package/src/movethewheels/types/index.ts +9 -1
- package/src/movethewheels/utils/download.ts +62 -0
- package/src/movethewheels/utils/mapMarkers.ts +58 -0
- package/dist/AnalyticsPage-Dz6g2MvK.js +0 -215
- package/dist/CreateOrderPage-C5iwnjp4.js +0 -479
- package/dist/CustomerDetailsPage-B6nwFyLO.js +0 -406
- package/dist/CustomersPage-BKxMS1AV.js +0 -161
- package/dist/DashboardPage-BVjQi0EB.js +0 -387
- package/dist/DataTable-DmciIKKM.js +0 -249
- package/dist/DriverDetailsPage-CN3WQ0B9.js +0 -507
- package/dist/DriversPage-CH9rjQC-.js +0 -165
- package/dist/FinancePage-DEDykSOP.js +0 -158
- package/dist/InventoryPage-e0ze63iX.js +0 -253
- package/dist/LiveTrackingPage-DCX-K-LR.js +0 -379
- package/dist/MarketplacePage-CIQ97_zp.js +0 -247
- package/dist/MetricCard-BY0QrMD4.js +0 -146
- package/dist/OrderDetailsPage-DYiZ-9UK.js +0 -429
- package/dist/OrdersListPage-Cn90P1Xx.js +0 -307
- package/dist/ProductsPage-QJfHHpqB.js +0 -169
- package/dist/ReportsPage-Zp53q3_8.js +0 -284
- package/dist/RouteDetailsPage-DUStXSf9.js +0 -531
- package/dist/TrackingPage-W-jtdM3Y.js +0 -325
- package/dist/VehicleDetailsPage-Bceo5oz3.js +0 -410
- package/dist/VehiclesPage-ApKThYeG.js +0 -165
- package/dist/WarehousesPage-DCbXXXWE.js +0 -162
- package/dist/useAnalytics-DS8n0xgg.js +0 -297
- /package/dist/{formatters-CF2S2l1m.js → formatters-FWJDwTFv.js} +0 -0
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Displays detailed information about a specific order.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { useState } from 'react';
|
|
7
8
|
import { useParams } from 'react-router-dom';
|
|
8
9
|
import {
|
|
9
10
|
Package,
|
|
@@ -19,6 +20,9 @@ import {
|
|
|
19
20
|
CheckCircle,
|
|
20
21
|
Edit,
|
|
21
22
|
Printer,
|
|
23
|
+
ArrowRight,
|
|
24
|
+
XCircle,
|
|
25
|
+
UserCheck,
|
|
22
26
|
} from 'lucide-react';
|
|
23
27
|
import { PageLayout, LoadingState, ErrorState } from '../components/PageLayout';
|
|
24
28
|
import {
|
|
@@ -35,6 +39,12 @@ import {
|
|
|
35
39
|
Button,
|
|
36
40
|
Badge,
|
|
37
41
|
Separator,
|
|
42
|
+
Label,
|
|
43
|
+
Select,
|
|
44
|
+
SelectContent,
|
|
45
|
+
SelectItem,
|
|
46
|
+
SelectTrigger,
|
|
47
|
+
SelectValue,
|
|
38
48
|
Table,
|
|
39
49
|
TableBody,
|
|
40
50
|
TableCell,
|
|
@@ -43,15 +53,106 @@ import {
|
|
|
43
53
|
TableRow,
|
|
44
54
|
} from '../components/ui';
|
|
45
55
|
import { EmphasisPanel, NextSteps, PagePurpose } from '@burdenoff/fe-libs/ui';
|
|
46
|
-
import { useOrder } from '../hooks/useOrders';
|
|
56
|
+
import { useOrder, useOrders } from '../hooks/useOrders';
|
|
57
|
+
import { useFleet } from '../hooks/useFleet';
|
|
58
|
+
import { FormError } from '../components/FormError';
|
|
59
|
+
import { Notice, useNotice } from '../components/Notice';
|
|
60
|
+
import { DocumentsCard } from '../components/DocumentsCard';
|
|
47
61
|
import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
|
|
48
62
|
import { formatCurrency, formatDate, formatAddress } from '../utils/formatters';
|
|
49
63
|
import { joinPath } from '../utils/navigation';
|
|
64
|
+
import type { OrderStatus } from '../types';
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Forward order-status pipeline. `updateOrderStatus` walks this sequence one
|
|
68
|
+
* step at a time; CANCELLED is offered separately as a terminal action.
|
|
69
|
+
*/
|
|
70
|
+
const STATUS_FLOW: OrderStatus[] = [
|
|
71
|
+
'pending',
|
|
72
|
+
'confirmed',
|
|
73
|
+
'assigned',
|
|
74
|
+
'picked_up',
|
|
75
|
+
'in_transit',
|
|
76
|
+
'out_for_delivery',
|
|
77
|
+
'delivered',
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
const STATUS_LABELS: Record<OrderStatus, string> = {
|
|
81
|
+
pending: 'Pending',
|
|
82
|
+
confirmed: 'Confirmed',
|
|
83
|
+
assigned: 'Assigned',
|
|
84
|
+
picked_up: 'Picked Up',
|
|
85
|
+
in_transit: 'In Transit',
|
|
86
|
+
out_for_delivery: 'Out for Delivery',
|
|
87
|
+
delivered: 'Delivered',
|
|
88
|
+
cancelled: 'Cancelled',
|
|
89
|
+
failed: 'Failed',
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
function nextStatus(current: OrderStatus): OrderStatus | null {
|
|
93
|
+
const index = STATUS_FLOW.indexOf(current);
|
|
94
|
+
if (index === -1 || index >= STATUS_FLOW.length - 1) return null;
|
|
95
|
+
return STATUS_FLOW[index + 1];
|
|
96
|
+
}
|
|
50
97
|
|
|
51
98
|
export default function OrderDetailsPage() {
|
|
52
99
|
const { orderId } = useParams<{ orderId: string }>();
|
|
53
100
|
const { basePath, navigate } = useMoveTheWheels();
|
|
54
101
|
const { order, isLoading, error, refetch } = useOrder(orderId);
|
|
102
|
+
const { updateOrder, updateOrderStatus } = useOrders();
|
|
103
|
+
const { vehicles, drivers } = useFleet();
|
|
104
|
+
const { message: notice, notify } = useNotice();
|
|
105
|
+
|
|
106
|
+
const [selectedVehicle, setSelectedVehicle] = useState('');
|
|
107
|
+
const [selectedDriver, setSelectedDriver] = useState('');
|
|
108
|
+
const [assigning, setAssigning] = useState(false);
|
|
109
|
+
const [advancing, setAdvancing] = useState(false);
|
|
110
|
+
const [actionError, setActionError] = useState<string | null>(null);
|
|
111
|
+
|
|
112
|
+
const availableVehicles = vehicles.filter(
|
|
113
|
+
(vehicle) => vehicle.status === 'available' || vehicle.id === order?.vehicleId
|
|
114
|
+
);
|
|
115
|
+
const availableDrivers = drivers.filter(
|
|
116
|
+
(driver) => driver.status === 'available' || driver.id === order?.driverId
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
const handleAssign = async () => {
|
|
120
|
+
if (!orderId || (!selectedVehicle && !selectedDriver)) {
|
|
121
|
+
setActionError('Pick a vehicle and/or a driver to assign.');
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
setAssigning(true);
|
|
125
|
+
setActionError(null);
|
|
126
|
+
try {
|
|
127
|
+
await updateOrder(orderId, {
|
|
128
|
+
vehicleId: selectedVehicle || undefined,
|
|
129
|
+
driverId: selectedDriver || undefined,
|
|
130
|
+
});
|
|
131
|
+
notify('Assignment updated.');
|
|
132
|
+
setSelectedVehicle('');
|
|
133
|
+
setSelectedDriver('');
|
|
134
|
+
refetch();
|
|
135
|
+
} catch (err) {
|
|
136
|
+
setActionError(err instanceof Error ? err.message : 'Failed to update assignment.');
|
|
137
|
+
} finally {
|
|
138
|
+
setAssigning(false);
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const handleStatusChange = async (status: OrderStatus, successMessage: string) => {
|
|
143
|
+
if (!orderId) return;
|
|
144
|
+
setAdvancing(true);
|
|
145
|
+
setActionError(null);
|
|
146
|
+
try {
|
|
147
|
+
await updateOrderStatus(orderId, status);
|
|
148
|
+
notify(successMessage);
|
|
149
|
+
refetch();
|
|
150
|
+
} catch (err) {
|
|
151
|
+
setActionError(err instanceof Error ? err.message : 'Failed to update status.');
|
|
152
|
+
} finally {
|
|
153
|
+
setAdvancing(false);
|
|
154
|
+
}
|
|
155
|
+
};
|
|
55
156
|
|
|
56
157
|
const goTo = (path: string) => {
|
|
57
158
|
if (navigate) {
|
|
@@ -360,7 +461,7 @@ export default function OrderDetailsPage() {
|
|
|
360
461
|
</div>
|
|
361
462
|
</EmphasisPanel>
|
|
362
463
|
|
|
363
|
-
{/* Assignment
|
|
464
|
+
{/* Assignment — assign a vehicle & driver and advance the status */}
|
|
364
465
|
<Card>
|
|
365
466
|
<CardHeader>
|
|
366
467
|
<CardTitle className="flex items-center gap-2">
|
|
@@ -370,18 +471,118 @@ export default function OrderDetailsPage() {
|
|
|
370
471
|
</CardHeader>
|
|
371
472
|
<CardContent>
|
|
372
473
|
<div className="space-y-4">
|
|
474
|
+
{actionError && <FormError message={actionError} />}
|
|
475
|
+
{notice && <Notice message={notice} />}
|
|
476
|
+
|
|
373
477
|
<div>
|
|
374
|
-
<p className="text-sm text-text-secondary">
|
|
375
|
-
<p className="text-sm">{order.
|
|
478
|
+
<p className="text-sm text-text-secondary">Tracking Number</p>
|
|
479
|
+
<p className="text-sm font-mono">{order.trackingNumber || '—'}</p>
|
|
376
480
|
</div>
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
481
|
+
|
|
482
|
+
<Separator />
|
|
483
|
+
|
|
484
|
+
<div className="space-y-3">
|
|
485
|
+
<div className="space-y-1.5">
|
|
486
|
+
<Label htmlFor="assign-vehicle">Vehicle</Label>
|
|
487
|
+
<Select value={selectedVehicle} onValueChange={setSelectedVehicle}>
|
|
488
|
+
<SelectTrigger id="assign-vehicle">
|
|
489
|
+
<SelectValue
|
|
490
|
+
placeholder={
|
|
491
|
+
order.vehicleId ? `Current: ${order.vehicleId}` : 'Select a vehicle'
|
|
492
|
+
}
|
|
493
|
+
/>
|
|
494
|
+
</SelectTrigger>
|
|
495
|
+
<SelectContent>
|
|
496
|
+
{availableVehicles.length === 0 ? (
|
|
497
|
+
<SelectItem value="none" disabled>
|
|
498
|
+
No available vehicles
|
|
499
|
+
</SelectItem>
|
|
500
|
+
) : (
|
|
501
|
+
availableVehicles.map((vehicle) => (
|
|
502
|
+
<SelectItem key={vehicle.id} value={vehicle.id}>
|
|
503
|
+
{vehicle.vehicleNumber} ({vehicle.type})
|
|
504
|
+
</SelectItem>
|
|
505
|
+
))
|
|
506
|
+
)}
|
|
507
|
+
</SelectContent>
|
|
508
|
+
</Select>
|
|
509
|
+
</div>
|
|
510
|
+
|
|
511
|
+
<div className="space-y-1.5">
|
|
512
|
+
<Label htmlFor="assign-driver">Driver</Label>
|
|
513
|
+
<Select value={selectedDriver} onValueChange={setSelectedDriver}>
|
|
514
|
+
<SelectTrigger id="assign-driver">
|
|
515
|
+
<SelectValue
|
|
516
|
+
placeholder={
|
|
517
|
+
order.driverId ? `Current: ${order.driverId}` : 'Select a driver'
|
|
518
|
+
}
|
|
519
|
+
/>
|
|
520
|
+
</SelectTrigger>
|
|
521
|
+
<SelectContent>
|
|
522
|
+
{availableDrivers.length === 0 ? (
|
|
523
|
+
<SelectItem value="none" disabled>
|
|
524
|
+
No available drivers
|
|
525
|
+
</SelectItem>
|
|
526
|
+
) : (
|
|
527
|
+
availableDrivers.map((driver) => (
|
|
528
|
+
<SelectItem key={driver.id} value={driver.id}>
|
|
529
|
+
{driver.fullName} ({driver.employeeId})
|
|
530
|
+
</SelectItem>
|
|
531
|
+
))
|
|
532
|
+
)}
|
|
533
|
+
</SelectContent>
|
|
534
|
+
</Select>
|
|
535
|
+
</div>
|
|
536
|
+
|
|
537
|
+
<Button
|
|
538
|
+
className="w-full"
|
|
539
|
+
onClick={handleAssign}
|
|
540
|
+
disabled={assigning || (!selectedVehicle && !selectedDriver)}
|
|
541
|
+
>
|
|
542
|
+
<UserCheck size={16} />
|
|
543
|
+
{assigning ? 'Assigning…' : 'Assign driver & vehicle'}
|
|
544
|
+
</Button>
|
|
380
545
|
</div>
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
546
|
+
|
|
547
|
+
<Separator />
|
|
548
|
+
|
|
549
|
+
<div className="space-y-2">
|
|
550
|
+
<p className="text-sm text-text-secondary">
|
|
551
|
+
Current status: <span className="font-medium">{STATUS_LABELS[order.status]}</span>
|
|
552
|
+
</p>
|
|
553
|
+
{(() => {
|
|
554
|
+
const upcoming = nextStatus(order.status);
|
|
555
|
+
if (!upcoming) return null;
|
|
556
|
+
return (
|
|
557
|
+
<Button
|
|
558
|
+
variant="outline"
|
|
559
|
+
className="w-full"
|
|
560
|
+
onClick={() =>
|
|
561
|
+
handleStatusChange(
|
|
562
|
+
upcoming,
|
|
563
|
+
`Order advanced to ${STATUS_LABELS[upcoming]}.`
|
|
564
|
+
)
|
|
565
|
+
}
|
|
566
|
+
disabled={advancing}
|
|
567
|
+
>
|
|
568
|
+
<ArrowRight size={16} />
|
|
569
|
+
{advancing ? 'Updating…' : `Advance to ${STATUS_LABELS[upcoming]}`}
|
|
570
|
+
</Button>
|
|
571
|
+
);
|
|
572
|
+
})()}
|
|
573
|
+
{order.status !== 'cancelled' && order.status !== 'delivered' && (
|
|
574
|
+
<Button
|
|
575
|
+
variant="outline"
|
|
576
|
+
className="w-full text-status-error-text"
|
|
577
|
+
onClick={() => handleStatusChange('cancelled', 'Order cancelled.')}
|
|
578
|
+
disabled={advancing}
|
|
579
|
+
>
|
|
580
|
+
<XCircle size={16} />
|
|
581
|
+
Cancel order
|
|
582
|
+
</Button>
|
|
583
|
+
)}
|
|
384
584
|
</div>
|
|
585
|
+
|
|
385
586
|
<Button
|
|
386
587
|
variant="outline"
|
|
387
588
|
className="w-full"
|
|
@@ -394,6 +595,9 @@ export default function OrderDetailsPage() {
|
|
|
394
595
|
</CardContent>
|
|
395
596
|
</Card>
|
|
396
597
|
|
|
598
|
+
{/* Documents — real files via wspace-files-svc */}
|
|
599
|
+
<DocumentsCard entityType="order" entityId={order.id} />
|
|
600
|
+
|
|
397
601
|
{/* Special Instructions */}
|
|
398
602
|
{order.specialInstructions && (
|
|
399
603
|
<Card>
|
|
@@ -5,11 +5,23 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { useState } from 'react';
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
Package,
|
|
10
|
+
Plus,
|
|
11
|
+
Filter,
|
|
12
|
+
Download,
|
|
13
|
+
RefreshCw,
|
|
14
|
+
UserPlus,
|
|
15
|
+
UserCheck,
|
|
16
|
+
Clock,
|
|
17
|
+
Truck,
|
|
18
|
+
CheckCircle2,
|
|
19
|
+
} from 'lucide-react';
|
|
9
20
|
import { PageLayout } from '../components/PageLayout';
|
|
21
|
+
import { MetricCard, MetricGrid } from '../components/MetricCard';
|
|
10
22
|
import { DataTable, type Column } from '../components/DataTable';
|
|
11
23
|
import { StatusBadge, PriorityBadge, TypeBadge } from '../components/StatusBadge';
|
|
12
|
-
import { CTAOverflowMenu,
|
|
24
|
+
import { CTAOverflowMenu, PagePurpose } from '@burdenoff/fe-libs/ui';
|
|
13
25
|
import {
|
|
14
26
|
Button,
|
|
15
27
|
Card,
|
|
@@ -21,7 +33,7 @@ import {
|
|
|
21
33
|
SelectValue,
|
|
22
34
|
Badge,
|
|
23
35
|
} from '../components/ui';
|
|
24
|
-
import { useOrders } from '../hooks/useOrders';
|
|
36
|
+
import { useOrders, useOrderStats } from '../hooks/useOrders';
|
|
25
37
|
import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
|
|
26
38
|
import { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';
|
|
27
39
|
import { formatCurrency, formatDate } from '../utils/formatters';
|
|
@@ -37,21 +49,36 @@ export default function OrdersListPage() {
|
|
|
37
49
|
const { basePath, navigate } = useMoveTheWheels();
|
|
38
50
|
const [statusFilter, setStatusFilter] = useState<OrderStatus | 'all'>('all');
|
|
39
51
|
const [typeFilter, setTypeFilter] = useState<'b2b' | 'b2c' | 'all'>('all');
|
|
52
|
+
const [unassignedOnly, setUnassignedOnly] = useState(false);
|
|
53
|
+
const [page, setPage] = useState(0);
|
|
54
|
+
const [pageSize, setPageSize] = useState(25);
|
|
55
|
+
|
|
56
|
+
const filtersActive = statusFilter !== 'all' || typeFilter !== 'all' || unassignedOnly;
|
|
40
57
|
|
|
41
|
-
const { orders,
|
|
58
|
+
const { orders, hasMore, isLoading, refetch } = useOrders({
|
|
42
59
|
filters: {
|
|
43
60
|
status: statusFilter === 'all' ? undefined : statusFilter,
|
|
44
61
|
orderType: typeFilter === 'all' ? undefined : typeFilter,
|
|
62
|
+
unassigned: unassignedOnly || undefined,
|
|
45
63
|
},
|
|
46
|
-
limit:
|
|
64
|
+
limit: pageSize,
|
|
65
|
+
offset: page * pageSize,
|
|
47
66
|
});
|
|
48
67
|
|
|
68
|
+
// Pipeline totals + "at a glance" tiles come from the live aggregate, never
|
|
69
|
+
// from the truncated current page.
|
|
70
|
+
const { stats } = useOrderStats();
|
|
71
|
+
const totalOrders = stats.total;
|
|
72
|
+
|
|
49
73
|
const goTo = (path: string) => {
|
|
50
74
|
if (navigate) {
|
|
51
75
|
navigate(joinPath(basePath, path));
|
|
52
76
|
}
|
|
53
77
|
};
|
|
54
78
|
|
|
79
|
+
// Reset to the first page whenever the active filter set changes.
|
|
80
|
+
const resetPage = () => setPage(0);
|
|
81
|
+
|
|
55
82
|
const columns: Column<Order>[] = [
|
|
56
83
|
{
|
|
57
84
|
id: 'orderNumber',
|
|
@@ -129,6 +156,29 @@ export default function OrdersListPage() {
|
|
|
129
156
|
className: 'min-w-[100px] whitespace-nowrap',
|
|
130
157
|
cell: (row) => formatDate(row.createdAt, 'relative'),
|
|
131
158
|
},
|
|
159
|
+
{
|
|
160
|
+
id: 'driver',
|
|
161
|
+
header: t('movethewheels.orders.table.driver', 'Driver'),
|
|
162
|
+
className: 'min-w-[120px] whitespace-nowrap',
|
|
163
|
+
cell: (row) =>
|
|
164
|
+
row.driverId ? (
|
|
165
|
+
<span className="inline-flex items-center gap-1 text-sm text-status-success-text">
|
|
166
|
+
<UserCheck size={14} /> {t('movethewheels.orders.table.assigned', 'Assigned')}
|
|
167
|
+
</span>
|
|
168
|
+
) : (
|
|
169
|
+
<Button
|
|
170
|
+
variant="outline"
|
|
171
|
+
size="sm"
|
|
172
|
+
onClick={(e) => {
|
|
173
|
+
e.stopPropagation();
|
|
174
|
+
goTo(`orders/${row.id}`);
|
|
175
|
+
}}
|
|
176
|
+
>
|
|
177
|
+
<UserPlus size={14} className="mr-1" />
|
|
178
|
+
{t('movethewheels.orders.table.assign', 'Assign')}
|
|
179
|
+
</Button>
|
|
180
|
+
),
|
|
181
|
+
},
|
|
132
182
|
];
|
|
133
183
|
|
|
134
184
|
const statusOptions: { value: OrderStatus | 'all'; label: string }[] = [
|
|
@@ -181,47 +231,40 @@ export default function OrdersListPage() {
|
|
|
181
231
|
)}
|
|
182
232
|
</PagePurpose>
|
|
183
233
|
|
|
184
|
-
{/* Pipeline at a glance —
|
|
185
|
-
<
|
|
186
|
-
<p className="text-sm font-medium text-text-secondary">
|
|
234
|
+
{/* Pipeline at a glance — premium stat cards */}
|
|
235
|
+
<div>
|
|
236
|
+
<p className="mb-3 text-sm font-medium text-text-secondary">
|
|
187
237
|
{t('movethewheels.orders.glance.title', 'Pipeline at a glance')}
|
|
188
238
|
</p>
|
|
189
|
-
<
|
|
190
|
-
<
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
<
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
</p>
|
|
219
|
-
<p className="text-xs text-text-secondary">
|
|
220
|
-
{t('movethewheels.orders.glance.delivered', 'Delivered')}
|
|
221
|
-
</p>
|
|
222
|
-
</div>
|
|
223
|
-
</div>
|
|
224
|
-
</EmphasisPanel>
|
|
239
|
+
<MetricGrid columns={4}>
|
|
240
|
+
<MetricCard
|
|
241
|
+
label={t('movethewheels.orders.glance.total', 'Total orders')}
|
|
242
|
+
value={totalOrders}
|
|
243
|
+
icon={<Package size={20} />}
|
|
244
|
+
accent="brand"
|
|
245
|
+
/>
|
|
246
|
+
<MetricCard
|
|
247
|
+
label={t('movethewheels.orders.glance.pending', 'Pending')}
|
|
248
|
+
value={stats.byStatus.pending}
|
|
249
|
+
icon={<Clock size={20} />}
|
|
250
|
+
accent="warning"
|
|
251
|
+
subtitle={t('movethewheels.orders.glance.pendingSub', 'Awaiting fulfilment')}
|
|
252
|
+
/>
|
|
253
|
+
<MetricCard
|
|
254
|
+
label={t('movethewheels.orders.glance.inTransit', 'In transit')}
|
|
255
|
+
value={stats.byStatus.in_transit + stats.byStatus.out_for_delivery}
|
|
256
|
+
icon={<Truck size={20} />}
|
|
257
|
+
accent="blue"
|
|
258
|
+
subtitle={t('movethewheels.orders.glance.inTransitSub', 'On the road now')}
|
|
259
|
+
/>
|
|
260
|
+
<MetricCard
|
|
261
|
+
label={t('movethewheels.orders.glance.delivered', 'Delivered')}
|
|
262
|
+
value={stats.byStatus.delivered}
|
|
263
|
+
icon={<CheckCircle2 size={20} />}
|
|
264
|
+
accent="success"
|
|
265
|
+
/>
|
|
266
|
+
</MetricGrid>
|
|
267
|
+
</div>
|
|
225
268
|
|
|
226
269
|
{/* Filters */}
|
|
227
270
|
<Card>
|
|
@@ -235,7 +278,10 @@ export default function OrdersListPage() {
|
|
|
235
278
|
</div>
|
|
236
279
|
<Select
|
|
237
280
|
value={statusFilter}
|
|
238
|
-
onValueChange={(value) =>
|
|
281
|
+
onValueChange={(value) => {
|
|
282
|
+
setStatusFilter(value as OrderStatus | 'all');
|
|
283
|
+
resetPage();
|
|
284
|
+
}}
|
|
239
285
|
>
|
|
240
286
|
<SelectTrigger className="w-[180px]">
|
|
241
287
|
<SelectValue placeholder={t('movethewheels.orders.filters.status', 'Status')} />
|
|
@@ -250,7 +296,10 @@ export default function OrdersListPage() {
|
|
|
250
296
|
</Select>
|
|
251
297
|
<Select
|
|
252
298
|
value={typeFilter}
|
|
253
|
-
onValueChange={(value) =>
|
|
299
|
+
onValueChange={(value) => {
|
|
300
|
+
setTypeFilter(value as 'b2b' | 'b2c' | 'all');
|
|
301
|
+
resetPage();
|
|
302
|
+
}}
|
|
254
303
|
>
|
|
255
304
|
<SelectTrigger className="w-[150px]">
|
|
256
305
|
<SelectValue placeholder={t('movethewheels.orders.filters.type', 'Type')} />
|
|
@@ -263,13 +312,26 @@ export default function OrdersListPage() {
|
|
|
263
312
|
<SelectItem value="b2c">B2C</SelectItem>
|
|
264
313
|
</SelectContent>
|
|
265
314
|
</Select>
|
|
266
|
-
|
|
315
|
+
<Button
|
|
316
|
+
variant={unassignedOnly ? 'default' : 'outline'}
|
|
317
|
+
size="sm"
|
|
318
|
+
onClick={() => {
|
|
319
|
+
setUnassignedOnly((prev) => !prev);
|
|
320
|
+
resetPage();
|
|
321
|
+
}}
|
|
322
|
+
>
|
|
323
|
+
<UserPlus size={14} className="mr-1" />
|
|
324
|
+
{t('movethewheels.orders.filters.unassigned', 'Needs driver')}
|
|
325
|
+
</Button>
|
|
326
|
+
{filtersActive && (
|
|
267
327
|
<Button
|
|
268
328
|
variant="ghost"
|
|
269
329
|
size="sm"
|
|
270
330
|
onClick={() => {
|
|
271
331
|
setStatusFilter('all');
|
|
272
332
|
setTypeFilter('all');
|
|
333
|
+
setUnassignedOnly(false);
|
|
334
|
+
resetPage();
|
|
273
335
|
}}
|
|
274
336
|
>
|
|
275
337
|
{t('movethewheels.orders.filters.clear', 'Clear filters')}
|
|
@@ -289,6 +351,15 @@ export default function OrdersListPage() {
|
|
|
289
351
|
searchFields={['orderNumber', 'customerInfo'] as (keyof Order)[]}
|
|
290
352
|
onRowClick={(row) => goTo(`orders/${row.id}`)}
|
|
291
353
|
getRowId={(row) => row.id}
|
|
354
|
+
pageIndex={page}
|
|
355
|
+
pageSize={pageSize}
|
|
356
|
+
onPageChange={setPage}
|
|
357
|
+
onPageSizeChange={(size) => {
|
|
358
|
+
setPageSize(size);
|
|
359
|
+
setPage(0);
|
|
360
|
+
}}
|
|
361
|
+
totalItems={filtersActive ? undefined : totalOrders}
|
|
362
|
+
hasNextPage={hasMore}
|
|
292
363
|
emptyMessage={t('movethewheels.orders.empty', 'No orders found')}
|
|
293
364
|
emptyIllustration="empty-data"
|
|
294
365
|
emptyTitle={t('movethewheels.orders.emptyTitle', 'No orders yet')}
|
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
* Products Page - List of all products
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { useState } from 'react';
|
|
6
|
+
import { Package, Plus, RefreshCw, Download, AlertTriangle, Thermometer } from 'lucide-react';
|
|
6
7
|
import { PageLayout } from '../components/PageLayout';
|
|
8
|
+
import { MetricCard, MetricGrid } from '../components/MetricCard';
|
|
7
9
|
import { DataTable, type Column } from '../components/DataTable';
|
|
8
10
|
import { Badge } from '../components/ui';
|
|
9
|
-
import { CTAOverflowMenu,
|
|
10
|
-
import { useProducts } from '../hooks/useInventory';
|
|
11
|
+
import { CTAOverflowMenu, PagePurpose } from '@burdenoff/fe-libs/ui';
|
|
12
|
+
import { useProducts, useProductStats } from '../hooks/useInventory';
|
|
11
13
|
import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
|
|
12
14
|
import { formatCurrency } from '../utils/formatters';
|
|
13
15
|
import { joinPath } from '../utils/navigation';
|
|
@@ -15,7 +17,14 @@ import type { Product } from '../types';
|
|
|
15
17
|
|
|
16
18
|
export default function ProductsPage() {
|
|
17
19
|
const { basePath, navigate } = useMoveTheWheels();
|
|
18
|
-
const
|
|
20
|
+
const [page, setPage] = useState(0);
|
|
21
|
+
const [pageSize, setPageSize] = useState(25);
|
|
22
|
+
const { products, hasMore, isLoading, refetch } = useProducts({
|
|
23
|
+
limit: pageSize,
|
|
24
|
+
offset: page * pageSize,
|
|
25
|
+
});
|
|
26
|
+
// Bounded aggregate for the tiles + subtitle — not the truncated page.
|
|
27
|
+
const { stats } = useProductStats();
|
|
19
28
|
|
|
20
29
|
const goTo = (path: string) => navigate?.(joinPath(basePath, path));
|
|
21
30
|
|
|
@@ -94,7 +103,7 @@ export default function ProductsPage() {
|
|
|
94
103
|
return (
|
|
95
104
|
<PageLayout
|
|
96
105
|
title="Products"
|
|
97
|
-
subtitle={`${
|
|
106
|
+
subtitle={`${stats.total} products`}
|
|
98
107
|
icon={<Package size={24} />}
|
|
99
108
|
headerActions={
|
|
100
109
|
<CTAOverflowMenu
|
|
@@ -105,7 +114,11 @@ export default function ProductsPage() {
|
|
|
105
114
|
}}
|
|
106
115
|
actions={[
|
|
107
116
|
{ label: 'Refresh', icon: <RefreshCw size={16} />, onSelect: () => refetch() },
|
|
108
|
-
{
|
|
117
|
+
{
|
|
118
|
+
label: 'Export',
|
|
119
|
+
icon: <Download size={16} />,
|
|
120
|
+
onSelect: () => goTo('import-export'),
|
|
121
|
+
},
|
|
109
122
|
]}
|
|
110
123
|
/>
|
|
111
124
|
}
|
|
@@ -116,28 +129,31 @@ export default function ProductsPage() {
|
|
|
116
129
|
routed — keep them accurate so shipments are quoted and handled correctly.
|
|
117
130
|
</PagePurpose>
|
|
118
131
|
|
|
119
|
-
{/* Catalog at a glance —
|
|
120
|
-
<
|
|
121
|
-
<p className="text-sm font-medium text-text-secondary">Catalog at a glance</p>
|
|
122
|
-
<
|
|
123
|
-
<
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
132
|
+
{/* Catalog at a glance — premium stat cards */}
|
|
133
|
+
<div>
|
|
134
|
+
<p className="mb-3 text-sm font-medium text-text-secondary">Catalog at a glance</p>
|
|
135
|
+
<MetricGrid columns={3}>
|
|
136
|
+
<MetricCard
|
|
137
|
+
label="Total products"
|
|
138
|
+
value={stats.total}
|
|
139
|
+
icon={<Package size={20} />}
|
|
140
|
+
accent="brand"
|
|
141
|
+
/>
|
|
142
|
+
<MetricCard
|
|
143
|
+
label="Special handling"
|
|
144
|
+
value={stats.specialHandling}
|
|
145
|
+
icon={<AlertTriangle size={20} />}
|
|
146
|
+
accent="warning"
|
|
147
|
+
subtitle="Fragile or hazardous"
|
|
148
|
+
/>
|
|
149
|
+
<MetricCard
|
|
150
|
+
label="Temp-controlled"
|
|
151
|
+
value={stats.temperatureControlled}
|
|
152
|
+
icon={<Thermometer size={20} />}
|
|
153
|
+
accent="blue"
|
|
154
|
+
/>
|
|
155
|
+
</MetricGrid>
|
|
156
|
+
</div>
|
|
141
157
|
|
|
142
158
|
<DataTable
|
|
143
159
|
columns={columns}
|
|
@@ -148,6 +164,14 @@ export default function ProductsPage() {
|
|
|
148
164
|
searchFields={['name', 'sku', 'category'] as (keyof Product)[]}
|
|
149
165
|
onRowClick={(row) => goTo(`inventory/products/${row.id}`)}
|
|
150
166
|
getRowId={(row) => row.id}
|
|
167
|
+
pageIndex={page}
|
|
168
|
+
pageSize={pageSize}
|
|
169
|
+
onPageChange={setPage}
|
|
170
|
+
onPageSizeChange={(size) => {
|
|
171
|
+
setPageSize(size);
|
|
172
|
+
setPage(0);
|
|
173
|
+
}}
|
|
174
|
+
hasNextPage={hasMore}
|
|
151
175
|
emptyIllustration="empty-files"
|
|
152
176
|
emptyTitle="No products yet"
|
|
153
177
|
emptyDescription="Add products to your catalog so they can be added to orders and stocked in warehouses."
|