Загрузка данных
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>
</>
);
};