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


import { AppConsts } from '@/shared';
import { PAGINATION_LIMIT_50 } from '@/shared/consts/constants';
import { useComponentHeight } from '@/shared/hooks/useComponentHeight';
import { rootRoute } from '@app/router/routes/root/root';
import { styled } from '@mui/material';
import {
  ArchiveIcon,
  PencilIcon,
  SidebarIcon,
  SortIcon,
} from '@sber-friend/flamingo-icons';
import {
  useArchiveLocationTicketMutation,
  useGetLocationByTreeIdQuery,
  useGetLocationTicketsQuery,
} from '@shared/api/location/client';
import {
  Box,
  Button,
  ColumnsProps,
  Dropdown,
  IconButton,
  Loader,
  Row,
  Search,
  Table,
  Tooltip,
  Typography,
} from '@shared/components';
import { useUpdateSearchParams } from '@shared/hooks';
import { useSetDrawerType } from '@shared/hooks/drawer/useSetDrawerType';
import {
  EventActionTypes,
  useSendClickStreamEvent,
} from '@shared/hooks/useSendClickStreamEvent';
import { DrawerTypes } from '@shared/model/drawer/drawer.types';
import dayjs from 'dayjs';
import { useEffect, type MouseEvent } from 'react';
import { useTranslation } from 'react-i18next';

const LocationTicketsTableStyled = styled(Table)`
  &::-webkit-scrollbar {
    height: 12px !important;
  }
`;

