Загрузка данных
import { LocationFilters } from '@/features/filters/LocationFilters/LocationFilters';
import { ModelSelect } from '@/features/Settings/selects';
import { EquipmentModel } from '@/features/Settings/selects/lib';
import { AppConsts } from '@/shared';
import {
Button,
Column,
DateTimeField,
IconButton,
Row,
Select,
TextField,
Typography,
} from '@/shared/components';
import { SearchSelect } from '@/shared/components/SearchSelect';
import { ChevronsDownIcon, ChevronsUpIcon } from '@sber-friend/flamingo-icons';
import { useTranslation } from 'react-i18next';
import { useAuditSideFilter } from '../controller';
export const AuditSideFilter = () => {
const { t } = useTranslation('common');
const {
searchParams,
isFetchingUsers,
userOptions,
typeOptions,
smartRoomId,
smartRoomIdError,
modelIds,
cityId,
floorId,
placeId,
regionId,
roomId,
locationIds,
isLocationFiltersOpen,
setSearchUser,
resetFilters,
refetchQuery,
handleChangeTimeFrom,
handleChange,
handleChangeTimeMax,
handleChangeUser,
handleSmartRoomIdChange,
handleLocationFiltersToggle,
handleChangeModels,
setRegionId,
setCityId,
setPlaceId,
setFloorId,
setRoomId,
changeLocationIds,
} = useAuditSideFilter();
return (
<Column minWidth={330} maxWidth={330} gridGap={16}>
<DateTimeField
label={t('timeFrom')}
onChange={handleChangeTimeFrom}
margin='none'
value={searchParams.tmMin && searchParams.tmMin * AppConsts.THOUSAND}
/>
<DateTimeField
label={t('timeTo')}
margin='none'
onChange={handleChangeTimeMax}
value={searchParams.tmMax && searchParams.tmMax * AppConsts.THOUSAND}
/>
<Select
label={t('type')}
onChange={handleChange('type')}
options={typeOptions}
value={searchParams.type}
/>
<SearchSelect
label={t('user')}
placeholder={t('user:placeholders.searchByFullName')}
loading={isFetchingUsers}
value={searchParams?.userId || ''}
setValue={setSearchUser}
options={userOptions}
onChangeCallback={handleChangeUser}
/>
<ModelSelect
multiple
isCreateButton={false}
type={EquipmentModel.device}
value={modelIds}
onChange={handleChangeModels}
/>
<Select
label={t('source')}
onChange={handleChange('source')}
value={searchParams.source}
options={[
{ label: t('all'), value: undefined },
{ label: t('monitoring'), value: 'smartroom-monitoring' },
{ label: t('authentication'), value: 'smartroom-auth' },
{
label: t('notifications:notifications'),
value: 'smartroom-notifications',
},
]}
/>
<TextField
label='Smartroom ID'
value={smartRoomId}
onChange={handleSmartRoomIdChange}
error={smartRoomIdError}
helperText={smartRoomIdError ? t('messages.invalidUUIDMessage') : ''}
/>
<Row justifyContent='space-between'>
<Typography variant='h4'>Поиск локации</Typography>
<IconButton onClick={handleLocationFiltersToggle}>
{isLocationFiltersOpen ? <ChevronsUpIcon /> : <ChevronsDownIcon />}
</IconButton>
</Row>
{isLocationFiltersOpen && (
<Column gridGap={16}>
<LocationFilters
isRooms
regionId={regionId}
setRegionId={setRegionId}
cityId={cityId}
setCityId={setCityId}
placeId={placeId}
setPlaceId={setPlaceId}
floorId={floorId}
setFloorId={setFloorId}
roomId={roomId}
setRoomId={setRoomId}
ids={locationIds || []}
withDropdown={false}
onLocationIdsChange={changeLocationIds}
/>
</Column>
)}
<Column gridGap={8}>
<Button onClick={refetchQuery} color='primary'>
{t('buttons.update')}
</Button>
<Button onClick={resetFilters} color='error'>
{t('buttons.reset')}
</Button>
</Column>
</Column>
);
};
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, useState } from 'react';
import { useTranslation } from 'react-i18next';
//TO DO вынести в отдельный файл с типами
type LocationEvent = ChangeEvent<{ name?: string; value: unknown }>;
type LocationFiltersProps = {
'data-fui-tid'?: string;
withDropdown?: boolean;
isRooms?: boolean;
ids?: number[];
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;
};
// 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,
}: LocationFiltersProps) => {
const { t } = useTranslation('locations');
useEffect(() => {
if (ids?.length === 0) {
setRegionId([]);
setCityId([]);
setPlaceId([]);
setFloorId([]);
setRoomId([]);
}
}, [ids]);
const locationIds =
roomId?.length > 0
? roomId
: floorId?.length > 0
? floorId
: placeId?.length > 0
? placeId
: cityId?.length > 0
? cityId
: regionId;
useEffect(() => {
if (locationIds?.length >= 0) {
onLocationIdsChange(Helpers.convertStringsToNumbers(locationIds));
}
}, [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([]);
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}
multiple
isRoot
/>
<SideFilterSelect
value={cityId}
parentIds={regionId}
label={t('city')}
onChange={handleChangeCity}
multiple
/>
<SideFilterSelect
value={placeId}
parentIds={cityId}
label={t('place')}
onChange={handleChangePlace}
multiple
/>
<SideFilterSelect
value={floorId}
parentIds={placeId}
label={t('floor')}
onChange={handleChangeFloor}
multiple
isFloor
/>
{isRooms && (
<SideFilterSelect
value={roomId}
parentIds={floorId}
label={t('room')}
onChange={handleChangeRoom}
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>
);
};
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 { useState } from 'react';
export type SideFilterSelectProps = {
value: string[] | string;
onChange: SelectProps['onChange'];
parentIds?: string[] | string;
isRoot?: boolean;
isFloor?: boolean;
} & Omit<SelectProps, 'onChange' | 'value' | 'options'>;
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}`;
};
export const SideFilterSelect = ({
value,
onChange,
disabled,
label,
parentIds,
isRoot = false,
isFloor = false,
...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 innerOption = location?.map((option) => ({
value: String(option.treeId),
label: getLocationLabel(option, parentIds),
}));
return (
<Select
label={label}
options={innerOption}
value={value}
onChange={onChange}
disabled={disabled || (!isRoot && !location.length)}
{...rest}
/>
);
};
import { Location, LocationsGetResponse } from '@/shared/api/location/client';
import { DispatchStateAction } from '@components/types';
import { QueryStatus } from '@reduxjs/toolkit/dist/query/react';
import hash from 'object-hash';
import { useEffect } from 'react';
import { LocationFilterListLib } from './LocationFilterListLib';
export const useSetLocation = ({
entity,
status,
setter,
isFloor,
deps = [],
}: {
status: QueryStatus;
entity: LocationsGetResponse | undefined;
setter: DispatchStateAction<Location[]>;
isFloor?: boolean;
deps?: unknown[];
}) => {
useEffect(() => {
if (entity && status === QueryStatus.fulfilled) {
const sortedData = (() =>
isFloor
? LocationFilterListLib.sortFloors(entity)
: LocationFilterListLib.sortByName(entity))();
setter(sortedData);
}
}, [hash(entity ?? {}), status, ...(<any[]>deps)]);
};
export const useResetLocationFilterSelect = ({
setter,
ids,
}: {
setter: DispatchStateAction<Location[]>;
ids: string[] | string;
}) =>
useEffect(() => {
if (!ids || !ids.length) {
setter([]);
}
}, [ids, setter]);
import { Location, LocationsGetResponse } from '@/shared/api/location/client';
import { DispatchStateAction } from '@components/types';
import { QueryStatus } from '@reduxjs/toolkit/dist/query/react';
import hash from 'object-hash';
import { useEffect } from 'react';
import { LocationFilterListLib } from './LocationFilterListLib';
export const useSetLocation = ({
entity,
status,
setter,
isFloor,
deps = [],
}: {
status: QueryStatus;
entity: LocationsGetResponse | undefined;
setter: DispatchStateAction<Location[]>;
isFloor?: boolean;
deps?: unknown[];
}) => {
useEffect(() => {
if (entity && status === QueryStatus.fulfilled) {
const sortedData = (() =>
isFloor
? LocationFilterListLib.sortFloors(entity)
: LocationFilterListLib.sortByName(entity))();
setter(sortedData);
}
}, [hash(entity ?? {}), status, ...(<any[]>deps)]);
};
export const useResetLocationFilterSelect = ({
setter,
ids,
}: {
setter: DispatchStateAction<Location[]>;
ids: string[] | string;
}) =>
useEffect(() => {
if (!ids || !ids.length) {
setter([]);
}
}, [ids, setter]);