@dashadmin/dash-components 1.3.73 → 1.3.74-srcfree.1

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 (60) hide show
  1. package/package.json +24 -21
  2. package/src/components/AuditLog/AuditLog.tsx +0 -490
  3. package/src/components/AuditLog/index.ts +0 -2
  4. package/src/components/AutoCompleteArrayInput/AutoCompleteCheckBoxInput.tsx +0 -917
  5. package/src/components/AutoCompleteArrayInput/AutocompleteCheckBoxArrayInput.tsx +0 -39
  6. package/src/components/AutoCompleteArrayInputAutoSearch/AutoCompleteCheckBoxInput.tsx +0 -916
  7. package/src/components/AutoCompleteArrayInputAutoSearch/AutocompleteCheckBoxArrayAutoSearch.tsx.wip +0 -42
  8. package/src/components/AutoCompleteArrayInputAutoSearch/AutocompleteCheckBoxArrayInput.tsx +0 -44
  9. package/src/components/Json/Json.tsx +0 -312
  10. package/src/components/JsonColorSelector/JsonColorSelector.tsx +0 -1443
  11. package/src/components/JsonColorSelector/JsonColorSelector.tsx.bkup +0 -746
  12. package/src/components/JsonColorSelector/JsonColorSelectorEnhanced.tsx +0 -1135
  13. package/src/components/JsonColorSelector/JsonCssVarValues.tsx +0 -867
  14. package/src/components/JsonColorSelector/components/BasicColorSelector.tsx +0 -333
  15. package/src/components/JsonColorSelector/components/ColorEditDialog.tsx +0 -495
  16. package/src/components/JsonColorSelector/components/ColorPaletteItem.tsx +0 -80
  17. package/src/components/JsonColorSelector/components/ImageColorExtractor.tsx +0 -420
  18. package/src/components/JsonColorSelector/helpers/functions.tsx +0 -123
  19. package/src/components/JsonColorSelector/helpers/paletteDerivation.ts +0 -367
  20. package/src/components/JsonColorSelector/interfaces/interfaces.tsx +0 -22
  21. package/src/components/JsonColorSelector/useAsyncColorThief.tsx +0 -97
  22. package/src/components/ListActive/ListActive.tsx +0 -106
  23. package/src/components/ListActive/ListBoolean.tsx +0 -96
  24. package/src/components/NotificationPreferences/NotificationPreferences.tsx +0 -480
  25. package/src/components/NotificationPreferences/index.tsx +0 -2
  26. package/src/components/SearchableSelects/RASearchableSelect.tsx +0 -303
  27. package/src/components/SearchableSelects/RASearchableSelectCheckboxes.tsx +0 -617
  28. package/src/components/SearchableSelects/RASearchableSelectChips.tsx +0 -561
  29. package/src/components/SearchableSelects/RASearchableSelectChipsRecordContext.tsx +0 -366
  30. package/src/components/SearchableSelects/SearchableSelect.tsx +0 -323
  31. package/src/components/SearchableSelects/SearchableSelectCheckboxes.tsx +0 -358
  32. package/src/components/SortableDataGrid/README.md +0 -1
  33. package/src/components/SortableDataGrid/SortableDataGrid.tsx +0 -56
  34. package/src/components/SortableDataGrid/SortableDataGridBody.tsx +0 -175
  35. package/src/components/SortableDataGrid/SortableDataGridHeader.tsx +0 -171
  36. package/src/components/SortableDataGrid/SortableDataGridRow.tsx +0 -247
  37. package/src/components/Upload/SingleImageUploader.tsx +0 -55
  38. package/src/components/custom/PackageCopyMethod.tsx +0 -109
  39. package/src/components/custom/PackageRadioButtonGroup.tsx +0 -49
  40. package/src/components/dialog/Basic.tsx +0 -11
  41. package/src/components/dialog/Correct.tsx +0 -16
  42. package/src/components/dialog/Error.tsx +0 -15
  43. package/src/components/dialog/Info.tsx +0 -15
  44. package/src/components/dialog/InfoV2.tsx +0 -35
  45. package/src/components/dialog/NotFound.tsx +0 -18
  46. package/src/components/dialog/QuickSearch.tsx +0 -115
  47. package/src/components/notifications/NotificationsCenter.tsx +0 -743
  48. package/src/components/notifications/index.ts +0 -42
  49. package/src/components/tables/PaginationComponent.tsx +0 -17
  50. package/src/components/theme/AppLoadingFallback.tsx +0 -42
  51. package/src/components/theme/ErrorBoundary.tsx +0 -57
  52. package/src/components/theme/InitialLoader.tsx +0 -16
  53. package/src/components/theme/NotFound.tsx +0 -124
  54. package/src/components/theme/NotResults.tsx +0 -56
  55. package/src/hooks/useDraggable.ts +0 -11
  56. package/src/hooks/useNotifications.ts +0 -14
  57. package/src/hooks/useQuickSearch.tsx +0 -29
  58. package/src/index.tsx +0 -77
  59. package/src/styles/index.less +0 -0
  60. package/src/utils/setNativeValue.tsx +0 -23
