@dashadmin/dash-components 1.3.73 → 1.3.74-srcfree.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 (111) hide show
  1. package/dist/components/AuditLog/AuditLog.d.ts +27 -0
  2. package/dist/components/AutoCompleteArrayInput/AutoCompleteCheckBoxInput.d.ts +22 -0
  3. package/dist/components/AutoCompleteArrayInput/AutocompleteCheckBoxArrayInput.d.ts +7 -0
  4. package/dist/components/AutoCompleteArrayInputAutoSearch/AutoCompleteCheckBoxInput.d.ts +22 -0
  5. package/dist/components/AutoCompleteArrayInputAutoSearch/AutocompleteCheckBoxArrayInput.d.ts +7 -0
  6. package/dist/components/Json/Json.d.ts +5 -0
  7. package/dist/components/JsonColorSelector/JsonColorSelector.d.ts +5 -0
  8. package/dist/components/JsonColorSelector/JsonColorSelectorEnhanced.d.ts +4 -0
  9. package/dist/components/JsonColorSelector/JsonCssVarValues.d.ts +5 -0
  10. package/dist/components/JsonColorSelector/components/BasicColorSelector.d.ts +9 -0
  11. package/dist/components/JsonColorSelector/components/ColorEditDialog.d.ts +12 -0
  12. package/dist/components/JsonColorSelector/components/ColorPaletteItem.d.ts +7 -0
  13. package/dist/components/JsonColorSelector/components/ImageColorExtractor.d.ts +9 -0
  14. package/dist/components/JsonColorSelector/helpers/functions.d.ts +11 -0
  15. package/dist/components/JsonColorSelector/helpers/paletteDerivation.d.ts +37 -0
  16. package/{src/components/JsonColorSelector/interfaces/interfaces.tsx → dist/components/JsonColorSelector/interfaces/interfaces.d.ts} +6 -8
  17. package/dist/components/JsonColorSelector/useAsyncColorThief.d.ts +11 -0
  18. package/dist/components/ListActive/ListActive.d.ts +10 -0
  19. package/dist/components/ListActive/ListBoolean.d.ts +10 -0
  20. package/dist/components/NotificationPreferences/NotificationPreferences.d.ts +6 -0
  21. package/dist/components/SearchableSelects/RASearchableSelect.d.ts +25 -0
  22. package/dist/components/SearchableSelects/RASearchableSelectCheckboxes.d.ts +35 -0
  23. package/dist/components/SearchableSelects/RASearchableSelectChips.d.ts +33 -0
  24. package/dist/components/SearchableSelects/RASearchableSelectChipsRecordContext.d.ts +30 -0
  25. package/dist/components/SearchableSelects/SearchableSelect.d.ts +25 -0
  26. package/dist/components/SearchableSelects/SearchableSelectCheckboxes.d.ts +29 -0
  27. package/dist/components/SortableDataGrid/SortableDataGrid.d.ts +7 -0
  28. package/dist/components/SortableDataGrid/SortableDataGridBody.d.ts +7 -0
  29. package/dist/components/SortableDataGrid/SortableDataGridHeader.d.ts +6 -0
  30. package/dist/components/SortableDataGrid/SortableDataGridRow.d.ts +5 -0
  31. package/dist/components/Upload/SingleImageUploader.d.ts +8 -0
  32. package/dist/components/custom/PackageCopyMethod.d.ts +15 -0
  33. package/dist/components/custom/PackageRadioButtonGroup.d.ts +8 -0
  34. package/dist/components/dialog/Basic.d.ts +2 -0
  35. package/dist/components/dialog/Correct.d.ts +2 -0
  36. package/dist/components/dialog/Error.d.ts +2 -0
  37. package/dist/components/dialog/Info.d.ts +2 -0
  38. package/dist/components/dialog/InfoV2.d.ts +2 -0
  39. package/dist/components/dialog/NotFound.d.ts +2 -0
  40. package/dist/components/dialog/QuickSearch.d.ts +6 -0
  41. package/dist/components/notifications/NotificationsCenter.d.ts +109 -0
  42. package/dist/components/notifications/index.d.ts +2 -0
  43. package/dist/components/tables/PaginationComponent.d.ts +4 -0
  44. package/dist/components/theme/AppLoadingFallback.d.ts +6 -0
  45. package/dist/components/theme/ErrorBoundary.d.ts +19 -0
  46. package/dist/components/theme/InitialLoader.d.ts +3 -0
  47. package/dist/components/theme/NotFound.d.ts +13 -0
  48. package/dist/components/theme/NotResults.d.ts +7 -0
  49. package/dist/hooks/useDraggable.d.ts +1 -0
  50. package/dist/hooks/useNotifications.d.ts +2 -0
  51. package/dist/hooks/useQuickSearch.d.ts +6 -0
  52. package/{src/index.tsx → dist/index.d.ts} +10 -58
  53. package/dist/utils/setNativeValue.d.ts +2 -0
  54. package/package.json +24 -21
  55. package/src/components/AuditLog/AuditLog.tsx +0 -490
  56. package/src/components/AutoCompleteArrayInput/AutoCompleteCheckBoxInput.tsx +0 -917
  57. package/src/components/AutoCompleteArrayInput/AutocompleteCheckBoxArrayInput.tsx +0 -39
  58. package/src/components/AutoCompleteArrayInputAutoSearch/AutoCompleteCheckBoxInput.tsx +0 -916
  59. package/src/components/AutoCompleteArrayInputAutoSearch/AutocompleteCheckBoxArrayAutoSearch.tsx.wip +0 -42
  60. package/src/components/AutoCompleteArrayInputAutoSearch/AutocompleteCheckBoxArrayInput.tsx +0 -44
  61. package/src/components/Json/Json.tsx +0 -312
  62. package/src/components/JsonColorSelector/JsonColorSelector.tsx +0 -1443
  63. package/src/components/JsonColorSelector/JsonColorSelector.tsx.bkup +0 -746
  64. package/src/components/JsonColorSelector/JsonColorSelectorEnhanced.tsx +0 -1135
  65. package/src/components/JsonColorSelector/JsonCssVarValues.tsx +0 -867
  66. package/src/components/JsonColorSelector/components/BasicColorSelector.tsx +0 -333
  67. package/src/components/JsonColorSelector/components/ColorEditDialog.tsx +0 -495
  68. package/src/components/JsonColorSelector/components/ColorPaletteItem.tsx +0 -80
  69. package/src/components/JsonColorSelector/components/ImageColorExtractor.tsx +0 -420
  70. package/src/components/JsonColorSelector/helpers/functions.tsx +0 -123
  71. package/src/components/JsonColorSelector/helpers/paletteDerivation.ts +0 -367
  72. package/src/components/JsonColorSelector/useAsyncColorThief.tsx +0 -97
  73. package/src/components/ListActive/ListActive.tsx +0 -106
  74. package/src/components/ListActive/ListBoolean.tsx +0 -96
  75. package/src/components/NotificationPreferences/NotificationPreferences.tsx +0 -480
  76. package/src/components/SearchableSelects/RASearchableSelect.tsx +0 -303
  77. package/src/components/SearchableSelects/RASearchableSelectCheckboxes.tsx +0 -617
  78. package/src/components/SearchableSelects/RASearchableSelectChips.tsx +0 -561
  79. package/src/components/SearchableSelects/RASearchableSelectChipsRecordContext.tsx +0 -366
  80. package/src/components/SearchableSelects/SearchableSelect.tsx +0 -323
  81. package/src/components/SearchableSelects/SearchableSelectCheckboxes.tsx +0 -358
  82. package/src/components/SortableDataGrid/README.md +0 -1
  83. package/src/components/SortableDataGrid/SortableDataGrid.tsx +0 -56
  84. package/src/components/SortableDataGrid/SortableDataGridBody.tsx +0 -175
  85. package/src/components/SortableDataGrid/SortableDataGridHeader.tsx +0 -171
  86. package/src/components/SortableDataGrid/SortableDataGridRow.tsx +0 -247
  87. package/src/components/Upload/SingleImageUploader.tsx +0 -55
  88. package/src/components/custom/PackageCopyMethod.tsx +0 -109
  89. package/src/components/custom/PackageRadioButtonGroup.tsx +0 -49
  90. package/src/components/dialog/Basic.tsx +0 -11
  91. package/src/components/dialog/Correct.tsx +0 -16
  92. package/src/components/dialog/Error.tsx +0 -15
  93. package/src/components/dialog/Info.tsx +0 -15
  94. package/src/components/dialog/InfoV2.tsx +0 -35
  95. package/src/components/dialog/NotFound.tsx +0 -18
  96. package/src/components/dialog/QuickSearch.tsx +0 -115
  97. package/src/components/notifications/NotificationsCenter.tsx +0 -743
  98. package/src/components/notifications/index.ts +0 -42
  99. package/src/components/tables/PaginationComponent.tsx +0 -17
  100. package/src/components/theme/AppLoadingFallback.tsx +0 -42
  101. package/src/components/theme/ErrorBoundary.tsx +0 -57
  102. package/src/components/theme/InitialLoader.tsx +0 -16
  103. package/src/components/theme/NotFound.tsx +0 -124
  104. package/src/components/theme/NotResults.tsx +0 -56
  105. package/src/hooks/useDraggable.ts +0 -11
  106. package/src/hooks/useNotifications.ts +0 -14
  107. package/src/hooks/useQuickSearch.tsx +0 -29
  108. package/src/styles/index.less +0 -0
  109. package/src/utils/setNativeValue.tsx +0 -23
  110. /package/{src/components/AuditLog/index.ts → dist/components/AuditLog/index.d.ts} +0 -0
  111. /package/{src/components/NotificationPreferences/index.tsx → dist/components/NotificationPreferences/index.d.ts} +0 -0
