Загрузка данных


import { WithLocationFilter } from '@/app/router/queries';
import { type AuditRouteSearch } from '@/app/router/routes/audit';
import { useCallback } from 'react';

type Params = {
  searchParams: AuditRouteSearch;
  updateParamAndResetPage: (paramName: string, value: unknown) => void;
};

export const useAuditLocationFilters = ({
  searchParams,
  updateParamAndResetPage,
}: Params) => {
  const setLocationStringArray = useCallback(
    (paramName: string, value: string[]) => {
      updateParamAndResetPage(paramName, value);
    },
    [updateParamAndResetPage],
  );

  const setRegionId = useCallback(
    (value: string[]) =>
      setLocationStringArray(
        WithLocationFilter.WITH_LOCATION_FILTER_QUERY.regionId,
        value,
      ),
    [setLocationStringArray],
  );

  const setCityId = useCallback(
    (value: string[]) =>
      setLocationStringArray(
        WithLocationFilter.WITH_LOCATION_FILTER_QUERY.cityId,
        value,
      ),
    [setLocationStringArray],
  );

  const setPlaceId = useCallback(
    (value: string[]) =>
      setLocationStringArray(
        WithLocationFilter.WITH_LOCATION_FILTER_QUERY.placeId,
        value,
      ),
    [setLocationStringArray],
  );

  const setFloorId = useCallback(
    (value: string[]) =>
      setLocationStringArray(
        WithLocationFilter.WITH_LOCATION_FILTER_QUERY.floorId,
        value,
      ),
    [setLocationStringArray],
  );

  const setRoomId = useCallback(
    (value: string[]) =>
      setLocationStringArray(
        WithLocationFilter.WITH_LOCATION_FILTER_QUERY.roomId,
        value,
      ),
    [setLocationStringArray],
  );

  const changeLocationIds = useCallback(
    (value: string[]) => {
      updateParamAndResetPage('locationIds', value);
    },
    [updateParamAndResetPage],
  );

  return {
    locationIds: searchParams.locationIds,
    cityId: searchParams.cityId,
    floorId: searchParams.floorId,
    placeId: searchParams.placeId,
    regionId: searchParams.regionId,
    roomId: searchParams.roomId,
    setRegionId,
    setCityId,
    setPlaceId,
    setFloorId,
    setRoomId,
    changeLocationIds,
  };
};


