@mohsinonxrm/pptb-fetchxml-studio 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +1 -1
  3. package/dist/assets/index-DD9fzkbz.js +232 -0
  4. package/dist/index.html +70 -0
  5. package/index.html +0 -1
  6. package/npm-shrinkwrap.json +6329 -0
  7. package/package.json +95 -51
  8. package/CHANGELOG.md +0 -120
  9. package/eslint.config.js +0 -23
  10. package/public/vite.svg +0 -1
  11. package/src/app/AppShell.tsx +0 -1733
  12. package/src/assets/20px-Microsoft_Office_Excel_(2025/342/200/223present).svg.png +0 -0
  13. package/src/assets/react.svg +0 -1
  14. package/src/features/fetchxml/api/dataverseMetadata.ts +0 -192
  15. package/src/features/fetchxml/api/excelExport.ts +0 -318
  16. package/src/features/fetchxml/api/formattedValues.ts +0 -107
  17. package/src/features/fetchxml/api/pptbClient.ts +0 -2635
  18. package/src/features/fetchxml/model/displaySettings.ts +0 -18
  19. package/src/features/fetchxml/model/fetchxml.ts +0 -346
  20. package/src/features/fetchxml/model/fetchxmlIntellisense.ts +0 -583
  21. package/src/features/fetchxml/model/fetchxmlParser.ts +0 -708
  22. package/src/features/fetchxml/model/layoutxml.ts +0 -372
  23. package/src/features/fetchxml/model/nodes.ts +0 -255
  24. package/src/features/fetchxml/model/operators.ts +0 -624
  25. package/src/features/fetchxml/model/treeUtils.ts +0 -174
  26. package/src/features/fetchxml/state/builderStore.tsx +0 -1160
  27. package/src/features/fetchxml/state/cache.ts +0 -393
  28. package/src/features/fetchxml/ui/Dialogs/BulkDeleteDialog.tsx +0 -352
  29. package/src/features/fetchxml/ui/Dialogs/DeleteConfirmDialog.tsx +0 -396
  30. package/src/features/fetchxml/ui/Dialogs/SaveViewDialog.tsx +0 -612
  31. package/src/features/fetchxml/ui/Dialogs/SolutionPicker.tsx +0 -114
  32. package/src/features/fetchxml/ui/Dialogs/WorkflowPickerDialog.tsx +0 -437
  33. package/src/features/fetchxml/ui/LeftPane/PropertiesPanel/editors/AttributeEditor.tsx +0 -214
  34. package/src/features/fetchxml/ui/LeftPane/PropertiesPanel/editors/ConditionEditor.tsx +0 -533
  35. package/src/features/fetchxml/ui/LeftPane/PropertiesPanel/editors/FetchOptionsEditor.tsx +0 -296
  36. package/src/features/fetchxml/ui/LeftPane/PropertiesPanel/editors/FilterEditor.tsx +0 -106
  37. package/src/features/fetchxml/ui/LeftPane/PropertiesPanel/editors/LinkEntityEditor.tsx +0 -433
  38. package/src/features/fetchxml/ui/LeftPane/PropertiesPanel/editors/OrderEditor.tsx +0 -161
  39. package/src/features/fetchxml/ui/LeftPane/PropertiesPanel/editors/ValueOfPicker.tsx +0 -194
  40. package/src/features/fetchxml/ui/LeftPane/PropertiesPanel.tsx +0 -225
  41. package/src/features/fetchxml/ui/LeftPane/TreeView.tsx +0 -604
  42. package/src/features/fetchxml/ui/RightPane/AddColumnsPanel.tsx +0 -800
  43. package/src/features/fetchxml/ui/RightPane/ColumnConfigDialog.tsx +0 -170
  44. package/src/features/fetchxml/ui/RightPane/DataGridCellRenderers.tsx +0 -356
  45. package/src/features/fetchxml/ui/RightPane/EditColumnsPanel.tsx +0 -372
  46. package/src/features/fetchxml/ui/RightPane/FetchXmlEditor.tsx +0 -399
  47. package/src/features/fetchxml/ui/RightPane/FormattedValueUtils.ts +0 -189
  48. package/src/features/fetchxml/ui/RightPane/LayoutXmlViewer.tsx +0 -171
  49. package/src/features/fetchxml/ui/RightPane/PreviewTabs.tsx +0 -471
  50. package/src/features/fetchxml/ui/RightPane/ResultsCommandBar.tsx +0 -430
  51. package/src/features/fetchxml/ui/RightPane/ResultsGrid.tsx +0 -983
  52. package/src/features/fetchxml/ui/RightPane/XmlTextArea.tsx +0 -65
  53. package/src/features/fetchxml/ui/Settings/SettingsDrawer.tsx +0 -175
  54. package/src/features/fetchxml/ui/Toolbar/EntitySelector.tsx +0 -788
  55. package/src/features/fetchxml/ui/Toolbar/LoadViewPicker.tsx +0 -274
  56. package/src/features/fetchxml/ui/Toolbar/SaveViewButton.tsx +0 -174
  57. package/src/main.tsx +0 -11
  58. package/src/shared/components/AttributePicker.tsx +0 -138
  59. package/src/shared/components/BetweenInputPicker.tsx +0 -120
  60. package/src/shared/components/BooleanValuePicker.tsx +0 -165
  61. package/src/shared/components/DateTimeInputPicker.tsx +0 -170
  62. package/src/shared/components/DynamicValueList.tsx +0 -128
  63. package/src/shared/components/ExcelIcon.tsx +0 -30
  64. package/src/shared/components/MultiValuePicker.tsx +0 -178
  65. package/src/shared/components/NumericInputPicker.tsx +0 -156
  66. package/src/shared/components/OptionSetValuePicker.tsx +0 -150
  67. package/src/shared/components/RelationshipPicker.tsx +0 -268
  68. package/src/shared/contexts/ThemeContext.tsx +0 -19
  69. package/src/shared/hooks/useAccessMode.ts +0 -77
  70. package/src/shared/hooks/useLazyMetadata.ts +0 -132
  71. package/src/shared/hooks/usePptbContext.ts +0 -170
  72. package/src/shared/hooks/usePublisherFilter.ts +0 -214
  73. package/src/shared/hooks/useSolutionFilter.ts +0 -162
  74. package/src/shared/utils/debug.ts +0 -87
  75. package/tsconfig.app.json +0 -28
  76. package/tsconfig.json +0 -7
  77. package/tsconfig.node.json +0 -26
  78. package/vite.config.ts +0 -7