type Props = {
  withCreateTicketButton?: boolean;
};
export const LocationTickets = ({ withCreateTicketButton = true }: Props) => {
  const { t } = useTranslation('common');
  const { sendEvent } = useSendClickStreamEvent(
    EventActionTypes.archiveLocationTicket,
  );

  const { detailedLocationId, ticketSearch, ticketSortType, ticketSortColumn } =
    rootRoute.useSearch<any>();
  const updateSearchParams = useUpdateSearchParams();
  const openDrawer = useSetDrawerType();

  const { data: locationData } = useGetLocationByTreeIdQuery(
    {
      treeId: Number(detailedLocationId),
    },
    { skip: !detailedLocationId },
  );

  const locationId = locationData?.id ?? '';

  const {
    data,
    isLoading: isTicketsLoading,
    isSuccess: isTicketsSuccess,
  } = useGetLocationTicketsQuery(
    {
      locationId: locationId,
      search: ticketSearch,
      sortColumn: ticketSortColumn ?? 'updated_at',
      sortType: ticketSortType ?? 'desc',
    },
    { skip: !locationId },
  );

  const [archiveTicket, { isLoading, isSuccess }] =
    useArchiveLocationTicketMutation();

  useEffect(() => {
    if (isSuccess) {
      sendEvent();
    }
  }, [isSuccess]);

  const { ref, height: tableHeight } = useComponentHeight({
    deps: [isTicketsSuccess],
  });

  if (isTicketsLoading) {
    return <Loader isLoading={isTicketsLoading} size='large' />;
  }

  const items = [
    {
      id: 'desc',
      title: t('sortBy.desc'),
      value: 'desc',
    },
    {
      id: 'asc',
      title: t('sortBy.asc'),
      value: 'asc',
    },
  ];

  const handleCloseMenu = (
    event: MouseEvent<HTMLElement>,
    sortColumn: 'number' | 'archived' | 'deadline',
  ) => {
    updateSearchParams({
      queryObject: {
        ticketSortColumn: sortColumn,
        // @ts-ignore
        ticketSortType: event.target.dataset!.value,
      },
    });
  };
  const columns: ColumnsProps[] = [
    {
      headerName: t('number'),
      field: 'number',
      width: 200,
      minWidth: 200,
      icon: (
        <Dropdown
          variant='text'
          size='smaller'
          disableSelection
          MenuItems={items}
          id='demo-dropdown-31'
          placement='bottom-end'
          onCloseMenuItem={(e) => handleCloseMenu(e, 'number')}
          icon={
            <IconButton size='smaller' aria-label='demo-sort' variant='square'>
              <SortIcon />
            </IconButton>
          }
        />
      ),
    },
    {
      headerName: t('archived'),
      field: 'archived',
      width: 125,
      minWidth: 125,
      icon: (
        <Dropdown
          variant='text'
          size='smaller'
          disableSelection
          MenuItems={items}
          placement='bottom-end'
          onCloseMenuItem={(e) => handleCloseMenu(e, 'archived')}
          icon={
            <IconButton size='smaller' aria-label='demo-sort' variant='square'>
              <SortIcon />
            </IconButton>
          }
        />
      ),
    },
    {
      headerName: t('createdAt'),
      field: 'registredAt',
      width: 160,
      minWidth: 160,
    },
    {
      headerName: t('ticket:deadline'),
      field: 'deadline',
      width: 160,
      minWidth: 160,
      icon: (
        <Dropdown
          variant='text'
          size='smaller'
          disableSelection
          MenuItems={items}
          id='demo-dropdown-31'
          placement='bottom-end'
          onCloseMenuItem={(e) => handleCloseMenu(e, 'deadline')}
          icon={
            <IconButton size='smaller' aria-label='demo-sort' variant='square'>
              <SortIcon />
            </IconButton>
          }
        />
      ),
    },
    { headerName: t('actions'), field: 'actions', width: 160, minWidth: 160 },
  ];

  const rows = data?.tickets?.map((ticket) => {
    console.log(ticket.registeredAt);
    return {
      ...ticket,
      id: ticket.id ?? '',
      registredAt: dayjs(ticket.registeredAt).format(
        AppConsts.Date.format.standard,
      ),
      deadline: dayjs(ticket.deadline).format(AppConsts.Date.format.standard),
      archived: ticket.archived ? t('yes') : t('no'),
      actions: (
        <Row>
          <Tooltip title={t('ticket:ticketInfo')}>
            <IconButton
              color='primary'
              loading={isLoading}
              onClick={() => {
                if (ticket.id) {
                  openDrawer(DrawerTypes.ticketInfo);
                  updateSearchParams({
                    queryObject: {
                      [AppConsts.SEARCH_PARAMS_NAME.TICKET_ID]: ticket?.id,
                    },
                  });
                }
              }}
            >
              <SidebarIcon />
            </IconButton>
          </Tooltip>
          <Tooltip title={t('archive')}>
            <IconButton
              disabled={ticket.archived}
              color='primary'
              loading={isLoading}
              onClick={() => {
                if (ticket.id) {
                  archiveTicket({ ticketId: ticket.id });
                }
              }}
            >
              <ArchiveIcon />
            </IconButton>
          </Tooltip>
          <Tooltip title={t('ticket:editTicket')}>
            <IconButton
              disabled={ticket.archived}
              color='primary'
              onClick={() => {
                updateSearchParams({
                  paramName: AppConsts.SEARCH_PARAMS_NAME.TICKET_ID,
                  value: ticket.id,
                });
                openDrawer(DrawerTypes.ticketUpdate);
              }}
            >
              <PencilIcon />
            </IconButton>
          </Tooltip>
        </Row>
      ),
    };
  });

  const handleSearchTicket = (value: string) =>
    updateSearchParams({
      paramName: AppConsts.SEARCH_PARAMS_NAME.TICKET_SEARCH,
      value,
    });

  const hasActiveTicket = data?.tickets?.some((ticket) => !ticket.archived);

  const handleOpenCreateTicketDrawer = () => {
    openDrawer(DrawerTypes.ticketCreate);
  };

  return (
    <>
      <Search
        placeholder={t('placeholders.search')}
        setValue={handleSearchTicket}
        value={ticketSearch}
        debounce={1000}
        margin='none'
      />

      {withCreateTicketButton && (
        <Button
          disabled={hasActiveTicket}
          onClick={handleOpenCreateTicketDrawer}
        >
          {hasActiveTicket
            ? t('ticket:messages.ticketExists')
            : t('ticket:createTicket')}
        </Button>
      )}

      {isTicketsSuccess && !!data.tickets?.length && (
        <Box ref={ref} style={{ flex: 'auto', overflow: 'auto' }}>
          <LocationTicketsTableStyled
            rows={rows}
            columns={columns}
            stickyHeader
            maxHeight={tableHeight}
            paginationDefaultValue={PAGINATION_LIMIT_50}
          />
        </Box>
      )}
      {isTicketsSuccess && !data.tickets?.length && (
        <Typography variant='h6'>{t('ticket:messages.noTickets')}</Typography>
      )}
    </>
  );
};


