@burdenoff/microfe-movethewheels 2026.703.3 → 2026.703.4
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-3YOnHO9Y.js} +3 -3
- package/dist/{AnalyticsPage-Dz6g2MvK.js → AnalyticsPage-CMpI1070.js} +11 -11
- package/dist/CreateOrderPage-DRAgGo9i.js +490 -0
- package/dist/CustomerDetailsPage-BCeMmOFe.js +615 -0
- package/dist/{CustomersPage-BKxMS1AV.js → CustomersPage-CaNBMXmM.js} +64 -53
- package/dist/{DashboardPage-BVjQi0EB.js → DashboardPage-DIKlXde3.js} +16 -16
- package/dist/DataTable-XvUcTjwi.js +340 -0
- package/dist/DocumentsCard-U2lSe-cn.js +169 -0
- package/dist/{DriverDetailsPage-CN3WQ0B9.js → DriverDetailsPage-BQrYd_Lp.js} +178 -195
- package/dist/{DriversPage-CH9rjQC-.js → DriversPage-PYmdFiby.js} +65 -54
- package/dist/FinancePage-Cwh9Bw4M.js +136 -0
- package/dist/{FleetPage-DqkFi3s3.js → FleetPage-3ZHsB-TN.js} +23 -22
- package/dist/{FormError-BlOjnXOq.js → FormError-cuLV2jVD.js} +1 -1
- package/dist/{ImportExportPage-DKJRCezl.js → ImportExportPage-C6f7DqhM.js} +50 -70
- package/dist/{InventoryPage-e0ze63iX.js → InventoryPage-DpiqVu1v.js} +8 -8
- package/dist/{LiveTrackingPage-DCX-K-LR.js → LiveTrackingPage-FAzzlVnX.js} +9 -9
- package/dist/MarketplacePage-Bh_2ns4v.js +426 -0
- package/dist/Notice-CSoDYKw9.js +46 -0
- package/dist/OrderDetailsPage-zCj0lW0j.js +584 -0
- package/dist/{OrdersListPage-Cn90P1Xx.js → OrdersListPage-CAMsuUck.js} +166 -89
- package/dist/{ProductDetailsPage-C9uOiYLp.js → ProductDetailsPage-wsxAQkA0.js} +12 -12
- package/dist/{ProductsPage-QJfHHpqB.js → ProductsPage-DC5qDLfz.js} +60 -49
- package/dist/ReportsPage-BwUJ37eO.js +356 -0
- package/dist/RouteDetailsPage-DyMJE9AS.js +583 -0
- package/dist/{RoutesPage-DMfoicM1.js → RoutesPage-Dtc669yc.js} +57 -46
- package/dist/{SettingsPage-Bn-v0mbQ.js → SettingsPage-CpH9xhQg.js} +5 -27
- package/dist/{TrackingPage-W-jtdM3Y.js → TrackingPage-DaNEFG_h.js} +10 -10
- package/dist/{VehicleDetailsPage-Bceo5oz3.js → VehicleDetailsPage-DldYNsYV.js} +125 -142
- package/dist/{VehiclesPage-ApKThYeG.js → VehiclesPage-BFw13iMt.js} +61 -50
- package/dist/{WarehouseDetailsPage-L_6sWigM.js → WarehouseDetailsPage-CMG7or6G.js} +17 -17
- package/dist/{WarehousesPage-DCbXXXWE.js → WarehousesPage-Wkytnt86.js} +58 -47
- package/dist/arrow-right-Q3XQpTPR.js +11 -0
- package/dist/arrow-up-xNF1zJUG.js +17 -0
- package/dist/chevron-left-B2_r0Elx.js +8 -0
- package/dist/{wrench-CANXWxF-.js → fuel-B6_lnKDs.js} +2 -5
- package/dist/index.js +163 -174
- package/dist/upload-CHZofOqb.js +21 -0
- package/dist/{useAnalytics-DS8n0xgg.js → useAnalytics-D7HW2XRb.js} +52 -2
- package/dist/{useCustomers-BmTNGxiI.js → useCustomers-BPW4__PR.js} +29 -26
- package/dist/{useFleet-DKqXZ16m.js → useFleet-ChBkxAd6.js} +81 -69
- package/dist/{useInventory-DkyiVY_A.js → useInventory-CAAVMRp6.js} +83 -38
- package/dist/{useOrders-Cfk1GLiq.js → useOrders-B465S2nt.js} +58 -47
- package/dist/{useReports-CTqZr-AB.js → useReports-jQ3GpNOm.js} +40 -34
- package/dist/{useRoutes-BQWO607N.js → useRoutes-hS3d5-xr.js} +49 -41
- package/dist/{useTracking-B-va1kO3.js → useTracking-RjGz9PZG.js} +2 -2
- package/dist/user-check-BEKRCi7v.js +20 -0
- package/dist/wrench-q_rgJfaP.js +8 -0
- package/dist/x-tMeDMcCw.js +11 -0
- package/package.json +1 -1
- package/src/movethewheels/components/DataTable.tsx +252 -84
- package/src/movethewheels/components/DocumentsCard.tsx +102 -0
- package/src/movethewheels/components/Notice.tsx +61 -0
- package/src/movethewheels/components/index.ts +7 -0
- package/src/movethewheels/hooks/index.ts +9 -0
- package/src/movethewheels/hooks/useAnalytics.ts +95 -0
- 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 +2 -2
- package/src/movethewheels/pages/CreateOrderPage.tsx +44 -16
- package/src/movethewheels/pages/CustomerDetailsPage.tsx +239 -33
- package/src/movethewheels/pages/CustomersPage.tsx +27 -11
- package/src/movethewheels/pages/DriverDetailsPage.tsx +16 -30
- package/src/movethewheels/pages/DriversPage.tsx +26 -9
- package/src/movethewheels/pages/FinancePage.tsx +67 -84
- package/src/movethewheels/pages/MarketplacePage.tsx +265 -50
- package/src/movethewheels/pages/OrderDetailsPage.tsx +214 -10
- package/src/movethewheels/pages/OrdersListPage.tsx +76 -13
- package/src/movethewheels/pages/ProductsPage.tsx +27 -7
- package/src/movethewheels/pages/ReportsPage.tsx +65 -4
- package/src/movethewheels/pages/RouteDetailsPage.tsx +74 -13
- package/src/movethewheels/pages/RoutesPage.tsx +22 -6
- package/src/movethewheels/pages/SettingsPage.tsx +3 -19
- package/src/movethewheels/pages/VehicleDetailsPage.tsx +3 -33
- package/src/movethewheels/pages/VehiclesPage.tsx +26 -5
- package/src/movethewheels/pages/WarehousesPage.tsx +22 -17
- package/src/movethewheels/types/index.ts +8 -0
- package/src/movethewheels/utils/download.ts +62 -0
- package/dist/CreateOrderPage-C5iwnjp4.js +0 -479
- package/dist/CustomerDetailsPage-B6nwFyLO.js +0 -406
- package/dist/DataTable-DmciIKKM.js +0 -249
- package/dist/FinancePage-DEDykSOP.js +0 -158
- package/dist/MarketplacePage-CIQ97_zp.js +0 -247
- package/dist/OrderDetailsPage-DYiZ-9UK.js +0 -429
- package/dist/ReportsPage-Zp53q3_8.js +0 -284
- package/dist/RouteDetailsPage-DUStXSf9.js +0 -531
- /package/dist/{MetricCard-BY0QrMD4.js → MetricCard-CVBVpu7o.js} +0 -0
- /package/dist/{StatusBadge-BZurYHMy.js → StatusBadge-C87mDpDP.js} +0 -0
- /package/dist/{box-D9Pal4HM.js → box-Eu05PXDh.js} +0 -0
- /package/dist/{chart-column-BFIJysDR.js → chart-column-ouzgxIv_.js} +0 -0
- /package/dist/{chevron-right-EMIIyyJ1.js → chevron-right-eV4LjaSi.js} +0 -0
- /package/dist/{circle-alert-iY24lSlp.js → circle-alert-Bzh2DBSF.js} +0 -0
- /package/dist/{circle-check-big-w0TrLKMF.js → circle-check-big-CGMSpQmD.js} +0 -0
- /package/dist/{clock-DJZtdl8u.js → clock-Bl3ptexL.js} +0 -0
- /package/dist/{dollar-sign-CgKQRz_e.js → dollar-sign-CAVU95SO.js} +0 -0
- /package/dist/{download-DRXxoiQR.js → download-C5NbvVDc.js} +0 -0
- /package/dist/{file-text-DWWY6_f5.js → file-text-D-1uvLZG.js} +0 -0
- /package/dist/{filter-DloLC2eE.js → filter-DUfUg6BO.js} +0 -0
- /package/dist/{formatters-CF2S2l1m.js → formatters-BQr-aR5W.js} +0 -0
- /package/dist/{map-CkJaULb2.js → map-C-M-FQLR.js} +0 -0
- /package/dist/{map-pin-BGiCvyCs.js → map-pin-Dznl5l40.js} +0 -0
- /package/dist/{navigation-D36prnAn.js → navigation-BJSJl-VG.js} +0 -0
- /package/dist/{package-BChUzpJS.js → package-CcRttV2N.js} +0 -0
- /package/dist/{phone-Ac9s3mo_.js → phone-DVSZHt_j.js} +0 -0
- /package/dist/{plus-Z76q-FZm.js → plus-C_XZDwBz.js} +0 -0
- /package/dist/{refresh-cw-DKt6f5T6.js → refresh-cw-CfgN67l3.js} +0 -0
- /package/dist/{save-CojywNhG.js → save-DJE8Fpdv.js} +0 -0
- /package/dist/{search-CDGefqb3.js → search-D_VyGcZT.js} +0 -0
- /package/dist/{square-pen-CopE9GmB.js → square-pen-D-6JYrVQ.js} +0 -0
- /package/dist/{star-nHlLjDKR.js → star-BpRlUZuU.js} +0 -0
- /package/dist/{store-BR9Voi5E.js → store-B-8HMuyZ.js} +0 -0
- /package/dist/{trash-2-BKhar6Hs.js → trash-2-8vmv-XV_.js} +0 -0
- /package/dist/{trending-up-Dohgd4t_.js → trending-up-Dz6PqPRj.js} +0 -0
- /package/dist/{triangle-alert-i-XQL9Rd.js → triangle-alert-D7Y3J_4z.js} +0 -0
- /package/dist/{truck-C0v_hbLR.js → truck-D-y2IDzv.js} +0 -0
- /package/dist/{useAssistant-D4v3oxD3.js → useAssistant-DVW4QgQb.js} +0 -0
- /package/dist/{useImportExport-DHr-SWSw.js → useImportExport-DrPDHEDK.js} +0 -0
- /package/dist/{useSettings-CkDH_-gM.js → useSettings-BB8X2RLh.js} +0 -0
- /package/dist/{user-jyFU3lXk.js → user-DzfTn4dE.js} +0 -0
- /package/dist/{users-DZ-ilS6B.js → users-CCrq-w0C.js} +0 -0
- /package/dist/{warehouse-6m5kXMLu.js → warehouse-CaqfmMKh.js} +0 -0
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
* Customers Page
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import { useState } from 'react';
|
|
5
6
|
import { Users, Plus, RefreshCw, Download, Star } from 'lucide-react';
|
|
6
7
|
import { PageLayout } from '../components/PageLayout';
|
|
7
8
|
import { DataTable, type Column } from '../components/DataTable';
|
|
8
9
|
import { TypeBadge } from '../components/StatusBadge';
|
|
9
10
|
import { Badge, Avatar, AvatarFallback, Button } from '../components/ui';
|
|
10
11
|
import { CTAOverflowMenu, EmphasisPanel, PagePurpose } from '@burdenoff/fe-libs/ui';
|
|
11
|
-
import { useCustomers } from '../hooks/useCustomers';
|
|
12
|
+
import { useCustomers, useCustomerStats } from '../hooks/useCustomers';
|
|
12
13
|
import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
|
|
13
14
|
import { formatDate } from '../utils/formatters';
|
|
14
15
|
import { joinPath } from '../utils/navigation';
|
|
@@ -16,7 +17,14 @@ import type { Customer } from '../types';
|
|
|
16
17
|
|
|
17
18
|
export default function CustomersPage() {
|
|
18
19
|
const { basePath, navigate } = useMoveTheWheels();
|
|
19
|
-
const
|
|
20
|
+
const [page, setPage] = useState(0);
|
|
21
|
+
const [pageSize, setPageSize] = useState(25);
|
|
22
|
+
const { customers, hasMore, isLoading, refetch } = useCustomers({
|
|
23
|
+
limit: pageSize,
|
|
24
|
+
offset: page * pageSize,
|
|
25
|
+
});
|
|
26
|
+
// Bounded aggregate for the tiles + subtitle — never the truncated page.
|
|
27
|
+
const { stats } = useCustomerStats();
|
|
20
28
|
|
|
21
29
|
const goTo = (path: string) => navigate?.(joinPath(basePath, path));
|
|
22
30
|
|
|
@@ -85,7 +93,7 @@ export default function CustomersPage() {
|
|
|
85
93
|
return (
|
|
86
94
|
<PageLayout
|
|
87
95
|
title="Customers"
|
|
88
|
-
subtitle={`${
|
|
96
|
+
subtitle={`${stats.total} customers`}
|
|
89
97
|
icon={<Users size={24} />}
|
|
90
98
|
headerActions={
|
|
91
99
|
<CTAOverflowMenu
|
|
@@ -96,7 +104,11 @@ export default function CustomersPage() {
|
|
|
96
104
|
}}
|
|
97
105
|
actions={[
|
|
98
106
|
{ label: 'Refresh', icon: <RefreshCw size={16} />, onSelect: () => refetch() },
|
|
99
|
-
{
|
|
107
|
+
{
|
|
108
|
+
label: 'Export',
|
|
109
|
+
icon: <Download size={16} />,
|
|
110
|
+
onSelect: () => goTo('import-export'),
|
|
111
|
+
},
|
|
100
112
|
]}
|
|
101
113
|
/>
|
|
102
114
|
}
|
|
@@ -112,19 +124,15 @@ export default function CustomersPage() {
|
|
|
112
124
|
<p className="text-sm font-medium text-text-secondary">Customer base at a glance</p>
|
|
113
125
|
<div className="mt-3 grid grid-cols-3 gap-4">
|
|
114
126
|
<div>
|
|
115
|
-
<p className="text-2xl font-bold text-text-primary tabular-nums">{
|
|
127
|
+
<p className="text-2xl font-bold text-text-primary tabular-nums">{stats.total}</p>
|
|
116
128
|
<p className="text-xs text-text-secondary">Total customers</p>
|
|
117
129
|
</div>
|
|
118
130
|
<div>
|
|
119
|
-
<p className="text-2xl font-bold text-text-primary tabular-nums">
|
|
120
|
-
{customers.filter((c) => c.isActive).length}
|
|
121
|
-
</p>
|
|
131
|
+
<p className="text-2xl font-bold text-text-primary tabular-nums">{stats.active}</p>
|
|
122
132
|
<p className="text-xs text-text-secondary">Active</p>
|
|
123
133
|
</div>
|
|
124
134
|
<div>
|
|
125
|
-
<p className="text-2xl font-bold text-text-primary tabular-nums">
|
|
126
|
-
{customers.filter((c) => c.type === 'b2b').length}
|
|
127
|
-
</p>
|
|
135
|
+
<p className="text-2xl font-bold text-text-primary tabular-nums">{stats.b2b}</p>
|
|
128
136
|
<p className="text-xs text-text-secondary">Business accounts</p>
|
|
129
137
|
</div>
|
|
130
138
|
</div>
|
|
@@ -139,6 +147,14 @@ export default function CustomersPage() {
|
|
|
139
147
|
searchFields={['name', 'email', 'company'] as (keyof Customer)[]}
|
|
140
148
|
onRowClick={(row) => goTo(`customers/${row.id}`)}
|
|
141
149
|
getRowId={(row) => row.id}
|
|
150
|
+
pageIndex={page}
|
|
151
|
+
pageSize={pageSize}
|
|
152
|
+
onPageChange={setPage}
|
|
153
|
+
onPageSizeChange={(size) => {
|
|
154
|
+
setPageSize(size);
|
|
155
|
+
setPage(0);
|
|
156
|
+
}}
|
|
157
|
+
hasNextPage={hasMore}
|
|
142
158
|
emptyIllustration="empty-people"
|
|
143
159
|
emptyTitle="No customers yet"
|
|
144
160
|
emptyDescription="Add your first customer to start booking deliveries for them."
|
|
@@ -34,6 +34,7 @@ import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
|
|
|
34
34
|
import { formatCurrency, formatDate } from '../utils/formatters';
|
|
35
35
|
import { joinPath } from '../utils/navigation';
|
|
36
36
|
import { FormError } from '../components/FormError';
|
|
37
|
+
import { DocumentsCard } from '../components/DocumentsCard';
|
|
37
38
|
|
|
38
39
|
function NewDriverForm() {
|
|
39
40
|
const { basePath, navigate } = useMoveTheWheels();
|
|
@@ -310,46 +311,31 @@ export default function DriverDetailsPage() {
|
|
|
310
311
|
</CardContent>
|
|
311
312
|
</Card>
|
|
312
313
|
|
|
313
|
-
{/*
|
|
314
|
+
{/* License — real driver credential fields */}
|
|
314
315
|
<Card>
|
|
315
316
|
<CardHeader>
|
|
316
317
|
<CardTitle className="flex items-center gap-2">
|
|
317
|
-
<FileText size={18} />
|
|
318
|
+
<FileText size={18} /> License
|
|
318
319
|
</CardTitle>
|
|
319
320
|
</CardHeader>
|
|
320
321
|
<CardContent>
|
|
321
|
-
<div className="
|
|
322
|
-
<div
|
|
323
|
-
<
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
</div>
|
|
332
|
-
<Badge variant="default">Valid</Badge>
|
|
322
|
+
<div className="flex items-center justify-between p-3 border rounded-lg">
|
|
323
|
+
<div>
|
|
324
|
+
<p className="font-medium">Driver's License</p>
|
|
325
|
+
<p className="text-sm text-text-secondary">
|
|
326
|
+
{driver.licenseNumber || '—'}
|
|
327
|
+
{driver.licenseType ? ` - ${driver.licenseType}` : ''}
|
|
328
|
+
</p>
|
|
329
|
+
<p className="text-sm text-text-secondary">
|
|
330
|
+
Expires: {formatDate(driver.licenseExpiry)}
|
|
331
|
+
</p>
|
|
333
332
|
</div>
|
|
334
|
-
{driver.documents.map((doc) => (
|
|
335
|
-
<div
|
|
336
|
-
key={doc.id}
|
|
337
|
-
className="flex items-center justify-between p-3 border rounded-lg"
|
|
338
|
-
>
|
|
339
|
-
<div>
|
|
340
|
-
<p className="font-medium capitalize">{doc.type.replace('_', ' ')}</p>
|
|
341
|
-
<p className="text-sm text-text-secondary">
|
|
342
|
-
Expires: {formatDate(doc.expiryDate)}
|
|
343
|
-
</p>
|
|
344
|
-
</div>
|
|
345
|
-
<Badge variant={doc.verified ? 'default' : 'secondary'}>
|
|
346
|
-
{doc.verified ? 'Verified' : 'Pending'}
|
|
347
|
-
</Badge>
|
|
348
|
-
</div>
|
|
349
|
-
))}
|
|
350
333
|
</div>
|
|
351
334
|
</CardContent>
|
|
352
335
|
</Card>
|
|
336
|
+
|
|
337
|
+
{/* Documents — real files via wspace-files-svc */}
|
|
338
|
+
<DocumentsCard entityType="driver" entityId={driver.id} />
|
|
353
339
|
</div>
|
|
354
340
|
|
|
355
341
|
<div className="space-y-6">
|
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
* Drivers Page - List of all drivers
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import { useState } from 'react';
|
|
5
6
|
import { Users, Plus, RefreshCw, Download, Star } from 'lucide-react';
|
|
6
7
|
import { PageLayout } from '../components/PageLayout';
|
|
7
8
|
import { DataTable, type Column } from '../components/DataTable';
|
|
8
9
|
import { StatusBadge } from '../components/StatusBadge';
|
|
9
10
|
import { Avatar, AvatarFallback } from '../components/ui';
|
|
10
11
|
import { CTAOverflowMenu, EmphasisPanel, PagePurpose } from '@burdenoff/fe-libs/ui';
|
|
11
|
-
import { useDrivers } from '../hooks/useFleet';
|
|
12
|
+
import { useDrivers, useFleetStats } from '../hooks/useFleet';
|
|
12
13
|
import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
|
|
13
14
|
import { formatDate } from '../utils/formatters';
|
|
14
15
|
import { joinPath } from '../utils/navigation';
|
|
@@ -16,7 +17,15 @@ import type { Driver } from '../types';
|
|
|
16
17
|
|
|
17
18
|
export default function DriversPage() {
|
|
18
19
|
const { basePath, navigate } = useMoveTheWheels();
|
|
19
|
-
const
|
|
20
|
+
const [page, setPage] = useState(0);
|
|
21
|
+
const [pageSize, setPageSize] = useState(25);
|
|
22
|
+
const { drivers, isLoading, refetch } = useDrivers({
|
|
23
|
+
limit: pageSize,
|
|
24
|
+
offset: page * pageSize,
|
|
25
|
+
});
|
|
26
|
+
// Workforce totals + tiles from the live aggregate, not the current page.
|
|
27
|
+
const { stats } = useFleetStats();
|
|
28
|
+
const totalDrivers = stats.totalDrivers;
|
|
20
29
|
|
|
21
30
|
const goTo = (path: string) => navigate?.(joinPath(basePath, path));
|
|
22
31
|
|
|
@@ -99,7 +108,11 @@ export default function DriversPage() {
|
|
|
99
108
|
}}
|
|
100
109
|
actions={[
|
|
101
110
|
{ label: 'Refresh', icon: <RefreshCw size={16} />, onSelect: () => refetch() },
|
|
102
|
-
{
|
|
111
|
+
{
|
|
112
|
+
label: 'Export',
|
|
113
|
+
icon: <Download size={16} />,
|
|
114
|
+
onSelect: () => goTo('import-export'),
|
|
115
|
+
},
|
|
103
116
|
]}
|
|
104
117
|
/>
|
|
105
118
|
}
|
|
@@ -120,17 +133,13 @@ export default function DriversPage() {
|
|
|
120
133
|
</div>
|
|
121
134
|
<div>
|
|
122
135
|
<p className="text-2xl font-bold text-text-primary tabular-nums">
|
|
123
|
-
{
|
|
136
|
+
{stats.driversByStatus.available + stats.driversByStatus.busy}
|
|
124
137
|
</p>
|
|
125
138
|
<p className="text-xs text-text-secondary">On duty / available</p>
|
|
126
139
|
</div>
|
|
127
140
|
<div>
|
|
128
141
|
<p className="text-2xl font-bold text-text-primary tabular-nums">
|
|
129
|
-
{
|
|
130
|
-
? (drivers.reduce((sum, d) => sum + d.ratings.overall, 0) / drivers.length).toFixed(
|
|
131
|
-
1
|
|
132
|
-
)
|
|
133
|
-
: '—'}
|
|
142
|
+
{stats.avgDriverRating > 0 ? stats.avgDriverRating.toFixed(1) : '—'}
|
|
134
143
|
</p>
|
|
135
144
|
<p className="text-xs text-text-secondary">Avg rating</p>
|
|
136
145
|
</div>
|
|
@@ -146,6 +155,14 @@ export default function DriversPage() {
|
|
|
146
155
|
searchFields={['fullName', 'employeeId'] as (keyof Driver)[]}
|
|
147
156
|
onRowClick={(row) => goTo(`fleet/drivers/${row.id}`)}
|
|
148
157
|
getRowId={(row) => row.id}
|
|
158
|
+
pageIndex={page}
|
|
159
|
+
pageSize={pageSize}
|
|
160
|
+
onPageChange={setPage}
|
|
161
|
+
onPageSizeChange={(size) => {
|
|
162
|
+
setPageSize(size);
|
|
163
|
+
setPage(0);
|
|
164
|
+
}}
|
|
165
|
+
totalItems={totalDrivers}
|
|
149
166
|
emptyIllustration="empty-people"
|
|
150
167
|
emptyTitle="No drivers yet"
|
|
151
168
|
emptyDescription="Add drivers to start assigning deliveries and routes."
|
|
@@ -1,22 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Finance Page
|
|
3
|
+
*
|
|
4
|
+
* Shows only REAL figures returned by Query.financeMetrics (revenue, fuel cost,
|
|
5
|
+
* open invoices) — no simulated paid/pending splits. Invoicing and settlement
|
|
6
|
+
* are handled by the platform Billing module (global-billing-svc), which this
|
|
7
|
+
* page deep-links to rather than re-implementing an Invoice model here.
|
|
3
8
|
*/
|
|
4
9
|
|
|
5
|
-
import { DollarSign, TrendingUp, CreditCard, FileText,
|
|
10
|
+
import { DollarSign, TrendingUp, CreditCard, FileText, Fuel, ArrowRight } from 'lucide-react';
|
|
6
11
|
import { PageLayout } from '../components/PageLayout';
|
|
7
12
|
import { MetricCard, MetricGrid } from '../components/MetricCard';
|
|
8
13
|
import { Card, CardContent, CardHeader, CardTitle, Button } from '../components/ui';
|
|
9
|
-
import { EmphasisPanel,
|
|
10
|
-
import { useAnalytics } from '../hooks/useAnalytics';
|
|
11
|
-
import {
|
|
14
|
+
import { EmphasisPanel, PagePurpose } from '@burdenoff/fe-libs/ui';
|
|
15
|
+
import { useAnalytics, useFinanceMetrics } from '../hooks/useAnalytics';
|
|
16
|
+
import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
|
|
12
17
|
import { formatCurrency } from '../utils/formatters';
|
|
13
18
|
|
|
14
19
|
export default function FinancePage() {
|
|
15
20
|
const { analytics } = useAnalytics();
|
|
16
|
-
const {
|
|
21
|
+
const { metrics } = useFinanceMetrics();
|
|
22
|
+
const { navigate } = useMoveTheWheels();
|
|
17
23
|
|
|
18
|
-
|
|
19
|
-
const
|
|
24
|
+
// Deep-link to the host Billing module (mounted at /billing in the app shell).
|
|
25
|
+
const openBilling = () => navigate('/billing');
|
|
20
26
|
|
|
21
27
|
return (
|
|
22
28
|
<PageLayout
|
|
@@ -24,20 +30,15 @@ export default function FinancePage() {
|
|
|
24
30
|
subtitle="Financial overview and reports"
|
|
25
31
|
icon={<DollarSign size={24} />}
|
|
26
32
|
headerActions={
|
|
27
|
-
<
|
|
28
|
-
<
|
|
29
|
-
|
|
30
|
-
</Button>
|
|
31
|
-
<Button variant="outline">
|
|
32
|
-
<Download size={16} /> Export
|
|
33
|
-
</Button>
|
|
34
|
-
</div>
|
|
33
|
+
<Button variant="outline" onClick={openBilling}>
|
|
34
|
+
<FileText size={16} /> Invoices & Billing
|
|
35
|
+
</Button>
|
|
35
36
|
}
|
|
36
37
|
>
|
|
37
38
|
<PagePurpose icon={<DollarSign size={18} />}>
|
|
38
|
-
Follow the money behind your deliveries. See revenue
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
Follow the money behind your deliveries. See total revenue, how much has already been
|
|
40
|
+
collected on delivered orders, fuel cost and how many invoices are still open — then head to
|
|
41
|
+
Billing to raise and settle invoices.
|
|
41
42
|
</PagePurpose>
|
|
42
43
|
|
|
43
44
|
<MetricGrid columns={4}>
|
|
@@ -45,28 +46,25 @@ export default function FinancePage() {
|
|
|
45
46
|
treatment="glass"
|
|
46
47
|
glow
|
|
47
48
|
label="Total Revenue"
|
|
48
|
-
value={formatCurrency(
|
|
49
|
+
value={formatCurrency(metrics.totalRevenue)}
|
|
49
50
|
icon={<DollarSign size={20} />}
|
|
50
|
-
change="+12.5%"
|
|
51
|
-
changeType="positive"
|
|
52
|
-
trend="up"
|
|
53
51
|
/>
|
|
54
52
|
<MetricCard
|
|
55
|
-
label="
|
|
56
|
-
value={formatCurrency(
|
|
53
|
+
label="Collected (Delivered)"
|
|
54
|
+
value={formatCurrency(metrics.deliveredRevenue)}
|
|
57
55
|
icon={<TrendingUp size={20} />}
|
|
58
56
|
changeType="positive"
|
|
59
57
|
/>
|
|
60
58
|
<MetricCard
|
|
61
|
-
label="
|
|
62
|
-
value={formatCurrency(
|
|
63
|
-
icon={<
|
|
59
|
+
label="Fuel Cost"
|
|
60
|
+
value={formatCurrency(metrics.fuelCost)}
|
|
61
|
+
icon={<Fuel size={20} />}
|
|
64
62
|
changeType="neutral"
|
|
65
63
|
/>
|
|
66
64
|
<MetricCard
|
|
67
|
-
label="
|
|
68
|
-
value={
|
|
69
|
-
icon={<
|
|
65
|
+
label="Open Invoices"
|
|
66
|
+
value={String(metrics.openInvoices)}
|
|
67
|
+
icon={<CreditCard size={20} />}
|
|
70
68
|
/>
|
|
71
69
|
</MetricGrid>
|
|
72
70
|
|
|
@@ -76,70 +74,55 @@ export default function FinancePage() {
|
|
|
76
74
|
<CardTitle>Revenue by Month</CardTitle>
|
|
77
75
|
</CardHeader>
|
|
78
76
|
<CardContent>
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
77
|
+
{analytics.trends.revenue.length > 0 ? (
|
|
78
|
+
<div className="space-y-4">
|
|
79
|
+
{analytics.trends.revenue.map((r) => (
|
|
80
|
+
<div
|
|
81
|
+
key={r.month}
|
|
82
|
+
className="flex items-center justify-between p-3 bg-bg-sunken/50 rounded-lg"
|
|
83
|
+
>
|
|
84
|
+
<span className="font-medium">{r.month}</span>
|
|
85
|
+
<span className="text-lg font-bold">{formatCurrency(r.revenue)}</span>
|
|
86
|
+
</div>
|
|
87
|
+
))}
|
|
88
|
+
</div>
|
|
89
|
+
) : (
|
|
90
|
+
<p className="text-text-secondary text-center py-6">
|
|
91
|
+
No revenue recorded yet. Revenue appears here as orders are delivered.
|
|
92
|
+
</p>
|
|
93
|
+
)}
|
|
90
94
|
</CardContent>
|
|
91
95
|
</Card>
|
|
92
96
|
|
|
93
|
-
{/*
|
|
97
|
+
{/* Invoices & Settlement — reuse of the Billing module */}
|
|
94
98
|
<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>
|
|
99
|
+
<h2 className="text-base font-semibold text-text-primary">Invoices & Settlement</h2>
|
|
100
|
+
<p className="text-sm text-text-secondary">
|
|
101
|
+
Invoicing, payments and settlement run through the platform Billing module — the single
|
|
102
|
+
source of truth for what's been billed and paid. MoveTheWheels doesn't keep its
|
|
103
|
+
own invoice ledger.
|
|
104
|
+
</p>
|
|
105
|
+
<div className="flex items-center justify-between p-4 bg-status-warning-bg/10 rounded-lg">
|
|
106
|
+
<div className="flex items-center gap-3">
|
|
107
|
+
<div className="w-10 h-10 rounded-full bg-status-warning-bg/20 flex items-center justify-center">
|
|
108
|
+
<CreditCard size={20} className="text-status-warning-text" />
|
|
106
109
|
</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>
|
|
110
|
+
<div>
|
|
111
|
+
<p className="font-medium">Open invoices</p>
|
|
112
|
+
<p className="text-sm text-text-secondary">
|
|
113
|
+
{metrics.openInvoices} awaiting settlement
|
|
114
|
+
</p>
|
|
122
115
|
</div>
|
|
123
|
-
<span className="text-xl font-bold text-status-warning-text">
|
|
124
|
-
{formatCurrency(pendingPayments)}
|
|
125
|
-
</span>
|
|
126
116
|
</div>
|
|
117
|
+
<span className="text-xl font-bold text-status-warning-text">
|
|
118
|
+
{metrics.openInvoices}
|
|
119
|
+
</span>
|
|
127
120
|
</div>
|
|
121
|
+
<Button className="w-full" onClick={openBilling}>
|
|
122
|
+
Open Billing <ArrowRight size={16} />
|
|
123
|
+
</Button>
|
|
128
124
|
</EmphasisPanel>
|
|
129
125
|
</div>
|
|
130
|
-
|
|
131
|
-
<Card>
|
|
132
|
-
<CardHeader>
|
|
133
|
-
<CardTitle>Recent Transactions</CardTitle>
|
|
134
|
-
</CardHeader>
|
|
135
|
-
<CardContent>
|
|
136
|
-
<IllustratedEmptyState
|
|
137
|
-
illustration="empty-data"
|
|
138
|
-
title="No transactions yet"
|
|
139
|
-
description="Once payments are processed, your transaction history will appear here. Connect a payment gateway to start recording transactions."
|
|
140
|
-
/>
|
|
141
|
-
</CardContent>
|
|
142
|
-
</Card>
|
|
143
126
|
</PageLayout>
|
|
144
127
|
);
|
|
145
128
|
}
|