Загрузка данных
import {
Location,
useGetLocationChildrenQuery,
} from '@/shared/api/location/client';
import { Helpers } from '@/shared/lib';
import {
useResetLocationFilterSelect,
useSetLocation,
} from '@entities/LocationFiltersList/lib/hooks';
import { Select, SelectProps } from '@shared/components';
import { ChangeEvent, useMemo, useState } from 'react';
export type SideFilterSelectProps = {
value: string[] | string;
onChange: NonNullable<SelectProps['onChange']>;
parentIds?: string[] | string;
isRoot?: boolean;
isFloor?: boolean;
onSelectedLocationIdsChange?: (locationIds: string[]) => void;
/**
* Возвращает полные объекты выбранных локаций.
* Необязательный prop, существующие вызовы не затрагивает.
*/
onSelectedLocationsChange?: (locations: Location[]) => void;
/**
* Исключает переданные treeId из списка options.
* Необязательный prop, существующие вызовы не затрагивает.
*/
excludedTreeIds?: Array<string | number>;
required?: boolean;
} & Omit<SelectProps, 'onChange' | 'value' | 'options'>;
type LocationEvent = ChangeEvent<{
name?: string;
value: unknown;
}>;
const getLocationLabel = (
option: Location,
parentIds?: string[] | string,
): string => {
const hasMultipleParents =
Array.isArray(parentIds) && parentIds.length > 1;
if (!hasMultipleParents || !option.pathName) {
return option.name ?? '';
}
return `${option.name}, ${option.pathName}`;
};
const normalizeValue = (value: unknown): string[] => {
if (Array.isArray(value)) {
return value.map(String);
}
return value ? [String(value)] : [];
};
const getSelectedLocations = (
locations: Location[],
selectedTreeIds: string[],
): Location[] => {
const selectedTreeIdSet = new Set(selectedTreeIds);
return locations.filter((option) =>
selectedTreeIdSet.has(String(option.treeId)),
);
};
const getSelectedLocationIds = (
locations: Location[],
): string[] =>
locations
.map((option) => option.id)
.filter(
(id): id is NonNullable<typeof id> =>
id !== null && id !== undefined,
)
.map(String);
export const SideFilterSelect = ({
value,
onChange,
disabled,
label,
parentIds,
isRoot = false,
required,
isFloor = false,
onSelectedLocationIdsChange,
onSelectedLocationsChange,
excludedTreeIds = [],
...rest
}: SideFilterSelectProps) => {
const [location, setLocation] = useState<Location[]>([]);
const requestParams = isRoot
? {}
: {
parentId:
typeof parentIds === 'object'
? Helpers.convertStringsToNumbers(parentIds)
: [Number(parentIds)],
};
const requestOptions = isRoot
? {}
: {
skip:
typeof parentIds === 'object'
? !parentIds?.length
: !parentIds,
};
const { data, status } = useGetLocationChildrenQuery(
requestParams,
{
...requestOptions,
refetchOnMountOrArgChange: true,
},
);
useSetLocation({
entity: data,
status,
setter: setLocation,
isFloor,
});
useResetLocationFilterSelect({
setter: setLocation,
ids: parentIds ?? '',
});
const handleChange: NonNullable<
SelectProps['onChange']
> = (...args) => {
const event = args[0] as LocationEvent;
const selectedTreeIds = normalizeValue(
event.target.value,
);
const selectedLocations = getSelectedLocations(
location,
selectedTreeIds,
);
onChange(...args);
onSelectedLocationIdsChange?.(
getSelectedLocationIds(selectedLocations),
);
onSelectedLocationsChange?.(
selectedLocations,
);
};
const excludedTreeIdSet = useMemo(
() => new Set(excludedTreeIds.map(String)),
[excludedTreeIds],
);
const innerOption = location
.filter(
(option) =>
!excludedTreeIdSet.has(
String(option.treeId),
),
)
.map((option) => ({
value: String(option.treeId),
label: getLocationLabel(
option,
parentIds,
),
}));
return (
<Select
label={label}
options={innerOption}
value={value}
onChange={handleChange}
disabled={
disabled || (!isRoot && !location.length)
}
required={required}
{...rest}
/>
);
};