export class AppConsts {
  public static Date = {
    format: { Rfc3339: 'YYYY-MM-DDTHH:mm:ss[Z]', standard: 'DD-MM-YYYY HH:mm' },
  };

  public static readonly NOTIFICATIONS_ENABLED_LS_KEY = 'notifications_enabled';

  public static readonly MONITORING_DASHBOARDS_OPEN_LS_KEY =
    'monitoring_dashboards_open';

  public static readonly MONITORING_EQUIPMENT_TABLE_COLUMNS_LS_KEY =
    'monitoring_equipment_table_columns';

  public static readonly EQUIPMENT_TABLE_COLUMNS_LS_KEY =
    'equipment_table_columns';

  private static readonly _DEFAULT_MONITORING_EQUIPMENT_TABLE_COLUMNS: string[] =
    [
      'type',
      'name',
      'status',
      'segment',
      'equipmentModelName',
      'equipmentTypeName',
      'manufacturerTypeName',
      'gveState',
      'tcpState',
      'icmpState',
      'hardwareId',
      'interfaceType',
      'ip',
      'mac',
    ];

  static get DEFAULT_MONITORING_EQUIPMENT_TABLE_COLUMNS(): string[] {
    return this._DEFAULT_MONITORING_EQUIPMENT_TABLE_COLUMNS;
  }

  private static readonly _DEFAULT_EQUIPMENT_TABLE_COLUMNS: string[] = [
    'type',
    'name',
    'pathName',
    'ip',
    'mac',
    'segment',
    'equipmentModelName',
    'equipmentTypeName',
    'manufacturerTypeName',
    'interfaceType',
    'pingStatus',
    'powerState',
    'status',
    'gveState',
    'tcpState',
    'icmpState',
  ];

  static get DEFAULT_EQUIPMENT_TABLE_COLUMNS(): string[] {
    return this._DEFAULT_EQUIPMENT_TABLE_COLUMNS;
  }

  public static readonly LOCATION_TABLE_LIMIT_LS_KEY = 'location_table_limit';

  public static readonly MONITORING_LOCATION_TABLE_LIMIT_LS_KEY =
    'monitoring_location_table_limit';

  public static readonly LOCATION_TABLE_COLUMNS_LS_KEY =
    'location_table_columns';

  private static readonly _DEFAULT_LOCATION_TABLE_COLUMNS: string[] = [
    'name',
    'pathName',
    'roomState',
    'equipmentsNumber',
    'serviceStatus',
    'status',
    'ticket',
  ];

  static get DEFAULT_LOCATION_TABLE_COLUMNS(): string[] {
    return this._DEFAULT_LOCATION_TABLE_COLUMNS;
  }

  public static readonly LOCATION_PAGE_TABLE_COLUMNS_LS_KEY =
    'location_page_table_columns';

  private static readonly _DEFAULT_LOCATION_PAGE_TABLE_COLUMNS: string[] = [
    'name',
    'pathName',
    'equipmentsNumber',
    'serviceStatus',
    'status',
  ];

  static get DEFAULT_LOCATION_PAGE_TABLE_COLUMNS(): string[] {
    return this._DEFAULT_LOCATION_PAGE_TABLE_COLUMNS;
  }

  public static readonly MONITORING_DEVICE_FIELDS_LS_KEY =
    'monitoring_device_fields';

  private static readonly _DEFAULT_MONITORING_DEVICE_FIELDS: string[] = [
    'name',
    'typeName',
    'hardwareId',
    'manufacturerName',
    'modelName',
    'ip',
    'interface',
    'status',
    'powerState',
  ];

  static get DEFAULT_MONITORING_DEVICE_FIELDS(): string[] {
    return this._DEFAULT_MONITORING_DEVICE_FIELDS;
  }

