@burdenoff/microfe-movethewheels 2026.827.1 → 2026.830.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.
Files changed (39) hide show
  1. package/dist/MoveTheWheelsRoot.js +86 -83
  2. package/dist/MoveTheWheelsRoutes.js +107 -104
  3. package/dist/hooks/index.js +1 -0
  4. package/dist/hooks/useImportExport.js +22 -51
  5. package/dist/hooks/useReports.js +63 -73
  6. package/dist/hooks/useSettings.js +52 -44
  7. package/dist/index.js +38 -37
  8. package/dist/movethewheels/hooks/index.d.ts +1 -0
  9. package/dist/movethewheels/hooks/useImportExport.d.ts +10 -0
  10. package/dist/movethewheels/pages/MarketplacePage.d.ts +7 -4
  11. package/dist/movethewheels/shared/hooks/useTr.d.ts +16 -0
  12. package/dist/pages/AIAssistantPage.js +45 -43
  13. package/dist/pages/DashboardPage.js +6 -6
  14. package/dist/pages/FleetPage.js +7 -7
  15. package/dist/pages/InventoryPage.js +6 -6
  16. package/dist/pages/MarketplacePage.js +11 -432
  17. package/dist/pages/OrdersListPage.js +5 -5
  18. package/dist/pages/ReportsPage.js +120 -109
  19. package/dist/pages/SettingsPage.js +175 -154
  20. package/dist/pages/TrackingPage.js +5 -5
  21. package/dist/shared/hooks/useTr.js +12 -0
  22. package/package.json +2 -2
  23. package/src/movethewheels/MoveTheWheelsRoot.tsx +28 -24
  24. package/src/movethewheels/MoveTheWheelsRoutes.tsx +5 -2
  25. package/src/movethewheels/hooks/index.ts +3 -0
  26. package/src/movethewheels/hooks/useImportExport.ts +36 -72
  27. package/src/movethewheels/hooks/useReports.ts +73 -87
  28. package/src/movethewheels/hooks/useSettings.ts +50 -53
  29. package/src/movethewheels/pages/AIAssistantPage.tsx +50 -32
  30. package/src/movethewheels/pages/MarketplacePage.tsx +16 -539
  31. package/src/movethewheels/pages/ReportsPage.tsx +23 -0
  32. package/src/movethewheels/pages/SettingsPage.tsx +154 -70
  33. package/src/movethewheels/shared/hooks/useTr.ts +37 -0
  34. package/dist/generated/global-operations.d.ts +0 -1
  35. package/dist/generated/global-types.d.ts +0 -23526
  36. package/dist/node_modules/lucide-react/dist/esm/icons/chevron-down.js +0 -8
  37. package/dist/node_modules/lucide-react/dist/esm/icons/chevron-up.js +0 -8
  38. package/src/generated/global-operations.ts +0 -2
  39. package/src/generated/global-types.ts +0 -27259
@@ -30,6 +30,7 @@ import { MoveTheWheelsProvider } from './providers/MoveTheWheelsProvider';
30
30
  import { FeatureFlagProvider, useFeatureFlag } from '@burdenoff/fe-libs/shared/feature-flags';
31
31
  import type { NavItem, MoveTheWheelsRootProps } from './types';
32
32
  import { MoveTheWheelsRoutes } from './MoveTheWheelsRoutes';
33
+ import { useTr } from './hooks';
33
34
 
