@dashadmin/dash-components 1.3.25 → 1.3.28
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/README.md +35 -0
- package/dist/components/AuditLog/AuditLog.js +345 -1
- package/dist/components/AuditLog/index.js +4 -1
- package/dist/components/AutoCompleteArrayInput/AutoCompleteCheckBoxInput.js +630 -3
- package/dist/components/AutoCompleteArrayInput/AutocompleteCheckBoxArrayInput.js +53 -1
- package/dist/components/AutoCompleteArrayInputAutoSearch/AutoCompleteCheckBoxInput.js +647 -3
- package/dist/components/AutoCompleteArrayInputAutoSearch/AutocompleteCheckBoxArrayInput.js +53 -1
- package/dist/components/Json/Json.js +235 -1
- package/dist/components/JsonColorSelector/JsonColorSelector.js +1172 -1
- package/dist/components/JsonColorSelector/JsonColorSelectorEnhanced.js +863 -2
- package/dist/components/JsonColorSelector/JsonCssVarValues.js +738 -1
- package/dist/components/JsonColorSelector/components/ColorEditDialog.js +426 -1
- package/dist/components/JsonColorSelector/components/ColorPaletteItem.js +75 -1
- package/dist/components/JsonColorSelector/components/ImageColorExtractor.js +283 -1
- package/dist/components/JsonColorSelector/helpers/functions.js +101 -1
- package/dist/components/JsonColorSelector/useAsyncColorThief.js +105 -1
- package/dist/components/ListActive/ListActive.js +126 -1
- package/dist/components/ListActive/ListBoolean.js +124 -1
- package/dist/components/NotificationPreferences/NotificationPreferences.js +317 -1
- package/dist/components/NotificationPreferences/index.js +7 -1
- package/dist/components/SearchableSelects/RASearchableSelect.js +236 -1
- package/dist/components/SearchableSelects/RASearchableSelectCheckboxes.js +479 -1
- package/dist/components/SearchableSelects/RASearchableSelectChips.js +465 -1
- package/dist/components/SearchableSelects/RASearchableSelectChipsRecordContext.js +295 -1
- package/dist/components/SearchableSelects/SearchableSelect.js +202 -1
- package/dist/components/SearchableSelects/SearchableSelectCheckboxes.js +290 -1
- package/dist/components/SortableDataGrid/SortableDataGrid.js +72 -1
- package/dist/components/SortableDataGrid/SortableDataGridBody.js +167 -1
- package/dist/components/SortableDataGrid/SortableDataGridHeader.js +109 -1
- package/dist/components/SortableDataGrid/SortableDataGridRow.js +283 -1
- package/dist/components/Upload/SingleImageUploader.js +40 -1
- package/dist/components/custom/PackageCopyMethod.js +78 -1
- package/dist/components/custom/PackageRadioButtonGroup.js +45 -1
- package/dist/components/dialog/Basic.js +8 -1
- package/dist/components/dialog/Correct.js +14 -1
- package/dist/components/dialog/Error.js +14 -1
- package/dist/components/dialog/Info.js +14 -1
- package/dist/components/dialog/InfoV2.js +21 -1
- package/dist/components/dialog/NotFound.js +14 -1
- package/dist/components/dialog/QuickSearch.js +117 -1
- package/dist/components/notifications/NotificationsCenter.js +597 -1
- package/dist/components/notifications/index.js +24 -1
- package/dist/components/tables/PaginationComponent.js +36 -1
- package/dist/components/theme/AppLoadingFallback.js +38 -2
- package/dist/components/theme/ErrorBoundary.js +52 -1
- package/dist/components/theme/InitialLoader.js +13 -1
- package/dist/components/theme/NotFound.js +136 -1
- package/dist/components/theme/NotResults.js +38 -1
- package/dist/hooks/useDraggable.js +6 -1
- package/dist/hooks/useNotifications.js +8 -1
- package/dist/hooks/useQuickSearch.js +26 -1
- package/dist/index.js +66 -5567
- package/dist/utils/setNativeValue.js +17 -1
- package/package.json +148 -132
- package/src/components/AuditLog/AuditLog.tsx +476 -0
- package/src/components/AuditLog/index.ts +2 -0
- package/src/components/AutoCompleteArrayInput/AutoCompleteCheckBoxInput.tsx +917 -0
- package/src/components/AutoCompleteArrayInput/AutocompleteCheckBoxArrayInput.tsx +39 -0
- package/src/components/AutoCompleteArrayInputAutoSearch/AutoCompleteCheckBoxInput.tsx +916 -0
- package/src/components/AutoCompleteArrayInputAutoSearch/AutocompleteCheckBoxArrayInput.tsx +44 -0
- package/src/components/Json/Json.tsx +312 -0
- package/src/components/JsonColorSelector/JsonColorSelector.tsx +1435 -0
- package/src/components/JsonColorSelector/JsonColorSelectorEnhanced.tsx +1029 -0
- package/src/components/JsonColorSelector/JsonCssVarValues.tsx +863 -0
- package/src/components/JsonColorSelector/components/ColorEditDialog.tsx +491 -0
- package/src/components/JsonColorSelector/components/ColorPaletteItem.tsx +80 -0
- package/src/components/JsonColorSelector/components/ImageColorExtractor.tsx +328 -0
- package/src/components/JsonColorSelector/helpers/functions.tsx +123 -0
- package/src/components/JsonColorSelector/interfaces/interfaces.tsx +22 -0
- package/src/components/JsonColorSelector/useAsyncColorThief.tsx +97 -0
- package/src/components/ListActive/ListActive.tsx +106 -0
- package/src/components/ListActive/ListBoolean.tsx +96 -0
- package/src/components/NotificationPreferences/NotificationPreferences.tsx +463 -0
- package/src/components/NotificationPreferences/index.tsx +2 -0
- package/src/components/SearchableSelects/RASearchableSelect.tsx +300 -0
- package/src/components/SearchableSelects/RASearchableSelectCheckboxes.tsx +604 -0
- package/src/components/SearchableSelects/RASearchableSelectChips.tsx +558 -0
- package/src/components/SearchableSelects/RASearchableSelectChipsRecordContext.tsx +362 -0
- package/src/components/SearchableSelects/SearchableSelect.tsx +321 -0
- package/src/components/SearchableSelects/SearchableSelectCheckboxes.tsx +355 -0
- package/src/components/SortableDataGrid/SortableDataGrid.tsx +56 -0
- package/src/components/SortableDataGrid/SortableDataGridBody.tsx +175 -0
- package/src/components/SortableDataGrid/SortableDataGridHeader.tsx +171 -0
- package/src/components/SortableDataGrid/SortableDataGridRow.tsx +247 -0
- package/src/components/Upload/SingleImageUploader.tsx +55 -0
- package/src/components/custom/PackageCopyMethod.tsx +109 -0
- package/src/components/custom/PackageRadioButtonGroup.tsx +47 -0
- package/src/components/dialog/Basic.tsx +11 -0
- package/src/components/dialog/Correct.tsx +16 -0
- package/src/components/dialog/Error.tsx +15 -0
- package/src/components/dialog/Info.tsx +15 -0
- package/src/components/dialog/InfoV2.tsx +35 -0
- package/src/components/dialog/NotFound.tsx +18 -0
- package/src/components/dialog/QuickSearch.tsx +115 -0
- package/src/components/notifications/NotificationsCenter.tsx +792 -0
- package/src/components/notifications/index.ts +42 -0
- package/src/components/tables/PaginationComponent.tsx +17 -0
- package/src/components/theme/AppLoadingFallback.tsx +42 -0
- package/src/components/theme/ErrorBoundary.tsx +57 -0
- package/src/components/theme/InitialLoader.tsx +16 -0
- package/src/components/theme/NotFound.tsx +124 -0
- package/src/components/theme/NotResults.tsx +54 -0
- package/src/hooks/useDraggable.ts +11 -0
- package/src/hooks/useNotifications.ts +14 -0
- package/src/hooks/useQuickSearch.tsx +29 -0
- package/src/index.tsx +77 -0
- package/src/utils/setNativeValue.tsx +23 -0
- package/dist/color-thief-CTwGFhOB.js +0 -272
- /package/{dist → src}/components/AutoCompleteArrayInputAutoSearch/AutocompleteCheckBoxArrayAutoSearch.tsx.wip +0 -0
- /package/{dist → src}/components/JsonColorSelector/JsonColorSelector.tsx.bkup +0 -0
- /package/{dist → src}/components/SortableDataGrid/README.md +0 -0
- /package/{dist → src}/styles/index.less +0 -0
|
@@ -0,0 +1,863 @@
|
|
|
1
|
+
import { IDashAutoAdminCustomFieldComponent } from 'dash-auto-admin';
|
|
2
|
+
import { useRecordContext } from 'react-admin';
|
|
3
|
+
import { useFormContext } from 'react-hook-form';
|
|
4
|
+
import {
|
|
5
|
+
Box,
|
|
6
|
+
Button,
|
|
7
|
+
IconButton,
|
|
8
|
+
TextField,
|
|
9
|
+
Typography,
|
|
10
|
+
Card,
|
|
11
|
+
CardContent,
|
|
12
|
+
Chip,
|
|
13
|
+
InputAdornment,
|
|
14
|
+
} from '@mui/material';
|
|
15
|
+
import DeleteIcon from '@mui/icons-material/Delete';
|
|
16
|
+
import AddIcon from '@mui/icons-material/Add';
|
|
17
|
+
import SearchIcon from '@mui/icons-material/Search';
|
|
18
|
+
import ClearIcon from '@mui/icons-material/Clear';
|
|
19
|
+
import { useState, useEffect, useCallback, useMemo } from 'react';
|
|
20
|
+
|
|
21
|
+
interface KeyValuePair {
|
|
22
|
+
key: string;
|
|
23
|
+
value: string;
|
|
24
|
+
id: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
type ValueType = 'string' | 'number' | 'boolean' | 'json';
|
|
28
|
+
|
|
29
|
+
// Helper function to detect value type
|
|
30
|
+
const detectValueType = (value: string): ValueType => {
|
|
31
|
+
if (value === 'true' || value === 'false') return 'boolean';
|
|
32
|
+
if (!isNaN(Number(value)) && value.trim() !== '') return 'number';
|
|
33
|
+
if ((value.startsWith('{') && value.endsWith('}')) ||
|
|
34
|
+
(value.startsWith('[') && value.endsWith(']'))) return 'json';
|
|
35
|
+
return 'string';
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// Helper function to format value for display
|
|
39
|
+
const formatValueForDisplay = (value: string, maxLength: number = 30): string => {
|
|
40
|
+
if (value.length <= maxLength) return value;
|
|
41
|
+
return value.substring(0, maxLength) + '...';
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// Inline editable value item component
|
|
45
|
+
const ValueItem: React.FC<{
|
|
46
|
+
pair: KeyValuePair;
|
|
47
|
+
onUpdate: (pair: KeyValuePair) => void;
|
|
48
|
+
onDelete: (id: string) => void;
|
|
49
|
+
existingKeys: string[];
|
|
50
|
+
}> = ({ pair, onUpdate, onDelete, existingKeys }) => {
|
|
51
|
+
const [isEditingKey, setIsEditingKey] = useState(false);
|
|
52
|
+
const [isEditingValue, setIsEditingValue] = useState(false);
|
|
53
|
+
const [editKey, setEditKey] = useState(pair.key);
|
|
54
|
+
const [editValue, setEditValue] = useState(pair.value);
|
|
55
|
+
const [keyError, setKeyError] = useState('');
|
|
56
|
+
const [valueError, setValueError] = useState('');
|
|
57
|
+
|
|
58
|
+
const valueType = detectValueType(pair.value);
|
|
59
|
+
|
|
60
|
+
// Color coding for different value types
|
|
61
|
+
const getTypeColor = (type: ValueType) => {
|
|
62
|
+
switch (type) {
|
|
63
|
+
case 'string': return '#2196f3'; // Blue
|
|
64
|
+
case 'number': return '#4caf50'; // Green
|
|
65
|
+
case 'boolean': return '#ff9800'; // Orange
|
|
66
|
+
case 'json': return '#9c27b0'; // Purple
|
|
67
|
+
default: return '#757575'; // Grey
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const validateKey = (key: string) => {
|
|
72
|
+
const isDuplicate = existingKeys.some(k => k === key && key !== pair.key);
|
|
73
|
+
setKeyError(isDuplicate ? 'Key already exists' : '');
|
|
74
|
+
return !isDuplicate && key.trim() !== '';
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const validateValue = (value: string, type: ValueType) => {
|
|
78
|
+
let error = '';
|
|
79
|
+
if (type === 'number' && isNaN(Number(value)) && value.trim() !== '') {
|
|
80
|
+
error = 'Invalid number format';
|
|
81
|
+
} else if (type === 'boolean' && !['true', 'false'].includes(value.toLowerCase())) {
|
|
82
|
+
error = 'Boolean must be "true" or "false"';
|
|
83
|
+
} else if (type === 'json') {
|
|
84
|
+
try {
|
|
85
|
+
JSON.parse(value);
|
|
86
|
+
} catch (e) {
|
|
87
|
+
error = 'Invalid JSON format';
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
setValueError(error);
|
|
91
|
+
return !error;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const handleKeySubmit = () => {
|
|
95
|
+
if (validateKey(editKey)) {
|
|
96
|
+
onUpdate({ ...pair, key: editKey });
|
|
97
|
+
setIsEditingKey(false);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const handleValueSubmit = () => {
|
|
102
|
+
const currentType = detectValueType(editValue);
|
|
103
|
+
if (validateValue(editValue, currentType)) {
|
|
104
|
+
onUpdate({ ...pair, value: editValue });
|
|
105
|
+
setIsEditingValue(false);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const handleKeyCancel = () => {
|
|
110
|
+
setEditKey(pair.key);
|
|
111
|
+
setKeyError('');
|
|
112
|
+
setIsEditingKey(false);
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const handleValueCancel = () => {
|
|
116
|
+
setEditValue(pair.value);
|
|
117
|
+
setValueError('');
|
|
118
|
+
setIsEditingValue(false);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
return (
|
|
122
|
+
<Card
|
|
123
|
+
variant="outlined"
|
|
124
|
+
sx={{
|
|
125
|
+
mb: 1,
|
|
126
|
+
//border: `2px solid ${getTypeColor(valueType)}`,
|
|
127
|
+
'&:hover': {
|
|
128
|
+
boxShadow: 2,
|
|
129
|
+
},
|
|
130
|
+
}}
|
|
131
|
+
>
|
|
132
|
+
<CardContent sx={{ p: 2, '&:last-child': { pb: 2 } }}>
|
|
133
|
+
<Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 2 }}>
|
|
134
|
+
{/* Key Section */}
|
|
135
|
+
<Box sx={{ flex: 1, minWidth: 0 }}>
|
|
136
|
+
<Typography variant="caption" color="textSecondary" sx={{ display: 'block', mb: 0.5 }}>
|
|
137
|
+
Key
|
|
138
|
+
</Typography>
|
|
139
|
+
{isEditingKey ? (
|
|
140
|
+
<TextField
|
|
141
|
+
fullWidth
|
|
142
|
+
size="small"
|
|
143
|
+
value={editKey}
|
|
144
|
+
onChange={(e) => {
|
|
145
|
+
setEditKey(e.target.value);
|
|
146
|
+
validateKey(e.target.value);
|
|
147
|
+
}}
|
|
148
|
+
error={!!keyError}
|
|
149
|
+
helperText={keyError}
|
|
150
|
+
onKeyDown={(e) => {
|
|
151
|
+
if (e.key === 'Enter') handleKeySubmit();
|
|
152
|
+
if (e.key === 'Escape') handleKeyCancel();
|
|
153
|
+
}}
|
|
154
|
+
onBlur={handleKeySubmit}
|
|
155
|
+
autoFocus
|
|
156
|
+
/>
|
|
157
|
+
) : (
|
|
158
|
+
<Box
|
|
159
|
+
onClick={() => setIsEditingKey(true)}
|
|
160
|
+
sx={{
|
|
161
|
+
cursor: 'pointer',
|
|
162
|
+
p: 1,
|
|
163
|
+
borderRadius: 1,
|
|
164
|
+
backgroundColor: 'rgba(0,0,0,0.04)',
|
|
165
|
+
'&:hover': {
|
|
166
|
+
backgroundColor: 'rgba(0,0,0,0.08)',
|
|
167
|
+
},
|
|
168
|
+
minHeight: 24,
|
|
169
|
+
display: 'flex',
|
|
170
|
+
alignItems: 'center',
|
|
171
|
+
}}
|
|
172
|
+
>
|
|
173
|
+
<Typography
|
|
174
|
+
variant="body2"
|
|
175
|
+
sx={{
|
|
176
|
+
fontFamily: 'monospace',
|
|
177
|
+
wordBreak: 'break-all',
|
|
178
|
+
}}
|
|
179
|
+
>
|
|
180
|
+
{pair.key || 'Click to add key'}
|
|
181
|
+
</Typography>
|
|
182
|
+
</Box>
|
|
183
|
+
)}
|
|
184
|
+
</Box>
|
|
185
|
+
|
|
186
|
+
{/* Value Section */}
|
|
187
|
+
<Box sx={{ flex: 1, minWidth: 0 }}>
|
|
188
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 0.5 }}>
|
|
189
|
+
<Typography variant="caption" color="textSecondary">
|
|
190
|
+
Value
|
|
191
|
+
</Typography>
|
|
192
|
+
<Chip
|
|
193
|
+
label={valueType.toUpperCase()}
|
|
194
|
+
size="small"
|
|
195
|
+
sx={{
|
|
196
|
+
height: 16,
|
|
197
|
+
fontSize: '0.6rem',
|
|
198
|
+
backgroundColor: getTypeColor(valueType),
|
|
199
|
+
color: '#fff',
|
|
200
|
+
'& .MuiChip-label': {
|
|
201
|
+
padding: '0 4px',
|
|
202
|
+
},
|
|
203
|
+
}}
|
|
204
|
+
/>
|
|
205
|
+
</Box>
|
|
206
|
+
{isEditingValue ? (
|
|
207
|
+
<TextField
|
|
208
|
+
fullWidth
|
|
209
|
+
size="small"
|
|
210
|
+
value={editValue}
|
|
211
|
+
onChange={(e) => {
|
|
212
|
+
setEditValue(e.target.value);
|
|
213
|
+
const currentType = detectValueType(e.target.value);
|
|
214
|
+
validateValue(e.target.value, currentType);
|
|
215
|
+
}}
|
|
216
|
+
error={!!valueError}
|
|
217
|
+
helperText={valueError}
|
|
218
|
+
multiline={valueType === 'json'}
|
|
219
|
+
rows={valueType === 'json' ? 3 : 1}
|
|
220
|
+
onKeyDown={(e) => {
|
|
221
|
+
if (e.key === 'Enter' && !e.shiftKey && valueType !== 'json') {
|
|
222
|
+
e.preventDefault();
|
|
223
|
+
handleValueSubmit();
|
|
224
|
+
}
|
|
225
|
+
if (e.key === 'Escape') handleValueCancel();
|
|
226
|
+
}}
|
|
227
|
+
onBlur={handleValueSubmit}
|
|
228
|
+
autoFocus
|
|
229
|
+
/>
|
|
230
|
+
) : (
|
|
231
|
+
<Box
|
|
232
|
+
onClick={() => setIsEditingValue(true)}
|
|
233
|
+
sx={{
|
|
234
|
+
cursor: 'pointer',
|
|
235
|
+
p: 1,
|
|
236
|
+
borderRadius: 1,
|
|
237
|
+
backgroundColor: 'rgba(0,0,0,0.04)',
|
|
238
|
+
'&:hover': {
|
|
239
|
+
backgroundColor: 'rgba(0,0,0,0.08)',
|
|
240
|
+
},
|
|
241
|
+
minHeight: 24,
|
|
242
|
+
display: 'flex',
|
|
243
|
+
alignItems: 'center',
|
|
244
|
+
}}
|
|
245
|
+
>
|
|
246
|
+
<Typography
|
|
247
|
+
variant="body2"
|
|
248
|
+
sx={{
|
|
249
|
+
fontFamily: 'monospace',
|
|
250
|
+
wordBreak: 'break-all',
|
|
251
|
+
}}
|
|
252
|
+
title={pair.value} // Show full value on hover
|
|
253
|
+
>
|
|
254
|
+
{formatValueForDisplay(pair.value) || 'Click to add value'}
|
|
255
|
+
</Typography>
|
|
256
|
+
</Box>
|
|
257
|
+
)}
|
|
258
|
+
</Box>
|
|
259
|
+
|
|
260
|
+
{/* Actions */}
|
|
261
|
+
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
|
262
|
+
<IconButton
|
|
263
|
+
size="small"
|
|
264
|
+
onClick={() => onDelete(pair.id)}
|
|
265
|
+
color="error"
|
|
266
|
+
>
|
|
267
|
+
<DeleteIcon fontSize="small" />
|
|
268
|
+
</IconButton>
|
|
269
|
+
</Box>
|
|
270
|
+
</Box>
|
|
271
|
+
</CardContent>
|
|
272
|
+
</Card>
|
|
273
|
+
);
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
export const JsonEdit: React.FC<IDashAutoAdminCustomFieldComponent> = (props) => {
|
|
277
|
+
const { attribute } = props;
|
|
278
|
+
|
|
279
|
+
const record = useRecordContext();
|
|
280
|
+
const { setValue, getValues } = useFormContext();
|
|
281
|
+
|
|
282
|
+
const attributePath = attribute.attribute;
|
|
283
|
+
const isNestedSetting = attributePath.startsWith('settings.');
|
|
284
|
+
const settingsPath = isNestedSetting ? attributePath.split('.').slice(1).join('.') : attributePath;
|
|
285
|
+
|
|
286
|
+
const [pairs, setPairs] = useState<KeyValuePair[]>([]);
|
|
287
|
+
const [searchTerm, setSearchTerm] = useState<string>('');
|
|
288
|
+
const [isLoading, setIsLoading] = useState<boolean>(true);
|
|
289
|
+
|
|
290
|
+
// Filter pairs based on search term
|
|
291
|
+
const filteredPairs = useMemo(() => {
|
|
292
|
+
if (!searchTerm.trim()) return pairs;
|
|
293
|
+
|
|
294
|
+
return pairs.filter(pair =>
|
|
295
|
+
pair.key.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
|
296
|
+
pair.value.toLowerCase().includes(searchTerm.toLowerCase())
|
|
297
|
+
);
|
|
298
|
+
}, [pairs, searchTerm]);
|
|
299
|
+
|
|
300
|
+
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
301
|
+
setSearchTerm(event.target.value);
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
const handleClearSearch = () => {
|
|
305
|
+
setSearchTerm('');
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
// Initial data loading effect
|
|
309
|
+
useEffect(() => {
|
|
310
|
+
const loadInitialData = async () => {
|
|
311
|
+
setIsLoading(true);
|
|
312
|
+
|
|
313
|
+
try {
|
|
314
|
+
let initialValue: Record<string, any> = {};
|
|
315
|
+
let defaultValues: Record<string, any> = {};
|
|
316
|
+
|
|
317
|
+
try {
|
|
318
|
+
defaultValues = attribute?.default_value || {};
|
|
319
|
+
} catch (e) {
|
|
320
|
+
defaultValues = {};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (isNestedSetting && record?.settings) {
|
|
324
|
+
const settings = record.settings || {};
|
|
325
|
+
initialValue = { ...defaultValues, ...getNestedValue(settings, settingsPath) };
|
|
326
|
+
} else if (record?.[attributePath]) {
|
|
327
|
+
initialValue = { ...defaultValues, ...record[attributePath] };
|
|
328
|
+
} else {
|
|
329
|
+
initialValue = defaultValues;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const initialPairs = Object.entries(initialValue).map(([key, value]) => ({
|
|
333
|
+
key,
|
|
334
|
+
value: String(value),
|
|
335
|
+
id: generateId()
|
|
336
|
+
}));
|
|
337
|
+
|
|
338
|
+
setPairs(initialPairs);
|
|
339
|
+
|
|
340
|
+
} catch (error) {
|
|
341
|
+
console.error('Error loading initial data:', error);
|
|
342
|
+
} finally {
|
|
343
|
+
setIsLoading(false);
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
loadInitialData();
|
|
348
|
+
}, [record, attributePath]);
|
|
349
|
+
|
|
350
|
+
const getNestedValue = (obj: any, path: string): any => {
|
|
351
|
+
if (!path) return obj;
|
|
352
|
+
const keys = path.split('.');
|
|
353
|
+
return keys.reduce((o, key) => (o && o[key] !== undefined ? o[key] : {}), obj);
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
const setNestedValue = (obj: any, path: string, value: any): any => {
|
|
357
|
+
if (!path) return value;
|
|
358
|
+
|
|
359
|
+
const keys = path.split('.');
|
|
360
|
+
const lastKey = keys.pop();
|
|
361
|
+
const lastObj = keys.reduce((o, key) => {
|
|
362
|
+
if (o[key] === undefined) o[key] = {};
|
|
363
|
+
return o[key];
|
|
364
|
+
}, obj);
|
|
365
|
+
|
|
366
|
+
if (lastKey) {
|
|
367
|
+
lastObj[lastKey] = value;
|
|
368
|
+
}
|
|
369
|
+
return obj;
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
const generateId = (): string => {
|
|
373
|
+
return Math.random().toString(36).substring(2, 11);
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
const addPair = () => {
|
|
377
|
+
const newPair = { key: '', value: '', id: generateId() };
|
|
378
|
+
const updatedPairs = [...pairs, newPair];
|
|
379
|
+
setPairs(updatedPairs);
|
|
380
|
+
updateFormValue(updatedPairs);
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
const handleUpdatePair = (updatedPair: KeyValuePair) => {
|
|
384
|
+
const updatedPairs = pairs.map(p => p.id === updatedPair.id ? updatedPair : p);
|
|
385
|
+
setPairs(updatedPairs);
|
|
386
|
+
updateFormValue(updatedPairs);
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
const handleDeletePair = (id: string) => {
|
|
390
|
+
const updatedPairs = pairs.filter(pair => pair.id !== id);
|
|
391
|
+
setPairs(updatedPairs);
|
|
392
|
+
updateFormValue(updatedPairs);
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
const updateFormValue = (currentPairs: KeyValuePair[]) => {
|
|
396
|
+
const obj = currentPairs.reduce((acc, pair) => {
|
|
397
|
+
if (pair.key.trim()) {
|
|
398
|
+
// Parse value based on detected type
|
|
399
|
+
const valueType = detectValueType(pair.value);
|
|
400
|
+
let parsedValue: any = pair.value;
|
|
401
|
+
|
|
402
|
+
try {
|
|
403
|
+
switch (valueType) {
|
|
404
|
+
case 'number':
|
|
405
|
+
parsedValue = Number(pair.value);
|
|
406
|
+
break;
|
|
407
|
+
case 'boolean':
|
|
408
|
+
parsedValue = pair.value.toLowerCase() === 'true';
|
|
409
|
+
break;
|
|
410
|
+
case 'json':
|
|
411
|
+
parsedValue
|
|
412
|
+
parsedValue = JSON.parse(pair.value);
|
|
413
|
+
break;
|
|
414
|
+
case 'string':
|
|
415
|
+
default:
|
|
416
|
+
parsedValue = pair.value;
|
|
417
|
+
break;
|
|
418
|
+
}
|
|
419
|
+
} catch (e) {
|
|
420
|
+
// If parsing fails, use string value
|
|
421
|
+
parsedValue = pair.value;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
acc[pair.key] = parsedValue;
|
|
425
|
+
}
|
|
426
|
+
return acc;
|
|
427
|
+
}, {} as Record<string, any>);
|
|
428
|
+
|
|
429
|
+
if (isNestedSetting) {
|
|
430
|
+
const currentSettings = getValues('settings') || {};
|
|
431
|
+
const updatedSettings = setNestedValue({...currentSettings}, settingsPath, obj);
|
|
432
|
+
setValue('settings', updatedSettings, { shouldDirty: true });
|
|
433
|
+
} else {
|
|
434
|
+
setValue(attributePath, obj, { shouldDirty: true });
|
|
435
|
+
}
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
// Show loading state while component is initializing
|
|
439
|
+
if (isLoading) {
|
|
440
|
+
return (
|
|
441
|
+
<Box sx={{ mt: 1, mb: 2, display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: 200 }}>
|
|
442
|
+
<Typography variant="body2" color="textSecondary">
|
|
443
|
+
Loading values...
|
|
444
|
+
</Typography>
|
|
445
|
+
</Box>
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
return (
|
|
450
|
+
<Box sx={{ mt: 1, mb: 2 }}>
|
|
451
|
+
<Typography variant="subtitle1" gutterBottom>
|
|
452
|
+
{attribute.label || 'Key-Value Pairs'}
|
|
453
|
+
</Typography>
|
|
454
|
+
|
|
455
|
+
{/* Header Actions */}
|
|
456
|
+
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 3 }}>
|
|
457
|
+
<Button
|
|
458
|
+
startIcon={<AddIcon />}
|
|
459
|
+
variant="outlined"
|
|
460
|
+
size="small"
|
|
461
|
+
onClick={addPair}
|
|
462
|
+
>
|
|
463
|
+
Add Pair
|
|
464
|
+
</Button>
|
|
465
|
+
</Box>
|
|
466
|
+
|
|
467
|
+
{/* Search Control */}
|
|
468
|
+
{pairs.length > 0 && (
|
|
469
|
+
<Box sx={{ mb: 3 }}>
|
|
470
|
+
<TextField
|
|
471
|
+
fullWidth
|
|
472
|
+
size="small"
|
|
473
|
+
placeholder="Search by key or value..."
|
|
474
|
+
value={searchTerm}
|
|
475
|
+
onChange={handleSearchChange}
|
|
476
|
+
InputProps={{
|
|
477
|
+
startAdornment: (
|
|
478
|
+
<InputAdornment position="start">
|
|
479
|
+
<SearchIcon />
|
|
480
|
+
</InputAdornment>
|
|
481
|
+
),
|
|
482
|
+
endAdornment: searchTerm && (
|
|
483
|
+
<InputAdornment position="end">
|
|
484
|
+
<IconButton
|
|
485
|
+
size="small"
|
|
486
|
+
onClick={handleClearSearch}
|
|
487
|
+
edge="end"
|
|
488
|
+
>
|
|
489
|
+
<ClearIcon />
|
|
490
|
+
</IconButton>
|
|
491
|
+
</InputAdornment>
|
|
492
|
+
),
|
|
493
|
+
}}
|
|
494
|
+
/>
|
|
495
|
+
</Box>
|
|
496
|
+
)}
|
|
497
|
+
|
|
498
|
+
{/* Results count when filtering */}
|
|
499
|
+
{searchTerm && (
|
|
500
|
+
<Box sx={{ mb: 2 }}>
|
|
501
|
+
<Chip
|
|
502
|
+
label={`${filteredPairs.length} of ${pairs.length} pairs`}
|
|
503
|
+
size="small"
|
|
504
|
+
variant="outlined"
|
|
505
|
+
onDelete={handleClearSearch}
|
|
506
|
+
/>
|
|
507
|
+
</Box>
|
|
508
|
+
)}
|
|
509
|
+
|
|
510
|
+
{/* Values List */}
|
|
511
|
+
<Box sx={{ mb: 2 }}>
|
|
512
|
+
{filteredPairs.map((pair) => (
|
|
513
|
+
<ValueItem
|
|
514
|
+
key={pair.id}
|
|
515
|
+
pair={pair}
|
|
516
|
+
onUpdate={handleUpdatePair}
|
|
517
|
+
onDelete={handleDeletePair}
|
|
518
|
+
existingKeys={pairs.map(p => p.key)}
|
|
519
|
+
/>
|
|
520
|
+
))}
|
|
521
|
+
</Box>
|
|
522
|
+
|
|
523
|
+
{/* Show message when no results found */}
|
|
524
|
+
{searchTerm && filteredPairs.length === 0 && (
|
|
525
|
+
<Box sx={{ textAlign: 'center', py: 4 }}>
|
|
526
|
+
<Typography variant="body2" color="textSecondary">
|
|
527
|
+
No pairs found matching your search
|
|
528
|
+
</Typography>
|
|
529
|
+
<Button size="small" onClick={handleClearSearch} sx={{ mt: 1 }}>
|
|
530
|
+
Clear search
|
|
531
|
+
</Button>
|
|
532
|
+
</Box>
|
|
533
|
+
)}
|
|
534
|
+
|
|
535
|
+
{/* Show message when no values exist */}
|
|
536
|
+
{pairs.length === 0 && (
|
|
537
|
+
<Box sx={{ textAlign: 'center', py: 4 }}>
|
|
538
|
+
<Typography variant="body2" color="textSecondary" gutterBottom>
|
|
539
|
+
No key-value pairs configured yet
|
|
540
|
+
</Typography>
|
|
541
|
+
<Button
|
|
542
|
+
startIcon={<AddIcon />}
|
|
543
|
+
variant="contained"
|
|
544
|
+
onClick={addPair}
|
|
545
|
+
>
|
|
546
|
+
Add Your First Pair
|
|
547
|
+
</Button>
|
|
548
|
+
</Box>
|
|
549
|
+
)}
|
|
550
|
+
</Box>
|
|
551
|
+
);
|
|
552
|
+
};
|
|
553
|
+
|
|
554
|
+
export const JsonView: React.FC<IDashAutoAdminCustomFieldComponent> = (props) => {
|
|
555
|
+
const { attribute } = props;
|
|
556
|
+
const record = useRecordContext();
|
|
557
|
+
|
|
558
|
+
const attributePath = attribute.attribute;
|
|
559
|
+
const isNestedSetting = attributePath.startsWith('settings.');
|
|
560
|
+
const settingsPath = isNestedSetting ? attributePath.split('.').slice(1).join('.') : attributePath;
|
|
561
|
+
|
|
562
|
+
const [searchTerm, setSearchTerm] = useState<string>('');
|
|
563
|
+
const [isLoading, setIsLoading] = useState<boolean>(true);
|
|
564
|
+
const [displayValue, setDisplayValue] = useState<Record<string, any>>({});
|
|
565
|
+
|
|
566
|
+
// Convert displayValue to pairs for filtering
|
|
567
|
+
const displayPairs = useMemo(() => {
|
|
568
|
+
return Object.entries(displayValue).map(([key, value]) => ({
|
|
569
|
+
key,
|
|
570
|
+
value: String(value),
|
|
571
|
+
id: key
|
|
572
|
+
}));
|
|
573
|
+
}, [displayValue]);
|
|
574
|
+
|
|
575
|
+
// Initial data loading effect
|
|
576
|
+
useEffect(() => {
|
|
577
|
+
const loadViewData = async () => {
|
|
578
|
+
setIsLoading(true);
|
|
579
|
+
|
|
580
|
+
try {
|
|
581
|
+
let viewValue: Record<string, any> = {};
|
|
582
|
+
let defaultValues: Record<string, any> = {};
|
|
583
|
+
|
|
584
|
+
try {
|
|
585
|
+
defaultValues = attribute?.default_value || {};
|
|
586
|
+
} catch (e) {
|
|
587
|
+
defaultValues = {};
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
if (isNestedSetting && record?.settings) {
|
|
591
|
+
const settings = record.settings || {};
|
|
592
|
+
if (settingsPath) {
|
|
593
|
+
const keys = settingsPath.split('.');
|
|
594
|
+
viewValue = { ...defaultValues, ...keys.reduce((o, key) => (o && o[key] !== undefined ? o[key] : {}), settings) };
|
|
595
|
+
} else {
|
|
596
|
+
viewValue = { ...defaultValues, ...settings };
|
|
597
|
+
}
|
|
598
|
+
} else if (record?.[attributePath]) {
|
|
599
|
+
viewValue = { ...defaultValues, ...record[attributePath] };
|
|
600
|
+
} else {
|
|
601
|
+
viewValue = defaultValues;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
setDisplayValue(viewValue);
|
|
605
|
+
|
|
606
|
+
} catch (error) {
|
|
607
|
+
console.error('Error loading view data:', error);
|
|
608
|
+
} finally {
|
|
609
|
+
setIsLoading(false);
|
|
610
|
+
}
|
|
611
|
+
};
|
|
612
|
+
|
|
613
|
+
loadViewData();
|
|
614
|
+
}, [record, attributePath, attribute, isNestedSetting, settingsPath]);
|
|
615
|
+
|
|
616
|
+
// Filter display values based on search term
|
|
617
|
+
const filteredDisplayValue = useMemo(() => {
|
|
618
|
+
if (!searchTerm.trim()) return displayValue;
|
|
619
|
+
|
|
620
|
+
return Object.entries(displayValue)
|
|
621
|
+
.filter(([key, value]) =>
|
|
622
|
+
key.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
|
623
|
+
String(value).toLowerCase().includes(searchTerm.toLowerCase())
|
|
624
|
+
)
|
|
625
|
+
.reduce((acc, [key, value]) => {
|
|
626
|
+
acc[key] = value;
|
|
627
|
+
return acc;
|
|
628
|
+
}, {} as Record<string, any>);
|
|
629
|
+
}, [displayValue, searchTerm]);
|
|
630
|
+
|
|
631
|
+
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
632
|
+
setSearchTerm(event.target.value);
|
|
633
|
+
};
|
|
634
|
+
|
|
635
|
+
const handleClearSearch = () => {
|
|
636
|
+
setSearchTerm('');
|
|
637
|
+
};
|
|
638
|
+
|
|
639
|
+
// Show loading state while component is initializing
|
|
640
|
+
if (isLoading) {
|
|
641
|
+
return (
|
|
642
|
+
<Box sx={{ mt: 1, mb: 2, display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: 200 }}>
|
|
643
|
+
<Typography variant="body2" color="textSecondary">
|
|
644
|
+
Loading values...
|
|
645
|
+
</Typography>
|
|
646
|
+
</Box>
|
|
647
|
+
);
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
return (
|
|
651
|
+
<Box sx={{ mt: 1, mb: 2 }}>
|
|
652
|
+
<Typography variant="subtitle1" gutterBottom>
|
|
653
|
+
{attribute.label || 'Key-Value Pairs'}
|
|
654
|
+
</Typography>
|
|
655
|
+
|
|
656
|
+
{Object.keys(displayValue).length > 0 && (
|
|
657
|
+
<>
|
|
658
|
+
{/* Search Control for View Mode */}
|
|
659
|
+
<Box sx={{ mb: 3 }}>
|
|
660
|
+
<TextField
|
|
661
|
+
fullWidth
|
|
662
|
+
size="small"
|
|
663
|
+
placeholder="Search by key or value..."
|
|
664
|
+
value={searchTerm}
|
|
665
|
+
onChange={handleSearchChange}
|
|
666
|
+
InputProps={{
|
|
667
|
+
startAdornment: (
|
|
668
|
+
<InputAdornment position="start">
|
|
669
|
+
<SearchIcon />
|
|
670
|
+
</InputAdornment>
|
|
671
|
+
),
|
|
672
|
+
endAdornment: searchTerm && (
|
|
673
|
+
<InputAdornment position="end">
|
|
674
|
+
<IconButton
|
|
675
|
+
size="small"
|
|
676
|
+
onClick={handleClearSearch}
|
|
677
|
+
edge="end"
|
|
678
|
+
>
|
|
679
|
+
<ClearIcon />
|
|
680
|
+
</IconButton>
|
|
681
|
+
</InputAdornment>
|
|
682
|
+
),
|
|
683
|
+
}}
|
|
684
|
+
/>
|
|
685
|
+
</Box>
|
|
686
|
+
|
|
687
|
+
{/* Results count when filtering */}
|
|
688
|
+
{searchTerm && (
|
|
689
|
+
<Box sx={{ mb: 2 }}>
|
|
690
|
+
<Chip
|
|
691
|
+
label={`${Object.keys(filteredDisplayValue).length} of ${Object.keys(displayValue).length} pairs`}
|
|
692
|
+
size="small"
|
|
693
|
+
variant="outlined"
|
|
694
|
+
onDelete={handleClearSearch}
|
|
695
|
+
/>
|
|
696
|
+
</Box>
|
|
697
|
+
)}
|
|
698
|
+
|
|
699
|
+
{Object.keys(filteredDisplayValue).length > 0 ? (
|
|
700
|
+
<Box sx={{ mb: 2 }}>
|
|
701
|
+
{Object.entries(filteredDisplayValue).map(([key, value]) => {
|
|
702
|
+
const valueType = detectValueType(String(value));
|
|
703
|
+
|
|
704
|
+
// Color coding for different value types
|
|
705
|
+
const getTypeColor = (type: ValueType) => {
|
|
706
|
+
switch (type) {
|
|
707
|
+
case 'string': return '#2196f3'; // Blue
|
|
708
|
+
case 'number': return '#4caf50'; // Green
|
|
709
|
+
case 'boolean': return '#ff9800'; // Orange
|
|
710
|
+
case 'json': return '#9c27b0'; // Purple
|
|
711
|
+
default: return '#757575'; // Grey
|
|
712
|
+
}
|
|
713
|
+
};
|
|
714
|
+
|
|
715
|
+
return (
|
|
716
|
+
<Card
|
|
717
|
+
key={key}
|
|
718
|
+
variant="outlined"
|
|
719
|
+
sx={{
|
|
720
|
+
mb: 1,
|
|
721
|
+
border: `2px solid ${getTypeColor(valueType)}`,
|
|
722
|
+
}}
|
|
723
|
+
>
|
|
724
|
+
<CardContent sx={{ p: 2, '&:last-child': { pb: 2 } }}>
|
|
725
|
+
<Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 2 }}>
|
|
726
|
+
{/* Key Section */}
|
|
727
|
+
<Box sx={{ flex: 1, minWidth: 0 }}>
|
|
728
|
+
<Typography variant="caption" color="textSecondary" sx={{ display: 'block', mb: 0.5 }}>
|
|
729
|
+
Key
|
|
730
|
+
</Typography>
|
|
731
|
+
<Box
|
|
732
|
+
sx={{
|
|
733
|
+
p: 1,
|
|
734
|
+
borderRadius: 1,
|
|
735
|
+
backgroundColor: 'rgba(0,0,0,0.04)',
|
|
736
|
+
minHeight: 24,
|
|
737
|
+
display: 'flex',
|
|
738
|
+
alignItems: 'center',
|
|
739
|
+
}}
|
|
740
|
+
>
|
|
741
|
+
<Typography
|
|
742
|
+
variant="body2"
|
|
743
|
+
sx={{
|
|
744
|
+
fontFamily: 'monospace',
|
|
745
|
+
wordBreak: 'break-all',
|
|
746
|
+
}}
|
|
747
|
+
>
|
|
748
|
+
{key}
|
|
749
|
+
</Typography>
|
|
750
|
+
</Box>
|
|
751
|
+
</Box>
|
|
752
|
+
|
|
753
|
+
{/* Value Section */}
|
|
754
|
+
<Box sx={{ flex: 1, minWidth: 0 }}>
|
|
755
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 0.5 }}>
|
|
756
|
+
<Typography variant="caption" color="textSecondary">
|
|
757
|
+
Value
|
|
758
|
+
</Typography>
|
|
759
|
+
<Chip
|
|
760
|
+
label={valueType.toUpperCase()}
|
|
761
|
+
size="small"
|
|
762
|
+
sx={{
|
|
763
|
+
height: 16,
|
|
764
|
+
fontSize: '0.6rem',
|
|
765
|
+
backgroundColor: getTypeColor(valueType),
|
|
766
|
+
color: '#fff',
|
|
767
|
+
'& .MuiChip-label': {
|
|
768
|
+
padding: '0 4px',
|
|
769
|
+
},
|
|
770
|
+
}}
|
|
771
|
+
/>
|
|
772
|
+
</Box>
|
|
773
|
+
<Box
|
|
774
|
+
sx={{
|
|
775
|
+
p: 1,
|
|
776
|
+
borderRadius: 1,
|
|
777
|
+
backgroundColor: 'rgba(0,0,0,0.04)',
|
|
778
|
+
minHeight: 24,
|
|
779
|
+
display: 'flex',
|
|
780
|
+
alignItems: 'center',
|
|
781
|
+
}}
|
|
782
|
+
>
|
|
783
|
+
<Typography
|
|
784
|
+
variant="body2"
|
|
785
|
+
sx={{
|
|
786
|
+
fontFamily: 'monospace',
|
|
787
|
+
wordBreak: 'break-all',
|
|
788
|
+
}}
|
|
789
|
+
title={String(value)} // Show full value on hover
|
|
790
|
+
>
|
|
791
|
+
{formatValueForDisplay(String(value))}
|
|
792
|
+
</Typography>
|
|
793
|
+
</Box>
|
|
794
|
+
</Box>
|
|
795
|
+
</Box>
|
|
796
|
+
</CardContent>
|
|
797
|
+
</Card>
|
|
798
|
+
);
|
|
799
|
+
})}
|
|
800
|
+
</Box>
|
|
801
|
+
) : (
|
|
802
|
+
<Box sx={{ textAlign: 'center', py: 4 }}>
|
|
803
|
+
<Typography variant="body2" color="textSecondary">
|
|
804
|
+
No pairs found matching your search
|
|
805
|
+
</Typography>
|
|
806
|
+
<Button size="small" onClick={handleClearSearch} sx={{ mt: 1 }}>
|
|
807
|
+
Clear search
|
|
808
|
+
</Button>
|
|
809
|
+
</Box>
|
|
810
|
+
)}
|
|
811
|
+
</>
|
|
812
|
+
)}
|
|
813
|
+
|
|
814
|
+
{Object.keys(displayValue).length === 0 && (
|
|
815
|
+
<Box sx={{ textAlign: 'center', py: 4 }}>
|
|
816
|
+
<Typography variant="body2" color="textSecondary">
|
|
817
|
+
No key-value pairs configured
|
|
818
|
+
</Typography>
|
|
819
|
+
</Box>
|
|
820
|
+
)}
|
|
821
|
+
</Box>
|
|
822
|
+
);
|
|
823
|
+
};
|
|
824
|
+
|
|
825
|
+
const JsonCssVarValues = ({ method, attribute, resourceConfig }: IDashAutoAdminCustomFieldComponent) => {
|
|
826
|
+
const [componentLoading, setComponentLoading] = useState(true);
|
|
827
|
+
|
|
828
|
+
// Set a small timeout to allow component to initialize
|
|
829
|
+
useEffect(() => {
|
|
830
|
+
const timer = setTimeout(() => {
|
|
831
|
+
setComponentLoading(false);
|
|
832
|
+
}, 50);
|
|
833
|
+
|
|
834
|
+
return () => {
|
|
835
|
+
clearTimeout(timer);
|
|
836
|
+
};
|
|
837
|
+
}, [
|
|
838
|
+
]);
|
|
839
|
+
|
|
840
|
+
// Show initial loading state
|
|
841
|
+
if (componentLoading) {
|
|
842
|
+
return (
|
|
843
|
+
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: 100 }}>
|
|
844
|
+
<Typography variant="body2" color="textSecondary">
|
|
845
|
+
Initializing key-value editor...
|
|
846
|
+
</Typography>
|
|
847
|
+
</Box>
|
|
848
|
+
);
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
switch (method) {
|
|
852
|
+
case 'edit':
|
|
853
|
+
return <JsonEdit attribute={attribute} method={method} resourceConfig={resourceConfig} />;
|
|
854
|
+
case 'create':
|
|
855
|
+
return <JsonEdit attribute={attribute} method={method} resourceConfig={resourceConfig} />;
|
|
856
|
+
case 'view':
|
|
857
|
+
return <JsonView attribute={attribute} method={method} resourceConfig={resourceConfig} />;
|
|
858
|
+
default:
|
|
859
|
+
return null;
|
|
860
|
+
}
|
|
861
|
+
};
|
|
862
|
+
|
|
863
|
+
export default JsonCssVarValues;
|