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


import dayjs, { Dayjs } from 'dayjs';
import React, { useEffect, useRef, useState } from 'react';

import { frontExceptionController } from '@api/controllers/frontExceptionController';
import api from '@api/index';
import { ResizableColumnType } from '@components/Table/types/columns';
import { standartDateFormat } from '@configs/standartDateFormat';

export interface FixingData extends Record<string, unknown> {
  caption: number | null;
  changed: number | null;
  high: number;
  last: number;
  low: number;
  openValue: number;
  prevPrice: number | null;
  ticker: string;
  tradeDate: string;
}

interface UseFixingsFacadeResult {
  selectedFixing: FixingData[];
  filteredSelectedFixing: FixingData[];
  fixingsData?: FixingData[];
  selectedRow: FixingData | null;
  onRowClick: (record: FixingData) => void;
  onCloseButtonClick: () => void;
  onChangeDate: (date: Dayjs | null, from: boolean) => void;
  fromDate: string;
  toDate: string;
  columns: ResizableColumnType<FixingData>[];
}

const fixingsColumnsDefault: ResizableColumnType<FixingData>[] = [
  {
    key: 'caption',
    dataIndex: 'caption',
    title: 'Наименование',
    type: 'string',
    align: 'left',
    hidden: false,
    width: 100,
    position: 0,
  },
  {
    key: 'ticker',
    dataIndex: 'ticker',
    title: 'Идентификатор',
    type: 'string',
    align: 'left',
    hidden: false,
    width: 100,
    position: 1,
  },
  {
    key: 'openValue',
    dataIndex: 'openValue',
    title: 'Значение',
    type: 'number',
    align: 'right',
    numbersFormatter: true,
    hidden: false,
    width: 100,
    position: 2,
  },
  {
    key: 'tradeDate',
    dataIndex: 'tradeDate',
    title: 'Дата и время обновления',
    type: 'date',
    align: 'left',
    hidden: false,
    width: 100,
    position: 3,
    render: (text: string) => ({
      children: (
        <div style={{ marginLeft: 'auto' }}>
          {text ? dayjs(text).format(standartDateFormat.dateTimeFormat) : ''}
          {text && <span style={{ color: '#868479' }}> МСК</span>}
        </div>
      ),
    }),
  },
];

export const useFixingsFacade = function (): UseFixingsFacadeResult {
  const [selectedFixing, setSelectedFixing] = useState<FixingData[]>([]);
  const [selectedRow, setSelectedRow] = useState<FixingData | null>(null);
  const [filteredSelectedFixing, setFilteredSelectedFixing] = useState<FixingData[]>([]);
  const [fixingsData, setFixingsData] = useState<FixingData[]>();
  const [fromDate, setFromDate] = useState<string>(new Date('01-01-2018').toString());
  const [toDate, setToDate] = useState<string>(new Date().toString());

  const updateFixings = () => {
    api.getFixingsList().then((res) => {
      const { data, status } = res;

      if (!!data && !Array.isArray(data)) {
        const serializedStrangeDataSource = JSON.stringify(data);

        frontExceptionController.handleException({
          name: 'FIXINGS_STRANGE_DATA',
          message: `Неожиданное значение в данных. Статус: ${status}. Данные: ${serializedStrangeDataSource}`,
        });
      }

      setFixingsData(data);
    });
  };

  const [customInterval, setCustomInterval] = useState<undefined | number>(undefined);
  const timerId = useRef<NodeJS.Timeout>();

  const onFixingsDataUpdate = () => {
    const gmtPlus3Time = new Date().toLocaleString('en-US', {
      timeZone: 'Europe/Moscow',
    });
    const time = gmtPlus3Time.split(',')[1].split(':');
    const intervalCount =
      gmtPlus3Time.includes('PM') &&
      ((time[0].trim() === '12' && Number(time[1]) >= 30) ||
        (time[0].trim() === '1' && Number(time[1]) <= 30))
        ? 60000
        : 3600000;

    setCustomInterval(intervalCount);
  };

  useEffect(() => {
    clearInterval(timerId.current);

    if (customInterval) {
      timerId.current = setInterval(updateFixings, customInterval);
    }
  }, [customInterval]);

  useEffect(() => {
    updateFixings();
    setInterval(onFixingsDataUpdate, 60000);

    return () => {
      clearInterval(timerId.current);
    };
  }, []);

  const onChangeDate = (date: Dayjs | null, from: boolean) => {
    if (from) {
      setFromDate(date ? dayjs(date).format('YYYY-MM-DD') : '');
    } else {
      setToDate(date ? dayjs(date).format('YYYY-MM-DD') : '');
    }
  };

  useEffect(() => {
    if (fromDate === '' && toDate === '') {
      setFilteredSelectedFixing(selectedFixing);
    }

    if (fromDate !== '' && toDate !== '') {
      const filteredData = selectedFixing.filter(
        (item) =>
          new Date(item.tradeDate).getTime() > new Date(fromDate).getTime() &&
          new Date(item.tradeDate).getTime() < new Date(toDate).getTime(),
      );

      setFilteredSelectedFixing(filteredData);
    }
  }, [fromDate, toDate]);

  useEffect(() => {
    if (selectedFixing) {
      setFilteredSelectedFixing(selectedFixing);
    }
  }, [selectedFixing]);

  const onRowClick = (record: FixingData) => {
    setSelectedRow(record);

    api.getFixingById(record.ticker).then((res) => {
      setSelectedFixing(res.data);
    });
  };

  const onCloseButtonClick = () => {
    setSelectedFixing([]);
    setSelectedRow(null);
    setFromDate(new Date('01-01-2018').toString());
    setToDate(new Date().toString());
  };

  return {
    selectedFixing,
    filteredSelectedFixing,
    fixingsData,
    selectedRow,
    onRowClick,
    onCloseButtonClick,
    onChangeDate,
    fromDate,
    toDate,
    columns: fixingsColumnsDefault,
  };
};

















