@visns-studio/visns-components 5.16.0 → 5.16.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.
package/package.json
CHANGED
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
"react-dom": "^17.0.0 || ^18.0.0"
|
|
95
95
|
},
|
|
96
96
|
"name": "@visns-studio/visns-components",
|
|
97
|
-
"version": "5.16.
|
|
97
|
+
"version": "5.16.1",
|
|
98
98
|
"description": "Various packages to assist in the development of our Custom Applications.",
|
|
99
99
|
"main": "src/index.js",
|
|
100
100
|
"files": [
|
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
|
+
import { X as CircleX } from 'lucide-react';
|
|
3
|
+
import Select, { components } from 'react-select';
|
|
4
|
+
import styles from '../styles/GenericFormBuilder.module.scss';
|
|
5
|
+
|
|
6
|
+
// Field types that cannot produce a value we can compare against — disabled in the picker.
|
|
7
|
+
const NON_VALUE_TYPES = new Set([
|
|
8
|
+
'plaintext',
|
|
9
|
+
'plaintextheading',
|
|
10
|
+
'signature',
|
|
11
|
+
'canvas',
|
|
12
|
+
'file',
|
|
13
|
+
'image',
|
|
14
|
+
'videoUpload',
|
|
15
|
+
'table_text',
|
|
16
|
+
]);
|
|
17
|
+
|
|
18
|
+
// Field types whose `options` array should populate the Value dropdown.
|
|
19
|
+
const OPTION_VALUE_TYPES = new Set([
|
|
20
|
+
'checkbox',
|
|
21
|
+
'dropdown',
|
|
22
|
+
'radio',
|
|
23
|
+
'toggle',
|
|
24
|
+
'table_radio',
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
// Stored as a literal string so the existing PDF blade (pdf/form.blade.php:132) keeps working.
|
|
28
|
+
const FILLED_IN_SENTINEL = 'not null';
|
|
29
|
+
|
|
30
|
+
const operatorOf = (conditionalValue) =>
|
|
31
|
+
conditionalValue === FILLED_IN_SENTINEL ? 'is filled in' : 'is';
|
|
32
|
+
|
|
33
|
+
// Group fields by their preceding `type: 'section'` header. Fields that can't be
|
|
34
|
+
// used in a condition (non-value types, or fields that appear after the current
|
|
35
|
+
// one so their value isn't yet known) are omitted from the list entirely.
|
|
36
|
+
const buildGroupedOptions = (allFields, currentField, typeLabelFor) => {
|
|
37
|
+
const currentIndex = allFields.findIndex(
|
|
38
|
+
(f) => f.id === currentField.id
|
|
39
|
+
);
|
|
40
|
+
// Brand-new field not yet in the array → treat as "at the end" so every
|
|
41
|
+
// existing field qualifies as an earlier reference.
|
|
42
|
+
const currentPos = currentIndex === -1 ? allFields.length : currentIndex;
|
|
43
|
+
|
|
44
|
+
const groups = [];
|
|
45
|
+
let group = { label: 'General', options: [] };
|
|
46
|
+
groups.push(group);
|
|
47
|
+
|
|
48
|
+
allFields.forEach((f, idx) => {
|
|
49
|
+
if (!f || !f.id) return;
|
|
50
|
+
if (f.type === 'section') {
|
|
51
|
+
group = { label: f.label || 'Section', options: [] };
|
|
52
|
+
groups.push(group);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (f.id === currentField.id) return;
|
|
56
|
+
if (NON_VALUE_TYPES.has(f.type)) return;
|
|
57
|
+
if (idx >= currentPos) return;
|
|
58
|
+
|
|
59
|
+
group.options.push({
|
|
60
|
+
value: f.id,
|
|
61
|
+
label: f.label || '(untitled)',
|
|
62
|
+
type: f.type,
|
|
63
|
+
typeLabel: typeLabelFor(f.type),
|
|
64
|
+
sectionLabel: group.label,
|
|
65
|
+
hasOptions: OPTION_VALUE_TYPES.has(f.type),
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Within each group, surface option-bearing fields first; preserve document
|
|
70
|
+
// order inside each bucket via a stable sort index.
|
|
71
|
+
groups.forEach((g) => {
|
|
72
|
+
g.options = g.options
|
|
73
|
+
.map((o, i) => ({ ...o, __i: i }))
|
|
74
|
+
.sort((a, b) => {
|
|
75
|
+
if (a.hasOptions !== b.hasOptions)
|
|
76
|
+
return a.hasOptions ? -1 : 1;
|
|
77
|
+
return a.__i - b.__i;
|
|
78
|
+
})
|
|
79
|
+
.map(({ __i, ...rest }) => rest);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
return groups.filter((g) => g.options.length > 0);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// Custom Option renderer — shows the field label alongside a small type pill
|
|
86
|
+
// so duplicate labels stay distinguishable.
|
|
87
|
+
const FieldOption = (props) => {
|
|
88
|
+
const { data } = props;
|
|
89
|
+
return (
|
|
90
|
+
<components.Option {...props}>
|
|
91
|
+
<div
|
|
92
|
+
style={{
|
|
93
|
+
display: 'flex',
|
|
94
|
+
alignItems: 'center',
|
|
95
|
+
justifyContent: 'space-between',
|
|
96
|
+
gap: 8,
|
|
97
|
+
}}
|
|
98
|
+
>
|
|
99
|
+
<span
|
|
100
|
+
style={{
|
|
101
|
+
overflow: 'hidden',
|
|
102
|
+
textOverflow: 'ellipsis',
|
|
103
|
+
whiteSpace: 'nowrap',
|
|
104
|
+
}}
|
|
105
|
+
>
|
|
106
|
+
{data.label}
|
|
107
|
+
</span>
|
|
108
|
+
<span
|
|
109
|
+
style={{
|
|
110
|
+
fontSize: '0.72rem',
|
|
111
|
+
color: '#666',
|
|
112
|
+
background: 'rgba(0,0,0,0.06)',
|
|
113
|
+
borderRadius: 4,
|
|
114
|
+
padding: '1px 6px',
|
|
115
|
+
flexShrink: 0,
|
|
116
|
+
}}
|
|
117
|
+
>
|
|
118
|
+
{data.typeLabel}
|
|
119
|
+
</span>
|
|
120
|
+
</div>
|
|
121
|
+
</components.Option>
|
|
122
|
+
);
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
// Collapsed (selected) display carries the section breadcrumb so duplicate labels
|
|
126
|
+
// (e.g. many "Notes" fields) remain distinguishable after selection.
|
|
127
|
+
const FieldSingleValue = (props) => {
|
|
128
|
+
const { data } = props;
|
|
129
|
+
return (
|
|
130
|
+
<components.SingleValue {...props}>
|
|
131
|
+
{data.sectionLabel && data.sectionLabel !== 'General' ? (
|
|
132
|
+
<span style={{ color: '#888' }}>
|
|
133
|
+
{data.sectionLabel} ›{' '}
|
|
134
|
+
</span>
|
|
135
|
+
) : null}
|
|
136
|
+
{data.label}
|
|
137
|
+
{data.typeLabel ? (
|
|
138
|
+
<span style={{ color: '#888' }}> — {data.typeLabel}</span>
|
|
139
|
+
) : null}
|
|
140
|
+
</components.SingleValue>
|
|
141
|
+
);
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
function ConditionalDisplayEditor({
|
|
145
|
+
currentField,
|
|
146
|
+
allFields = [],
|
|
147
|
+
fieldTypes = [],
|
|
148
|
+
onChange,
|
|
149
|
+
}) {
|
|
150
|
+
const conditionalFieldId = currentField.conditional_field || '';
|
|
151
|
+
const conditionalValue = currentField.conditional_value || '';
|
|
152
|
+
const operator = operatorOf(conditionalValue);
|
|
153
|
+
|
|
154
|
+
const selectedField = allFields.find((f) => f.id === conditionalFieldId);
|
|
155
|
+
const referenceMissing = conditionalFieldId && !selectedField;
|
|
156
|
+
|
|
157
|
+
const typeLabelFor = (type) =>
|
|
158
|
+
fieldTypes.find((t) => t.value === type)?.label || type;
|
|
159
|
+
|
|
160
|
+
const groupedOptions = useMemo(
|
|
161
|
+
() => buildGroupedOptions(allFields, currentField, typeLabelFor),
|
|
162
|
+
// typeLabelFor is pure over fieldTypes; depend on its source array.
|
|
163
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
164
|
+
[allFields, currentField.id, fieldTypes]
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
const selectedOption = useMemo(() => {
|
|
168
|
+
if (!conditionalFieldId) return null;
|
|
169
|
+
for (const g of groupedOptions) {
|
|
170
|
+
const match = g.options.find((o) => o.value === conditionalFieldId);
|
|
171
|
+
if (match) return match;
|
|
172
|
+
}
|
|
173
|
+
return null;
|
|
174
|
+
}, [groupedOptions, conditionalFieldId]);
|
|
175
|
+
|
|
176
|
+
const targetHasOptions =
|
|
177
|
+
selectedField && OPTION_VALUE_TYPES.has(selectedField.type);
|
|
178
|
+
const targetOptions = targetHasOptions ? selectedField.options || [] : [];
|
|
179
|
+
const selectedValueOption = targetOptions.find(
|
|
180
|
+
(o) => o.id === conditionalValue
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
const emitChange = (patch) => {
|
|
184
|
+
if (onChange) onChange(patch);
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const handleFieldChange = (option) => {
|
|
188
|
+
emitChange({
|
|
189
|
+
conditional_field: option ? option.value : '',
|
|
190
|
+
conditional_value: '',
|
|
191
|
+
});
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const handleOperatorChange = (e) => {
|
|
195
|
+
const nextOperator = e.target.value;
|
|
196
|
+
if (nextOperator === 'is filled in') {
|
|
197
|
+
emitChange({ conditional_value: FILLED_IN_SENTINEL });
|
|
198
|
+
} else if (conditionalValue === FILLED_IN_SENTINEL) {
|
|
199
|
+
emitChange({ conditional_value: '' });
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const handleValueChange = (e) => {
|
|
204
|
+
emitChange({ conditional_value: e.target.value });
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const handleClear = (e) => {
|
|
208
|
+
e.preventDefault();
|
|
209
|
+
emitChange({ conditional_field: '', conditional_value: '' });
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const renderValueControl = () => {
|
|
213
|
+
if (operator === 'is filled in') return null;
|
|
214
|
+
if (!selectedField) return null;
|
|
215
|
+
|
|
216
|
+
if (targetHasOptions) {
|
|
217
|
+
return (
|
|
218
|
+
<select
|
|
219
|
+
name="conditional_value"
|
|
220
|
+
value={conditionalValue}
|
|
221
|
+
onChange={handleValueChange}
|
|
222
|
+
>
|
|
223
|
+
<option value="">Please select a value</option>
|
|
224
|
+
{targetOptions.map((opt, i) => (
|
|
225
|
+
<option
|
|
226
|
+
value={opt.id}
|
|
227
|
+
key={`cond-value-${opt.id}-${i}`}
|
|
228
|
+
>
|
|
229
|
+
{opt.label}
|
|
230
|
+
</option>
|
|
231
|
+
))}
|
|
232
|
+
</select>
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const inputType = selectedField.type === 'number' ? 'number' : 'text';
|
|
237
|
+
return (
|
|
238
|
+
<input
|
|
239
|
+
name="conditional_value"
|
|
240
|
+
value={conditionalValue}
|
|
241
|
+
type={inputType}
|
|
242
|
+
onChange={handleValueChange}
|
|
243
|
+
/>
|
|
244
|
+
);
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
const fieldBreadcrumb = selectedOption
|
|
248
|
+
? `${
|
|
249
|
+
selectedOption.sectionLabel &&
|
|
250
|
+
selectedOption.sectionLabel !== 'General'
|
|
251
|
+
? `${selectedOption.sectionLabel} › `
|
|
252
|
+
: ''
|
|
253
|
+
}${selectedOption.label}`
|
|
254
|
+
: selectedField
|
|
255
|
+
? selectedField.label
|
|
256
|
+
: '…';
|
|
257
|
+
|
|
258
|
+
const renderPreview = () => {
|
|
259
|
+
if (!conditionalFieldId && !conditionalValue) {
|
|
260
|
+
return (
|
|
261
|
+
<span>
|
|
262
|
+
No condition set — this field will always be shown.
|
|
263
|
+
</span>
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (referenceMissing) return null;
|
|
268
|
+
|
|
269
|
+
if (operator === 'is filled in') {
|
|
270
|
+
return (
|
|
271
|
+
<span>
|
|
272
|
+
This field will only show when{' '}
|
|
273
|
+
<strong>{fieldBreadcrumb}</strong> is filled in.
|
|
274
|
+
</span>
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (!conditionalValue) {
|
|
279
|
+
return (
|
|
280
|
+
<span>
|
|
281
|
+
Pick a value for <strong>{fieldBreadcrumb}</strong> to
|
|
282
|
+
finish the rule.
|
|
283
|
+
</span>
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const valueLabel = targetHasOptions
|
|
288
|
+
? selectedValueOption?.label || conditionalValue
|
|
289
|
+
: conditionalValue;
|
|
290
|
+
|
|
291
|
+
return (
|
|
292
|
+
<span>
|
|
293
|
+
This field will only show when{' '}
|
|
294
|
+
<strong>{fieldBreadcrumb}</strong> is{' '}
|
|
295
|
+
<strong>{valueLabel}</strong>.
|
|
296
|
+
</span>
|
|
297
|
+
);
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
const hasAnyRule = !!(conditionalFieldId || conditionalValue);
|
|
301
|
+
|
|
302
|
+
const reactSelectStyles = {
|
|
303
|
+
menu: (provided) => ({ ...provided, zIndex: 99999 }),
|
|
304
|
+
menuPortal: (base) => ({ ...base, zIndex: 99999 }),
|
|
305
|
+
control: (base) => ({
|
|
306
|
+
...base,
|
|
307
|
+
minHeight: 44,
|
|
308
|
+
borderRadius: 6,
|
|
309
|
+
}),
|
|
310
|
+
groupHeading: (base) => ({
|
|
311
|
+
...base,
|
|
312
|
+
fontSize: '0.7rem',
|
|
313
|
+
textTransform: 'uppercase',
|
|
314
|
+
letterSpacing: '0.04em',
|
|
315
|
+
color: 'var(--primary-color)',
|
|
316
|
+
fontWeight: 700,
|
|
317
|
+
padding: '6px 12px',
|
|
318
|
+
background: 'rgba(var(--primary-rgb), 0.05)',
|
|
319
|
+
}),
|
|
320
|
+
option: (base, state) => ({
|
|
321
|
+
...base,
|
|
322
|
+
cursor: state.isDisabled ? 'not-allowed' : 'pointer',
|
|
323
|
+
}),
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
return (
|
|
327
|
+
<>
|
|
328
|
+
<div className={`${styles.formItem} ${styles.fwItem}`}>
|
|
329
|
+
<div className={styles.gridtxt__header}>
|
|
330
|
+
<span>Conditional Field Display Criteria</span>
|
|
331
|
+
</div>
|
|
332
|
+
</div>
|
|
333
|
+
|
|
334
|
+
{referenceMissing ? (
|
|
335
|
+
<div
|
|
336
|
+
className={`${styles.formItem} ${styles.fwItem}`}
|
|
337
|
+
style={{
|
|
338
|
+
background: '#fff8e1',
|
|
339
|
+
border: '1px solid #f4c544',
|
|
340
|
+
borderRadius: 6,
|
|
341
|
+
padding: '8px 12px',
|
|
342
|
+
color: '#7a5c00',
|
|
343
|
+
fontSize: '0.85rem',
|
|
344
|
+
}}
|
|
345
|
+
>
|
|
346
|
+
The field this rule references no longer exists. Pick
|
|
347
|
+
another field or clear the rule.
|
|
348
|
+
</div>
|
|
349
|
+
) : null}
|
|
350
|
+
|
|
351
|
+
<div className={styles.formItem}>
|
|
352
|
+
<label className={styles.fi__label}>
|
|
353
|
+
<Select
|
|
354
|
+
inputId="conditional_field"
|
|
355
|
+
classNamePrefix="visns-select"
|
|
356
|
+
options={groupedOptions}
|
|
357
|
+
value={selectedOption}
|
|
358
|
+
onChange={handleFieldChange}
|
|
359
|
+
isClearable
|
|
360
|
+
isSearchable
|
|
361
|
+
placeholder="Always show this field (type to search…)"
|
|
362
|
+
menuPortalTarget={
|
|
363
|
+
typeof document !== 'undefined'
|
|
364
|
+
? document.body
|
|
365
|
+
: undefined
|
|
366
|
+
}
|
|
367
|
+
menuPlacement="auto"
|
|
368
|
+
components={{
|
|
369
|
+
Option: FieldOption,
|
|
370
|
+
SingleValue: FieldSingleValue,
|
|
371
|
+
}}
|
|
372
|
+
styles={reactSelectStyles}
|
|
373
|
+
noOptionsMessage={() => 'No matching fields'}
|
|
374
|
+
/>
|
|
375
|
+
<span
|
|
376
|
+
className={`${styles.fi__span} ${styles.prefocus}`}
|
|
377
|
+
>
|
|
378
|
+
Show only when
|
|
379
|
+
</span>
|
|
380
|
+
</label>
|
|
381
|
+
</div>
|
|
382
|
+
|
|
383
|
+
<div className={styles.formItem}>
|
|
384
|
+
<label className={styles.fi__label}>
|
|
385
|
+
<select
|
|
386
|
+
name="conditional_operator"
|
|
387
|
+
value={operator}
|
|
388
|
+
onChange={handleOperatorChange}
|
|
389
|
+
disabled={!conditionalFieldId}
|
|
390
|
+
>
|
|
391
|
+
<option value="is">is</option>
|
|
392
|
+
<option value="is filled in">is filled in</option>
|
|
393
|
+
</select>
|
|
394
|
+
<span className={styles.fi__span}>Condition</span>
|
|
395
|
+
</label>
|
|
396
|
+
</div>
|
|
397
|
+
|
|
398
|
+
{(() => {
|
|
399
|
+
const valueControl = renderValueControl();
|
|
400
|
+
if (!valueControl) return null;
|
|
401
|
+
return (
|
|
402
|
+
<div className={styles.formItem}>
|
|
403
|
+
<label className={styles.fi__label}>
|
|
404
|
+
{valueControl}
|
|
405
|
+
<span className={styles.fi__span}>Value</span>
|
|
406
|
+
</label>
|
|
407
|
+
</div>
|
|
408
|
+
);
|
|
409
|
+
})()}
|
|
410
|
+
|
|
411
|
+
<div
|
|
412
|
+
className={`${styles.formItem} ${styles.fwItem}`}
|
|
413
|
+
style={{
|
|
414
|
+
background: 'rgba(0,0,0,0.03)',
|
|
415
|
+
borderRadius: 6,
|
|
416
|
+
padding: '8px 12px',
|
|
417
|
+
color: 'var(--paragraph-color)',
|
|
418
|
+
fontSize: '0.85rem',
|
|
419
|
+
display: 'flex',
|
|
420
|
+
alignItems: 'center',
|
|
421
|
+
justifyContent: 'space-between',
|
|
422
|
+
gap: 8,
|
|
423
|
+
}}
|
|
424
|
+
>
|
|
425
|
+
<div>{renderPreview()}</div>
|
|
426
|
+
{hasAnyRule ? (
|
|
427
|
+
<button
|
|
428
|
+
type="button"
|
|
429
|
+
onClick={handleClear}
|
|
430
|
+
title="Clear rule"
|
|
431
|
+
style={{
|
|
432
|
+
background: 'transparent',
|
|
433
|
+
border: 'none',
|
|
434
|
+
cursor: 'pointer',
|
|
435
|
+
display: 'inline-flex',
|
|
436
|
+
alignItems: 'center',
|
|
437
|
+
color: '#666',
|
|
438
|
+
}}
|
|
439
|
+
>
|
|
440
|
+
<CircleX size={16} />
|
|
441
|
+
</button>
|
|
442
|
+
) : null}
|
|
443
|
+
</div>
|
|
444
|
+
</>
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
export default ConditionalDisplayEditor;
|
|
@@ -47,6 +47,7 @@ import 'react-datepicker/dist/react-datepicker.css';
|
|
|
47
47
|
import Breadcrumb from '../Breadcrumb';
|
|
48
48
|
import CustomFetch from '../Fetch';
|
|
49
49
|
import SketchConfig from '../sketch/json/config.json';
|
|
50
|
+
import ConditionalDisplayEditor from './ConditionalDisplayEditor';
|
|
50
51
|
|
|
51
52
|
import styles from '../styles/GenericFormBuilder.module.scss'; // Import the CSS module
|
|
52
53
|
|
|
@@ -69,6 +70,8 @@ function GenericFormBuilder({ setting, urlParam, userProfile }) {
|
|
|
69
70
|
size: 'half',
|
|
70
71
|
required: 'no',
|
|
71
72
|
textareaHeight: 'normal',
|
|
73
|
+
conditional_field: '',
|
|
74
|
+
conditional_value: '',
|
|
72
75
|
});
|
|
73
76
|
const [dataOption, setDataOption] = useState({
|
|
74
77
|
id: '',
|
|
@@ -657,6 +660,8 @@ function GenericFormBuilder({ setting, urlParam, userProfile }) {
|
|
|
657
660
|
size: 'half',
|
|
658
661
|
required: 'no',
|
|
659
662
|
textareaHeight: 'normal',
|
|
663
|
+
conditional_field: '',
|
|
664
|
+
conditional_value: '',
|
|
660
665
|
}));
|
|
661
666
|
setModalType(() => ({
|
|
662
667
|
type: 'create',
|
|
@@ -739,11 +744,32 @@ function GenericFormBuilder({ setting, urlParam, userProfile }) {
|
|
|
739
744
|
let newDetail = [...data.detail];
|
|
740
745
|
newDetail.splice(index, 1);
|
|
741
746
|
|
|
747
|
+
let orphanCount = 0;
|
|
748
|
+
newDetail = newDetail.map((item) => {
|
|
749
|
+
if (item.conditional_field === id) {
|
|
750
|
+
orphanCount += 1;
|
|
751
|
+
return {
|
|
752
|
+
...item,
|
|
753
|
+
conditional_field: '',
|
|
754
|
+
conditional_value: '',
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
return item;
|
|
758
|
+
});
|
|
759
|
+
|
|
742
760
|
setData((items) => ({
|
|
743
761
|
...items,
|
|
744
762
|
detail: newDetail,
|
|
745
763
|
}));
|
|
746
764
|
|
|
765
|
+
if (orphanCount > 0) {
|
|
766
|
+
toast.info(
|
|
767
|
+
`Cleared ${orphanCount} conditional rule${
|
|
768
|
+
orphanCount === 1 ? '' : 's'
|
|
769
|
+
} that referenced the deleted field.`
|
|
770
|
+
);
|
|
771
|
+
}
|
|
772
|
+
|
|
747
773
|
handleCloseModal();
|
|
748
774
|
}
|
|
749
775
|
},
|
|
@@ -2214,63 +2240,17 @@ function GenericFormBuilder({ setting, urlParam, userProfile }) {
|
|
|
2214
2240
|
</div>
|
|
2215
2241
|
) : null}
|
|
2216
2242
|
|
|
2217
|
-
<
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
<div className={styles.formItem}>
|
|
2229
|
-
<label className={styles.fi__label}>
|
|
2230
|
-
<select
|
|
2231
|
-
name="conditional_field"
|
|
2232
|
-
value={
|
|
2233
|
-
dataField.conditional_field
|
|
2234
|
-
}
|
|
2235
|
-
onChange={handleChangeForm}
|
|
2236
|
-
>
|
|
2237
|
-
<option>
|
|
2238
|
-
Please Select an Option
|
|
2239
|
-
</option>
|
|
2240
|
-
{data.detail &&
|
|
2241
|
-
data.detail.length > 0 &&
|
|
2242
|
-
data.detail.map(
|
|
2243
|
-
(item, key) => (
|
|
2244
|
-
<option
|
|
2245
|
-
value={item.id}
|
|
2246
|
-
key={`conditional-field-${item.id}-${key}`}
|
|
2247
|
-
>
|
|
2248
|
-
{item.label}
|
|
2249
|
-
</option>
|
|
2250
|
-
)
|
|
2251
|
-
)}
|
|
2252
|
-
</select>
|
|
2253
|
-
<span className={styles.fi__span}>
|
|
2254
|
-
Field
|
|
2255
|
-
</span>
|
|
2256
|
-
</label>
|
|
2257
|
-
</div>
|
|
2258
|
-
|
|
2259
|
-
<div className={styles.formItem}>
|
|
2260
|
-
<label className={styles.fi__label}>
|
|
2261
|
-
<input
|
|
2262
|
-
name="conditional_value"
|
|
2263
|
-
value={
|
|
2264
|
-
dataField.conditional_value
|
|
2265
|
-
}
|
|
2266
|
-
type="text"
|
|
2267
|
-
onChange={handleChangeForm}
|
|
2268
|
-
/>
|
|
2269
|
-
<span className={styles.fi__span}>
|
|
2270
|
-
Value
|
|
2271
|
-
</span>
|
|
2272
|
-
</label>
|
|
2273
|
-
</div>
|
|
2243
|
+
<ConditionalDisplayEditor
|
|
2244
|
+
currentField={dataField}
|
|
2245
|
+
allFields={data.detail || []}
|
|
2246
|
+
fieldTypes={fieldTypes}
|
|
2247
|
+
onChange={(patch) =>
|
|
2248
|
+
setDataField((prev) => ({
|
|
2249
|
+
...prev,
|
|
2250
|
+
...patch,
|
|
2251
|
+
}))
|
|
2252
|
+
}
|
|
2253
|
+
/>
|
|
2274
2254
|
|
|
2275
2255
|
<div
|
|
2276
2256
|
className={`${styles.formItem} ${styles.fwItem} ${styles.lastItem}`}
|