@dashadmin/dash-components 1.3.17 → 1.3.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/AuditLog/AuditLog.js +1 -0
- package/dist/components/AuditLog/index.js +1 -0
- package/dist/components/AutoCompleteArrayInput/AutoCompleteCheckBoxInput.js +3 -0
- package/dist/components/AutoCompleteArrayInput/AutocompleteCheckBoxArrayInput.js +1 -0
- package/dist/components/AutoCompleteArrayInputAutoSearch/AutoCompleteCheckBoxInput.js +3 -0
- package/dist/components/AutoCompleteArrayInputAutoSearch/AutocompleteCheckBoxArrayAutoSearch.tsx.wip +42 -0
- package/dist/components/AutoCompleteArrayInputAutoSearch/AutocompleteCheckBoxArrayInput.js +1 -0
- package/dist/components/Json/Json.js +1 -0
- package/dist/components/JsonColorSelector/JsonColorSelector.js +1 -0
- package/dist/components/JsonColorSelector/JsonColorSelector.tsx.bkup +746 -0
- package/dist/components/JsonColorSelector/JsonColorSelectorEnhanced.js +2 -0
- package/dist/components/JsonColorSelector/JsonCssVarValues.js +1 -0
- package/dist/components/JsonColorSelector/components/ColorEditDialog.js +1 -0
- package/dist/components/JsonColorSelector/components/ColorPaletteItem.js +1 -0
- package/dist/components/JsonColorSelector/components/ImageColorExtractor.js +1 -0
- package/dist/components/JsonColorSelector/helpers/functions.js +1 -0
- package/dist/components/JsonColorSelector/interfaces/interfaces.js +0 -0
- package/dist/components/JsonColorSelector/useAsyncColorThief.js +1 -0
- package/dist/components/ListActive/ListActive.js +1 -0
- package/dist/components/ListActive/ListBoolean.js +1 -0
- package/dist/components/NotificationPreferences/NotificationPreferences.js +1 -0
- package/dist/components/NotificationPreferences/index.js +1 -0
- package/dist/components/SearchableSelects/RASearchableSelect.js +1 -0
- package/dist/components/SearchableSelects/RASearchableSelectCheckboxes.js +1 -0
- package/dist/components/SearchableSelects/RASearchableSelectChips.js +1 -0
- package/dist/components/SearchableSelects/RASearchableSelectChipsRecordContext.js +1 -0
- package/dist/components/SearchableSelects/SearchableSelect.js +1 -0
- package/dist/components/SearchableSelects/SearchableSelectCheckboxes.js +1 -0
- package/dist/components/SortableDataGrid/README.md +1 -0
- package/dist/components/SortableDataGrid/SortableDataGrid.js +1 -0
- package/dist/components/SortableDataGrid/SortableDataGridBody.js +1 -0
- package/dist/components/SortableDataGrid/SortableDataGridHeader.js +1 -0
- package/dist/components/SortableDataGrid/SortableDataGridRow.js +1 -0
- package/dist/components/Upload/SingleImageUploader.js +1 -0
- package/dist/components/custom/PackageCopyMethod.js +1 -0
- package/dist/components/custom/PackageRadioButtonGroup.js +1 -0
- package/dist/components/dialog/Basic.js +1 -0
- package/dist/components/dialog/Correct.js +1 -0
- package/dist/components/dialog/Error.js +1 -0
- package/dist/components/dialog/Info.js +1 -0
- package/dist/components/dialog/InfoV2.js +1 -0
- package/dist/components/dialog/NotFound.js +1 -0
- package/dist/components/dialog/QuickSearch.js +1 -0
- package/dist/components/notifications/NotificationsCenter.js +1 -0
- package/dist/components/notifications/index.js +1 -0
- package/dist/components/tables/PaginationComponent.js +1 -0
- package/dist/components/theme/AppLoadingFallback.js +6 -0
- package/dist/components/theme/ErrorBoundary.js +1 -0
- package/dist/components/theme/InitialLoader.js +1 -0
- package/dist/components/theme/NotFound.js +1 -0
- package/dist/components/theme/NotResults.js +1 -0
- package/dist/hooks/useDraggable.js +1 -0
- package/dist/hooks/useNotifications.js +1 -0
- package/dist/hooks/useQuickSearch.js +1 -0
- package/dist/styles/index.less +0 -0
- package/dist/utils/setNativeValue.js +1 -0
- 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;
|