@burdenoff/microfe-movethewheels 2026.626.2 → 2026.703.1
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-AvsMbunM.js → AIAssistantPage-C3ovZ0Fh.js} +100 -73
- package/dist/AnalyticsPage-O8SpY-V4.js +215 -0
- package/dist/{CreateOrderPage-CjlnEtk3.js → CreateOrderPage-CB1_-_mN.js} +33 -35
- package/dist/{CustomerDetailsPage-I6cdDHUi.js → CustomerDetailsPage-BaeKEI1D.js} +42 -44
- package/dist/{CustomersPage-CGGfD4ki.js → CustomersPage-DRaLuHMC.js} +26 -28
- package/dist/{DashboardPage-D2HRqwg9.js → DashboardPage-DPjhNfKB.js} +85 -83
- package/dist/{DataTable-CD9ZhpLB.js → DataTable-DmciIKKM.js} +3 -3
- package/dist/{DriverDetailsPage-rg_pGNuP.js → DriverDetailsPage-CSSQTOzn.js} +52 -54
- package/dist/{DriversPage-DKiynISH.js → DriversPage-C2Bk0w4Q.js} +24 -26
- package/dist/{FinancePage-Vg8WPs38.js → FinancePage-lmp0Sdsw.js} +9 -9
- package/dist/FleetPage-DZn5pIqd.js +365 -0
- package/dist/ImportExportPage-BIUJZc7O.js +290 -0
- package/dist/InventoryPage-Cxh1zYJd.js +253 -0
- package/dist/{LiveTrackingPage-DQ1q4Id3.js → LiveTrackingPage-BY1yAkIt.js} +60 -56
- package/dist/MarketplacePage-RMuL17EA.js +247 -0
- package/dist/{MetricCard-B7yMyvMX.js → MetricCard-7aYgzZHK.js} +1 -1
- package/dist/{OrderDetailsPage-CFASgsYX.js → OrderDetailsPage-C2Mf4KSy.js} +83 -85
- package/dist/{OrdersListPage-BHeT8S5o.js → OrdersListPage-BrPBtnhp.js} +31 -33
- package/dist/{PageLayout-D3N8MCTm.js → PageLayout-D4LDHJJq.js} +483 -422
- package/dist/{ProductDetailsPage-CFGcbaHo.js → ProductDetailsPage-CezzzZcj.js} +27 -29
- package/dist/{ProductsPage--xEpbQ8A.js → ProductsPage-I5DilIoY.js} +21 -23
- package/dist/ReportsPage-BDXNmUng.js +284 -0
- package/dist/RouteDetailsPage-BcomL20M.js +263 -0
- package/dist/RoutesPage-lqX9ylHP.js +144 -0
- package/dist/SettingsPage-CQVIOaNs.js +421 -0
- package/dist/StatusBadge-NpTeHVlw.js +152 -0
- package/dist/{TrackingPage-CcRc_dva.js → TrackingPage-BXrjz7V8.js} +59 -61
- package/dist/{VehicleDetailsPage-3YDMPsPs.js → VehicleDetailsPage-DZjLcJgr.js} +40 -42
- package/dist/{VehiclesPage-UlxPWP9K.js → VehiclesPage-6W39XT5W.js} +19 -21
- package/dist/{WarehouseDetailsPage-Db7KeN-d.js → WarehouseDetailsPage-C10pgRGW.js} +38 -40
- package/dist/{WarehousesPage-CuTO-OPp.js → WarehousesPage-CHvBqNaQ.js} +15 -17
- package/dist/{arrow-left-CBpZKkpq.js → arrow-left-n48Y-Wis.js} +1 -1
- package/dist/{box-CAaiElsb.js → box-D9Pal4HM.js} +1 -1
- package/dist/{chart-column-D1Ym15-S.js → chart-column-BFIJysDR.js} +1 -1
- package/dist/{chevron-right-BIFGi0UA.js → chevron-right-EMIIyyJ1.js} +1 -1
- package/dist/{circle-alert-DjmNo5_L.js → circle-alert-iY24lSlp.js} +1 -1
- package/dist/{circle-check-big-CjuPVo3_.js → circle-check-big-w0TrLKMF.js} +1 -1
- package/dist/{clock-BjkdnddD.js → clock-DJZtdl8u.js} +1 -1
- package/dist/{dollar-sign-IB4O0wuQ.js → dollar-sign-CgKQRz_e.js} +1 -1
- package/dist/{download-Dpa9vqoG.js → download-DRXxoiQR.js} +1 -1
- package/dist/{file-text-C8Uioy11.js → file-text-DWWY6_f5.js} +1 -1
- package/dist/{filter-Da5OZaHI.js → filter-DloLC2eE.js} +1 -1
- package/dist/index.js +119 -111
- package/dist/{map-_Per4sT9.js → map-CkJaULb2.js} +1 -1
- package/dist/{map-pin-BSaf5IDL.js → map-pin-BGiCvyCs.js} +1 -1
- package/dist/{navigation-DaTbrhw2.js → navigation-D36prnAn.js} +1 -1
- package/dist/{package-BE7U0wQE.js → package-BChUzpJS.js} +1 -1
- package/dist/{phone-DV4StZiA.js → phone-Ac9s3mo_.js} +1 -1
- package/dist/{plus-Dl1rLeJR.js → plus-Z76q-FZm.js} +1 -1
- package/dist/{refresh-cw-CWfERAit.js → refresh-cw-DKt6f5T6.js} +1 -1
- package/dist/{save-XH8vydOn.js → save-CojywNhG.js} +1 -1
- package/dist/{search-OxoIiL_C.js → search-CDGefqb3.js} +1 -1
- package/dist/{square-pen-hLIbFQFV.js → square-pen-CopE9GmB.js} +1 -1
- package/dist/{star-YsVV3MNQ.js → star-nHlLjDKR.js} +1 -1
- package/dist/{store-RCBl7HB-.js → store-BR9Voi5E.js} +1 -1
- package/dist/{trending-up-C-85AwGT.js → trending-up-B7W-GL6P.js} +1 -1
- package/dist/{triangle-alert-BOhfGgmh.js → triangle-alert-BonHFFNz.js} +1 -1
- package/dist/{truck-CtDjNFbi.js → truck-DrlfSpRC.js} +1 -1
- package/dist/{useAnalytics-ph7eTIK6.js → useAnalytics-CgdzUfY_.js} +40 -40
- package/dist/useAssistant-D4v3oxD3.js +84 -0
- package/dist/{useCustomers-bS3a4ytk.js → useCustomers-QrlozybF.js} +24 -24
- package/dist/{useFleet-BdETplNE.js → useFleet-DiAC-IiH.js} +70 -67
- package/dist/useImportExport-BLMnAKCC.js +55 -0
- package/dist/{useInventory-Dwn18FPz.js → useInventory-DKoks37x.js} +33 -33
- package/dist/{useOrders-D_3_hGMp.js → useOrders-D3UZVy8Y.js} +42 -42
- package/dist/useReports-Puq4DCH9.js +78 -0
- package/dist/{useRoutes-v4aBaS-E.js → useRoutes-Do57LvRb.js} +31 -31
- package/dist/useSettings-CkDH_-gM.js +81 -0
- package/dist/{useTracking-De2KIUNu.js → useTracking-GJify4HN.js} +22 -22
- package/dist/{user-Dz5TJZFs.js → user-CjSoudeu.js} +1 -1
- package/dist/{users-g2N0eL-Z.js → users-BAM4gHTn.js} +1 -1
- package/dist/{warehouse-D3WWDeTX.js → warehouse-C7MSr47B.js} +1 -1
- package/dist/{wrench-CmxY1n4I.js → wrench-CANXWxF-.js} +1 -1
- package/package.json +1 -1
- package/src/movethewheels/MoveTheWheelsRoutes.tsx +27 -3
- package/src/movethewheels/components/StatusBadge.tsx +18 -37
- package/src/movethewheels/constants/index.ts +67 -26
- package/src/movethewheels/hooks/index.ts +12 -0
- package/src/movethewheels/hooks/useAnalytics.ts +20 -6
- package/src/movethewheels/hooks/useAssistant.ts +74 -0
- package/src/movethewheels/hooks/useFleet.ts +63 -59
- package/src/movethewheels/hooks/useImportExport.ts +101 -0
- package/src/movethewheels/hooks/useReports.ts +158 -0
- package/src/movethewheels/hooks/useSettings.ts +120 -0
- package/src/movethewheels/pages/AIAssistantPage.tsx +53 -15
- package/src/movethewheels/pages/AnalyticsPage.tsx +67 -64
- package/src/movethewheels/pages/CustomerDetailsPage.tsx +1 -1
- package/src/movethewheels/pages/CustomersPage.tsx +1 -1
- package/src/movethewheels/pages/DashboardPage.tsx +42 -22
- package/src/movethewheels/pages/DriverDetailsPage.tsx +1 -1
- package/src/movethewheels/pages/DriversPage.tsx +1 -1
- package/src/movethewheels/pages/FleetPage.tsx +96 -38
- package/src/movethewheels/pages/ImportExportPage.tsx +174 -68
- package/src/movethewheels/pages/InventoryPage.tsx +66 -28
- package/src/movethewheels/pages/LiveTrackingPage.tsx +12 -3
- package/src/movethewheels/pages/MarketplacePage.tsx +87 -45
- package/src/movethewheels/pages/OrderDetailsPage.tsx +2 -2
- package/src/movethewheels/pages/ReportsPage.tsx +194 -90
- package/src/movethewheels/pages/RouteDetailsPage.tsx +5 -11
- package/src/movethewheels/pages/RoutesPage.tsx +4 -9
- package/src/movethewheels/pages/SettingsPage.tsx +234 -56
- package/src/movethewheels/pages/TrackingPage.tsx +6 -6
- package/src/movethewheels/providers/MoveTheWheelsProvider.tsx +7 -6
- package/src/movethewheels/store/movethewheelsStore.ts +1 -1
- package/dist/AnalyticsPage-CCLfHgQZ.js +0 -212
- package/dist/FleetPage-DddXt8r8.js +0 -332
- package/dist/ImportExportPage-BW6qO3kc.js +0 -248
- package/dist/InventoryPage-rMGuaJN-.js +0 -222
- package/dist/MarketplacePage-DKrXAv62.js +0 -219
- package/dist/ReportsPage-Bb7_90rK.js +0 -235
- package/dist/RouteDetailsPage-CVtDVb1N.js +0 -282
- package/dist/RoutesPage-s57PtgcQ.js +0 -163
- package/dist/SettingsPage-BYNCBOpN.js +0 -298
- package/dist/StatusBadge-CijYQXtb.js +0 -209
- package/dist/graphqlClient-CdJyR_ed.js +0 -55
- package/dist/navigation-BgnOfsVd.js +0 -6
- package/dist/route-Dp9JiN_E.js +0 -51
- package/dist/settings-3Rw5NwFF.js +0 -28
- package/src/movethewheels/constants/mockData.ts +0 -1342
- /package/dist/{formatters-_vJlC-47.js → formatters-DhKZ-eA1.js} +0 -0
|
@@ -210,7 +210,7 @@ export default function OrderDetailsPage() {
|
|
|
210
210
|
</p>
|
|
211
211
|
)}
|
|
212
212
|
{order.actualPickup && (
|
|
213
|
-
<p className="text-sm text-
|
|
213
|
+
<p className="text-sm text-status-success-text mt-1">
|
|
214
214
|
<CheckCircle size={14} className="inline mr-1" />
|
|
215
215
|
Picked up: {formatDate(order.actualPickup, 'full')}
|
|
216
216
|
</p>
|
|
@@ -227,7 +227,7 @@ export default function OrderDetailsPage() {
|
|
|
227
227
|
</p>
|
|
228
228
|
)}
|
|
229
229
|
{order.actualDelivery && (
|
|
230
|
-
<p className="text-sm text-
|
|
230
|
+
<p className="text-sm text-status-success-text mt-1">
|
|
231
231
|
<CheckCircle size={14} className="inline mr-1" />
|
|
232
232
|
Delivered: {formatDate(order.actualDelivery, 'full')}
|
|
233
233
|
</p>
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Reports Page
|
|
3
|
+
*
|
|
4
|
+
* Lists reports from the backend (movethewheelsReports) and lets operators
|
|
5
|
+
* create, schedule and generate them via the real mutations. Pre-launch
|
|
6
|
+
* workspaces start empty — no fabricated report history is shown.
|
|
3
7
|
*/
|
|
4
8
|
|
|
5
|
-
import {
|
|
9
|
+
import { useState } from 'react';
|
|
10
|
+
import { FileBarChart, Download, Calendar, Filter, Clock, Plus } from 'lucide-react';
|
|
6
11
|
import { PageLayout } from '../components/PageLayout';
|
|
7
12
|
import {
|
|
8
13
|
Card,
|
|
@@ -16,53 +21,77 @@ import {
|
|
|
16
21
|
SelectItem,
|
|
17
22
|
SelectTrigger,
|
|
18
23
|
SelectValue,
|
|
24
|
+
Skeleton,
|
|
19
25
|
} from '../components/ui';
|
|
20
|
-
import { EmphasisPanel, PagePurpose } from '@burdenoff/fe-libs/ui';
|
|
26
|
+
import { EmphasisPanel, IllustratedEmptyState, PagePurpose } from '@burdenoff/fe-libs/ui';
|
|
27
|
+
import { useReports, type CreateReportInput } from '../hooks/useReports';
|
|
28
|
+
import { formatDate } from '../utils/formatters';
|
|
29
|
+
|
|
30
|
+
const REPORT_TYPES: { value: string; label: string }[] = [
|
|
31
|
+
{ value: 'operations', label: 'Operations Summary' },
|
|
32
|
+
{ value: 'finance', label: 'Financial Report' },
|
|
33
|
+
{ value: 'fleet', label: 'Fleet Utilization' },
|
|
34
|
+
{ value: 'customers', label: 'Customer Analytics' },
|
|
35
|
+
{ value: 'inventory', label: 'Inventory Status' },
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
interface Feedback {
|
|
39
|
+
tone: 'success' | 'error';
|
|
40
|
+
text: string;
|
|
41
|
+
}
|
|
21
42
|
|
|
22
43
|
export default function ReportsPage() {
|
|
23
|
-
const reports =
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
description:
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
44
|
+
const { reports, isLoading, error, refetch, createReport, generateReport } = useReports();
|
|
45
|
+
const [reportType, setReportType] = useState('operations');
|
|
46
|
+
const [busyId, setBusyId] = useState<string | null>(null);
|
|
47
|
+
const [creating, setCreating] = useState(false);
|
|
48
|
+
const [feedback, setFeedback] = useState<Feedback | null>(null);
|
|
49
|
+
|
|
50
|
+
const typeLabel = (value: string) =>
|
|
51
|
+
REPORT_TYPES.find((item) => item.value === value)?.label ?? value;
|
|
52
|
+
|
|
53
|
+
const handleCreate = async (schedule?: string) => {
|
|
54
|
+
setCreating(true);
|
|
55
|
+
setFeedback(null);
|
|
56
|
+
const input: CreateReportInput = {
|
|
57
|
+
name: `${typeLabel(reportType)}${schedule ? ' (Scheduled)' : ''}`,
|
|
58
|
+
type: reportType,
|
|
59
|
+
description: `${typeLabel(reportType)} generated from live operational data`,
|
|
60
|
+
...(schedule ? { schedule } : {}),
|
|
61
|
+
};
|
|
62
|
+
try {
|
|
63
|
+
await createReport(input);
|
|
64
|
+
setFeedback({
|
|
65
|
+
tone: 'success',
|
|
66
|
+
text: schedule ? 'Scheduled report created.' : 'Report created.',
|
|
67
|
+
});
|
|
68
|
+
} catch (createError) {
|
|
69
|
+
setFeedback({
|
|
70
|
+
tone: 'error',
|
|
71
|
+
text: createError instanceof Error ? createError.message : 'Could not create report.',
|
|
72
|
+
});
|
|
73
|
+
} finally {
|
|
74
|
+
setCreating(false);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const handleGenerate = async (id: string) => {
|
|
79
|
+
setBusyId(id);
|
|
80
|
+
setFeedback(null);
|
|
81
|
+
try {
|
|
82
|
+
await generateReport(id);
|
|
83
|
+
setFeedback({ tone: 'success', text: 'Report generated.' });
|
|
84
|
+
} catch (genError) {
|
|
85
|
+
setFeedback({
|
|
86
|
+
tone: 'error',
|
|
87
|
+
text: genError instanceof Error ? genError.message : 'Could not generate report.',
|
|
88
|
+
});
|
|
89
|
+
} finally {
|
|
90
|
+
setBusyId(null);
|
|
91
|
+
}
|
|
92
|
+
};
|
|
61
93
|
|
|
62
|
-
const scheduledReports =
|
|
63
|
-
{ name: 'Daily Summary', nextRun: 'Tomorrow 6:00 AM', recipients: 3 },
|
|
64
|
-
{ name: 'Weekly Review', nextRun: 'Monday 9:00 AM', recipients: 5 },
|
|
65
|
-
];
|
|
94
|
+
const scheduledReports = reports.filter((report) => report.schedule);
|
|
66
95
|
|
|
67
96
|
return (
|
|
68
97
|
<PageLayout
|
|
@@ -71,19 +100,20 @@ export default function ReportsPage() {
|
|
|
71
100
|
icon={<FileBarChart size={24} />}
|
|
72
101
|
headerActions={
|
|
73
102
|
<div className="flex gap-2">
|
|
74
|
-
<Select
|
|
75
|
-
<SelectTrigger className="w-[
|
|
103
|
+
<Select value={reportType} onValueChange={setReportType}>
|
|
104
|
+
<SelectTrigger className="w-[190px]">
|
|
76
105
|
<SelectValue />
|
|
77
106
|
</SelectTrigger>
|
|
78
107
|
<SelectContent>
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
108
|
+
{REPORT_TYPES.map((item) => (
|
|
109
|
+
<SelectItem key={item.value} value={item.value}>
|
|
110
|
+
{item.label}
|
|
111
|
+
</SelectItem>
|
|
112
|
+
))}
|
|
83
113
|
</SelectContent>
|
|
84
114
|
</Select>
|
|
85
|
-
<Button>
|
|
86
|
-
<
|
|
115
|
+
<Button onClick={() => void handleCreate()} disabled={creating}>
|
|
116
|
+
<Plus size={16} /> Create Report
|
|
87
117
|
</Button>
|
|
88
118
|
</div>
|
|
89
119
|
}
|
|
@@ -94,37 +124,94 @@ export default function ReportsPage() {
|
|
|
94
124
|
so stakeholders always have the latest numbers without logging in.
|
|
95
125
|
</PagePurpose>
|
|
96
126
|
|
|
127
|
+
{feedback && (
|
|
128
|
+
<div
|
|
129
|
+
role="status"
|
|
130
|
+
className={`rounded-lg p-3 text-sm ${
|
|
131
|
+
feedback.tone === 'success'
|
|
132
|
+
? 'bg-status-success-bg-subtle text-status-success-text'
|
|
133
|
+
: 'bg-status-error-bg-subtle text-status-error-text'
|
|
134
|
+
}`}
|
|
135
|
+
>
|
|
136
|
+
{feedback.text}
|
|
137
|
+
</div>
|
|
138
|
+
)}
|
|
139
|
+
|
|
97
140
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
98
141
|
<div className="lg:col-span-2 space-y-4">
|
|
99
142
|
<h3 className="font-semibold">Available Reports</h3>
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
143
|
+
|
|
144
|
+
{isLoading ? (
|
|
145
|
+
<div className="space-y-3">
|
|
146
|
+
{Array.from({ length: 3 }).map((_, i) => (
|
|
147
|
+
<Skeleton key={i} className="h-20 w-full rounded-lg" />
|
|
148
|
+
))}
|
|
149
|
+
</div>
|
|
150
|
+
) : error ? (
|
|
151
|
+
<Card className="border-status-error-border">
|
|
152
|
+
<CardContent className="p-6 text-center space-y-3">
|
|
153
|
+
<p className="text-status-error-text font-medium">Couldn't load reports</p>
|
|
154
|
+
<p className="text-sm text-text-secondary">{error.message}</p>
|
|
155
|
+
<Button variant="outline" onClick={refetch}>
|
|
156
|
+
Retry
|
|
157
|
+
</Button>
|
|
158
|
+
</CardContent>
|
|
159
|
+
</Card>
|
|
160
|
+
) : reports.length === 0 ? (
|
|
161
|
+
<Card>
|
|
162
|
+
<CardContent className="p-0">
|
|
163
|
+
<IllustratedEmptyState
|
|
164
|
+
illustration="empty-data"
|
|
165
|
+
title="No reports yet"
|
|
166
|
+
description="Create your first report to turn live operational data into a shareable summary."
|
|
167
|
+
action={
|
|
168
|
+
<Button onClick={() => void handleCreate()} disabled={creating}>
|
|
169
|
+
<Plus size={16} className="mr-1.5" /> Create Report
|
|
122
170
|
</Button>
|
|
123
|
-
|
|
124
|
-
|
|
171
|
+
}
|
|
172
|
+
/>
|
|
125
173
|
</CardContent>
|
|
126
174
|
</Card>
|
|
127
|
-
)
|
|
175
|
+
) : (
|
|
176
|
+
reports.map((report) => (
|
|
177
|
+
<Card key={report.id}>
|
|
178
|
+
<CardContent className="p-4">
|
|
179
|
+
<div className="flex items-center justify-between">
|
|
180
|
+
<div className="flex items-center gap-3">
|
|
181
|
+
<div className="w-10 h-10 rounded-lg bg-action-primary-bg/10 flex items-center justify-center">
|
|
182
|
+
<FileBarChart size={20} className="text-primary" />
|
|
183
|
+
</div>
|
|
184
|
+
<div>
|
|
185
|
+
<p className="font-medium">{report.name}</p>
|
|
186
|
+
<p className="text-sm text-text-secondary">
|
|
187
|
+
{report.description ?? typeLabel(report.type)}
|
|
188
|
+
</p>
|
|
189
|
+
</div>
|
|
190
|
+
</div>
|
|
191
|
+
<div className="flex items-center gap-3">
|
|
192
|
+
<div className="text-right">
|
|
193
|
+
<Badge variant="outline">{report.schedule ?? 'On demand'}</Badge>
|
|
194
|
+
{report.lastGenerated && (
|
|
195
|
+
<p className="text-xs text-text-secondary mt-1">
|
|
196
|
+
Last: {formatDate(new Date(report.lastGenerated), 'short')}
|
|
197
|
+
</p>
|
|
198
|
+
)}
|
|
199
|
+
</div>
|
|
200
|
+
<Button
|
|
201
|
+
variant="outline"
|
|
202
|
+
size="sm"
|
|
203
|
+
aria-label={`Generate ${report.name}`}
|
|
204
|
+
disabled={busyId === report.id}
|
|
205
|
+
onClick={() => void handleGenerate(report.id)}
|
|
206
|
+
>
|
|
207
|
+
<Download size={14} />
|
|
208
|
+
</Button>
|
|
209
|
+
</div>
|
|
210
|
+
</div>
|
|
211
|
+
</CardContent>
|
|
212
|
+
</Card>
|
|
213
|
+
))
|
|
214
|
+
)}
|
|
128
215
|
</div>
|
|
129
216
|
|
|
130
217
|
<div className="space-y-6">
|
|
@@ -133,14 +220,24 @@ export default function ReportsPage() {
|
|
|
133
220
|
<h2 className="flex items-center gap-2 text-base font-semibold text-text-primary">
|
|
134
221
|
<Clock size={18} /> Scheduled Reports
|
|
135
222
|
</h2>
|
|
136
|
-
{scheduledReports.
|
|
137
|
-
<
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
223
|
+
{scheduledReports.length === 0 ? (
|
|
224
|
+
<p className="text-sm text-text-secondary">
|
|
225
|
+
No scheduled reports yet. Schedule one to have it generated automatically.
|
|
226
|
+
</p>
|
|
227
|
+
) : (
|
|
228
|
+
scheduledReports.map((report) => (
|
|
229
|
+
<div key={report.id} className="p-3 border border-border-seam rounded-lg">
|
|
230
|
+
<p className="font-medium">{report.name}</p>
|
|
231
|
+
<p className="text-sm text-text-secondary">Schedule: {report.schedule}</p>
|
|
232
|
+
</div>
|
|
233
|
+
))
|
|
234
|
+
)}
|
|
235
|
+
<Button
|
|
236
|
+
variant="outline"
|
|
237
|
+
className="w-full"
|
|
238
|
+
disabled={creating}
|
|
239
|
+
onClick={() => void handleCreate('daily')}
|
|
240
|
+
>
|
|
144
241
|
Schedule New Report
|
|
145
242
|
</Button>
|
|
146
243
|
</EmphasisPanel>
|
|
@@ -150,15 +247,22 @@ export default function ReportsPage() {
|
|
|
150
247
|
<CardTitle>Quick Actions</CardTitle>
|
|
151
248
|
</CardHeader>
|
|
152
249
|
<CardContent className="space-y-2">
|
|
153
|
-
<Button
|
|
250
|
+
<Button
|
|
251
|
+
variant="outline"
|
|
252
|
+
className="w-full justify-start"
|
|
253
|
+
disabled={creating}
|
|
254
|
+
onClick={() => void handleCreate('weekly')}
|
|
255
|
+
>
|
|
154
256
|
<Calendar size={16} /> Schedule Report
|
|
155
257
|
</Button>
|
|
156
|
-
<Button
|
|
258
|
+
<Button
|
|
259
|
+
variant="outline"
|
|
260
|
+
className="w-full justify-start"
|
|
261
|
+
disabled={creating}
|
|
262
|
+
onClick={() => void handleCreate()}
|
|
263
|
+
>
|
|
157
264
|
<Filter size={16} /> Custom Report
|
|
158
265
|
</Button>
|
|
159
|
-
<Button variant="outline" className="w-full justify-start">
|
|
160
|
-
<Download size={16} /> Export All
|
|
161
|
-
</Button>
|
|
162
266
|
</CardContent>
|
|
163
267
|
</Card>
|
|
164
268
|
</div>
|
|
@@ -12,6 +12,7 @@ import { useRoute, useRoutes } from '../hooks/useRoutes';
|
|
|
12
12
|
import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
|
|
13
13
|
import { formatDate, formatAddress } from '../utils/formatters';
|
|
14
14
|
import { joinPath } from '../utils/navigation';
|
|
15
|
+
import { ROUTE_STATUS_COLORS } from '../constants';
|
|
15
16
|
|
|
16
17
|
export default function RouteDetailsPage() {
|
|
17
18
|
const { routeId } = useParams<{ routeId: string }>();
|
|
@@ -60,13 +61,6 @@ export default function RouteDetailsPage() {
|
|
|
60
61
|
}
|
|
61
62
|
};
|
|
62
63
|
|
|
63
|
-
const statusColors = {
|
|
64
|
-
planned: { bg: 'bg-blue-100', text: 'text-blue-700' },
|
|
65
|
-
in_progress: { bg: 'bg-yellow-100', text: 'text-yellow-700' },
|
|
66
|
-
completed: { bg: 'bg-green-100', text: 'text-green-700' },
|
|
67
|
-
cancelled: { bg: 'bg-red-100', text: 'text-red-700' },
|
|
68
|
-
};
|
|
69
|
-
|
|
70
64
|
return (
|
|
71
65
|
<PageLayout
|
|
72
66
|
title={route.name}
|
|
@@ -89,7 +83,7 @@ export default function RouteDetailsPage() {
|
|
|
89
83
|
}
|
|
90
84
|
headerContent={
|
|
91
85
|
<div className="flex gap-2">
|
|
92
|
-
<StatusBadge status={route.status} customColors={
|
|
86
|
+
<StatusBadge status={route.status} customColors={ROUTE_STATUS_COLORS[route.status]} />
|
|
93
87
|
<Badge variant={route.optimized ? 'default' : 'secondary'}>
|
|
94
88
|
{route.optimized ? 'Optimized' : 'Not Optimized'}
|
|
95
89
|
</Badge>
|
|
@@ -159,7 +153,7 @@ export default function RouteDetailsPage() {
|
|
|
159
153
|
<div key={stop.id} className="flex gap-4">
|
|
160
154
|
<div className="relative">
|
|
161
155
|
<div
|
|
162
|
-
className={`w-8 h-8 rounded-full flex items-center justify-center ${stop.status === 'completed' ? 'bg-
|
|
156
|
+
className={`w-8 h-8 rounded-full flex items-center justify-center ${stop.status === 'completed' ? 'bg-status-success-text text-text-inverse' : 'bg-bg-sunken'}`}
|
|
163
157
|
>
|
|
164
158
|
{stop.status === 'completed' ? <CheckCircle size={16} /> : stop.sequence}
|
|
165
159
|
</div>
|
|
@@ -188,7 +182,7 @@ export default function RouteDetailsPage() {
|
|
|
188
182
|
Scheduled: {formatDate(stop.scheduledTime, 'time')}
|
|
189
183
|
</p>
|
|
190
184
|
{stop.actualTime && (
|
|
191
|
-
<p className="text-sm text-
|
|
185
|
+
<p className="text-sm text-status-success-text">
|
|
192
186
|
Actual: {formatDate(stop.actualTime, 'time')}
|
|
193
187
|
</p>
|
|
194
188
|
)}
|
|
@@ -267,7 +261,7 @@ export default function RouteDetailsPage() {
|
|
|
267
261
|
{route.trafficConditions.map((tc, i) => (
|
|
268
262
|
<div
|
|
269
263
|
key={i}
|
|
270
|
-
className={`p-2 rounded mb-2 ${tc.condition === 'severe' ? 'bg-
|
|
264
|
+
className={`p-2 rounded mb-2 ${tc.condition === 'severe' ? 'bg-status-error-bg-subtle' : tc.condition === 'heavy' ? 'bg-status-warning-bg-subtle' : 'bg-status-warning-bg-subtle'}`}
|
|
271
265
|
>
|
|
272
266
|
<p className="text-sm font-medium capitalize">{tc.condition} traffic</p>
|
|
273
267
|
<p className="text-xs text-text-secondary">
|
|
@@ -12,6 +12,7 @@ import { useRoutes } from '../hooks/useRoutes';
|
|
|
12
12
|
import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
|
|
13
13
|
import { formatDate } from '../utils/formatters';
|
|
14
14
|
import { joinPath } from '../utils/navigation';
|
|
15
|
+
import { ROUTE_STATUS_COLORS } from '../constants';
|
|
15
16
|
import type { Route } from '../types';
|
|
16
17
|
|
|
17
18
|
export default function RoutesPage() {
|
|
@@ -33,15 +34,9 @@ export default function RoutesPage() {
|
|
|
33
34
|
header: 'Status',
|
|
34
35
|
accessorKey: 'status',
|
|
35
36
|
sortable: true,
|
|
36
|
-
cell: (row) =>
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
in_progress: { bg: 'bg-yellow-100', text: 'text-yellow-700' },
|
|
40
|
-
completed: { bg: 'bg-green-100', text: 'text-green-700' },
|
|
41
|
-
cancelled: { bg: 'bg-red-100', text: 'text-red-700' },
|
|
42
|
-
};
|
|
43
|
-
return <StatusBadge status={row.status} customColors={colors[row.status]} />;
|
|
44
|
-
},
|
|
37
|
+
cell: (row) => (
|
|
38
|
+
<StatusBadge status={row.status} customColors={ROUTE_STATUS_COLORS[row.status]} />
|
|
39
|
+
),
|
|
45
40
|
},
|
|
46
41
|
{
|
|
47
42
|
id: 'stops',
|