@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.
- package/LICENSE +661 -0
- package/README.md +1 -1
- package/dist/assets/index-DD9fzkbz.js +232 -0
- package/dist/index.html +70 -0
- package/index.html +0 -1
- package/npm-shrinkwrap.json +6329 -0
- package/package.json +95 -51
- package/CHANGELOG.md +0 -120
- package/eslint.config.js +0 -23
- package/public/vite.svg +0 -1
- package/src/app/AppShell.tsx +0 -1733
- package/src/assets/20px-Microsoft_Office_Excel_(2025/342/200/223present).svg.png +0 -0
- package/src/assets/react.svg +0 -1
- package/src/features/fetchxml/api/dataverseMetadata.ts +0 -192
- package/src/features/fetchxml/api/excelExport.ts +0 -318
- package/src/features/fetchxml/api/formattedValues.ts +0 -107
- package/src/features/fetchxml/api/pptbClient.ts +0 -2635
- package/src/features/fetchxml/model/displaySettings.ts +0 -18
- package/src/features/fetchxml/model/fetchxml.ts +0 -346
- package/src/features/fetchxml/model/fetchxmlIntellisense.ts +0 -583
- package/src/features/fetchxml/model/fetchxmlParser.ts +0 -708
- package/src/features/fetchxml/model/layoutxml.ts +0 -372
- package/src/features/fetchxml/model/nodes.ts +0 -255
- package/src/features/fetchxml/model/operators.ts +0 -624
- package/src/features/fetchxml/model/treeUtils.ts +0 -174
- package/src/features/fetchxml/state/builderStore.tsx +0 -1160
- package/src/features/fetchxml/state/cache.ts +0 -393
- package/src/features/fetchxml/ui/Dialogs/BulkDeleteDialog.tsx +0 -352
- package/src/features/fetchxml/ui/Dialogs/DeleteConfirmDialog.tsx +0 -396
- package/src/features/fetchxml/ui/Dialogs/SaveViewDialog.tsx +0 -612
- package/src/features/fetchxml/ui/Dialogs/SolutionPicker.tsx +0 -114
- package/src/features/fetchxml/ui/Dialogs/WorkflowPickerDialog.tsx +0 -437
- package/src/features/fetchxml/ui/LeftPane/PropertiesPanel/editors/AttributeEditor.tsx +0 -214
- package/src/features/fetchxml/ui/LeftPane/PropertiesPanel/editors/ConditionEditor.tsx +0 -533
- package/src/features/fetchxml/ui/LeftPane/PropertiesPanel/editors/FetchOptionsEditor.tsx +0 -296
- package/src/features/fetchxml/ui/LeftPane/PropertiesPanel/editors/FilterEditor.tsx +0 -106
- package/src/features/fetchxml/ui/LeftPane/PropertiesPanel/editors/LinkEntityEditor.tsx +0 -433
- package/src/features/fetchxml/ui/LeftPane/PropertiesPanel/editors/OrderEditor.tsx +0 -161
- package/src/features/fetchxml/ui/LeftPane/PropertiesPanel/editors/ValueOfPicker.tsx +0 -194
- package/src/features/fetchxml/ui/LeftPane/PropertiesPanel.tsx +0 -225
- package/src/features/fetchxml/ui/LeftPane/TreeView.tsx +0 -604
- package/src/features/fetchxml/ui/RightPane/AddColumnsPanel.tsx +0 -800
- package/src/features/fetchxml/ui/RightPane/ColumnConfigDialog.tsx +0 -170
- package/src/features/fetchxml/ui/RightPane/DataGridCellRenderers.tsx +0 -356
- package/src/features/fetchxml/ui/RightPane/EditColumnsPanel.tsx +0 -372
- package/src/features/fetchxml/ui/RightPane/FetchXmlEditor.tsx +0 -399
- package/src/features/fetchxml/ui/RightPane/FormattedValueUtils.ts +0 -189
- package/src/features/fetchxml/ui/RightPane/LayoutXmlViewer.tsx +0 -171
- package/src/features/fetchxml/ui/RightPane/PreviewTabs.tsx +0 -471
- package/src/features/fetchxml/ui/RightPane/ResultsCommandBar.tsx +0 -430
- package/src/features/fetchxml/ui/RightPane/ResultsGrid.tsx +0 -983
- package/src/features/fetchxml/ui/RightPane/XmlTextArea.tsx +0 -65
- package/src/features/fetchxml/ui/Settings/SettingsDrawer.tsx +0 -175
- package/src/features/fetchxml/ui/Toolbar/EntitySelector.tsx +0 -788
- package/src/features/fetchxml/ui/Toolbar/LoadViewPicker.tsx +0 -274
- package/src/features/fetchxml/ui/Toolbar/SaveViewButton.tsx +0 -174
- package/src/main.tsx +0 -11
- package/src/shared/components/AttributePicker.tsx +0 -138
- package/src/shared/components/BetweenInputPicker.tsx +0 -120
- package/src/shared/components/BooleanValuePicker.tsx +0 -165
- package/src/shared/components/DateTimeInputPicker.tsx +0 -170
- package/src/shared/components/DynamicValueList.tsx +0 -128
- package/src/shared/components/ExcelIcon.tsx +0 -30
- package/src/shared/components/MultiValuePicker.tsx +0 -178
- package/src/shared/components/NumericInputPicker.tsx +0 -156
- package/src/shared/components/OptionSetValuePicker.tsx +0 -150
- package/src/shared/components/RelationshipPicker.tsx +0 -268
- package/src/shared/contexts/ThemeContext.tsx +0 -19
- package/src/shared/hooks/useAccessMode.ts +0 -77
- package/src/shared/hooks/useLazyMetadata.ts +0 -132
- package/src/shared/hooks/usePptbContext.ts +0 -170
- package/src/shared/hooks/usePublisherFilter.ts +0 -214
- package/src/shared/hooks/useSolutionFilter.ts +0 -162
- package/src/shared/utils/debug.ts +0 -87
- package/tsconfig.app.json +0 -28
- package/tsconfig.json +0 -7
- package/tsconfig.node.json +0 -26
- package/vite.config.ts +0 -7
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Column Configuration Dialog
|
|
3
|
-
* Allows users to reorder columns by moving them up/down
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { useState, useCallback, useEffect } from "react";
|
|
7
|
-
import {
|
|
8
|
-
Dialog,
|
|
9
|
-
DialogSurface,
|
|
10
|
-
DialogTitle,
|
|
11
|
-
DialogBody,
|
|
12
|
-
DialogContent,
|
|
13
|
-
DialogActions,
|
|
14
|
-
Button,
|
|
15
|
-
makeStyles,
|
|
16
|
-
tokens,
|
|
17
|
-
} from "@fluentui/react-components";
|
|
18
|
-
import { ArrowUp24Regular, ArrowDown24Regular } from "@fluentui/react-icons";
|
|
19
|
-
import type { LayoutColumn } from "../../model/layoutxml";
|
|
20
|
-
|
|
21
|
-
const useStyles = makeStyles({
|
|
22
|
-
columnList: {
|
|
23
|
-
display: "flex",
|
|
24
|
-
flexDirection: "column",
|
|
25
|
-
gap: tokens.spacingVerticalXS,
|
|
26
|
-
maxHeight: "400px",
|
|
27
|
-
overflowY: "auto",
|
|
28
|
-
padding: tokens.spacingVerticalS,
|
|
29
|
-
border: `1px solid ${tokens.colorNeutralStroke1}`,
|
|
30
|
-
borderRadius: tokens.borderRadiusMedium,
|
|
31
|
-
},
|
|
32
|
-
columnItem: {
|
|
33
|
-
display: "flex",
|
|
34
|
-
alignItems: "center",
|
|
35
|
-
justifyContent: "space-between",
|
|
36
|
-
padding: `${tokens.spacingVerticalS} ${tokens.spacingHorizontalM}`,
|
|
37
|
-
backgroundColor: tokens.colorNeutralBackground1,
|
|
38
|
-
border: `1px solid ${tokens.colorNeutralStroke2}`,
|
|
39
|
-
borderRadius: tokens.borderRadiusSmall,
|
|
40
|
-
"&:hover": {
|
|
41
|
-
backgroundColor: tokens.colorNeutralBackground1Hover,
|
|
42
|
-
},
|
|
43
|
-
},
|
|
44
|
-
columnName: {
|
|
45
|
-
flex: 1,
|
|
46
|
-
overflow: "hidden",
|
|
47
|
-
textOverflow: "ellipsis",
|
|
48
|
-
whiteSpace: "nowrap",
|
|
49
|
-
},
|
|
50
|
-
buttonGroup: {
|
|
51
|
-
display: "flex",
|
|
52
|
-
gap: tokens.spacingHorizontalXS,
|
|
53
|
-
},
|
|
54
|
-
indexBadge: {
|
|
55
|
-
minWidth: "24px",
|
|
56
|
-
textAlign: "center",
|
|
57
|
-
color: tokens.colorNeutralForeground3,
|
|
58
|
-
fontSize: tokens.fontSizeBase200,
|
|
59
|
-
marginRight: tokens.spacingHorizontalS,
|
|
60
|
-
},
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
interface ColumnConfigDialogProps {
|
|
64
|
-
/** Whether the dialog is open */
|
|
65
|
-
open: boolean;
|
|
66
|
-
/** Columns to display for reordering */
|
|
67
|
-
columns: LayoutColumn[];
|
|
68
|
-
/** Called when dialog is closed without applying */
|
|
69
|
-
onClose: () => void;
|
|
70
|
-
/** Called when Apply is clicked with the new column order */
|
|
71
|
-
onReorder: (columns: LayoutColumn[]) => void;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export function ColumnConfigDialog({ open, columns, onClose, onReorder }: ColumnConfigDialogProps) {
|
|
75
|
-
const styles = useStyles();
|
|
76
|
-
const [localColumns, setLocalColumns] = useState<LayoutColumn[]>([]);
|
|
77
|
-
|
|
78
|
-
// Initialize local state when dialog opens or columns change
|
|
79
|
-
useEffect(() => {
|
|
80
|
-
if (open && columns.length > 0) {
|
|
81
|
-
setLocalColumns([...columns]);
|
|
82
|
-
}
|
|
83
|
-
}, [open, columns]);
|
|
84
|
-
|
|
85
|
-
const moveColumn = useCallback((index: number, direction: "up" | "down") => {
|
|
86
|
-
setLocalColumns((prev) => {
|
|
87
|
-
const newColumns = [...prev];
|
|
88
|
-
const targetIndex = direction === "up" ? index - 1 : index + 1;
|
|
89
|
-
|
|
90
|
-
if (targetIndex < 0 || targetIndex >= newColumns.length) {
|
|
91
|
-
return prev;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Swap columns
|
|
95
|
-
[newColumns[index], newColumns[targetIndex]] = [newColumns[targetIndex], newColumns[index]];
|
|
96
|
-
return newColumns;
|
|
97
|
-
});
|
|
98
|
-
}, []);
|
|
99
|
-
|
|
100
|
-
const handleApply = useCallback(() => {
|
|
101
|
-
onReorder(localColumns);
|
|
102
|
-
}, [localColumns, onReorder]);
|
|
103
|
-
|
|
104
|
-
if (columns.length === 0) {
|
|
105
|
-
return null;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
return (
|
|
109
|
-
<Dialog open={open} onOpenChange={(_e, data) => !data.open && onClose()}>
|
|
110
|
-
<DialogSurface>
|
|
111
|
-
<DialogBody>
|
|
112
|
-
<DialogTitle>Configure Columns</DialogTitle>
|
|
113
|
-
<DialogContent>
|
|
114
|
-
<p
|
|
115
|
-
style={{
|
|
116
|
-
marginBottom: tokens.spacingVerticalM,
|
|
117
|
-
color: tokens.colorNeutralForeground2,
|
|
118
|
-
}}
|
|
119
|
-
>
|
|
120
|
-
Use the arrow buttons to reorder columns. Changes apply when you click Apply.
|
|
121
|
-
</p>
|
|
122
|
-
<div className={styles.columnList}>
|
|
123
|
-
{localColumns.map((column, index) => (
|
|
124
|
-
<div key={column.name} className={styles.columnItem}>
|
|
125
|
-
<span className={styles.indexBadge}>{index + 1}</span>
|
|
126
|
-
<span className={styles.columnName}>{column.displayName || column.name}</span>
|
|
127
|
-
<span
|
|
128
|
-
style={{
|
|
129
|
-
color: tokens.colorNeutralForeground3,
|
|
130
|
-
fontSize: tokens.fontSizeBase200,
|
|
131
|
-
marginRight: tokens.spacingHorizontalS,
|
|
132
|
-
}}
|
|
133
|
-
>
|
|
134
|
-
{column.width}px
|
|
135
|
-
</span>
|
|
136
|
-
<div className={styles.buttonGroup}>
|
|
137
|
-
<Button
|
|
138
|
-
appearance="subtle"
|
|
139
|
-
size="small"
|
|
140
|
-
icon={<ArrowUp24Regular />}
|
|
141
|
-
disabled={index === 0}
|
|
142
|
-
onClick={() => moveColumn(index, "up")}
|
|
143
|
-
title="Move up"
|
|
144
|
-
/>
|
|
145
|
-
<Button
|
|
146
|
-
appearance="subtle"
|
|
147
|
-
size="small"
|
|
148
|
-
icon={<ArrowDown24Regular />}
|
|
149
|
-
disabled={index === localColumns.length - 1}
|
|
150
|
-
onClick={() => moveColumn(index, "down")}
|
|
151
|
-
title="Move down"
|
|
152
|
-
/>
|
|
153
|
-
</div>
|
|
154
|
-
</div>
|
|
155
|
-
))}
|
|
156
|
-
</div>
|
|
157
|
-
</DialogContent>
|
|
158
|
-
<DialogActions>
|
|
159
|
-
<Button appearance="secondary" onClick={onClose}>
|
|
160
|
-
Cancel
|
|
161
|
-
</Button>
|
|
162
|
-
<Button appearance="primary" onClick={handleApply}>
|
|
163
|
-
Apply
|
|
164
|
-
</Button>
|
|
165
|
-
</DialogActions>
|
|
166
|
-
</DialogBody>
|
|
167
|
-
</DialogSurface>
|
|
168
|
-
</Dialog>
|
|
169
|
-
);
|
|
170
|
-
}
|
|
@@ -1,356 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Rich cell renderers for different attribute types in DataGrid
|
|
3
|
-
* Renders values using appropriate Fluent UI v9 components
|
|
4
|
-
* Prefers formatted values from OData annotations when available
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { Switch, Badge, Label, SkeletonItem, makeStyles, tokens } from "@fluentui/react-components";
|
|
8
|
-
import type { AttributeMetadata } from "../../api/pptbClient";
|
|
9
|
-
|
|
10
|
-
const useStyles = makeStyles({
|
|
11
|
-
booleanCell: {
|
|
12
|
-
display: "flex",
|
|
13
|
-
alignItems: "center",
|
|
14
|
-
gap: tokens.spacingHorizontalXS,
|
|
15
|
-
},
|
|
16
|
-
lookupCell: {
|
|
17
|
-
display: "flex",
|
|
18
|
-
flexDirection: "column",
|
|
19
|
-
gap: "2px",
|
|
20
|
-
},
|
|
21
|
-
lookupName: {
|
|
22
|
-
fontSize: tokens.fontSizeBase200,
|
|
23
|
-
fontWeight: tokens.fontWeightSemibold,
|
|
24
|
-
},
|
|
25
|
-
lookupType: {
|
|
26
|
-
fontSize: tokens.fontSizeBase100,
|
|
27
|
-
color: tokens.colorNeutralForeground3,
|
|
28
|
-
},
|
|
29
|
-
numberCell: {
|
|
30
|
-
textAlign: "right",
|
|
31
|
-
fontVariantNumeric: "tabular-nums",
|
|
32
|
-
},
|
|
33
|
-
dateCell: {
|
|
34
|
-
fontVariantNumeric: "tabular-nums",
|
|
35
|
-
},
|
|
36
|
-
picklistCell: {
|
|
37
|
-
display: "flex",
|
|
38
|
-
alignItems: "center",
|
|
39
|
-
},
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Render a boolean/two-option value as a read-only Switch
|
|
44
|
-
* Uses formatted value from OData annotations if available
|
|
45
|
-
*/
|
|
46
|
-
export function BooleanCellRenderer({
|
|
47
|
-
value,
|
|
48
|
-
formattedValue,
|
|
49
|
-
attribute,
|
|
50
|
-
}: {
|
|
51
|
-
value: unknown;
|
|
52
|
-
formattedValue?: unknown;
|
|
53
|
-
attribute?: AttributeMetadata;
|
|
54
|
-
}) {
|
|
55
|
-
const styles = useStyles();
|
|
56
|
-
const boolValue = Boolean(value);
|
|
57
|
-
|
|
58
|
-
// Prefer formatted value, otherwise use metadata labels
|
|
59
|
-
const displayValue =
|
|
60
|
-
formattedValue ||
|
|
61
|
-
(boolValue
|
|
62
|
-
? attribute?.OptionSet?.TrueOption?.Label?.UserLocalizedLabel?.Label || "Yes"
|
|
63
|
-
: attribute?.OptionSet?.FalseOption?.Label?.UserLocalizedLabel?.Label || "No");
|
|
64
|
-
|
|
65
|
-
return (
|
|
66
|
-
<div className={styles.booleanCell}>
|
|
67
|
-
<Switch checked={boolValue} disabled />
|
|
68
|
-
<Label size="small">{String(displayValue)}</Label>
|
|
69
|
-
</div>
|
|
70
|
-
);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Render a picklist/state/status value as a Badge
|
|
75
|
-
* Uses formatted value from OData annotations if available
|
|
76
|
-
*/
|
|
77
|
-
export function PicklistCellRenderer({
|
|
78
|
-
value,
|
|
79
|
-
formattedValue,
|
|
80
|
-
attribute,
|
|
81
|
-
}: {
|
|
82
|
-
value: unknown;
|
|
83
|
-
formattedValue?: unknown;
|
|
84
|
-
attribute?: AttributeMetadata;
|
|
85
|
-
}) {
|
|
86
|
-
const styles = useStyles();
|
|
87
|
-
|
|
88
|
-
// Prefer formatted value
|
|
89
|
-
if (formattedValue) {
|
|
90
|
-
const appearance =
|
|
91
|
-
attribute?.AttributeType === "State" || attribute?.AttributeType === "Status"
|
|
92
|
-
? "filled"
|
|
93
|
-
: "tint";
|
|
94
|
-
return (
|
|
95
|
-
<div className={styles.picklistCell}>
|
|
96
|
-
<Badge appearance={appearance}>{String(formattedValue)}</Badge>
|
|
97
|
-
</div>
|
|
98
|
-
);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// Fallback to raw value with metadata lookup
|
|
102
|
-
const numValue = typeof value === "number" ? value : parseInt(String(value), 10);
|
|
103
|
-
|
|
104
|
-
if (isNaN(numValue)) {
|
|
105
|
-
return <span>—</span>;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Find the option label from metadata
|
|
109
|
-
const option = attribute?.OptionSet?.Options?.find((opt) => opt.Value === numValue);
|
|
110
|
-
const label = option?.Label?.UserLocalizedLabel?.Label || String(numValue);
|
|
111
|
-
|
|
112
|
-
// Use different badge colors for different states
|
|
113
|
-
const appearance =
|
|
114
|
-
attribute?.AttributeType === "State" || attribute?.AttributeType === "Status"
|
|
115
|
-
? "filled"
|
|
116
|
-
: "tint";
|
|
117
|
-
|
|
118
|
-
return (
|
|
119
|
-
<div className={styles.picklistCell}>
|
|
120
|
-
<Badge appearance={appearance}>{label}</Badge>
|
|
121
|
-
</div>
|
|
122
|
-
);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Render a lookup/customer/owner value
|
|
127
|
-
* Uses formatted value from OData annotations (preferred)
|
|
128
|
-
*/
|
|
129
|
-
export function LookupCellRenderer({
|
|
130
|
-
value,
|
|
131
|
-
formattedValue,
|
|
132
|
-
}: {
|
|
133
|
-
value: unknown;
|
|
134
|
-
formattedValue?: unknown;
|
|
135
|
-
}) {
|
|
136
|
-
const styles = useStyles();
|
|
137
|
-
|
|
138
|
-
// Prefer formatted value (just the name string)
|
|
139
|
-
if (formattedValue) {
|
|
140
|
-
return <span className={styles.lookupName}>{String(formattedValue)}</span>;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// Fallback to raw value (structured object)
|
|
144
|
-
if (!value || typeof value !== "object") {
|
|
145
|
-
return <span>—</span>;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
const lookupValue = value as { Id?: string; Name?: string; LogicalName?: string };
|
|
149
|
-
|
|
150
|
-
return (
|
|
151
|
-
<div className={styles.lookupCell}>
|
|
152
|
-
<span className={styles.lookupName}>{lookupValue.Name || "—"}</span>
|
|
153
|
-
{lookupValue.LogicalName && (
|
|
154
|
-
<span className={styles.lookupType}>{lookupValue.LogicalName}</span>
|
|
155
|
-
)}
|
|
156
|
-
</div>
|
|
157
|
-
);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* Render a numeric value (integer, decimal, money)
|
|
162
|
-
* Uses formatted value from OData annotations if available
|
|
163
|
-
*/
|
|
164
|
-
export function NumberCellRenderer({
|
|
165
|
-
value,
|
|
166
|
-
formattedValue,
|
|
167
|
-
attribute,
|
|
168
|
-
}: {
|
|
169
|
-
value: unknown;
|
|
170
|
-
formattedValue?: unknown;
|
|
171
|
-
attribute?: AttributeMetadata;
|
|
172
|
-
}) {
|
|
173
|
-
const styles = useStyles();
|
|
174
|
-
|
|
175
|
-
// Prefer formatted value (already includes currency symbol, etc.)
|
|
176
|
-
if (formattedValue) {
|
|
177
|
-
return <span className={styles.numberCell}>{String(formattedValue)}</span>;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// Fallback to manual formatting
|
|
181
|
-
if (value === null || value === undefined || value === "") {
|
|
182
|
-
return <span>—</span>;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
const numValue = typeof value === "number" ? value : parseFloat(String(value));
|
|
186
|
-
|
|
187
|
-
if (isNaN(numValue)) {
|
|
188
|
-
return <span>—</span>;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// Format based on attribute type
|
|
192
|
-
if (attribute?.AttributeType === "Money") {
|
|
193
|
-
// Format as currency (would need locale info for proper currency symbol)
|
|
194
|
-
return (
|
|
195
|
-
<span className={styles.numberCell}>
|
|
196
|
-
$
|
|
197
|
-
{numValue.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
|
|
198
|
-
</span>
|
|
199
|
-
);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
if (attribute?.AttributeType === "Integer" || attribute?.AttributeType === "BigInt") {
|
|
203
|
-
return <span className={styles.numberCell}>{numValue.toLocaleString()}</span>;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// Decimal/Double - use precision from metadata if available
|
|
207
|
-
const precision = attribute?.Precision ?? 2;
|
|
208
|
-
return (
|
|
209
|
-
<span className={styles.numberCell}>
|
|
210
|
-
{numValue.toLocaleString(undefined, {
|
|
211
|
-
minimumFractionDigits: 0,
|
|
212
|
-
maximumFractionDigits: precision,
|
|
213
|
-
})}
|
|
214
|
-
</span>
|
|
215
|
-
);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* Render a date/time value
|
|
220
|
-
* Uses formatted value from OData annotations if available
|
|
221
|
-
*/
|
|
222
|
-
export function DateTimeCellRenderer({
|
|
223
|
-
value,
|
|
224
|
-
formattedValue,
|
|
225
|
-
attribute,
|
|
226
|
-
}: {
|
|
227
|
-
value: unknown;
|
|
228
|
-
formattedValue?: unknown;
|
|
229
|
-
attribute?: AttributeMetadata;
|
|
230
|
-
}) {
|
|
231
|
-
const styles = useStyles();
|
|
232
|
-
|
|
233
|
-
// Prefer formatted value
|
|
234
|
-
if (formattedValue) {
|
|
235
|
-
return <span className={styles.dateCell}>{String(formattedValue)}</span>;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// Fallback to manual formatting
|
|
239
|
-
if (!value) {
|
|
240
|
-
return <span>—</span>;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
const dateValue = new Date(String(value));
|
|
244
|
-
|
|
245
|
-
if (isNaN(dateValue.getTime())) {
|
|
246
|
-
return <span>—</span>;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// Check if DateOnly format
|
|
250
|
-
const isDateOnly =
|
|
251
|
-
attribute?.Format === "DateOnly" || attribute?.DateTimeBehavior?.Value === "DateOnly";
|
|
252
|
-
|
|
253
|
-
if (isDateOnly) {
|
|
254
|
-
return (
|
|
255
|
-
<span className={styles.dateCell}>
|
|
256
|
-
{dateValue.toLocaleDateString(undefined, {
|
|
257
|
-
year: "numeric",
|
|
258
|
-
month: "short",
|
|
259
|
-
day: "numeric",
|
|
260
|
-
})}
|
|
261
|
-
</span>
|
|
262
|
-
);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// Full date-time
|
|
266
|
-
return (
|
|
267
|
-
<span className={styles.dateCell}>
|
|
268
|
-
{dateValue.toLocaleString(undefined, {
|
|
269
|
-
year: "numeric",
|
|
270
|
-
month: "short",
|
|
271
|
-
day: "numeric",
|
|
272
|
-
hour: "2-digit",
|
|
273
|
-
minute: "2-digit",
|
|
274
|
-
})}
|
|
275
|
-
</span>
|
|
276
|
-
);
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
/**
|
|
280
|
-
* Default text renderer for strings and other types
|
|
281
|
-
* Uses formatted value from OData annotations if available
|
|
282
|
-
*/
|
|
283
|
-
export function TextCellRenderer({
|
|
284
|
-
value,
|
|
285
|
-
formattedValue,
|
|
286
|
-
}: {
|
|
287
|
-
value: unknown;
|
|
288
|
-
formattedValue?: unknown;
|
|
289
|
-
}) {
|
|
290
|
-
// Prefer formatted value
|
|
291
|
-
const displayValue = formattedValue ?? value;
|
|
292
|
-
|
|
293
|
-
if (displayValue === null || displayValue === undefined || displayValue === "") {
|
|
294
|
-
return <span>—</span>;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
return <span>{String(displayValue)}</span>;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
/**
|
|
301
|
-
* Loading skeleton for virtualized cells
|
|
302
|
-
*/
|
|
303
|
-
export function LoadingCellRenderer() {
|
|
304
|
-
return <SkeletonItem />;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
/**
|
|
308
|
-
* Get the appropriate cell renderer for an attribute type
|
|
309
|
-
* Now accepts formatted value from OData annotations
|
|
310
|
-
*/
|
|
311
|
-
export function getCellRenderer(
|
|
312
|
-
attributeType: string | undefined,
|
|
313
|
-
value: unknown,
|
|
314
|
-
formattedValue: unknown | undefined,
|
|
315
|
-
attribute?: AttributeMetadata
|
|
316
|
-
) {
|
|
317
|
-
if (value === null || value === undefined) {
|
|
318
|
-
return <span>—</span>;
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
switch (attributeType) {
|
|
322
|
-
case "Boolean":
|
|
323
|
-
return (
|
|
324
|
-
<BooleanCellRenderer value={value} formattedValue={formattedValue} attribute={attribute} />
|
|
325
|
-
);
|
|
326
|
-
|
|
327
|
-
case "Picklist":
|
|
328
|
-
case "State":
|
|
329
|
-
case "Status":
|
|
330
|
-
return (
|
|
331
|
-
<PicklistCellRenderer value={value} formattedValue={formattedValue} attribute={attribute} />
|
|
332
|
-
);
|
|
333
|
-
|
|
334
|
-
case "Lookup":
|
|
335
|
-
case "Customer":
|
|
336
|
-
case "Owner":
|
|
337
|
-
return <LookupCellRenderer value={value} formattedValue={formattedValue} />;
|
|
338
|
-
|
|
339
|
-
case "Integer":
|
|
340
|
-
case "BigInt":
|
|
341
|
-
case "Decimal":
|
|
342
|
-
case "Double":
|
|
343
|
-
case "Money":
|
|
344
|
-
return (
|
|
345
|
-
<NumberCellRenderer value={value} formattedValue={formattedValue} attribute={attribute} />
|
|
346
|
-
);
|
|
347
|
-
|
|
348
|
-
case "DateTime":
|
|
349
|
-
return (
|
|
350
|
-
<DateTimeCellRenderer value={value} formattedValue={formattedValue} attribute={attribute} />
|
|
351
|
-
);
|
|
352
|
-
|
|
353
|
-
default:
|
|
354
|
-
return <TextCellRenderer value={value} formattedValue={formattedValue} />;
|
|
355
|
-
}
|
|
356
|
-
}
|