39948-vm/frontend/src/components/SelectFieldMany.tsx
2026-06-29 09:38:55 +02:00

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
/>
);
};