import { Dropdown, Form } from 'antd';
import type { MenuProps } from 'antd';
import locale from 'antd/es/date-picker/locale/ru_RU';
import dayjs from 'dayjs';
import 'dayjs/locale/ru';
import React, { FC, useState } from 'react';

import { DatePickerLight } from '@components/DatePicker';
import { RoundedClose } from '@components/Icons/RoundedClose';
import { Table, useTableController } from '@components/Table';
import type { ResizableColumnType } from '@components/Table/types/columns';
import { TextRegular } from '@components/TextMad';
import { Footnote } from '@components/TextUI';
import WidgetContentWrapper from '@components/WidgetContentWrapper';
import WidgetHeader from '@components/WidgetHeader';
import features from '@features';
import { useSettingsComponent } from '@hooks/table/useSettingsComponent';
import { Button } from '@uikit/Button';
import { Icon } from '@uikit/Icon';
import { FixingsValuesChart } from '@widgets/Fixings/FixingsValuesChart';
import { FixingsValuesTable } from '@widgets/Fixings/FixingsValuesTable';
import { FixingData, useFixingsFacade } from '@widgets/Fixings/useFixingsFacade';

import { WidgetContentBasicProps } from 'types/Widgets';

import './fixings.scss';

dayjs.locale('ru');

export interface FixingsProps {
  state: {
    firstColumns: ResizableColumnType<FixingData>[];
  };
}

type WidgetFixingsProps = WidgetContentBasicProps<FixingsProps>;

export const Fixings: FC<WidgetFixingsProps> = function (
  props: WidgetFixingsProps,
): React.JSX.Element {
  const {
    selectedFixing,
    filteredSelectedFixing,
    selectedRow,
    fixingsData,
    fromDate,
    toDate,
    onCloseButtonClick,
    onChangeDate,
    columns,
  } = useFixingsFacade();

  const [controller, requester] = useTableController<FixingData>();
  const [activeOption, setActiveOption] = useState('table');
  const { settingsComponent, setSettingsComponent } = useSettingsComponent();

  const dropdownItems = [
    {
      label: (
        <div className="fixing-button">
          <div className="icon">
            <Icon variant="file-up" />
          </div>
          Экспортировать в Excel
        </div>
      ),
      key: '1',
    },
  ] as MenuProps;

  const settingsItems = [
    {
      key: '1',
      label: settingsComponent,
    },
  ];

  const { widgetId } = props;

  return (
    <>
      <WidgetHeader
        showWidgetSetting
        items={settingsItems}
        titleExtra={
          selectedRow && (
            <Footnote style={{ display: 'flex' }}>
              {selectedRow.ticker}
            </Footnote>
          )
        }
        handlerSaveAsExcel={features.hideSaveAsExcel ? undefined : controller?.saveAsExcel}
        {...props}
      />
      <WidgetContentWrapper
        {...props}
        scrollable={false}
      >
        {selectedRow && (
          <div className="header">
            <div className="header-btn-row">
              <Button
                variant={activeOption === 'table' ? 'filled-primary' : 'outlined-secondary'}
                onClick={() => setActiveOption('table')}
                text="Таблица"
              />
              <Button
                variant={activeOption === 'chart' ? 'filled-primary' : 'outlined-secondary'}
                onClick={() => setActiveOption('chart')}
                text="График"
              />
            </div>
            {selectedRow && (
              <div className="header-daterange">
                <Form.Item label="Значения с">
                  <DatePickerLight
                    className="header-daterange-datepicker"
                    locale={locale}
                    format="DD-MM-YYYY"
                    onChange={(date) => onChangeDate(date, true)}
                    defaultValue={dayjs(fromDate)}
                  />
                </Form.Item>
                <Form.Item label="до">
                  <DatePickerLight
                    className="header-daterange-datepicker"
                    locale={locale}
                    format="DD-MM-YYYY"
                    onChange={(date) => onChangeDate(date, false)}
                    defaultValue={dayjs(toDate)}
                  />
                </Form.Item>
              </div>
            )}
            <div
              onClick={onCloseButtonClick}
              className="icon"
            >
              <RoundedClose />
            </div>
          </div>
        )}

        {!selectedRow && (
          <div className="fixing">
            <Table<FixingData>
              widgetId={widgetId}
              columns={columns}
              dataSource={fixingsData}
              setSettingsComponent={setSettingsComponent}
              setController={requester}
            />
          </div>
        )}

        {selectedRow && activeOption === 'table' && (
          <Dropdown
            menu={dropdownItems}
            trigger={['contextMenu']}
          >
            <div>
              <FixingsValuesTable
                fixingSelectedValue={filteredSelectedFixing || selectedFixing}
                widgetId={widgetId}
              />
            </div>
          </Dropdown>
        )}

        {selectedRow && activeOption === 'chart' && (
          <>
            <TextRegular className="fixing-title">
              {selectedRow.caption}
            </TextRegular>
            <FixingsValuesChart fixingSelectedValue={filteredSelectedFixing || selectedFixing} />
          </>
        )}
      </WidgetContentWrapper>
    </>
  );
};