34
35
  /**
35
36
  * Registers the MoveTheWheels sidebar nav tree with the host shell.
@@ -43,6 +44,8 @@ const NavRegistrar: FC<Pick<MoveTheWheelsRootProps, 'registerNavItems' | 'basePa
43
44
  registerNavItems,
44
45
  basePath = '/',
45
46
  }) => {
47
+ const tr = useTr();
48
+
46
49
  // The backend has no ServiceProvider/ServiceOffering model (dropped by
47
50
  // migration 20260707000001_movethewheels_reuse_cleanup) — hide the
48
51
  // Marketplace nav entry until a serviceProviders/serviceOfferings resolver
@@ -62,7 +65,7 @@ const NavRegistrar: FC<Pick<MoveTheWheelsRootProps, 'registerNavItems' | 'basePa
62
65
 
63
66
  const movethewheelsNavItem: NavItem = {
64
67
  id: 'movethewheels',
65
- label: 'MoveTheWheels',
68
+ label: tr('movethewheels.page.title', 'MoveTheWheels'),
66
69
  path: basePath,
67
70
  icon: <Truck size={20} />,
68
71
  order: 25,
@@ -70,49 +73,49 @@ const NavRegistrar: FC<Pick<MoveTheWheelsRootProps, 'registerNavItems' | 'basePa
70
73
  children: [
71
74
  {
72
75
  id: 'mtw-dashboard',
73
- label: 'Dashboard',
76
+ label: tr('movethewheels.root.nav.dashboard', 'Dashboard'),
74
77
  path: basePath,
75
78
  icon: <LayoutDashboard size={16} />,
76
79
  order: 1,
77
80
  },
78
81
  {
79
82
  id: 'mtw-orders',
80
- label: 'Orders',
83
+ label: tr('movethewheels.root.nav.orders', 'Orders'),
81
84
  path: joinPath(basePath, 'orders'),
82
85
  icon: <Package size={16} />,
83
86
  order: 2,
84
87
  },
85
88
  {
86
89
  id: 'mtw-tracking',
87
- label: 'Tracking',
90
+ label: tr('movethewheels.root.nav.tracking', 'Tracking'),
88
91
  path: joinPath(basePath, 'tracking'),
89
92
  icon: <Map size={16} />,
90
93
  order: 3,
91
94
  },
92
95
  {
93
96
  id: 'mtw-fleet',
94
- label: 'Fleet',
97
+ label: tr('movethewheels.root.nav.fleet', 'Fleet'),
95
98
  path: joinPath(basePath, 'fleet'),
96
99
  icon: <Truck size={16} />,
97
100
  order: 4,
98
101
  children: [
99
102
  {
100
103
  id: 'mtw-fleet-overview',
101
- label: 'Overview',
104
+ label: tr('movethewheels.root.nav.overview', 'Overview'),
102
105
  path: joinPath(basePath, 'fleet'),
103
106
  icon: <LayoutDashboard size={14} />,
104
107
  order: 1,
105
108
  },
106
109
  {
107
110
  id: 'mtw-vehicles',
108
- label: 'Vehicles',
111
+ label: tr('movethewheels.root.nav.vehicles', 'Vehicles'),
109
112
  path: joinPath(basePath, 'fleet/vehicles'),
110
113
  icon: <Car size={14} />,
111
114
  order: 2,
112
115
  },
113
116
  {
114
117
  id: 'mtw-drivers',
115
- label: 'Drivers',
118
+ label: tr('movethewheels.root.nav.drivers', 'Drivers'),
116
119
  path: joinPath(basePath, 'fleet/drivers'),
117
120
  icon: <UserCheck size={14} />,
118
121
  order: 3,
@@ -121,28 +124,28 @@ const NavRegistrar: FC<Pick<MoveTheWheelsRootProps, 'registerNavItems' | 'basePa
121
124
  },
122
125
  {
123
126
  id: 'mtw-inventory',
124
- label: 'Inventory',
127
+ label: tr('movethewheels.root.nav.inventory', 'Inventory'),
125
128
  path: joinPath(basePath, 'inventory'),
126
129
  icon: <Warehouse size={16} />,
127
130
  order: 5,
128
131
  children: [
129
132
  {
130
133
  id: 'mtw-inventory-overview',
131
- label: 'Overview',
134
+ label: tr('movethewheels.root.nav.overview', 'Overview'),
132
135
  path: joinPath(basePath, 'inventory'),
133
136
  icon: <LayoutDashboard size={14} />,
134
137
  order: 1,
135
138
  },
136
139
  {
137
140
  id: 'mtw-products',
138
- label: 'Products',
141
+ label: tr('movethewheels.root.nav.products', 'Products'),
139
142
  path: joinPath(basePath, 'inventory/products'),
140
143
  icon: <Boxes size={14} />,
141
144
  order: 2,
142
145
  },
143
146
  {
144
147
  id: 'mtw-warehouses',
145
- label: 'Warehouses',
148
+ label: tr('movethewheels.root.nav.warehouses', 'Warehouses'),
146
149
  path: joinPath(basePath, 'inventory/warehouses'),
147
150
  icon: <Warehouse size={14} />,
148
151
  order: 3,
@@ -151,28 +154,28 @@ const NavRegistrar: FC<Pick<MoveTheWheelsRootProps, 'registerNavItems' | 'basePa
151
154
  },
152
155
  {
153
156
  id: 'mtw-customers',
154
- label: 'Customers',
157
+ label: tr('movethewheels.root.nav.customers', 'Customers'),
155
158
  path: joinPath(basePath, 'customers'),
156
159
  icon: <Users size={16} />,
157
160
  order: 6,
158
161
  },
159
162
  {
160
163
  id: 'mtw-routes',
161
- label: 'Routes',
164
+ label: tr('movethewheels.root.nav.routes', 'Routes'),
162
165
  path: joinPath(basePath, 'routes'),
163
166
  icon: <Route size={16} />,
164
167
  order: 7,
165
168
  },
166
169
  {
167
170
  id: 'mtw-analytics',
168
- label: 'Analytics',
171
+ label: tr('movethewheels.root.nav.analytics', 'Analytics'),
169
172
  path: joinPath(basePath, 'analytics'),
170
173
  icon: <BarChart3 size={16} />,
171
174
  order: 8,
172
175
  },
173
176
  {
174
177
  id: 'mtw-finance',
175
- label: 'Finance',
178
+ label: tr('movethewheels.root.nav.finance', 'Finance'),
176
179
  path: joinPath(basePath, 'finance'),
177
180
  icon: <DollarSign size={16} />,
178
181
  order: 9,
@@ -183,7 +186,7 @@ const NavRegistrar: FC<Pick<MoveTheWheelsRootProps, 'registerNavItems' | 'basePa
183
186
  ? [
184
187
  {
185
188
  id: 'mtw-marketplace',
186
- label: 'Marketplace',
189
+ label: tr('movethewheels.root.nav.marketplace', 'Marketplace'),
187
190
  path: joinPath(basePath, 'marketplace'),
188
191
  icon: <Store size={16} />,
189
192
  order: 10,
@@ -192,28 +195,28 @@ const NavRegistrar: FC<Pick<MoveTheWheelsRootProps, 'registerNavItems' | 'basePa
192
195
  : []),
193
196
  {
194
197
  id: 'mtw-import-export',
195
- label: 'Import/Export',
198
+ label: tr('movethewheels.root.nav.importExport', 'Import/Export'),
196
199
  path: joinPath(basePath, 'import-export'),
197
200
  icon: <Globe size={16} />,
198
201
  order: 11,
199
202
  },
200
203
  {
201
204
  id: 'mtw-reports',
202
- label: 'Reports',
205
+ label: tr('movethewheels.root.nav.reports', 'Reports'),
203
206
  path: joinPath(basePath, 'reports'),
204
207
  icon: <FileText size={16} />,
205
208
  order: 12,
206
209
  },
207
210
  {
208
211
  id: 'mtw-ai-assistant',
209
- label: 'AI Assistant',
212
+ label: tr('movethewheels.root.nav.aiAssistant', 'AI Assistant'),
210
213
  path: joinPath(basePath, 'ai-assistant'),
211
214
  icon: <Bot size={16} />,
212
215
  order: 13,
213
216
  },
214
217
  {
215
218
  id: 'mtw-settings',
216
- label: 'Settings',
219
+ label: tr('movethewheels.root.nav.settings', 'Settings'),
217
220
  path: joinPath(basePath, 'settings'),
218
221
  icon: <Settings size={16} />,
219
222
  order: 99,
@@ -226,7 +229,7 @@ const NavRegistrar: FC<Pick<MoveTheWheelsRootProps, 'registerNavItems' | 'basePa
226
229
 
227
230
  // Return cleanup function to unregister on unmount
228
231
  return cleanup;
229
- }, [registerNavItems, basePath, marketplaceEnabled]);
232
+ }, [registerNavItems, basePath, marketplaceEnabled, tr]);
230
233
 
231
234
  return null;
232
235
  };
@@ -254,6 +257,7 @@ const NavRegistrar: FC<Pick<MoveTheWheelsRootProps, 'registerNavItems' | 'basePa
254
257
  */
