@burdenoff/microfe-movethewheels 2026.626.2 → 2026.703.2

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.
Files changed (135) hide show
  1. package/dist/{AIAssistantPage-AvsMbunM.js → AIAssistantPage-C3cPsWt-.js} +100 -73
  2. package/dist/AnalyticsPage-Dz6g2MvK.js +215 -0
  3. package/dist/{CreateOrderPage-CjlnEtk3.js → CreateOrderPage-C5iwnjp4.js} +36 -67
  4. package/dist/CustomerDetailsPage-B6nwFyLO.js +406 -0
  5. package/dist/{CustomersPage-CGGfD4ki.js → CustomersPage-2Y05LVyh.js} +26 -28
  6. package/dist/{DashboardPage-D2HRqwg9.js → DashboardPage-BVjQi0EB.js} +85 -83
  7. package/dist/{DataTable-CD9ZhpLB.js → DataTable-DmciIKKM.js} +3 -3
  8. package/dist/DriverDetailsPage-CN3WQ0B9.js +507 -0
  9. package/dist/{DriversPage-DKiynISH.js → DriversPage-B0Zpx8OQ.js} +25 -27
  10. package/dist/{FinancePage-Vg8WPs38.js → FinancePage-DEDykSOP.js} +9 -9
  11. package/dist/FleetPage-DqkFi3s3.js +365 -0
  12. package/dist/FormError-BlOjnXOq.js +15 -0
  13. package/dist/ImportExportPage-DKJRCezl.js +290 -0
  14. package/dist/InventoryPage-DwfgCUQq.js +253 -0
  15. package/dist/{LiveTrackingPage-DQ1q4Id3.js → LiveTrackingPage-DCX-K-LR.js} +60 -56
  16. package/dist/MarketplacePage-CIQ97_zp.js +247 -0
  17. package/dist/{MetricCard-B7yMyvMX.js → MetricCard-BY0QrMD4.js} +1 -1
  18. package/dist/{OrderDetailsPage-CFASgsYX.js → OrderDetailsPage-DYiZ-9UK.js} +83 -85
  19. package/dist/{OrdersListPage-BHeT8S5o.js → OrdersListPage-BjDlCKJd.js} +31 -33
  20. package/dist/{PageLayout-D3N8MCTm.js → PageLayout-D4LDHJJq.js} +483 -422
  21. package/dist/ProductDetailsPage-C9uOiYLp.js +350 -0
  22. package/dist/{ProductsPage--xEpbQ8A.js → ProductsPage-C1G9h7Xd.js} +22 -24
  23. package/dist/ReportsPage-Zp53q3_8.js +284 -0
  24. package/dist/RouteDetailsPage-DUStXSf9.js +531 -0
  25. package/dist/RoutesPage-DMfoicM1.js +144 -0
  26. package/dist/SettingsPage-Bn-v0mbQ.js +421 -0
  27. package/dist/StatusBadge-BZurYHMy.js +152 -0
  28. package/dist/{TrackingPage-CcRc_dva.js → TrackingPage-W-jtdM3Y.js} +59 -61
  29. package/dist/VehicleDetailsPage-Bceo5oz3.js +410 -0
  30. package/dist/{VehiclesPage-UlxPWP9K.js → VehiclesPage-VBtOep91.js} +20 -22
  31. package/dist/WarehouseDetailsPage-L_6sWigM.js +493 -0
  32. package/dist/{WarehousesPage-CuTO-OPp.js → WarehousesPage-DCbXXXWE.js} +16 -18
  33. package/dist/{arrow-left-CBpZKkpq.js → arrow-left-n48Y-Wis.js} +1 -1
  34. package/dist/{box-CAaiElsb.js → box-D9Pal4HM.js} +1 -1
  35. package/dist/{chart-column-D1Ym15-S.js → chart-column-BFIJysDR.js} +1 -1
  36. package/dist/{chevron-right-BIFGi0UA.js → chevron-right-EMIIyyJ1.js} +1 -1
  37. package/dist/{circle-alert-DjmNo5_L.js → circle-alert-iY24lSlp.js} +1 -1
  38. package/dist/{circle-check-big-CjuPVo3_.js → circle-check-big-w0TrLKMF.js} +1 -1
  39. package/dist/{clock-BjkdnddD.js → clock-DJZtdl8u.js} +1 -1
  40. package/dist/{dollar-sign-IB4O0wuQ.js → dollar-sign-CgKQRz_e.js} +1 -1
  41. package/dist/{download-Dpa9vqoG.js → download-DRXxoiQR.js} +1 -1
  42. package/dist/{file-text-C8Uioy11.js → file-text-DWWY6_f5.js} +1 -1
  43. package/dist/{filter-Da5OZaHI.js → filter-DloLC2eE.js} +1 -1
  44. package/dist/index.js +186 -162
  45. package/dist/{map-_Per4sT9.js → map-CkJaULb2.js} +1 -1
  46. package/dist/{map-pin-BSaf5IDL.js → map-pin-BGiCvyCs.js} +1 -1
  47. package/dist/{navigation-DaTbrhw2.js → navigation-D36prnAn.js} +1 -1
  48. package/dist/{package-BE7U0wQE.js → package-BChUzpJS.js} +1 -1
  49. package/dist/{phone-DV4StZiA.js → phone-Ac9s3mo_.js} +1 -1
  50. package/dist/{plus-Dl1rLeJR.js → plus-Z76q-FZm.js} +1 -1
  51. package/dist/{refresh-cw-CWfERAit.js → refresh-cw-DKt6f5T6.js} +1 -1
  52. package/dist/{save-XH8vydOn.js → save-CojywNhG.js} +1 -1
  53. package/dist/{search-OxoIiL_C.js → search-CDGefqb3.js} +1 -1
  54. package/dist/{square-pen-hLIbFQFV.js → square-pen-CopE9GmB.js} +1 -1
  55. package/dist/{star-YsVV3MNQ.js → star-nHlLjDKR.js} +1 -1
  56. package/dist/{store-RCBl7HB-.js → store-BR9Voi5E.js} +1 -1
  57. package/dist/trash-2-BKhar6Hs.js +32 -0
  58. package/dist/{trending-up-C-85AwGT.js → trending-up-Dohgd4t_.js} +1 -1
  59. package/dist/{triangle-alert-BOhfGgmh.js → triangle-alert-i-XQL9Rd.js} +1 -1
  60. package/dist/{truck-CtDjNFbi.js → truck-C0v_hbLR.js} +1 -1
  61. package/dist/{useAnalytics-ph7eTIK6.js → useAnalytics-DS8n0xgg.js} +40 -40
  62. package/dist/useAssistant-D4v3oxD3.js +84 -0
  63. package/dist/{useCustomers-bS3a4ytk.js → useCustomers-BmTNGxiI.js} +24 -24
  64. package/dist/{useFleet-BdETplNE.js → useFleet-DKqXZ16m.js} +133 -109
  65. package/dist/useImportExport-DHr-SWSw.js +55 -0
  66. package/dist/{useInventory-Dwn18FPz.js → useInventory-DkyiVY_A.js} +95 -75
  67. package/dist/{useOrders-D_3_hGMp.js → useOrders-Cfk1GLiq.js} +42 -42
  68. package/dist/useReports-CTqZr-AB.js +78 -0
  69. package/dist/{useRoutes-v4aBaS-E.js → useRoutes-BQWO607N.js} +31 -31
  70. package/dist/useSettings-CkDH_-gM.js +81 -0
  71. package/dist/{useTracking-De2KIUNu.js → useTracking-B-va1kO3.js} +22 -22
  72. package/dist/{user-Dz5TJZFs.js → user-jyFU3lXk.js} +1 -1
  73. package/dist/{users-g2N0eL-Z.js → users-DZ-ilS6B.js} +1 -1
  74. package/dist/{warehouse-D3WWDeTX.js → warehouse-6m5kXMLu.js} +1 -1
  75. package/dist/{wrench-CmxY1n4I.js → wrench-CANXWxF-.js} +1 -1
  76. package/package.json +1 -1
  77. package/src/movethewheels/MoveTheWheelsRoutes.tsx +31 -3
  78. package/src/movethewheels/components/FormError.tsx +25 -0
  79. package/src/movethewheels/components/StatusBadge.tsx +18 -37
  80. package/src/movethewheels/constants/index.ts +67 -26
  81. package/src/movethewheels/hooks/index.ts +12 -0
  82. package/src/movethewheels/hooks/useAnalytics.ts +20 -6
  83. package/src/movethewheels/hooks/useAssistant.ts +74 -0
  84. package/src/movethewheels/hooks/useFleet.ts +170 -59
  85. package/src/movethewheels/hooks/useImportExport.ts +101 -0
  86. package/src/movethewheels/hooks/useInventory.ts +107 -0
  87. package/src/movethewheels/hooks/useReports.ts +158 -0
  88. package/src/movethewheels/hooks/useSettings.ts +120 -0
  89. package/src/movethewheels/pages/AIAssistantPage.tsx +53 -15
  90. package/src/movethewheels/pages/AnalyticsPage.tsx +67 -64
  91. package/src/movethewheels/pages/CustomerDetailsPage.tsx +154 -21
  92. package/src/movethewheels/pages/CustomersPage.tsx +1 -1
  93. package/src/movethewheels/pages/DashboardPage.tsx +42 -22
  94. package/src/movethewheels/pages/DriverDetailsPage.tsx +160 -3
  95. package/src/movethewheels/pages/DriversPage.tsx +6 -2
  96. package/src/movethewheels/pages/FleetPage.tsx +96 -38
  97. package/src/movethewheels/pages/ImportExportPage.tsx +174 -68
  98. package/src/movethewheels/pages/InventoryPage.tsx +66 -28
  99. package/src/movethewheels/pages/LiveTrackingPage.tsx +12 -3
  100. package/src/movethewheels/pages/MarketplacePage.tsx +87 -45
  101. package/src/movethewheels/pages/OrderDetailsPage.tsx +2 -2
  102. package/src/movethewheels/pages/ProductDetailsPage.tsx +146 -3
  103. package/src/movethewheels/pages/ProductsPage.tsx +5 -1
  104. package/src/movethewheels/pages/ReportsPage.tsx +194 -90
  105. package/src/movethewheels/pages/RouteDetailsPage.tsx +290 -36
  106. package/src/movethewheels/pages/RoutesPage.tsx +4 -9
  107. package/src/movethewheels/pages/SettingsPage.tsx +234 -56
  108. package/src/movethewheels/pages/TrackingPage.tsx +6 -6
  109. package/src/movethewheels/pages/VehicleDetailsPage.tsx +158 -2
  110. package/src/movethewheels/pages/VehiclesPage.tsx +5 -1
  111. package/src/movethewheels/pages/WarehouseDetailsPage.tsx +224 -2
  112. package/src/movethewheels/pages/WarehousesPage.tsx +5 -1
  113. package/src/movethewheels/providers/MoveTheWheelsProvider.tsx +7 -6
  114. package/src/movethewheels/store/movethewheelsStore.ts +1 -1
  115. package/dist/AnalyticsPage-CCLfHgQZ.js +0 -212
  116. package/dist/CustomerDetailsPage-I6cdDHUi.js +0 -265
  117. package/dist/DriverDetailsPage-rg_pGNuP.js +0 -330
  118. package/dist/FleetPage-DddXt8r8.js +0 -332
  119. package/dist/ImportExportPage-BW6qO3kc.js +0 -248
  120. package/dist/InventoryPage-rMGuaJN-.js +0 -222
  121. package/dist/MarketplacePage-DKrXAv62.js +0 -219
  122. package/dist/ProductDetailsPage-CFGcbaHo.js +0 -195
  123. package/dist/ReportsPage-Bb7_90rK.js +0 -235
  124. package/dist/RouteDetailsPage-CVtDVb1N.js +0 -282
  125. package/dist/RoutesPage-s57PtgcQ.js +0 -163
  126. package/dist/SettingsPage-BYNCBOpN.js +0 -298
  127. package/dist/StatusBadge-CijYQXtb.js +0 -209
  128. package/dist/VehicleDetailsPage-3YDMPsPs.js +0 -220
  129. package/dist/WarehouseDetailsPage-Db7KeN-d.js +0 -238
  130. package/dist/graphqlClient-CdJyR_ed.js +0 -55
  131. package/dist/navigation-BgnOfsVd.js +0 -6
  132. package/dist/route-Dp9JiN_E.js +0 -51
  133. package/dist/settings-3Rw5NwFF.js +0 -28
  134. package/src/movethewheels/constants/mockData.ts +0 -1342
  135. /package/dist/{formatters-_vJlC-47.js → formatters-CF2S2l1m.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-green-600 mt-1">
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-green-600 mt-1">
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>
@@ -2,15 +2,157 @@
2
2
  * Product Details Page
3
3
  */
4
4
 
5
+ import { useState } from 'react';
5
6
  import { useParams } from 'react-router-dom';
6
- import { Package, ArrowLeft, Edit, Box, DollarSign, Tag } from 'lucide-react';
7
+ import { Package, ArrowLeft, Edit, Box, DollarSign, Tag, Save } from 'lucide-react';
7
8
  import { PageLayout, LoadingState, ErrorState } from '../components/PageLayout';
8
- import { Card, CardContent, CardHeader, CardTitle, Button, Badge } from '../components/ui';
9
+ import {
10
+ Card,
11
+ CardContent,
12
+ CardHeader,
13
+ CardTitle,
14
+ Button,
15
+ Badge,
16
+ Input,
17
+ Label,
18
+ } from '../components/ui';
9
19
  import { EmphasisPanel, NextSteps, PagePurpose } from '@burdenoff/fe-libs/ui';
10
- import { useProduct } from '../hooks/useInventory';
20
+ import { useProduct, useInventory } from '../hooks/useInventory';
11
21
  import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
12
22
  import { formatCurrency } from '../utils/formatters';
13
23
  import { joinPath } from '../utils/navigation';
24
+ import { FormError } from '../components/FormError';
25
+
26
+ function NewProductForm() {
27
+ const { basePath, navigate } = useMoveTheWheels();
28
+ const { createProduct } = useInventory();
29
+ const [form, setForm] = useState({
30
+ name: '',
31
+ sku: '',
32
+ category: '',
33
+ brand: '',
34
+ weight: '',
35
+ });
36
+ const [submitting, setSubmitting] = useState(false);
37
+ const [error, setError] = useState<string | null>(null);
38
+
39
+ const goBack = () => navigate?.(joinPath(basePath, 'inventory/products'));
40
+
41
+ const handleSubmit = async (e: React.FormEvent) => {
42
+ e.preventDefault();
43
+ if (!form.name.trim() || !form.sku.trim()) {
44
+ setError('Product name and SKU are required.');
45
+ return;
46
+ }
47
+ setSubmitting(true);
48
+ setError(null);
49
+ try {
50
+ const weight = form.weight.trim() ? Number(form.weight) : undefined;
51
+ const created = await createProduct({
52
+ name: form.name.trim(),
53
+ sku: form.sku.trim(),
54
+ category: form.category.trim() || undefined,
55
+ brand: form.brand.trim() || undefined,
56
+ weight: weight !== undefined && !Number.isNaN(weight) ? weight : undefined,
57
+ });
58
+ navigate?.(joinPath(basePath, `inventory/products/${created.id}`));
59
+ } catch (err) {
60
+ console.error('Failed to create product:', err);
61
+ setError(err instanceof Error ? err.message : 'Failed to create product. Please try again.');
62
+ setSubmitting(false);
63
+ }
64
+ };
65
+
66
+ return (
67
+ <PageLayout
68
+ title="Add Product"
69
+ subtitle="Add an item to your catalog"
70
+ icon={<Package size={24} />}
71
+ headerActions={
72
+ <div className="flex gap-2">
73
+ <Button variant="outline" onClick={goBack} disabled={submitting}>
74
+ <ArrowLeft size={16} /> Cancel
75
+ </Button>
76
+ <Button onClick={handleSubmit} disabled={submitting}>
77
+ <Save size={16} /> {submitting ? 'Saving...' : 'Add Product'}
78
+ </Button>
79
+ </div>
80
+ }
81
+ >
82
+ <PagePurpose icon={<Package size={18} />}>
83
+ Add a product so it can be added to orders and stocked in warehouses. A name and SKU are all
84
+ it takes to start — category, brand and weight help with picking, pricing and routing.
85
+ </PagePurpose>
86
+
87
+ <form onSubmit={handleSubmit} className="max-w-2xl space-y-6">
88
+ <FormError message={error} />
89
+ <EmphasisPanel>
90
+ <h2 className="text-base font-semibold text-text-primary mb-4">Product Details</h2>
91
+ <div className="space-y-4">
92
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
93
+ <div className="space-y-2">
94
+ <Label htmlFor="name">
95
+ Name <span className="text-status-error-text">*</span>
96
+ </Label>
97
+ <Input
98
+ id="name"
99
+ value={form.name}
100
+ onChange={(e) => setForm((prev) => ({ ...prev, name: e.target.value }))}
101
+ placeholder="Wireless Headphones"
102
+ required
103
+ />
104
+ </div>
105
+ <div className="space-y-2">
106
+ <Label htmlFor="sku">
107
+ SKU <span className="text-status-error-text">*</span>
108
+ </Label>
109
+ <Input
110
+ id="sku"
111
+ value={form.sku}
112
+ onChange={(e) => setForm((prev) => ({ ...prev, sku: e.target.value }))}
113
+ placeholder="SKU-001"
114
+ required
115
+ />
116
+ </div>
117
+ </div>
118
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
119
+ <div className="space-y-2">
120
+ <Label htmlFor="category">Category</Label>
121
+ <Input
122
+ id="category"
123
+ value={form.category}
124
+ onChange={(e) => setForm((prev) => ({ ...prev, category: e.target.value }))}
125
+ placeholder="Electronics"
126
+ />
127
+ </div>
128
+ <div className="space-y-2">
129
+ <Label htmlFor="brand">Brand</Label>
130
+ <Input
131
+ id="brand"
132
+ value={form.brand}
133
+ onChange={(e) => setForm((prev) => ({ ...prev, brand: e.target.value }))}
134
+ placeholder="Acme"
135
+ />
136
+ </div>
137
+ <div className="space-y-2">
138
+ <Label htmlFor="weight">Weight (kg)</Label>
139
+ <Input
140
+ id="weight"
141
+ type="number"
142
+ min="0"
143
+ step="0.01"
144
+ value={form.weight}
145
+ onChange={(e) => setForm((prev) => ({ ...prev, weight: e.target.value }))}
146
+ placeholder="0.5"
147
+ />
148
+ </div>
149
+ </div>
150
+ </div>
151
+ </EmphasisPanel>
152
+ </form>
153
+ </PageLayout>
154
+ );
155
+ }
14
156
 
15
157
  export default function ProductDetailsPage() {
16
158
  const { productId } = useParams<{ productId: string }>();
@@ -20,6 +162,7 @@ export default function ProductDetailsPage() {
20
162
  const goBack = () => navigate?.(joinPath(basePath, 'inventory/products'));
21
163
  const goTo = (path: string) => navigate?.(joinPath(basePath, path));
22
164
 
165
+ if (!productId) return <NewProductForm />;
23
166
  if (isLoading) return <LoadingState message="Loading product details..." />;
24
167
  if (error || !product) return <ErrorState title="Product not found" onRetry={refetch} />;
25
168
 
@@ -98,7 +98,11 @@ export default function ProductsPage() {
98
98
  icon={<Package size={24} />}
99
99
  headerActions={
100
100
  <CTAOverflowMenu
101
- primary={{ label: 'Add Product', icon: <Plus size={16} />, onSelect: () => {} }}
101
+ primary={{
102
+ label: 'Add Product',
103
+ icon: <Plus size={16} />,
104
+ onSelect: () => goTo('inventory/products/new'),
105
+ }}
102
106
  actions={[
103
107
  { label: 'Refresh', icon: <RefreshCw size={16} />, onSelect: () => refetch() },
104
108
  { label: 'Export', icon: <Download size={16} />, onSelect: () => {} },
@@ -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 { FileBarChart, Download, Calendar, Filter, Clock } from 'lucide-react';
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
- name: 'Daily Operations Summary',
26
- description: 'Overview of daily deliveries and performance',
27
- frequency: 'Daily',
28
- lastGenerated: '2024-01-15',
29
- },
30
- {
31
- name: 'Weekly Performance Report',
32
- description: 'KPIs and metrics for the week',
33
- frequency: 'Weekly',
34
- lastGenerated: '2024-01-14',
35
- },
36
- {
37
- name: 'Monthly Financial Report',
38
- description: 'Revenue, expenses, and profit analysis',
39
- frequency: 'Monthly',
40
- lastGenerated: '2024-01-01',
41
- },
42
- {
43
- name: 'Fleet Utilization Report',
44
- description: 'Vehicle and driver utilization metrics',
45
- frequency: 'Weekly',
46
- lastGenerated: '2024-01-14',
47
- },
48
- {
49
- name: 'Customer Analytics Report',
50
- description: 'Customer satisfaction and retention',
51
- frequency: 'Monthly',
52
- lastGenerated: '2024-01-01',
53
- },
54
- {
55
- name: 'Inventory Status Report',
56
- description: 'Stock levels and movement analysis',
57
- frequency: 'Daily',
58
- lastGenerated: '2024-01-15',
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 defaultValue="30d">
75
- <SelectTrigger className="w-[150px]">
103
+ <Select value={reportType} onValueChange={setReportType}>
104
+ <SelectTrigger className="w-[190px]">
76
105
  <SelectValue />
77
106
  </SelectTrigger>
78
107
  <SelectContent>
79
- <SelectItem value="7d">Last 7 days</SelectItem>
80
- <SelectItem value="30d">Last 30 days</SelectItem>
81
- <SelectItem value="90d">Last 90 days</SelectItem>
82
- <SelectItem value="custom">Custom</SelectItem>
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
- <FileBarChart size={16} /> Create Report
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
- {reports.map((report) => (
101
- <Card key={report.name}>
102
- <CardContent className="p-4">
103
- <div className="flex items-center justify-between">
104
- <div className="flex items-center gap-3">
105
- <div className="w-10 h-10 rounded-lg bg-action-primary-bg/10 flex items-center justify-center">
106
- <FileBarChart size={20} className="text-primary" />
107
- </div>
108
- <div>
109
- <p className="font-medium">{report.name}</p>
110
- <p className="text-sm text-text-secondary">{report.description}</p>
111
- </div>
112
- </div>
113
- <div className="flex items-center gap-3">
114
- <div className="text-right">
115
- <Badge variant="outline">{report.frequency}</Badge>
116
- <p className="text-xs text-text-secondary mt-1">
117
- Last: {report.lastGenerated}
118
- </p>
119
- </div>
120
- <Button variant="outline" size="sm">
121
- <Download size={14} />
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&apos;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
- </div>
124
- </div>
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.map((report) => (
137
- <div key={report.name} className="p-3 border border-border-seam rounded-lg">
138
- <p className="font-medium">{report.name}</p>
139
- <p className="text-sm text-text-secondary">Next: {report.nextRun}</p>
140
- <p className="text-xs text-text-secondary">{report.recipients} recipients</p>
141
- </div>
142
- ))}
143
- <Button variant="outline" className="w-full">
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 variant="outline" className="w-full justify-start">
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 variant="outline" className="w-full justify-start">
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>