@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,357 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Dashboard View Component
4
+ * Renders a dashboard based on the dashboardName parameter.
5
+ * Edit mode shows an inline config panel (DashboardConfigPanel / WidgetConfigPanel)
6
+ * on the right side, following the same pattern as ListView.
7
+ */
8
+ import { useState, useEffect, useCallback, useMemo } from 'react';
9
+ import { useParams } from 'react-router-dom';
10
+ import { DashboardRenderer, DashboardConfigPanel, WidgetConfigPanel, } from '@object-ui/plugin-dashboard';
11
+ import { ModalForm } from '@object-ui/plugin-form';
12
+ import { toast } from 'sonner';
13
+ import { Empty, EmptyTitle, EmptyDescription, Button, Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, } from '@object-ui/components';
14
+ import { LayoutDashboard, Pencil, TrendingUp, BarChart3, LineChart, PieChart, Table2, LayoutGrid, Plus, Trash2, } from 'lucide-react';
15
+ import { MetadataPanel, useMetadataInspector } from './MetadataInspector';
16
+ import { SkeletonDashboard } from '../skeletons';
17
+ import { useMetadata } from '../providers/MetadataProvider';
18
+ import { resolveI18nLabel } from '../utils';
19
+ import { useAdapter } from '../providers/AdapterProvider';
20
+ import { useObjectTranslation } from '@object-ui/i18n';
21
+ // ---------------------------------------------------------------------------
22
+ // Widget type palette for the add-widget toolbar
23
+ // ---------------------------------------------------------------------------
24
+ const WIDGET_TYPES = [
25
+ { type: 'metric', label: 'KPI Metric', Icon: TrendingUp },
26
+ { type: 'bar', label: 'Bar Chart', Icon: BarChart3 },
27
+ { type: 'line', label: 'Line Chart', Icon: LineChart },
28
+ { type: 'pie', label: 'Pie Chart', Icon: PieChart },
29
+ { type: 'table', label: 'Table', Icon: Table2 },
30
+ { type: 'grid', label: 'Grid', Icon: LayoutGrid },
31
+ ];
32
+ let widgetCounter = 0;
33
+ function createWidgetId() {
34
+ widgetCounter += 1;
35
+ return `widget_${Date.now()}_${widgetCounter}`;
36
+ }
37
+ // ---------------------------------------------------------------------------
38
+ // Helpers
39
+ // ---------------------------------------------------------------------------
40
+ /** Ensure every widget in the schema has a unique id. */
41
+ function ensureWidgetIds(schema) {
42
+ if (!schema.widgets?.length)
43
+ return schema;
44
+ const needsFix = schema.widgets.some((w) => !w.id);
45
+ if (!needsFix)
46
+ return schema;
47
+ return {
48
+ ...schema,
49
+ widgets: schema.widgets.map((w) => (w.id ? w : { ...w, id: createWidgetId() })),
50
+ };
51
+ }
52
+ /** Resolve a human-friendly default title for a new widget type. */
53
+ function defaultWidgetTitle(type) {
54
+ const entry = WIDGET_TYPES.find((t) => t.type === type);
55
+ return entry ? `New ${entry.label}` : 'New Widget';
56
+ }
57
+ // ---------------------------------------------------------------------------
58
+ // Helpers: flatten / unflatten widget config for WidgetConfigPanel
59
+ // ---------------------------------------------------------------------------
60
+ function flattenWidgetConfig(widget) {
61
+ return {
62
+ title: widget.title ?? '',
63
+ description: widget.description ?? '',
64
+ type: widget.type ?? 'metric',
65
+ object: widget.object ?? '',
66
+ categoryField: widget.categoryField ?? '',
67
+ valueField: widget.valueField ?? '',
68
+ aggregate: widget.aggregate ?? 'count',
69
+ layoutW: widget.layout?.w ?? 1,
70
+ layoutH: widget.layout?.h ?? 1,
71
+ colorVariant: widget.colorVariant ?? 'default',
72
+ actionUrl: widget.actionUrl ?? '',
73
+ };
74
+ }
75
+ function unflattenWidgetConfig(config, base) {
76
+ return {
77
+ title: config.title,
78
+ description: config.description,
79
+ type: config.type,
80
+ object: config.object,
81
+ categoryField: config.categoryField,
82
+ valueField: config.valueField,
83
+ aggregate: config.aggregate,
84
+ layout: { ...(base.layout || {}), w: config.layoutW, h: config.layoutH },
85
+ colorVariant: config.colorVariant,
86
+ actionUrl: config.actionUrl,
87
+ };
88
+ }
89
+ function extractDashboardConfig(schema) {
90
+ return {
91
+ columns: schema.columns ?? 3,
92
+ gap: schema.gap ?? 4,
93
+ rowHeight: String(schema.rowHeight ?? '120'),
94
+ refreshInterval: String(schema.refreshInterval ?? '0'),
95
+ title: schema.title ?? '',
96
+ showDescription: schema.showDescription ?? true,
97
+ theme: schema.theme ?? 'auto',
98
+ };
99
+ }
100
+ // ---------------------------------------------------------------------------
101
+ // Component
102
+ // ---------------------------------------------------------------------------
103
+ export function DashboardView({ dataSource }) {
104
+ const { dashboardName } = useParams();
105
+ const { showDebug } = useMetadataInspector();
106
+ const adapter = useAdapter();
107
+ const { t } = useObjectTranslation();
108
+ const [isLoading, setIsLoading] = useState(true);
109
+ const [configPanelOpen, setConfigPanelOpen] = useState(false);
110
+ const [selectedWidgetId, setSelectedWidgetId] = useState(null);
111
+ // Version counter — incremented on save to refresh the stable config reference
112
+ const [configVersion, setConfigVersion] = useState(0);
113
+ // Modal state for header action buttons that request a modal (e.g. New Opportunity)
114
+ const [modalState, setModalState] = useState(null);
115
+ const closeModal = useCallback((result) => {
116
+ setModalState((curr) => {
117
+ if (curr)
118
+ curr.resolve(result);
119
+ return null;
120
+ });
121
+ }, []);
122
+ const modalHandler = useCallback((schema) => new Promise((resolve) => {
123
+ // Normalize string schema (e.g. action.target = 'opportunity') to a
124
+ // ModalForm-compatible descriptor so header `modal` actions like
125
+ // { actionType: 'modal', actionUrl: 'opportunity' } open the create
126
+ // form for that object.
127
+ const normalized = typeof schema === 'string'
128
+ ? { objectName: schema, mode: 'create' }
129
+ : schema;
130
+ setModalState({ schema: normalized, resolve });
131
+ }), []);
132
+ const scriptHandlers = useMemo(() => ({
133
+ export_dashboard_pdf: async () => {
134
+ toast.info('Preparing PDF export…');
135
+ try {
136
+ window.print();
137
+ return { success: true };
138
+ }
139
+ catch (err) {
140
+ toast.error(`Export failed: ${err?.message || String(err)}`);
141
+ return { success: false, error: err?.message || String(err) };
142
+ }
143
+ },
144
+ forecast_dashboard: async () => {
145
+ toast.info('Forecast view coming soon');
146
+ return { success: true };
147
+ },
148
+ }), []);
149
+ useEffect(() => {
150
+ setIsLoading(true);
151
+ setEditSchema(null);
152
+ setConfigPanelOpen(false);
153
+ setSelectedWidgetId(null);
154
+ queueMicrotask(() => setIsLoading(false));
155
+ }, [dashboardName]);
156
+ const { dashboards, objects: metadataObjects, refresh } = useMetadata();
157
+ const dashboard = dashboards?.find((d) => d.name === dashboardName);
158
+ // Local schema state for live preview — initialized from metadata
159
+ const [editSchema, setEditSchema] = useState(null);
160
+ // When metadata refreshes (dashboard reference changes), discard stale
161
+ // editSchema if the config panel is already closed.
162
+ useEffect(() => {
163
+ if (!configPanelOpen) {
164
+ setEditSchema(null);
165
+ }
166
+ // eslint-disable-next-line react-hooks/exhaustive-deps
167
+ }, [dashboard]);
168
+ // ---- Save helper --------------------------------------------------------
169
+ const saveSchema = useCallback(async (schema) => {
170
+ try {
171
+ if (adapter) {
172
+ await adapter.update('sys_dashboard', dashboardName, schema);
173
+ // Refresh metadata cache so closing the config panel shows saved data
174
+ refresh().catch(() => { });
175
+ }
176
+ }
177
+ catch (err) {
178
+ console.warn('[DashboardView] Auto-save failed:', err);
179
+ }
180
+ }, [adapter, dashboardName, refresh]);
181
+ // ---- Open / close config panel ------------------------------------------
182
+ const handleOpenConfigPanel = useCallback(() => {
183
+ setEditSchema(ensureWidgetIds(dashboard));
184
+ setConfigPanelOpen(true);
185
+ setConfigVersion((v) => v + 1);
186
+ }, [dashboard]);
187
+ const handleCloseConfigPanel = useCallback(() => {
188
+ setConfigPanelOpen(false);
189
+ setSelectedWidgetId(null);
190
+ }, []);
191
+ // ---- Widget management --------------------------------------------------
192
+ const addWidget = useCallback((type) => {
193
+ if (!editSchema)
194
+ return;
195
+ const id = createWidgetId();
196
+ const newWidget = {
197
+ id,
198
+ title: defaultWidgetTitle(type),
199
+ type,
200
+ layout: {
201
+ x: 0,
202
+ y: (editSchema.widgets?.length ?? 0),
203
+ w: editSchema.columns ?? 2,
204
+ h: 1,
205
+ },
206
+ };
207
+ const newSchema = { ...editSchema, widgets: [...(editSchema.widgets || []), newWidget] };
208
+ setEditSchema(newSchema);
209
+ saveSchema(newSchema);
210
+ setSelectedWidgetId(id);
211
+ setConfigVersion((v) => v + 1);
212
+ }, [editSchema, saveSchema]);
213
+ const removeWidget = useCallback((widgetId) => {
214
+ if (!editSchema)
215
+ return;
216
+ const newSchema = {
217
+ ...editSchema,
218
+ widgets: editSchema.widgets.filter((w) => w.id !== widgetId),
219
+ };
220
+ setEditSchema(newSchema);
221
+ saveSchema(newSchema);
222
+ if (selectedWidgetId === widgetId) {
223
+ setSelectedWidgetId(null);
224
+ }
225
+ }, [editSchema, selectedWidgetId, saveSchema]);
226
+ // ---- Dashboard config panel handlers ------------------------------------
227
+ // Stabilize config reference: only recompute after explicit actions (panel
228
+ // open, save, widget add). configVersion is incremented on those actions.
229
+ // This prevents useConfigDraft from resetting the draft on every live field
230
+ // change (same pattern as ViewConfigPanel's stableActiveView).
231
+ const dashboardConfig = useMemo(() => extractDashboardConfig(editSchema || dashboard),
232
+ // eslint-disable-next-line react-hooks/exhaustive-deps
233
+ [configVersion]);
234
+ const handleDashboardConfigSave = useCallback((config) => {
235
+ if (!editSchema)
236
+ return;
237
+ const newSchema = {
238
+ ...editSchema,
239
+ columns: config.columns,
240
+ gap: config.gap,
241
+ rowHeight: config.rowHeight,
242
+ refreshInterval: Number(config.refreshInterval) || 0,
243
+ title: config.title,
244
+ showDescription: config.showDescription,
245
+ theme: config.theme,
246
+ };
247
+ setEditSchema(newSchema);
248
+ saveSchema(newSchema);
249
+ setConfigVersion((v) => v + 1);
250
+ }, [editSchema, saveSchema]);
251
+ const handleDashboardFieldChange = useCallback((field, value) => {
252
+ if (!editSchema)
253
+ return;
254
+ // Map config field keys to proper DashboardSchema updates for live preview
255
+ setEditSchema((prev) => {
256
+ if (!prev)
257
+ return prev;
258
+ if (field === 'refreshInterval') {
259
+ return { ...prev, refreshInterval: Number(value) || 0 };
260
+ }
261
+ return { ...prev, [field]: value };
262
+ });
263
+ }, [editSchema]);
264
+ // ---- Widget config panel handlers ---------------------------------------
265
+ const selectedWidget = editSchema?.widgets?.find((w) => w.id === selectedWidgetId);
266
+ // Stabilize widget config: only recompute after explicit actions (widget
267
+ // switch, save, add). configVersion is incremented on save/add, and
268
+ // selectedWidgetId changes on widget switch — this prevents useConfigDraft
269
+ // from resetting the draft on every live field change.
270
+ const widgetConfig = useMemo(() => (selectedWidget ? flattenWidgetConfig(selectedWidget) : {}),
271
+ // eslint-disable-next-line react-hooks/exhaustive-deps
272
+ [selectedWidgetId, configVersion]);
273
+ const handleWidgetConfigSave = useCallback((config) => {
274
+ if (!editSchema || !selectedWidgetId || !selectedWidget)
275
+ return;
276
+ const updates = unflattenWidgetConfig(config, selectedWidget);
277
+ const newSchema = {
278
+ ...editSchema,
279
+ widgets: editSchema.widgets.map((w) => w.id === selectedWidgetId ? { ...w, ...updates } : w),
280
+ };
281
+ setEditSchema(newSchema);
282
+ saveSchema(newSchema);
283
+ setConfigVersion((v) => v + 1);
284
+ }, [editSchema, selectedWidgetId, selectedWidget, saveSchema]);
285
+ const handleWidgetFieldChange = useCallback((field, value) => {
286
+ if (!selectedWidgetId)
287
+ return;
288
+ setEditSchema((prev) => {
289
+ if (!prev)
290
+ return prev;
291
+ const widget = prev.widgets?.find((w) => w.id === selectedWidgetId);
292
+ if (!widget)
293
+ return prev;
294
+ const flat = flattenWidgetConfig(widget);
295
+ flat[field] = value;
296
+ const updates = unflattenWidgetConfig(flat, widget);
297
+ return {
298
+ ...prev,
299
+ widgets: prev.widgets.map((w) => w.id === selectedWidgetId ? { ...w, ...updates } : w),
300
+ };
301
+ });
302
+ }, [selectedWidgetId]);
303
+ // ---- Metadata-driven dropdown options -----------------------------------
304
+ const availableObjects = useMemo(() => {
305
+ if (!metadataObjects?.length)
306
+ return undefined;
307
+ return metadataObjects.map((obj) => ({
308
+ value: obj.name,
309
+ label: obj.label || obj.name,
310
+ }));
311
+ }, [metadataObjects]);
312
+ const availableFields = useMemo(() => {
313
+ const objectName = selectedWidget?.object;
314
+ if (!objectName || !metadataObjects?.length)
315
+ return undefined;
316
+ const obj = metadataObjects.find((o) => o.name === objectName);
317
+ if (!obj?.fields)
318
+ return undefined;
319
+ const fields = obj.fields;
320
+ if (Array.isArray(fields)) {
321
+ return fields
322
+ .filter((f) => f.name)
323
+ .map((f) => ({ value: f.name, label: f.label || f.name }));
324
+ }
325
+ // fields can be Record<string, FieldMetadata>
326
+ return Object.entries(fields).map(([key, f]) => ({
327
+ value: key,
328
+ label: f.label || key,
329
+ }));
330
+ }, [selectedWidget?.object, metadataObjects]);
331
+ // ---- Loading / not-found guards -----------------------------------------
332
+ if (isLoading) {
333
+ return _jsx(SkeletonDashboard, {});
334
+ }
335
+ if (!dashboard) {
336
+ 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(LayoutDashboard, { className: "h-6 w-6 text-muted-foreground" }) }), _jsx(EmptyTitle, { children: "Dashboard Not Found" }), _jsxs(EmptyDescription, { children: ["The dashboard \"", dashboardName, "\" could not be found. It may have been removed or renamed."] })] }) }));
337
+ }
338
+ const previewSchema = editSchema || dashboard;
339
+ 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: resolveI18nLabel(dashboard.label, t) || dashboard.name }), dashboard.description && (_jsx("p", { className: "text-sm text-muted-foreground mt-1 line-clamp-2", children: resolveI18nLabel(dashboard.description, t) }))] }), _jsxs("div", { className: "shrink-0 flex items-center gap-1.5", children: [configPanelOpen && (_jsx("div", { className: "flex items-center gap-1 mr-2", role: "toolbar", "aria-label": "Add widgets", "data-testid": "dashboard-widget-toolbar", children: WIDGET_TYPES.map(({ type, label, Icon }) => (_jsxs("button", { type: "button", "data-testid": `dashboard-add-${type}`, onClick: () => addWidget(type), className: "inline-flex items-center gap-1 rounded-md border border-input bg-background px-2 py-1.5 text-[11px] font-medium text-muted-foreground hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", title: `Add ${label}`, "aria-label": `Add ${label} widget`, children: [_jsx(Plus, { className: "h-3 w-3" }), _jsx(Icon, { className: "h-3 w-3" })] }, type))) })), _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": "dashboard-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-2 sm:p-4 md:p-6", children: _jsx(DashboardRenderer, { schema: previewSchema, dataSource: dataSource, designMode: configPanelOpen, selectedWidgetId: selectedWidgetId, onWidgetClick: setSelectedWidgetId, modalHandler: modalHandler, scriptHandlers: scriptHandlers }) }), selectedWidget ? (_jsx(WidgetConfigPanel, { open: configPanelOpen, onClose: handleCloseConfigPanel, config: widgetConfig, onSave: handleWidgetConfigSave, onFieldChange: handleWidgetFieldChange, availableObjects: availableObjects, availableFields: availableFields, headerExtra: _jsx(Button, { size: "sm", variant: "ghost", onClick: () => removeWidget(selectedWidgetId), className: "h-7 w-7 p-0 text-destructive hover:text-destructive", "data-testid": "widget-delete-button", title: "Delete widget", children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) }) }, selectedWidgetId)) : (_jsx(DashboardConfigPanel, { open: configPanelOpen, onClose: handleCloseConfigPanel, config: dashboardConfig, onSave: handleDashboardConfigSave, onFieldChange: handleDashboardFieldChange })), _jsx(MetadataPanel, { open: showDebug, sections: [{ title: 'Dashboard Configuration', data: previewSchema }] })] }), modalState && modalState.schema?.objectName ? (_jsx(ModalForm, { schema: {
340
+ type: 'object-form',
341
+ formType: 'modal',
342
+ objectName: modalState.schema.objectName,
343
+ mode: modalState.schema.mode || 'create',
344
+ recordId: modalState.schema.recordId,
345
+ title: modalState.schema.title,
346
+ description: modalState.schema.description,
347
+ fields: modalState.schema.fields,
348
+ open: true,
349
+ onOpenChange: (open) => { if (!open)
350
+ closeModal({ success: false }); },
351
+ onSuccess: (data) => { closeModal({ success: true, reload: true, data }); },
352
+ onCancel: () => { closeModal({ success: false }); },
353
+ showSubmit: true,
354
+ showCancel: true,
355
+ }, dataSource: adapter })) : modalState ? (_jsx(Dialog, { open: true, onOpenChange: (open) => { if (!open)
356
+ closeModal({ success: false }); }, children: _jsxs(DialogContent, { children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: modalState.schema?.title || 'Action' }), modalState.schema?.description && (_jsx(DialogDescription, { children: modalState.schema.description }))] }), _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "outline", onClick: () => closeModal({ success: false }), children: "Cancel" }), _jsx(Button, { onClick: () => closeModal({ success: true }), children: "OK" })] })] }) })) : null] }));
357
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * DesignDrawer
3
+ *
4
+ * A right-side drawer that hosts visual editors (PageCanvasEditor / DashboardEditor)
5
+ * for inline editing with real-time preview. Uses the Shadcn Sheet primitive so the
6
+ * main workspace remains visible while the user edits.
7
+ */
8
+ export interface DesignDrawerProps {
9
+ /** Whether the drawer is open */
10
+ open: boolean;
11
+ /** Called when the drawer requests close */
12
+ onOpenChange: (open: boolean) => void;
13
+ /** Human-readable title shown in the drawer header */
14
+ title: string;
15
+ /** Optional description */
16
+ description?: string;
17
+ /** The current schema (PageSchema | DashboardSchema) */
18
+ schema: any;
19
+ /** Called when the editor modifies the schema — parent updates its own state for live preview */
20
+ onSchemaChange: (updated: any) => void;
21
+ /** sys_page | sys_dashboard */
22
+ collection: string;
23
+ /** Record key for the update call */
24
+ recordName: string;
25
+ /** Render prop: receives (schema, onChange) and returns the editor JSX */
26
+ children: (schema: any, onChange: (updated: any) => void) => React.ReactNode;
27
+ }
28
+ export declare function DesignDrawer({ open, onOpenChange, title, description, schema, onSchemaChange, collection, recordName, children, }: DesignDrawerProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,51 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * DesignDrawer
4
+ *
5
+ * A right-side drawer that hosts visual editors (PageCanvasEditor / DashboardEditor)
6
+ * for inline editing with real-time preview. Uses the Shadcn Sheet primitive so the
7
+ * main workspace remains visible while the user edits.
8
+ */
9
+ import { useCallback, useEffect, useRef } from 'react';
10
+ import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetDescription, } from '@object-ui/components';
11
+ import { toast } from 'sonner';
12
+ import { useAdapter } from '../providers/AdapterProvider';
13
+ export function DesignDrawer({ open, onOpenChange, title, description, schema, onSchemaChange, collection, recordName, children, }) {
14
+ const dataSource = useAdapter();
15
+ const schemaRef = useRef(schema);
16
+ schemaRef.current = schema;
17
+ const saveSchema = useCallback(async (toSave) => {
18
+ try {
19
+ if (dataSource) {
20
+ await dataSource.update(collection, recordName, toSave);
21
+ return true;
22
+ }
23
+ }
24
+ catch (err) {
25
+ console.warn('[DesignDrawer] Auto-save failed:', err);
26
+ // Save errors are non-blocking; user can retry via Ctrl+S
27
+ }
28
+ return false;
29
+ }, [dataSource, collection, recordName]);
30
+ const handleChange = useCallback(async (updated) => {
31
+ onSchemaChange(updated);
32
+ await saveSchema(updated);
33
+ }, [onSchemaChange, saveSchema]);
34
+ // Ctrl+S / Cmd+S keyboard shortcut to explicitly save while drawer is open
35
+ useEffect(() => {
36
+ if (!open)
37
+ return;
38
+ const handleKeyDown = (e) => {
39
+ if ((e.ctrlKey || e.metaKey) && e.key === 's') {
40
+ e.preventDefault();
41
+ saveSchema(schemaRef.current).then((ok) => {
42
+ if (ok)
43
+ toast.success(`${title} saved`);
44
+ });
45
+ }
46
+ };
47
+ window.addEventListener('keydown', handleKeyDown);
48
+ return () => window.removeEventListener('keydown', handleKeyDown);
49
+ }, [open, saveSchema, title]);
50
+ return (_jsx(Sheet, { open: open, onOpenChange: onOpenChange, modal: false, children: _jsxs(SheetContent, { side: "right", hideOverlay: true, className: "w-full sm:w-[540px] sm:max-w-[540px] lg:w-[640px] lg:max-w-[640px] p-0 flex flex-col", "data-testid": "design-drawer", children: [_jsxs(SheetHeader, { className: "px-4 py-3 border-b shrink-0", children: [_jsx(SheetTitle, { children: title }), _jsx(SheetDescription, { className: description ? '' : 'sr-only', children: description || `Visual editor — configure and preview ${title}` })] }), _jsx("div", { className: "flex-1 overflow-auto p-4", children: children(schema, handleChange) })] }) }));
51
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * MetadataInspector
3
+ *
4
+ * Shared debug panel that shows raw JSON metadata for any view.
5
+ * Used across ObjectView, DashboardView, PageView, ReportView, and RecordDetailView.
6
+ */
7
+ interface MetadataSection {
8
+ title: string;
9
+ data: any;
10
+ }
11
+ interface MetadataInspectorProps {
12
+ /** Sections of metadata to display */
13
+ sections: MetadataSection[];
14
+ /** Whether the panel is open */
15
+ open: boolean;
16
+ /** Toggle callback */
17
+ onToggle: () => void;
18
+ }
19
+ /**
20
+ * Toggle button for the metadata inspector.
21
+ * Place this in your header/toolbar area.
22
+ */
23
+ export declare function MetadataToggle({ open, onToggle, className }: {
24
+ open: boolean;
25
+ onToggle: () => void;
26
+ className?: string;
27
+ }): import("react/jsx-runtime").JSX.Element;
28
+ /**
29
+ * The side panel that renders JSON metadata sections.
30
+ */
31
+ export declare function MetadataPanel({ sections, open }: Omit<MetadataInspectorProps, 'onToggle'>): import("react/jsx-runtime").JSX.Element | null;
32
+ /**
33
+ * Hook to manage MetadataInspector open/close state.
34
+ * Automatically opens when `?__debug` URL parameter is present.
35
+ */
36
+ export declare function useMetadataInspector(): {
37
+ showDebug: boolean;
38
+ toggleDebug: () => void;
39
+ };
40
+ export {};
@@ -0,0 +1,69 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * MetadataInspector
4
+ *
5
+ * Shared debug panel that shows raw JSON metadata for any view.
6
+ * Used across ObjectView, DashboardView, PageView, ReportView, and RecordDetailView.
7
+ */
8
+ import { useState, useMemo } from 'react';
9
+ import { Button } from '@object-ui/components';
10
+ import { Code2, Copy, Check, ChevronDown, ChevronRight } from 'lucide-react';
11
+ import { parseDebugFlags } from '@object-ui/core';
12
+ /**
13
+ * Toggle button for the metadata inspector.
14
+ * Place this in your header/toolbar area.
15
+ */
16
+ export function MetadataToggle({ open, onToggle, className }) {
17
+ return (_jsxs(Button, { size: "sm", variant: open ? 'secondary' : 'outline', onClick: onToggle, className: `shadow-none gap-2 ${className || ''}`, title: "Toggle Metadata Inspector", children: [_jsx(Code2, { className: "h-4 w-4" }), _jsx("span", { className: "hidden lg:inline", children: "Metadata" })] }));
18
+ }
19
+ /**
20
+ * The side panel that renders JSON metadata sections.
21
+ */
22
+ export function MetadataPanel({ sections, open }) {
23
+ if (!open)
24
+ return null;
25
+ const [expandedSections, setExpandedSections] = useState(['0']);
26
+ const [copiedIndex, setCopiedIndex] = useState(null);
27
+ const handleCopy = async (data, index) => {
28
+ try {
29
+ await navigator.clipboard.writeText(JSON.stringify(data, null, 2));
30
+ setCopiedIndex(index);
31
+ setTimeout(() => setCopiedIndex(null), 2000);
32
+ }
33
+ catch (err) {
34
+ console.error('Failed to copy:', err);
35
+ }
36
+ };
37
+ const toggleSection = (index) => {
38
+ setExpandedSections(prev => prev.includes(index)
39
+ ? prev.filter(i => i !== index)
40
+ : [...prev, index]);
41
+ };
42
+ return (_jsxs("div", { className: "w-80 border-l bg-background p-0 overflow-hidden flex flex-col shrink-0 z-20 transition-all", children: [_jsxs("div", { className: "px-4 py-3 border-b bg-muted/5 flex items-center justify-between", children: [_jsx("span", { className: "text-xs font-semibold uppercase tracking-wider text-muted-foreground", children: "Metadata Inspector" }), _jsx("span", { className: "text-[10px] text-muted-foreground/60", children: "JSON" })] }), _jsx("div", { className: "flex-1 overflow-auto", children: sections.map((section, index) => {
43
+ const sectionId = String(index);
44
+ const isExpanded = expandedSections.includes(sectionId);
45
+ const isCopied = copiedIndex === index;
46
+ return (_jsxs("div", { className: "border-b last:border-b-0", children: [_jsxs("div", { className: "flex items-center justify-between px-4 py-3 hover:bg-muted/5 transition-colors", children: [_jsxs("button", { onClick: () => toggleSection(sectionId), className: "flex-1 flex items-center justify-between text-left", children: [_jsx("h4", { className: "text-xs font-semibold text-foreground", children: section.title }), isExpanded ? (_jsx(ChevronDown, { className: "h-3 w-3 text-muted-foreground ml-2" })) : (_jsx(ChevronRight, { className: "h-3 w-3 text-muted-foreground ml-2" }))] }), _jsx("button", { onClick: () => handleCopy(section.data, index), className: "p-1 hover:bg-muted rounded transition-colors ml-2", title: "Copy JSON", children: isCopied ? (_jsx(Check, { className: "h-3 w-3 text-green-600" })) : (_jsx(Copy, { className: "h-3 w-3 text-muted-foreground" })) })] }), isExpanded && (_jsx("div", { className: "px-4 pb-4", children: _jsx("div", { className: "relative rounded-md border border-border/50 bg-muted/5 overflow-hidden", children: _jsx("pre", { className: "text-[11px] leading-relaxed p-3 overflow-auto max-h-96 font-mono text-foreground/90", children: JSON.stringify(section.data, null, 2) }) }) }))] }, index));
47
+ }) })] }));
48
+ }
49
+ /**
50
+ * Hook to manage MetadataInspector open/close state.
51
+ * Automatically opens when `?__debug` URL parameter is present.
52
+ */
53
+ export function useMetadataInspector() {
54
+ const autoOpen = useMemo(() => {
55
+ try {
56
+ return typeof window !== 'undefined'
57
+ ? parseDebugFlags(window.location.search).enabled
58
+ : false;
59
+ }
60
+ catch {
61
+ return false;
62
+ }
63
+ }, []);
64
+ const [showDebug, setShowDebug] = useState(autoOpen);
65
+ return {
66
+ showDebug,
67
+ toggleDebug: () => setShowDebug((prev) => !prev),
68
+ };
69
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Console ObjectView
3
+ *
4
+ * Thin wrapper around the plugin-view ObjectView that adds:
5
+ * - Multi-view resolution from objectDef.list_views
6
+ * - MetadataInspector toggle
7
+ * - Drawer for record detail preview
8
+ * - useObjectActions for toolbar create button
9
+ * - ListView delegation for non-grid view types (kanban, calendar, chart, etc.)
10
+ */
11
+ import '@object-ui/plugin-grid';
12
+ import '@object-ui/plugin-kanban';
13
+ import '@object-ui/plugin-calendar';
14
+ export declare function ObjectView({ dataSource, objects, onEdit }: any): import("react/jsx-runtime").JSX.Element;