255
258
  export const MoveTheWheelsRoot: FC<MoveTheWheelsRootProps> = (props) => {
256
259
  const { registerNavItems, registerFooterItems, basePath = '/' } = props;
260
+ const tr = useTr();
257
261
 
258
262
  // Register footer items
259
263
  useEffect(() => {
@@ -262,7 +266,7 @@ export const MoveTheWheelsRoot: FC<MoveTheWheelsRootProps> = (props) => {
262
266
  const footerItems = [
263
267
  {
264
268
  id: 'mtw-status',
265
- label: 'Logistics Ready',
269
+ label: tr('movethewheels.root.footer.status', 'Logistics Ready'),
266
270
  type: 'status' as const,
267
271
  order: 20,
268
272
  section: 'left' as const,
@@ -271,7 +275,7 @@ export const MoveTheWheelsRoot: FC<MoveTheWheelsRootProps> = (props) => {
271
275
 
272
276
  const cleanup = registerFooterItems(footerItems);
273
277
  return cleanup;
274
- }, [registerFooterItems]);
278
+ }, [registerFooterItems, tr]);
275
279
 
276
280
  return (
277
281
  <FeatureFlagProvider workspaceId={null} gateway="global" defaultEnabled={true}>
@@ -9,6 +9,7 @@ import { Routes, Route, useNavigate } from 'react-router';
9
9
  import { lazy, Suspense } from 'react';
10
10
  import { NotFoundPage } from '@burdenoff/fe-libs/shared/pages';
11
11
  import { useMoveTheWheels } from './providers/MoveTheWheelsProvider';
12
+ import { useTr } from './hooks';
12
13
 
13
14
  /**
14
15
  * Catch-all 404 rendered inside the content area (previously a redirect to ""
@@ -18,9 +19,10 @@ import { useMoveTheWheels } from './providers/MoveTheWheelsProvider';
18
19
  function MoveTheWheelsNotFound() {
19
20
  const { navigate, basePath } = useMoveTheWheels();
20
21
  const routerNavigate = useNavigate();
22
+ const tr = useTr();
21
23
  return (
22
24
  <NotFoundPage
23
- homeLabel="Back to MoveTheWheels"
25
+ homeLabel={tr('movethewheels.root.notFound.homeLabel', 'Back to MoveTheWheels')}
24
26
  onGoHome={() => {
25
27
  if (navigate) {
26
28
  navigate(basePath || '/');
@@ -34,11 +36,12 @@ function MoveTheWheelsNotFound() {
34
36
 
35
37
  // Loading fallback
36
38
  function PageLoader() {
39
+ const tr = useTr();
37
40
  return (
38
41
  <div className="flex items-center justify-center min-h-[400px]">
39
42
  <div className="flex flex-col items-center gap-3">
40
43
  <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
41
- <p className="text-sm text-text-secondary">Loading...</p>
44
+ <p className="text-sm text-text-secondary">{tr('movethewheels.root.loading', 'Loading...')}</p>
42
45
  </div>
43
46
  </div>
44
47
  );
@@ -4,6 +4,9 @@
4
4
  * All custom hooks for data access and operations.
5
5
  */
6
6
 
7
+ // Shared i18n helper
8
+ export { useTr } from '../shared/hooks/useTr';
9
+
7
10
  // Orders
8
11
  export { useOrders, useOrder, useOrderStats } from './useOrders';
9
12
 
@@ -1,6 +1,4 @@
1
- import { useCallback, useMemo, useState } from 'react';
2
- import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
3
- import { executeGraphQL } from '../utils/graphqlClient';
1
+ import { useCallback, useState } from 'react';
4
2
 
5
3
  export interface ImportExportResult {
6
4
  success: boolean;
@@ -8,26 +6,6 @@ export interface ImportExportResult {
8
6
  payload?: Record<string, unknown> | null;
9
7
  }
10
8
 
11
- const EXPORT_DATA_MUTATION = `
12
- mutation MovethewheelsExportData($input: MovethewheelsExportDataInput!) {
13
- exportData(input: $input) {
14
- success
15
- message
16
- payload
17
- }
18
- }
19
- `;
20
-
21
- const IMPORT_DATA_MUTATION = `
22
- mutation MovethewheelsImportData($input: MovethewheelsImportDataInput!) {
23
- importData(input: $input) {
24
- success
25
- message
26
- payload
27
- }
28
- }
29
- `;
30
-
31
9
  interface UseImportExportResult {
32
10
  exportData: (resource: string, format?: string) => Promise<ImportExportResult>;
33
11
  importData: (resource: string, payload: Record<string, unknown>) => Promise<ImportExportResult>;
@@ -35,66 +13,52 @@ interface UseImportExportResult {
35
13
  error: Error | null;
36
14
  }
37
15
 
16
+ /**
17
+ * Import/export surface withdrawn from wspace-movethewheels-svc by migration
18
+ * 20260707000001_movethewheels_reuse_cleanup. The service now exposes
19
+ * `exportMtwEntities` (async job, returns MtwExportJob) but no import
20
+ * counterpart, so the old synchronous exportData/importData mutations are no
21
+ * longer resolvable. This hook keeps the same contract for consumers but
22
+ * rejects calls with a clear message until a replacement import/export flow is
23
+ * designed. The original operations are preserved in
24
+ * src/operations/wspace/movethewheels/mutations/marketplace-reports.withdrawn.graphql.
25
+ */
38
26
  export function useImportExport(): UseImportExportResult {
39
- const { apiGatewayUrl, authToken, workspaceToken, workspaceId, tenantId, currentUser } =
40
- useMoveTheWheels();
41
27
  const [isBusy, setIsBusy] = useState(false);
42
28
  const [error, setError] = useState<Error | null>(null);
43
29
 
44
- const graphQLOptions = useMemo(
45
- () => ({
46
- apiGatewayUrl,
47
- authToken,
48
- workspaceToken,
49
- workspaceId,
50
- tenantId,
51
- actorId: currentUser?.id,
52
- }),
53
- [apiGatewayUrl, authToken, workspaceToken, workspaceId, tenantId, currentUser?.id]
54
- );
30
+ const withBusy = useCallback(async <T>(fn: () => Promise<T>): Promise<T> => {
31
+ setIsBusy(true);
32
+ setError(null);
33
+ try {
34
+ return await fn();
35
+ } catch (err) {
36
+ const normalized = err instanceof Error ? err : new Error('Import/export failed');
37
+ setError(normalized);
38
+ throw normalized;
39
+ } finally {
40
+ setIsBusy(false);
41
+ }
42
+ }, []);
55
43
 
56
44
  const exportData = useCallback(
57
- async (resource: string, format?: string): Promise<ImportExportResult> => {
58
- setIsBusy(true);
59
- setError(null);
60
- try {
61
- const data = await executeGraphQL<{ exportData: ImportExportResult }>(
62
- graphQLOptions,
63
- EXPORT_DATA_MUTATION,
64
- { input: { resource, format } }
45
+ async (resource: string, _format?: string): Promise<ImportExportResult> =>
46
+ withBusy(async () => {
47
+ throw new Error(
48
+ `Export for "${resource}" is not available: the import/export surface was withdrawn from the service.`
65
49
  );
66
- return data.exportData;
67
- } catch (fetchError) {
68
- const normalized = fetchError instanceof Error ? fetchError : new Error('Export failed');
69
- setError(normalized);
70
- throw normalized;
71
- } finally {
72
- setIsBusy(false);
73
- }
74
- },
75
- [graphQLOptions]
50
+ }),
51
+ [withBusy]
76
52
  );
77
53
 
78
54
  const importData = useCallback(
79
- async (resource: string, payload: Record<string, unknown>): Promise<ImportExportResult> => {
80
- setIsBusy(true);
81
- setError(null);
82
- try {
83
- const data = await executeGraphQL<{ importData: ImportExportResult }>(
84
- graphQLOptions,
85
- IMPORT_DATA_MUTATION,
86
- { input: { resource, payload } }
55
+ async (resource: string, _payload: Record<string, unknown>): Promise<ImportExportResult> =>
56
+ withBusy(async () => {
57
+ throw new Error(
58
+ `Import for "${resource}" is not available: the import surface was withdrawn from the service.`
87
59
  );
88
- return data.importData;
89
- } catch (fetchError) {
90
- const normalized = fetchError instanceof Error ? fetchError : new Error('Import failed');
91
- setError(normalized);
92
- throw normalized;
93
- } finally {
94
- setIsBusy(false);
95
- }
96
- },
97
- [graphQLOptions]
60
+ }),
61
+ [withBusy]
98
62
  );
99
63
 
100
64
  return { exportData, importData, isBusy, error };
@@ -1,6 +1,5 @@
1
- import { useCallback, useEffect, useMemo, useState } from 'react';
1
+ import { useCallback, useEffect, useState } from 'react';
2
2
  import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
3
- import { executeGraphQL } from '../utils/graphqlClient';
4
3
 
5
4
  export interface MovethewheelsReport {
6
5
  id: string;
@@ -20,46 +19,6 @@ export interface GeneratedReport {
20
19
  report: MovethewheelsReport;
21
20
  }
22
21
 
23
- const REPORT_FIELDS = `
24
- id
25
- name
26
- description
27
- type
28
- status
29
- schedule
30
- lastGenerated
31
- createdAt
32
- updatedAt
33
- `;
34
-
35
- const LIST_REPORTS_QUERY = `
36
- query MovethewheelsReports($filter: MovethewheelsReportFilterInput, $pagination: PaginationInput) {
37
- movethewheelsReports(filter: $filter, pagination: $pagination) {
38
- ${REPORT_FIELDS}
39
- }
40
- }
41
- `;
42
-
43
- const CREATE_REPORT_MUTATION = `
44
- mutation CreateMovethewheelsReport($input: CreateMovethewheelsReportInput!) {
45
- createMovethewheelsReport(input: $input) {
46
- ${REPORT_FIELDS}
47
- }
48
- }
49
- `;
50
-
51
- const GENERATE_REPORT_MUTATION = `
52
- mutation GenerateMovethewheelsReport($input: GenerateMovethewheelsReportInput!) {
53
- generateMovethewheelsReport(input: $input) {
54
- generatedAt
55
- result
56
- report {
57
- ${REPORT_FIELDS}
58
- }
59
- }
60
- }
61
- `;
62
-
63
22
  export interface CreateReportInput {
64
23
  name: string;
65
24
  type: string;
@@ -87,44 +46,60 @@ interface UseReportsResult {
87
46
  ) => Promise<GeneratedReport>;
88
47
  }
89
48
 
49
+ function newId(): string {
50
+ if (typeof crypto !== 'undefined' && 'randomUUID' in crypto) {
51
+ return crypto.randomUUID();
52
+ }
53
+ return `${Date.now()}-${Math.random().toString(36).slice(2)}`;
54
+ }
55
+
56
+ function storageKey(workspaceId: string | undefined): string {
57
+ return `movethewheels:reports:${workspaceId ?? 'default'}`;
58
+ }
59
+
60
+ function readReports(workspaceId: string | undefined): MovethewheelsReport[] {
61
+ if (typeof window === 'undefined') return [];
62
+ try {
63
+ const raw = window.localStorage.getItem(storageKey(workspaceId));
64
+ if (!raw) return [];
65
+ const parsed = JSON.parse(raw) as MovethewheelsReport[];
66
+ return Array.isArray(parsed) ? parsed : [];
67
+ } catch {
68
+ return [];
69
+ }
70
+ }
71
+
72
+ function writeReports(workspaceId: string | undefined, reports: MovethewheelsReport[]): void {
73
+ if (typeof window === 'undefined') return;
74
+ try {
75
+ window.localStorage.setItem(storageKey(workspaceId), JSON.stringify(reports));
76
+ } catch {
77
+ // Ignore storage errors.
78
+ }
79
+ }
80
+
90
81
  export function useReports(options: UseReportsOptions = {}): UseReportsResult {
91
82
  const { limit = 100, offset = 0 } = options;
92
- const { apiGatewayUrl, authToken, workspaceToken, workspaceId, tenantId, currentUser } =
93
- useMoveTheWheels();
83
+ const { workspaceId } = useMoveTheWheels();
94
84
  const [reports, setReports] = useState<MovethewheelsReport[]>([]);
95
85
  const [hasMore, setHasMore] = useState(false);
96
86
  const [isLoading, setIsLoading] = useState(false);
97
87
  const [error, setError] = useState<Error | null>(null);
98
88
 
99
- const graphQLOptions = useMemo(
100
- () => ({
101
- apiGatewayUrl,
102
- authToken,
103
- workspaceToken,
104
- workspaceId,
105
- tenantId,
106
- actorId: currentUser?.id,
107
- }),
108
- [apiGatewayUrl, authToken, workspaceToken, workspaceId, tenantId, currentUser?.id]
109
- );
110
-
111
- const load = useCallback(async () => {
89
+ const load = useCallback(() => {
112
90
  setIsLoading(true);
113
91
  setError(null);
114
92
  try {
115
- const data = await executeGraphQL<{ movethewheelsReports: MovethewheelsReport[] }>(
116
- graphQLOptions,
117
- LIST_REPORTS_QUERY,
118
- { pagination: { limit, offset } }
119
- );
120
- setHasMore(data.movethewheelsReports.length === limit);
121
- setReports(data.movethewheelsReports);
122
- } catch (fetchError) {
123
- setError(fetchError instanceof Error ? fetchError : new Error('Failed to load reports'));
93
+ const all = readReports(workspaceId);
94
+ const page = all.slice(offset, offset + limit);
95
+ setHasMore(page.length === limit);
96
+ setReports(page);
97
+ } catch (loadError) {
98
+ setError(loadError instanceof Error ? loadError : new Error('Failed to load reports'));
124
99
  } finally {
125
100
  setIsLoading(false);
126
101
  }
127
- }, [graphQLOptions, limit, offset]);
102
+ }, [workspaceId, limit, offset]);
128
103
 
129
104
  useEffect(() => {
130
105
  void load();
@@ -136,33 +111,44 @@ export function useReports(options: UseReportsOptions = {}): UseReportsResult {
136
111
 
137
112
  const createReport = useCallback(
138
113
  async (input: CreateReportInput): Promise<MovethewheelsReport> => {
139
- const data = await executeGraphQL<{ createMovethewheelsReport: MovethewheelsReport }>(
140
- graphQLOptions,
141
- CREATE_REPORT_MUTATION,
142
- { input }
143
- );
144
- const created = data.createMovethewheelsReport;
145
- setReports((previous) => [created, ...previous]);
114
+ const now = new Date().toISOString();
115
+ const created: MovethewheelsReport = {
116
+ id: newId(),
117
+ name: input.name,
118
+ description: input.description ?? null,
119
+ type: input.type,
120
+ status: input.status ?? 'ready',
121
+ schedule: input.schedule ?? null,
122
+ lastGenerated: null,
123
+ createdAt: now,
124
+ updatedAt: now,
125
+ };
126
+ setReports((previous) => {
127
+ const next = [created, ...previous];
128
+ writeReports(workspaceId, next);
129
+ return next;
130
+ });
146
131
  return created;
147
132
  },
148
- [graphQLOptions]
133
+ [workspaceId]
149
134
  );
150
135
 
151
136
  const generateReport = useCallback(
152
- async (reportId: string, parameters?: Record<string, unknown>): Promise<GeneratedReport> => {
153
- const data = await executeGraphQL<{ generateMovethewheelsReport: GeneratedReport }>(
154
- graphQLOptions,
155
- GENERATE_REPORT_MUTATION,
156
- { input: { reportId, parameters } }
157
- );
158
- const generated = data.generateMovethewheelsReport;
159
- setReports((previous) =>
160
- previous.map((item) => (item.id === reportId ? generated.report : item))
137
+ async (_reportId: string, _parameters?: Record<string, unknown>): Promise<GeneratedReport> => {
138
+ throw new Error(
139
+ 'Report generation is not available: the movethewheelsReports surface was withdrawn from the service.'
161
140
  );
162
- return generated;
163
141
  },
164
- [graphQLOptions]
142
+ []
165
143
  );
166
144
 
167
- return { reports, hasMore, isLoading, error, refetch, createReport, generateReport };
145
+ return {
146
+ reports,
147
+ hasMore,
148
+ isLoading,
149
+ error,
150
+ refetch,
151
+ createReport,
152
+ generateReport,
153
+ };
168
154
  }