119 lines
3.4 KiB
TypeScript
119 lines
3.4 KiB
TypeScript
import React, { useEffect, useId, useRef, useState } from 'react';
|
|
import { AsyncPaginate } from 'react-select-async-paginate';
|
|
import axios from 'axios';
|
|
|
|
const areStringArraysEqual = (left = [], right = []) =>
|
|
left.length === right.length &&
|
|
left.every((value, index) => value === right[index]);
|
|
|
|
const areSelectOptionsEqual = (left = [], right = []) =>
|
|
left.length === right.length &&
|
|
left.every(
|
|
(option, index) =>
|
|
option?.value === right[index]?.value &&
|
|
option?.label === right[index]?.label,
|
|
);
|
|
|
|
export const SelectFieldMany = ({
|
|
options,
|
|
field,
|
|
form,
|
|
itemRef,
|
|
showField,
|
|
}) => {
|
|
const [value, setValue] = useState([]);
|
|
const appliedOptionsSignatureRef = useRef<string | null>(null);
|
|
const PAGE_SIZE = 50;
|
|
|
|
useEffect(() => {
|
|
if (field.value?.[0] && typeof field.value[0] !== 'string') {
|
|
const normalizedValue = field.value.map((el) => el.id);
|
|
const isAlreadyNormalized =
|
|
Array.isArray(field.value) &&
|
|
areStringArraysEqual(field.value, normalizedValue);
|
|
|
|
if (!isAlreadyNormalized) {
|
|
form.setFieldValue(field.name, normalizedValue, false);
|
|
}
|
|
} else if (!field.value || field.value.length === 0) {
|
|
setValue((currentValue) =>
|
|
currentValue.length === 0 ? currentValue : [],
|
|
);
|
|
}
|
|
}, [field.name, field.value, form]);
|
|
|
|
useEffect(() => {
|
|
if (Array.isArray(options) && options.length > 0) {
|
|
const selectedOptions = options.map((el) => ({
|
|
value: el.id,
|
|
label: el[showField],
|
|
}));
|
|
const selectedIds = options.map((el) => el.id);
|
|
const optionsSignature = selectedIds.join('|');
|
|
const shouldApplyInitialOptions =
|
|
appliedOptionsSignatureRef.current !== optionsSignature;
|
|
const fieldValueMatchesInitialOptions = areStringArraysEqual(
|
|
field.value,
|
|
selectedIds,
|
|
);
|
|
|
|
if (shouldApplyInitialOptions) {
|
|
appliedOptionsSignatureRef.current = optionsSignature;
|
|
form.setFieldValue(field.name, selectedIds, false);
|
|
}
|
|
|
|
if (shouldApplyInitialOptions || fieldValueMatchesInitialOptions) {
|
|
setValue((currentValue) =>
|
|
areSelectOptionsEqual(currentValue, selectedOptions)
|
|
? currentValue
|
|
: selectedOptions,
|
|
);
|
|
}
|
|
}
|
|
}, [field.name, field.value, form, options, showField]);
|
|
|
|
const mapResponseToValuesAndLabels = (data) => ({
|
|
value: data.id,
|
|
label: data.label,
|
|
});
|
|
|
|
const handleChange = (
|
|
data: Array<{ value: string; label: string }> | null,
|
|
) => {
|
|
const selectedOptions = data || [];
|
|
setValue(selectedOptions);
|
|
form.setFieldValue(
|
|
field.name,
|
|
selectedOptions.map((el) => el?.value || null),
|
|
);
|
|
};
|
|
|
|
async function callApi(
|
|
inputValue: string,
|
|
loadedOptions: Array<{ value: string; label: string }>,
|
|
) {
|
|
const path = `/${itemRef}/autocomplete?limit=${PAGE_SIZE}&offset=${loadedOptions.length}${inputValue ? `&query=${inputValue}` : ''}`;
|
|
const { data } = await axios(path);
|
|
return {
|
|
options: data.map(mapResponseToValuesAndLabels),
|
|
hasMore: data.length === PAGE_SIZE,
|
|
};
|
|
}
|
|
return (
|
|
<AsyncPaginate
|
|
classNames={{
|
|
control: () => 'px-1 py-2',
|
|
}}
|
|
classNamePrefix='react-select'
|
|
instanceId={useId()}
|
|
value={value}
|
|
isMulti
|
|
debounceTimeout={1000}
|
|
loadOptions={callApi}
|
|
onChange={handleChange}
|
|
defaultOptions
|
|
isClearable
|
|
/>
|
|
);
|
|
};
|