import { type AuditRouteSearch } from '@/app/router/routes/audit';
import { useGetApiV1MessageTypesQuery } from '@/shared/api/audit/client';
import {
  useGetUserByIdQuery,
  useGetUsersQuery,
} from '@/shared/api/user/client';
import { useEffect, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { AuditLib } from '../../lib/AuditLib';
import { AUDIT_TYPE_ORDER } from './messagesOrder';

export const useAuditOptions = (searchParams: AuditRouteSearch) => {
  const { t } = useTranslation('common');
  const [searchUser, setSearchUser] = useState('');
  const hasFetchedUser = useRef(false);

  const { data: auditTypesData } = useGetApiV1MessageTypesQuery();

  const { data: usersData, isFetching: isFetchingUsers } = useGetUsersQuery({
    limit: 10,
    sortType: 'asc',
    sortColumn: 'full_name',
    search: searchUser,
  });

  const { data: user } = useGetUserByIdQuery(
    { userId: searchParams.userId ?? '' },
    { skip: false },
  );

  useEffect(() => {
    if (searchParams.userId && user) {
      hasFetchedUser.current = true;
    }
  }, [searchParams.userId, user]);

  const typeOptions = useMemo(() => {
    const types = auditTypesData?.types;

    if (!Array.isArray(types)) {
      return [];
    }

    const typeSet = new Set<string>(types);
    const orderedTypes = AUDIT_TYPE_ORDER.filter((type) => typeSet.has(type));
    const unknownTypes = types.filter(
      (type) =>
        !AUDIT_TYPE_ORDER.includes(type as (typeof AUDIT_TYPE_ORDER)[number]),
    );

    return [
      { label: t('all'), value: undefined },
      ...[...orderedTypes, ...unknownTypes].map((type) => ({
        value: type,
        label: t(`audit:${type}`, { defaultValue: type }),
      })),
    ];
  }, [auditTypesData, t]);

  const userOptions = useMemo(() => {
    const options = usersData?.users
      ? AuditLib.mapUsersToOptions(usersData.users)
      : [];

    if (!searchParams.userId || !user) {
      return options;
    }

    return [...options, { label: user.fullName!, value: user.id! }];
  }, [searchParams.userId, user, usersData]);

  return {
    searchUser,
    setSearchUser,
    hasFetchedUser,
    isFetchingUsers,
    typeOptions,
    userOptions,
  };
};


import { useUpdateSearchParams } from '@/shared/hooks';
import { useCallback } from 'react';

export const useAuditSearchUpdater = () => {
  const updateSearchParams = useUpdateSearchParams();

  const resetPage = useCallback(() => {
    updateSearchParams({ paramName: 'offset', value: 0 });
  }, [updateSearchParams]);

  const updateParamAndResetPage = useCallback(
    (paramName: string, value: unknown) => {
      updateSearchParams({ paramName, value });
      resetPage();
    },
    [resetPage, updateSearchParams],
  );

  return {
    updateSearchParams,
    resetPage,
    updateParamAndResetPage,
  };
};


import { auditRoute } from '@/app/router/routes/audit';
import { AppConsts } from '@/shared';
import { useGetApiV1MessageQuery } from '@/shared/api/audit/client';
import { RouterConsts } from '@/shared/consts/RouterConsts';
import { ChangeEvent, useCallback, useState } from 'react';
import { useAuditLocationFilters } from './useAuditLocationFilters';
import { useAuditOptions } from './useAuditOptions';
import { useAuditSearchUpdater } from './useAuditSearchUpdater';
import { useSmartRoomIdFilter } from './useAuditSmartRoomIdFilter';

type ModelSelectValue = string | number | boolean | null;

export const useAuditSideFilter = () => {
  const searchParams = auditRoute.useSearch();

  const { refetch } = useGetApiV1MessageQuery(searchParams);
  const { updateSearchParams, updateParamAndResetPage } =
    useAuditSearchUpdater();

  const [isLocationFiltersOpen, setIsLocationFiltersOpen] = useState(false);

  const auditOptions = useAuditOptions(searchParams);

  const smartRoomFilter = useSmartRoomIdFilter({
    initialValue: searchParams.smartroomId,
    updateParamAndResetPage,
  });

  const locationFilters = useAuditLocationFilters({
    searchParams,
    updateParamAndResetPage,
  });

  const handleChange =
    (paramName: string) =>
    (event: ChangeEvent<{ name?: string | undefined; value: unknown }>) => {
      updateParamAndResetPage(paramName, event.target.value);
    };

  const handleChangeUser = useCallback(
    (value: string) => {
      updateParamAndResetPage('userId', value);
    },
    [updateParamAndResetPage],
  );

  const handleChangeTimeFrom = useCallback(
    (time: Date) => {
      updateParamAndResetPage(
        'tmMin',
        new Date(time).getTime() / AppConsts.THOUSAND,
      );
    },
    [updateParamAndResetPage],
  );

  const handleChangeTimeMax = useCallback(
    (time: Date) => {
      updateParamAndResetPage(
        'tmMax',
        new Date(time).getTime() / AppConsts.THOUSAND,
      );
    },
    [updateParamAndResetPage],
  );

  const handleMacChange = useCallback(
    (event: ChangeEvent<HTMLInputElement>) => {
      updateParamAndResetPage('mac', event.target.value || undefined);
    },
    [updateParamAndResetPage],
  );

  const handleIpChange = useCallback(
    (event: ChangeEvent<HTMLInputElement>) => {
      updateParamAndResetPage('ip', event.target.value || undefined);
    },
    [updateParamAndResetPage],
  );

  const handleChangeModels = useCallback(
    (value: ModelSelectValue[]) => {
      updateParamAndResetPage('modelIds', value.map(String));
    },
    [updateParamAndResetPage],
  );

  const handleLocationFiltersToggle = useCallback(() => {
    setIsLocationFiltersOpen((prev) => !prev);
  }, []);

  const resetFilters = useCallback(() => {
    updateSearchParams({
      queryObject: { ...RouterConsts.AUDIT_DEFAULT_QUERY },
    });
    smartRoomFilter.resetSmartRoomId();
  }, [smartRoomFilter, updateSearchParams]);

  const refetchQuery = useCallback(() => {
    refetch();
  }, [refetch]);

  return {
    searchParams,
    isLocationFiltersOpen,

    ...auditOptions,
    ...smartRoomFilter,
    ...locationFilters,

    modelIds: searchParams.modelIds,

    refetchQuery,
    resetFilters,
    handleChange,
    handleChangeUser,
    handleChangeTimeFrom,
    handleChangeTimeMax,
    handleMacChange,
    handleIpChange,
    handleLocationFiltersToggle,
    handleChangeModels,
  };
};


import { Helpers } from '@/shared/lib';
import { ChangeEvent, useCallback, useEffect, useState } from 'react';

type Params = {
  initialValue?: string;
  updateParamAndResetPage: (paramName: string, value: unknown) => void;
};

export const useSmartRoomIdFilter = ({
  initialValue,
  updateParamAndResetPage,
}: Params) => {
  const [smartRoomId, setSmartRoomId] = useState(initialValue || '');
  const [smartRoomIdError, setSmartRoomIdError] = useState(false);

  useEffect(() => {
    const valid = Helpers.isUUID(smartRoomId);

    setSmartRoomIdError(smartRoomId !== '' && !valid);
    updateParamAndResetPage(
      'smartroomId',
      valid || smartRoomId === '' ? smartRoomId : '',
    );
  }, [smartRoomId, updateParamAndResetPage]);

  const handleSmartRoomIdChange = useCallback(
    (event: ChangeEvent<HTMLInputElement>) => {
      setSmartRoomId(event.target.value);
    },
    [],
  );

  const resetSmartRoomId = useCallback(() => {
    setSmartRoomId('');
    setSmartRoomIdError(false);
  }, []);

  return {
    smartRoomId,
    smartRoomIdError,
    handleSmartRoomIdChange,
    resetSmartRoomId,
  };
};



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
            outputIdType='id'
            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>
  );
};