@burdenoff/microfe-movethewheels 2026.1026.1 → 2026.1026.3
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/MoveTheWheelsRoot.js +126 -159
- package/dist/MoveTheWheelsRoutes.js +16 -32
- package/dist/components/CustomerPicker.js +2 -2
- package/dist/hooks/index.js +0 -3
- package/dist/index.js +27 -30
- package/dist/movethewheels/hooks/index.d.ts +0 -3
- package/dist/pages/CreateOrderPage.js +1 -1
- package/package.json +1 -1
- package/src/movethewheels/MoveTheWheelsRoot.tsx +2 -46
- package/src/movethewheels/MoveTheWheelsRoutes.tsx +0 -16
- package/src/movethewheels/components/CustomerPicker.tsx +2 -2
- package/src/movethewheels/hooks/index.ts +0 -9
- package/src/movethewheels/pages/CreateOrderPage.tsx +1 -1
- package/dist/hooks/useImportExport.js +0 -27
- package/dist/hooks/useReports.js +0 -75
- package/dist/hooks/useSettings.js +0 -67
- package/dist/movethewheels/hooks/useImportExport.d.ts +0 -23
- package/dist/movethewheels/hooks/useReports.d.ts +0 -39
- package/dist/movethewheels/hooks/useSettings.d.ts +0 -17
- package/dist/movethewheels/pages/ImportExportPage.d.ts +0 -8
- package/dist/movethewheels/pages/MarketplacePage.d.ts +0 -12
- package/dist/movethewheels/pages/ReportsPage.d.ts +0 -8
- package/dist/movethewheels/pages/SettingsPage.d.ts +0 -8
- package/dist/node_modules/lucide-react/dist/esm/icons/bell.js +0 -11
- package/dist/node_modules/lucide-react/dist/esm/icons/file-chart-column-increasing.js +0 -26
- package/dist/node_modules/lucide-react/dist/esm/icons/file-spreadsheet.js +0 -30
- package/dist/node_modules/lucide-react/dist/esm/icons/globe.js +0 -20
- package/dist/node_modules/lucide-react/dist/esm/icons/settings.js +0 -13
- package/dist/node_modules/lucide-react/dist/esm/icons/shield.js +0 -8
- package/dist/node_modules/lucide-react/dist/esm/icons/store.js +0 -26
- package/dist/node_modules/lucide-react/dist/esm/icons/webhook.js +0 -18
- package/dist/pages/ImportExportPage.js +0 -245
- package/dist/pages/MarketplacePage.js +0 -23
- package/dist/pages/ReportsPage.js +0 -328
- package/dist/pages/SettingsPage.js +0 -404
- package/dist/utils/download.js +0 -22
- package/src/movethewheels/hooks/useImportExport.ts +0 -65
- package/src/movethewheels/hooks/useReports.ts +0 -154
- package/src/movethewheels/hooks/useSettings.ts +0 -124
- package/src/movethewheels/pages/ImportExportPage.tsx +0 -288
- package/src/movethewheels/pages/MarketplacePage.tsx +0 -36
- package/src/movethewheels/pages/ReportsPage.tsx +0 -379
- package/src/movethewheels/pages/SettingsPage.tsx +0 -548
- package/src/operations/wspace/movethewheels/fragments/core.withdrawn.graphql +0 -59
- package/src/operations/wspace/movethewheels/mutations/marketplace-reports.withdrawn.graphql +0 -93
- package/src/operations/wspace/movethewheels/queries/marketplace-reports.withdrawn.graphql +0 -58
|
@@ -1,379 +0,0 @@
|
|
|
1
|
-
/**
|
|
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.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { useState } from 'react';
|
|
10
|
-
import {
|
|
11
|
-
FileBarChart,
|
|
12
|
-
Download,
|
|
13
|
-
Calendar,
|
|
14
|
-
Filter,
|
|
15
|
-
Clock,
|
|
16
|
-
Plus,
|
|
17
|
-
ChevronLeft,
|
|
18
|
-
ChevronRight,
|
|
19
|
-
} from 'lucide-react';
|
|
20
|
-
import { PageLayout } from '../components/PageLayout';
|
|
21
|
-
import {
|
|
22
|
-
Card,
|
|
23
|
-
CardContent,
|
|
24
|
-
CardHeader,
|
|
25
|
-
CardTitle,
|
|
26
|
-
Button,
|
|
27
|
-
Badge,
|
|
28
|
-
Select,
|
|
29
|
-
SelectContent,
|
|
30
|
-
SelectItem,
|
|
31
|
-
SelectTrigger,
|
|
32
|
-
SelectValue,
|
|
33
|
-
Skeleton,
|
|
34
|
-
} from '../components/ui';
|
|
35
|
-
import { EmphasisPanel, IllustratedEmptyState, PagePurpose } from '@burdenoff/fe-libs/ui';
|
|
36
|
-
import { VolumeBar } from '../components/charts';
|
|
37
|
-
import { useReports, type CreateReportInput } from '../hooks/useReports';
|
|
38
|
-
import { useAnalytics } from '../hooks/useAnalytics';
|
|
39
|
-
import { useDemandForecast } from '../hooks/useDemandForecast';
|
|
40
|
-
import { UpcomingDemandPanel } from '../components/UpcomingDemandPanel';
|
|
41
|
-
import { formatDate } from '../utils/formatters';
|
|
42
|
-
import { downloadFile, downloadJson, recordToCsv, slugifyForFilename } from '../utils/download';
|
|
43
|
-
|
|
44
|
-
const REPORT_TYPES: { value: string; label: string }[] = [
|
|
45
|
-
{ value: 'operations', label: 'Operations Summary' },
|
|
46
|
-
{ value: 'finance', label: 'Financial Report' },
|
|
47
|
-
{ value: 'fleet', label: 'Fleet Utilization' },
|
|
48
|
-
{ value: 'customers', label: 'Customer Analytics' },
|
|
49
|
-
{ value: 'inventory', label: 'Inventory Status' },
|
|
50
|
-
];
|
|
51
|
-
|
|
52
|
-
interface Feedback {
|
|
53
|
-
tone: 'success' | 'error';
|
|
54
|
-
text: string;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const REPORTS_PAGE_SIZE = 10;
|
|
58
|
-
|
|
59
|
-
export default function ReportsPage() {
|
|
60
|
-
const [page, setPage] = useState(0);
|
|
61
|
-
const { reports, hasMore, isLoading, error, refetch, createReport, generateReport } = useReports({
|
|
62
|
-
limit: REPORTS_PAGE_SIZE,
|
|
63
|
-
offset: page * REPORTS_PAGE_SIZE,
|
|
64
|
-
});
|
|
65
|
-
const { analytics } = useAnalytics();
|
|
66
|
-
const previewData = analytics.trends.dailyOrders.slice(-7);
|
|
67
|
-
const {
|
|
68
|
-
laneForecast,
|
|
69
|
-
customerForecast,
|
|
70
|
-
laneVariance,
|
|
71
|
-
customerVariance,
|
|
72
|
-
isLoading: forecastLoading,
|
|
73
|
-
error: forecastError,
|
|
74
|
-
refetch: refetchForecast,
|
|
75
|
-
} = useDemandForecast(4, 8);
|
|
76
|
-
const [reportType, setReportType] = useState('operations');
|
|
77
|
-
const [busyId, setBusyId] = useState<string | null>(null);
|
|
78
|
-
const [creating, setCreating] = useState(false);
|
|
79
|
-
const [feedback, setFeedback] = useState<Feedback | null>(null);
|
|
80
|
-
|
|
81
|
-
const typeLabel = (value: string) =>
|
|
82
|
-
REPORT_TYPES.find((item) => item.value === value)?.label ?? value;
|
|
83
|
-
|
|
84
|
-
const handleCreate = async (schedule?: string) => {
|
|
85
|
-
setCreating(true);
|
|
86
|
-
setFeedback(null);
|
|
87
|
-
const input: CreateReportInput = {
|
|
88
|
-
name: `${typeLabel(reportType)}${schedule ? ' (Scheduled)' : ''}`,
|
|
89
|
-
type: reportType,
|
|
90
|
-
description: `${typeLabel(reportType)} generated from live operational data`,
|
|
91
|
-
...(schedule ? { schedule } : {}),
|
|
92
|
-
};
|
|
93
|
-
try {
|
|
94
|
-
await createReport(input);
|
|
95
|
-
setFeedback({
|
|
96
|
-
tone: 'success',
|
|
97
|
-
text: schedule ? 'Scheduled report created.' : 'Report created.',
|
|
98
|
-
});
|
|
99
|
-
} catch (createError) {
|
|
100
|
-
setFeedback({
|
|
101
|
-
tone: 'error',
|
|
102
|
-
text: createError instanceof Error ? createError.message : 'Could not create report.',
|
|
103
|
-
});
|
|
104
|
-
} finally {
|
|
105
|
-
setCreating(false);
|
|
106
|
-
}
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
const handleGenerate = async (id: string) => {
|
|
110
|
-
setBusyId(id);
|
|
111
|
-
setFeedback(null);
|
|
112
|
-
try {
|
|
113
|
-
const generated = await generateReport(id);
|
|
114
|
-
// Turn the freshly generated report into a downloadable file. Prefer a
|
|
115
|
-
// flat CSV of the result payload; fall back to the full JSON document so a
|
|
116
|
-
// file always lands even when the result is nested/empty.
|
|
117
|
-
const baseName = `${slugifyForFilename(generated.report.name)}-${generated.generatedAt.slice(
|
|
118
|
-
0,
|
|
119
|
-
10
|
|
120
|
-
)}`;
|
|
121
|
-
const csv = recordToCsv(generated.result ?? null);
|
|
122
|
-
if (csv) {
|
|
123
|
-
downloadFile(`${baseName}.csv`, csv, 'text/csv');
|
|
124
|
-
} else {
|
|
125
|
-
downloadJson(`${baseName}.json`, generated);
|
|
126
|
-
}
|
|
127
|
-
setFeedback({ tone: 'success', text: 'Report generated and downloaded.' });
|
|
128
|
-
} catch (genError) {
|
|
129
|
-
setFeedback({
|
|
130
|
-
tone: 'error',
|
|
131
|
-
text: genError instanceof Error ? genError.message : 'Could not generate report.',
|
|
132
|
-
});
|
|
133
|
-
} finally {
|
|
134
|
-
setBusyId(null);
|
|
135
|
-
}
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
const scheduledReports = reports.filter((report) => report.schedule);
|
|
139
|
-
|
|
140
|
-
return (
|
|
141
|
-
<PageLayout
|
|
142
|
-
title="Reports"
|
|
143
|
-
subtitle="Generate and schedule reports"
|
|
144
|
-
icon={<FileBarChart size={24} />}
|
|
145
|
-
headerActions={
|
|
146
|
-
<div className="flex gap-2">
|
|
147
|
-
<Select value={reportType} onValueChange={setReportType}>
|
|
148
|
-
<SelectTrigger className="w-[190px]">
|
|
149
|
-
<SelectValue />
|
|
150
|
-
</SelectTrigger>
|
|
151
|
-
<SelectContent>
|
|
152
|
-
{REPORT_TYPES.map((item) => (
|
|
153
|
-
<SelectItem key={item.value} value={item.value}>
|
|
154
|
-
{item.label}
|
|
155
|
-
</SelectItem>
|
|
156
|
-
))}
|
|
157
|
-
</SelectContent>
|
|
158
|
-
</Select>
|
|
159
|
-
<Button onClick={() => void handleCreate()} disabled={creating}>
|
|
160
|
-
<Plus size={16} /> Create Report
|
|
161
|
-
</Button>
|
|
162
|
-
</div>
|
|
163
|
-
}
|
|
164
|
-
>
|
|
165
|
-
<PagePurpose icon={<FileBarChart size={18} />}>
|
|
166
|
-
Turn your operational data into shareable summaries. Generate one-off reports on orders,
|
|
167
|
-
finance, fleet, customers or inventory, or schedule them to land in inboxes automatically —
|
|
168
|
-
so stakeholders always have the latest numbers without logging in.
|
|
169
|
-
</PagePurpose>
|
|
170
|
-
|
|
171
|
-
{feedback && (
|
|
172
|
-
<div
|
|
173
|
-
role="status"
|
|
174
|
-
className={`rounded-lg p-3 text-sm ${
|
|
175
|
-
feedback.tone === 'success'
|
|
176
|
-
? 'bg-status-success-bg-subtle text-status-success-text'
|
|
177
|
-
: 'bg-status-error-bg-subtle text-status-error-text'
|
|
178
|
-
}`}
|
|
179
|
-
>
|
|
180
|
-
{feedback.text}
|
|
181
|
-
</div>
|
|
182
|
-
)}
|
|
183
|
-
|
|
184
|
-
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
185
|
-
<div className="lg:col-span-2 space-y-4">
|
|
186
|
-
<h3 className="font-semibold">Available Reports</h3>
|
|
187
|
-
|
|
188
|
-
{isLoading ? (
|
|
189
|
-
<div className="space-y-3">
|
|
190
|
-
{Array.from({ length: 3 }).map((_, i) => (
|
|
191
|
-
<Skeleton key={i} className="h-20 w-full rounded-lg" />
|
|
192
|
-
))}
|
|
193
|
-
</div>
|
|
194
|
-
) : error ? (
|
|
195
|
-
<Card className="border-status-error-border">
|
|
196
|
-
<CardContent className="p-6 text-center space-y-3">
|
|
197
|
-
<p className="text-status-error-text font-medium">Couldn't load reports</p>
|
|
198
|
-
<p className="text-sm text-text-secondary">{error.message}</p>
|
|
199
|
-
<Button variant="outline" onClick={refetch}>
|
|
200
|
-
Retry
|
|
201
|
-
</Button>
|
|
202
|
-
</CardContent>
|
|
203
|
-
</Card>
|
|
204
|
-
) : reports.length === 0 ? (
|
|
205
|
-
<Card>
|
|
206
|
-
<CardContent className="p-0">
|
|
207
|
-
<IllustratedEmptyState
|
|
208
|
-
illustration="empty-data"
|
|
209
|
-
title="No reports yet"
|
|
210
|
-
description="Create your first report to turn live operational data into a shareable summary."
|
|
211
|
-
action={
|
|
212
|
-
<Button onClick={() => void handleCreate()} disabled={creating}>
|
|
213
|
-
<Plus size={16} className="mr-1.5" /> Create Report
|
|
214
|
-
</Button>
|
|
215
|
-
}
|
|
216
|
-
/>
|
|
217
|
-
</CardContent>
|
|
218
|
-
</Card>
|
|
219
|
-
) : (
|
|
220
|
-
reports.map((report) => (
|
|
221
|
-
<Card key={report.id}>
|
|
222
|
-
<CardContent className="p-4">
|
|
223
|
-
<div className="flex items-center justify-between">
|
|
224
|
-
<div className="flex items-center gap-3">
|
|
225
|
-
<div className="w-10 h-10 rounded-lg bg-action-primary-bg/10 flex items-center justify-center">
|
|
226
|
-
<FileBarChart size={20} className="text-primary" />
|
|
227
|
-
</div>
|
|
228
|
-
<div>
|
|
229
|
-
<p className="font-medium">{report.name}</p>
|
|
230
|
-
<p className="text-sm text-text-secondary">
|
|
231
|
-
{report.description ?? typeLabel(report.type)}
|
|
232
|
-
</p>
|
|
233
|
-
</div>
|
|
234
|
-
</div>
|
|
235
|
-
<div className="flex items-center gap-3">
|
|
236
|
-
<div className="text-right">
|
|
237
|
-
<Badge variant="outline">{report.schedule ?? 'On demand'}</Badge>
|
|
238
|
-
{report.lastGenerated && (
|
|
239
|
-
<p className="text-xs text-text-secondary mt-1">
|
|
240
|
-
Last: {formatDate(new Date(report.lastGenerated), 'short')}
|
|
241
|
-
</p>
|
|
242
|
-
)}
|
|
243
|
-
</div>
|
|
244
|
-
<Button
|
|
245
|
-
variant="outline"
|
|
246
|
-
size="sm"
|
|
247
|
-
aria-label={`Generate ${report.name}`}
|
|
248
|
-
disabled={busyId === report.id}
|
|
249
|
-
onClick={() => void handleGenerate(report.id)}
|
|
250
|
-
>
|
|
251
|
-
<Download size={14} />
|
|
252
|
-
</Button>
|
|
253
|
-
</div>
|
|
254
|
-
</div>
|
|
255
|
-
</CardContent>
|
|
256
|
-
</Card>
|
|
257
|
-
))
|
|
258
|
-
)}
|
|
259
|
-
|
|
260
|
-
{!isLoading && !error && (reports.length > 0 || page > 0) && (
|
|
261
|
-
<div className="flex flex-wrap items-center justify-between gap-3 pt-1">
|
|
262
|
-
<span className="text-sm text-text-secondary">
|
|
263
|
-
Showing {reports.length === 0 ? 0 : page * REPORTS_PAGE_SIZE + 1} to{' '}
|
|
264
|
-
{page * REPORTS_PAGE_SIZE + reports.length}
|
|
265
|
-
</span>
|
|
266
|
-
<div className="flex items-center gap-1">
|
|
267
|
-
<Button
|
|
268
|
-
variant="outline"
|
|
269
|
-
size="icon"
|
|
270
|
-
className="h-8 w-8"
|
|
271
|
-
aria-label="Previous page"
|
|
272
|
-
onClick={() => setPage((p) => Math.max(0, p - 1))}
|
|
273
|
-
disabled={page === 0}
|
|
274
|
-
>
|
|
275
|
-
<ChevronLeft className="h-4 w-4" />
|
|
276
|
-
</Button>
|
|
277
|
-
<span className="px-3 text-sm">Page {page + 1}</span>
|
|
278
|
-
<Button
|
|
279
|
-
variant="outline"
|
|
280
|
-
size="icon"
|
|
281
|
-
className="h-8 w-8"
|
|
282
|
-
aria-label="Next page"
|
|
283
|
-
onClick={() => setPage((p) => p + 1)}
|
|
284
|
-
disabled={!hasMore}
|
|
285
|
-
>
|
|
286
|
-
<ChevronRight className="h-4 w-4" />
|
|
287
|
-
</Button>
|
|
288
|
-
</div>
|
|
289
|
-
</div>
|
|
290
|
-
)}
|
|
291
|
-
</div>
|
|
292
|
-
|
|
293
|
-
<div className="space-y-6">
|
|
294
|
-
{/* Demand forecast (BOFF-4393) — shared with AnalyticsPage so reports
|
|
295
|
-
and analytics show the same planning signal. */}
|
|
296
|
-
<UpcomingDemandPanel
|
|
297
|
-
laneForecast={laneForecast}
|
|
298
|
-
customerForecast={customerForecast}
|
|
299
|
-
laneVariance={laneVariance}
|
|
300
|
-
customerVariance={customerVariance}
|
|
301
|
-
isLoading={forecastLoading}
|
|
302
|
-
error={forecastError}
|
|
303
|
-
onRetry={refetchForecast}
|
|
304
|
-
/>
|
|
305
|
-
|
|
306
|
-
{/* Data preview — a real snapshot (recent order volume) of what a
|
|
307
|
-
generated report draws from. Honest empty state when no data. */}
|
|
308
|
-
<Card>
|
|
309
|
-
<CardHeader>
|
|
310
|
-
<CardTitle className="text-base">Data preview</CardTitle>
|
|
311
|
-
</CardHeader>
|
|
312
|
-
<CardContent>
|
|
313
|
-
<p className="mb-2 text-xs text-text-secondary">Order volume · last 7 days</p>
|
|
314
|
-
<VolumeBar
|
|
315
|
-
xKey="date"
|
|
316
|
-
height={160}
|
|
317
|
-
showLegend={false}
|
|
318
|
-
data={previewData}
|
|
319
|
-
series={[{ dataKey: 'orders', name: 'Orders' }]}
|
|
320
|
-
emptyMessage="No order data to preview yet"
|
|
321
|
-
/>
|
|
322
|
-
</CardContent>
|
|
323
|
-
</Card>
|
|
324
|
-
|
|
325
|
-
{/* Scheduled Reports — the page's single emphasis zone */}
|
|
326
|
-
<EmphasisPanel className="space-y-3">
|
|
327
|
-
<h2 className="flex items-center gap-2 text-base font-semibold text-text-primary">
|
|
328
|
-
<Clock size={18} /> Scheduled Reports
|
|
329
|
-
</h2>
|
|
330
|
-
{scheduledReports.length === 0 ? (
|
|
331
|
-
<p className="text-sm text-text-secondary">
|
|
332
|
-
No scheduled reports yet. Schedule one to have it generated automatically.
|
|
333
|
-
</p>
|
|
334
|
-
) : (
|
|
335
|
-
scheduledReports.map((report) => (
|
|
336
|
-
<div key={report.id} className="p-3 border border-border-seam rounded-lg">
|
|
337
|
-
<p className="font-medium">{report.name}</p>
|
|
338
|
-
<p className="text-sm text-text-secondary">Schedule: {report.schedule}</p>
|
|
339
|
-
</div>
|
|
340
|
-
))
|
|
341
|
-
)}
|
|
342
|
-
<Button
|
|
343
|
-
variant="outline"
|
|
344
|
-
className="w-full"
|
|
345
|
-
disabled={creating}
|
|
346
|
-
onClick={() => void handleCreate('daily')}
|
|
347
|
-
>
|
|
348
|
-
Schedule New Report
|
|
349
|
-
</Button>
|
|
350
|
-
</EmphasisPanel>
|
|
351
|
-
|
|
352
|
-
<Card>
|
|
353
|
-
<CardHeader>
|
|
354
|
-
<CardTitle>Quick Actions</CardTitle>
|
|
355
|
-
</CardHeader>
|
|
356
|
-
<CardContent className="space-y-2">
|
|
357
|
-
<Button
|
|
358
|
-
variant="outline"
|
|
359
|
-
className="w-full justify-start"
|
|
360
|
-
disabled={creating}
|
|
361
|
-
onClick={() => void handleCreate('weekly')}
|
|
362
|
-
>
|
|
363
|
-
<Calendar size={16} /> Schedule Report
|
|
364
|
-
</Button>
|
|
365
|
-
<Button
|
|
366
|
-
variant="outline"
|
|
367
|
-
className="w-full justify-start"
|
|
368
|
-
disabled={creating}
|
|
369
|
-
onClick={() => void handleCreate()}
|
|
370
|
-
>
|
|
371
|
-
<Filter size={16} /> Custom Report
|
|
372
|
-
</Button>
|
|
373
|
-
</CardContent>
|
|
374
|
-
</Card>
|
|
375
|
-
</div>
|
|
376
|
-
</div>
|
|
377
|
-
</PageLayout>
|
|
378
|
-
);
|
|
379
|
-
}
|