From 9bd8a2a015eef56e1b7ea23551b28775f42f5e60 Mon Sep 17 00:00:00 2001 From: Grog Date: Wed, 21 Oct 2026 10:51:13 +0300 Subject: [PATCH] added chess table --- web/src/App.css | 6 + web/src/api/client.ts | 12 +- .../TreeFilterDropdown/TreeFilterDropdown.css | 3 + .../TreeFilterDropdown/TreeFilterDropdown.tsx | 149 +++++++++++ web/src/services/consodilation/chess.ts | 22 +- web/src/types/chess/chess.ts | 11 +- .../ConsolidatedChessContent.css | 6 - .../ConsolidatedChessContent.tsx | 219 +++++++++------ .../ConsolidatedChessTable.css | 19 ++ .../ConsolidationChessTable.tsx | 183 +++++++++++++ .../ConsolidationChessTableFilter.tsx | 253 ++++++++++++++++++ 11 files changed, 786 insertions(+), 97 deletions(-) create mode 100644 web/src/components/TreeFilterDropdown/TreeFilterDropdown.css create mode 100644 web/src/components/TreeFilterDropdown/TreeFilterDropdown.tsx delete mode 100644 web/src/widgets/ConsolidatedChessContent/ConsolidatedChessContent.css create mode 100644 web/src/widgets/ConsolidationChessTable/ConsolidatedChessTable.css create mode 100644 web/src/widgets/ConsolidationChessTable/ConsolidationChessTable.tsx create mode 100644 web/src/widgets/ConsolidationChessTable/ConsolidationChessTableFilter.tsx diff --git a/web/src/App.css b/web/src/App.css index e69de29..8dd8ac0 100644 --- a/web/src/App.css +++ b/web/src/App.css @@ -0,0 +1,6 @@ +.ant-select-selection-item{ + background-color: #ecfdf3 !important; + color: #1f5f35 !important; + box-shadow: inset 0 0 0 1px #2ea64b38 !important; + border-radius: 999px !important; +} \ No newline at end of file diff --git a/web/src/api/client.ts b/web/src/api/client.ts index e6522d2..5966bd4 100644 --- a/web/src/api/client.ts +++ b/web/src/api/client.ts @@ -127,10 +127,16 @@ export async function apiFetch(path: string, init: RequestInit = {}): Promise } /** Бинарный ответ (скан, xlsx) с токеном и тем же разбором ошибок; имя — из Content-Disposition, если доступно. */ -export async function apiFetchBlob(path: string): Promise<{ blob: Blob; filename: string | null }> { - const response = await send(path, {}); +export async function apiFetchBlob( + path: string, + init: RequestInit = {} // Добавили init +): Promise<{ blob: Blob; filename: string | null }> { + const response = await send(path, init); const blob = await response.blob(); - return { blob, filename: filenameFromContentDisposition(response.headers.get('Content-Disposition')) }; + return { + blob, + filename: filenameFromContentDisposition(response.headers.get('Content-Disposition')) + }; } function withJsonBody(method: string, body: unknown): RequestInit { diff --git a/web/src/components/TreeFilterDropdown/TreeFilterDropdown.css b/web/src/components/TreeFilterDropdown/TreeFilterDropdown.css new file mode 100644 index 0000000..b71b9a1 --- /dev/null +++ b/web/src/components/TreeFilterDropdown/TreeFilterDropdown.css @@ -0,0 +1,3 @@ +.ant-tree-switcher-noop{ + display: none !important; +} \ No newline at end of file diff --git a/web/src/components/TreeFilterDropdown/TreeFilterDropdown.tsx b/web/src/components/TreeFilterDropdown/TreeFilterDropdown.tsx new file mode 100644 index 0000000..7bdf118 --- /dev/null +++ b/web/src/components/TreeFilterDropdown/TreeFilterDropdown.tsx @@ -0,0 +1,149 @@ +import { useState, useMemo, useEffect } from 'react'; +import { Input, Button, Tree, Space } from 'antd'; +import { SearchOutlined } from '@ant-design/icons'; +import './TreeFilterDropdown.css'; + +export type OptionType = { + title: string; + key: string; + code?:string; + children?: OptionType[]; +}; + +interface TreeFilterDropdownProps { + title: string; + placeholder?: string; + treeData: OptionType[]; + selectedKeys: string[]; + onChange: (keys: string[]) => void; + onClose: () => void; +} + +export const TreeFilterDropdown: React.FC = ({ + title, + placeholder = 'Введите значение', + treeData, + selectedKeys, + onChange, + onClose, +}) => { + const [query, setQuery] = useState(''); + + const [checkedKeys, setCheckedKeys] = useState(selectedKeys); + const [expandedKeys, setExpandedKeys] = useState([]); + + useEffect(() => { + setCheckedKeys(selectedKeys); + }, [selectedKeys]); + + const filteredTreeData = useMemo(() => { + if (!query) return treeData; + + const filterNode = (node: OptionType): OptionType | null => { + const titleStr = String(node.title).toLowerCase(); + const isMatch = titleStr.includes(query.toLowerCase()); + + let filteredChildren: OptionType[] = []; + if (node.children) { + filteredChildren = node.children.map(filterNode).filter((child): child is OptionType => child !== null); + } + + if (isMatch || filteredChildren.length > 0) { + return { ...node, children: filteredChildren.length > 0 ? filteredChildren : undefined }; + } + return null; + }; + + return treeData.map(filterNode).filter((node): node is OptionType => node !== null); + }, [treeData, query]); + + useEffect(() => { + if (query) { + const getAllKeys = (nodes: OptionType[]): string[] => { + let keys: string[] = []; + nodes.forEach((node) => { + keys.push(node.key); + if (node.children) keys = [...keys, ...getAllKeys(node.children)]; + }); + return keys; + }; + setExpandedKeys(getAllKeys(filteredTreeData)); + } + }, [query, filteredTreeData]); + + const getAllNodeKeys = (nodes: OptionType[]): string[] => { + let keys: string[] = []; + nodes.forEach((node) => { + keys.push(node.key); + if (node.children) keys = [...keys, ...getAllNodeKeys(node.children)]; + }); + return keys; + }; + + const handleSelectAll = () => { + setCheckedKeys(getAllNodeKeys(treeData)); + }; + + const handleClear = () => { + setCheckedKeys([]); + }; + + const handleCancel = () => { + setCheckedKeys(selectedKeys); + onClose(); + }; + + return ( +
+
{title}
+ +
Поиск
+ } + placeholder={placeholder} + value={query} + onChange={(e) => setQuery(e.target.value)} + style={{ marginBottom: 12 }} + /> + + + + + + +
+ { + const checked = Array.isArray(keys) ? keys : keys.checked; + setCheckedKeys(checked as string[]); + }} + expandedKeys={expandedKeys} + onExpand={(keys) => setExpandedKeys(keys as string[])} + /> +
+ +
+ + +
+
+ ); +}; \ No newline at end of file diff --git a/web/src/services/consodilation/chess.ts b/web/src/services/consodilation/chess.ts index 7e61aa4..6e5513d 100644 --- a/web/src/services/consodilation/chess.ts +++ b/web/src/services/consodilation/chess.ts @@ -1,4 +1,20 @@ -import { api } from "../../api/client"; -import { IntercompanyMatrixResponse } from "../../types/chess/chess"; +import { api, apiFetchBlob } from '../../api/client'; +import { ChessManualRequest, IntercompanyMatrixResponse } from '../../types/chess/chess'; -export const fetchChessConsolidations = (id: string) => api.get(`/consolidations/versions/${id}/intercompany-matrix/all`); +export const fetchChessConsolidations = (id: string) => + api.get(`/consolidations/versions/${id}/intercompany-matrix/all`); + +export const fetchChessConsolidationsManual = (versionId: string, data: ChessManualRequest): Promise => { + return api.post(`/consolidations/versions/${versionId}/intercompany-matrix/manual`, data); +}; + +export const exportChessTableAll = (versionId: string) => + apiFetchBlob(`/consolidations/versions/${versionId}/intercompany-matrix/all/export`); +export const exportChessTableManual = (versionId: string, data: ChessManualRequest) => + apiFetchBlob(`/consolidations/versions/${versionId}/intercompany-matrix/manual/export`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(data), + }); diff --git a/web/src/types/chess/chess.ts b/web/src/types/chess/chess.ts index 0524ce7..c7efcf5 100644 --- a/web/src/types/chess/chess.ts +++ b/web/src/types/chess/chess.ts @@ -9,9 +9,18 @@ export interface MatrixCell { amount_a: string; amount_b: string; variance_amount: string; + exceeds_threshold:boolean + one_sided:boolean + variance_percent:string| null + skipped_osv_entry_count:number } export interface IntercompanyMatrixResponse { companies: MatrixCompany[]; cells: MatrixCell[]; -} \ No newline at end of file +} + +export type ChessManualRequest = { + debit_account_ids: string[]; + credit_account_ids: string[]; +}; diff --git a/web/src/widgets/ConsolidatedChessContent/ConsolidatedChessContent.css b/web/src/widgets/ConsolidatedChessContent/ConsolidatedChessContent.css deleted file mode 100644 index d9c6dc3..0000000 --- a/web/src/widgets/ConsolidatedChessContent/ConsolidatedChessContent.css +++ /dev/null @@ -1,6 +0,0 @@ -.empty-cell { - background-color: #f2f4f7; -} -.ant-table-tbody > tr:hover > td.empty-cell { - background-color: #f2f4f7 !important; -} \ No newline at end of file diff --git a/web/src/widgets/ConsolidatedChessContent/ConsolidatedChessContent.tsx b/web/src/widgets/ConsolidatedChessContent/ConsolidatedChessContent.tsx index 768a4ac..7058f11 100644 --- a/web/src/widgets/ConsolidatedChessContent/ConsolidatedChessContent.tsx +++ b/web/src/widgets/ConsolidatedChessContent/ConsolidatedChessContent.tsx @@ -1,15 +1,22 @@ -import React, { useEffect, useMemo, useState } from 'react'; -import { Table, Typography } from 'antd'; -import type { ColumnsType } from 'antd/es/table'; +import { useEffect, useMemo, useState, useRef } from 'react'; import { useParams } from 'react-router-dom'; +import { Button } from 'antd'; +import { FullscreenOutlined, FullscreenExitOutlined, DownloadOutlined } from '@ant-design/icons'; import { ApiError } from '../../api/client'; -import { fetchChessConsolidations } from '../../services/consodilation/chess'; -import type { IntercompanyMatrixResponse, MatrixCell, MatrixCompany } from '../../types/chess/chess'; +import { + exportChessTableAll, + exportChessTableManual, + fetchChessConsolidations, + fetchChessConsolidationsManual, +} from '../../services/consodilation/chess'; +import type { IntercompanyMatrixResponse } from '../../types/chess/chess'; import { useNotificationApi } from '../../utils/useNotification'; +import { ConsolidationChessTable } from '../ConsolidationChessTable/ConsolidationChessTable'; +import { ConsolidationChessTableFilter } from '../ConsolidationChessTable/ConsolidationChessTableFilter'; +import { saveBlob } from '../../utils/download'; + -import './ConsolidatedChessContent.css'; -const { Text } = Typography; export const ChessContent = () => { const { version_id } = useParams<{ version_id: string }>(); @@ -17,103 +24,147 @@ export const ChessContent = () => { const [data, setData] = useState(null); const [loading, setLoading] = useState(false); + const [expoerLoading, setExportLoading] = useState(false); + const [companies, setCompanies] = useState([]); + const [decimals, setDecimals] = useState(2); + const [units, setUnits] = useState('thousand'); + const [debit, setDebit] = useState([]); + const [credit, setCredit] = useState([]); - const loadData = async (vId: string) => { + const containerRef = useRef(null); + const [isFullscreen, setIsFullscreen] = useState(false); + + const toggleFullscreen = async () => { + if (!containerRef.current) return; + try { + if (!document.fullscreenElement) { + await containerRef.current.requestFullscreen(); + } else { + await document.exitFullscreen(); + } + } catch (e) { + console.log(e); + } + }; + + const loadData = async (vId: string, dIds: string[], cIds: string[]) => { setLoading(true); try { - const res = await fetchChessConsolidations(vId); + let res; + if (dIds?.length && cIds?.length) { + res = await fetchChessConsolidationsManual(vId, { debit_account_ids: dIds, credit_account_ids: cIds }); + } else { + res = await fetchChessConsolidations(vId); + } setData(res); } catch (e) { if (e instanceof ApiError) { notification.error({ description: e.message }); - } + } } finally { setLoading(false); } }; + const download = async () => { + if (!version_id) { + return; + } + setExportLoading(true); + try { + if (debit?.length && credit?.length) { + const { blob, filename } = await exportChessTableManual(version_id, { debit_account_ids: debit, credit_account_ids: credit }); + saveBlob(blob, filename ?? 'Шахматка'); + } else { + const { blob, filename } = await exportChessTableAll(version_id); + saveBlob(blob, filename ?? 'Шахматка'); + } + } catch (e) { + if (e instanceof ApiError) { + notification.error({ description: e.message }); + } + } finally { + setExportLoading(false); + } + }; + useEffect(() => { if (!version_id) return; - loadData(version_id); - }, [version_id]); + loadData(version_id, debit, credit); + }, [version_id, debit, credit]); - const matrixData = useMemo(() => { - if (!data?.cells) return {}; - const result: Record> = {}; - - data.cells.forEach((cell) => { - if (!result[cell.company_a_id]) result[cell.company_a_id] = {}; - result[cell.company_a_id][cell.company_b_id] = cell; - }); - - return result; - }, [data?.cells]); - - const columns: ColumnsType = useMemo(() => { - if (!data?.companies) return []; - - const firstColumn: ColumnsType[number] = { - title: 'ДБ / КТ', - dataIndex: 'name', - key: 'name', - fixed: 'left', - width: 200, - render: (text) => {text}, - onCell: () => ({ style: { background: '#fff', zIndex: 2 } }), + useEffect(() => { + const handleFullscreenChange = () => { + setIsFullscreen(!!document.fullscreenElement); }; + document.addEventListener('fullscreenchange', handleFullscreenChange); + return () => { + document.removeEventListener('fullscreenchange', handleFullscreenChange); + }; + }, []); - // Внутри companyColumns - const companyColumns = data.companies.map((company) => ({ - title: company.name, - dataIndex: company.id, - key: company.id, - width: 150, - align: 'right' as const, + const filteredData = useMemo(() => { + if (!data) return null; + if (companies.length === 0) return data; - onCell: (row: MatrixCompany) => { - const cell = matrixData[row.id]?.[company.id]; - if (!cell) { - // Возвращаем класс через onCell, это работает надежнее - return { className: 'empty-cell' }; - } - return {}; - }, + const filteredCompanies = data.companies.filter((c) => companies.includes(c.id)); + const filteredCells = data.cells.filter((c) => companies.includes(c.company_a_id) && companies.includes(c.company_b_id)); - render: (_: unknown, row: MatrixCompany) => { - const cell = matrixData[row.id]?.[company.id]; - - if (!cell) { - return —; - } - - return ( -
- {cell.amount_a} - {cell.amount_b} - - {cell.variance_amount} - -
- ); - }, - })); - - return [firstColumn, ...companyColumns]; - }, [data?.companies, matrixData]); + return { companies: filteredCompanies, cells: filteredCells }; + }, [data, companies]); return ( - - dataSource={data?.companies || []} - columns={columns} - rowKey='id' - pagination={false} - loading={loading} - scroll={{ x: 'max-content', y: 'calc(100vh - 280px)' }} - size='small' - bordered - /> +
+ +
+ +
+ + +
+ } + + /> + ); }; diff --git a/web/src/widgets/ConsolidationChessTable/ConsolidatedChessTable.css b/web/src/widgets/ConsolidationChessTable/ConsolidatedChessTable.css new file mode 100644 index 0000000..b462e36 --- /dev/null +++ b/web/src/widgets/ConsolidationChessTable/ConsolidatedChessTable.css @@ -0,0 +1,19 @@ +.empty-cell { + background-color: #f2f4f7; +} +.ant-table-tbody > tr:hover > td.empty-cell { + background-color: #f2f4f7 !important; +} + +.ant-table-summary{ + border-end-end-radius: 8px !important; + border-end-start-radius: 8px !important; +} + + +.ant-table-cell-fix { + background-color: #f8fafc !important; +} +.resRow{ + background-color:#e6f4ea !important ; +} diff --git a/web/src/widgets/ConsolidationChessTable/ConsolidationChessTable.tsx b/web/src/widgets/ConsolidationChessTable/ConsolidationChessTable.tsx new file mode 100644 index 0000000..c41e4d2 --- /dev/null +++ b/web/src/widgets/ConsolidationChessTable/ConsolidationChessTable.tsx @@ -0,0 +1,183 @@ +import React, { useMemo } from 'react'; +import { Table, Typography } from 'antd'; +import type { ColumnsType } from 'antd/es/table'; +import type { IntercompanyMatrixResponse, MatrixCell, MatrixCompany } from '../../types/chess/chess'; +import './ConsolidatedChessTable.css'; + +const { Text } = Typography; + +interface ConsolidationChessTableI { + data: IntercompanyMatrixResponse | null; + loading: boolean; + decimals: number; + units: string; + toolbar?: React.ReactNode; + isFullscreen?: boolean; +} + +type TableRow = MatrixCompany & { isTotal?: boolean }; + +export const ConsolidationChessTable: React.FC = ({ + data, + loading, + decimals, + units, + toolbar, + isFullscreen = false, +}) => { + const matrixData = useMemo(() => { + if (!data?.cells) return {}; + const result: Record> = {}; + data.cells.forEach((cell) => { + if (!result[cell.company_a_id]) result[cell.company_a_id] = {}; + result[cell.company_a_id][cell.company_b_id] = cell; + }); + return result; + }, [data?.cells]); + + const formatValue = (val: string | number) => { + if (val === undefined || val === null || val === '') return ''; + let num = typeof val === 'string' ? parseFloat(val) : val; + if (isNaN(num)) return String(val); + + if (units === 'thousand') { + num /= 1000; + } else if (units === 'million') { + num /= 1000000; + } else if (units === 'billion') { + num /= 1_000_000_000; + } + + return num.toFixed(decimals).replace('.', ','); + }; + + const parseAmount = (val: string | null | undefined): number => { + if (!val) return 0; + + const isNegative: boolean = val.startsWith('(') && val.endsWith(')'); + const cleanVal = isNegative ? val.slice(1, -1) : val; + + const num = parseFloat(cleanVal); + + if (isNaN(num)) { + return 0; + } + + return isNegative ? -num : num; + }; + + const columns: ColumnsType = useMemo(() => { + if (!data?.companies) return []; + + const firstColumn: ColumnsType[number] = { + title: 'ДБ / КТ', + dataIndex: 'name', + key: 'name', + fixed: 'left', + width: 200, + render: (text, row) => {text}, + onCell: (row) => ({ + style: { + background: row.isTotal ? '#e6f4ea' : '#fff', + zIndex: 2, + }, + }), + }; + + const companyColumns = data.companies.map((company) => ({ + title: company.name, + dataIndex: company.id, + key: company.id, + width: 150, + align: 'right' as const, + onCell: (row: TableRow) => { + if (row.isTotal) return { style: { background: '#e6f4ea' } }; + + const cell = matrixData[row.id]?.[company.id]; + if (!cell) return { className: 'empty-cell' }; + return {}; + }, + render: (_: unknown, row: TableRow) => { + if (row.isTotal) { + const totalVal = (row as any)[company.id]; + return {formatValue(totalVal)}; + } + const cell = matrixData[row.id]?.[company.id]; + if (!cell) return —; + return ( +
+ {formatValue(cell.amount_a)} + {formatValue(cell.amount_b)} + {formatValue(cell.variance_amount)} +
+ ); + }, + })); + + return [firstColumn, ...companyColumns]; + }, [data?.companies, matrixData, decimals, units]); + + const totalRowData = useMemo(() => { + if (!data?.companies) return null; + + const totalRow: Record = { + id: 'total-row', + name: 'Итого', + isTotal: true, + }; + + data.companies.forEach((colCompany) => { + let colTotal = 0; + + data.companies.forEach((rowCompany) => { + const cell = matrixData[rowCompany.id]?.[colCompany.id]; + if (cell?.variance_amount) { + colTotal += parseAmount(cell.variance_amount); + } + }); + + totalRow[colCompany.id] = colTotal; + }); + + return totalRow as TableRow; + }, [data?.companies, matrixData]); + + const scrollY = isFullscreen ? 'calc(100vh - 180px)' : 436; + + return ( + + dataSource={data?.companies || []} + columns={columns} + rowKey='id' + pagination={false} + loading={loading} + scroll={{ x: 'max-content', y: scrollY }} + size='middle' + bordered + title={() => toolbar} + summary={() => { + if (!totalRowData) return null; + + return ( + + + +
+ Итого +
+
+ + {data?.companies.map((company, index) => ( + +
+ {formatValue((totalRowData as any)[company.id])} +
+
+ ))} +
+
+ ); + }} + /> + ); +}; \ No newline at end of file diff --git a/web/src/widgets/ConsolidationChessTable/ConsolidationChessTableFilter.tsx b/web/src/widgets/ConsolidationChessTable/ConsolidationChessTableFilter.tsx new file mode 100644 index 0000000..1775e11 --- /dev/null +++ b/web/src/widgets/ConsolidationChessTable/ConsolidationChessTableFilter.tsx @@ -0,0 +1,253 @@ +import React, { useEffect, useState } from 'react'; +import { Space, Select, Button } from 'antd'; +import type { MatrixCompany } from '../../types/chess/chess'; +import { OptionType, TreeFilterDropdown } from '../../components/TreeFilterDropdown/TreeFilterDropdown'; +import { api, ApiError } from '../../api/client'; +import { useNotificationApi } from '../../utils/useNotification'; + +interface Account { + id: string; + code: string; + parent_id: string | null; + name: string; + kind: string; + row_type: string; + is_off_balance: boolean; + is_debit: boolean; + is_credit: boolean; +} + +interface ConsolidationChessTableFilterI { + companies: string[]; + onCompaniesChange: (val: string[]) => void; + decimals: number; + onDecimalsChange: (val: number) => void; + debit: string[]; + onDebitChange: (val: string[]) => void; + credit: string[]; + onCreditChange: (val: string[]) => void; + allCompanies: MatrixCompany[]; + units: string; + onUnitsChange: (val: string) => void; +} + +const buildTree = (items: Account[]): OptionType[] => { + const map: Record = {}; + const roots: OptionType[] = []; + + items.forEach((item) => { + map[item.id] = { + title: `${item.code} - ${item.name}`, + key: item.id, + code: item.code, + children: [], + }; + }); + + items.forEach((item) => { + if (item.parent_id && map[item.parent_id]) { + map[item.parent_id].children.push(map[item.id]); + } else { + roots.push(map[item.id]); + } + }); + + const cleanUp = (nodes: OptionType[]): OptionType[] => { + return nodes.map((node) => { + if (node.children && node.children.length > 0) { + return { ...node, children: cleanUp(node.children) }; + } + return { title: node.title, key: node.key, code: node.code }; + }); + }; + + return cleanUp(roots); +}; + +export const ConsolidationChessTableFilter: React.FC = ({ + companies, + onCompaniesChange, + decimals, + onDecimalsChange, + debit, + onDebitChange, + credit, + onCreditChange, + allCompanies, + units, + onUnitsChange, +}) => { + const [companyOpen, setCompanyOpen] = useState(false); + const [debitOpen, setDebitOpen] = useState(false); + const [creditOpen, setCreditOpen] = useState(false); + + const [optionsLoading, setOptionsLoading] = useState(false); + const [debitTreeData, setDebitTreeData] = useState([]); + const [creditTreeData, setCreditTreeData] = useState([]); + + const notification = useNotificationApi(); + + const companyOptions = allCompanies.map((c) => ({ label: c.name, value: c.id })); + const companyTreeData = allCompanies.map((c) => ({ title: c.name, key: c.id })); + + const UNIT_OPTIONS = [ + { value: 'thousand', label: 'тыс. руб.' }, + { value: 'million', label: 'млн. руб.' }, + { value: 'billion', label: 'млрд руб.' }, + ]; + const DECIMALS_OPTIONS = [ + { value: 0, label: '0' }, + { value: 1, label: '1' }, + { value: 2, label: '2' }, + { value: 3, label: '3' }, + ]; + + async function getOptionHandler() { + setOptionsLoading(true); + try { + const res = await api.get('/directories/accounts'); + + const debitAccounts = res.filter((acc) => acc.is_debit); + const creditAccounts = res.filter((acc) => acc.is_credit); + + setDebitTreeData(buildTree(debitAccounts)); + setCreditTreeData(buildTree(creditAccounts)); + } catch (e) { + if (e instanceof ApiError) { + notification.error({ description: e.message }); + } else { + notification.error({ description: 'Не удалось загрузить счета' }); + } + } finally { + setOptionsLoading(false); + } + } + + function dropFiltersHandler() { + onCompaniesChange([]); + onDebitChange([]); + onCreditChange([]); + } + + useEffect(() => { + getOptionHandler(); + }, []); + + return ( +
+ + + Год + + + + Компании + ({ label: node.code, value: node.key }))} + popupRender={() => ( + setDebitOpen(false)} + /> + )} + loading={optionsLoading} + /> + + + + Кт + + + + + Знаков после запятой +