@object-ui/app-shell 3.3.0 → 3.3.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 (173) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/README.md +22 -2
  3. package/dist/chrome/CommandPalette.d.ts +16 -0
  4. package/dist/chrome/CommandPalette.js +88 -0
  5. package/dist/chrome/ConditionalAuthWrapper.d.ts +23 -0
  6. package/dist/chrome/ConditionalAuthWrapper.js +135 -0
  7. package/dist/chrome/ConsoleToaster.d.ts +11 -0
  8. package/dist/chrome/ConsoleToaster.js +28 -0
  9. package/dist/chrome/ErrorBoundary.d.ts +39 -0
  10. package/dist/chrome/ErrorBoundary.js +64 -0
  11. package/dist/chrome/KeyboardShortcutsDialog.d.ts +7 -0
  12. package/dist/chrome/KeyboardShortcutsDialog.js +63 -0
  13. package/dist/chrome/LoadingScreen.d.ts +12 -0
  14. package/dist/chrome/LoadingScreen.js +44 -0
  15. package/dist/chrome/OnboardingWalkthrough.d.ts +9 -0
  16. package/dist/chrome/OnboardingWalkthrough.js +11 -0
  17. package/dist/chrome/ThemeProvider.d.ts +13 -0
  18. package/dist/chrome/ThemeProvider.js +37 -0
  19. package/dist/chrome/index.d.ts +8 -0
  20. package/dist/chrome/index.js +8 -0
  21. package/dist/console/AppContent.d.ts +26 -0
  22. package/dist/console/AppContent.js +266 -0
  23. package/dist/console/ConsoleShell.d.ts +66 -0
  24. package/dist/console/ConsoleShell.js +97 -0
  25. package/dist/console/auth/AuthPageLayout.d.ts +9 -0
  26. package/dist/console/auth/AuthPageLayout.js +4 -0
  27. package/dist/console/auth/ForgotPasswordPage.d.ts +4 -0
  28. package/dist/console/auth/ForgotPasswordPage.js +23 -0
  29. package/dist/console/auth/LoginPage.d.ts +4 -0
  30. package/dist/console/auth/LoginPage.js +24 -0
  31. package/dist/console/auth/RegisterPage.d.ts +4 -0
  32. package/dist/console/auth/RegisterPage.js +29 -0
  33. package/dist/console/auth/index.d.ts +4 -0
  34. package/dist/console/auth/index.js +4 -0
  35. package/dist/console/home/AppCard.d.ts +14 -0
  36. package/dist/console/home/AppCard.js +36 -0
  37. package/dist/console/home/HomeLayout.d.ts +15 -0
  38. package/dist/console/home/HomeLayout.js +20 -0
  39. package/dist/console/home/HomePage.d.ts +18 -0
  40. package/dist/console/home/HomePage.js +54 -0
  41. package/dist/console/home/QuickActions.d.ts +9 -0
  42. package/dist/console/home/QuickActions.js +53 -0
  43. package/dist/console/home/RecentApps.d.ts +13 -0
  44. package/dist/console/home/RecentApps.js +32 -0
  45. package/dist/console/home/StarredApps.d.ts +13 -0
  46. package/dist/console/home/StarredApps.js +29 -0
  47. package/dist/console/home/index.d.ts +11 -0
  48. package/dist/console/home/index.js +11 -0
  49. package/dist/console/organizations/CreateWorkspaceDialog.d.ts +16 -0
  50. package/dist/console/organizations/CreateWorkspaceDialog.js +71 -0
  51. package/dist/console/organizations/OrganizationsLayout.d.ts +15 -0
  52. package/dist/console/organizations/OrganizationsLayout.js +20 -0
  53. package/dist/console/organizations/OrganizationsPage.d.ts +9 -0
  54. package/dist/console/organizations/OrganizationsPage.js +76 -0
  55. package/dist/console/organizations/index.d.ts +8 -0
  56. package/dist/console/organizations/index.js +8 -0
  57. package/dist/console/organizations/manage/AcceptInvitationPage.d.ts +7 -0
  58. package/dist/console/organizations/manage/AcceptInvitationPage.js +100 -0
  59. package/dist/console/organizations/manage/InvitationsPage.d.ts +6 -0
  60. package/dist/console/organizations/manage/InvitationsPage.js +107 -0
  61. package/dist/console/organizations/manage/InviteMemberDialog.d.ts +16 -0
  62. package/dist/console/organizations/manage/InviteMemberDialog.js +83 -0
  63. package/dist/console/organizations/manage/MembersPage.d.ts +6 -0
  64. package/dist/console/organizations/manage/MembersPage.js +89 -0
  65. package/dist/console/organizations/manage/OrganizationLayout.d.ts +8 -0
  66. package/dist/console/organizations/manage/OrganizationLayout.js +51 -0
  67. package/dist/console/organizations/manage/SettingsPage.d.ts +6 -0
  68. package/dist/console/organizations/manage/SettingsPage.js +141 -0
  69. package/dist/console/organizations/manage/orgContext.d.ts +5 -0
  70. package/dist/console/organizations/manage/orgContext.js +4 -0
  71. package/dist/context/FavoritesProvider.d.ts +50 -0
  72. package/dist/context/FavoritesProvider.js +129 -0
  73. package/dist/context/NavigationContext.d.ts +46 -0
  74. package/dist/context/NavigationContext.js +59 -0
  75. package/dist/context/index.d.ts +3 -0
  76. package/dist/context/index.js +2 -0
  77. package/dist/hooks/index.d.ts +7 -0
  78. package/dist/hooks/index.js +7 -0
  79. package/dist/hooks/useFavorites.d.ts +13 -0
  80. package/dist/hooks/useFavorites.js +13 -0
  81. package/dist/hooks/useMetadataService.d.ts +13 -0
  82. package/dist/hooks/useMetadataService.js +18 -0
  83. package/dist/hooks/useNavPins.d.ts +16 -0
  84. package/dist/hooks/useNavPins.js +72 -0
  85. package/dist/hooks/useNavigationSync.d.ts +82 -0
  86. package/dist/hooks/useNavigationSync.js +495 -0
  87. package/dist/hooks/useObjectActions.d.ts +38 -0
  88. package/dist/hooks/useObjectActions.js +92 -0
  89. package/dist/hooks/useRecentItems.d.ts +21 -0
  90. package/dist/hooks/useRecentItems.js +50 -0
  91. package/dist/hooks/useResponsiveSidebar.d.ts +8 -0
  92. package/dist/hooks/useResponsiveSidebar.js +28 -0
  93. package/dist/index.d.ts +29 -0
  94. package/dist/index.js +35 -0
  95. package/dist/layout/ActivityFeed.d.ts +22 -0
  96. package/dist/layout/ActivityFeed.js +57 -0
  97. package/dist/layout/AppHeader.d.ts +33 -0
  98. package/dist/layout/AppHeader.js +182 -0
  99. package/dist/layout/AppSidebar.d.ts +14 -0
  100. package/dist/layout/AppSidebar.js +217 -0
  101. package/dist/layout/AppSwitcher.d.ts +12 -0
  102. package/dist/layout/AppSwitcher.js +39 -0
  103. package/dist/layout/AuthPageLayout.d.ts +9 -0
  104. package/dist/layout/AuthPageLayout.js +4 -0
  105. package/dist/layout/ConnectionStatus.d.ts +14 -0
  106. package/dist/layout/ConnectionStatus.js +36 -0
  107. package/dist/layout/ConsoleLayout.d.ts +21 -0
  108. package/dist/layout/ConsoleLayout.js +71 -0
  109. package/dist/layout/LocaleSwitcher.d.ts +7 -0
  110. package/dist/layout/LocaleSwitcher.js +27 -0
  111. package/dist/layout/ModeToggle.d.ts +1 -0
  112. package/dist/layout/ModeToggle.js +10 -0
  113. package/dist/layout/UnifiedSidebar.d.ts +22 -0
  114. package/dist/layout/UnifiedSidebar.js +210 -0
  115. package/dist/layout/index.d.ts +11 -0
  116. package/dist/layout/index.js +10 -0
  117. package/dist/providers/AdapterProvider.d.ts +21 -0
  118. package/dist/providers/AdapterProvider.js +52 -0
  119. package/dist/providers/ExpressionProvider.d.ts +50 -0
  120. package/dist/providers/ExpressionProvider.js +67 -0
  121. package/dist/providers/MetadataProvider.d.ts +18 -0
  122. package/dist/providers/MetadataProvider.js +324 -0
  123. package/dist/services/MetadataService.d.ts +119 -0
  124. package/dist/services/MetadataService.js +211 -0
  125. package/dist/skeletons/SkeletonDashboard.d.ts +13 -0
  126. package/dist/skeletons/SkeletonDashboard.js +12 -0
  127. package/dist/skeletons/SkeletonDetail.d.ts +15 -0
  128. package/dist/skeletons/SkeletonDetail.js +12 -0
  129. package/dist/skeletons/SkeletonGrid.d.ts +15 -0
  130. package/dist/skeletons/SkeletonGrid.js +12 -0
  131. package/dist/skeletons/index.d.ts +3 -0
  132. package/dist/skeletons/index.js +3 -0
  133. package/dist/utils/getIcon.d.ts +10 -0
  134. package/dist/utils/getIcon.js +24 -0
  135. package/dist/utils/index.d.ts +33 -0
  136. package/dist/utils/index.js +62 -0
  137. package/dist/utils/metadataConverters.d.ts +78 -0
  138. package/dist/utils/metadataConverters.js +74 -0
  139. package/dist/views/ActionConfirmDialog.d.ts +22 -0
  140. package/dist/views/ActionConfirmDialog.js +22 -0
  141. package/dist/views/ActionParamDialog.d.ts +23 -0
  142. package/dist/views/ActionParamDialog.js +67 -0
  143. package/dist/views/DashboardView.d.ts +9 -0
  144. package/dist/views/DashboardView.js +357 -0
  145. package/dist/views/DesignDrawer.d.ts +28 -0
  146. package/dist/views/DesignDrawer.js +51 -0
  147. package/dist/views/MetadataInspector.d.ts +40 -0
  148. package/dist/views/MetadataInspector.js +69 -0
  149. package/dist/views/ObjectView.d.ts +14 -0
  150. package/dist/views/ObjectView.js +770 -0
  151. package/dist/views/PageView.d.ts +6 -0
  152. package/dist/views/PageView.js +49 -0
  153. package/dist/views/RecordDetailView.d.ts +14 -0
  154. package/dist/views/RecordDetailView.js +508 -0
  155. package/dist/views/ReportView.d.ts +4 -0
  156. package/dist/views/ReportView.js +284 -0
  157. package/dist/views/SearchResultsPage.d.ts +9 -0
  158. package/dist/views/SearchResultsPage.js +107 -0
  159. package/dist/views/ViewConfigPanel.d.ts +56 -0
  160. package/dist/views/ViewConfigPanel.js +91 -0
  161. package/dist/views/index.d.ts +11 -0
  162. package/dist/views/index.js +11 -0
  163. package/package.json +59 -11
  164. package/.turbo/turbo-build.log +0 -4
  165. package/src/components/AppShell.tsx +0 -31
  166. package/src/components/DashboardRenderer.tsx +0 -36
  167. package/src/components/FormRenderer.tsx +0 -71
  168. package/src/components/ObjectRenderer.tsx +0 -122
  169. package/src/components/PageRenderer.tsx +0 -28
  170. package/src/index.ts +0 -20
  171. package/src/types.ts +0 -78
  172. package/tsconfig.json +0 -20
  173. package/tsconfig.tsbuildinfo +0 -1
