@burdenoff/microfe-movethewheels 2026.703.4 → 2026.703.6
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-3YOnHO9Y.js → AIAssistantPage-BmILdHgO.js} +16 -15
- package/dist/AnalyticsPage-BSlsiSDE.js +194 -0
- package/dist/{CreateOrderPage-DRAgGo9i.js → CreateOrderPage-DaCgVaO3.js} +40 -40
- package/dist/{CustomerDetailsPage-BCeMmOFe.js → CustomerDetailsPage-DErpbafW.js} +71 -71
- package/dist/CustomersPage-C1lRU6xs.js +205 -0
- package/dist/DashboardPage-Q5-5EKAU.js +542 -0
- package/dist/{DataTable-XvUcTjwi.js → DataTable-KjzbwnRL.js} +31 -31
- package/dist/{DocumentsCard-U2lSe-cn.js → DocumentsCard-T9y6cv4-.js} +25 -25
- package/dist/{DriverDetailsPage-BQrYd_Lp.js → DriverDetailsPage-DnHGkDC7.js} +64 -64
- package/dist/DriversPage-C0ujWVOn.js +221 -0
- package/dist/FinancePage-CA2XEgsQ.js +175 -0
- package/dist/{FleetPage-3ZHsB-TN.js → FleetPage-DrvXW7wK.js} +22 -22
- package/dist/{FormError-cuLV2jVD.js → FormError-CRsMP7TQ.js} +1 -1
- package/dist/{ImportExportPage-C6f7DqhM.js → ImportExportPage-Cac9HjFx.js} +12 -12
- package/dist/InventoryPage-Ezf4gqKI.js +259 -0
- package/dist/LiveTrackingPage-CzbVwDqK.js +429 -0
- package/dist/{MarketplacePage-Bh_2ns4v.js → MarketplacePage-BHDmSQmo.js} +116 -116
- package/dist/MetricCard-DQlDNqmK.js +191 -0
- package/dist/{Notice-CSoDYKw9.js → Notice-GmA7JDR3.js} +2 -2
- package/dist/{OrderDetailsPage-zCj0lW0j.js → OrderDetailsPage-CR0Wc1aN.js} +58 -58
- package/dist/OrdersListPage-C8Bu6wXM.js +386 -0
- package/dist/{PageLayout-D4LDHJJq.js → PageLayout-DX1Ou6V6.js} +164 -134
- package/dist/{ProductDetailsPage-wsxAQkA0.js → ProductDetailsPage-B_ns2MOe.js} +55 -55
- package/dist/ProductsPage-c92BsEoA.js +186 -0
- package/dist/ReportsPage-CPLXtLT2.js +377 -0
- package/dist/{RouteDetailsPage-DyMJE9AS.js → RouteDetailsPage-DOfcRiFv.js} +208 -181
- package/dist/{RoutesPage-Dtc669yc.js → RoutesPage-BZGublby.js} +12 -12
- package/dist/{SettingsPage-CpH9xhQg.js → SettingsPage-5cweX_xh.js} +15 -15
- package/dist/Sparkline-zSXvG6vA.js +16729 -0
- package/dist/{StatusBadge-C87mDpDP.js → StatusBadge-oZjownr1.js} +7 -7
- package/dist/TrackingPage-Ln9qVflK.js +368 -0
- package/dist/{VehicleDetailsPage-DldYNsYV.js → VehicleDetailsPage-CrkzE0dN.js} +53 -53
- package/dist/VehiclesPage-CTQWH7af.js +184 -0
- package/dist/{WarehouseDetailsPage-CMG7or6G.js → WarehouseDetailsPage-CwDLaG65.js} +64 -64
- package/dist/WarehousesPage-KW02DvZg.js +182 -0
- package/dist/{arrow-left-n48Y-Wis.js → arrow-left-DJDw_Yqv.js} +1 -1
- package/dist/{arrow-right-Q3XQpTPR.js → arrow-right-Cb2fLTPJ.js} +1 -1
- package/dist/{arrow-up-xNF1zJUG.js → arrow-up-PxaOIyiT.js} +1 -1
- package/dist/{box-Eu05PXDh.js → box-hSJVtvGf.js} +1 -1
- package/dist/boxes-DDnxEkT5.js +54 -0
- package/dist/{chart-column-ouzgxIv_.js → chart-column-xXgAZvRl.js} +1 -1
- package/dist/charts-CfCwQg6o.js +2018 -0
- package/dist/{chevron-left-B2_r0Elx.js → chevron-left-dhEGJYdh.js} +1 -1
- package/dist/{chevron-right-eV4LjaSi.js → chevron-right-Cud2NDvm.js} +1 -1
- package/dist/chunk-BdsCSvBP.js +20 -0
- package/dist/{circle-alert-Bzh2DBSF.js → circle-alert-Rx0p3D5w.js} +1 -1
- package/dist/circle-check-BCxRFdFo.js +13 -0
- package/dist/{circle-check-big-CGMSpQmD.js → circle-check-big-BcIJ8xjx.js} +1 -1
- package/dist/{clock-Bl3ptexL.js → clock-B2_YxJgB.js} +1 -1
- package/dist/{dollar-sign-CAVU95SO.js → dollar-sign-CQmAICv4.js} +1 -1
- package/dist/{download-C5NbvVDc.js → download-CG4Tponq.js} +1 -1
- package/dist/{file-text-D-1uvLZG.js → file-text-CzH-zMub.js} +1 -1
- package/dist/{filter-DUfUg6BO.js → filter-C0u2Lwfn.js} +1 -1
- package/dist/{fuel-B6_lnKDs.js → fuel-C2KELcM-.js} +1 -1
- package/dist/index.css +2 -0
- package/dist/index.js +214 -225
- package/dist/{map-C-M-FQLR.js → map-FuPu5aTw.js} +1 -1
- package/dist/{map-pin-Dznl5l40.js → map-pin-czpLv9uy.js} +1 -1
- package/dist/mapMarkers-NCV9MRKR.js +28 -0
- package/dist/maps-D9m7PKrs.js +5070 -0
- package/dist/{navigation-BJSJl-VG.js → navigation-D5_pdCSS.js} +1 -1
- package/dist/{package-CcRttV2N.js → package-B0sdi6lU.js} +1 -1
- package/dist/{phone-DVSZHt_j.js → phone-V2nuzPu9.js} +1 -1
- package/dist/{plus-C_XZDwBz.js → plus-BqQxkfoU.js} +1 -1
- package/dist/{refresh-cw-CfgN67l3.js → refresh-cw-CEOAPEcV.js} +1 -1
- package/dist/route-CZWcW-Dw.js +22 -0
- package/dist/{save-DJE8Fpdv.js → save-DDFvrxWl.js} +1 -1
- package/dist/{search-D_VyGcZT.js → search-D7Ya6jxj.js} +1 -1
- package/dist/{square-pen-D-6JYrVQ.js → square-pen-DoZZS4Lw.js} +1 -1
- package/dist/{star-BpRlUZuU.js → star-B7PgVu5j.js} +1 -1
- package/dist/{store-B-8HMuyZ.js → store-YVzcIG4A.js} +1 -1
- package/dist/{trash-2-8vmv-XV_.js → trash-2-BegnBzwI.js} +1 -1
- package/dist/{trending-up-Dz6PqPRj.js → trending-up-DBd3XNdb.js} +1 -1
- package/dist/{triangle-alert-D7Y3J_4z.js → triangle-alert-Bxk3Kgs6.js} +1 -1
- package/dist/{truck-D-y2IDzv.js → truck-Clrn0oHp.js} +1 -1
- package/dist/{upload-CHZofOqb.js → upload-Df5JmMwH.js} +1 -1
- package/dist/{useAssistant-DVW4QgQb.js → useAssistant-Cb8nnIVC.js} +11 -28
- package/dist/{useCustomers-BPW4__PR.js → useCustomers-Dq2IR2HQ.js} +1 -1
- package/dist/{useFleet-ChBkxAd6.js → useFleet-CapnWBcA.js} +1 -1
- package/dist/{useImportExport-DrPDHEDK.js → useImportExport-BoGzrlJy.js} +1 -1
- package/dist/{useInventory-CAAVMRp6.js → useInventory-Ci6M2d9f.js} +1 -1
- package/dist/{useOrders-B465S2nt.js → useOrders-BuQU1OHX.js} +1 -1
- package/dist/{useReports-jQ3GpNOm.js → useReports-DIx8W7CO.js} +1 -1
- package/dist/{useRoutes-hS3d5-xr.js → useRoutes-D7EvI_pm.js} +1 -1
- package/dist/{useSettings-BB8X2RLh.js → useSettings-BhX251UQ.js} +6 -6
- package/dist/{useTracking-RjGz9PZG.js → useTracking-C40jmZ6W.js} +3 -3
- package/dist/{user-DzfTn4dE.js → user-BlcJrbtE.js} +1 -1
- package/dist/{user-check-BEKRCi7v.js → user-check-DtnQmimP.js} +1 -1
- package/dist/{users-CCrq-w0C.js → users-Cvwko-Dx.js} +1 -1
- package/dist/{warehouse-CaqfmMKh.js → warehouse-LWLVTk6G.js} +1 -1
- package/dist/{wrench-q_rgJfaP.js → wrench-Cme0RbyR.js} +1 -1
- package/dist/{x-tMeDMcCw.js → x-D8C9Pg5V.js} +1 -1
- package/package.json +5 -1
- package/src/movethewheels/components/GlassPanel.tsx +71 -0
- package/src/movethewheels/components/MetricCard.tsx +171 -73
- 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 +44 -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/useAnalytics.ts +14 -4
- package/src/movethewheels/pages/AnalyticsPage.tsx +93 -73
- package/src/movethewheels/pages/CreateOrderPage.tsx +4 -4
- package/src/movethewheels/pages/CustomersPage.tsx +29 -20
- package/src/movethewheels/pages/DashboardPage.tsx +238 -12
- package/src/movethewheels/pages/DriverDetailsPage.tsx +1 -1
- package/src/movethewheels/pages/DriversPage.tsx +35 -24
- package/src/movethewheels/pages/FinancePage.tsx +68 -30
- package/src/movethewheels/pages/InventoryPage.tsx +15 -3
- package/src/movethewheels/pages/LiveTrackingPage.tsx +124 -16
- package/src/movethewheels/pages/OrdersListPage.tsx +46 -38
- package/src/movethewheels/pages/ProductsPage.tsx +28 -24
- package/src/movethewheels/pages/ReportsPage.tsx +23 -0
- package/src/movethewheels/pages/RouteDetailsPage.tsx +46 -7
- package/src/movethewheels/pages/SettingsPage.tsx +2 -2
- package/src/movethewheels/pages/TrackingPage.tsx +76 -6
- package/src/movethewheels/pages/VehicleDetailsPage.tsx +1 -1
- package/src/movethewheels/pages/VehiclesPage.tsx +34 -24
- package/src/movethewheels/pages/WarehouseDetailsPage.tsx +1 -1
- package/src/movethewheels/pages/WarehousesPage.tsx +28 -22
- package/src/movethewheels/types/index.ts +1 -1
- package/src/movethewheels/utils/graphqlClient.ts +81 -31
- package/src/movethewheels/utils/mapMarkers.ts +58 -0
- package/dist/AnalyticsPage-CMpI1070.js +0 -215
- package/dist/CustomersPage-CaNBMXmM.js +0 -172
- package/dist/DashboardPage-DIKlXde3.js +0 -387
- package/dist/DriversPage-PYmdFiby.js +0 -176
- package/dist/FinancePage-Cwh9Bw4M.js +0 -136
- package/dist/InventoryPage-DpiqVu1v.js +0 -253
- package/dist/LiveTrackingPage-FAzzlVnX.js +0 -379
- package/dist/MetricCard-CVBVpu7o.js +0 -146
- package/dist/OrdersListPage-CAMsuUck.js +0 -384
- package/dist/ProductsPage-DC5qDLfz.js +0 -180
- package/dist/ReportsPage-BwUJ37eO.js +0 -356
- package/dist/TrackingPage-DaNEFG_h.js +0 -325
- package/dist/VehiclesPage-BFw13iMt.js +0 -176
- package/dist/WarehousesPage-Wkytnt86.js +0 -173
- package/dist/useAnalytics-D7HW2XRb.js +0 -347
- /package/dist/{formatters-BQr-aR5W.js → formatters-FWJDwTFv.js} +0 -0
|
@@ -2,7 +2,15 @@
|
|
|
2
2
|
* Inventory Page - Overview of inventory management
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
Package,
|
|
7
|
+
Warehouse,
|
|
8
|
+
AlertTriangle,
|
|
9
|
+
ChevronRight,
|
|
10
|
+
Plus,
|
|
11
|
+
Boxes,
|
|
12
|
+
DollarSign,
|
|
13
|
+
} from 'lucide-react';
|
|
6
14
|
import { PageLayout, PageSection } from '../components/PageLayout';
|
|
7
15
|
import { MetricCard, MetricGrid, MetricCardSkeleton } from '../components/MetricCard';
|
|
8
16
|
import { Card, CardContent, Button, Badge, Progress } from '../components/ui';
|
|
@@ -88,22 +96,26 @@ export default function InventoryPage() {
|
|
|
88
96
|
label={t('movethewheels.inventory.metrics.totalProducts', 'Total Products')}
|
|
89
97
|
value={stats.totalProducts}
|
|
90
98
|
icon={<Package size={20} />}
|
|
99
|
+
accent="brand"
|
|
91
100
|
onClick={() => goTo('inventory/products')}
|
|
92
101
|
/>
|
|
93
102
|
<MetricCard
|
|
94
103
|
label={t('movethewheels.inventory.metrics.totalItems', 'Total Items')}
|
|
95
104
|
value={stats.totalItems.toLocaleString()}
|
|
96
|
-
icon={<
|
|
105
|
+
icon={<Boxes size={20} />}
|
|
106
|
+
accent="success"
|
|
97
107
|
/>
|
|
98
108
|
<MetricCard
|
|
99
109
|
label={t('movethewheels.inventory.metrics.inventoryValue', 'Inventory Value')}
|
|
100
110
|
value={formatCurrency(stats.totalValue)}
|
|
101
|
-
icon={<
|
|
111
|
+
icon={<DollarSign size={20} />}
|
|
112
|
+
accent="blue"
|
|
102
113
|
/>
|
|
103
114
|
<MetricCard
|
|
104
115
|
label={t('movethewheels.inventory.metrics.warehouses', 'Warehouses')}
|
|
105
116
|
value={stats.totalWarehouses}
|
|
106
117
|
icon={<Warehouse size={20} />}
|
|
118
|
+
accent="neutral"
|
|
107
119
|
onClick={() => goTo('inventory/warehouses')}
|
|
108
120
|
/>
|
|
109
121
|
</MetricGrid>
|
|
@@ -4,11 +4,13 @@
|
|
|
4
4
|
* Real-time map view of all active deliveries.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { useState } from 'react';
|
|
7
|
+
import { useMemo, useState } from 'react';
|
|
8
8
|
import { MapPin, Truck, Package, RefreshCw, List, Map } from 'lucide-react';
|
|
9
9
|
import { PageLayout, EmptyState } from '../components/PageLayout';
|
|
10
10
|
import { StatusBadge } from '../components/StatusBadge';
|
|
11
11
|
import { MetricCard, MetricGrid } from '../components/MetricCard';
|
|
12
|
+
import { LogisticsMap, type LogisticsMarker } from '../components/maps';
|
|
13
|
+
import { deliveryMarkerStatus, firstRealLatLng } from '../utils/mapMarkers';
|
|
12
14
|
import {
|
|
13
15
|
Card,
|
|
14
16
|
CardContent,
|
|
@@ -27,12 +29,15 @@ import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
|
|
|
27
29
|
import { formatDate } from '../utils/formatters';
|
|
28
30
|
import { joinPath } from '../utils/navigation';
|
|
29
31
|
|
|
32
|
+
type LiveFilter = 'all' | 'in_transit' | 'out_for_delivery' | 'other';
|
|
33
|
+
|
|
30
34
|
export default function LiveTrackingPage() {
|
|
31
35
|
const { basePath, navigate } = useMoveTheWheels();
|
|
32
36
|
const { activeDeliveries, isLoading, refetch } = useLiveTracking();
|
|
33
37
|
const { unacknowledgedAlerts } = useTrackingAlerts();
|
|
34
38
|
const [viewMode, setViewMode] = useState<'map' | 'list'>('map');
|
|
35
39
|
const [selectedDelivery, setSelectedDelivery] = useState<string | null>(null);
|
|
40
|
+
const [filter, setFilter] = useState<LiveFilter>('all');
|
|
36
41
|
|
|
37
42
|
const goTo = (path: string) => {
|
|
38
43
|
if (navigate) {
|
|
@@ -44,6 +49,70 @@ export default function LiveTrackingPage() {
|
|
|
44
49
|
? activeDeliveries.find((d) => d.order?.id === selectedDelivery)
|
|
45
50
|
: null;
|
|
46
51
|
|
|
52
|
+
const filteredDeliveries = useMemo(
|
|
53
|
+
() =>
|
|
54
|
+
activeDeliveries.filter((d) => {
|
|
55
|
+
const status = d.order?.status;
|
|
56
|
+
if (filter === 'all') return true;
|
|
57
|
+
if (filter === 'in_transit') return status === 'in_transit';
|
|
58
|
+
if (filter === 'out_for_delivery') return status === 'out_for_delivery';
|
|
59
|
+
return status !== 'in_transit' && status !== 'out_for_delivery';
|
|
60
|
+
}),
|
|
61
|
+
[activeDeliveries, filter]
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
// Build brand markers from the best real coordinate we hold for each delivery —
|
|
65
|
+
// the vehicle GPS fix, else the driver's, else the latest tracking event.
|
|
66
|
+
// Deliveries with no real coordinate are honestly omitted (never plotted at 0,0).
|
|
67
|
+
const markers = useMemo<LogisticsMarker[]>(() => {
|
|
68
|
+
return filteredDeliveries.flatMap((d) => {
|
|
69
|
+
if (!d.order) return [];
|
|
70
|
+
const coord = firstRealLatLng(
|
|
71
|
+
d.vehicle?.currentLocation,
|
|
72
|
+
d.driver?.currentLocation,
|
|
73
|
+
d.tracking?.currentLocation
|
|
74
|
+
);
|
|
75
|
+
if (!coord) return [];
|
|
76
|
+
return [
|
|
77
|
+
{
|
|
78
|
+
id: d.order.id,
|
|
79
|
+
lat: coord.lat,
|
|
80
|
+
lng: coord.lng,
|
|
81
|
+
label: d.order.orderNumber,
|
|
82
|
+
status: deliveryMarkerStatus(d.order.status),
|
|
83
|
+
sublabel:
|
|
84
|
+
d.driver?.fullName ||
|
|
85
|
+
d.vehicle?.vehicleNumber ||
|
|
86
|
+
d.tracking?.currentLocation.address ||
|
|
87
|
+
undefined,
|
|
88
|
+
},
|
|
89
|
+
];
|
|
90
|
+
});
|
|
91
|
+
}, [filteredDeliveries]);
|
|
92
|
+
|
|
93
|
+
const locatedCount = markers.length;
|
|
94
|
+
|
|
95
|
+
const filterChips: { value: LiveFilter; label: string; count: number }[] = [
|
|
96
|
+
{ value: 'all', label: 'All', count: activeDeliveries.length },
|
|
97
|
+
{
|
|
98
|
+
value: 'in_transit',
|
|
99
|
+
label: 'In transit',
|
|
100
|
+
count: activeDeliveries.filter((d) => d.order?.status === 'in_transit').length,
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
value: 'out_for_delivery',
|
|
104
|
+
label: 'Out for delivery',
|
|
105
|
+
count: activeDeliveries.filter((d) => d.order?.status === 'out_for_delivery').length,
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
value: 'other',
|
|
109
|
+
label: 'Other',
|
|
110
|
+
count: activeDeliveries.filter(
|
|
111
|
+
(d) => d.order?.status !== 'in_transit' && d.order?.status !== 'out_for_delivery'
|
|
112
|
+
).length,
|
|
113
|
+
},
|
|
114
|
+
];
|
|
115
|
+
|
|
47
116
|
return (
|
|
48
117
|
<PageLayout
|
|
49
118
|
title="Live Tracking"
|
|
@@ -134,20 +203,37 @@ export default function LiveTrackingPage() {
|
|
|
134
203
|
{/* Map / List View */}
|
|
135
204
|
<div className="lg:col-span-2">
|
|
136
205
|
{viewMode === 'map' ? (
|
|
137
|
-
<Card className="
|
|
138
|
-
<CardContent className="p-0
|
|
139
|
-
<div className="
|
|
140
|
-
|
|
141
|
-
<
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
206
|
+
<Card className="overflow-hidden">
|
|
207
|
+
<CardContent className="p-0">
|
|
208
|
+
<div className="flex flex-wrap items-center gap-2 border-b border-border-seam p-3">
|
|
209
|
+
{filterChips.map((chip) => (
|
|
210
|
+
<button
|
|
211
|
+
key={chip.value}
|
|
212
|
+
type="button"
|
|
213
|
+
onClick={() => setFilter(chip.value)}
|
|
214
|
+
className={`inline-flex items-center gap-1.5 rounded-full border px-3 py-1 text-xs font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-action-primary-bg ${
|
|
215
|
+
filter === chip.value
|
|
216
|
+
? 'border-transparent bg-action-primary-bg text-action-primary-text'
|
|
217
|
+
: 'border-border-seam text-text-secondary hover:bg-bg-sunken/60'
|
|
218
|
+
}`}
|
|
219
|
+
>
|
|
220
|
+
{chip.label}
|
|
221
|
+
<span className="tabular-nums opacity-80">{chip.count}</span>
|
|
222
|
+
</button>
|
|
223
|
+
))}
|
|
224
|
+
<span className="ml-auto text-xs text-text-secondary">
|
|
225
|
+
{locatedCount} of {filteredDeliveries.length} located
|
|
226
|
+
</span>
|
|
150
227
|
</div>
|
|
228
|
+
<LogisticsMap
|
|
229
|
+
markers={markers}
|
|
230
|
+
height={560}
|
|
231
|
+
emptyMessage={
|
|
232
|
+
filteredDeliveries.length === 0
|
|
233
|
+
? 'No active deliveries to plot'
|
|
234
|
+
: 'Awaiting live GPS positions for these deliveries'
|
|
235
|
+
}
|
|
236
|
+
/>
|
|
151
237
|
</CardContent>
|
|
152
238
|
</Card>
|
|
153
239
|
) : (
|
|
@@ -158,7 +244,7 @@ export default function LiveTrackingPage() {
|
|
|
158
244
|
<CardContent className="p-0">
|
|
159
245
|
<ScrollArea className="h-[550px]">
|
|
160
246
|
<div className="divide-y">
|
|
161
|
-
{
|
|
247
|
+
{filteredDeliveries.map((delivery) => {
|
|
162
248
|
if (!delivery.order) {
|
|
163
249
|
return null;
|
|
164
250
|
}
|
|
@@ -214,7 +300,7 @@ export default function LiveTrackingPage() {
|
|
|
214
300
|
</div>
|
|
215
301
|
);
|
|
216
302
|
})}
|
|
217
|
-
{
|
|
303
|
+
{filteredDeliveries.length === 0 && (
|
|
218
304
|
<EmptyState
|
|
219
305
|
illustration="empty-sessions"
|
|
220
306
|
title="No active deliveries"
|
|
@@ -268,6 +354,28 @@ export default function LiveTrackingPage() {
|
|
|
268
354
|
<p className="text-sm">
|
|
269
355
|
{selected.tracking?.currentLocation.address || 'Updating...'}
|
|
270
356
|
</p>
|
|
357
|
+
{(() => {
|
|
358
|
+
const coord = firstRealLatLng(
|
|
359
|
+
selected.vehicle?.currentLocation,
|
|
360
|
+
selected.driver?.currentLocation,
|
|
361
|
+
selected.tracking?.currentLocation
|
|
362
|
+
);
|
|
363
|
+
return coord ? (
|
|
364
|
+
<LogisticsMap
|
|
365
|
+
className="mt-3 overflow-hidden rounded-lg border border-border-seam"
|
|
366
|
+
height={160}
|
|
367
|
+
markers={[
|
|
368
|
+
{
|
|
369
|
+
id: selected.order?.id ?? 'selected',
|
|
370
|
+
lat: coord.lat,
|
|
371
|
+
lng: coord.lng,
|
|
372
|
+
label: selected.order?.orderNumber ?? 'Delivery',
|
|
373
|
+
status: deliveryMarkerStatus(selected.order?.status),
|
|
374
|
+
},
|
|
375
|
+
]}
|
|
376
|
+
/>
|
|
377
|
+
) : null;
|
|
378
|
+
})()}
|
|
271
379
|
</div>
|
|
272
380
|
<div>
|
|
273
381
|
<p className="text-sm text-text-secondary">Estimated Arrival</p>
|
|
@@ -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,
|
|
@@ -219,44 +231,40 @@ export default function OrdersListPage() {
|
|
|
219
231
|
)}
|
|
220
232
|
</PagePurpose>
|
|
221
233
|
|
|
222
|
-
{/* Pipeline at a glance —
|
|
223
|
-
<
|
|
224
|
-
<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">
|
|
225
237
|
{t('movethewheels.orders.glance.title', 'Pipeline at a glance')}
|
|
226
238
|
</p>
|
|
227
|
-
<
|
|
228
|
-
<
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
<
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
<
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
</p>
|
|
257
|
-
</div>
|
|
258
|
-
</div>
|
|
259
|
-
</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>
|
|
260
268
|
|
|
261
269
|
{/* Filters */}
|
|
262
270
|
<Card>
|
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { useState } from 'react';
|
|
6
|
-
import { Package, Plus, RefreshCw, Download } from 'lucide-react';
|
|
6
|
+
import { Package, Plus, RefreshCw, Download, AlertTriangle, Thermometer } from 'lucide-react';
|
|
7
7
|
import { PageLayout } from '../components/PageLayout';
|
|
8
|
+
import { MetricCard, MetricGrid } from '../components/MetricCard';
|
|
8
9
|
import { DataTable, type Column } from '../components/DataTable';
|
|
9
10
|
import { Badge } from '../components/ui';
|
|
10
|
-
import { CTAOverflowMenu,
|
|
11
|
+
import { CTAOverflowMenu, PagePurpose } from '@burdenoff/fe-libs/ui';
|
|
11
12
|
import { useProducts, useProductStats } from '../hooks/useInventory';
|
|
12
13
|
import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
|
|
13
14
|
import { formatCurrency } from '../utils/formatters';
|
|
@@ -128,28 +129,31 @@ export default function ProductsPage() {
|
|
|
128
129
|
routed — keep them accurate so shipments are quoted and handled correctly.
|
|
129
130
|
</PagePurpose>
|
|
130
131
|
|
|
131
|
-
{/* Catalog at a glance —
|
|
132
|
-
<
|
|
133
|
-
<p className="text-sm font-medium text-text-secondary">Catalog at a glance</p>
|
|
134
|
-
<
|
|
135
|
-
<
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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>
|
|
153
157
|
|
|
154
158
|
<DataTable
|
|
155
159
|
columns={columns}
|
|
@@ -33,7 +33,9 @@ import {
|
|
|
33
33
|
Skeleton,
|
|
34
34
|
} from '../components/ui';
|
|
35
35
|
import { EmphasisPanel, IllustratedEmptyState, PagePurpose } from '@burdenoff/fe-libs/ui';
|
|
36
|
+
import { VolumeBar } from '../components/charts';
|
|
36
37
|
import { useReports, type CreateReportInput } from '../hooks/useReports';
|
|
38
|
+
import { useAnalytics } from '../hooks/useAnalytics';
|
|
37
39
|
import { formatDate } from '../utils/formatters';
|
|
38
40
|
import { downloadFile, downloadJson, recordToCsv, slugifyForFilename } from '../utils/download';
|
|
39
41
|
|
|
@@ -58,6 +60,8 @@ export default function ReportsPage() {
|
|
|
58
60
|
limit: REPORTS_PAGE_SIZE,
|
|
59
61
|
offset: page * REPORTS_PAGE_SIZE,
|
|
60
62
|
});
|
|
63
|
+
const { analytics } = useAnalytics();
|
|
64
|
+
const previewData = analytics.trends.dailyOrders.slice(-7);
|
|
61
65
|
const [reportType, setReportType] = useState('operations');
|
|
62
66
|
const [busyId, setBusyId] = useState<string | null>(null);
|
|
63
67
|
const [creating, setCreating] = useState(false);
|
|
@@ -276,6 +280,25 @@ export default function ReportsPage() {
|
|
|
276
280
|
</div>
|
|
277
281
|
|
|
278
282
|
<div className="space-y-6">
|
|
283
|
+
{/* Data preview — a real snapshot (recent order volume) of what a
|
|
284
|
+
generated report draws from. Honest empty state when no data. */}
|
|
285
|
+
<Card>
|
|
286
|
+
<CardHeader>
|
|
287
|
+
<CardTitle className="text-base">Data preview</CardTitle>
|
|
288
|
+
</CardHeader>
|
|
289
|
+
<CardContent>
|
|
290
|
+
<p className="mb-2 text-xs text-text-secondary">Order volume · last 7 days</p>
|
|
291
|
+
<VolumeBar
|
|
292
|
+
xKey="date"
|
|
293
|
+
height={160}
|
|
294
|
+
showLegend={false}
|
|
295
|
+
data={previewData}
|
|
296
|
+
series={[{ dataKey: 'orders', name: 'Orders' }]}
|
|
297
|
+
emptyMessage="No order data to preview yet"
|
|
298
|
+
/>
|
|
299
|
+
</CardContent>
|
|
300
|
+
</Card>
|
|
301
|
+
|
|
279
302
|
{/* Scheduled Reports — the page's single emphasis zone */}
|
|
280
303
|
<EmphasisPanel className="space-y-3">
|
|
281
304
|
<h2 className="flex items-center gap-2 text-base font-semibold text-text-primary">
|
|
@@ -37,6 +37,8 @@ import {
|
|
|
37
37
|
SelectValue,
|
|
38
38
|
} from '../components/ui';
|
|
39
39
|
import { EmphasisPanel, NextSteps, PagePurpose } from '@burdenoff/fe-libs/ui';
|
|
40
|
+
import { RouteMap, type RouteStop as MapRouteStop } from '../components/maps';
|
|
41
|
+
import { firstRealLatLng } from '../utils/mapMarkers';
|
|
40
42
|
import { useRoute, useRoutes } from '../hooks/useRoutes';
|
|
41
43
|
import { useFleet } from '../hooks/useFleet';
|
|
42
44
|
import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
|
|
@@ -165,8 +167,9 @@ function NewRouteForm() {
|
|
|
165
167
|
}
|
|
166
168
|
>
|
|
167
169
|
<PagePurpose icon={<Navigation size={18} />}>
|
|
168
|
-
Plan a new delivery route — name it, list the stops to visit, and optionally assign a
|
|
169
|
-
and driver. Once created you can optimize it to sequence the stops into the shortest
|
|
170
|
+
Plan a new delivery route — name it, list the stops to visit, and optionally assign a
|
|
171
|
+
vehicle and driver. Once created you can optimize it to sequence the stops into the shortest
|
|
172
|
+
run.
|
|
170
173
|
</PagePurpose>
|
|
171
174
|
|
|
172
175
|
<form onSubmit={handleSubmit} className="max-w-3xl space-y-6">
|
|
@@ -363,6 +366,22 @@ export default function RouteDetailsPage() {
|
|
|
363
366
|
if (isLoading) return <LoadingState message="Loading route details..." />;
|
|
364
367
|
if (error || !route) return <ErrorState title="Route not found" onRetry={refetch} />;
|
|
365
368
|
|
|
369
|
+
// Parse coordinates off each waypoint's address; stops without a real coord are
|
|
370
|
+
// dropped from the map (but still listed below), so the plotted line is honest.
|
|
371
|
+
const mapStops: MapRouteStop[] = route.stops
|
|
372
|
+
.map((stop) => {
|
|
373
|
+
const coord = firstRealLatLng(stop.address);
|
|
374
|
+
return coord
|
|
375
|
+
? {
|
|
376
|
+
lat: coord.lat,
|
|
377
|
+
lng: coord.lng,
|
|
378
|
+
sequence: stop.sequence,
|
|
379
|
+
label: formatAddress(stop.address) || `Order #${stop.orderId || stop.sequence}`,
|
|
380
|
+
}
|
|
381
|
+
: null;
|
|
382
|
+
})
|
|
383
|
+
.filter((stop): stop is MapRouteStop => stop !== null);
|
|
384
|
+
|
|
366
385
|
return (
|
|
367
386
|
<PageLayout
|
|
368
387
|
title={route.name}
|
|
@@ -447,12 +466,32 @@ export default function RouteDetailsPage() {
|
|
|
447
466
|
<CardTitle>Route Map</CardTitle>
|
|
448
467
|
</CardHeader>
|
|
449
468
|
<CardContent>
|
|
450
|
-
|
|
451
|
-
<
|
|
452
|
-
|
|
453
|
-
|
|
469
|
+
{mapStops.length > 0 ? (
|
|
470
|
+
<RouteMap
|
|
471
|
+
className="overflow-hidden rounded-lg border border-border-seam"
|
|
472
|
+
height={360}
|
|
473
|
+
stops={mapStops}
|
|
474
|
+
/>
|
|
475
|
+
) : (
|
|
476
|
+
<div className="rounded-lg border border-border-seam bg-bg-sunken/40 p-4">
|
|
477
|
+
<p className="mb-3 text-sm text-text-secondary">
|
|
478
|
+
These stops don't have map coordinates yet — showing the planned sequence
|
|
479
|
+
instead.
|
|
480
|
+
</p>
|
|
481
|
+
<ol className="space-y-2">
|
|
482
|
+
{route.stops.map((stop) => (
|
|
483
|
+
<li key={stop.id} className="flex items-start gap-3">
|
|
484
|
+
<span className="flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-action-primary-bg/10 text-xs font-semibold text-primary">
|
|
485
|
+
{stop.sequence}
|
|
486
|
+
</span>
|
|
487
|
+
<span className="text-sm">
|
|
488
|
+
{formatAddress(stop.address) || `Order #${stop.orderId || stop.sequence}`}
|
|
489
|
+
</span>
|
|
490
|
+
</li>
|
|
491
|
+
))}
|
|
492
|
+
</ol>
|
|
454
493
|
</div>
|
|
455
|
-
|
|
494
|
+
)}
|
|
456
495
|
</CardContent>
|
|
457
496
|
</Card>
|
|
458
497
|
|
|
@@ -222,7 +222,7 @@ export default function SettingsPage() {
|
|
|
222
222
|
<EmphasisPanel>
|
|
223
223
|
<h2 className="text-base font-semibold text-text-primary">Company Information</h2>
|
|
224
224
|
<p className="text-sm text-text-secondary mb-4">Basic company details</p>
|
|
225
|
-
<div className="grid grid-cols-2 gap-4">
|
|
225
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
|
226
226
|
<div>
|
|
227
227
|
<Label>Company Name</Label>
|
|
228
228
|
<Input
|
|
@@ -270,7 +270,7 @@ export default function SettingsPage() {
|
|
|
270
270
|
</CardTitle>
|
|
271
271
|
</CardHeader>
|
|
272
272
|
<CardContent className="space-y-4">
|
|
273
|
-
<div className="grid grid-cols-3 gap-4">
|
|
273
|
+
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
|
|
274
274
|
<div>
|
|
275
275
|
<Label>Timezone</Label>
|
|
276
276
|
<Select
|