@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
|
@@ -1,22 +1,49 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Finance Page
|
|
3
|
+
*
|
|
4
|
+
* Shows only REAL figures returned by Query.financeMetrics (revenue, delivered
|
|
5
|
+
* revenue, fuel cost, open invoices) plus the real revenue trend series — no
|
|
6
|
+
* simulated paid/pending splits and no fabricated cost categories. Invoicing and
|
|
7
|
+
* settlement are handled by the platform Billing module, which this page
|
|
8
|
+
* deep-links to rather than re-implementing an Invoice ledger here.
|
|
3
9
|
*/
|
|
4
10
|
|
|
5
|
-
import {
|
|
11
|
+
import { useMemo } from 'react';
|
|
12
|
+
import { DollarSign, TrendingUp, CreditCard, FileText, Fuel, ArrowRight } from 'lucide-react';
|
|
6
13
|
import { PageLayout } from '../components/PageLayout';
|
|
7
14
|
import { MetricCard, MetricGrid } from '../components/MetricCard';
|
|
8
15
|
import { Card, CardContent, CardHeader, CardTitle, Button } from '../components/ui';
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
16
|
+
import { TrendChart, VolumeBar, chartTokens } from '../components/charts';
|
|
17
|
+
import { EmphasisPanel, PagePurpose } from '@burdenoff/fe-libs/ui';
|
|
18
|
+
import { useAnalytics, useFinanceMetrics } from '../hooks/useAnalytics';
|
|
19
|
+
import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
|
|
12
20
|
import { formatCurrency } from '../utils/formatters';
|
|
13
21
|
|
|
14
22
|
export default function FinancePage() {
|
|
15
23
|
const { analytics } = useAnalytics();
|
|
16
|
-
const {
|
|
24
|
+
const { metrics } = useFinanceMetrics();
|
|
25
|
+
const { navigate } = useMoveTheWheels();
|
|
17
26
|
|
|
18
|
-
|
|
19
|
-
const
|
|
27
|
+
// Deep-link to the host Billing module (mounted at /billing in the app shell).
|
|
28
|
+
const openBilling = () => navigate('/billing');
|
|
29
|
+
|
|
30
|
+
const revenueSeries = useMemo(
|
|
31
|
+
() => analytics.trends.revenue.map((r) => ({ month: r.month, revenue: r.revenue })),
|
|
32
|
+
[analytics.trends.revenue]
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
// Real dollar figures only — total revenue, money already collected on delivered
|
|
36
|
+
// orders, and fuel spend. No invented category breakdown.
|
|
37
|
+
const economics = useMemo(
|
|
38
|
+
() => [
|
|
39
|
+
{ label: 'Total revenue', amount: metrics.totalRevenue },
|
|
40
|
+
{ label: 'Collected', amount: metrics.deliveredRevenue },
|
|
41
|
+
{ label: 'Fuel cost', amount: metrics.fuelCost },
|
|
42
|
+
],
|
|
43
|
+
[metrics.totalRevenue, metrics.deliveredRevenue, metrics.fuelCost]
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const hasEconomics = economics.some((e) => e.amount > 0);
|
|
20
47
|
|
|
21
48
|
return (
|
|
22
49
|
<PageLayout
|
|
@@ -24,20 +51,15 @@ export default function FinancePage() {
|
|
|
24
51
|
subtitle="Financial overview and reports"
|
|
25
52
|
icon={<DollarSign size={24} />}
|
|
26
53
|
headerActions={
|
|
27
|
-
<
|
|
28
|
-
<
|
|
29
|
-
|
|
30
|
-
</Button>
|
|
31
|
-
<Button variant="outline">
|
|
32
|
-
<Download size={16} /> Export
|
|
33
|
-
</Button>
|
|
34
|
-
</div>
|
|
54
|
+
<Button variant="outline" onClick={openBilling}>
|
|
55
|
+
<FileText size={16} /> Invoices & Billing
|
|
56
|
+
</Button>
|
|
35
57
|
}
|
|
36
58
|
>
|
|
37
59
|
<PagePurpose icon={<DollarSign size={18} />}>
|
|
38
|
-
Follow the money behind your deliveries. See revenue
|
|
39
|
-
|
|
40
|
-
|
|
60
|
+
Follow the money behind your deliveries. See total revenue, how much has already been
|
|
61
|
+
collected on delivered orders, fuel cost and how many invoices are still open — then head to
|
|
62
|
+
Billing to raise and settle invoices.
|
|
41
63
|
</PagePurpose>
|
|
42
64
|
|
|
43
65
|
<MetricGrid columns={4}>
|
|
@@ -45,99 +67,98 @@ export default function FinancePage() {
|
|
|
45
67
|
treatment="glass"
|
|
46
68
|
glow
|
|
47
69
|
label="Total Revenue"
|
|
48
|
-
value={formatCurrency(
|
|
70
|
+
value={formatCurrency(metrics.totalRevenue)}
|
|
49
71
|
icon={<DollarSign size={20} />}
|
|
50
|
-
|
|
51
|
-
changeType="positive"
|
|
52
|
-
trend="up"
|
|
72
|
+
accent="success"
|
|
53
73
|
/>
|
|
54
74
|
<MetricCard
|
|
55
|
-
label="
|
|
56
|
-
value={formatCurrency(
|
|
75
|
+
label="Collected (Delivered)"
|
|
76
|
+
value={formatCurrency(metrics.deliveredRevenue)}
|
|
57
77
|
icon={<TrendingUp size={20} />}
|
|
58
|
-
|
|
78
|
+
accent="brand"
|
|
59
79
|
/>
|
|
60
80
|
<MetricCard
|
|
61
|
-
label="
|
|
62
|
-
value={formatCurrency(
|
|
63
|
-
icon={<
|
|
64
|
-
|
|
81
|
+
label="Fuel Cost"
|
|
82
|
+
value={formatCurrency(metrics.fuelCost)}
|
|
83
|
+
icon={<Fuel size={20} />}
|
|
84
|
+
accent="warning"
|
|
65
85
|
/>
|
|
66
86
|
<MetricCard
|
|
67
|
-
label="
|
|
68
|
-
value={
|
|
69
|
-
icon={<
|
|
87
|
+
label="Open Invoices"
|
|
88
|
+
value={String(metrics.openInvoices)}
|
|
89
|
+
icon={<CreditCard size={20} />}
|
|
90
|
+
accent="blue"
|
|
70
91
|
/>
|
|
71
92
|
</MetricGrid>
|
|
72
93
|
|
|
73
|
-
<div className="grid grid-cols-1 lg:grid-cols-
|
|
74
|
-
<Card>
|
|
94
|
+
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
95
|
+
<Card className="lg:col-span-2">
|
|
75
96
|
<CardHeader>
|
|
76
|
-
<CardTitle>Revenue
|
|
97
|
+
<CardTitle>Revenue trend</CardTitle>
|
|
77
98
|
</CardHeader>
|
|
78
99
|
<CardContent>
|
|
79
|
-
<
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
</div>
|
|
100
|
+
<TrendChart
|
|
101
|
+
xKey="month"
|
|
102
|
+
variant="area"
|
|
103
|
+
height={280}
|
|
104
|
+
showLegend={false}
|
|
105
|
+
data={revenueSeries}
|
|
106
|
+
series={[{ dataKey: 'revenue', name: 'Revenue', color: chartTokens.brandBlue }]}
|
|
107
|
+
valueFormatter={(v) => formatCurrency(Number(v))}
|
|
108
|
+
emptyMessage="No revenue recorded yet. Revenue appears here as orders are delivered."
|
|
109
|
+
/>
|
|
90
110
|
</CardContent>
|
|
91
111
|
</Card>
|
|
92
112
|
|
|
93
|
-
{/*
|
|
113
|
+
{/* Invoices & Settlement — reuse of the Billing module */}
|
|
94
114
|
<EmphasisPanel className="space-y-4">
|
|
95
|
-
<h2 className="text-base font-semibold text-text-primary">
|
|
96
|
-
<
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
</div>
|
|
115
|
+
<h2 className="text-base font-semibold text-text-primary">Invoices & Settlement</h2>
|
|
116
|
+
<p className="text-sm text-text-secondary">
|
|
117
|
+
Invoicing, payments and settlement run through the platform Billing module — the single
|
|
118
|
+
source of truth for what's been billed and paid. MoveTheWheels doesn't keep
|
|
119
|
+
its own invoice ledger.
|
|
120
|
+
</p>
|
|
121
|
+
<div className="flex items-center justify-between p-4 bg-status-warning-bg/10 rounded-lg">
|
|
122
|
+
<div className="flex items-center gap-3">
|
|
123
|
+
<div className="w-10 h-10 rounded-full bg-status-warning-bg/20 flex items-center justify-center">
|
|
124
|
+
<CreditCard size={20} className="text-status-warning-text" />
|
|
106
125
|
</div>
|
|
107
|
-
<
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
<div className="flex items-center gap-3">
|
|
113
|
-
<div className="w-10 h-10 rounded-full bg-status-warning-bg/20 flex items-center justify-center">
|
|
114
|
-
<CreditCard size={20} className="text-status-warning-text" />
|
|
115
|
-
</div>
|
|
116
|
-
<div>
|
|
117
|
-
<p className="font-medium">Pending</p>
|
|
118
|
-
<p className="text-sm text-text-secondary">
|
|
119
|
-
{stats.byStatus.pending + stats.byStatus.in_transit} orders
|
|
120
|
-
</p>
|
|
121
|
-
</div>
|
|
126
|
+
<div>
|
|
127
|
+
<p className="font-medium">Open invoices</p>
|
|
128
|
+
<p className="text-sm text-text-secondary">
|
|
129
|
+
{metrics.openInvoices} awaiting settlement
|
|
130
|
+
</p>
|
|
122
131
|
</div>
|
|
123
|
-
<span className="text-xl font-bold text-status-warning-text">
|
|
124
|
-
{formatCurrency(pendingPayments)}
|
|
125
|
-
</span>
|
|
126
132
|
</div>
|
|
133
|
+
<span className="text-xl font-bold text-status-warning-text">
|
|
134
|
+
{metrics.openInvoices}
|
|
135
|
+
</span>
|
|
127
136
|
</div>
|
|
137
|
+
<Button className="w-full" onClick={openBilling}>
|
|
138
|
+
Open Billing <ArrowRight size={16} />
|
|
139
|
+
</Button>
|
|
128
140
|
</EmphasisPanel>
|
|
129
141
|
</div>
|
|
130
142
|
|
|
131
143
|
<Card>
|
|
132
144
|
<CardHeader>
|
|
133
|
-
<CardTitle>
|
|
145
|
+
<CardTitle>Revenue vs cost</CardTitle>
|
|
134
146
|
</CardHeader>
|
|
135
147
|
<CardContent>
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
148
|
+
{hasEconomics ? (
|
|
149
|
+
<VolumeBar
|
|
150
|
+
xKey="label"
|
|
151
|
+
height={260}
|
|
152
|
+
showLegend={false}
|
|
153
|
+
data={economics}
|
|
154
|
+
series={[{ dataKey: 'amount', name: 'Amount' }]}
|
|
155
|
+
valueFormatter={(v) => formatCurrency(Number(v))}
|
|
156
|
+
/>
|
|
157
|
+
) : (
|
|
158
|
+
<p className="py-8 text-center text-sm text-text-secondary">
|
|
159
|
+
Revenue and cost figures appear here as orders are delivered.
|
|
160
|
+
</p>
|
|
161
|
+
)}
|
|
141
162
|
</CardContent>
|
|
142
163
|
</Card>
|
|
143
164
|
</PageLayout>
|
|
@@ -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>
|