@dashadmin/dash-components 1.3.16 → 1.3.19

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 (57) hide show
  1. package/dist/components/AuditLog/AuditLog.js +1 -0
  2. package/dist/components/AuditLog/index.js +1 -0
  3. package/dist/components/AutoCompleteArrayInput/AutoCompleteCheckBoxInput.js +3 -0
  4. package/dist/components/AutoCompleteArrayInput/AutocompleteCheckBoxArrayInput.js +1 -0
  5. package/dist/components/AutoCompleteArrayInputAutoSearch/AutoCompleteCheckBoxInput.js +3 -0
  6. package/dist/components/AutoCompleteArrayInputAutoSearch/AutocompleteCheckBoxArrayAutoSearch.tsx.wip +42 -0
  7. package/dist/components/AutoCompleteArrayInputAutoSearch/AutocompleteCheckBoxArrayInput.js +1 -0
  8. package/dist/components/Json/Json.js +1 -0
  9. package/dist/components/JsonColorSelector/JsonColorSelector.js +1 -0
  10. package/dist/components/JsonColorSelector/JsonColorSelector.tsx.bkup +746 -0
  11. package/dist/components/JsonColorSelector/JsonColorSelectorEnhanced.js +2 -0
  12. package/dist/components/JsonColorSelector/JsonCssVarValues.js +1 -0
  13. package/dist/components/JsonColorSelector/components/ColorEditDialog.js +1 -0
  14. package/dist/components/JsonColorSelector/components/ColorPaletteItem.js +1 -0
  15. package/dist/components/JsonColorSelector/components/ImageColorExtractor.js +1 -0
  16. package/dist/components/JsonColorSelector/helpers/functions.js +1 -0
  17. package/dist/components/JsonColorSelector/interfaces/interfaces.js +0 -0
  18. package/dist/components/JsonColorSelector/useAsyncColorThief.js +1 -0
  19. package/dist/components/ListActive/ListActive.js +1 -0
  20. package/dist/components/ListActive/ListBoolean.js +1 -0
  21. package/dist/components/NotificationPreferences/NotificationPreferences.js +1 -0
  22. package/dist/components/NotificationPreferences/index.js +1 -0
  23. package/dist/components/SearchableSelects/RASearchableSelect.js +1 -0
  24. package/dist/components/SearchableSelects/RASearchableSelectCheckboxes.js +1 -0
  25. package/dist/components/SearchableSelects/RASearchableSelectChips.js +1 -0
  26. package/dist/components/SearchableSelects/RASearchableSelectChipsRecordContext.js +1 -0
  27. package/dist/components/SearchableSelects/SearchableSelect.js +1 -0
  28. package/dist/components/SearchableSelects/SearchableSelectCheckboxes.js +1 -0
  29. package/dist/components/SortableDataGrid/README.md +1 -0
  30. package/dist/components/SortableDataGrid/SortableDataGrid.js +1 -0
  31. package/dist/components/SortableDataGrid/SortableDataGridBody.js +1 -0
  32. package/dist/components/SortableDataGrid/SortableDataGridHeader.js +1 -0
  33. package/dist/components/SortableDataGrid/SortableDataGridRow.js +1 -0
  34. package/dist/components/Upload/SingleImageUploader.js +1 -0
  35. package/dist/components/custom/PackageCopyMethod.js +1 -0
  36. package/dist/components/custom/PackageRadioButtonGroup.js +1 -0
  37. package/dist/components/dialog/Basic.js +1 -0
  38. package/dist/components/dialog/Correct.js +1 -0
  39. package/dist/components/dialog/Error.js +1 -0
  40. package/dist/components/dialog/Info.js +1 -0
  41. package/dist/components/dialog/InfoV2.js +1 -0
  42. package/dist/components/dialog/NotFound.js +1 -0
  43. package/dist/components/dialog/QuickSearch.js +1 -0
  44. package/dist/components/notifications/NotificationsCenter.js +1 -0
  45. package/dist/components/notifications/index.js +1 -0
  46. package/dist/components/tables/PaginationComponent.js +1 -0
  47. package/dist/components/theme/AppLoadingFallback.js +6 -0
  48. package/dist/components/theme/ErrorBoundary.js +1 -0
  49. package/dist/components/theme/InitialLoader.js +1 -0
  50. package/dist/components/theme/NotFound.js +1 -0
  51. package/dist/components/theme/NotResults.js +1 -0
  52. package/dist/hooks/useDraggable.js +1 -0
  53. package/dist/hooks/useNotifications.js +1 -0
  54. package/dist/hooks/useQuickSearch.js +1 -0
  55. package/dist/styles/index.less +0 -0
  56. package/dist/utils/setNativeValue.js +1 -0
  57. package/package.json +14 -3
