@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,178 +0,0 @@
1
- /**
2
- * Input picker for operators that require MULTIPLE values (e.g., in, not-in, contain-values)
3
- * Uses Fluent UI v9 TagPicker for multi-select picklists and DynamicValueList for other types
4
- */
5
-
6
- import { useState, useEffect } from "react";
7
- import {
8
- TagPicker,
9
- TagPickerControl,
10
- TagPickerGroup,
11
- TagPickerInput,
12
- TagPickerList,
13
- TagPickerOption,
14
- Tag,
15
- makeStyles,
16
- } from "@fluentui/react-components";
17
- import type { TagPickerProps } from "@fluentui/react-components";
18
- import { loadAttributeWithOptionSet } from "../../features/fetchxml/api/dataverseMetadata";
19
- import type { AttributeMetadata } from "../../features/fetchxml/api/pptbClient";
20
- import { DynamicValueList } from "./DynamicValueList";
21
-
22
- const useStyles = makeStyles({
23
- container: {
24
- width: "100%",
25
- },
26
- });
27
-
28
- interface MultiValuePickerProps {
29
- entityLogicalName: string;
30
- attributeLogicalName: string;
31
- attribute: AttributeMetadata;
32
- value?: (string | number)[]; // Array of values (mixed types supported from parser)
33
- onChange: (value: (string | number)[] | undefined) => void;
34
- placeholder?: string;
35
- }
36
-
37
- /**
38
- * MultiValuePicker component for multi-value operators like 'in', 'not-in', 'contain-values'
39
- */
40
- export function MultiValuePicker({
41
- entityLogicalName,
42
- attributeLogicalName,
43
- attribute,
44
- value = [],
45
- onChange,
46
- placeholder = "Enter value",
47
- }: MultiValuePickerProps) {
48
- const styles = useStyles();
49
- const [options, setOptions] = useState<Array<{ label: string; value: number }>>([]);
50
- const [selectedValues, setSelectedValues] = useState<string[]>((value ?? []).map(String));
51
-
52
- const isPicklist =
53
- attribute.AttributeType === "Picklist" ||
54
- attribute.AttributeType === "State" ||
55
- attribute.AttributeType === "Status";
56
-
57
- // Load picklist options when needed
58
- useEffect(() => {
59
- if (isPicklist) {
60
- loadAttributeWithOptionSet(entityLogicalName, attributeLogicalName)
61
- .then((metadata: AttributeMetadata) => {
62
- if (metadata.OptionSet?.Options) {
63
- const opts = metadata.OptionSet.Options.map((opt) => ({
64
- label: opt.Label?.UserLocalizedLabel?.Label || String(opt.Value),
65
- value: opt.Value,
66
- }));
67
- setOptions(opts);
68
- }
69
- })
70
- .catch((error: unknown) => {
71
- console.error("Failed to load OptionSet metadata:", error);
72
- });
73
- }
74
- }, [entityLogicalName, attributeLogicalName, isPicklist]);
75
-
76
- // Sync external value changes to internal state (for picklist only)
77
- useEffect(() => {
78
- if (!isPicklist) return;
79
-
80
- const newSelectedValues = (value ?? []).map(String);
81
- // Only update if the values actually changed (avoid infinite loop)
82
- const hasChanged =
83
- newSelectedValues.length !== selectedValues.length ||
84
- newSelectedValues.some((v, i) => v !== selectedValues[i]);
85
-
86
- if (hasChanged) {
87
- setSelectedValues(newSelectedValues);
88
- }
89
- // eslint-disable-next-line react-hooks/exhaustive-deps
90
- }, [value, isPicklist]); // selectedValues intentionally excluded to prevent infinite loop
91
-
92
- const handleDynamicListChange = (values: string[]) => {
93
- // Determine if we need to convert to numbers
94
- const isNumeric =
95
- attribute.AttributeType === "Integer" ||
96
- attribute.AttributeType === "BigInt" ||
97
- attribute.AttributeType === "Decimal" ||
98
- attribute.AttributeType === "Double" ||
99
- attribute.AttributeType === "Money";
100
-
101
- if (isNumeric) {
102
- const numbers = values.map((v) => parseFloat(v)).filter((n) => !isNaN(n));
103
- onChange(numbers.length > 0 ? numbers : undefined);
104
- } else {
105
- onChange(values.length > 0 ? values : undefined);
106
- }
107
- };
108
-
109
- const handlePicklistChange: TagPickerProps["onOptionSelect"] = (_, data) => {
110
- const selected = data.selectedOptions.map((opt) => parseInt(opt, 10)).filter((n) => !isNaN(n));
111
-
112
- // Update local state immediately for controlled component behavior
113
- setSelectedValues(data.selectedOptions);
114
-
115
- // Notify parent of the change (parent will update value prop, but useEffect will detect no change)
116
- onChange(selected.length > 0 ? selected : undefined);
117
- };
118
-
119
- if (isPicklist) {
120
- const availableOptions = options.filter((opt) => !selectedValues.includes(String(opt.value)));
121
-
122
- return (
123
- <div className={styles.container}>
124
- <TagPicker onOptionSelect={handlePicklistChange} selectedOptions={selectedValues}>
125
- <TagPickerControl>
126
- <TagPickerGroup aria-label="Selected values">
127
- {selectedValues.map((val) => {
128
- const opt = options.find((o) => String(o.value) === val);
129
- return (
130
- <Tag key={val} shape="rounded" value={val}>
131
- {opt ? `${opt.label} - ${opt.value}` : val}
132
- </Tag>
133
- );
134
- })}
135
- </TagPickerGroup>
136
- <TagPickerInput aria-label={placeholder} placeholder={placeholder} />
137
- </TagPickerControl>
138
- <TagPickerList>
139
- {availableOptions.length > 0 ? (
140
- availableOptions.map((opt) => (
141
- <TagPickerOption
142
- key={opt.value}
143
- value={String(opt.value)}
144
- text={`${opt.label} - ${opt.value}`}
145
- >
146
- {opt.label} - {opt.value}
147
- </TagPickerOption>
148
- ))
149
- ) : (
150
- <TagPickerOption value="no-options" text="No options available">
151
- No options available
152
- </TagPickerOption>
153
- )}
154
- </TagPickerList>
155
- </TagPicker>
156
- </div>
157
- );
158
- }
159
-
160
- // For non-picklist attributes, use dynamic value list with add/remove buttons
161
- const isNumeric =
162
- attribute.AttributeType === "Integer" ||
163
- attribute.AttributeType === "BigInt" ||
164
- attribute.AttributeType === "Decimal" ||
165
- attribute.AttributeType === "Double" ||
166
- attribute.AttributeType === "Money";
167
-
168
- return (
169
- <div className={styles.container}>
170
- <DynamicValueList
171
- values={(value ?? []).map(String)}
172
- onChange={handleDynamicListChange}
173
- placeholder={placeholder}
174
- inputType={isNumeric ? "number" : "text"}
175
- />
176
- </div>
177
- );
178
- }
@@ -1,156 +0,0 @@
1
- /**
2
- * Shared Numeric Input Picker component using Fluent UI v9 Input
3
- * Handles Integer, BigInt, Decimal, and Double attribute types
4
- * Sets min/max values and precision based on metadata
5
- */
6
-
7
- import { useState, useEffect } from "react";
8
- import { Input, useId } from "@fluentui/react-components";
9
- import type { AttributeMetadata } from "../../features/fetchxml/api/pptbClient";
10
-
11
- interface NumericInputPickerProps {
12
- attribute: AttributeMetadata;
13
- value?: number | string;
14
- onChange: (value: number | undefined) => void;
15
- placeholder?: string;
16
- disabled?: boolean;
17
- }
18
-
19
- export function NumericInputPicker({
20
- attribute,
21
- value,
22
- onChange,
23
- placeholder = "Enter a number",
24
- disabled = false,
25
- }: NumericInputPickerProps) {
26
- const inputId = useId("numeric-input");
27
- const [inputValue, setInputValue] = useState<string>("");
28
-
29
- // Format number to avoid scientific notation
30
- const formatNumber = (num: number): string => {
31
- if (Math.abs(num) < 1e-10 && num !== 0) {
32
- // Very small numbers - use high precision
33
- return num.toFixed(20).replace(/\.?0+$/, "");
34
- } else if (Number.isInteger(num)) {
35
- // Integer - no decimal point needed
36
- return num.toString();
37
- } else {
38
- // Decimal - preserve precision but remove trailing zeros
39
- return num.toFixed(10).replace(/\.?0+$/, "");
40
- }
41
- };
42
-
43
- // Sync input value with prop value
44
- useEffect(() => {
45
- if (value !== undefined && value !== null && value !== "") {
46
- const numValue = typeof value === "number" ? value : parseFloat(String(value));
47
- setInputValue(isNaN(numValue) ? String(value) : formatNumber(numValue));
48
- } else {
49
- setInputValue("");
50
- }
51
- }, [value]);
52
-
53
- // Determine input step based on attribute type and precision
54
- const getStep = (): number | undefined => {
55
- const attrType = attribute.AttributeType;
56
-
57
- // Integer and BigInt: whole numbers only
58
- if (attrType === "Integer" || attrType === "BigInt") {
59
- return 1;
60
- }
61
-
62
- // Decimal and Double: use precision if available
63
- if ((attrType === "Decimal" || attrType === "Double") && attribute.Precision !== undefined) {
64
- // step = 10^(-precision), e.g., precision 2 → step 0.01
65
- return Math.pow(10, -attribute.Precision);
66
- }
67
-
68
- return undefined;
69
- };
70
-
71
- // Round value to the attribute's precision
72
- const roundToPrecision = (val: number): number => {
73
- const attrType = attribute.AttributeType;
74
-
75
- // Integer and BigInt: round to whole number
76
- if (attrType === "Integer" || attrType === "BigInt") {
77
- return Math.round(val);
78
- }
79
-
80
- // Decimal and Double: round to precision
81
- if ((attrType === "Decimal" || attrType === "Double") && attribute.Precision !== undefined) {
82
- const multiplier = Math.pow(10, attribute.Precision);
83
- return Math.round(val * multiplier) / multiplier;
84
- }
85
-
86
- return val;
87
- };
88
-
89
- // Handle input change
90
- const handleChange: React.ChangeEventHandler<HTMLInputElement> = (ev) => {
91
- const newValue = ev.target.value;
92
- setInputValue(newValue);
93
-
94
- // Parse and validate
95
- if (newValue === "") {
96
- onChange(undefined);
97
- return;
98
- }
99
-
100
- const parsed = parseFloat(newValue);
101
- if (!isNaN(parsed)) {
102
- // Round to precision to avoid floating-point errors
103
- const rounded = roundToPrecision(parsed);
104
- onChange(rounded);
105
- }
106
- };
107
-
108
- // Handle blur to validate min/max
109
- const handleBlur = () => {
110
- if (inputValue === "") {
111
- onChange(undefined);
112
- return;
113
- }
114
-
115
- const parsed = parseFloat(inputValue);
116
- if (isNaN(parsed)) {
117
- // Invalid number, clear
118
- setInputValue("");
119
- onChange(undefined);
120
- return;
121
- }
122
-
123
- // Round to precision first to avoid floating-point errors
124
- let constrainedValue = roundToPrecision(parsed);
125
-
126
- // Enforce min/max constraints
127
- if (attribute.MinValue !== undefined && constrainedValue < attribute.MinValue) {
128
- constrainedValue = attribute.MinValue;
129
- }
130
-
131
- if (attribute.MaxValue !== undefined && constrainedValue > attribute.MaxValue) {
132
- constrainedValue = attribute.MaxValue;
133
- }
134
-
135
- // Update if constrained or rounded
136
- if (constrainedValue !== parsed) {
137
- setInputValue(String(constrainedValue));
138
- onChange(constrainedValue);
139
- }
140
- };
141
-
142
- return (
143
- <Input
144
- id={inputId}
145
- type="number"
146
- value={inputValue}
147
- onChange={handleChange}
148
- onBlur={handleBlur}
149
- placeholder={placeholder}
150
- disabled={disabled}
151
- min={attribute.MinValue}
152
- max={attribute.MaxValue}
153
- step={getStep()}
154
- />
155
- );
156
- }
@@ -1,150 +0,0 @@
1
- /**
2
- * Shared OptionSet Value Picker component using Fluent UI v9 Combobox
3
- * Loads option values from attribute metadata and displays as searchable dropdown
4
- * Supports Picklist, State, and Status attribute types
5
- */
6
-
7
- import { useState, useEffect } from "react";
8
- import { Combobox, useComboboxFilter, useId } from "@fluentui/react-components";
9
- import { loadAttributeWithOptionSet } from "../../features/fetchxml/api/dataverseMetadata";
10
- import type { AttributeMetadata } from "../../features/fetchxml/api/pptbClient";
11
-
12
- interface OptionSetOption {
13
- value: number;
14
- label: string;
15
- displayText: string; // "Label - Value" format
16
- }
17
-
18
- interface OptionSetValuePickerProps {
19
- entityLogicalName: string;
20
- attributeLogicalName: string;
21
- value?: number | string; // Can be number or string representation
22
- onChange: (value: number | undefined) => void;
23
- placeholder?: string;
24
- disabled?: boolean;
25
- }
26
-
27
- export function OptionSetValuePicker({
28
- entityLogicalName,
29
- attributeLogicalName,
30
- value,
31
- onChange,
32
- placeholder = "Select a value",
33
- disabled = false,
34
- }: OptionSetValuePickerProps) {
35
- const comboId = useId("optionset-combobox");
36
- const [options, setOptions] = useState<OptionSetOption[]>([]);
37
- const [loading, setLoading] = useState(false);
38
- const [error, setError] = useState<string | null>(null);
39
- const [query, setQuery] = useState<string>("");
40
-
41
- // Load option set values when entity/attribute changes
42
- useEffect(() => {
43
- if (!entityLogicalName || !attributeLogicalName) {
44
- setOptions([]);
45
- return;
46
- }
47
-
48
- setLoading(true);
49
- setError(null);
50
-
51
- loadAttributeWithOptionSet(entityLogicalName, attributeLogicalName)
52
- .then((attr: AttributeMetadata) => {
53
- if (!attr.OptionSet?.Options) {
54
- setError("No options found for this attribute");
55
- setOptions([]);
56
- setLoading(false);
57
- return;
58
- }
59
-
60
- // Convert metadata options to our format
61
- const optionsList: OptionSetOption[] = attr.OptionSet.Options.map((opt) => {
62
- const label = opt.Label?.UserLocalizedLabel?.Label || `Option ${opt.Value}`;
63
- return {
64
- value: opt.Value,
65
- label,
66
- displayText: `${label} - ${opt.Value}`,
67
- };
68
- });
69
-
70
- setOptions(optionsList);
71
- setLoading(false);
72
- })
73
- .catch((err: unknown) => {
74
- console.error("Failed to load option set values:", err);
75
- setError("Failed to load options");
76
- setLoading(false);
77
- });
78
- }, [entityLogicalName, attributeLogicalName]);
79
-
80
- // Sync query with value when value changes or options load
81
- useEffect(() => {
82
- if (value !== undefined && value !== null && value !== "") {
83
- const numValue = typeof value === "string" ? parseInt(value, 10) : value;
84
- const opt = options.find((o) => o.value === numValue);
85
- if (opt) {
86
- setQuery(opt.displayText);
87
- } else {
88
- setQuery(String(value));
89
- }
90
- } else {
91
- setQuery("");
92
- }
93
- }, [value, options]);
94
-
95
- // Convert options to combobox format and filter by both label and value
96
- const comboboxOptions = options
97
- .filter((opt) => {
98
- if (!query) return true;
99
- const searchLower = query.toLowerCase();
100
- return (
101
- opt.label.toLowerCase().includes(searchLower) || String(opt.value).includes(searchLower)
102
- );
103
- })
104
- .map((opt) => ({
105
- children: opt.displayText,
106
- value: String(opt.value), // Combobox uses string values
107
- }));
108
-
109
- // Apply Fluent UI's visual filtering only (we've already filtered the data)
110
- const filteredChildren = useComboboxFilter(query, comboboxOptions, {
111
- noOptionsMessage: loading ? "Loading options..." : error || "No options found",
112
- filter: () => true, // Always return true since we pre-filtered
113
- });
114
-
115
- const handleChange: React.ChangeEventHandler<HTMLInputElement> = (ev) => {
116
- setQuery(ev.target.value);
117
- };
118
-
119
- const handleOptionSelect: NonNullable<React.ComponentProps<typeof Combobox>["onOptionSelect"]> = (
120
- _ev,
121
- data
122
- ) => {
123
- if (!data.optionValue) {
124
- // Clear button clicked
125
- setQuery("");
126
- onChange(undefined);
127
- return;
128
- }
129
-
130
- const selectedOption = options.find((opt) => String(opt.value) === data.optionValue);
131
- if (selectedOption) {
132
- setQuery(selectedOption.displayText);
133
- onChange(selectedOption.value);
134
- }
135
- };
136
-
137
- return (
138
- <Combobox
139
- aria-labelledby={comboId}
140
- placeholder={placeholder}
141
- value={query}
142
- onChange={handleChange}
143
- onOptionSelect={handleOptionSelect}
144
- disabled={disabled || loading}
145
- clearable
146
- >
147
- {filteredChildren}
148
- </Combobox>
149
- );
150
- }