import qs from 'qs' import { useCallback, useEffect, useRef, useState } from 'react' import type { SelectOptionItem } from '@/components/formElements/input/inputUtils' import axiosInstance from '@/config/axios' import { debounce } from '@/helpers' type DebouncedSearch = ((value: string) => void) & { cancel?: () => void } interface Options { apiField: string disabled?: boolean enabled: boolean filterName: string filterValue: string | number getCurrentValue: () => unknown isOutsideFilter?: boolean pageSize: number searchMode: 'local' | 'api' selectKey: string selectValue: string url?: string } export function useRemoteSelectOptions({ apiField, disabled, enabled, filterName, filterValue, getCurrentValue, isOutsideFilter, pageSize, searchMode, selectKey, selectValue, url, }: Options) { const [options, setOptions] = useState([]) const [loading, setLoading] = useState(false) const [error, setError] = useState(null) const sourceOptions = useRef([]) const firstFetch = useRef(true) const abortController = useRef(null) const debouncedSearch = useRef(null) const fetchOptions = useCallback( async (searchValue?: string) => { if (!url) return if (!firstFetch.current && searchMode === 'local') { setOptions( searchValue ? sourceOptions.current.filter((item) => { const value = (item as Record)[selectValue] return typeof value === 'string' && value.includes(searchValue) }) : sourceOptions.current ) return } abortController.current?.abort() const controller = new AbortController() abortController.current = controller setLoading(true) setError(null) try { const currentValue = getCurrentValue() const selectedFilter = searchValue ? { [selectValue]: searchValue } : currentValue ? { [selectKey]: currentValue } : {} const commonFilter = filterValue ? { [filterName]: filterValue } : {} const params = isOutsideFilter ? { ...commonFilter, ...selectedFilter } : { filters: { ...commonFilter, ...selectedFilter }, page: 1, pageSize } const response = await axiosInstance.get(url, { params, paramsSerializer: (value) => qs.stringify(value, { arrayFormat: 'brackets' }), signal: controller.signal, }) const data = response.data?.[apiField] const nextOptions = Array.isArray(data) ? (data as SelectOptionItem[]) : [] sourceOptions.current = nextOptions setOptions(nextOptions) firstFetch.current = false } catch (requestError) { if (!controller.signal.aborted) setError(requestError) } finally { if (abortController.current === controller) setLoading(false) } }, [apiField, filterName, filterValue, getCurrentValue, isOutsideFilter, pageSize, searchMode, selectKey, selectValue, url] ) useEffect(() => { debouncedSearch.current?.cancel?.() debouncedSearch.current = debounce((value: string) => void fetchOptions(value), 500) return () => debouncedSearch.current?.cancel?.() }, [fetchOptions]) useEffect(() => { if (!enabled || disabled) return firstFetch.current = true void fetchOptions() return () => abortController.current?.abort() }, [disabled, enabled, fetchOptions, filterValue]) const search = useCallback((value: string) => debouncedSearch.current?.(value), []) return { error, loading, options, refresh: fetchOptions, search } }