  private static readonly _SEARCH_PARAMS_NAME = {
    EDIT_COMMENT: 'editComment',
    COMMENT_ID: 'commentId',
    DETAILED_LOCATION_ID: 'detailedLocationId',
    LOCATION_ID: 'treeId',
    IS_ROOM_ID: 'isRoomId',
    LIMIT: 'limit',
    PAGE: 'page',
    SORT_COLUMN: 'sortColumn',
    SORT_TYPE: 'sortType',
    SEARCH: 'search',
    SEGMENT: 'segment',
    REGION_ID: 'regionId',
    CITY_ID: 'cityId',
    PLACE_ID: 'placeId',
    FLOOR_ID: 'floorId',
    HAS_TICKETS: 'hasTickets',
    TICKET_ID: 'ticketId',
    TICKET_SEARCH: 'ticketSearch',
  };

  public static THOUSAND = 1000;

  public static START_OF_DAY = new Date().setHours(0, 0, 0, 0) / 1000;

  static get SEARCH_PARAMS_NAME() {
    return this._SEARCH_PARAMS_NAME;
  }

  public static readonly _SIP_URI_REGEX =
    /^(?<user>[^@;]+)@(?<domain>[^;]+)(?:;(?<params>.*))?$/;

  static get SIP_URI_REGEX() {
    return this._SIP_URI_REGEX;
  }

  public static auditMessageTypes: Record<string, string> = {
    sign_in: 'Вход',
    log_out: 'Выход',
    user_register: 'Регистрация пользователя',
    user_update: 'Обновление пользователя',
    user_delete: 'Удаление пользователя',
    manufacturer_create: 'Создание производителя',
    manufacturer_update: 'Обновление производителя',
    manufacturer_delete: 'Удаление производителя',
    controller_create: 'Создание контроллера',
    controller_update: 'Обновление контроллера',
    controller_delete: 'Удаления контроллера',
    controller_model_create: 'Создание модели контроллера',
    controller_model_update: 'Обновление модели контроллера',
    controller_model_delete: 'Удаление модели контроллера',
    controller_type_create: 'Создание типа контроллера',
    controller_type_update: 'Обновление типа контроллера',
    controller_type_delete: 'Удаление типа контроллера',
    device_create: 'Создание устройства',
    device_update: 'Обновление устройства',
    device_delete: 'Удаление устройства',
    device_type_create: 'Создание типа устройства',
    device_type_update: 'Обновление типа устройства',
    device_type_delete: 'Удаление типа устройства',
    device_model_create: 'Создание модели устройства',
    device_model_update: 'Обновление модели устройства',
    device_model_delete: 'Удаление модели устройства',
    location_create: 'Создание локации',
    location_update: 'Обновление локации',
    location_delete: 'Удаление локации',
    location_note_create: 'Создание заметки локации',
    location_note_update: 'Обновление заметки локации',
    location_note_delete: 'Удаление заметки локации',
    location_ticket_create: 'Создание заявки для локации',
    location_ticket_update: 'Обновление заявки для локации',
    location_ticket_delete: 'Удаление заявки для локации',
  };
}

{
    "tickets": [
        {
            "id": "2fc48d7e-13a4-4477-9712-b344253bfe49",
            "number": "12321",
            "registeredAt": "2026-05-04T08:52:21Z",
            "deadline": "2026-05-13T09:13:00Z",
            "locationId": "a15b668a-403a-4b66-8eda-f67aec80d3f0",
            "archived": true,
            "description": "1121",
            "contractor": {
                "id": "442e09e3-b5d8-4664-af62-58946fc09b93",
                "name": "Atm",
                "description": ""
            }
        },
        {
            "id": "63b7b18a-467b-4cb0-9c9f-b69174c5c118",
            "number": "difjui",
            "registeredAt": "2026-05-07T11:13:09Z",
            "deadline": "2026-05-21T11:20:00Z",
            "locationId": "a15b668a-403a-4b66-8eda-f67aec80d3f0",
            "archived": true,
            "description": "dusfidsjui",
            "contractor": null
        },
        {
            "id": "cc157eb6-3c80-43c6-877d-30f2c5a27d77",
            "number": "fjsvijsdnvi",
            "registeredAt": "2026-05-07T11:22:11Z",
            "deadline": "2026-05-14T11:22:00Z",
            "locationId": "a15b668a-403a-4b66-8eda-f67aec80d3f0",
            "archived": false,
            "description": "ufidnidsn",
            "contractor": null
        }
    ],
    "total": 3
}