Загрузка данных
import { SideFilterSelect } from '@/shared/components/SideFilterSelect/SideFilterSelect';
import { Helpers } from '@/shared/lib';
import { CheckIcon, SortIcon } from '@sber-friend/flamingo-icons';
import { Badge, Box, Button, Dropdown } from '@shared/components';
import { ChangeEvent, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
// TO DO вынести в отдельный файл с типами
type LocationEvent = ChangeEvent<{ name?: string; value: unknown }>;
type LocationOutputIdType = 'treeId' | 'id';
type LocationLevel = 'region' | 'city' | 'place' | 'floor' | 'room';
type SelectedLocationIdsByLevel = Record<LocationLevel, number[]>;
type LocationFiltersProps = {
'data-fui-tid'?: string;
withDropdown?: boolean;
isRooms?: boolean;
ids?: number[];
/**
* Что отдавать наружу в onLocationIdsChange.
* treeId - старое поведение по умолчанию.
* id - обычный location id, при этом внутренние запросы все равно идут по treeId.
*/
outputIdType?: LocationOutputIdType;
regionId: string[];
cityId: string[];
placeId: string[];
floorId: string[];
roomId: string[];
setRegionId: (regionId: string[]) => void;
setCityId: (cityId: string[]) => void;
setPlaceId: (placeId: string[]) => void;
setFloorId: (floorId: string[]) => void;
setRoomId: (roomId: string[]) => void;
onLocationIdsChange: (locationIds: number[]) => void;
};
const EMPTY_SELECTED_LOCATION_IDS: SelectedLocationIdsByLevel = {
region: [],
city: [],
place: [],
floor: [],
room: [],
};
const getDeepestSelectedLevel = ({
regionId,
cityId,
placeId,
floorId,
roomId,
}: Pick<
LocationFiltersProps,
'regionId' | 'cityId' | 'placeId' | 'floorId' | 'roomId'
>): LocationLevel => {
if (roomId?.length > 0) {
return 'room';
}
if (floorId?.length > 0) {
return 'floor';
}
if (placeId?.length > 0) {
return 'place';
}
if (cityId?.length > 0) {
return 'city';
}
return 'region';
};
// TO DO refactor вместо locationId будет treeId
export const LocationFilters = ({
'data-fui-tid': tid,
isRooms = false,
ids,
regionId,
cityId,
placeId,
floorId,
roomId,
setRegionId,
setCityId,
setFloorId,
setPlaceId,
setRoomId,
onLocationIdsChange,
withDropdown = true,
outputIdType = 'treeId',
}: LocationFiltersProps) => {
const { t } = useTranslation('locations');
const [selectedLocationIdsByLevel, setSelectedLocationIdsByLevel] =
useState<SelectedLocationIdsByLevel>(EMPTY_SELECTED_LOCATION_IDS);
useEffect(() => {
if (ids?.length === 0) {
setRegionId([]);
setCityId([]);
setPlaceId([]);
setFloorId([]);
setRoomId([]);
}
}, [ids, setCityId, setFloorId, setPlaceId, setRegionId, setRoomId]);
const locationTreeIds =
roomId?.length > 0
? roomId
: floorId?.length > 0
? floorId
: placeId?.length > 0
? placeId
: cityId?.length > 0
? cityId
: regionId;
const deepestSelectedLevel = useMemo(
() =>
getDeepestSelectedLevel({
regionId,
cityId,
placeId,
floorId,
roomId,
}),
[cityId, floorId, placeId, regionId, roomId],
);
const outputLocationIds = useMemo(() => {
if (outputIdType === 'treeId') {
return Helpers.convertStringsToNumbers(locationTreeIds);
}
return selectedLocationIdsByLevel[deepestSelectedLevel];
}, [
deepestSelectedLevel,
locationTreeIds,
outputIdType,
selectedLocationIdsByLevel,
]);
useEffect(() => {
if (outputIdType === 'id' && locationTreeIds.length > 0) {
const selectedIds = selectedLocationIdsByLevel[deepestSelectedLevel];
if (selectedIds.length !== locationTreeIds.length) {
return;
}
}
onLocationIdsChange(outputLocationIds);
}, [
deepestSelectedLevel,
locationTreeIds.length,
onLocationIdsChange,
outputIdType,
outputLocationIds,
selectedLocationIdsByLevel,
]);
const updateSelectedLocationIdsByLevel =
(level: LocationLevel) => (locationIds: number[]) => {
setSelectedLocationIdsByLevel((prev) => ({
...prev,
[level]: locationIds,
}));
};
const updateLocationIds = (newLocationIds: number[]) => {
onLocationIdsChange(newLocationIds);
};
const handleChangeRegion = (event: LocationEvent) => {
const { value } = event.target;
const newRegionId = value as string[];
setRegionId(newRegionId);
setCityId([]);
setPlaceId([]);
setFloorId([]);
setRoomId([]);
};
const handleChangeCity = (event: LocationEvent) => {
const { value } = event.target;
const newCityId = value as string[];
setCityId(newCityId);
setPlaceId([]);
setFloorId([]);
setRoomId([]);
};
const handleChangePlace = (event: LocationEvent) => {
const { value } = event.target;
const newPlaceId = value as string[];
setPlaceId(newPlaceId);
setFloorId([]);
setRoomId([]);
};
const handleChangeFloor = (event: LocationEvent) => {
const { value } = event.target;
const newFloorId = value as string[];
setFloorId(newFloorId);
setRoomId([]);
};
const handleChangeRoom = (event: LocationEvent) => {
const { value } = event.target;
const newRoomId = value as string[];
setRoomId(newRoomId);
};
const handleReset = () => {
setRegionId([]);
setCityId([]);
setPlaceId([]);
setFloorId([]);
setRoomId([]);
setSelectedLocationIdsByLevel(EMPTY_SELECTED_LOCATION_IDS);
updateLocationIds([]);
};
const [anchorElement, setAnchorElement] = useState(null);
const handleOpen = (event: any) => {
setAnchorElement(event.currentTarget);
};
const handleClose = (event: any) => {
event.preventDefault();
setAnchorElement(null);
};
const filtersContent = (
<>
<SideFilterSelect
value={regionId}
label={t('region')}
onChange={handleChangeRegion}
onSelectedLocationIdsChange={updateSelectedLocationIdsByLevel('region')}
multiple
isRoot
/>
<SideFilterSelect
value={cityId}
parentIds={regionId}
label={t('city')}
onChange={handleChangeCity}
onSelectedLocationIdsChange={updateSelectedLocationIdsByLevel('city')}
multiple
/>
<SideFilterSelect
value={placeId}
parentIds={cityId}
label={t('place')}
onChange={handleChangePlace}
onSelectedLocationIdsChange={updateSelectedLocationIdsByLevel('place')}
multiple
/>
<SideFilterSelect
value={floorId}
parentIds={placeId}
label={t('floor')}
onChange={handleChangeFloor}
onSelectedLocationIdsChange={updateSelectedLocationIdsByLevel('floor')}
multiple
isFloor
/>
{isRooms && (
<SideFilterSelect
value={roomId}
parentIds={floorId}
label={t('room')}
onChange={handleChangeRoom}
onSelectedLocationIdsChange={updateSelectedLocationIdsByLevel('room')}
multiple
/>
)}
<Button size='small' onClick={handleReset}>
{t('common:buttons.reset')}
</Button>
</>
);
if (!withDropdown) {
return filtersContent;
}
return (
<Badge
invisible={
!regionId?.length &&
!cityId?.length &&
!placeId?.length &&
!floorId?.length &&
!roomId?.length
}
badgeContent={<CheckIcon />}
color='primary'
>
<Dropdown
data-fui-tid={tid}
size='small'
icon={<SortIcon />}
onClickButton={handleOpen}
onClosePopover={handleClose}
zIndexMenu={100}
anchorElement={anchorElement}
>
<Box
display={'flex'}
gridGap={8}
flexDirection={'column'}
p={2}
minWidth={330}
maxWidth={330}
>
{filtersContent}
</Box>
</Dropdown>
</Badge>
);
};