@@ -1,171 +0,0 @@
1
- /**
2
- * Monaco editor for LayoutXML (read-only)
3
- * Displays the LayoutXML that defines column configuration for the grid
4
- */
5
-
6
- import { useRef, useCallback } from "react";
7
- import Editor from "@monaco-editor/react";
8
- import type * as MonacoEditor from "monaco-editor";
9
- import { makeStyles, Spinner, tokens, Toolbar, ToolbarButton } from "@fluentui/react-components";
10
- import { Copy24Regular } from "@fluentui/react-icons";
11
- import { useTheme } from "../../../../shared/contexts/ThemeContext";
12
-
13
- const useStyles = makeStyles({
14
- container: {
15
- display: "flex",
16
- flexDirection: "column",
17
- height: "100%",
18
- overflow: "hidden",
19
- },
20
- toolbar: {
21
- display: "flex",
22
- alignItems: "center",
23
- justifyContent: "space-between",
24
- padding: "4px 8px",
25
- borderBottom: `1px solid ${tokens.colorNeutralStroke1}`,
26
- backgroundColor: tokens.colorNeutralBackground2,
27
- flexShrink: 0,
28
- },
29
- toolbarLeft: {
30
- display: "flex",
31
- alignItems: "center",
32
- gap: tokens.spacingHorizontalS,
33
- },
34
- toolbarRight: {
35
- display: "flex",
36
- alignItems: "center",
37
- gap: tokens.spacingHorizontalS,
38
- },
39
- editorWrapper: {
40
- flex: 1,
41
- overflow: "hidden",
42
- minHeight: 0,
43
- },
44
- loadingContainer: {
45
- display: "flex",
46
- alignItems: "center",
47
- justifyContent: "center",
48
- height: "100%",
49
- backgroundColor: tokens.colorNeutralBackground1,
50
- },
51
- label: {
52
- fontSize: tokens.fontSizeBase200,
53
- fontWeight: tokens.fontWeightSemibold,
54
- color: tokens.colorNeutralForeground2,
55
- },
56
- });
57
-
58
- interface LayoutXmlViewerProps {
59
- /** The LayoutXML string to display */
60
- layoutXml: string;
61
- }
62
-
63
- export function LayoutXmlViewer({ layoutXml }: LayoutXmlViewerProps) {
64
- const styles = useStyles();
65
- const { isDark } = useTheme();
66
- const editorRef = useRef<MonacoEditor.editor.IStandaloneCodeEditor | null>(null);
67
-
68
- // Format XML with indentation for better readability
69
- const formatXml = (xml: string): string => {
70
- if (!xml) return "";
71
- try {
72
- // Simple XML formatting - add newlines and indentation
73
- let formatted = "";
74
- let indent = 0;
75
- const parts = xml.replace(/>\s*</g, ">\n<").split("\n");
76
-
77
- for (const part of parts) {
78
- const trimmed = part.trim();
79
- if (!trimmed) continue;
80
-
81
- // Decrease indent for closing tags
82
- if (trimmed.startsWith("</")) {
83
- indent = Math.max(0, indent - 1);
84
- }
85
-
86
- formatted += " ".repeat(indent) + trimmed + "\n";
87
-
88
- // Increase indent for opening tags (not self-closing)
89
- if (
90
- trimmed.startsWith("<") &&
91
- !trimmed.startsWith("</") &&
92
- !trimmed.startsWith("<?") &&
93
- !trimmed.endsWith("/>") &&
94
- !trimmed.includes("</")
95
- ) {
96
- indent++;
97
- }
98
- }
99
-
100
- return formatted.trim();
101
- } catch {
102
- return xml;
103
- }
104
- };
105
-
106
- const formattedXml = formatXml(layoutXml);
107
-
108
- const handleEditorMount = useCallback((editor: MonacoEditor.editor.IStandaloneCodeEditor) => {
109
- editorRef.current = editor;
110
- }, []);
111
-
112
- const handleCopy = useCallback(async () => {
113
- try {
114
- await navigator.clipboard.writeText(formattedXml);
115
- } catch (error) {
116
- console.error("Failed to copy to clipboard:", error);
117
- }
118
- }, [formattedXml]);
119
-
120
- return (
121
- <div className={styles.container}>
122
- <div className={styles.toolbar}>
123
- <div className={styles.toolbarLeft}>
124
- <span className={styles.label}>LayoutXML (Read-only)</span>
125
- </div>
126
- <Toolbar size="small">
127
- <ToolbarButton
128
- icon={<Copy24Regular />}
129
- onClick={handleCopy}
130
- disabled={!layoutXml}
131
- aria-label="Copy LayoutXML"
132
- >
133
- Copy
134
- </ToolbarButton>
135
- </Toolbar>
136
- </div>
137
- <div className={styles.editorWrapper}>
138
- <Editor
139
- height="100%"
140
- language="xml"
141
- theme={isDark ? "vs-dark" : "vs"}
142
- value={formattedXml}
143
- options={{
144
- readOnly: true,
145
- minimap: { enabled: false },
146
- lineNumbers: "on",
147
- scrollBeyondLastLine: false,
148
- wordWrap: "on",
149
- fontSize: 13,
150
- tabSize: 2,
151
- automaticLayout: true,
152
- folding: true,
153
- renderLineHighlight: "line",
154
- scrollbar: {
155
- vertical: "visible",
156
- horizontal: "visible",
157
- verticalScrollbarSize: 10,
158
- horizontalScrollbarSize: 10,
159
- },
160
- }}
161
- onMount={handleEditorMount}
162
- loading={
163
- <div className={styles.loadingContainer}>
164
- <Spinner size="medium" label="Loading editor..." />
165
- </div>
166
- }
167
- />
168
- </div>
169
- </div>
170
- );
171
- }
@@ -1,471 +0,0 @@
1
- /**
2
- * Tabbed preview panel with FetchXML editor and Results grid
3
- */
4
-
5
- import { useState, useCallback, type ReactNode } from "react";
6
- import {
7
- TabList,
8
- Tab,
9
- type SelectTabData,
10
- type SelectTabEvent,
11
- Button,
12
- makeStyles,
13
- Toolbar,
14
- ToolbarDivider,
15
- ProgressBar,
16
- tokens,
17
- MessageBar,
18
- MessageBarBody,
19
- MessageBarTitle,
20
- MessageBarActions,
21
- } from "@fluentui/react-components";
22
- import { Play24Regular, Dismiss16Regular, Settings20Regular } from "@fluentui/react-icons";
23
- import { FetchXmlEditor } from "./FetchXmlEditor";
24
- import { LayoutXmlViewer } from "./LayoutXmlViewer";
25
- import { ResultsGrid, type QueryResult, type SortChangeData } from "./ResultsGrid";
26
- import { ResultsCommandBar } from "./ResultsCommandBar";
27
- import type { RelatedEntityColumn } from "./AddColumnsPanel";
28
- import type { AttributeMetadata, RelationshipMetadata } from "../../api/pptbClient";
29
- import type { FetchNode } from "../../model/nodes";
30
- import type { ParseResult } from "../../model/fetchxmlParser";
31
- import type { LayoutXmlConfig } from "../../model/layoutxml";
32
- import type { WorkflowInfo } from "../../api/pptbClient";
33
- import type { DisplaySettings } from "../../model/displaySettings";
34
-
35
- const useStyles = makeStyles({
36
- container: {
37
- display: "flex",
38
- flexDirection: "column",
39
- height: "100%",
40
- overflow: "hidden",
41
- },
42
- header: {
43
- display: "flex",
44
- alignItems: "center",
45
- justifyContent: "space-between",
46
- borderBottom: `1px solid ${tokens.colorNeutralStroke1}`,
47
- paddingRight: "16px",
48
- },
49
- progressContainer: {
50
- paddingLeft: "16px",
51
- paddingRight: "16px",
52
- paddingTop: "8px",
53
- paddingBottom: "8px",
54
- borderBottom: `1px solid ${tokens.colorNeutralStroke1}`,
55
- },
56
- tabContent: {
57
- flex: 1,
58
- overflow: "hidden",
59
- padding: "6px",
60
- backgroundColor: tokens.colorNeutralBackground3, // Neutral canvas background
61
- },
62
- // Shared surface card styling (floating cards with shadow)
63
- surfaceCard: {
64
- borderRadius: tokens.borderRadiusMedium,
65
- boxShadow: tokens.shadow16,
66
- backgroundColor: tokens.colorNeutralBackground1,
67
- border: `1px solid ${tokens.colorNeutralStroke2}`,
68
- },
69
- // Toolbar card styling
70
- toolbarCard: {
71
- borderRadius: tokens.borderRadiusMedium,
72
- boxShadow: tokens.shadow16,
73
- backgroundColor: tokens.colorNeutralBackground1,
74
- border: `1px solid ${tokens.colorNeutralStroke2}`,
75
- padding: "4px 8px",
76
- flexShrink: 0,
77
- },
78
- // Grid card styling
79
- gridCard: {
80
- borderRadius: tokens.borderRadiusMedium,
81
- boxShadow: tokens.shadow16,
82
- backgroundColor: tokens.colorNeutralBackground1,
83
- border: `1px solid ${tokens.colorNeutralStroke2}`,
84
- display: "flex",
85
- flexDirection: "column",
86
- flex: 1,
87
- minHeight: "240px",
88
- minWidth: "480px",
89
- overflow: "hidden",
90
- marginTop: "8px",
91
- },
92
- // Code card for FetchXML tab
93
- codeCard: {
94
- borderRadius: tokens.borderRadiusMedium,
95
- boxShadow: tokens.shadow16,
96
- backgroundColor: tokens.colorNeutralBackground1,
97
- border: `1px solid ${tokens.colorNeutralStroke2}`,
98
- height: "100%",
99
- display: "flex",
100
- flexDirection: "column",
101
- },
102
- // Results tab layout grid
103
- resultsLayout: {
104
- display: "flex",
105
- flexDirection: "column",
106
- height: "100%",
107
- overflow: "hidden",
108
- },
109
- messageBarContainer: {
110
- paddingLeft: "16px",
111
- paddingRight: "16px",
112
- paddingTop: "8px",
113
- paddingBottom: "8px",
114
- borderBottom: `1px solid ${tokens.colorNeutralStroke1}`,
115
- },
116
- });
117
-
118
- interface PreviewTabsProps {
119
- xml: string;
120
- /** LayoutXML string for the grid column configuration */
121
- layoutXml?: string;
122
- result: QueryResult | null;
123
- isExecuting?: boolean;
124
- /** Whether more pages are being loaded (for progress display) */
125
- isLoadingMore?: boolean;
126
- onExecute?: () => void;
127
- /** Export via Dataverse ExportToExcel API */
128
- onExport?: () => void;
129
- /** Export locally using exceljs */
130
- onExportLocal?: () => void;
131
- onParseToTree?: (xmlString: string) => ParseResult;
132
- /** Multi-entity attribute metadata: Map<entityLogicalName, Map<attributeLogicalName, AttributeMetadata>> */
133
- attributeMetadata?: Map<string, Map<string, AttributeMetadata>>;
134
- fetchQuery?: FetchNode | null;
135
- /** Column layout configuration for ordering and sizing */
136
- columnConfig?: LayoutXmlConfig | null;
137
- /** Callback when column width changes */
138
- onColumnResize?: (columnName: string, width: number) => void;
139
- /** Callback when columns are reordered */
140
- onReorderColumns?: (columns: LayoutXmlConfig["columns"]) => void;
141
- /** Callback when user wants to add a column from available attributes */
142
- onAddColumn?: (attributeName: string) => void;
143
- /** Callback when user wants to add columns from related entity */
144
- onAddRelatedColumns?: (columns: RelatedEntityColumn[]) => void;
145
- /** Callback when user removes a column */
146
- onRemoveColumn?: (columnName: string) => void;
147
- /** Callback when user changes sort on a column */
148
- onSortChange?: (data: SortChangeData) => void;
149
- /** Optional SaveViewButton to render in the toolbar */
150
- saveViewButton?: ReactNode;
151
- /** Callback when user scrolls near bottom (infinite scroll) */
152
- onLoadMore?: () => void;
153
- /** Whether export is available (requires a saved view) */
154
- canExport?: boolean;
155
- /** Whether export is in progress */
156
- isExporting?: boolean;
157
- /** Export error message */
158
- exportError?: string;
159
- /** Callback to dismiss export error */
160
- onDismissExportError?: () => void;
161
- /** Tooltip text for disabled export button */
162
- exportDisabledReason?: string;
163
- /** Entity display name */
164
- entityDisplayName?: string;
165
- /** Lookup relationships for related columns */
166
- lookupRelationships?: RelationshipMetadata[];
167
- /** One-to-many relationships for 1-N related columns */
168
- oneToManyRelationships?: RelationshipMetadata[];
169
- /** Whether relationship data is loading */
170
- isLoadingRelationships?: boolean;
171
- /** Callback to load attributes for a related entity */
172
- onLoadRelatedAttributes?: (entityLogicalName: string) => Promise<AttributeMetadata[]>;
173
- /** Callback to reset columns to default */
174
- onResetToDefault?: () => void;
175
- // Record action callbacks
176
- /** Callback when user clicks Open (opens record(s) in new tab) */
177
- onOpenRecord?: (recordIds: string[]) => void;
178
- /** Callback when user clicks Copy URL (copies record URL(s)) */
179
- onCopyRecordUrl?: (recordIds: string[]) => void;
180
- /** Callback when user clicks Delete */
181
- onDeleteRecords?: (recordIds: string[]) => void;
182
- /** Callback when user clicks Bulk Delete */
183
- onBulkDeleteRecords?: (recordIds: string[]) => void;
184
- /** Callback when user clicks Activate */
185
- onActivateRecords?: (recordIds: string[]) => void;
186
- /** Callback when user clicks Deactivate */
187
- onDeactivateRecords?: (recordIds: string[]) => void;
188
- /** Callback when user wants to run a specific workflow directly */
189
- onRunSpecificWorkflow?: (workflow: WorkflowInfo, recordIds: string[]) => void;
190
- /** Whether user can delete records */
191
- canDelete?: boolean;
192
- /** Whether user can bulk delete records */
193
- canBulkDelete?: boolean;
194
- /** Whether user can run workflows */
195
- canRunWorkflow?: boolean;
196
- /** Fetch available workflows for the entity */
197
- onFetchWorkflows?: () => Promise<WorkflowInfo[]>;
198
- /** Whether the current query is an aggregate query (disables delete/workflow) */
199
- isAggregateQuery?: boolean;
200
- /** Whether the query has 1-N or N-N relationships that cause row duplication */
201
- hasOneToManyRelationship?: boolean;
202
- /** Callback when selection changes in ResultsGrid */
203
- onSelectionChange?: (recordIds: string[]) => void;
204
- /** Callback to get currently selected record IDs */
205
- getSelectedRecordIds?: () => string[];
206
- /** Callback when user clicks Settings button */
207
- onOpenSettings?: () => void;
208
- /** Display settings (logical names, value format) */
209
- displaySettings?: DisplaySettings;
210
- }
211
-
212
- export function PreviewTabs({
213
- xml,
214
- layoutXml,
215
- result,
216
- isExecuting,
217
- isLoadingMore,
218
- onExecute,
219
- onExport,
220
- onExportLocal,
221
- onParseToTree,
222
- attributeMetadata,
223
- fetchQuery,
224
- columnConfig,
225
- onColumnResize,
226
- onReorderColumns,
227
- onAddColumn,
228
- onAddRelatedColumns,
229
- onRemoveColumn,
230
- onSortChange,
231
- saveViewButton,
232
- onLoadMore,
233
- canExport,
234
- isExporting,
235
- exportError,
236
- onDismissExportError,
237
- exportDisabledReason,
238
- entityDisplayName,
239
- lookupRelationships,
240
- oneToManyRelationships,
241
- isLoadingRelationships,
242
- onLoadRelatedAttributes,
243
- onResetToDefault,
244
- onOpenRecord,
245
- onCopyRecordUrl,
246
- onDeleteRecords,
247
- onBulkDeleteRecords,
248
- onActivateRecords,
249
- onDeactivateRecords,
250
- onRunSpecificWorkflow,
251
- canDelete,
252
- canBulkDelete,
253
- canRunWorkflow,
254
- onFetchWorkflows,
255
- isAggregateQuery,
256
- hasOneToManyRelationship,
257
- onSelectionChange,
258
- getSelectedRecordIds,
259
- onOpenSettings,
260
- displaySettings,
261
- }: PreviewTabsProps) {
262
- const styles = useStyles();
263
- const [selectedTab, setSelectedTab] = useState<"xml" | "layout" | "results">("xml");
264
- const [toolbarSelectedCount, setToolbarSelectedCount] = useState(0);
265
- const [selectedRecordIds, setSelectedRecordIds] = useState<string[]>([]);
266
-
267
- const handleTabSelect = (_event: SelectTabEvent, data: SelectTabData) => {
268
- setSelectedTab(data.value as "xml" | "layout" | "results");
269
- };
270
-
271
- const handleExecute = () => {
272
- // Switch to Results tab when executing
273
- setSelectedTab("results");
274
- onExecute?.();
275
- };
276
-
277
- const handleSelectionChange = useCallback(
278
- (recordIds: string[]) => {
279
- setSelectedRecordIds(recordIds);
280
- // Also notify parent
281
- onSelectionChange?.(recordIds);
282
- },
283
- [onSelectionChange]
284
- );
285
-
286
- // If parent provided a getter, use ours; otherwise use internal state
287
- const getSelectedIds = useCallback(() => {
288
- return getSelectedRecordIds ? getSelectedRecordIds() : selectedRecordIds;
289
- }, [getSelectedRecordIds, selectedRecordIds]);
290
-
291
- return (
292
- <div className={styles.container}>
293
- <div className={styles.header}>
294
- <TabList selectedValue={selectedTab} onTabSelect={handleTabSelect}>
295
- <Tab value="xml">FetchXML</Tab>
296
- <Tab value="layout">LayoutXML</Tab>
297
- <Tab value="results">Results</Tab>
298
- </TabList>
299
- <Toolbar size="small">
300
- <Button
301
- appearance="primary"
302
- icon={<Play24Regular />}
303
- onClick={handleExecute}
304
- disabled={isExecuting || isLoadingMore || !xml}
305
- >
306
- {isExecuting ? "Executing..." : "Execute"}
307
- </Button>
308
- {selectedTab === "xml" && saveViewButton && (
309
- <>
310
- <ToolbarDivider />
311
- {saveViewButton}
312
- </>
313
- )}
314
- <ToolbarDivider />
315
- <Button
316
- appearance="subtle"
317
- icon={<Settings20Regular />}
318
- onClick={onOpenSettings}
319
- aria-label="Settings"
320
- />
321
- </Toolbar>
322
- </div>
323
- {(isExecuting || isLoadingMore) && (
324
- <div className={styles.progressContainer}>
325
- <ProgressBar />
326
- </div>
327
- )}
328
- {/* Export status MessageBar */}
329
- {isExporting && (
330
- <div className={styles.messageBarContainer}>
331
- <MessageBar intent="info">
332
- <MessageBarBody>
333
- <MessageBarTitle>Exporting</MessageBarTitle>
334
- Exporting view to Excel...
335
- </MessageBarBody>
336
- </MessageBar>
337
- </div>
338
- )}
339
- {exportError && !isExporting && (
340
- <div className={styles.messageBarContainer}>
341
- <MessageBar intent="error">
342
- <MessageBarBody>
343
- <MessageBarTitle>Export Failed</MessageBarTitle>
344
- {exportError}
345
- </MessageBarBody>
346
- <MessageBarActions
347
- containerAction={
348
- <Button
349
- appearance="transparent"
350
- icon={<Dismiss16Regular />}
351
- onClick={onDismissExportError}
352
- aria-label="Dismiss"
353
- />
354
- }
355
- />
356
- </MessageBar>
357
- </div>
358
- )}
359
- <div className={styles.tabContent}>
360
- {selectedTab === "xml" && (
361
- <div className={styles.codeCard}>
362
- <FetchXmlEditor xml={xml} onParseToTree={onParseToTree} />
363
- </div>
364
- )}
365
- {selectedTab === "layout" && (
366
- <div className={styles.codeCard}>
367
- <LayoutXmlViewer layoutXml={layoutXml || ""} />
368
- </div>
369
- )}
370
- {selectedTab === "results" && (
371
- <div className={styles.resultsLayout}>
372
- <div className={styles.toolbarCard}>
373
- <ResultsCommandBar
374
- selectedCount={toolbarSelectedCount}
375
- onOpen={() => {
376
- const ids = getSelectedIds();
377
- if (ids?.length && onOpenRecord) {
378
- onOpenRecord(ids);
379
- }
380
- }}
381
- onCopyUrl={() => {
382
- const ids = getSelectedIds();
383
- if (ids?.length && onCopyRecordUrl) {
384
- onCopyRecordUrl(ids);
385
- }
386
- }}
387
- onActivate={() => {
388
- const ids = getSelectedIds();
389
- if (ids?.length && onActivateRecords) {
390
- onActivateRecords(ids);
391
- }
392
- }}
393
- onDeactivate={() => {
394
- const ids = getSelectedIds();
395
- if (ids?.length && onDeactivateRecords) {
396
- onDeactivateRecords(ids);
397
- }
398
- }}
399
- onDelete={() => {
400
- const ids = getSelectedIds();
401
- // For delete, we allow empty selection to trigger "delete all" dialog
402
- if (onDeleteRecords) {
403
- onDeleteRecords(ids || []);
404
- }
405
- }}
406
- onBulkDelete={() => {
407
- const ids = getSelectedIds();
408
- if (onBulkDeleteRecords) {
409
- onBulkDeleteRecords(ids || []);
410
- }
411
- }}
412
- canDelete={canDelete}
413
- canBulkDelete={canBulkDelete}
414
- deleteDisabled={isAggregateQuery || hasOneToManyRelationship}
415
- onRunSpecificWorkflow={(workflow: WorkflowInfo) => {
416
- const ids = getSelectedIds();
417
- if (ids?.length && onRunSpecificWorkflow) {
418
- onRunSpecificWorkflow(workflow, ids);
419
- }
420
- }}
421
- canRunWorkflow={canRunWorkflow}
422
- workflowDisabled={isAggregateQuery || hasOneToManyRelationship}
423
- onFetchWorkflows={onFetchWorkflows}
424
- onExport={onExport || (() => console.log("Export not yet implemented"))}
425
- onExportLocal={onExportLocal}
426
- canExport={canExport}
427
- isExporting={isExporting}
428
- exportDisabledReason={exportDisabledReason}
429
- entityName={result?.entityLogicalName}
430
- entityDisplayName={entityDisplayName}
431
- columns={columnConfig?.columns}
432
- onReorderColumns={onReorderColumns}
433
- onRemoveColumn={onRemoveColumn}
434
- availableAttributes={
435
- // Get root entity attributes from multi-entity map
436
- attributeMetadata && fetchQuery?.entity?.name
437
- ? Array.from(attributeMetadata.get(fetchQuery.entity.name)?.values() || [])
438
- : undefined
439
- }
440
- selectedAttributes={fetchQuery?.entity.attributes.map((a) => a.name)}
441
- onAddColumn={onAddColumn}
442
- onAddRelatedColumns={onAddRelatedColumns}
443
- lookupRelationships={lookupRelationships}
444
- oneToManyRelationships={oneToManyRelationships}
445
- isLoadingRelationships={isLoadingRelationships}
446
- onLoadRelatedAttributes={onLoadRelatedAttributes}
447
- onResetToDefault={onResetToDefault}
448
- />
449
- </div>
450
- <div className={styles.gridCard}>
451
- <ResultsGrid
452
- result={result}
453
- isLoading={isExecuting}
454
- isLoadingMore={isLoadingMore}
455
- attributeMetadata={attributeMetadata}
456
- fetchQuery={fetchQuery}
457
- onSelectedCountChange={setToolbarSelectedCount}
458
- onSelectionChange={handleSelectionChange}
459
- columnConfig={columnConfig}
460
- onColumnResize={onColumnResize}
461
- onSortChange={onSortChange}
462
- onLoadMore={onLoadMore}
463
- displaySettings={displaySettings}
464
- />
465
- </div>
466
- </div>
467
- )}
468
- </div>
469
- </div>
470
- );
471
- }