@@ -0,0 +1,284 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useEffect, useCallback, useMemo } from 'react';
3
+ import { useParams } from 'react-router-dom';
4
+ import { ReportViewer, ReportConfigPanel } from '@object-ui/plugin-report';
5
+ import { Empty, EmptyTitle, EmptyDescription } from '@object-ui/components';
6
+ import { Pencil, BarChart3, Loader2 } from 'lucide-react';
7
+ import { MetadataPanel, useMetadataInspector } from './MetadataInspector';
8
+ import { useMetadata } from '../providers/MetadataProvider';
9
+ import { useAdapter } from '../providers/AdapterProvider';
10
+ // Fallback fields when no schema is available
11
+ const FALLBACK_FIELDS = [
12
+ { value: 'month', label: 'Month', type: 'text' },
13
+ { value: 'revenue', label: 'Revenue', type: 'number' },
14
+ { value: 'count', label: 'Count', type: 'number' },
15
+ { value: 'region', label: 'Region', type: 'text' },
16
+ { value: 'product', label: 'Product', type: 'text' },
17
+ { value: 'source', label: 'Lead Source', type: 'text' },
18
+ { value: 'stage', label: 'Stage', type: 'text' },
19
+ { value: 'amount', label: 'Amount', type: 'number' },
20
+ ];
21
+ export function ReportView({ dataSource }) {
22
+ const { reportName } = useParams();
23
+ const { showDebug } = useMetadataInspector();
24
+ const adapter = useAdapter();
25
+ const [configPanelOpen, setConfigPanelOpen] = useState(false);
26
+ // Version counter — incremented on save to refresh the stable config reference
27
+ const [configVersion, setConfigVersion] = useState(0);
28
+ // Find report definition from API-driven metadata
29
+ const { reports, objects, loading, refresh } = useMetadata();
30
+ const initialReport = reports?.find((r) => r.name === reportName);
31
+ const [reportData, setReportData] = useState(initialReport);
32
+ // Local schema state for live preview — initialized from metadata
33
+ const [editSchema, setEditSchema] = useState(null);
34
+ // State for report runtime data
35
+ const [reportRuntimeData, setReportRuntimeData] = useState([]);
36
+ const [dataLoading, setDataLoading] = useState(false);
37
+ // Derive available fields from object schema for filter/sort editors
38
+ // Uses live editSchema when available to respond to objectName changes
39
+ const availableFields = useMemo(() => {
40
+ const liveReport = editSchema || reportData;
41
+ const objName = liveReport?.objectName || liveReport?.dataSource?.object || liveReport?.dataSource?.resource;
42
+ if (objName && objects?.length) {
43
+ const objDef = objects.find((o) => o.name === objName);
44
+ if (objDef?.fields) {
45
+ const fields = objDef.fields;
46
+ if (Array.isArray(fields)) {
47
+ return fields.map((f) => typeof f === 'string'
48
+ ? { value: f, label: f, type: 'text' }
49
+ : { value: f.name, label: f.label || f.name, type: f.type || 'text' });
50
+ }
51
+ return Object.entries(fields).map(([name, def]) => ({
52
+ value: name,
53
+ label: def.label || name,
54
+ type: def.type || 'text',
55
+ }));
56
+ }
57
+ }
58
+ return FALLBACK_FIELDS;
59
+ }, [editSchema, reportData, objects]);
60
+ // ---- Save helper --------------------------------------------------------
61
+ const saveSchema = useCallback(async (schema) => {
62
+ try {
63
+ if (adapter) {
64
+ await adapter.update('sys_report', reportName, schema);
65
+ refresh().catch(() => { });
66
+ }
67
+ }
68
+ catch (err) {
69
+ console.warn('[ReportView] Auto-save failed:', err);
70
+ }
71
+ }, [adapter, reportName, refresh]);
72
+ // ---- Open / close config panel ------------------------------------------
73
+ const handleOpenConfigPanel = useCallback(() => {
74
+ setEditSchema(reportData);
75
+ setConfigPanelOpen(true);
76
+ setConfigVersion((v) => v + 1);
77
+ }, [reportData]);
78
+ const handleCloseConfigPanel = useCallback(() => {
79
+ setConfigPanelOpen(false);
80
+ }, []);
81
+ // ---- Report config panel handlers --------------------------------------
82
+ // Stabilize config reference: only recompute after explicit actions (panel
83
+ // open, save). configVersion is incremented on those actions.
84
+ // This prevents useConfigDraft from resetting the draft on every live field
85
+ // change (same pattern as DashboardView's dashboardConfig).
86
+ const reportConfig = useMemo(() => editSchema || reportData,
87
+ // eslint-disable-next-line react-hooks/exhaustive-deps
88
+ [configVersion]);
89
+ const handleReportConfigSave = useCallback((config) => {
90
+ setEditSchema(config);
91
+ saveSchema(config);
92
+ setConfigVersion((v) => v + 1);
93
+ }, [saveSchema]);
94
+ const handleReportFieldChange = useCallback((field, value) => {
95
+ setEditSchema((prev) => ({ ...prev, [field]: value }));
96
+ }, []);
97
+ // Sync reportData when metadata finishes loading or reportName changes
98
+ useEffect(() => {
99
+ setReportData(initialReport);
100
+ }, [initialReport]);
101
+ // When metadata refreshes, discard stale editSchema if the config panel
102
+ // is already closed.
103
+ useEffect(() => {
104
+ if (!configPanelOpen) {
105
+ setEditSchema(null);
106
+ }
107
+ // eslint-disable-next-line react-hooks/exhaustive-deps
108
+ }, [initialReport]);
109
+ // Load report runtime data when report definition changes.
110
+ // Use the live editSchema (when available) so that config panel changes
111
+ // to objectName, filters, or limit are immediately reflected in the preview.
112
+ const dataFetchSource = editSchema || reportData;
113
+ // Memoize serialized dependency keys to avoid re-fetching on every render
114
+ const filtersKey = useMemo(() => JSON.stringify(dataFetchSource?.filters), [dataFetchSource?.filters]);
115
+ const dataSourceKey = useMemo(() => JSON.stringify(dataFetchSource?.dataSource), [dataFetchSource?.dataSource]);
116
+ const inlineDataKey = useMemo(() => JSON.stringify(dataFetchSource?.data), [dataFetchSource?.data]);
117
+ useEffect(() => {
118
+ if (!dataFetchSource || !dataSource) {
119
+ setReportRuntimeData([]);
120
+ return;
121
+ }
122
+ // If report has inline data, use it directly
123
+ if (dataFetchSource.data && Array.isArray(dataFetchSource.data)) {
124
+ setReportRuntimeData(dataFetchSource.data);
125
+ return;
126
+ }
127
+ // If report has a dataSource config, fetch data using it
128
+ if (dataFetchSource.dataSource) {
129
+ const fetchDataFromSource = async () => {
130
+ setDataLoading(true);
131
+ try {
132
+ // Use the dataSource configuration to fetch data
133
+ const resource = dataFetchSource.dataSource.object || dataFetchSource.dataSource.resource;
134
+ if (!resource) {
135
+ console.warn('ReportView: dataSource missing object/resource property');
136
+ setReportRuntimeData([]);
137
+ return;
138
+ }
139
+ const result = await dataSource.find(resource, {
140
+ $filter: dataFetchSource.dataSource.filter,
141
+ $orderby: dataFetchSource.dataSource.sort,
142
+ $top: dataFetchSource.dataSource.limit,
143
+ });
144
+ setReportRuntimeData(result.data || []);
145
+ }
146
+ catch (error) {
147
+ console.error('ReportView: Failed to load data from dataSource', error);
148
+ setReportRuntimeData([]);
149
+ }
150
+ finally {
151
+ setDataLoading(false);
152
+ }
153
+ };
154
+ fetchDataFromSource();
155
+ return;
156
+ }
157
+ // If report has an objectName, fetch data from that object
158
+ if (dataFetchSource.objectName) {
159
+ const fetchDataFromObject = async () => {
160
+ setDataLoading(true);
161
+ try {
162
+ const result = await dataSource.find(dataFetchSource.objectName, {
163
+ $filter: dataFetchSource.filters,
164
+ $orderby: dataFetchSource.sort,
165
+ $top: dataFetchSource.limit || 100, // Default limit to avoid fetching too much data
166
+ });
167
+ setReportRuntimeData(result.data || []);
168
+ }
169
+ catch (error) {
170
+ console.error('ReportView: Failed to load data from objectName', error);
171
+ setReportRuntimeData([]);
172
+ }
173
+ finally {
174
+ setDataLoading(false);
175
+ }
176
+ };
177
+ fetchDataFromObject();
178
+ return;
179
+ }
180
+ // No data source configured
181
+ setReportRuntimeData([]);
182
+ // eslint-disable-next-line react-hooks/exhaustive-deps
183
+ }, [
184
+ dataFetchSource?.objectName,
185
+ dataFetchSource?.limit,
186
+ filtersKey,
187
+ dataSourceKey,
188
+ inlineDataKey,
189
+ dataSource,
190
+ ]);
191
+ if (loading) {
192
+ return (_jsx("div", { className: "h-full flex items-center justify-center p-8", children: _jsx(Loader2, { className: "h-8 w-8 animate-spin text-muted-foreground" }) }));
193
+ }
194
+ if (!initialReport || !reportData) {
195
+ if (!loading && !initialReport) {
196
+ return (_jsx("div", { className: "h-full flex items-center justify-center p-8", children: _jsxs(Empty, { children: [_jsx("div", { className: "mx-auto mb-4 flex h-12 w-12 items-center justify-center rounded-full bg-muted", children: _jsx(BarChart3, { className: "h-6 w-6 text-muted-foreground" }) }), _jsx(EmptyTitle, { children: "Report Not Found" }), _jsxs(EmptyDescription, { children: ["The report \"", reportName, "\" could not be found. It may have been removed or renamed."] })] }) }));
197
+ }
198
+ return (_jsx("div", { className: "h-full flex items-center justify-center p-8", children: _jsx(Loader2, { className: "h-8 w-8 animate-spin text-muted-foreground" }) }));
199
+ }
200
+ // Wrap the report definition in the ReportViewer schema
201
+ // The ReportViewer expects a schema property which is of type ReportViewerSchema
202
+ // That schema has a 'report' property which is the actual report definition (ReportSchema)
203
+ // Map @objectstack/spec report format to @object-ui/types ReportSchema:
204
+ // - 'label' → 'title'
205
+ // - 'columns' (with 'field') → 'fields' (with 'name') + auto-generate 'sections'
206
+ const mapReportForViewer = (src) => {
207
+ const mapped = { ...src };
208
+ if (!mapped.title && mapped.label) {
209
+ mapped.title = mapped.label;
210
+ }
211
+ // Map spec 'columns' (field/label/aggregate) → ReportSchema 'fields' (name/label/aggregation)
212
+ if (!mapped.fields && Array.isArray(mapped.columns)) {
213
+ mapped.fields = mapped.columns.map((col) => ({
214
+ name: col.field || col.name,
215
+ label: col.label,
216
+ type: col.type,
217
+ format: col.format,
218
+ renderAs: col.renderAs,
219
+ colorMap: col.colorMap,
220
+ ...(col.aggregate ? { aggregation: col.aggregate, showInSummary: true } : {}),
221
+ }));
222
+ }
223
+ // Always regenerate sections from current fields so that live config
224
+ // changes (e.g. field picker updates) are immediately reflected in
225
+ // the preview. This fixes the linkage bug where config panel edits
226
+ // did not update the rendered report.
227
+ if (mapped.fields && Array.isArray(mapped.fields) && mapped.fields.length > 0) {
228
+ const hasSummaryFields = mapped.fields.some((f) => f.showInSummary || f.aggregation);
229
+ const reportType = mapped.reportType || 'tabular';
230
+ const sections = [];
231
+ if (reportType === 'summary' || hasSummaryFields) {
232
+ sections.push({ type: 'summary', title: 'Key Metrics' });
233
+ }
234
+ sections.push({
235
+ type: 'table',
236
+ title: 'Details',
237
+ columns: mapped.fields.map((f) => ({
238
+ name: f.name,
239
+ label: f.label,
240
+ type: f.type,
241
+ format: f.format,
242
+ renderAs: f.renderAs,
243
+ colorMap: f.colorMap,
244
+ })),
245
+ });
246
+ // Generate chart section from chartConfig if configured
247
+ if (mapped.chartConfig?.chartType) {
248
+ sections.push({
249
+ type: 'chart',
250
+ title: 'Chart',
251
+ chart: {
252
+ type: 'chart',
253
+ chartType: mapped.chartConfig.chartType,
254
+ xAxisField: mapped.chartConfig.xAxisField,
255
+ yAxisFields: mapped.chartConfig.yAxisFields,
256
+ },
257
+ });
258
+ }
259
+ // Preserve any user-defined chart sections from the original schema
260
+ if (Array.isArray(src.sections)) {
261
+ const chartSections = src.sections.filter((s) => s.type === 'chart' && !mapped.chartConfig?.chartType);
262
+ sections.push(...chartSections);
263
+ }
264
+ mapped.sections = sections;
265
+ }
266
+ else if (!mapped.sections) {
267
+ // No fields and no sections — leave empty
268
+ mapped.sections = [];
269
+ }
270
+ return mapped;
271
+ };
272
+ // Use live-edited schema for preview (persists after closing panel until metadata refreshes)
273
+ const previewReport = editSchema || reportData;
274
+ const reportForViewer = mapReportForViewer(previewReport);
275
+ const viewerSchema = {
276
+ type: 'report-viewer',
277
+ report: reportForViewer, // The report definition
278
+ data: reportRuntimeData, // Runtime data fetched from the data source
279
+ showToolbar: true,
280
+ allowExport: true,
281
+ loading: dataLoading, // Loading state for data fetching
282
+ };
283
+ return (_jsxs("div", { className: "flex flex-col h-full overflow-hidden bg-background", children: [_jsxs("div", { className: "flex flex-col sm:flex-row justify-between sm:items-center gap-3 sm:gap-4 p-4 sm:p-6 border-b shrink-0", children: [_jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("h1", { className: "text-lg sm:text-xl md:text-2xl font-bold tracking-tight truncate", children: previewReport.title || previewReport.label || 'Report Viewer' }), previewReport.description && (_jsx("p", { className: "text-sm text-muted-foreground mt-1 line-clamp-2", children: previewReport.description }))] }), _jsx("div", { className: "shrink-0 flex items-center gap-1.5", children: _jsxs("button", { type: "button", onClick: handleOpenConfigPanel, className: "inline-flex items-center gap-1.5 rounded-md border border-input bg-background px-2.5 py-1.5 text-xs font-medium text-muted-foreground shadow-sm hover:bg-accent hover:text-accent-foreground", "data-testid": "report-edit-button", children: [_jsx(Pencil, { className: "h-3.5 w-3.5" }), "Edit"] }) })] }), _jsxs("div", { className: "flex-1 overflow-hidden flex flex-col sm:flex-row relative", children: [_jsx("div", { className: "flex-1 min-w-0 overflow-auto p-4 sm:p-6 lg:p-8 bg-muted/5", children: _jsx("div", { className: "max-w-5xl mx-auto shadow-sm border rounded-lg sm:rounded-xl bg-background overflow-hidden min-h-150", children: _jsx(ReportViewer, { schema: viewerSchema }) }) }), _jsx(ReportConfigPanel, { open: configPanelOpen, onClose: handleCloseConfigPanel, config: reportConfig, onSave: handleReportConfigSave, onFieldChange: handleReportFieldChange, availableFields: availableFields }), _jsx(MetadataPanel, { open: showDebug, sections: [{ title: 'Report Configuration', data: previewReport }] })] })] }));
284
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * SearchResultsPage
3
+ *
4
+ * A dedicated search results page accessible via /apps/:appName/search?q=...
5
+ * Extends the command palette with a full-page search experience, showing
6
+ * objects, dashboards, pages, and reports matching the query.
7
+ * @module
8
+ */
9
+ export declare function SearchResultsPage(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,107 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * SearchResultsPage
4
+ *
5
+ * A dedicated search results page accessible via /apps/:appName/search?q=...
6
+ * Extends the command palette with a full-page search experience, showing
7
+ * objects, dashboards, pages, and reports matching the query.
8
+ * @module
9
+ */
10
+ import { useState, useMemo } from 'react';
11
+ import { useSearchParams, Link, useParams } from 'react-router-dom';
12
+ import { Input, Card, CardContent, Badge, } from '@object-ui/components';
13
+ import { Search, Database, LayoutDashboard, FileText, BarChart3, ArrowLeft, } from 'lucide-react';
14
+ import { useMetadata } from '../providers/MetadataProvider';
15
+ /** Flatten nested navigation groups into a flat list of leaf items */
16
+ function flattenNavigation(items) {
17
+ const result = [];
18
+ for (const item of items) {
19
+ if (item.type === 'group' && item.children) {
20
+ result.push(...flattenNavigation(item.children));
21
+ }
22
+ else {
23
+ result.push(item);
24
+ }
25
+ }
26
+ return result;
27
+ }
28
+ const TYPE_ICONS = {
29
+ object: Database,
30
+ dashboard: LayoutDashboard,
31
+ page: FileText,
32
+ report: BarChart3,
33
+ };
34
+ const TYPE_COLORS = {
35
+ object: 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200',
36
+ dashboard: 'bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-200',
37
+ page: 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200',
38
+ report: 'bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-200',
39
+ };
40
+ export function SearchResultsPage() {
41
+ const { appName } = useParams();
42
+ const [searchParams, setSearchParams] = useSearchParams();
43
+ const queryParam = searchParams.get('q') || '';
44
+ const [query, setQuery] = useState(queryParam);
45
+ const { apps: metadataApps } = useMetadata();
46
+ const apps = metadataApps || [];
47
+ const activeApp = apps.find((a) => a.name === appName) || apps[0];
48
+ const baseUrl = `/apps/${appName}`;
49
+ // Build searchable items from navigation
50
+ const allItems = useMemo(() => {
51
+ if (!activeApp)
52
+ return [];
53
+ const navItems = flattenNavigation(activeApp.navigation || []);
54
+ return navItems.map((item) => {
55
+ let href = '#';
56
+ if (item.type === 'object') {
57
+ href = `${baseUrl}/${item.objectName}`;
58
+ if (item.viewName)
59
+ href += `/view/${item.viewName}`;
60
+ }
61
+ else if (item.type === 'dashboard')
62
+ href = `${baseUrl}/dashboard/${item.dashboardName}`;
63
+ else if (item.type === 'page')
64
+ href = `${baseUrl}/page/${item.pageName}`;
65
+ else if (item.type === 'report')
66
+ href = `${baseUrl}/report/${item.reportName}`;
67
+ return {
68
+ id: item.id,
69
+ label: item.label || item.objectName || item.dashboardName || item.pageName || item.reportName || '',
70
+ href,
71
+ type: item.type,
72
+ description: item.description,
73
+ };
74
+ }).filter((item) => item.href !== '#');
75
+ }, [activeApp, baseUrl]);
76
+ // Filter results
77
+ const results = useMemo(() => {
78
+ if (!query.trim())
79
+ return allItems;
80
+ const lower = query.toLowerCase();
81
+ return allItems.filter(item => item.label.toLowerCase().includes(lower) ||
82
+ item.type.toLowerCase().includes(lower) ||
83
+ (item.description && item.description.toLowerCase().includes(lower)));
84
+ }, [allItems, query]);
85
+ const handleSearch = (value) => {
86
+ setQuery(value);
87
+ setSearchParams(value ? { q: value } : {});
88
+ };
89
+ // Group results by type
90
+ const grouped = useMemo(() => {
91
+ var _a;
92
+ const groups = {};
93
+ for (const r of results) {
94
+ (groups[_a = r.type] || (groups[_a] = [])).push(r);
95
+ }
96
+ return groups;
97
+ }, [results]);
98
+ return (_jsxs("div", { className: "flex flex-col gap-6 p-4 sm:p-6 max-w-4xl mx-auto", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsxs(Link, { to: baseUrl, className: "flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground transition-colors", children: [_jsx(ArrowLeft, { className: "h-4 w-4" }), "Back"] }), _jsx("h1", { className: "text-xl font-semibold", children: "Search" })] }), _jsxs("div", { className: "relative", children: [_jsx("label", { htmlFor: "search-results-input", className: "sr-only", children: "Search objects, dashboards, pages, reports" }), _jsx(Search, { className: "absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground", "aria-hidden": "true" }), _jsx(Input, { id: "search-results-input", value: query, onChange: (e) => handleSearch(e.target.value), placeholder: "Search objects, dashboards, pages, reports...", className: "pl-10 h-11 text-base", autoFocus: true })] }), _jsx("div", { className: "text-sm text-muted-foreground", children: query.trim()
99
+ ? `${results.length} result${results.length !== 1 ? 's' : ''} for "${query}"`
100
+ : `${allItems.length} items available` }), results.length === 0 ? (_jsxs("div", { className: "flex flex-col items-center justify-center py-12 text-center", children: [_jsx(Search, { className: "h-12 w-12 text-muted-foreground/30 mb-4" }), _jsx("p", { className: "text-lg font-medium text-muted-foreground", children: "No results found" }), _jsx("p", { className: "text-sm text-muted-foreground/80 mt-1", children: "Try adjusting your search terms" })] })) : (_jsx("div", { className: "space-y-6", children: Object.entries(grouped).map(([type, items]) => {
101
+ const TypeIcon = TYPE_ICONS[type] || Database;
102
+ return (_jsxs("div", { children: [_jsxs("h2", { className: "text-sm font-medium text-muted-foreground mb-2 flex items-center gap-1.5", children: [_jsx(TypeIcon, { className: "h-4 w-4" }), type.charAt(0).toUpperCase() + type.slice(1), "s", _jsx(Badge, { variant: "secondary", className: "ml-1 text-xs", children: items.length })] }), _jsx("div", { className: "grid gap-2", children: items.map(item => {
103
+ const ItemIcon = TYPE_ICONS[item.type] || Database;
104
+ return (_jsx(Link, { to: item.href, className: "rounded-lg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2", children: _jsx(Card, { className: "hover:bg-accent/50 transition-colors cursor-pointer", children: _jsxs(CardContent, { className: "flex items-center gap-3 p-3", children: [_jsx("div", { className: `flex h-8 w-8 items-center justify-center rounded ${TYPE_COLORS[item.type] || ''}`, children: _jsx(ItemIcon, { className: "h-4 w-4" }) }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("p", { className: "text-sm font-medium truncate", children: item.label }), item.description && (_jsx("p", { className: "text-xs text-muted-foreground truncate", children: item.description }))] }), _jsx(Badge, { variant: "outline", className: "text-xs shrink-0", children: item.type })] }) }) }, item.id));
105
+ }) })] }, type));
106
+ }) }))] }));
107
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * ViewConfigPanel — Schema-Driven
3
+ *
4
+ * Airtable-style right-side configuration panel for inline view editing.
5
+ * Migrated to Schema-Driven architecture using ConfigPanelRenderer +
6
+ * useConfigDraft, replacing ~1600 lines of imperative code with a
7
+ * declarative schema factory.
8
+ *
9
+ * All changes are buffered in a local draft state. Clicking Save commits
10
+ * the draft via onSave; Discard resets to the original activeView.
11
+ */
12
+ /** Editor panel types that can be opened from clickable rows */
13
+ export type EditorPanelType = 'columns' | 'filter' | 'sort';
14
+ export interface ViewConfigPanelProps {
15
+ /** Whether the panel is open */
16
+ open: boolean;
17
+ /** Close callback */
18
+ onClose: () => void;
19
+ /** Panel mode: "edit" for existing views, "create" for new views */
20
+ mode?: 'create' | 'edit';
21
+ /** The active view definition */
22
+ activeView: {
23
+ id: string;
24
+ label?: string;
25
+ type?: string;
26
+ columns?: string[];
27
+ filter?: any[];
28
+ sort?: any[];
29
+ description?: string;
30
+ showSearch?: boolean;
31
+ showFilters?: boolean;
32
+ showSort?: boolean;
33
+ allowExport?: boolean;
34
+ showDescription?: boolean;
35
+ addRecordViaForm?: boolean;
36
+ exportOptions?: any;
37
+ [key: string]: any;
38
+ };
39
+ /** The object definition */
40
+ objectDef: {
41
+ name: string;
42
+ label?: string;
43
+ description?: string;
44
+ fields?: Record<string, any>;
45
+ [key: string]: any;
46
+ };
47
+ /** Optional record count to display */
48
+ recordCount?: number;
49
+ /** Called when any view config field changes (local draft update) */
50
+ onViewUpdate?: (field: string, value: any) => void;
51
+ /** Called to persist all draft changes */
52
+ onSave?: (draft: Record<string, any>) => void;
53
+ /** Called when create-mode view is created */
54
+ onCreate?: (config: Record<string, any>) => void;
55
+ }
56
+ export declare function ViewConfigPanel({ open, onClose, mode, activeView, objectDef, onViewUpdate, onSave, onCreate }: ViewConfigPanelProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,91 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * ViewConfigPanel — Schema-Driven
4
+ *
5
+ * Airtable-style right-side configuration panel for inline view editing.
6
+ * Migrated to Schema-Driven architecture using ConfigPanelRenderer +
7
+ * useConfigDraft, replacing ~1600 lines of imperative code with a
8
+ * declarative schema factory.
9
+ *
10
+ * All changes are buffered in a local draft state. Clicking Save commits
11
+ * the draft via onSave; Discard resets to the original activeView.
12
+ */
13
+ import { useMemo, useEffect, useRef, useCallback } from 'react';
14
+ import { ConfigPanelRenderer, useConfigDraft } from '@object-ui/components';
15
+ import { useObjectTranslation } from '@object-ui/i18n';
16
+ import { buildViewConfigSchema, deriveFieldOptions, toFilterGroup, toSortItems, VIEW_TYPE_LABELS, } from '@object-ui/plugin-view';
17
+ export function ViewConfigPanel({ open, onClose, mode = 'edit', activeView, objectDef, onViewUpdate, onSave, onCreate }) {
18
+ const { t } = useObjectTranslation();
19
+ const panelRef = useRef(null);
20
+ // Default empty view for create mode
21
+ const defaultNewView = useMemo(() => ({
22
+ id: `view_${Date.now()}`,
23
+ label: t('console.objectView.newView'),
24
+ type: 'grid',
25
+ columns: [],
26
+ filter: [],
27
+ sort: [],
28
+ showSearch: true,
29
+ showFilters: true,
30
+ showSort: true,
31
+ }), []); // eslint-disable-line react-hooks/exhaustive-deps
32
+ // Stabilize source reference: only change when view ID changes.
33
+ // This prevents useConfigDraft from resetting on every parent re-render
34
+ // (same behavior as original useEffect with [activeView.id] dependency).
35
+ const stableKey = mode === 'create' ? 'create' : activeView.id;
36
+ const stableActiveView = useMemo(() => ({ ...activeView }), [stableKey]);
37
+ const effectiveActiveView = mode === 'create' ? defaultNewView : stableActiveView;
38
+ // Schema-driven draft state management
39
+ const { draft, isDirty, updateField, discard, undo, redo, canUndo, canRedo } = useConfigDraft(effectiveActiveView, {
40
+ mode,
41
+ onUpdate: onViewUpdate,
42
+ });
43
+ // Focus the panel when it opens for keyboard accessibility
44
+ useEffect(() => {
45
+ if (open && panelRef.current) {
46
+ panelRef.current.focus();
47
+ }
48
+ }, [open]);
49
+ // Derive field options from objectDef
50
+ const fieldOptions = useMemo(() => deriveFieldOptions(objectDef), [objectDef.fields]);
51
+ // Bridge: filter/sort → builder format
52
+ const filterGroupValue = useMemo(() => toFilterGroup(draft.filter), [draft.filter]);
53
+ const sortItemsValue = useMemo(() => toSortItems(draft.sort), [draft.sort]);
54
+ // Build schema
55
+ const schema = useMemo(() => buildViewConfigSchema({
56
+ t,
57
+ fieldOptions,
58
+ objectDef,
59
+ updateField,
60
+ filterGroupValue,
61
+ sortItemsValue,
62
+ }), [t, fieldOptions, objectDef, updateField, filterGroupValue, sortItemsValue]);
63
+ // Override breadcrumb with dynamic view type
64
+ const viewType = draft.type || 'grid';
65
+ const dynamicSchema = useMemo(() => ({
66
+ ...schema,
67
+ breadcrumb: [t('console.objectView.page'), VIEW_TYPE_LABELS[viewType] || viewType],
68
+ }), [schema, t, viewType]);
69
+ // Save/discard handlers with create mode support
70
+ const handleSave = useCallback(() => {
71
+ if (mode === 'create') {
72
+ onCreate?.(draft);
73
+ }
74
+ else {
75
+ onSave?.(draft);
76
+ }
77
+ // Clear dirty state after save while preserving draft values
78
+ discard();
79
+ }, [draft, onSave, onCreate, mode, discard]);
80
+ const handleDiscard = useCallback(() => {
81
+ if (mode === 'create') {
82
+ onClose();
83
+ return;
84
+ }
85
+ discard();
86
+ }, [discard, mode, onClose]);
87
+ const panelTitle = mode === 'create'
88
+ ? t('console.objectView.createView')
89
+ : t('console.objectView.configureView');
90
+ return (_jsx(ConfigPanelRenderer, { open: open, onClose: onClose, schema: dynamicSchema, draft: draft, isDirty: isDirty, onFieldChange: updateField, onSave: handleSave, onDiscard: handleDiscard, onUndo: undo, onRedo: redo, canUndo: canUndo, canRedo: canRedo, undoLabel: t('designer.undo'), redoLabel: t('designer.redo'), objectDef: objectDef, saveLabel: t('console.objectView.save'), discardLabel: t('console.objectView.discard'), panelRef: panelRef, role: "complementary", ariaLabel: panelTitle, tabIndex: -1, testId: "view-config-panel", closeTitle: t('console.objectView.closePanel'), footerTestId: "view-config-footer", saveTestId: "view-config-save", discardTestId: "view-config-discard", className: "transition-all overflow-hidden" }));
91
+ }
@@ -0,0 +1,11 @@
1
+ export { ObjectView } from './ObjectView';
2
+ export { RecordDetailView } from './RecordDetailView';
3
+ export { DashboardView } from './DashboardView';
4
+ export { PageView } from './PageView';
5
+ export { ReportView } from './ReportView';
6
+ export { SearchResultsPage } from './SearchResultsPage';
7
+ export { ActionConfirmDialog } from './ActionConfirmDialog';
8
+ export { ActionParamDialog } from './ActionParamDialog';
9
+ export { MetadataToggle, MetadataPanel } from './MetadataInspector';
10
+ export { ViewConfigPanel } from './ViewConfigPanel';
11
+ export { DesignDrawer } from './DesignDrawer';
@@ -0,0 +1,11 @@
1
+ export { ObjectView } from './ObjectView';
2
+ export { RecordDetailView } from './RecordDetailView';
3
+ export { DashboardView } from './DashboardView';
4
+ export { PageView } from './PageView';
5
+ export { ReportView } from './ReportView';
6
+ export { SearchResultsPage } from './SearchResultsPage';
7
+ export { ActionConfirmDialog } from './ActionConfirmDialog';
8
+ export { ActionParamDialog } from './ActionParamDialog';
9
+ export { MetadataToggle, MetadataPanel } from './MetadataInspector';
10
+ export { ViewConfigPanel } from './ViewConfigPanel';
11
+ export { DesignDrawer } from './DesignDrawer';