@@ -1,1443 +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
- Dialog,
11
- DialogTitle,
12
- DialogContent,
13
- DialogActions,
14
- Card,
15
- CardContent,
16
- Chip,
17
- InputAdornment,
18
- FormControl,
19
- InputLabel,
20
- Select,
21
- MenuItem,
22
- Tabs,
23
- Tab,
24
- useColorScheme,
25
- Grid,
26
- Pagination,
27
- Link,
28
- } from '@mui/material';
29
- import DeleteIcon from '@mui/icons-material/Delete';
30
- import AddIcon from '@mui/icons-material/Add';
31
- import EditIcon from '@mui/icons-material/Edit';
32
- import PreviewIcon from '@mui/icons-material/Preview';
33
- import SearchIcon from '@mui/icons-material/Search';
34
- import ClearIcon from '@mui/icons-material/Clear';
35
- import FilterListIcon from '@mui/icons-material/FilterList';
36
- import { ChromePicker, RGBColor } from 'react-color';
37
- import { useState, useEffect, useRef, useCallback, useMemo } from 'react';
38
- import { updateDomCssVariables } from 'dash-utils';
39
- import { AuthPersistenceService } from 'dash-auth';
40
-
41
- interface KeyValuePair {
42
- key: string;
43
- value: string;
44
- id: string;
45
- }
46
-
47
- interface ColorFormat {
48
- hex: string;
49
- rgb: RGBColor;
50
- hsl: { h: number; s: number; l: number; a?: number };
51
- }
52
-
53
- type ColorFormatType = 'hex' | 'rgb' | 'rgba' | 'hsl' | 'hsla';
54
-
55
- // Helper function to determine text color based on background
56
- const getContrastColor = (color: string): string => {
57
- // Handle different color formats
58
- let r: number, g: number, b: number, a: number = 1;
59
-
60
- if (color.startsWith('#')) {
61
- // Hex color
62
- const hex = color.replace('#', '');
63
- if (hex.length === 8) {
64
- // RGBA hex
65
- r = parseInt(hex.substr(0, 2), 16);
66
- g = parseInt(hex.substr(2, 2), 16);
67
- b = parseInt(hex.substr(4, 2), 16);
68
- a = parseInt(hex.substr(6, 2), 16) / 255;
69
- } else {
70
- // RGB hex
71
- r = parseInt(hex.substr(0, 2), 16);
72
- g = parseInt(hex.substr(2, 2), 16);
73
- b = parseInt(hex.substr(4, 2), 16);
74
- }
75
- } else if (color.startsWith('rgb')) {
76
- // RGB/RGBA color
77
- const match = color.match(/rgba?\(([^)]+)\)/);
78
- if (match) {
79
- const values = match[1].split(',').map(v => parseFloat(v.trim()));
80
- [r, g, b, a = 1] = values;
81
- } else {
82
- return '#000000';
83
- }
84
- } else if (color.startsWith('hsl')) {
85
- // For HSL, we'll use a simple approach
86
- return '#000000';
87
- } else {
88
- return '#000000';
89
- }
90
-
91
- // Calculate luminance considering alpha
92
- const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
93
- const effectiveLuminance = luminance * a + (1 - a); // Blend with white background
94
-
95
- return effectiveLuminance > 0.5 ? '#000000' : '#ffffff';
96
- };
97
-
98
- // Helper function to convert color to different formats
99
- const convertColor = (color: ColorFormat, format: ColorFormatType): string => {
100
- switch (format) {
101
- case 'hex':
102
- return color.hex;
103
- case 'rgb':
104
- return `rgb(${Math.round(color.rgb.r)}, ${Math.round(color.rgb.g)}, ${Math.round(color.rgb.b)})`;
105
- case 'rgba':
106
- return `rgba(${Math.round(color.rgb.r)}, ${Math.round(color.rgb.g)}, ${Math.round(color.rgb.b)}, ${color.rgb.a || 1})`;
107
- case 'hsl':
108
- return `hsl(${Math.round(color.hsl.h)}, ${Math.round(color.hsl.s * 100)}%, ${Math.round(color.hsl.l * 100)}%)`;
109
- case 'hsla':
110
- return `hsla(${Math.round(color.hsl.h)}, ${Math.round(color.hsl.s * 100)}%, ${Math.round(color.hsl.l * 100)}%, ${color.hsl.a || 1})`;
111
- default:
112
- return color.hex;
113
- }
114
- };
115
-
116
- // Helper function to parse color key and create chips
117
- const parseColorKey = (key: string) => {
118
- const parts = key.split('--');
119
- const baseName = parts[0];
120
- const mode = parts[1];
121
-
122
- // Split base name by hyphens for individual chips
123
- const baseChips = baseName.split('-').filter(part => part.length > 0);
124
-
125
- return { baseChips, mode };
126
- };
127
-
128
- // Helper function to extract available modes from color keys
129
- const extractAvailableModes = (pairs: KeyValuePair[]): string[] => {
130
- const modes = new Set<string>();
131
-
132
- pairs.forEach(pair => {
133
- const { mode } = parseColorKey(pair.key);
134
- if (mode) {
135
- modes.add(mode);
136
- }
137
- });
138
-
139
- return Array.from(modes).sort();
140
- };
141
-
142
- // Helper function to get mode icon
143
- const getModeIcon = (mode: string | undefined): string => {
144
- if (!mode) return '';
145
-
146
- switch (mode.toLowerCase()) {
147
- case 'light':
148
- return '☀️'; // Sun icon
149
- case 'dark':
150
- return '🌙'; // Moon icon
151
- default:
152
- return mode; // Plain text for other modes
153
- }
154
- };
155
-
156
- // Color palette item component
157
- const ColorPaletteItem: React.FC<{
158
- pair: KeyValuePair;
159
- onEdit: (pair: KeyValuePair) => void;
160
- onDelete: (id: string) => void;
161
- }> = ({ pair, onEdit, onDelete }) => {
162
- const textColor = getContrastColor(pair.value);
163
- const { baseChips, mode } = parseColorKey(pair.key);
164
- const modeIcon = getModeIcon(mode);
165
-
166
- return (
167
- <Card
168
- onClick={() => onEdit(pair)}
169
- elevation={1}
170
- sx={{
171
- width: '100%',
172
- display: 'flex',
173
- alignItems: 'stretch', // Ensure stretch for full height
174
- cursor: 'pointer',
175
- transition: 'all 0.2s ease-in-out',
176
- '&:hover': {
177
- transform: 'translateY(-2px)',
178
- boxShadow: 3,
179
- },
180
- borderRadius: 2,
181
- overflow: 'hidden',
182
- position: 'relative',
183
- height: 80, // Fixed height for consistency
184
- backgroundColor: 'background.paper',
185
- }}
186
- >
187
- {/* Left Color Box */}
188
- <Box
189
- sx={{
190
- width: 80, // Fixed square width
191
- backgroundColor: pair.value,
192
- flexShrink: 0,
193
- display: 'flex',
194
- alignItems: 'center',
195
- justifyContent: 'center',
196
- position: 'relative',
197
- }}
198
- >
199
- {/* Hover Edit Icon Overlay */}
200
- <Box
201
- sx={{
202
- position: 'absolute',
203
- top: 0,
204
- left: 0,
205
- right: 0,
206
- bottom: 0,
207
- backgroundColor: 'rgba(0,0,0,0.3)',
208
- opacity: 0,
209
- transition: 'opacity 0.2s',
210
- '&:hover': { opacity: 1 },
211
- display: 'flex',
212
- alignItems: 'center',
213
- justifyContent: 'center',
214
- }}
215
- >
216
- <EditIcon sx={{ color: '#fff' }} />
217
- </Box>
218
- </Box>
219
-
220
- {/* Right Details Section */}
221
- <Box sx={{
222
- flexGrow: 1,
223
- p: 1.5,
224
- display: 'flex',
225
- flexDirection: 'column',
226
- justifyContent: 'space-between', // Space key and chips
227
- overflow: 'hidden'
228
- }}>
229
- <Box sx={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', mb: 0.5 }}>
230
- <Typography variant="caption" sx={{ fontFamily: 'monospace', fontWeight: 'bold', color: 'text.secondary' }}>
231
- {pair.value}
232
- </Typography>
233
- {mode && (
234
- <Chip
235
- label={mode}
236
- size="small"
237
- icon={<span style={{ marginLeft: 6, fontSize: '0.8rem' }}>{modeIcon}</span>}
238
- sx={{
239
- height: 18,
240
- fontSize: '0.65rem',
241
- '& .MuiChip-label': { padding: '0 6px' },
242
- }}
243
- />
244
- )}
245
- </Box>
246
-
247
- <Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
248
- {baseChips.map((chip, index) => (
249
- <Chip
250
- key={index}
251
- label={chip}
252
- size="small"
253
- variant="outlined"
254
- sx={{
255
- height: 20,
256
- fontSize: '0.7rem',
257
- maxWidth: '100%',
258
- '& .MuiChip-label': {
259
- padding: '0 6px',
260
- overflow: 'hidden',
261
- textOverflow: 'ellipsis',
262
- },
263
- }}
264
- />
265
- ))}
266
- </Box>
267
- </Box>
268
- </Card>
269
- );
270
- };
271
-
272
- // Color edit dialog component
273
- const ColorEditDialog: React.FC<{
274
- open: boolean;
275
- pair: KeyValuePair | null;
276
- onClose: () => void;
277
- onSave: (pair: KeyValuePair) => void;
278
- onDelete: (id: string) => void;
279
- existingKeys: string[];
280
- updateSingleDomColor: (key: string, color: string) => void;
281
- }> = ({ open, pair, onClose, onSave, onDelete, existingKeys,updateSingleDomColor }) => {
282
- const [editedPair, setEditedPair] = useState<KeyValuePair | null>(null);
283
- const [colorFormat, setColorFormat] = useState<ColorFormatType>('hex');
284
- const [keyError, setKeyError] = useState<string>('');
285
- const [tabValue, setTabValue] = useState(0);
286
-
287
- useEffect(() => {
288
- if (pair) {
289
- setEditedPair({ ...pair });
290
- // Detect color format
291
- if (pair.value.startsWith('rgba(')) {
292
- setColorFormat('rgba');
293
- } else if (pair.value.startsWith('rgb(')) {
294
- setColorFormat('rgb');
295
- } else if (pair.value.startsWith('hsla(')) {
296
- setColorFormat('hsla');
297
- } else if (pair.value.startsWith('hsl(')) {
298
- setColorFormat('hsl');
299
- } else {
300
- setColorFormat('hex');
301
- }
302
- }
303
- setKeyError('');
304
- }, [pair]);
305
-
306
- const handleKeyChange = (newKey: string) => {
307
- if (!editedPair) return;
308
-
309
- const isDuplicate = existingKeys.some(key => key === newKey && newKey !== pair?.key);
310
- setKeyError(isDuplicate ? 'Key already exists' : '');
311
-
312
- setEditedPair({ ...editedPair, key: newKey });
313
- };
314
-
315
- const handleColorChange = (color: any) => {
316
- if (!editedPair) return;
317
-
318
- const newValue = convertColor(color, colorFormat);
319
- setEditedPair({ ...editedPair, value: newValue });
320
-
321
- updateSingleDomColor(editedPair.key, newValue);
322
-
323
- };
324
-
325
- const handleSave = () => {
326
- if (!editedPair || keyError || !editedPair.key.trim()) return;
327
- onSave(editedPair);
328
- onClose();
329
- };
330
-
331
- const handleDelete = () => {
332
- if (!editedPair) return;
333
- onDelete(editedPair.id);
334
- onClose();
335
- };
336
-
337
- if (!editedPair) return null;
338
-
339
- return (
340
- <Dialog
341
- open={open}
342
- onClose={onClose}
343
- maxWidth="sm"
344
- fullWidth
345
- slotProps={{
346
- paper: {
347
- sx: { minHeight: 600 }
348
- }
349
- }}
350
- >
351
- <DialogTitle>
352
- {pair?.key ? `Edit Color: ${pair.key}` : 'Add New Color'}
353
- </DialogTitle>
354
- <DialogContent>
355
- <Box sx={{ display: 'flex', flexDirection: 'column', gap: 3, pt: 1 }}>
356
- {/* Color Name */}
357
- <TextField
358
- fullWidth
359
- label="Color Name"
360
- value={editedPair.key}
361
- onChange={(e) => handleKeyChange(e.target.value)}
362
- error={!!keyError}
363
- helperText={keyError}
364
- placeholder="e.g., primary-color--light, background-dark--dark"
365
- />
366
-
367
- {/* Color Format Selector */}
368
- <FormControl fullWidth>
369
- <InputLabel>Color Format</InputLabel>
370
- <Select
371
- value={colorFormat}
372
- label="Color Format"
373
- onChange={(e) => setColorFormat(e.target.value as ColorFormatType)}
374
- >
375
- <MenuItem value="hex">HEX</MenuItem>
376
- <MenuItem value="rgb">RGB</MenuItem>
377
- <MenuItem value="rgba">RGBA (with alpha)</MenuItem>
378
- <MenuItem value="hsl">HSL</MenuItem>
379
- <MenuItem value="hsla">HSLA (with alpha)</MenuItem>
380
- </Select>
381
- </FormControl>
382
-
383
- {/* Color Picker Tabs */}
384
- <Box>
385
- <Tabs value={tabValue} onChange={(_, newValue) => setTabValue(newValue)}>
386
- <Tab label="Color Picker" />
387
- <Tab label="Manual Input" />
388
- </Tabs>
389
-
390
- {tabValue === 0 && (
391
- <Box sx={{ mt: 2, display: 'flex', justifyContent: 'center' }}>
392
- <ChromePicker
393
- color={editedPair.value}
394
- onChange={handleColorChange}
395
- disableAlpha={!['rgba', 'hsla'].includes(colorFormat)}
396
- />
397
- </Box>
398
- )}
399
-
400
- {tabValue === 1 && (
401
- <Box sx={{ mt: 2 }}>
402
- <TextField
403
- fullWidth
404
- label={`Color Value (${colorFormat.toUpperCase()})`}
405
- value={editedPair.value}
406
- onChange={(e) => setEditedPair({ ...editedPair, value: e.target.value })}
407
- placeholder={
408
- colorFormat === 'hex' ? '#ff0000' :
409
- colorFormat === 'rgb' ? 'rgb(255, 0, 0)' :
410
- colorFormat === 'rgba' ? 'rgba(255, 0, 0, 1)' :
411
- colorFormat === 'hsl' ? 'hsl(0, 100%, 50%)' :
412
- 'hsla(0, 100%, 50%, 1)'
413
- }
414
- slotProps={{
415
- input: {
416
- startAdornment: (
417
- <InputAdornment position="start">
418
- <Box
419
- sx={{
420
- width: 20,
421
- height: 20,
422
- backgroundColor: editedPair.value,
423
- border: '1px solid #ccc',
424
- borderRadius: 1,
425
- }}
426
- />
427
- </InputAdornment>
428
- ),
429
- }
430
- }}
431
- />
432
- </Box>
433
- )}
434
- </Box>
435
-
436
- {/* Color Preview */}
437
- <Box>
438
- <Typography variant="subtitle2" gutterBottom>
439
- Preview
440
- </Typography>
441
- <Box
442
- sx={{
443
- width: '100%',
444
- height: 80,
445
- backgroundColor: editedPair.value,
446
- border: '1px solid #ccc',
447
- borderRadius: 1,
448
- display: 'flex',
449
- alignItems: 'center',
450
- justifyContent: 'center',
451
- position: 'relative',
452
- }}
453
- >
454
- <Typography
455
- variant="body2"
456
- sx={{
457
- color: getContrastColor(editedPair.value),
458
- fontWeight: 'bold',
459
- backgroundColor: 'rgba(0,0,0,0.1)',
460
- padding: '4px 8px',
461
- borderRadius: 1,
462
- }}
463
- >
464
- {editedPair.key || 'Color Preview'}
465
- </Typography>
466
- </Box>
467
- </Box>
468
- </Box>
469
- </DialogContent>
470
- <DialogActions>
471
- {pair?.key && (
472
- <Button
473
- onClick={handleDelete}
474
- color="error"
475
- startIcon={<DeleteIcon />}
476
- >
477
- Delete
478
- </Button>
479
- )}
480
- <Box sx={{ flexGrow: 1 }} />
481
- <Button onClick={onClose}>
482
- Cancel
483
- </Button>
484
- <Button
485
- onClick={handleSave}
486
- variant="contained"
487
- disabled={!!keyError || !editedPair.key.trim()}
488
- >
489
- Save
490
- </Button>
491
- </DialogActions>
492
- </Dialog>
493
- );
494
- };
495
-
496
- export const JsonEdit: React.FC<IDashAutoAdminCustomFieldComponent> = (props) => {
497
- const { method, attribute, resourceConfig } = props;
498
-
499
- const record = useRecordContext();
500
- const { setValue, getValues } = useFormContext();
501
-
502
- const attributePath = attribute.attribute;
503
- const isNestedSetting = attributePath.startsWith('settings.');
504
- const settingsPath = isNestedSetting ? attributePath.split('.').slice(1).join('.') : attributePath;
505
-
506
- const [pairs, setPairs] = useState<KeyValuePair[]>([]);
507
- const [searchTerm, setSearchTerm] = useState<string>('');
508
- const [selectedMode, setSelectedMode] = useState<string>('');
509
- const [isLoading, setIsLoading] = useState<boolean>(true);
510
- const [isRendering, setIsRendering] = useState<boolean>(true);
511
- const [editDialogOpen, setEditDialogOpen] = useState<boolean>(false);
512
- const [editingPair, setEditingPair] = useState<KeyValuePair | null>(null);
513
-
514
- // Pagination state
515
- const [page, setPage] = useState<number>(1);
516
- const [pageSize] = useState<number>(24);
517
-
518
- // Throttling refs
519
- const throttleTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
520
- const pendingUpdatesRef = useRef<Map<string, { key: string; color: string }>>(new Map());
521
-
522
- // Dispatch loading events
523
- const dispatchLoadingEvent = useCallback((loading: boolean) => {
524
- window.dispatchEvent(new MessageEvent('auto-admin-loading-state', { data: loading }));
525
- }, []);
526
-
527
- // Extract available modes from pairs
528
- const availableModes = useMemo(() => {
529
- return extractAvailableModes(pairs);
530
- }, [pairs]);
531
-
532
- // Filter pairs based on search term and selected mode
533
- const filteredPairs = useMemo(() => {
534
- let filtered = pairs;
535
-
536
- // Filter by search term
537
- if (searchTerm.trim()) {
538
- filtered = filtered.filter(pair =>
539
- pair.key.toLowerCase().includes(searchTerm.toLowerCase())
540
- );
541
- }
542
-
543
- // Filter by selected mode
544
- if (selectedMode) {
545
- filtered = filtered.filter(pair => {
546
- const { mode } = parseColorKey(pair.key);
547
- return mode === selectedMode;
548
- });
549
- }
550
-
551
- return filtered;
552
- }, [pairs, searchTerm, selectedMode]);
553
-
554
- const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
555
- setSearchTerm(event.target.value);
556
- };
557
-
558
- const handleClearSearch = () => {
559
- setSearchTerm('');
560
- };
561
-
562
- const handleModeChange = (event: any) => {
563
- setSelectedMode(event.target.value);
564
- };
565
-
566
- const handleClearMode = () => {
567
- setSelectedMode('');
568
- };
569
-
570
- // Initial data loading effect
571
- useEffect(() => {
572
- const loadInitialData = async () => {
573
- setIsLoading(true);
574
- dispatchLoadingEvent(true);
575
-
576
- try {
577
- let initialValue: Record<string, any> = {};
578
- let defaultValues: Record<string, any> = {};
579
-
580
- try {
581
- defaultValues = attribute?.default_value || {};
582
- } catch (e) {
583
- defaultValues = {};
584
- }
585
-
586
- if (isNestedSetting && record?.settings) {
587
- const settings = record.settings || {};
588
- initialValue = { ...defaultValues, ...getNestedValue(settings, settingsPath) };
589
- } else if (record?.[attributePath]) {
590
- initialValue = { ...defaultValues, ...record[attributePath] };
591
- } else {
592
- initialValue = defaultValues;
593
- }
594
-
595
- const initialPairs = Object.entries(initialValue).map(([key, value]) => ({
596
- key,
597
- value: String(value),
598
- id: generateId()
599
- }));
600
-
601
- setPairs(initialPairs);
602
-
603
- // Simulate processing time for large datasets
604
- if (initialPairs.length > 50) {
605
- await new Promise(resolve => setTimeout(resolve, 100));
606
- }
607
-
608
- } catch (error) {
609
- console.error('Error loading initial data:', error);
610
- } finally {
611
- setIsLoading(false);
612
- }
613
- };
614
-
615
- loadInitialData();
616
- }, [record, attributePath]);
617
-
618
- // Rendering completion effect
619
- useEffect(() => {
620
- if (!isLoading && pairs.length >= 0) {
621
- setIsRendering(true);
622
-
623
- requestAnimationFrame(() => {
624
- requestAnimationFrame(() => {
625
- setIsRendering(false);
626
- dispatchLoadingEvent(false);
627
- });
628
- });
629
- }
630
- }, [isLoading, pairs.length, dispatchLoadingEvent]);
631
-
632
- // Cleanup throttle timeout on unmount
633
- useEffect(() => {
634
- return () => {
635
- if (throttleTimeoutRef.current) {
636
- clearTimeout(throttleTimeoutRef.current);
637
- }
638
- dispatchLoadingEvent(false);
639
- };
640
- }, [dispatchLoadingEvent]);
641
-
642
- const getNestedValue = (obj: any, path: string): any => {
643
- if (!path) return obj;
644
- const keys = path.split('.');
645
- return keys.reduce((o, key) => (o && o[key] !== undefined ? o[key] : {}), obj);
646
- };
647
-
648
- const setNestedValue = (obj: any, path: string, value: any): any => {
649
- if (!path) return value;
650
-
651
- const keys = path.split('.');
652
- const lastKey = keys.pop();
653
- const lastObj = keys.reduce((o, key) => {
654
- if (o[key] === undefined) o[key] = {};
655
- return o[key];
656
- }, obj);
657
-
658
- if (lastKey) {
659
- lastObj[lastKey] = value;
660
- }
661
- return obj;
662
- };
663
-
664
- const generateId = (): string => {
665
- return Math.random().toString(36).substring(2, 11);
666
- };
667
-
668
- const addPair = () => {
669
- const newPair = { key: '', value: '#000000', id: generateId() };
670
- setEditingPair(newPair);
671
- setEditDialogOpen(true);
672
- };
673
-
674
- const handleEditPair = (pair: KeyValuePair) => {
675
- setEditingPair(pair);
676
- setEditDialogOpen(true);
677
- };
678
-
679
- const handleSavePair = (editedPair: KeyValuePair) => {
680
- const existingIndex = pairs.findIndex(p => p.id === editedPair.id);
681
- let updatedPairs: KeyValuePair[];
682
-
683
- if (existingIndex >= 0) {
684
- // Update existing pair
685
- updatedPairs = pairs.map(p => p.id === editedPair.id ? editedPair : p);
686
- } else {
687
- // Add new pair
688
- updatedPairs = [...pairs, editedPair];
689
- }
690
-
691
- setPairs(updatedPairs);
692
- updateFormValue(updatedPairs);
693
- updateSingleDomColor(editedPair.key, editedPair.value);
694
- };
695
-
696
- const handleDeletePair = (id: string) => {
697
- const updatedPairs = pairs.filter(pair => pair.id !== id);
698
- setPairs(updatedPairs);
699
- updateFormValue(updatedPairs);
700
- };
701
-
702
- // Update only a single DOM color property
703
- const updateSingleDomColor = (key: string, color: string) => {
704
- if (!key.trim()) return;
705
-
706
- const currentTheme = document.documentElement.getAttribute('data-theme');
707
- const themeSuffix = `--${currentTheme}`;
708
-
709
- if (key.endsWith(themeSuffix)) {
710
- const baseKey = key.slice(0, -themeSuffix.length);
711
- document.documentElement.style.setProperty(`--${baseKey}`, color);
712
- }
713
- };
714
-
715
- // Batch update for preview only - processes all colors at once
716
- /*const updateDomColors = (colors: Record<string, any>) => {
717
- const currentTheme = document.documentElement.getAttribute('data-theme');
718
- const themeSuffix = `--${currentTheme}`;
719
-
720
- requestAnimationFrame(() => {
721
- Object.entries(colors).forEach(([key, value]) => {
722
- if (key.endsWith(themeSuffix)) {
723
- const baseKey = key.slice(0, -themeSuffix.length);
724
- document.documentElement.style.setProperty(`--${baseKey}`, String(value));
725
- }
726
- });
727
- });
728
- };*/
729
-
730
-
731
- const getTenantSettingsValues = () => {
732
- const persistedTenantSettings = AuthPersistenceService.getTenantSettings();
733
- if (persistedTenantSettings) {
734
- return persistedTenantSettings?.values || {};
735
- }
736
- return {}
737
- };
738
-
739
-
740
- const handlePreview = (m:string) => {
741
- const colorsObj = pairs.reduce((acc, pair) => {
742
- if (pair.key.trim()) {
743
- acc[pair.key] = pair.value;
744
- }
745
- return acc;
746
- }, {} as Record<string, any>);
747
-
748
- //updateAllDomColors();
749
- //updateDomColors(colorsObj);
750
- updateDomCssVariables(m,colorsObj,getTenantSettingsValues());
751
-
752
- };
753
-
754
- const updateFormValue = (currentPairs: KeyValuePair[]) => {
755
- const obj = currentPairs.reduce((acc, pair) => {
756
- if (pair.key.trim()) {
757
- acc[pair.key] = pair.value;
758
- }
759
- return acc;
760
- }, {} as Record<string, any>);
761
-
762
- if (isNestedSetting) {
763
- const currentSettings = getValues('settings') || {};
764
- const updatedSettings = setNestedValue({...currentSettings}, settingsPath, obj);
765
- setValue('settings', updatedSettings, { shouldDirty: true });
766
- } else {
767
- setValue(attributePath, obj, { shouldDirty: true });
768
- }
769
- };
770
-
771
- // Show loading state while component is initializing or rendering
772
- if (isLoading || isRendering) {
773
- return (
774
- <Box sx={{ mt: 1, mb: 2, display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: 200 }}>
775
- <Typography variant="body2" color="textSecondary">
776
- {isLoading ? 'Loading color settings...' : 'Rendering colors...'}
777
- </Typography>
778
- </Box>
779
- );
780
- }
781
-
782
- return (
783
- <Box sx={{ mt: 1, mb: 2 }}>
784
- <Typography variant="subtitle1" gutterBottom>
785
- {attribute.label || 'Color Palette'}
786
- </Typography>
787
-
788
- {/* Header Actions */}
789
- <Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 3 }}>
790
- <Button
791
- startIcon={<PreviewIcon />}
792
- variant="contained"
793
- size="small"
794
- onClick={() => handlePreview(document.documentElement.getAttribute('data-theme'))}
795
- color="primary"
796
- >
797
- Preview Colors TEST
798
- </Button>
799
- <Button
800
- startIcon={<AddIcon />}
801
- variant="outlined"
802
- size="small"
803
- onClick={addPair}
804
- >
805
- Add Color
806
- </Button>
807
- </Box>
808
-
809
- {/* Search and Filter Controls */}
810
- <Box sx={{ mb: 3, display: 'flex', gap: 2, alignItems: 'flex-end' }}>
811
- {/* Search Field */}
812
- <TextField
813
- fullWidth
814
- size="small"
815
- placeholder="Search colors by name..."
816
- value={searchTerm}
817
- onChange={handleSearchChange}
818
- slotProps={{
819
- input: {
820
- startAdornment: (
821
- <InputAdornment position="start">
822
- <SearchIcon />
823
- </InputAdornment>
824
- ),
825
- endAdornment: searchTerm && (
826
- <InputAdornment position="end">
827
- <IconButton
828
- size="small"
829
- onClick={handleClearSearch}
830
- edge="end"
831
- >
832
- <ClearIcon />
833
- </IconButton>
834
- </InputAdornment>
835
- ),
836
- }
837
- }}
838
- />
839
-
840
- {/* Mode Selector */}
841
- <FormControl size="small" sx={{ minWidth: 150 }}>
842
- <InputLabel>Mode</InputLabel>
843
- <Select
844
- value={selectedMode}
845
- label="Mode"
846
- onChange={handleModeChange}
847
- startAdornment={
848
- <InputAdornment position="start">
849
- <FilterListIcon fontSize="small" />
850
- </InputAdornment>
851
- }
852
- endAdornment={selectedMode && (
853
- <InputAdornment position="end">
854
- <IconButton
855
- size="small"
856
- onClick={handleClearMode}
857
- edge="end"
858
- sx={{ mr: 1 }}
859
- >
860
- <ClearIcon fontSize="small" />
861
- </IconButton>
862
- </InputAdornment>
863
- )}
864
- >
865
- <MenuItem value="">
866
- <em>All Modes</em>
867
- </MenuItem>
868
- {availableModes.map((mode) => (
869
- <MenuItem key={mode} value={mode}>
870
- <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
871
- <span>{getModeIcon(mode)}</span>
872
- <span>{mode}</span>
873
- </Box>
874
- </MenuItem>
875
- ))}
876
- </Select>
877
- </FormControl>
878
- </Box>
879
-
880
- {/* Results count when filtering */}
881
- {(searchTerm || selectedMode) && (
882
- <Box sx={{ mb: 2, display: 'flex', alignItems: 'center', gap: 1, flexWrap: 'wrap' }}>
883
- <Chip
884
- label={`${filteredPairs.length} of ${pairs.length} colors`}
885
- size="small"
886
- variant="outlined"
887
- />
888
- {selectedMode && (
889
- <Chip
890
- label={`Mode: ${selectedMode}`}
891
- size="small"
892
- variant="filled"
893
- color="primary"
894
- onDelete={handleClearMode}
895
- avatar={<span style={{ paddingLeft: 6 }}>{getModeIcon(selectedMode)}</span>}
896
- />
897
- )}
898
- {searchTerm && (
899
- <Chip
900
- label={`Search: "${searchTerm}"`}
901
- size="small"
902
- variant="filled"
903
- color="secondary"
904
- onDelete={handleClearSearch}
905
- />
906
- )}
907
- </Box>
908
- )}
909
-
910
- {/* Color Grid with Pagination */}
911
- {isRendering ? (
912
- <Box sx={{ display: 'flex', justifyContent: 'center', p: 4, alignItems: 'center', flexDirection: 'column', gap: 2 }}>
913
- <Typography>Rendering Grid...</Typography>
914
- </Box>
915
- ) : filteredPairs.length > 0 ? (
916
- <>
917
- <Box sx={{
918
- display: 'grid',
919
- gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))',
920
- gap: 2,
921
- width: '100%'
922
- }}>
923
- {filteredPairs
924
- .slice((page - 1) * pageSize, page * pageSize)
925
- .map((pair) => (
926
- <Box key={pair.id} sx={{ minWidth: 0 }}>
927
- <ColorPaletteItem
928
- pair={pair}
929
- onEdit={handleEditPair}
930
- onDelete={handleDeletePair}
931
- />
932
- </Box>
933
- ))}
934
- </Box>
935
-
936
- {/* Pagination Controls */}
937
- {filteredPairs.length > pageSize && (
938
- <Box sx={{ mt: 4, display: 'flex', justifyContent: 'center' }}>
939
- <Pagination
940
- count={Math.ceil(filteredPairs.length / pageSize)}
941
- page={page}
942
- onChange={(_, value) => setPage(value)}
943
- color="primary"
944
- size="large"
945
- showFirstButton
946
- showLastButton
947
- />
948
- </Box>
949
- )}
950
-
951
- <Typography variant="caption" sx={{ display: 'block', textAlign: 'center', mt: 2, color: 'text.secondary' }}>
952
- Showing {Math.min((page - 1) * pageSize + 1, filteredPairs.length)} - {Math.min(page * pageSize, filteredPairs.length)} of {filteredPairs.length} colors
953
- </Typography>
954
- </>
955
- ) : (
956
- <Box sx={{
957
- p: 4,
958
- textAlign: 'center',
959
- bgcolor: 'background.paper',
960
- borderRadius: 1,
961
- border: '1px dashed #ccc'
962
- }}>
963
- <Typography color="textSecondary">
964
- No colors found using current filters.
965
- </Typography>
966
- <Box sx={{ mt: 1 }}>
967
- <Link
968
- component="button"
969
- variant="body2"
970
- onClick={() => {
971
- setSearchTerm('');
972
- setSelectedMode('');
973
- setPage(1);
974
- }}
975
- >
976
- Clear filters
977
- </Link>
978
- </Box>
979
- </Box>
980
- )}
981
-
982
- {/* Show message when no colors exist */}
983
- {pairs.length === 0 && (
984
- <Box sx={{ textAlign: 'center', py: 4 }}>
985
- <Typography variant="body2" color="textSecondary" gutterBottom>
986
- No colors in your palette yet
987
- </Typography>
988
- <Button
989
- startIcon={<AddIcon />}
990
- variant="contained"
991
- onClick={addPair}
992
- >
993
- Add Your First Color
994
- </Button>
995
- </Box>
996
- )}
997
-
998
- {/* Edit Dialog */}
999
- <ColorEditDialog
1000
- open={editDialogOpen}
1001
- pair={editingPair}
1002
- onClose={() => {
1003
- setEditDialogOpen(false);
1004
- setEditingPair(null);
1005
- }}
1006
- onSave={handleSavePair}
1007
- onDelete={handleDeletePair}
1008
- existingKeys={pairs.map(p => p.key)}
1009
- updateSingleDomColor={updateSingleDomColor}
1010
- />
1011
- </Box>
1012
- );
1013
- };
1014
-
1015
- export const JsonView: React.FC<IDashAutoAdminCustomFieldComponent> = (props) => {
1016
- const { attribute } = props;
1017
- const record = useRecordContext();
1018
-
1019
- const attributePath = attribute.attribute;
1020
- const isNestedSetting = attributePath.startsWith('settings.');
1021
- const settingsPath = isNestedSetting ? attributePath.split('.').slice(1).join('.') : attributePath;
1022
-
1023
- const [searchTerm, setSearchTerm] = useState<string>('');
1024
- const [selectedMode, setSelectedMode] = useState<string>('');
1025
- const [isLoading, setIsLoading] = useState<boolean>(true);
1026
- const [isRendering, setIsRendering] = useState<boolean>(true);
1027
- const [displayValue, setDisplayValue] = useState<Record<string, any>>({});
1028
-
1029
- // Dispatch loading events
1030
- const dispatchLoadingEvent = useCallback((loading: boolean) => {
1031
- window.dispatchEvent(new MessageEvent('auto-admin-loading-state', { data: loading }));
1032
- }, []);
1033
-
1034
- // Convert displayValue to pairs for mode extraction
1035
- const displayPairs = useMemo(() => {
1036
- return Object.entries(displayValue).map(([key, value]) => ({
1037
- key,
1038
- value: String(value),
1039
- id: key
1040
- }));
1041
- }, [displayValue]);
1042
-
1043
- // Extract available modes from display pairs
1044
- const availableModes = useMemo(() => {
1045
- return extractAvailableModes(displayPairs);
1046
- }, [displayPairs]);
1047
-
1048
- // Initial data loading effect
1049
- useEffect(() => {
1050
- const loadViewData = async () => {
1051
- setIsLoading(true);
1052
- dispatchLoadingEvent(true);
1053
-
1054
- try {
1055
- let viewValue: Record<string, any> = {};
1056
- let defaultValues: Record<string, any> = {};
1057
-
1058
- try {
1059
- defaultValues = attribute?.default_value || {};
1060
- } catch (e) {
1061
- defaultValues = {};
1062
- }
1063
-
1064
- if (isNestedSetting && record?.settings) {
1065
- const settings = record.settings || {};
1066
- if (settingsPath) {
1067
- const keys = settingsPath.split('.');
1068
- viewValue = { ...defaultValues, ...keys.reduce((o, key) => (o && o[key] !== undefined ? o[key] : {}), settings) };
1069
- } else {
1070
- viewValue = { ...defaultValues, ...settings };
1071
- }
1072
- } else if (record?.[attributePath]) {
1073
- viewValue = { ...defaultValues, ...record[attributePath] };
1074
- } else {
1075
- viewValue = defaultValues;
1076
- }
1077
-
1078
- setDisplayValue(viewValue);
1079
-
1080
- // Simulate processing time for large datasets
1081
- if (Object.keys(viewValue).length > 50) {
1082
- await new Promise(resolve => setTimeout(resolve, 100));
1083
- }
1084
-
1085
- } catch (error) {
1086
- console.error('Error loading view data:', error);
1087
- } finally {
1088
- setIsLoading(false);
1089
- }
1090
- };
1091
-
1092
- loadViewData();
1093
- }, [record, attributePath, attribute, isNestedSetting, settingsPath]);
1094
-
1095
- // Rendering completion effect
1096
- useEffect(() => {
1097
- if (!isLoading && Object.keys(displayValue).length >= 0) {
1098
- setIsRendering(true);
1099
-
1100
- requestAnimationFrame(() => {
1101
- requestAnimationFrame(() => {
1102
- setIsRendering(false);
1103
- dispatchLoadingEvent(false);
1104
- });
1105
- });
1106
- }
1107
- }, [isLoading, displayValue, dispatchLoadingEvent]);
1108
-
1109
- // Cleanup effect
1110
- useEffect(() => {
1111
- return () => {
1112
- dispatchLoadingEvent(false);
1113
- };
1114
- }, [dispatchLoadingEvent]);
1115
-
1116
- // Filter display values based on search term and selected mode
1117
- const filteredDisplayValue = useMemo(() => {
1118
- let filtered = Object.entries(displayValue);
1119
-
1120
- // Filter by search term
1121
- if (searchTerm.trim()) {
1122
- filtered = filtered.filter(([key]) =>
1123
- key.toLowerCase().includes(searchTerm.toLowerCase())
1124
- );
1125
- }
1126
-
1127
- // Filter by selected mode
1128
- if (selectedMode) {
1129
- filtered = filtered.filter(([key]) => {
1130
- const { mode } = parseColorKey(key);
1131
- return mode === selectedMode;
1132
- });
1133
- }
1134
-
1135
- return filtered.reduce((acc, [key, value]) => {
1136
- acc[key] = value;
1137
- return acc;
1138
- }, {} as Record<string, any>);
1139
- }, [displayValue, searchTerm, selectedMode]);
1140
-
1141
- const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
1142
- setSearchTerm(event.target.value);
1143
- };
1144
-
1145
- const handleClearSearch = () => {
1146
- setSearchTerm('');
1147
- };
1148
-
1149
- const handleModeChange = (event: any) => {
1150
- setSelectedMode(event.target.value);
1151
- };
1152
-
1153
- const handleClearMode = () => {
1154
- setSelectedMode('');
1155
- };
1156
-
1157
- // Show loading state while component is initializing or rendering
1158
- if (isLoading || isRendering) {
1159
- return (
1160
- <Box sx={{ mt: 1, mb: 2, display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: 200 }}>
1161
- <Typography variant="body2" color="textSecondary">
1162
- {isLoading ? 'Loading color settings...' : 'Rendering colors...'}
1163
- </Typography>
1164
- </Box>
1165
- );
1166
- }
1167
-
1168
- return (
1169
- <Box sx={{ mt: 1, mb: 2 }}>
1170
- <Typography variant="subtitle1" gutterBottom>
1171
- {attribute.label || 'Color Palette'}
1172
- </Typography>
1173
-
1174
- {Object.keys(displayValue).length > 0 && (
1175
- <>
1176
- {/* Search and Filter Controls for View Mode */}
1177
- <Box sx={{ mb: 3, display: 'flex', gap: 2, alignItems: 'flex-end' }}>
1178
- {/* Search Field */}
1179
- <TextField
1180
- fullWidth
1181
- size="small"
1182
- placeholder="Search colors by name..."
1183
- value={searchTerm}
1184
- onChange={handleSearchChange}
1185
- slotProps={{
1186
- input: {
1187
- startAdornment: (
1188
- <InputAdornment position="start">
1189
- <SearchIcon />
1190
- </InputAdornment>
1191
- ),
1192
- endAdornment: searchTerm && (
1193
- <InputAdornment position="end">
1194
- <IconButton
1195
- size="small"
1196
- onClick={handleClearSearch}
1197
- edge="end"
1198
- >
1199
- <ClearIcon />
1200
- </IconButton>
1201
- </InputAdornment>
1202
- ),
1203
- }
1204
- }}
1205
- />
1206
-
1207
- {/* Mode Selector */}
1208
- <FormControl size="small" sx={{ minWidth: 150 }}>
1209
- <InputLabel>Mode</InputLabel>
1210
- <Select
1211
- value={selectedMode}
1212
- label="Mode"
1213
- onChange={handleModeChange}
1214
- startAdornment={
1215
- <InputAdornment position="start">
1216
- <FilterListIcon fontSize="small" />
1217
- </InputAdornment>
1218
- }
1219
- endAdornment={selectedMode && (
1220
- <InputAdornment position="end">
1221
- <IconButton
1222
- size="small"
1223
- onClick={handleClearMode}
1224
- edge="end"
1225
- sx={{ mr: 1 }}
1226
- >
1227
- <ClearIcon fontSize="small" />
1228
- </IconButton>
1229
- </InputAdornment>
1230
- )}
1231
- >
1232
- <MenuItem value="">
1233
- <em>All Modes</em>
1234
- </MenuItem>
1235
- {availableModes.map((mode) => (
1236
- <MenuItem key={mode} value={mode}>
1237
- <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
1238
- <span>{getModeIcon(mode)}</span>
1239
- <span>{mode}</span>
1240
- </Box>
1241
- </MenuItem>
1242
- ))}
1243
- </Select>
1244
- </FormControl>
1245
- </Box>
1246
-
1247
- {/* Results count when filtering */}
1248
- {(searchTerm || selectedMode) && (
1249
- <Box sx={{ mb: 2, display: 'flex', alignItems: 'center', gap: 1, flexWrap: 'wrap' }}>
1250
- <Chip
1251
- label={`${Object.keys(filteredDisplayValue).length} of ${Object.keys(displayValue).length} colors`}
1252
- size="small"
1253
- variant="outlined"
1254
- />
1255
- {selectedMode && (
1256
- <Chip
1257
- label={`Mode: ${getModeIcon(selectedMode)} ${selectedMode}`}
1258
- size="small"
1259
- variant="filled"
1260
- color="primary"
1261
- onDelete={handleClearMode}
1262
- />
1263
- )}
1264
- {searchTerm && (
1265
- <Chip
1266
- label={`Search: "${searchTerm}"`}
1267
- size="small"
1268
- variant="filled"
1269
- color="secondary"
1270
- onDelete={handleClearSearch}
1271
- />
1272
- )}
1273
- </Box>
1274
- )}
1275
-
1276
- {Object.keys(filteredDisplayValue).length > 0 ? (
1277
- <Box sx={{
1278
- display: 'grid',
1279
- gridTemplateColumns: 'repeat(auto-fill, minmax(240px, 1fr))', // Double the width
1280
- gap: 0,
1281
- border: '1px solid #e0e0e0',
1282
- }}>
1283
- {Object.entries(filteredDisplayValue).map(([key, value]) => {
1284
- const { baseChips, mode } = parseColorKey(key);
1285
- const modeIcon = getModeIcon(mode);
1286
-
1287
- return (
1288
- <Box
1289
- key={key}
1290
- sx={{
1291
- aspectRatio: 'unset', // Remove aspect ratio
1292
- height: '30px', // Set fixed height
1293
- transition: 'all 0.2s ease-in-out',
1294
- '&:hover': {
1295
- transform: 'translateY(-2px)',
1296
- boxShadow: 3,
1297
- },
1298
- position: 'relative',
1299
- overflow: 'hidden',
1300
- backgroundColor: value as string,
1301
- display: 'flex',
1302
- flexDirection: 'row', // Change to row
1303
- alignItems: 'center',
1304
- justifyContent: 'flex-start', // Align to start
1305
- border: '1px solid rgba(255,255,255,0.1)',
1306
- padding: '0 8px', // Horizontal padding only
1307
- gap: 0.5,
1308
- }}
1309
- >
1310
- {/* Base name chips */}
1311
- <Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.25, justifyContent: 'center' }}>
1312
- {baseChips.map((chip, index) => (
1313
- <Chip
1314
- key={index}
1315
- label={chip}
1316
- size="small"
1317
- sx={{
1318
- height: 16,
1319
- fontSize: '0.6rem',
1320
- backgroundColor: 'rgba(255,255,255,0.1)',
1321
- color: '#fff',
1322
- '& .MuiChip-label': {
1323
- padding: '0 4px',
1324
- },
1325
- }}
1326
- />
1327
- ))}
1328
- </Box>
1329
-
1330
- {/* Mode chip/icon */}
1331
- {mode && (
1332
- <Chip
1333
- label={modeIcon}
1334
- size="small"
1335
- sx={{
1336
- height: 16,
1337
- fontSize: '0.6rem',
1338
- backgroundColor: 'rgba(0,0,0,0.7)',
1339
- color: '#fff',
1340
- '& .MuiChip-label': {
1341
- padding: '0 4px',
1342
- },
1343
- }}
1344
- />
1345
- )}
1346
-
1347
- {/* Color value - smaller and at bottom */}
1348
- <Typography
1349
- variant="caption"
1350
- sx={{
1351
- color: getContrastColor(value as string),
1352
- backgroundColor: 'rgba(0,0,0,0.1)',
1353
- padding: '1px 4px',
1354
- borderRadius: 0.5,
1355
- fontSize: '0.5rem',
1356
- fontFamily: 'monospace',
1357
- textAlign: 'center',
1358
- mt: 'auto',
1359
- }}
1360
- >
1361
- {value as string}
1362
- </Typography>
1363
- </Box>
1364
- );
1365
- })}
1366
- </Box>
1367
- ) : (
1368
- <Box sx={{ textAlign: 'center', py: 4 }}>
1369
- <Typography variant="body2" color="textSecondary">
1370
- No colors found matching your filters
1371
- </Typography>
1372
- <Box sx={{ mt: 1, display: 'flex', justifyContent: 'center', gap: 1 }}>
1373
- {searchTerm && (
1374
- <Button size="small" onClick={handleClearSearch}>
1375
- Clear search
1376
- </Button>
1377
- )}
1378
- {selectedMode && (
1379
- <Button size="small" onClick={handleClearMode}>
1380
- Clear mode filter
1381
- </Button>
1382
- )}
1383
- </Box>
1384
- </Box>
1385
- )}
1386
- </>
1387
- )}
1388
-
1389
- {Object.keys(displayValue).length === 0 && (
1390
- <Box sx={{ textAlign: 'center', py: 4 }}>
1391
- <Typography variant="body2" color="textSecondary">
1392
- No colors configured in this palette
1393
- </Typography>
1394
- </Box>
1395
- )}
1396
- </Box>
1397
- );
1398
- };
1399
-
1400
- const JsonColorSelector = ({ method, attribute, resourceConfig }: IDashAutoAdminCustomFieldComponent) => {
1401
- const [componentLoading, setComponentLoading] = useState(true);
1402
-
1403
-
1404
- // Dispatch loading event when component mounts
1405
- useEffect(() => {
1406
- window.dispatchEvent(new MessageEvent('auto-admin-loading-state', { data: true }));
1407
-
1408
- // Set a small timeout to allow component to initialize
1409
- const timer = setTimeout(() => {
1410
- setComponentLoading(false);
1411
- }, 50);
1412
-
1413
- return () => {
1414
- clearTimeout(timer);
1415
- // Ensure loading state is cleared on unmount
1416
- window.dispatchEvent(new MessageEvent('auto-admin-loading-state', { data: false }));
1417
- };
1418
- }, []);
1419
-
1420
- // Show initial loading state
1421
- if (componentLoading) {
1422
- return (
1423
- <Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: 100 }}>
1424
- <Typography variant="body2" color="textSecondary">
1425
- Initializing color palette...
1426
- </Typography>
1427
- </Box>
1428
- );
1429
- }
1430
-
1431
- switch (method) {
1432
- case 'edit':
1433
- return <JsonEdit attribute={attribute} method={method} resourceConfig={resourceConfig} />;
1434
- case 'create':
1435
- return <JsonEdit attribute={attribute} method={method} resourceConfig={resourceConfig} />;
1436
- case 'view':
1437
- return <JsonView attribute={attribute} method={method} resourceConfig={resourceConfig} />;
1438
- default:
1439
- return null;
1440
- }
1441
- };
1442
-
1443
- export default JsonColorSelector;