@visns-studio/visns-components 5.22.0 → 5.25.0
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.
|
|
97
|
+
"version": "5.25.0",
|
|
98
98
|
"description": "Various packages to assist in the development of our Custom Applications.",
|
|
99
99
|
"main": "src/index.js",
|
|
100
100
|
"files": [
|
package/src/components/Form.jsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import './styles/global.css';
|
|
2
2
|
|
|
3
|
-
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
|
3
|
+
import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react';
|
|
4
4
|
import { useNavigate } from 'react-router-dom';
|
|
5
5
|
import { motion } from 'framer-motion';
|
|
6
6
|
import { toast } from 'react-toastify';
|
|
@@ -71,7 +71,7 @@ function Form({
|
|
|
71
71
|
closeModal,
|
|
72
72
|
columnId,
|
|
73
73
|
formType,
|
|
74
|
-
formSettings,
|
|
74
|
+
formSettings: formSettingsProp,
|
|
75
75
|
fetchTable,
|
|
76
76
|
mapbox,
|
|
77
77
|
modalOpen,
|
|
@@ -97,6 +97,35 @@ function Form({
|
|
|
97
97
|
const dataRef = useRef([]);
|
|
98
98
|
const groupClickTimeoutRef = useRef(null);
|
|
99
99
|
|
|
100
|
+
// Hide fields restricted to roles the user doesn't have (field.roles array)
|
|
101
|
+
const formSettings = useMemo(() => {
|
|
102
|
+
if (!formSettingsProp?.fields?.length) {
|
|
103
|
+
return formSettingsProp;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const fields = formSettingsProp.fields.filter((field) => {
|
|
107
|
+
if (
|
|
108
|
+
!field.roles ||
|
|
109
|
+
!Array.isArray(field.roles) ||
|
|
110
|
+
field.roles.length === 0
|
|
111
|
+
) {
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return field.roles.some((requiredRole) =>
|
|
116
|
+
userProfile?.roles?.some(
|
|
117
|
+
(userRole) => userRole.name === requiredRole
|
|
118
|
+
)
|
|
119
|
+
);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
if (fields.length === formSettingsProp.fields.length) {
|
|
123
|
+
return formSettingsProp;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return { ...formSettingsProp, fields };
|
|
127
|
+
}, [formSettingsProp, userProfile]);
|
|
128
|
+
|
|
100
129
|
const childDropdownCallback = (data) => {
|
|
101
130
|
let _form = { ...formSettings };
|
|
102
131
|
|
|
@@ -1179,6 +1208,31 @@ function Form({
|
|
|
1179
1208
|
} else {
|
|
1180
1209
|
_inputClass[item.id] = ''; // Clear error class if not required
|
|
1181
1210
|
}
|
|
1211
|
+
|
|
1212
|
+
// Numeric bounds validation (min / greaterThan) for filled values
|
|
1213
|
+
const numericValue = formData[item.id];
|
|
1214
|
+
if (
|
|
1215
|
+
(item.min !== undefined ||
|
|
1216
|
+
item.greaterThan !== undefined) &&
|
|
1217
|
+
numericValue !== null &&
|
|
1218
|
+
numericValue !== undefined &&
|
|
1219
|
+
numericValue !== '' &&
|
|
1220
|
+
!isNaN(Number(numericValue))
|
|
1221
|
+
) {
|
|
1222
|
+
if (
|
|
1223
|
+
item.min !== undefined &&
|
|
1224
|
+
Number(numericValue) < item.min
|
|
1225
|
+
) {
|
|
1226
|
+
validation += `${item.label} must be at least ${item.min}.<br/>`;
|
|
1227
|
+
_inputClass[item.id] = styles.inputError;
|
|
1228
|
+
} else if (
|
|
1229
|
+
item.greaterThan !== undefined &&
|
|
1230
|
+
Number(numericValue) <= item.greaterThan
|
|
1231
|
+
) {
|
|
1232
|
+
validation += `${item.label} must be greater than ${item.greaterThan}.<br/>`;
|
|
1233
|
+
_inputClass[item.id] = styles.inputError;
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1182
1236
|
});
|
|
1183
1237
|
|
|
1184
1238
|
setInputClass(_inputClass);
|
|
@@ -1816,6 +1870,7 @@ function Form({
|
|
|
1816
1870
|
// In bulk edit mode, call saveAnother for each row in the group
|
|
1817
1871
|
if (bulkEditMode && bulkEditGroupData?.groupRowIds?.length) {
|
|
1818
1872
|
let hasError = false;
|
|
1873
|
+
const saveAnotherErrors = [];
|
|
1819
1874
|
for (const rowId of bulkEditGroupData.groupRowIds) {
|
|
1820
1875
|
const saveAnotherUrl = `${saveAnother.url}/${rowId}`;
|
|
1821
1876
|
const resSaveAnother =
|
|
@@ -1826,12 +1881,27 @@ function Form({
|
|
|
1826
1881
|
);
|
|
1827
1882
|
if (resSaveAnother.data.error !== '') {
|
|
1828
1883
|
hasError = true;
|
|
1884
|
+
if (
|
|
1885
|
+
!saveAnotherErrors.includes(
|
|
1886
|
+
resSaveAnother.data
|
|
1887
|
+
.error
|
|
1888
|
+
)
|
|
1889
|
+
) {
|
|
1890
|
+
saveAnotherErrors.push(
|
|
1891
|
+
resSaveAnother.data
|
|
1892
|
+
.error
|
|
1893
|
+
);
|
|
1894
|
+
}
|
|
1829
1895
|
}
|
|
1830
1896
|
}
|
|
1831
1897
|
if (!hasError) {
|
|
1832
1898
|
toast.success(saveAnother.message);
|
|
1833
1899
|
} else {
|
|
1834
1900
|
saveAnotherFailed = true;
|
|
1901
|
+
saveAnotherErrors.forEach(
|
|
1902
|
+
(error) =>
|
|
1903
|
+
toast.error(error)
|
|
1904
|
+
);
|
|
1835
1905
|
}
|
|
1836
1906
|
if (fetchTable) {
|
|
1837
1907
|
const createdItem =
|
|
@@ -1858,6 +1928,9 @@ function Form({
|
|
|
1858
1928
|
}
|
|
1859
1929
|
} else {
|
|
1860
1930
|
saveAnotherFailed = true;
|
|
1931
|
+
toast.error(
|
|
1932
|
+
resSaveAnother.data.error
|
|
1933
|
+
);
|
|
1861
1934
|
if (fetchTable) {
|
|
1862
1935
|
const createdItem =
|
|
1863
1936
|
res.data.data || res.data;
|
|
@@ -249,23 +249,37 @@ function MultiSelect({
|
|
|
249
249
|
|
|
250
250
|
const handleCreate = async (inputValue) => {
|
|
251
251
|
if (creatableConfig.url && creatableConfig.method) {
|
|
252
|
+
// creatableConfig.payload lets the consumer send extra fields
|
|
253
|
+
// with the create (e.g. {is_active: 1}); creatableConfig.field
|
|
254
|
+
// overrides the body key for the typed value (default "label",
|
|
255
|
+
// e.g. "name" for entities whose label column is `name`).
|
|
256
|
+
const valueKey = creatableConfig.field || 'label';
|
|
252
257
|
const res = await CustomFetch(
|
|
253
258
|
creatableConfig.url,
|
|
254
259
|
creatableConfig.method,
|
|
255
|
-
{
|
|
260
|
+
{ [valueKey]: inputValue, ...(creatableConfig.payload || {}) }
|
|
256
261
|
);
|
|
257
262
|
|
|
258
|
-
|
|
263
|
+
const created = res?.data?.data ?? res?.data;
|
|
264
|
+
if (res && created && created.id) {
|
|
259
265
|
const newOption = {
|
|
260
|
-
value:
|
|
261
|
-
label:
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
...res.data,
|
|
266
|
+
value: created.id,
|
|
267
|
+
label: created.label || created.name || inputValue,
|
|
268
|
+
description: created.description || null,
|
|
269
|
+
...created,
|
|
265
270
|
};
|
|
266
271
|
|
|
272
|
+
const nextValue = [...selectValue, newOption];
|
|
267
273
|
setSelectOptions((prevOptions) => [...prevOptions, newOption]);
|
|
268
|
-
setSelectValue(
|
|
274
|
+
setSelectValue(nextValue);
|
|
275
|
+
// Propagate to the parent form so the freshly-created option
|
|
276
|
+
// is part of the field value and is persisted on save —
|
|
277
|
+
// updating local state alone left it out of the submission.
|
|
278
|
+
onChange(
|
|
279
|
+
nextValue,
|
|
280
|
+
{ action: 'create-option', option: newOption },
|
|
281
|
+
settings.id
|
|
282
|
+
);
|
|
269
283
|
}
|
|
270
284
|
}
|
|
271
285
|
};
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
1
2
|
import numeral from 'numeral';
|
|
2
3
|
import moment from 'moment';
|
|
3
4
|
import parse from 'html-react-parser';
|
|
4
5
|
import Select from 'react-select';
|
|
6
|
+
import CreatableSelect from 'react-select/creatable';
|
|
7
|
+
import CustomFetch from '../Fetch';
|
|
5
8
|
import { Download, AlarmClock } from 'lucide-react';
|
|
6
9
|
import { toast } from 'react-toastify';
|
|
7
10
|
import CellWithTooltip from '../cells/CellWithTooltip';
|
|
@@ -346,6 +349,168 @@ export const renderColourColumn = ({ column, commonProps }) => {
|
|
|
346
349
|
};
|
|
347
350
|
|
|
348
351
|
// Dropdown Column Renderer
|
|
352
|
+
// Compact react-select styling shared by the grid cell editors
|
|
353
|
+
const compactSelectStyles = {
|
|
354
|
+
menuPortal: (base) => ({ ...base, zIndex: 9999 }),
|
|
355
|
+
control: (base) => ({
|
|
356
|
+
...base,
|
|
357
|
+
border: 'none',
|
|
358
|
+
boxShadow: 'none',
|
|
359
|
+
backgroundColor: 'transparent',
|
|
360
|
+
minHeight: 'unset',
|
|
361
|
+
}),
|
|
362
|
+
valueContainer: (base) => ({ ...base, padding: '0 4px' }),
|
|
363
|
+
multiValue: (base) => ({ ...base, margin: '1px 2px' }),
|
|
364
|
+
multiValueLabel: (base) => ({
|
|
365
|
+
...base,
|
|
366
|
+
fontSize: '12px',
|
|
367
|
+
padding: '1px 4px',
|
|
368
|
+
}),
|
|
369
|
+
placeholder: (base) => ({ ...base, color: '#777' }),
|
|
370
|
+
indicatorSeparator: () => ({ display: 'none' }),
|
|
371
|
+
dropdownIndicator: (base) => ({ ...base, padding: '0 4px' }),
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
// Inline editor for the free-text companion of a multiDropdown's "Other"
|
|
375
|
+
// option (column.other = {matchLabel, key, placeholder}). Commits on
|
|
376
|
+
// blur/Enter via the column's update endpoint.
|
|
377
|
+
const OtherValueInput = ({ column, data, handleDropdownUpdate }) => {
|
|
378
|
+
const current = data[column.other.key] ?? '';
|
|
379
|
+
const [value, setValue] = useState(current);
|
|
380
|
+
|
|
381
|
+
useEffect(() => {
|
|
382
|
+
setValue(current);
|
|
383
|
+
}, [current]);
|
|
384
|
+
|
|
385
|
+
const commit = () => {
|
|
386
|
+
if (
|
|
387
|
+
value === current ||
|
|
388
|
+
!column.update?.key ||
|
|
389
|
+
!column.update?.url ||
|
|
390
|
+
!column.update?.method
|
|
391
|
+
) {
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
handleDropdownUpdate(
|
|
396
|
+
column.other.key,
|
|
397
|
+
`${column.update.url}/${data.id}`,
|
|
398
|
+
column.update.method,
|
|
399
|
+
value
|
|
400
|
+
);
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
return (
|
|
404
|
+
<input
|
|
405
|
+
type="text"
|
|
406
|
+
value={value}
|
|
407
|
+
placeholder={column.other.placeholder || 'Other…'}
|
|
408
|
+
onChange={(e) => setValue(e.target.value)}
|
|
409
|
+
onBlur={commit}
|
|
410
|
+
onKeyDown={(e) => {
|
|
411
|
+
if (e.key === 'Enter') e.target.blur();
|
|
412
|
+
}}
|
|
413
|
+
onClick={(e) => e.stopPropagation()}
|
|
414
|
+
style={{
|
|
415
|
+
width: '100%',
|
|
416
|
+
marginTop: '2px',
|
|
417
|
+
padding: '3px 6px',
|
|
418
|
+
fontSize: '12px',
|
|
419
|
+
border: '1px dashed #b9b9b9',
|
|
420
|
+
borderRadius: '4px',
|
|
421
|
+
outline: 'none',
|
|
422
|
+
backgroundColor: '#fffdf2',
|
|
423
|
+
}}
|
|
424
|
+
/>
|
|
425
|
+
);
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
// Single-select cell that can also create new entries on the fly
|
|
429
|
+
// (column.creatable = {url, field, payload?}): typing a value not in
|
|
430
|
+
// the options offers an "Add …" action that POSTs to the entity
|
|
431
|
+
// endpoint and assigns the created id to the row.
|
|
432
|
+
const CreatableDropdownCell = ({
|
|
433
|
+
column,
|
|
434
|
+
data,
|
|
435
|
+
optionsList,
|
|
436
|
+
handleDropdownUpdate,
|
|
437
|
+
}) => {
|
|
438
|
+
const [creating, setCreating] = useState(false);
|
|
439
|
+
|
|
440
|
+
const options = optionsList.map((o) => ({ value: o.id, label: o.label }));
|
|
441
|
+
const currentId = data[column.id]?.id || data[column.id] || '';
|
|
442
|
+
const currentOption =
|
|
443
|
+
options.find((o) => o.value === currentId) || null;
|
|
444
|
+
|
|
445
|
+
const fallbackText =
|
|
446
|
+
!currentId && column.fallbackKey && data[column.fallbackKey]
|
|
447
|
+
? String(data[column.fallbackKey])
|
|
448
|
+
: null;
|
|
449
|
+
|
|
450
|
+
const assign = (value) => {
|
|
451
|
+
if (column.update?.key && column.update?.url && column.update?.method) {
|
|
452
|
+
handleDropdownUpdate(
|
|
453
|
+
column.update.key,
|
|
454
|
+
`${column.update.url}/${data.id}`,
|
|
455
|
+
column.update.method,
|
|
456
|
+
value
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
const handleCreate = async (inputValue) => {
|
|
462
|
+
const label = String(inputValue ?? '').trim();
|
|
463
|
+
if (!label || !column.creatable?.url || !column.creatable?.field) {
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
setCreating(true);
|
|
468
|
+
try {
|
|
469
|
+
const res = await CustomFetch(column.creatable.url, 'POST', {
|
|
470
|
+
[column.creatable.field]: label,
|
|
471
|
+
...(column.creatable.payload || {}),
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
const created = res?.data?.data;
|
|
475
|
+
if (res?.data?.error === '' && created?.id) {
|
|
476
|
+
assign(created.id);
|
|
477
|
+
} else {
|
|
478
|
+
toast.error(res?.data?.error || 'Could not create entry');
|
|
479
|
+
}
|
|
480
|
+
} catch (err) {
|
|
481
|
+
console.error(err);
|
|
482
|
+
toast.error('Could not create entry');
|
|
483
|
+
} finally {
|
|
484
|
+
setCreating(false);
|
|
485
|
+
}
|
|
486
|
+
};
|
|
487
|
+
|
|
488
|
+
return (
|
|
489
|
+
<CreatableSelect
|
|
490
|
+
options={options}
|
|
491
|
+
value={currentOption}
|
|
492
|
+
placeholder={
|
|
493
|
+
fallbackText ||
|
|
494
|
+
`Select ${/^[aeioAEIO]/.test(column.label) ? 'an' : 'a'} ${
|
|
495
|
+
column.label
|
|
496
|
+
}`
|
|
497
|
+
}
|
|
498
|
+
isDisabled={creating}
|
|
499
|
+
isLoading={creating}
|
|
500
|
+
menuPortalTarget={
|
|
501
|
+
typeof document !== 'undefined' ? document.body : undefined
|
|
502
|
+
}
|
|
503
|
+
menuPlacement="auto"
|
|
504
|
+
formatCreateLabel={(v) => `Add "${v}"`}
|
|
505
|
+
styles={compactSelectStyles}
|
|
506
|
+
onChange={(selected) => {
|
|
507
|
+
if (selected) assign(selected.value);
|
|
508
|
+
}}
|
|
509
|
+
onCreateOption={handleCreate}
|
|
510
|
+
/>
|
|
511
|
+
);
|
|
512
|
+
};
|
|
513
|
+
|
|
349
514
|
export const renderDropdownColumn = ({
|
|
350
515
|
column,
|
|
351
516
|
commonProps,
|
|
@@ -389,6 +554,19 @@ export const renderDropdownColumn = ({
|
|
|
389
554
|
}
|
|
390
555
|
}
|
|
391
556
|
|
|
557
|
+
// creatable: swap the native select for a react-select that
|
|
558
|
+
// can add new entries to the source entity on the fly
|
|
559
|
+
if (column.creatable) {
|
|
560
|
+
return (
|
|
561
|
+
<CreatableDropdownCell
|
|
562
|
+
column={column}
|
|
563
|
+
data={data}
|
|
564
|
+
optionsList={optionsList}
|
|
565
|
+
handleDropdownUpdate={handleDropdownUpdate}
|
|
566
|
+
/>
|
|
567
|
+
);
|
|
568
|
+
}
|
|
569
|
+
|
|
392
570
|
return (
|
|
393
571
|
<select
|
|
394
572
|
name={column.id}
|
|
@@ -540,66 +718,107 @@ export const renderMultiDropdownColumn = ({
|
|
|
540
718
|
? String(data[column.fallbackKey])
|
|
541
719
|
: 'Select...';
|
|
542
720
|
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
721
|
+
// other: when the option matching other.matchLabel is
|
|
722
|
+
// selected, show a free-text input for its companion value
|
|
723
|
+
// (e.g. the "Other" tour format text)
|
|
724
|
+
const otherSelected =
|
|
725
|
+
column.other &&
|
|
726
|
+
value.some(
|
|
727
|
+
(v) =>
|
|
728
|
+
String(v.label).toLowerCase() ===
|
|
729
|
+
String(
|
|
730
|
+
column.other.matchLabel ?? 'Other'
|
|
731
|
+
).toLowerCase()
|
|
732
|
+
);
|
|
733
|
+
|
|
734
|
+
const save = (ids) => {
|
|
735
|
+
if (
|
|
736
|
+
column.update?.key &&
|
|
737
|
+
column.update?.url &&
|
|
738
|
+
column.update?.method
|
|
739
|
+
) {
|
|
740
|
+
handleDropdownUpdate(
|
|
741
|
+
column.update.key,
|
|
742
|
+
`${column.update.url}/${data.id}`,
|
|
743
|
+
column.update.method,
|
|
744
|
+
ids
|
|
745
|
+
);
|
|
746
|
+
}
|
|
747
|
+
};
|
|
748
|
+
|
|
749
|
+
// creatable: typing a value not in the list offers "Add …"
|
|
750
|
+
// which POSTs a new entry to the source entity and adds it to
|
|
751
|
+
// this row's selection.
|
|
752
|
+
const handleCreate = async (inputValue) => {
|
|
753
|
+
const label = String(inputValue ?? '').trim();
|
|
754
|
+
if (
|
|
755
|
+
!label ||
|
|
756
|
+
!column.creatable?.url ||
|
|
757
|
+
!column.creatable?.field
|
|
758
|
+
) {
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
try {
|
|
762
|
+
const res = await CustomFetch(
|
|
763
|
+
column.creatable.url,
|
|
764
|
+
'POST',
|
|
765
|
+
{
|
|
766
|
+
[column.creatable.field]: label,
|
|
767
|
+
...(column.creatable.payload || {}),
|
|
768
|
+
}
|
|
769
|
+
);
|
|
770
|
+
const created = res?.data?.data;
|
|
771
|
+
if (res?.data?.error === '' && created?.id) {
|
|
772
|
+
save([
|
|
773
|
+
...value.map((v) => v.value),
|
|
774
|
+
created.id,
|
|
775
|
+
]);
|
|
776
|
+
} else {
|
|
777
|
+
toast.error(
|
|
778
|
+
res?.data?.error || 'Could not create entry'
|
|
779
|
+
);
|
|
554
780
|
}
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
}),
|
|
578
|
-
placeholder: (base) => ({
|
|
579
|
-
...base,
|
|
580
|
-
color: '#777',
|
|
581
|
-
}),
|
|
582
|
-
indicatorSeparator: () => ({ display: 'none' }),
|
|
583
|
-
dropdownIndicator: (base) => ({
|
|
584
|
-
...base,
|
|
585
|
-
padding: '0 4px',
|
|
586
|
-
}),
|
|
587
|
-
}}
|
|
588
|
-
onChange={(selected) => {
|
|
589
|
-
if (
|
|
590
|
-
column.update?.key &&
|
|
591
|
-
column.update?.url &&
|
|
592
|
-
column.update?.method
|
|
593
|
-
) {
|
|
594
|
-
handleDropdownUpdate(
|
|
595
|
-
column.update.key,
|
|
596
|
-
`${column.update.url}/${data.id}`,
|
|
597
|
-
column.update.method,
|
|
598
|
-
(selected || []).map((s) => s.value)
|
|
599
|
-
);
|
|
781
|
+
} catch (err) {
|
|
782
|
+
console.error(err);
|
|
783
|
+
toast.error('Could not create entry');
|
|
784
|
+
}
|
|
785
|
+
};
|
|
786
|
+
|
|
787
|
+
const SelectComponent = column.creatable
|
|
788
|
+
? CreatableSelect
|
|
789
|
+
: Select;
|
|
790
|
+
|
|
791
|
+
return (
|
|
792
|
+
<div>
|
|
793
|
+
<SelectComponent
|
|
794
|
+
isMulti
|
|
795
|
+
options={options}
|
|
796
|
+
value={value}
|
|
797
|
+
placeholder={placeholder}
|
|
798
|
+
isClearable={false}
|
|
799
|
+
menuPortalTarget={
|
|
800
|
+
typeof document !== 'undefined'
|
|
801
|
+
? document.body
|
|
802
|
+
: undefined
|
|
600
803
|
}
|
|
601
|
-
|
|
602
|
-
|
|
804
|
+
menuPlacement="auto"
|
|
805
|
+
styles={compactSelectStyles}
|
|
806
|
+
{...(column.creatable && {
|
|
807
|
+
onCreateOption: handleCreate,
|
|
808
|
+
formatCreateLabel: (v) => `Add "${v}"`,
|
|
809
|
+
})}
|
|
810
|
+
onChange={(selected) =>
|
|
811
|
+
save((selected || []).map((s) => s.value))
|
|
812
|
+
}
|
|
813
|
+
/>
|
|
814
|
+
{otherSelected && (
|
|
815
|
+
<OtherValueInput
|
|
816
|
+
column={column}
|
|
817
|
+
data={data}
|
|
818
|
+
handleDropdownUpdate={handleDropdownUpdate}
|
|
819
|
+
/>
|
|
820
|
+
)}
|
|
821
|
+
</div>
|
|
603
822
|
);
|
|
604
823
|
},
|
|
605
824
|
};
|