@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,612 +0,0 @@
1
- /**
2
- * SaveViewDialog - Dialog for saving views to Dataverse
3
- * Handles validation, name/description input, and view type selection
4
- */
5
-
6
- import { useState, useEffect, useCallback, useMemo } from "react";
7
- import {
8
- Dialog,
9
- DialogSurface,
10
- DialogBody,
11
- DialogTitle,
12
- DialogContent,
13
- DialogActions,
14
- Button,
15
- Input,
16
- Textarea,
17
- Field,
18
- Radio,
19
- RadioGroup,
20
- MessageBar,
21
- MessageBarTitle,
22
- MessageBarBody,
23
- Spinner,
24
- makeStyles,
25
- tokens,
26
- Divider,
27
- Text,
28
- } from "@fluentui/react-components";
29
- import {
30
- DismissRegular,
31
- InfoRegular,
32
- WarningRegular,
33
- ErrorCircleRegular,
34
- CheckmarkCircleRegular,
35
- } from "@fluentui/react-icons";
36
- import {
37
- validateFetchXmlExpression,
38
- checkSavedQueryPrivileges,
39
- checkUserQueryPrivileges,
40
- createSavedQuery,
41
- updateSavedQuery,
42
- createUserQuery,
43
- updateUserQuery,
44
- publishSavedQuery,
45
- addSolutionComponent,
46
- getUnmanagedSolutions,
47
- COMPONENT_TYPE_SAVEDQUERY,
48
- type ValidatorIssue,
49
- type Solution,
50
- } from "../../api/pptbClient";
51
- import { SolutionPicker } from "./SolutionPicker";
52
-
53
- const useStyles = makeStyles({
54
- surface: {
55
- maxWidth: "500px",
56
- width: "100%",
57
- },
58
- content: {
59
- display: "flex",
60
- flexDirection: "column",
61
- gap: "16px",
62
- },
63
- fieldGroup: {
64
- display: "flex",
65
- flexDirection: "column",
66
- gap: "12px",
67
- },
68
- validationSection: {
69
- display: "flex",
70
- flexDirection: "column",
71
- gap: "8px",
72
- },
73
- validationHeader: {
74
- display: "flex",
75
- alignItems: "center",
76
- gap: "8px",
77
- },
78
- validationList: {
79
- display: "flex",
80
- flexDirection: "column",
81
- gap: "6px",
82
- maxHeight: "150px",
83
- overflowY: "auto",
84
- paddingLeft: "4px",
85
- },
86
- validationItem: {
87
- display: "flex",
88
- alignItems: "flex-start",
89
- gap: "8px",
90
- fontSize: "13px",
91
- },
92
- severityIcon: {
93
- flexShrink: 0,
94
- marginTop: "2px",
95
- },
96
- severityLow: {
97
- color: tokens.colorPaletteBlueBackground2,
98
- },
99
- severityMedium: {
100
- color: tokens.colorPaletteYellowForeground1,
101
- },
102
- severityHigh: {
103
- color: tokens.colorPaletteRedForeground1,
104
- },
105
- severityCritical: {
106
- color: tokens.colorPaletteRedForeground1,
107
- },
108
- solutionInfo: {
109
- display: "flex",
110
- flexDirection: "column",
111
- gap: "4px",
112
- padding: "8px 12px",
113
- backgroundColor: tokens.colorNeutralBackground3,
114
- borderRadius: "4px",
115
- marginTop: "8px",
116
- },
117
- solutionLabel: {
118
- fontSize: "12px",
119
- color: tokens.colorNeutralForeground3,
120
- },
121
- solutionValue: {
122
- fontSize: "13px",
123
- fontWeight: 500,
124
- },
125
- overwriteWarning: {
126
- marginTop: "4px",
127
- },
128
- radioDescription: {
129
- fontSize: "12px",
130
- color: tokens.colorNeutralForeground3,
131
- marginLeft: "28px",
132
- marginTop: "-4px",
133
- },
134
- publishNote: {
135
- fontSize: "12px",
136
- color: tokens.colorNeutralForeground3,
137
- fontStyle: "italic",
138
- },
139
- });
140
-
141
- export type SaveViewDialogMode = "save" | "saveAs";
142
-
143
- interface SaveViewDialogProps {
144
- /** Whether the dialog is open */
145
- open: boolean;
146
- /** Close callback */
147
- onClose: () => void;
148
- /** Save complete callback */
149
- onSaveComplete: (viewId: string, viewType: "system" | "personal", viewName: string) => void;
150
- /** Dialog mode: save (overwrite) or saveAs (new) */
151
- mode: SaveViewDialogMode;
152
- /** Whether to publish after save (system views only) */
153
- shouldPublish: boolean;
154
- /** FetchXML to save */
155
- fetchXml: string;
156
- /** LayoutXML configuration */
157
- layoutXml: string;
158
- /** Entity logical name */
159
- entityLogicalName: string;
160
- /** Entity object type code */
161
- objectTypeCode: number;
162
- /** Primary ID attribute */
163
- primaryIdAttribute: string;
164
- /** Currently loaded view (for Save mode) */
165
- loadedView: {
166
- id: string;
167
- type: "system" | "personal";
168
- name: string;
169
- } | null;
170
- }
171
-
172
- interface ValidationState {
173
- status: "idle" | "validating" | "valid" | "warning" | "error";
174
- messages: ValidatorIssue[];
175
- errorMessage?: string;
176
- }
177
-
178
- export function SaveViewDialog({
179
- open,
180
- onClose,
181
- onSaveComplete,
182
- mode,
183
- shouldPublish,
184
- fetchXml,
185
- layoutXml,
186
- entityLogicalName,
187
- objectTypeCode: _objectTypeCode, // Reserved for future use
188
- primaryIdAttribute: _primaryIdAttribute, // Reserved for future use
189
- loadedView,
190
- }: SaveViewDialogProps) {
191
- const styles = useStyles();
192
-
193
- // Form state
194
- const [name, setName] = useState("");
195
- const [description, setDescription] = useState("");
196
- const [viewType, setViewType] = useState<"system" | "personal">("personal");
197
-
198
- // Solution state (for system views)
199
- const [selectedSolution, setSelectedSolution] = useState<Solution | null>(null);
200
- const [unmanagedSolutions, setUnmanagedSolutions] = useState<Solution[]>([]);
201
- const [solutionsLoading, setSolutionsLoading] = useState(false);
202
-
203
- // Privileges state - systemView privileges from checkSavedQueryPrivileges
204
- const [systemPrivileges, setSystemPrivileges] = useState<{
205
- canWrite: boolean;
206
- canPublish: boolean;
207
- } | null>(null);
208
- // Personal view privileges from checkUserQueryPrivileges
209
- const [personalPrivileges, setPersonalPrivileges] = useState<{
210
- canWrite: boolean;
211
- } | null>(null);
212
-
213
- // Validation state
214
- const [validation, setValidation] = useState<ValidationState>({
215
- status: "idle",
216
- messages: [],
217
- });
218
-
219
- // Save state
220
- const [isSaving, setIsSaving] = useState(false);
221
- const [saveError, setSaveError] = useState<string | null>(null);
222
-
223
- // Check if this is a "Save" to existing view
224
- const isOverwriteMode = mode === "save" && loadedView !== null;
225
-
226
- // Initialize form when dialog opens
227
- useEffect(() => {
228
- if (open) {
229
- if (isOverwriteMode && loadedView) {
230
- setName(loadedView.name);
231
- setViewType(loadedView.type);
232
- } else {
233
- setName("");
234
- setViewType("personal");
235
- }
236
- setDescription("");
237
- setSaveError(null);
238
- setValidation({ status: "idle", messages: [] });
239
- }
240
- }, [open, isOverwriteMode, loadedView]);
241
-
242
- // Check privileges when dialog opens
243
- useEffect(() => {
244
- if (open) {
245
- // Check both system and personal view privileges
246
- checkSavedQueryPrivileges().then(setSystemPrivileges);
247
- checkUserQueryPrivileges().then(setPersonalPrivileges);
248
- }
249
- }, [open]);
250
-
251
- // Load unmanaged solutions when system view is selected
252
- useEffect(() => {
253
- if (!open || viewType !== "system") {
254
- return;
255
- }
256
-
257
- async function loadSolutionInfo() {
258
- setSolutionsLoading(true);
259
- try {
260
- // Always load unmanaged solutions for the picker
261
- const solutions = await getUnmanagedSolutions();
262
- setUnmanagedSolutions(solutions);
263
- } catch (error) {
264
- console.error("Failed to load solution info:", error);
265
- setUnmanagedSolutions([]);
266
- } finally {
267
- setSolutionsLoading(false);
268
- }
269
- }
270
-
271
- loadSolutionInfo();
272
- }, [open, viewType]);
273
-
274
- // Auto-validate when dialog opens
275
- useEffect(() => {
276
- if (!open || !fetchXml) {
277
- return;
278
- }
279
-
280
- async function runValidation() {
281
- setValidation({ status: "validating", messages: [] });
282
-
283
- try {
284
- const result = await validateFetchXmlExpression(fetchXml);
285
- const messages = result.ValidationResults?.Messages || [];
286
-
287
- if (messages.length === 0) {
288
- setValidation({ status: "valid", messages: [] });
289
- } else {
290
- // Check for critical/high severity
291
- const hasCritical = messages.some((m) => m.Severity >= 2);
292
- setValidation({
293
- status: hasCritical ? "warning" : "warning",
294
- messages,
295
- });
296
- }
297
- } catch (error) {
298
- // Validation API call failed - this doesn't mean the FetchXML is invalid
299
- // Just skip validation and allow the user to proceed
300
- console.warn("FetchXML validation unavailable:", error);
301
- setValidation({
302
- status: "warning",
303
- messages: [],
304
- errorMessage: "Validation unavailable - FetchXML will be validated when saved",
305
- });
306
- }
307
- }
308
-
309
- runValidation();
310
- }, [open, fetchXml]);
311
-
312
- // Get severity icon
313
- const getSeverityIcon = useCallback(
314
- (severity: number) => {
315
- switch (severity) {
316
- case 0:
317
- return <InfoRegular className={`${styles.severityIcon} ${styles.severityLow}`} />;
318
- case 1:
319
- return <WarningRegular className={`${styles.severityIcon} ${styles.severityMedium}`} />;
320
- case 2:
321
- case 3:
322
- return <ErrorCircleRegular className={`${styles.severityIcon} ${styles.severityHigh}`} />;
323
- default:
324
- return <InfoRegular className={`${styles.severityIcon} ${styles.severityLow}`} />;
325
- }
326
- },
327
- [styles]
328
- );
329
-
330
- // Can save check
331
- const canSave = useMemo(() => {
332
- if (!name.trim()) return false;
333
- if (isSaving) return false;
334
- if (viewType === "system") {
335
- // System views require canWrite privilege (covers both create and update)
336
- if (!systemPrivileges?.canWrite) return false;
337
- } else {
338
- // Personal views require canWrite privilege
339
- if (!personalPrivileges?.canWrite) return false;
340
- }
341
- return true;
342
- }, [name, isSaving, viewType, systemPrivileges, personalPrivileges]);
343
-
344
- // Handle save
345
- const handleSave = useCallback(async () => {
346
- if (!canSave) return;
347
-
348
- setIsSaving(true);
349
- setSaveError(null);
350
-
351
- try {
352
- let viewId: string;
353
-
354
- if (viewType === "personal") {
355
- // Personal view
356
- if (isOverwriteMode && loadedView) {
357
- await updateUserQuery(loadedView.id, {
358
- name: name.trim(),
359
- fetchxml: fetchXml,
360
- layoutxml: layoutXml,
361
- description: description.trim() || undefined,
362
- });
363
- viewId = loadedView.id;
364
- } else {
365
- viewId = await createUserQuery({
366
- name: name.trim(),
367
- fetchxml: fetchXml,
368
- layoutxml: layoutXml,
369
- returnedtypecode: entityLogicalName,
370
- description: description.trim() || undefined,
371
- querytype: 0,
372
- });
373
- }
374
- } else {
375
- // System view
376
- if (isOverwriteMode && loadedView) {
377
- await updateSavedQuery(loadedView.id, {
378
- name: name.trim(),
379
- fetchxml: fetchXml,
380
- layoutxml: layoutXml,
381
- description: description.trim() || undefined,
382
- });
383
- viewId = loadedView.id;
384
- } else {
385
- viewId = await createSavedQuery({
386
- name: name.trim(),
387
- fetchxml: fetchXml,
388
- layoutxml: layoutXml,
389
- returnedtypecode: entityLogicalName,
390
- description: description.trim() || undefined,
391
- querytype: 0,
392
- });
393
-
394
- // Add to solution if selected
395
- if (selectedSolution) {
396
- await addSolutionComponent(
397
- viewId,
398
- COMPONENT_TYPE_SAVEDQUERY,
399
- selectedSolution.uniquename,
400
- false
401
- );
402
- }
403
- }
404
-
405
- // Publish if requested
406
- if (shouldPublish) {
407
- await publishSavedQuery(viewId);
408
- }
409
- }
410
-
411
- onSaveComplete(viewId, viewType, name.trim());
412
- } catch (error) {
413
- console.error("Failed to save view:", error);
414
- setSaveError(error instanceof Error ? error.message : "Failed to save view");
415
- } finally {
416
- setIsSaving(false);
417
- }
418
- }, [
419
- canSave,
420
- viewType,
421
- isOverwriteMode,
422
- loadedView,
423
- name,
424
- fetchXml,
425
- layoutXml,
426
- description,
427
- entityLogicalName,
428
- selectedSolution,
429
- shouldPublish,
430
- onSaveComplete,
431
- ]);
432
-
433
- // Dialog title
434
- const dialogTitle = useMemo(() => {
435
- if (isOverwriteMode) {
436
- return shouldPublish ? "Save and Publish View" : "Save View";
437
- }
438
- return "Save As New View";
439
- }, [isOverwriteMode, shouldPublish]);
440
-
441
- return (
442
- <Dialog open={open} onOpenChange={(_, data) => !data.open && onClose()}>
443
- <DialogSurface className={styles.surface}>
444
- <DialogBody>
445
- <DialogTitle
446
- action={
447
- <Button
448
- appearance="subtle"
449
- aria-label="Close"
450
- icon={<DismissRegular />}
451
- onClick={onClose}
452
- />
453
- }
454
- >
455
- {dialogTitle}
456
- </DialogTitle>
457
- <DialogContent className={styles.content}>
458
- {/* Validation Section */}
459
- <div className={styles.validationSection}>
460
- <div className={styles.validationHeader}>
461
- {validation.status === "validating" && (
462
- <>
463
- <Spinner size="tiny" />
464
- <Text>Validating FetchXML...</Text>
465
- </>
466
- )}
467
- {validation.status === "valid" && (
468
- <>
469
- <CheckmarkCircleRegular
470
- style={{ color: tokens.colorPaletteGreenForeground1 }}
471
- />
472
- <Text>FetchXML is valid</Text>
473
- </>
474
- )}
475
- {validation.status === "warning" && (
476
- <>
477
- <WarningRegular style={{ color: tokens.colorPaletteYellowForeground1 }} />
478
- <Text>
479
- {validation.messages.length} validation{" "}
480
- {validation.messages.length === 1 ? "message" : "messages"}
481
- </Text>
482
- </>
483
- )}
484
- {validation.status === "error" && (
485
- <>
486
- <ErrorCircleRegular style={{ color: tokens.colorPaletteRedForeground1 }} />
487
- <Text>{validation.errorMessage}</Text>
488
- </>
489
- )}
490
- </div>
491
-
492
- {validation.messages.length > 0 && (
493
- <div className={styles.validationList}>
494
- {validation.messages.map((msg, idx) => (
495
- <div key={idx} className={styles.validationItem}>
496
- {getSeverityIcon(msg.Severity)}
497
- <span>{msg.LocalizedMessageText}</span>
498
- </div>
499
- ))}
500
- </div>
501
- )}
502
- </div>
503
-
504
- <Divider />
505
-
506
- {/* Form Fields */}
507
- <div className={styles.fieldGroup}>
508
- <Field label="Name" required>
509
- <Input
510
- value={name}
511
- onChange={(_, data) => setName(data.value)}
512
- placeholder="Enter view name"
513
- disabled={isSaving}
514
- />
515
- </Field>
516
- <Field label="Description">
517
- <Textarea
518
- value={description}
519
- onChange={(_, data) => setDescription(data.value)}
520
- placeholder="Optional description"
521
- disabled={isSaving}
522
- rows={2}
523
- />
524
- </Field>
525
- {/* View Type Selection - only for Save As */}
526
- {!isOverwriteMode && (
527
- <Field label="View Type">
528
- <RadioGroup
529
- value={viewType}
530
- onChange={(_, data) => setViewType(data.value as "system" | "personal")}
531
- disabled={isSaving}
532
- >
533
- <Radio
534
- value="personal"
535
- label="Personal View"
536
- disabled={!personalPrivileges?.canWrite}
537
- />
538
- <div className={styles.radioDescription}>
539
- {personalPrivileges?.canWrite
540
- ? "Only visible to you"
541
- : "You don't have permission to create personal views"}
542
- </div>
543
- <Radio
544
- value="system"
545
- label="System View"
546
- disabled={!systemPrivileges?.canWrite}
547
- />
548
- <div className={styles.radioDescription}>
549
- {systemPrivileges?.canWrite
550
- ? "Visible to all users with access to this entity"
551
- : "You don't have permission to create system views"}
552
- </div>
553
- </RadioGroup>
554
- </Field>
555
- )}
556
- {/* Solution Picker for System Views */}
557
- {viewType === "system" && !isOverwriteMode && (
558
- <>
559
- {solutionsLoading ? (
560
- <div className={styles.solutionInfo}>
561
- <Spinner size="tiny" label="Loading solutions..." />
562
- </div>
563
- ) : (
564
- <SolutionPicker
565
- solutions={unmanagedSolutions}
566
- selectedSolution={selectedSolution}
567
- onSolutionChange={setSelectedSolution}
568
- disabled={isSaving}
569
- />
570
- )}
571
- </>
572
- )}{" "}
573
- {/* Overwrite Warning */}
574
- {isOverwriteMode && loadedView && (
575
- <MessageBar intent="warning" className={styles.overwriteWarning}>
576
- <MessageBarBody>
577
- <MessageBarTitle>Overwrite Warning</MessageBarTitle>
578
- This will overwrite the existing view "{loadedView.name}".
579
- </MessageBarBody>
580
- </MessageBar>
581
- )}
582
- {/* Publish Note */}
583
- {shouldPublish && viewType === "system" && (
584
- <Text className={styles.publishNote}>
585
- The view will be published after saving, making it immediately available to users.
586
- </Text>
587
- )}
588
- </div>
589
-
590
- {/* Save Error */}
591
- {saveError && (
592
- <MessageBar intent="error">
593
- <MessageBarBody>
594
- <MessageBarTitle>Save Failed</MessageBarTitle>
595
- {saveError}
596
- </MessageBarBody>
597
- </MessageBar>
598
- )}
599
- </DialogContent>
600
- <DialogActions>
601
- <Button appearance="secondary" onClick={onClose} disabled={isSaving}>
602
- Cancel
603
- </Button>
604
- <Button appearance="primary" onClick={handleSave} disabled={!canSave}>
605
- {isSaving ? <Spinner size="tiny" /> : shouldPublish ? "Save and Publish" : "Save"}
606
- </Button>
607
- </DialogActions>
608
- </DialogBody>
609
- </DialogSurface>
610
- </Dialog>
611
- );
612
- }