@@ -1,867 +0,0 @@
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
- slotProps={{
477
- input: {
478
- startAdornment: (
479
- <InputAdornment position="start">
480
- <SearchIcon />
481
- </InputAdornment>
482
- ),
483
- endAdornment: searchTerm && (
484
- <InputAdornment position="end">
485
- <IconButton
486
- size="small"
487
- onClick={handleClearSearch}
488
- edge="end"
489
- >
490
- <ClearIcon />
491
- </IconButton>
492
- </InputAdornment>
493
- ),
494
- }
495
- }}
496
- />
497
- </Box>
498
- )}
499
-
500
- {/* Results count when filtering */}
501
- {searchTerm && (
502
- <Box sx={{ mb: 2 }}>
503
- <Chip
504
- label={`${filteredPairs.length} of ${pairs.length} pairs`}
505
- size="small"
506
- variant="outlined"
507
- onDelete={handleClearSearch}
508
- />
509
- </Box>
510
- )}
511
-
512
- {/* Values List */}
513
- <Box sx={{ mb: 2 }}>
514
- {filteredPairs.map((pair) => (
515
- <ValueItem
516
- key={pair.id}
517
- pair={pair}
518
- onUpdate={handleUpdatePair}
519
- onDelete={handleDeletePair}
520
- existingKeys={pairs.map(p => p.key)}
521
- />
522
- ))}
523
- </Box>
524
-
525
- {/* Show message when no results found */}
526
- {searchTerm && filteredPairs.length === 0 && (
527
- <Box sx={{ textAlign: 'center', py: 4 }}>
528
- <Typography variant="body2" color="textSecondary">
529
- No pairs found matching your search
530
- </Typography>
531
- <Button size="small" onClick={handleClearSearch} sx={{ mt: 1 }}>
532
- Clear search
533
- </Button>
534
- </Box>
535
- )}
536
-
537
- {/* Show message when no values exist */}
538
- {pairs.length === 0 && (
539
- <Box sx={{ textAlign: 'center', py: 4 }}>
540
- <Typography variant="body2" color="textSecondary" gutterBottom>
541
- No key-value pairs configured yet
542
- </Typography>
543
- <Button
544
- startIcon={<AddIcon />}
545
- variant="contained"
546
- onClick={addPair}
547
- >
548
- Add Your First Pair
549
- </Button>
550
- </Box>
551
- )}
552
- </Box>
553
- );
554
- };
555
-
556
- export const JsonView: React.FC<IDashAutoAdminCustomFieldComponent> = (props) => {
557
- const { attribute } = props;
558
- const record = useRecordContext();
559
-
560
- const attributePath = attribute.attribute;
561
- const isNestedSetting = attributePath.startsWith('settings.');
562
- const settingsPath = isNestedSetting ? attributePath.split('.').slice(1).join('.') : attributePath;
563
-
564
- const [searchTerm, setSearchTerm] = useState<string>('');
565
- const [isLoading, setIsLoading] = useState<boolean>(true);
566
- const [displayValue, setDisplayValue] = useState<Record<string, any>>({});
567
-
568
- // Convert displayValue to pairs for filtering
569
- const displayPairs = useMemo(() => {
570
- return Object.entries(displayValue).map(([key, value]) => ({
571
- key,
572
- value: String(value),
573
- id: key
574
- }));
575
- }, [displayValue]);
576
-
577
- // Initial data loading effect
578
- useEffect(() => {
579
- const loadViewData = async () => {
580
- setIsLoading(true);
581
-
582
- try {
583
- let viewValue: Record<string, any> = {};
584
- let defaultValues: Record<string, any> = {};
585
-
586
- try {
587
- defaultValues = attribute?.default_value || {};
588
- } catch (e) {
589
- defaultValues = {};
590
- }
591
-
592
- if (isNestedSetting && record?.settings) {
593
- const settings = record.settings || {};
594
- if (settingsPath) {
595
- const keys = settingsPath.split('.');
596
- viewValue = { ...defaultValues, ...keys.reduce((o, key) => (o && o[key] !== undefined ? o[key] : {}), settings) };
597
- } else {
598
- viewValue = { ...defaultValues, ...settings };
599
- }
600
- } else if (record?.[attributePath]) {
601
- viewValue = { ...defaultValues, ...record[attributePath] };
602
- } else {
603
- viewValue = defaultValues;
604
- }
605
-
606
- setDisplayValue(viewValue);
607
-
608
- } catch (error) {
609
- console.error('Error loading view data:', error);
610
- } finally {
611
- setIsLoading(false);
612
- }
613
- };
614
-
615
- loadViewData();
616
- }, [record, attributePath, attribute, isNestedSetting, settingsPath]);
617
-
618
- // Filter display values based on search term
619
- const filteredDisplayValue = useMemo(() => {
620
- if (!searchTerm.trim()) return displayValue;
621
-
622
- return Object.entries(displayValue)
623
- .filter(([key, value]) =>
624
- key.toLowerCase().includes(searchTerm.toLowerCase()) ||
625
- String(value).toLowerCase().includes(searchTerm.toLowerCase())
626
- )
627
- .reduce((acc, [key, value]) => {
628
- acc[key] = value;
629
- return acc;
630
- }, {} as Record<string, any>);
631
- }, [displayValue, searchTerm]);
632
-
633
- const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
634
- setSearchTerm(event.target.value);
635
- };
636
-
637
- const handleClearSearch = () => {
638
- setSearchTerm('');
639
- };
640
-
641
- // Show loading state while component is initializing
642
- if (isLoading) {
643
- return (
644
- <Box sx={{ mt: 1, mb: 2, display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: 200 }}>
645
- <Typography variant="body2" color="textSecondary">
646
- Loading values...
647
- </Typography>
648
- </Box>
649
- );
650
- }
651
-
652
- return (
653
- <Box sx={{ mt: 1, mb: 2 }}>
654
- <Typography variant="subtitle1" gutterBottom>
655
- {attribute.label || 'Key-Value Pairs'}
656
- </Typography>
657
-
658
- {Object.keys(displayValue).length > 0 && (
659
- <>
660
- {/* Search Control for View Mode */}
661
- <Box sx={{ mb: 3 }}>
662
- <TextField
663
- fullWidth
664
- size="small"
665
- placeholder="Search by key or value..."
666
- value={searchTerm}
667
- onChange={handleSearchChange}
668
- slotProps={{
669
- input: {
670
- startAdornment: (
671
- <InputAdornment position="start">
672
- <SearchIcon />
673
- </InputAdornment>
674
- ),
675
- endAdornment: searchTerm && (
676
- <InputAdornment position="end">
677
- <IconButton
678
- size="small"
679
- onClick={handleClearSearch}
680
- edge="end"
681
- >
682
- <ClearIcon />
683
- </IconButton>
684
- </InputAdornment>
685
- ),
686
- }
687
- }}
688
- />
689
- </Box>
690
-
691
- {/* Results count when filtering */}
692
- {searchTerm && (
693
- <Box sx={{ mb: 2 }}>
694
- <Chip
695
- label={`${Object.keys(filteredDisplayValue).length} of ${Object.keys(displayValue).length} pairs`}
696
- size="small"
697
- variant="outlined"
698
- onDelete={handleClearSearch}
699
- />
700
- </Box>
701
- )}
702
-
703
- {Object.keys(filteredDisplayValue).length > 0 ? (
704
- <Box sx={{ mb: 2 }}>
705
- {Object.entries(filteredDisplayValue).map(([key, value]) => {
706
- const valueType = detectValueType(String(value));
707
-
708
- // Color coding for different value types
709
- const getTypeColor = (type: ValueType) => {
710
- switch (type) {
711
- case 'string': return '#2196f3'; // Blue
712
- case 'number': return '#4caf50'; // Green
713
- case 'boolean': return '#ff9800'; // Orange
714
- case 'json': return '#9c27b0'; // Purple
715
- default: return '#757575'; // Grey
716
- }
717
- };
718
-
719
- return (
720
- <Card
721
- key={key}
722
- variant="outlined"
723
- sx={{
724
- mb: 1,
725
- border: `2px solid ${getTypeColor(valueType)}`,
726
- }}
727
- >
728
- <CardContent sx={{ p: 2, '&:last-child': { pb: 2 } }}>
729
- <Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 2 }}>
730
- {/* Key Section */}
731
- <Box sx={{ flex: 1, minWidth: 0 }}>
732
- <Typography variant="caption" color="textSecondary" sx={{ display: 'block', mb: 0.5 }}>
733
- Key
734
- </Typography>
735
- <Box
736
- sx={{
737
- p: 1,
738
- borderRadius: 1,
739
- backgroundColor: 'rgba(0,0,0,0.04)',
740
- minHeight: 24,
741
- display: 'flex',
742
- alignItems: 'center',
743
- }}
744
- >
745
- <Typography
746
- variant="body2"
747
- sx={{
748
- fontFamily: 'monospace',
749
- wordBreak: 'break-all',
750
- }}
751
- >
752
- {key}
753
- </Typography>
754
- </Box>
755
- </Box>
756
-
757
- {/* Value Section */}
758
- <Box sx={{ flex: 1, minWidth: 0 }}>
759
- <Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 0.5 }}>
760
- <Typography variant="caption" color="textSecondary">
761
- Value
762
- </Typography>
763
- <Chip
764
- label={valueType.toUpperCase()}
765
- size="small"
766
- sx={{
767
- height: 16,
768
- fontSize: '0.6rem',
769
- backgroundColor: getTypeColor(valueType),
770
- color: '#fff',
771
- '& .MuiChip-label': {
772
- padding: '0 4px',
773
- },
774
- }}
775
- />
776
- </Box>
777
- <Box
778
- sx={{
779
- p: 1,
780
- borderRadius: 1,
781
- backgroundColor: 'rgba(0,0,0,0.04)',
782
- minHeight: 24,
783
- display: 'flex',
784
- alignItems: 'center',
785
- }}
786
- >
787
- <Typography
788
- variant="body2"
789
- sx={{
790
- fontFamily: 'monospace',
791
- wordBreak: 'break-all',
792
- }}
793
- title={String(value)} // Show full value on hover
794
- >
795
- {formatValueForDisplay(String(value))}
796
- </Typography>
797
- </Box>
798
- </Box>
799
- </Box>
800
- </CardContent>
801
- </Card>
802
- );
803
- })}
804
- </Box>
805
- ) : (
806
- <Box sx={{ textAlign: 'center', py: 4 }}>
807
- <Typography variant="body2" color="textSecondary">
808
- No pairs found matching your search
809
- </Typography>
810
- <Button size="small" onClick={handleClearSearch} sx={{ mt: 1 }}>
811
- Clear search
812
- </Button>
813
- </Box>
814
- )}
815
- </>
816
- )}
817
-
818
- {Object.keys(displayValue).length === 0 && (
819
- <Box sx={{ textAlign: 'center', py: 4 }}>
820
- <Typography variant="body2" color="textSecondary">
821
- No key-value pairs configured
822
- </Typography>
823
- </Box>
824
- )}
825
- </Box>
826
- );
827
- };
828
-
829
- const JsonCssVarValues = ({ method, attribute, resourceConfig }: IDashAutoAdminCustomFieldComponent) => {
830
- const [componentLoading, setComponentLoading] = useState(true);
831
-
832
- // Set a small timeout to allow component to initialize
833
- useEffect(() => {
834
- const timer = setTimeout(() => {
835
- setComponentLoading(false);
836
- }, 50);
837
-
838
- return () => {
839
- clearTimeout(timer);
840
- };
841
- }, [
842
- ]);
843
-
844
- // Show initial loading state
845
- if (componentLoading) {
846
- return (
847
- <Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: 100 }}>
848
- <Typography variant="body2" color="textSecondary">
849
- Initializing key-value editor...
850
- </Typography>
851
- </Box>
852
- );
853
- }
854
-
855
- switch (method) {
856
- case 'edit':
857
- return <JsonEdit attribute={attribute} method={method} resourceConfig={resourceConfig} />;
858
- case 'create':
859
- return <JsonEdit attribute={attribute} method={method} resourceConfig={resourceConfig} />;
860
- case 'view':
861
- return <JsonView attribute={attribute} method={method} resourceConfig={resourceConfig} />;
862
- default:
863
- return null;
864
- }
865
- };
866
-
867
- export default JsonCssVarValues;