@@ -0,0 +1,746 @@
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
+ Table,
11
+ TableBody,
12
+ TableCell,
13
+ TableContainer,
14
+ TableHead,
15
+ TableRow,
16
+ Paper,
17
+ Popover,
18
+ Slider,
19
+ Grid,
20
+ Input,
21
+ Tabs,
22
+ Tab
23
+ } from '@mui/material';
24
+ import DeleteIcon from '@mui/icons-material/Delete';
25
+ import AddIcon from '@mui/icons-material/Add';
26
+ import { useState, useEffect, useCallback } from 'react';
27
+
28
+ interface KeyValuePair {
29
+ key: string;
30
+ value: string;
31
+ id: string;
32
+ }
33
+
34
+ interface RGB {
35
+ r: number;
36
+ g: number;
37
+ b: number;
38
+ a: number;
39
+ }
40
+
41
+ interface HSL {
42
+ h: number;
43
+ s: number;
44
+ l: number;
45
+ a: number;
46
+ }
47
+
48
+ // Custom MUI-style color picker component
49
+ const MuiColorPicker = ({ color, onChange }: { color: string, onChange: (color: string) => void }) => {
50
+ const [tabValue, setTabValue] = useState(0);
51
+ const [rgb, setRgb] = useState<RGB>({ r: 0, g: 0, b: 0, a: 1 });
52
+ const [hsl, setHsl] = useState<HSL>({ h: 0, s: 0, l: 0, a: 1 });
53
+ const [hex, setHex] = useState('#000000');
54
+
55
+ // Parse the initial color
56
+ useEffect(() => {
57
+ if (color.startsWith('#')) {
58
+ setHex(color);
59
+ setRgb(hexToRgb(color));
60
+ setHsl(rgbToHsl(hexToRgb(color)));
61
+ } else if (color.startsWith('rgb')) {
62
+ const rgbValues = parseRgb(color);
63
+ setRgb(rgbValues);
64
+ setHex(rgbToHex(rgbValues));
65
+ setHsl(rgbToHsl(rgbValues));
66
+ }
67
+ }, [color]);
68
+
69
+ // Convert hex to rgb
70
+ const hexToRgb = (hex: string): RGB => {
71
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
72
+ return result ? {
73
+ r: parseInt(result[1], 16),
74
+ g: parseInt(result[2], 16),
75
+ b: parseInt(result[3], 16),
76
+ a: 1
77
+ } : { r: 0, g: 0, b: 0, a: 1 };
78
+ };
79
+
80
+ // Convert rgb to hex
81
+ const rgbToHex = (rgb: RGB): string => {
82
+ return '#' + ((1 << 24) + (rgb.r << 16) + (rgb.g << 8) + rgb.b).toString(16).slice(1);
83
+ };
84
+
85
+ // Parse rgb string
86
+ const parseRgb = (rgbStr: string): RGB => {
87
+ const match = rgbStr.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+(?:\.\d+)?))?\)/);
88
+ if (match) {
89
+ return {
90
+ r: parseInt(match[1]),
91
+ g: parseInt(match[2]),
92
+ b: parseInt(match[3]),
93
+ a: match[4] ? parseFloat(match[4]) : 1
94
+ };
95
+ }
96
+ return { r: 0, g: 0, b: 0, a: 1 };
97
+ };
98
+
99
+ // Convert rgb to hsl
100
+ const rgbToHsl = (rgb: RGB): HSL => {
101
+ const r = rgb.r / 255;
102
+ const g = rgb.g / 255;
103
+ const b = rgb.b / 255;
104
+
105
+ const max = Math.max(r, g, b);
106
+ const min = Math.min(r, g, b);
107
+ let h = 0, s = 0, l = (max + min) / 2;
108
+
109
+ if (max !== min) {
110
+ const d = max - min;
111
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
112
+
113
+ switch (max) {
114
+ case r: h = (g - b) / d + (g < b ? 6 : 0); break;
115
+ case g: h = (b - r) / d + 2; break;
116
+ case b: h = (r - g) / d + 4; break;
117
+ }
118
+
119
+ h /= 6;
120
+ }
121
+
122
+ return { h: h * 360, s: s * 100, l: l * 100, a: rgb.a };
123
+ };
124
+
125
+ // Convert hsl to rgb
126
+ const hslToRgb = (hsl: HSL): RGB => {
127
+ const h = hsl.h / 360;
128
+ const s = hsl.s / 100;
129
+ const l = hsl.l / 100;
130
+
131
+ let r, g, b;
132
+
133
+ if (s === 0) {
134
+ r = g = b = l;
135
+ } else {
136
+ const hue2rgb = (p: number, q: number, t: number) => {
137
+ if (t < 0) t += 1;
138
+ if (t > 1) t -= 1;
139
+ if (t < 1/6) return p + (q - p) * 6 * t;
140
+ if (t < 1/2) return q;
141
+ if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
142
+ return p;
143
+ };
144
+
145
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
146
+ const p = 2 * l - q;
147
+
148
+ r = hue2rgb(p, q, h + 1/3);
149
+ g = hue2rgb(p, q, h);
150
+ b = hue2rgb(p, q, h - 1/3);
151
+ }
152
+
153
+ return {
154
+ r: Math.round(r * 255),
155
+ g: Math.round(g * 255),
156
+ b: Math.round(b * 255),
157
+ a: hsl.a
158
+ };
159
+ };
160
+
161
+ // Handle RGB changes
162
+ const handleRgbChange = (channel: keyof RGB, value: number) => {
163
+ const newRgb = { ...rgb, [channel]: value };
164
+ setRgb(newRgb);
165
+ setHex(rgbToHex(newRgb));
166
+ setHsl(rgbToHsl(newRgb));
167
+
168
+ if (channel === 'a') {
169
+ onChange(`rgba(${newRgb.r}, ${newRgb.g}, ${newRgb.b}, ${newRgb.a})`);
170
+ } else {
171
+ onChange(rgbToHex(newRgb));
172
+ }
173
+ };
174
+
175
+ // Handle HSL changes
176
+ const handleHslChange = (channel: keyof HSL, value: number) => {
177
+ const newHsl = { ...hsl, [channel]: value };
178
+ setHsl(newHsl);
179
+ const newRgb = hslToRgb(newHsl);
180
+ setRgb(newRgb);
181
+ setHex(rgbToHex(newRgb));
182
+
183
+ if (channel === 'a') {
184
+ onChange(`rgba(${newRgb.r}, ${newRgb.g}, ${newRgb.b}, ${newRgb.a})`);
185
+ } else {
186
+ onChange(rgbToHex(newRgb));
187
+ }
188
+ };
189
+
190
+ // Handle hex input change
191
+ const handleHexChange = (event: React.ChangeEvent<HTMLInputElement>) => {
192
+ const newHex = event.target.value;
193
+ if (/^#[0-9A-F]{6}$/i.test(newHex)) {
194
+ setHex(newHex);
195
+ const newRgb = hexToRgb(newHex);
196
+ setRgb(newRgb);
197
+ setHsl(rgbToHsl(newRgb));
198
+ onChange(newHex);
199
+ }
200
+ };
201
+
202
+ // Generate color palette
203
+ const generateColorPalette = () => {
204
+ const colors = [];
205
+ const baseColors = [
206
+ '#f44336', '#e91e63', '#9c27b0', '#673ab7',
207
+ '#3f51b5', '#2196f3', '#03a9f4', '#00bcd4',
208
+ '#009688', '#4caf50', '#8bc34a', '#cddc39',
209
+ '#ffeb3b', '#ffc107', '#ff9800', '#ff5722',
210
+ '#795548', '#9e9e9e', '#607d8b', '#000000'
211
+ ];
212
+
213
+ for (let i = 0; i < baseColors.length; i++) {
214
+ colors.push(
215
+ <Box
216
+ key={i}
217
+ onClick={() => {
218
+ setHex(baseColors[i]);
219
+ const newRgb = hexToRgb(baseColors[i]);
220
+ setRgb(newRgb);
221
+ setHsl(rgbToHsl(newRgb));
222
+ onChange(baseColors[i]);
223
+ }}
224
+ sx={{
225
+ width: 24,
226
+ height: 24,
227
+ backgroundColor: baseColors[i],
228
+ cursor: 'pointer',
229
+ border: hex === baseColors[i] ? '2px solid #fff' : '1px solid #ccc',
230
+ boxShadow: hex === baseColors[i] ? '0 0 0 1px #000' : 'none',
231
+ borderRadius: '2px',
232
+ margin: '2px'
233
+ }}
234
+ />
235
+ );
236
+ }
237
+
238
+ return (
239
+ <Box sx={{ display: 'flex', flexWrap: 'wrap', maxWidth: 240, mb: 2 }}>
240
+ {colors}
241
+ </Box>
242
+ );
243
+ };
244
+
245
+ // Color preview
246
+ const colorPreview = (
247
+ <Box sx={{
248
+ width: '100%',
249
+ height: 40,
250
+ backgroundColor: rgb.a < 1 ? `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${rgb.a})` : hex,
251
+ borderRadius: 1,
252
+ border: '1px solid #ccc',
253
+ mb: 2,
254
+ backgroundImage: rgb.a < 1 ? 'linear-gradient(45deg, #ccc 25%, transparent 25%), linear-gradient(-45deg, #ccc 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #ccc 75%), linear-gradient(-45deg, transparent 75%, #ccc 75%)' : 'none',
255
+ backgroundSize: '10px 10px',
256
+ backgroundPosition: '0 0, 0 5px, 5px -5px, -5px 0px'
257
+ }} />
258
+ );
259
+
260
+ return (
261
+ <Box sx={{ p: 2, width: 240 }}>
262
+ {colorPreview}
263
+
264
+ {generateColorPalette()}
265
+
266
+ <Tabs
267
+ value={tabValue}
268
+ onChange={(_, newValue) => setTabValue(newValue)}
269
+ sx={{ mb: 2 }}
270
+ variant="fullWidth"
271
+ >
272
+ <Tab label="RGB" />
273
+ <Tab label="HSL" />
274
+ <Tab label="HEX" />
275
+ </Tabs>
276
+
277
+ {tabValue === 0 && (
278
+ <Box>
279
+ <Grid container spacing={2} alignItems="center" sx={{ mb: 1 }}>
280
+ <Grid item xs={2}>
281
+ <Typography>R</Typography>
282
+ </Grid>
283
+ <Grid item xs={7}>
284
+ <Slider
285
+ value={rgb.r}
286
+ min={0}
287
+ max={255}
288
+ onChange={(_, value) => handleRgbChange('r', value as number)}
289
+ sx={{
290
+ color: 'red',
291
+ '& .MuiSlider-thumb': {
292
+ backgroundColor: 'red',
293
+ }
294
+ }}
295
+ />
296
+ </Grid>
297
+ <Grid item xs={3}>
298
+ <Input
299
+ value={rgb.r}
300
+ size="small"
301
+ onChange={(e) => {
302
+ const val = parseInt(e.target.value);
303
+ if (!isNaN(val) && val >= 0 && val <= 255) {
304
+ handleRgbChange('r', val);
305
+ }
306
+ }}
307
+ inputProps={{
308
+ min: 0,
309
+ max: 255,
310
+ type: 'number',
311
+ }}
312
+ />
313
+ </Grid>
314
+ </Grid>
315
+
316
+ {/* Similar Grid containers for G, B, and A channels */}
317
+ {/* ... */}
318
+ </Box>
319
+ )}
320
+
321
+ {/* Similar blocks for HSL and HEX tabs */}
322
+ {/* ... */}
323
+ </Box>
324
+ );
325
+ };
326
+
327
+ export const JsonEdit: React.FC<IDashAutoAdminCustomFieldComponent> = (props) => {
328
+ const { method, attribute, resourceConfig } = props;
329
+ const record = useRecordContext();
330
+ const { setValue, getValues } = useFormContext();
331
+
332
+ const attributePath = attribute.attribute;
333
+ const isNestedSetting = attributePath.startsWith('settings.');
334
+ const settingsPath = isNestedSetting ? attributePath.split('.').slice(1).join('.') : attributePath;
335
+
336
+ const [pairs, setPairs] = useState<KeyValuePair[]>([]);
337
+ const [keyError, setKeyError] = useState<{[key: string]: string}>({});
338
+
339
+ // State for color picker - simplified to a single active picker ID
340
+ const [activePickerId, setActivePickerId] = useState<string | null>(null);
341
+ const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null);
342
+
343
+ useEffect(() => {
344
+ let initialValue: Record<string, any> = {};
345
+
346
+ if (isNestedSetting && record?.settings) {
347
+ const settings = record.settings || {};
348
+ initialValue = getNestedValue(settings, settingsPath) || {};
349
+ } else if (record?.[attributePath]) {
350
+ initialValue = record[attributePath] || {};
351
+ }
352
+
353
+ const initialPairs = Object.entries(initialValue).map(([key, value]) => ({
354
+ key,
355
+ value: String(value),
356
+ id: generateId()
357
+ }));
358
+
359
+ setPairs(initialPairs);
360
+ }, [record, attributePath]);
361
+
362
+ const getNestedValue = (obj: any, path: string): any => {
363
+ if (!path) return obj;
364
+ const keys = path.split('.');
365
+ return keys.reduce((o, key) => (o && o[key] !== undefined ? o[key] : {}), obj);
366
+ };
367
+
368
+ const setNestedValue = (obj: any, path: string, value: any): any => {
369
+ if (!path) return value;
370
+
371
+ const keys = path.split('.');
372
+ const lastKey = keys.pop();
373
+ const lastObj = keys.reduce((o, key) => {
374
+ if (o[key] === undefined) o[key] = {};
375
+ return o[key];
376
+ }, obj);
377
+
378
+ if (lastKey) {
379
+ lastObj[lastKey] = value;
380
+ }
381
+ return obj;
382
+ };
383
+
384
+ const generateId = (): string => {
385
+ return Math.random().toString(36).substring(2, 11);
386
+ };
387
+
388
+ const addPair = () => {
389
+ setPairs([...pairs, { key: '', value: '#000000', id: generateId() }]);
390
+ };
391
+
392
+ const removePair = (id: string) => {
393
+ setPairs(pairs.filter(pair => pair.id !== id));
394
+ updateFormValue(pairs.filter(pair => pair.id !== id));
395
+ };
396
+
397
+ const handleKeyChange = (id: string, newKey: string) => {
398
+ const isDuplicate = pairs.some(pair => pair.id !== id && pair.key === newKey);
399
+
400
+ if (isDuplicate) {
401
+ setKeyError({...keyError, [id]: 'Duplicate key'});
402
+ } else {
403
+ setKeyError({...keyError, [id]: ''});
404
+ }
405
+
406
+ const updatedPairs = pairs.map(pair =>
407
+ pair.id === id ? { ...pair, key: newKey } : pair
408
+ );
409
+
410
+ setPairs(updatedPairs);
411
+ updateFormValue(updatedPairs);
412
+ };
413
+
414
+ const handleColorChange = (id: string, newColor: string) => {
415
+ const updatedPairs = pairs.map(pair =>
416
+ pair.id === id ? { ...pair, value: newColor } : pair
417
+ );
418
+
419
+ setPairs(updatedPairs);
420
+ updateFormValue(updatedPairs);
421
+ };
422
+
423
+ const updateFormValue = (currentPairs: KeyValuePair[]) => {
424
+ const obj = currentPairs.reduce((acc, pair) => {
425
+ if (pair.key.trim()) {
426
+ acc[pair.key] = pair.value;
427
+ }
428
+ return acc;
429
+ }, {} as Record<string, any>);
430
+
431
+ if (isNestedSetting) {
432
+ const currentSettings = getValues('settings') || {};
433
+ const updatedSettings = setNestedValue({...currentSettings}, settingsPath, obj);
434
+ setValue('settings', updatedSettings, { shouldDirty: true });
435
+ } else {
436
+ setValue(attributePath, obj, { shouldDirty: true });
437
+ }
438
+ };
439
+
440
+ // Simplified handler for opening the color picker
441
+ const handleOpenColorPicker = useCallback((event: React.MouseEvent<HTMLElement>, id: string) => {
442
+ // Stop event propagation to prevent any parent handlers from firing
443
+ event.stopPropagation();
444
+
445
+ // Set both states in a single render cycle
446
+ setAnchorEl(event.currentTarget);
447
+ setActivePickerId(id);
448
+ }, []);
449
+
450
+ // Simplified handler for closing the color picker
451
+ const handleCloseColorPicker = useCallback(() => {
452
+ setAnchorEl(null);
453
+ setActivePickerId(null);
454
+ }, []);
455
+
456
+ return (
457
+ <Box sx={{ mt: 1, mb: 2 }}>
458
+ <Typography variant="subtitle1" gutterBottom>
459
+ {attribute.label || 'Color Settings'}
460
+ </Typography>
461
+
462
+ <TableContainer component={Paper} sx={{ mb: 2 }}>
463
+ <Table size="small">
464
+ <TableHead>
465
+ <TableRow>
466
+ <TableCell width="40%">Key</TableCell>
467
+ <TableCell width="50%">Color</TableCell>
468
+ <TableCell width="10%" align="center">Actions</TableCell>
469
+ </TableRow>
470
+ </TableHead>
471
+ <TableBody>
472
+ {pairs.map((pair) => (
473
+ <TableRow key={pair.id}>
474
+ <TableCell>
475
+ <TextField
476
+ fullWidth
477
+ size="small"
478
+ value={pair.key}
479
+ onChange={(e) => handleKeyChange(pair.id, e.target.value)}
480
+ error={!!keyError[pair.id]}
481
+ helperText={keyError[pair.id]}
482
+ />
483
+ </TableCell>
484
+ <TableCell>
485
+ <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
486
+ {/* Simplified color box with explicit onClick handler */}
487
+ <Box
488
+ onClick={(e) => handleOpenColorPicker(e, pair.id)}
489
+ sx={{
490
+ width: 36,
491
+ height: 36,
492
+ borderRadius: 1,
493
+ border: '2px solid #ccc',
494
+ backgroundColor: pair.value,
495
+ cursor: 'pointer',
496
+ backgroundImage: pair.value.includes('rgba') ?
497
+ 'linear-gradient(45deg, #ccc 25%, transparent 25%), linear-gradient(-45deg, #ccc 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #ccc 75%), linear-gradient(-45deg, transparent 75%, #ccc 75%)' :
498
+ 'none',
499
+ backgroundSize: '10px 10px',
500
+ backgroundPosition: '0 0, 0 5px, 5px -5px, -5px 0px'
501
+ }}
502
+ />
503
+ <TextField
504
+ fullWidth
505
+ size="small"
506
+ value={pair.value}
507
+ InputProps={{ readOnly: true }}
508
+ />
509
+ </Box>
510
+ </TableCell>
511
+ <TableCell align="center">
512
+ <IconButton
513
+ size="small"
514
+ onClick={() => removePair(pair.id)}
515
+ color="error"
516
+ >
517
+ <DeleteIcon fontSize="small" />
518
+ </IconButton>
519
+ </TableCell>
520
+ </TableRow>
521
+ ))}
522
+ </TableBody>
523
+ </Table>
524
+ </TableContainer>
525
+
526
+ {/* Simplified popover with explicit open condition */}
527
+ <Popover
528
+ open={Boolean(anchorEl) && activePickerId !== null}
529
+ anchorEl={anchorEl}
530
+ onClose={handleCloseColorPicker}
531
+ anchorOrigin={{
532
+ vertical: 'bottom',
533
+ horizontal: 'left',
534
+ }}
535
+ transformOrigin={{
536
+ vertical: 'top',
537
+ horizontal: 'left',
538
+ }}
539
+ sx={{
540
+ '& .MuiPopover-paper': {
541
+ boxShadow: 3,
542
+ borderRadius: 1
543
+ }
544
+ }}
545
+ >
546
+ {activePickerId && (
547
+ <Box p={2}>
548
+ {/* Simplified color picker - just a basic palette for testing */}
549
+ <Box sx={{ mb: 2 }}>
550
+ <Typography variant="subtitle2" gutterBottom>
551
+ Select a color
552
+ </Typography>
553
+ <Box sx={{
554
+ width: '100%',
555
+ height: 40,
556
+ backgroundColor: pairs.find(p => p.id === activePickerId)?.value || '#000000',
557
+ borderRadius: 1,
558
+ border: '1px solid #ccc',
559
+ mb: 2
560
+ }} />
561
+
562
+ {/* Basic color palette */}
563
+ <Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1, mb: 2 }}>
564
+ {['#f44336', '#e91e63', '#9c27b0', '#673ab7', '#3f51b5', '#2196f3',
565
+ '#03a9f4', '#00bcd4', '#009688', '#4caf50', '#8bc34a', '#cddc39',
566
+ '#ffeb3b', '#ffc107', '#ff9800', '#ff5722', '#795548', '#9e9e9e',
567
+ '#607d8b', '#000000'].map((color, index) => (
568
+ <Box
569
+ key={index}
570
+ onClick={() => {
571
+ handleColorChange(activePickerId, color);
572
+ }}
573
+ sx={{
574
+ width: 24,
575
+ height: 24,
576
+ backgroundColor: color,
577
+ cursor: 'pointer',
578
+ border: '1px solid #ccc',
579
+ borderRadius: '2px'
580
+ }}
581
+ />
582
+ ))}
583
+ </Box>
584
+
585
+ {/* Simple RGB sliders */}
586
+ <Typography variant="caption">Red</Typography>
587
+ <Slider
588
+ min={0}
589
+ max={255}
590
+ sx={{ color: 'red' }}
591
+ onChange={(_, value) => {
592
+ const currentColor = pairs.find(p => p.id === activePickerId)?.value || '#000000';
593
+ const rgb = hexToRgb(currentColor);
594
+ rgb.r = value as number;
595
+ handleColorChange(activePickerId, rgbToHex(rgb));
596
+ }}
597
+ />
598
+
599
+ <Typography variant="caption">Green</Typography>
600
+ <Slider
601
+ min={0}
602
+ max={255}
603
+ sx={{ color: 'green' }}
604
+ onChange={(_, value) => {
605
+ const currentColor = pairs.find(p => p.id === activePickerId)?.value || '#000000';
606
+ const rgb = hexToRgb(currentColor);
607
+ rgb.g = value as number;
608
+ handleColorChange(activePickerId, rgbToHex(rgb));
609
+ }}
610
+ />
611
+
612
+ <Typography variant="caption">Blue</Typography>
613
+ <Slider
614
+ min={0}
615
+ max={255}
616
+ sx={{ color: 'blue' }}
617
+ onChange={(_, value) => {
618
+ const currentColor = pairs.find(p => p.id === activePickerId)?.value || '#000000';
619
+ const rgb = hexToRgb(currentColor);
620
+ rgb.b = value as number;
621
+ handleColorChange(activePickerId, rgbToHex(rgb));
622
+ }}
623
+ />
624
+ </Box>
625
+ </Box>
626
+ )}
627
+ </Popover>
628
+
629
+ <Button
630
+ startIcon={<AddIcon />}
631
+ variant="outlined"
632
+ size="small"
633
+ onClick={addPair}
634
+ >
635
+ Add Color
636
+ </Button>
637
+ </Box>
638
+ );
639
+ };
640
+
641
+ // Helper functions for the simplified color picker
642
+ const hexToRgb = (hex: string): { r: number, g: number, b: number } => {
643
+ // Remove # if present
644
+ hex = hex.replace(/^#/, '');
645
+
646
+ // Parse the hex values
647
+ const bigint = parseInt(hex, 16);
648
+ const r = (bigint >> 16) & 255;
649
+ const g = (bigint >> 8) & 255;
650
+ const b = bigint & 255;
651
+
652
+ return { r, g, b };
653
+ };
654
+
655
+ const rgbToHex = (rgb: { r: number, g: number, b: number }): string => {
656
+ return '#' + ((1 << 24) + (rgb.r << 16) + (rgb.g << 8) + rgb.b).toString(16).slice(1);
657
+ };
658
+
659
+ export const JsonView: React.FC<IDashAutoAdminCustomFieldComponent> = (props) => {
660
+ const { attribute } = props;
661
+ const record = useRecordContext();
662
+
663
+ const attributePath = attribute.attribute;
664
+ const isNestedSetting = attributePath.startsWith('settings.');
665
+ const settingsPath = isNestedSetting ? attributePath.split('.').slice(1).join('.') : attributePath;
666
+
667
+ let displayValue: Record<string, any> = {};
668
+
669
+ if (isNestedSetting && record?.settings) {
670
+ const settings = record.settings || {};
671
+ if (settingsPath) {
672
+ const keys = settingsPath.split('.');
673
+ displayValue = keys.reduce((o, key) => (o && o[key] !== undefined ? o[key] : {}), settings);
674
+ } else {
675
+ displayValue = settings;
676
+ }
677
+ } else if (record?.[attributePath]) {
678
+ displayValue = record[attributePath];
679
+ }
680
+
681
+ return (
682
+ <Box sx={{ mt: 1, mb: 2 }}>
683
+ <Typography variant="subtitle1" gutterBottom>
684
+ {attribute.label || 'Color Settings'}
685
+ </Typography>
686
+
687
+ {Object.keys(displayValue).length > 0 ? (
688
+ <TableContainer component={Paper}>
689
+ <Table size="small">
690
+ <TableHead>
691
+ <TableRow>
692
+ <TableCell width="40%">Key</TableCell>
693
+ <TableCell width="60%">Color</TableCell>
694
+ </TableRow>
695
+ </TableHead>
696
+ <TableBody>
697
+ {Object.entries(displayValue).map(([key, value]) => (
698
+ <TableRow key={key}>
699
+ <TableCell>{key}</TableCell>
700
+ <TableCell>
701
+ <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
702
+ <Box
703
+ sx={{
704
+ width: 36,
705
+ height: 36,
706
+ borderRadius: 1,
707
+ border: '2px solid #ccc',
708
+ backgroundColor: value as string,
709
+ backgroundImage: String(value).includes('rgba') ?
710
+ 'linear-gradient(45deg, #ccc 25%, transparent 25%), linear-gradient(-45deg, #ccc 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #ccc 75%), linear-gradient(-45deg, transparent 75%, #ccc 75%)' :
711
+ 'none',
712
+ backgroundSize: '10px 10px',
713
+ backgroundPosition: '0 0, 0 5px, 5px -5px, -5px 0px'
714
+ }}
715
+ />
716
+ {String(value)}
717
+ </Box>
718
+ </TableCell>
719
+ </TableRow>
720
+ ))}
721
+ </TableBody>
722
+ </Table>
723
+ </TableContainer>
724
+ ) : (
725
+ <Typography variant="body2" color="textSecondary">
726
+ No colors configured
727
+ </Typography>
728
+ )}
729
+ </Box>
730
+ );
731
+ };
732
+
733
+ const JsonColorSelector = ({ method, attribute, resourceConfig }: IDashAutoAdminCustomFieldComponent) => {
734
+ switch (method) {
735
+ case 'edit':
736
+ return <><JsonEdit attribute={attribute} method={method} resourceConfig={resourceConfig} /></>;
737
+ case 'create':
738
+ return <JsonEdit attribute={attribute} method={method} resourceConfig={resourceConfig} />;
739
+ case 'view':
740
+ return <JsonView attribute={attribute} method={method} resourceConfig={resourceConfig} />;
741
+ default:
742
+ return null;
743
+ }
744
+ };
745
+
746
+ export default JsonColorSelector;