@visns-studio/visns-components 5.11.4 → 5.11.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1213 -11
- package/package.json +5 -5
- package/src/components/crm/AssociationManager.jsx +151 -33
- package/src/components/crm/Field.jsx +115 -60
- package/src/components/crm/columns/ColumnRenderers.jsx +19 -0
- package/src/components/crm/generic/GenericDetail.jsx +40 -0
- package/src/components/crm/generic/GenericEditableTable.jsx +3 -3
- package/src/components/crm/generic/GenericQuote.jsx +353 -1
- package/src/components/crm/generic/styles/GenericEditableTable.module.scss +102 -22
- package/src/components/crm/generic/styles/GenericQuote.module.scss +203 -0
- package/src/components/proposal/ProposalTemplatePreview.css +364 -0
- package/src/components/proposal/ProposalTemplatePreview.jsx +331 -0
- package/src/components/proposal/ProposalTemplateSectionManager.css +321 -0
- package/src/components/proposal/ProposalTemplateSectionManager.jsx +473 -0
- package/src/components/proposal/SectionEditor.css +508 -0
- package/src/components/proposal/SectionEditor.jsx +412 -0
- package/src/components/proposal/SectionTypeSelector.css +277 -0
- package/src/components/proposal/SectionTypeSelector.jsx +158 -0
- package/src/components/proposal/VariableInserter.css +392 -0
- package/src/components/proposal/VariableInserter.jsx +260 -0
- package/src/components/proposal/index.js +5 -0
- package/src/components/styles/global.css +179 -1
- package/src/index.js +13 -0
- package/src/utils/urlBuilder.js +40 -0
package/package.json
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"@nivo/pie": "^0.99.0",
|
|
14
14
|
"@tinymce/miniature": "^6.0.0",
|
|
15
15
|
"@tinymce/tinymce-react": "^6.2.1",
|
|
16
|
+
"@uiw/react-color": "^2.6.0",
|
|
16
17
|
"@visns-studio/visns-datagrid-community": "^1.0.14",
|
|
17
18
|
"@visns-studio/visns-datagrid-enterprise": "^1.0.14",
|
|
18
19
|
"@vitejs/plugin-react": "^4.5.2",
|
|
@@ -27,15 +28,14 @@
|
|
|
27
28
|
"html-react-parser": "^5.2.5",
|
|
28
29
|
"lodash": "^4.17.21",
|
|
29
30
|
"lodash.debounce": "^4.0.8",
|
|
30
|
-
"lucide-react": "^0.
|
|
31
|
+
"lucide-react": "^0.518.0",
|
|
31
32
|
"moment": "^2.30.1",
|
|
32
33
|
"motion": "^12.18.1",
|
|
33
34
|
"numeral": "^2.0.6",
|
|
34
35
|
"pluralize": "^8.0.0",
|
|
35
36
|
"qrcode.react": "^4.2.0",
|
|
36
37
|
"quill-image-uploader": "^1.3.0",
|
|
37
|
-
"react-big-calendar": "^1.19.
|
|
38
|
-
"react-color": "^2.19.3",
|
|
38
|
+
"react-big-calendar": "^1.19.4",
|
|
39
39
|
"react-copy-to-clipboard": "^5.1.0",
|
|
40
40
|
"react-datepicker": "^8.4.0",
|
|
41
41
|
"react-dropzone": "^14.3.8",
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
"react-dom": "^17.0.0 || ^18.0.0"
|
|
88
88
|
},
|
|
89
89
|
"name": "@visns-studio/visns-components",
|
|
90
|
-
"version": "5.11.
|
|
90
|
+
"version": "5.11.6",
|
|
91
91
|
"description": "Various packages to assist in the development of our Custom Applications.",
|
|
92
92
|
"main": "src/index.js",
|
|
93
93
|
"files": [
|
|
@@ -108,5 +108,5 @@
|
|
|
108
108
|
"url": "https://github.com/visnsstudio/visns-components/issues"
|
|
109
109
|
},
|
|
110
110
|
"homepage": "https://github.com/visnsstudio/visns-components#readme",
|
|
111
|
-
"packageManager": "yarn@4.9.
|
|
111
|
+
"packageManager": "yarn@4.9.2"
|
|
112
112
|
}
|
|
@@ -13,6 +13,7 @@ const AssociationManager = ({
|
|
|
13
13
|
}) => {
|
|
14
14
|
const [associations, setAssociations] = useState([]);
|
|
15
15
|
const [availableOptions, setAvailableOptions] = useState([]);
|
|
16
|
+
const [childOptions, setChildOptions] = useState({});
|
|
16
17
|
const [loading, setLoading] = useState(true);
|
|
17
18
|
|
|
18
19
|
// Dynamic state based on config
|
|
@@ -49,23 +50,42 @@ const AssociationManager = ({
|
|
|
49
50
|
|
|
50
51
|
// Load existing associations
|
|
51
52
|
const loadAssociations = async () => {
|
|
52
|
-
|
|
53
|
+
console.log('🔍 AssociationManager - loadAssociations called');
|
|
54
|
+
console.log('📡 Endpoints:', endpoints);
|
|
55
|
+
console.log('⚙️ Config:', config);
|
|
56
|
+
console.log('🆔 DataId:', dataId);
|
|
57
|
+
|
|
58
|
+
if (!endpoints.list) {
|
|
59
|
+
console.error('❌ No endpoints.list defined');
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
53
62
|
|
|
54
63
|
try {
|
|
55
64
|
setLoading(true);
|
|
56
65
|
const url = processTemplate(endpoints.list);
|
|
66
|
+
console.log('🌐 Loading associations from URL:', url);
|
|
67
|
+
|
|
57
68
|
const method = endpoints.listMethod || 'GET';
|
|
69
|
+
console.log('📤 HTTP Method:', method);
|
|
70
|
+
|
|
58
71
|
const response = await CustomFetch(url, method, endpoints.listParams || {});
|
|
72
|
+
console.log('📥 Raw API Response:', response);
|
|
59
73
|
|
|
60
74
|
const dataPath = config.dataPath || 'data';
|
|
75
|
+
console.log('🗂️ Using dataPath:', dataPath);
|
|
76
|
+
|
|
61
77
|
const associationsData = dataPath.split('.').reduce((obj, key) => obj?.[key], response);
|
|
78
|
+
console.log('📊 Extracted associations data:', associationsData);
|
|
62
79
|
|
|
63
80
|
// Ensure associationsData is always an array
|
|
64
81
|
const validAssociations = Array.isArray(associationsData) ? associationsData : [];
|
|
82
|
+
console.log('✅ Valid associations (final):', validAssociations);
|
|
83
|
+
console.log('📈 Associations count:', validAssociations.length);
|
|
84
|
+
|
|
65
85
|
setAssociations(validAssociations);
|
|
66
86
|
} catch (error) {
|
|
67
87
|
toast.error(config.messages?.loadError || 'Failed to load associations');
|
|
68
|
-
console.error('Error loading associations:', error);
|
|
88
|
+
console.error('❌ Error loading associations:', error);
|
|
69
89
|
setAssociations([]); // Set empty array on error
|
|
70
90
|
} finally {
|
|
71
91
|
setLoading(false);
|
|
@@ -74,20 +94,102 @@ const AssociationManager = ({
|
|
|
74
94
|
|
|
75
95
|
// Load available options for dropdown/select fields
|
|
76
96
|
const loadAvailableOptions = async () => {
|
|
77
|
-
|
|
97
|
+
console.log('🔍 AssociationManager - loadAvailableOptions called');
|
|
98
|
+
console.log('⚙️ Config.optionsEndpoint:', config.optionsEndpoint);
|
|
99
|
+
|
|
100
|
+
if (!config.optionsEndpoint) {
|
|
101
|
+
console.error('❌ No config.optionsEndpoint defined');
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
78
104
|
|
|
79
105
|
try {
|
|
80
106
|
const url = processTemplate(config.optionsEndpoint.url);
|
|
107
|
+
console.log('🌐 Loading options from URL:', url);
|
|
108
|
+
|
|
81
109
|
const method = config.optionsEndpoint.method || 'POST';
|
|
110
|
+
console.log('📤 HTTP Method:', method);
|
|
111
|
+
|
|
82
112
|
const params = config.optionsEndpoint.params || {};
|
|
113
|
+
console.log('📋 Request params:', params);
|
|
83
114
|
|
|
84
115
|
const response = await CustomFetch(url, method, params);
|
|
116
|
+
console.log('📥 Raw options API Response:', response);
|
|
117
|
+
|
|
85
118
|
const dataPath = config.optionsEndpoint.dataPath || 'data';
|
|
119
|
+
console.log('🗂️ Using options dataPath:', dataPath);
|
|
120
|
+
|
|
86
121
|
const optionsData = dataPath.split('.').reduce((obj, key) => obj?.[key], response) || [];
|
|
122
|
+
console.log('📊 Extracted options data:', optionsData);
|
|
123
|
+
console.log('📈 Options count:', optionsData.length);
|
|
87
124
|
|
|
88
125
|
setAvailableOptions(optionsData);
|
|
89
126
|
} catch (error) {
|
|
90
|
-
console.error('Failed to load options:', error);
|
|
127
|
+
console.error('❌ Failed to load options:', error);
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
// Load child dropdown options based on parent selection
|
|
132
|
+
const loadChildOptions = async (parentFieldId, parentValue, childConfig) => {
|
|
133
|
+
if (!parentValue || !childConfig.url) return [];
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
console.log(`🔍 Loading child options for ${childConfig.id} based on ${parentFieldId}:`, parentValue);
|
|
137
|
+
|
|
138
|
+
const filter = {
|
|
139
|
+
where: [
|
|
140
|
+
{
|
|
141
|
+
id: parentFieldId,
|
|
142
|
+
value: parentValue,
|
|
143
|
+
...(childConfig.whereHas && { whereHas: childConfig.whereHas })
|
|
144
|
+
}
|
|
145
|
+
]
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
if (childConfig.fields) {
|
|
149
|
+
filter.fields = childConfig.fields;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
console.log('📋 Child filter params:', filter);
|
|
153
|
+
|
|
154
|
+
const response = await CustomFetch(childConfig.url, 'POST', filter);
|
|
155
|
+
console.log('📥 Child options response:', response);
|
|
156
|
+
|
|
157
|
+
// Extract child options - response structure is {data: {data: [...]}}
|
|
158
|
+
const childData = response.data?.data || response.data || [];
|
|
159
|
+
console.log('📊 Extracted child options:', childData);
|
|
160
|
+
|
|
161
|
+
return childData;
|
|
162
|
+
} catch (error) {
|
|
163
|
+
console.error(`❌ Failed to load child options for ${childConfig.id}:`, error);
|
|
164
|
+
return [];
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
// Handle parent field changes and load child options
|
|
169
|
+
const handleFieldChange = async (fieldId, value, field) => {
|
|
170
|
+
// Update the field value
|
|
171
|
+
setNewAssociation(prev => ({ ...prev, [fieldId]: value }));
|
|
172
|
+
|
|
173
|
+
// Load child options if this field has children
|
|
174
|
+
if (field.child && Array.isArray(field.child)) {
|
|
175
|
+
console.log(`🔗 Parent field ${fieldId} changed, loading child options...`);
|
|
176
|
+
|
|
177
|
+
const newChildOptions = { ...childOptions };
|
|
178
|
+
|
|
179
|
+
for (const childConfig of field.child) {
|
|
180
|
+
const childFieldOptions = await loadChildOptions(fieldId, value?.value || value, childConfig);
|
|
181
|
+
newChildOptions[childConfig.id] = childFieldOptions;
|
|
182
|
+
console.log(`✅ Loaded ${childFieldOptions.length} options for ${childConfig.id}`);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
setChildOptions(newChildOptions);
|
|
186
|
+
|
|
187
|
+
// Clear child field values when parent changes
|
|
188
|
+
const updatedAssociation = { ...newAssociation, [fieldId]: value };
|
|
189
|
+
field.child.forEach(childConfig => {
|
|
190
|
+
updatedAssociation[childConfig.id] = '';
|
|
191
|
+
});
|
|
192
|
+
setNewAssociation(updatedAssociation);
|
|
91
193
|
}
|
|
92
194
|
};
|
|
93
195
|
|
|
@@ -164,21 +266,29 @@ const AssociationManager = ({
|
|
|
164
266
|
};
|
|
165
267
|
|
|
166
268
|
// Set primary handler
|
|
167
|
-
const handleSetPrimary = async (
|
|
269
|
+
const handleSetPrimary = async (association) => {
|
|
168
270
|
if (!endpoints.setPrimary) return;
|
|
169
271
|
|
|
170
272
|
const idField = config.primaryTargetField || config.deleteField || 'id';
|
|
273
|
+
const targetValue = association[idField] || association.customer_id || association.id;
|
|
171
274
|
|
|
172
|
-
|
|
275
|
+
console.log('🌟 Setting primary for association:', association);
|
|
276
|
+
console.log('🎯 Using field:', idField, 'with value:', targetValue);
|
|
277
|
+
|
|
278
|
+
await handleAction('setPrimary', { [idField]: targetValue });
|
|
173
279
|
};
|
|
174
280
|
|
|
175
281
|
// Remove primary handler
|
|
176
|
-
const handleRemovePrimary = async (
|
|
282
|
+
const handleRemovePrimary = async (association) => {
|
|
177
283
|
if (!endpoints.removePrimary) return;
|
|
178
284
|
|
|
179
285
|
const idField = config.primaryTargetField || config.deleteField || 'id';
|
|
286
|
+
const targetValue = association[idField] || association.customer_id || association.id;
|
|
287
|
+
|
|
288
|
+
console.log('🌟 Removing primary for association:', association);
|
|
289
|
+
console.log('🎯 Using field:', idField, 'with value:', targetValue);
|
|
180
290
|
|
|
181
|
-
await handleAction('removePrimary', { [idField]:
|
|
291
|
+
await handleAction('removePrimary', { [idField]: targetValue });
|
|
182
292
|
};
|
|
183
293
|
|
|
184
294
|
// Clear all primary statuses (useful after merge)
|
|
@@ -195,9 +305,17 @@ const AssociationManager = ({
|
|
|
195
305
|
};
|
|
196
306
|
|
|
197
307
|
useEffect(() => {
|
|
308
|
+
console.log('🚀 AssociationManager - Component initialized');
|
|
309
|
+
console.log('🆔 DataId:', dataId);
|
|
310
|
+
console.log('📡 Endpoints:', endpoints);
|
|
311
|
+
console.log('⚙️ Config:', config);
|
|
312
|
+
|
|
198
313
|
if (dataId) {
|
|
314
|
+
console.log('✅ DataId exists, loading data...');
|
|
199
315
|
loadAssociations();
|
|
200
316
|
loadAvailableOptions();
|
|
317
|
+
} else {
|
|
318
|
+
console.warn('⚠️ No dataId provided, skipping data load');
|
|
201
319
|
}
|
|
202
320
|
}, [dataId]);
|
|
203
321
|
|
|
@@ -245,21 +363,31 @@ const AssociationManager = ({
|
|
|
245
363
|
) : (
|
|
246
364
|
<div className={styles.associationsList}>
|
|
247
365
|
{associations.map((association) => {
|
|
366
|
+
console.log('🔍 Rendering association:', association);
|
|
367
|
+
|
|
248
368
|
const nameField = config.displayFields?.name || 'name';
|
|
249
369
|
const primaryField = config.displayFields?.primary || 'is_primary';
|
|
250
370
|
const relationshipField = config.displayFields?.relationship || 'relationship_type';
|
|
251
371
|
const notesField = config.displayFields?.notes || 'notes';
|
|
252
372
|
const dateField = config.displayFields?.date || 'associated_at';
|
|
253
373
|
|
|
374
|
+
console.log('📊 Field values:', {
|
|
375
|
+
name: association[nameField],
|
|
376
|
+
primary: association[primaryField],
|
|
377
|
+
relationship: association[relationshipField],
|
|
378
|
+
notes: association[notesField],
|
|
379
|
+
date: association[dateField]
|
|
380
|
+
});
|
|
381
|
+
|
|
254
382
|
return (
|
|
255
383
|
<div
|
|
256
384
|
key={association.id}
|
|
257
|
-
className={`${styles.associationItem} ${association[primaryField] ? styles.primary : ''}`}
|
|
385
|
+
className={`${styles.associationItem} ${Boolean(association[primaryField]) ? styles.primary : ''}`}
|
|
258
386
|
>
|
|
259
387
|
<div className={styles.associationInfo}>
|
|
260
388
|
<div className={styles.entityName}>
|
|
261
389
|
{association[nameField]}
|
|
262
|
-
{association[primaryField] && (
|
|
390
|
+
{Boolean(association[primaryField]) && (
|
|
263
391
|
<span className={styles.primaryBadge}>
|
|
264
392
|
<Star size={12} style={{ marginRight: '4px' }} />
|
|
265
393
|
{config.labels?.primaryBadge || 'Primary'}
|
|
@@ -288,21 +416,22 @@ const AssociationManager = ({
|
|
|
288
416
|
)}
|
|
289
417
|
</div>
|
|
290
418
|
<div className={styles.associationActions}>
|
|
291
|
-
{
|
|
419
|
+
{console.log('🎯 Actions for:', association[nameField], 'Primary:', association[primaryField], 'Boolean:', Boolean(association[primaryField]))}
|
|
420
|
+
{!Boolean(association[primaryField]) && endpoints.setPrimary && (
|
|
292
421
|
<button
|
|
293
422
|
type="button"
|
|
294
423
|
className={styles.setPrimaryBtn}
|
|
295
|
-
onClick={() => handleSetPrimary(association
|
|
424
|
+
onClick={() => handleSetPrimary(association)}
|
|
296
425
|
>
|
|
297
426
|
<Star size={14} style={{ marginRight: '4px' }} />
|
|
298
427
|
{config.labels?.setPrimary || 'Set Primary'}
|
|
299
428
|
</button>
|
|
300
429
|
)}
|
|
301
|
-
{association[primaryField] && endpoints.removePrimary && (
|
|
430
|
+
{Boolean(association[primaryField]) && endpoints.removePrimary && (
|
|
302
431
|
<button
|
|
303
432
|
type="button"
|
|
304
433
|
className={styles.removePrimaryBtn}
|
|
305
|
-
onClick={() => handleRemovePrimary(association
|
|
434
|
+
onClick={() => handleRemovePrimary(association)}
|
|
306
435
|
title={config.labels?.removePrimaryTooltip || 'Remove primary status from this association'}
|
|
307
436
|
>
|
|
308
437
|
<X size={14} style={{ marginRight: '4px' }} />
|
|
@@ -343,9 +472,9 @@ const AssociationManager = ({
|
|
|
343
472
|
<MultiSelect
|
|
344
473
|
settings={{ id: field.id }}
|
|
345
474
|
multi={field.multi || false}
|
|
346
|
-
onChange={(value) =>
|
|
475
|
+
onChange={(value) => handleFieldChange(field.id, value, field)}
|
|
347
476
|
inputValue={fieldValue}
|
|
348
|
-
options={availableOptions}
|
|
477
|
+
options={field.id === 'customer_id' ? availableOptions : (childOptions[field.id] || [])}
|
|
349
478
|
placeholder={field.placeholder || `Select ${field.label}...`}
|
|
350
479
|
/>
|
|
351
480
|
)}
|
|
@@ -353,13 +482,11 @@ const AssociationManager = ({
|
|
|
353
482
|
{field.type === 'select' && (
|
|
354
483
|
<select
|
|
355
484
|
value={fieldValue}
|
|
356
|
-
onChange={(e) =>
|
|
357
|
-
...prev,
|
|
358
|
-
[field.id]: e.target.value
|
|
359
|
-
}))}
|
|
485
|
+
onChange={(e) => handleFieldChange(field.id, e.target.value, field)}
|
|
360
486
|
className={styles.selectField}
|
|
361
487
|
>
|
|
362
|
-
|
|
488
|
+
<option value="">Select {field.label}...</option>
|
|
489
|
+
{(field.options || childOptions[field.id] || []).map(option => (
|
|
363
490
|
<option key={option.value || option} value={option.value || option}>
|
|
364
491
|
{option.label || option}
|
|
365
492
|
</option>
|
|
@@ -372,10 +499,7 @@ const AssociationManager = ({
|
|
|
372
499
|
<input
|
|
373
500
|
type="checkbox"
|
|
374
501
|
checked={fieldValue}
|
|
375
|
-
onChange={(e) =>
|
|
376
|
-
...prev,
|
|
377
|
-
[field.id]: e.target.checked
|
|
378
|
-
}))}
|
|
502
|
+
onChange={(e) => handleFieldChange(field.id, e.target.checked, field)}
|
|
379
503
|
/>
|
|
380
504
|
{field.checkboxLabel || field.label}
|
|
381
505
|
</label>
|
|
@@ -384,10 +508,7 @@ const AssociationManager = ({
|
|
|
384
508
|
{field.type === 'textarea' && (
|
|
385
509
|
<textarea
|
|
386
510
|
value={fieldValue}
|
|
387
|
-
onChange={(e) =>
|
|
388
|
-
...prev,
|
|
389
|
-
[field.id]: e.target.value
|
|
390
|
-
}))}
|
|
511
|
+
onChange={(e) => handleFieldChange(field.id, e.target.value, field)}
|
|
391
512
|
placeholder={field.placeholder || ''}
|
|
392
513
|
className={styles.textareaField}
|
|
393
514
|
rows={field.rows || 3}
|
|
@@ -398,10 +519,7 @@ const AssociationManager = ({
|
|
|
398
519
|
<input
|
|
399
520
|
type="text"
|
|
400
521
|
value={fieldValue}
|
|
401
|
-
onChange={(e) =>
|
|
402
|
-
...prev,
|
|
403
|
-
[field.id]: e.target.value
|
|
404
|
-
}))}
|
|
522
|
+
onChange={(e) => handleFieldChange(field.id, e.target.value, field)}
|
|
405
523
|
placeholder={field.placeholder || ''}
|
|
406
524
|
className={styles.textField}
|
|
407
525
|
/>
|
|
@@ -7,7 +7,7 @@ import Toggle from 'react-toggle';
|
|
|
7
7
|
import moment from 'moment';
|
|
8
8
|
import parse from 'html-react-parser';
|
|
9
9
|
import Popup from 'reactjs-popup';
|
|
10
|
-
import {
|
|
10
|
+
import { Sketch } from '@uiw/react-color';
|
|
11
11
|
import { CopyToClipboard } from 'react-copy-to-clipboard';
|
|
12
12
|
import { NumericFormat, PatternFormat } from 'react-number-format';
|
|
13
13
|
import { Editor } from '@tinymce/tinymce-react';
|
|
@@ -110,12 +110,13 @@ function Field({
|
|
|
110
110
|
right: '0px',
|
|
111
111
|
bottom: '0px',
|
|
112
112
|
left: '0px',
|
|
113
|
+
zIndex: '999998',
|
|
113
114
|
};
|
|
114
115
|
const popover = {
|
|
115
116
|
position: 'absolute',
|
|
116
|
-
zIndex: '
|
|
117
|
-
left: '
|
|
118
|
-
marginTop: '
|
|
117
|
+
zIndex: '999999',
|
|
118
|
+
left: '0',
|
|
119
|
+
marginTop: '8px',
|
|
119
120
|
};
|
|
120
121
|
|
|
121
122
|
useEffect(() => {
|
|
@@ -157,6 +158,33 @@ function Field({
|
|
|
157
158
|
fetchAutoValue();
|
|
158
159
|
}, [settings.url, settings.type, settings.id, settings.autoValueKey]);
|
|
159
160
|
|
|
161
|
+
// Handle profileId parameter - auto-populate field value with userProfile.id
|
|
162
|
+
useEffect(() => {
|
|
163
|
+
if (
|
|
164
|
+
settings.param === 'profileId' &&
|
|
165
|
+
(!settings.value || settings.value === '') &&
|
|
166
|
+
userProfile?.id &&
|
|
167
|
+
(!inputValue || inputValue === '')
|
|
168
|
+
) {
|
|
169
|
+
// Simulate an onChange event to update the form data with userProfile.id
|
|
170
|
+
const simulatedEvent = {
|
|
171
|
+
target: {
|
|
172
|
+
dataset: { name: settings.id },
|
|
173
|
+
value: userProfile.id,
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
onChange(simulatedEvent);
|
|
177
|
+
|
|
178
|
+
// Also directly update the form data to ensure it's synchronized
|
|
179
|
+
if (setFormData) {
|
|
180
|
+
setFormData((prevState) => ({
|
|
181
|
+
...prevState,
|
|
182
|
+
[settings.id]: userProfile.id,
|
|
183
|
+
}));
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}, [settings.param, settings.value, settings.id, userProfile?.id, inputValue, onChange, setFormData]);
|
|
187
|
+
|
|
160
188
|
useEffect(() => {
|
|
161
189
|
const fetchOptions = () => {
|
|
162
190
|
let filter = {};
|
|
@@ -429,6 +457,12 @@ function Field({
|
|
|
429
457
|
}
|
|
430
458
|
};
|
|
431
459
|
|
|
460
|
+
// Utility function to detect if a string is a valid hex color
|
|
461
|
+
const isHexColor = (str) => {
|
|
462
|
+
if (!str || typeof str !== 'string') return false;
|
|
463
|
+
return /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/.test(str);
|
|
464
|
+
};
|
|
465
|
+
|
|
432
466
|
const renderRelationText = (value) => {
|
|
433
467
|
let text = '';
|
|
434
468
|
|
|
@@ -446,23 +480,56 @@ function Field({
|
|
|
446
480
|
text = processKeys(value);
|
|
447
481
|
}
|
|
448
482
|
|
|
483
|
+
// If the text is a hex color, render it with a color preview
|
|
484
|
+
if (isHexColor(text)) {
|
|
485
|
+
return (
|
|
486
|
+
<div className="relation-color-container">
|
|
487
|
+
<div
|
|
488
|
+
className="relation-color-preview"
|
|
489
|
+
style={{ backgroundColor: text }}
|
|
490
|
+
title={`Color: ${text}`}
|
|
491
|
+
></div>
|
|
492
|
+
<span className="relation-color-text">{text}</span>
|
|
493
|
+
</div>
|
|
494
|
+
);
|
|
495
|
+
}
|
|
496
|
+
|
|
449
497
|
return text;
|
|
450
498
|
};
|
|
451
499
|
|
|
452
500
|
const renderRelationArrayText = (value) => {
|
|
453
|
-
let text = '';
|
|
454
|
-
|
|
455
501
|
if (value && settings.nameFrom) {
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
502
|
+
return (
|
|
503
|
+
<div className="relation-array-container">
|
|
504
|
+
{value.map((item, index) => {
|
|
505
|
+
const itemValue = item[settings.nameFrom];
|
|
506
|
+
|
|
507
|
+
if (isHexColor(itemValue)) {
|
|
508
|
+
return (
|
|
509
|
+
<div key={index} className="relation-color-container inline">
|
|
510
|
+
<div
|
|
511
|
+
className="relation-color-preview"
|
|
512
|
+
style={{ backgroundColor: itemValue }}
|
|
513
|
+
title={`Color: ${itemValue}`}
|
|
514
|
+
></div>
|
|
515
|
+
<span className="relation-color-text">{itemValue}</span>
|
|
516
|
+
{index < value.length - 1 && <span className="separator">, </span>}
|
|
517
|
+
</div>
|
|
518
|
+
);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
return (
|
|
522
|
+
<span key={index}>
|
|
523
|
+
{itemValue}
|
|
524
|
+
{index < value.length - 1 && ', '}
|
|
525
|
+
</span>
|
|
526
|
+
);
|
|
527
|
+
})}
|
|
528
|
+
</div>
|
|
529
|
+
);
|
|
463
530
|
}
|
|
464
531
|
|
|
465
|
-
return
|
|
532
|
+
return '';
|
|
466
533
|
};
|
|
467
534
|
|
|
468
535
|
const renderInput = () => {
|
|
@@ -597,52 +664,41 @@ function Field({
|
|
|
597
664
|
);
|
|
598
665
|
case 'colour':
|
|
599
666
|
return (
|
|
600
|
-
<div className="cpicker">
|
|
601
|
-
<div
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
inputValue && inputValue !== 'null'
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
667
|
+
<div className="cpicker-inline">
|
|
668
|
+
<div className="cpicker-preview-container">
|
|
669
|
+
<div
|
|
670
|
+
className="cpicker__box"
|
|
671
|
+
data-color={inputValue && inputValue !== 'null' ? inputValue : 'No colour selected'}
|
|
672
|
+
style={
|
|
673
|
+
inputValue && inputValue !== 'null'
|
|
674
|
+
? { backgroundColor: inputValue }
|
|
675
|
+
: {}
|
|
676
|
+
}
|
|
677
|
+
title={inputValue && inputValue !== 'null' ? `Current colour: ${inputValue}` : 'No colour selected'}
|
|
678
|
+
></div>
|
|
679
|
+
<span className="cpicker-value">
|
|
680
|
+
{inputValue && inputValue !== 'null' ? inputValue : 'No colour selected'}
|
|
681
|
+
</span>
|
|
682
|
+
</div>
|
|
683
|
+
<div className="cpicker-container">
|
|
684
|
+
<Sketch
|
|
685
|
+
color={
|
|
686
|
+
inputValue && inputValue !== 'null'
|
|
687
|
+
? inputValue
|
|
688
|
+
: '#000000'
|
|
689
|
+
}
|
|
690
|
+
onChange={(color) => {
|
|
691
|
+
onChangeColour(
|
|
692
|
+
{ target: { value: color.hex } },
|
|
693
|
+
{ hex: color.hex },
|
|
694
|
+
settings.id
|
|
616
695
|
);
|
|
617
696
|
}}
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
<div
|
|
624
|
-
style={cover}
|
|
625
|
-
onClick={() => {
|
|
626
|
-
setColorPickerShow(false);
|
|
627
|
-
}}
|
|
628
|
-
/>
|
|
629
|
-
|
|
630
|
-
<BlockPicker
|
|
631
|
-
color={
|
|
632
|
-
inputValue && inputValue !== 'null'
|
|
633
|
-
? inputValue
|
|
634
|
-
: ''
|
|
635
|
-
}
|
|
636
|
-
onChangeComplete={(color, event) => {
|
|
637
|
-
onChangeColour(
|
|
638
|
-
event,
|
|
639
|
-
color,
|
|
640
|
-
settings.id
|
|
641
|
-
);
|
|
642
|
-
}}
|
|
643
|
-
/>
|
|
644
|
-
</div>
|
|
645
|
-
) : null}
|
|
697
|
+
style={{
|
|
698
|
+
boxShadow: 'none',
|
|
699
|
+
border: 'none'
|
|
700
|
+
}}
|
|
701
|
+
/>
|
|
646
702
|
</div>
|
|
647
703
|
</div>
|
|
648
704
|
);
|
|
@@ -1954,13 +2010,12 @@ function Field({
|
|
|
1954
2010
|
'media',
|
|
1955
2011
|
'table',
|
|
1956
2012
|
'code',
|
|
1957
|
-
'hr',
|
|
1958
2013
|
],
|
|
1959
2014
|
toolbar:
|
|
1960
2015
|
'insertTextDropdown | undo redo | blocks | ' +
|
|
1961
2016
|
'bold italic forecolor | alignleft aligncenter ' +
|
|
1962
2017
|
'alignright alignjustify | bullist numlist outdent indent | ' +
|
|
1963
|
-
'
|
|
2018
|
+
'removeformat | table | code',
|
|
1964
2019
|
content_style:
|
|
1965
2020
|
'body { font-family:Helvetica,Arial,sans-serif; font-size:14px }',
|
|
1966
2021
|
setup: (editor) => {
|
|
@@ -767,6 +767,25 @@ export const renderRelationColumn = ({
|
|
|
767
767
|
typeof value === 'string' ||
|
|
768
768
|
typeof value === 'number'
|
|
769
769
|
) {
|
|
770
|
+
// Check if the value is a hex color and render with color preview
|
|
771
|
+
const isHexColor = (str) => {
|
|
772
|
+
if (!str || typeof str !== 'string') return false;
|
|
773
|
+
return /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/.test(str);
|
|
774
|
+
};
|
|
775
|
+
|
|
776
|
+
if (isHexColor(value)) {
|
|
777
|
+
return (
|
|
778
|
+
<div className="relation-color-container">
|
|
779
|
+
<div
|
|
780
|
+
className="relation-color-preview"
|
|
781
|
+
style={{ backgroundColor: value }}
|
|
782
|
+
title={`Color: ${value}`}
|
|
783
|
+
></div>
|
|
784
|
+
<span className="relation-color-text">{value}</span>
|
|
785
|
+
</div>
|
|
786
|
+
);
|
|
787
|
+
}
|
|
788
|
+
|
|
770
789
|
return (
|
|
771
790
|
<CellWithTooltip
|
|
772
791
|
value={value}
|