chore(web): format и biome для шахматки (ревью #158)

- npm run format (eslint --fix, biome check --write, prettier --write) по файлам шахматки;
- biome: import type и сортировка импортов; явный тип IntercompanyMatrixResponse у `let res`
  (ConsolidatedChessContent); Number.isNaN вместо isNaN (ConsolidationChessTable); key у span в OsvTabs;
  `?? null` вместо пустого фрагмента (ConsolidatioReportPage). Поведение не менялось.

Остаются предупреждения eslint в коде шахматки (any, deps хуков, console) — за автором.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Andrey Epifancev 2026-10-21 14:05:23 +04:00
parent 175343b843
commit e19291c886
13 changed files with 71 additions and 79 deletions

View File

@ -1,6 +1,6 @@
.ant-select-selection-item{ .ant-select-selection-item {
background-color: #ecfdf3 !important; background-color: #ecfdf3 !important;
color: #1f5f35 !important; color: #1f5f35 !important;
box-shadow: inset 0 0 0 1px #2ea64b38 !important; box-shadow: inset 0 0 0 1px #2ea64b38 !important;
border-radius: 999px !important; border-radius: 999px !important;
} }

View File

@ -17,9 +17,9 @@ import {
Main, Main,
Mono, Mono,
} from './layouts/'; } from './layouts/';
import { ConsolidatioReportPage } from './layouts/ConsolidatioReportPage';
import { store } from './store/store'; import { store } from './store/store';
import { Sidebar } from './widgets/Sidebar/Sidebar'; import { Sidebar } from './widgets/Sidebar/Sidebar';
import { ConsolidatioReportPage } from './layouts/ConsolidatioReportPage';
// Каркас: экраны по docs/context/10-user-journey.md добавляются срезами. // Каркас: экраны по docs/context/10-user-journey.md добавляются срезами.
function App() { function App() {
@ -37,7 +37,7 @@ function App() {
<Route path='/groups'> <Route path='/groups'>
<Route index element={<GroupsLayout />} /> <Route index element={<GroupsLayout />} />
<Route path=':group_id' element={<ConsolidationPage />} /> <Route path=':group_id' element={<ConsolidationPage />} />
<Route path=':group_id/versions/:version_id/:tab' element={<ConsolidatioReportPage/>} /> <Route path=':group_id/versions/:version_id/:tab' element={<ConsolidatioReportPage />} />
<Route path='monozaemchiki' element={<Mono />} /> <Route path='monozaemchiki' element={<Mono />} />
</Route> </Route>
<Route path='/catalogue/groups' element={<CatalogueGroup />} /> <Route path='/catalogue/groups' element={<CatalogueGroup />} />

View File

@ -129,13 +129,13 @@ export async function apiFetch<T>(path: string, init: RequestInit = {}): Promise
/** Бинарный ответ (скан, xlsx) с токеном и тем же разбором ошибок; имя — из Content-Disposition, если доступно. */ /** Бинарный ответ (скан, xlsx) с токеном и тем же разбором ошибок; имя — из Content-Disposition, если доступно. */
export async function apiFetchBlob( export async function apiFetchBlob(
path: string, path: string,
init: RequestInit = {} // Добавили init init: RequestInit = {}, // Добавили init
): Promise<{ blob: Blob; filename: string | null }> { ): Promise<{ blob: Blob; filename: string | null }> {
const response = await send(path, init); const response = await send(path, init);
const blob = await response.blob(); const blob = await response.blob();
return { return {
blob, blob,
filename: filenameFromContentDisposition(response.headers.get('Content-Disposition')) filename: filenameFromContentDisposition(response.headers.get('Content-Disposition')),
}; };
} }

View File

@ -1,3 +1,3 @@
.ant-tree-switcher-noop{ .ant-tree-switcher-noop {
display: none !important; display: none !important;
} }

View File

@ -1,12 +1,12 @@
import { useState, useMemo, useEffect } from 'react';
import { Input, Button, Tree, Space } from 'antd';
import { SearchOutlined } from '@ant-design/icons'; import { SearchOutlined } from '@ant-design/icons';
import { Button, Input, Space, Tree } from 'antd';
import { useEffect, useMemo, useState } from 'react';
import './TreeFilterDropdown.css'; import './TreeFilterDropdown.css';
export type OptionType = { export type OptionType = {
title: string; title: string;
key: string; key: string;
code?:string; code?: string;
children?: OptionType[]; children?: OptionType[];
}; };
@ -31,7 +31,7 @@ export const TreeFilterDropdown: React.FC<TreeFilterDropdownProps> = ({
const [checkedKeys, setCheckedKeys] = useState<string[]>(selectedKeys); const [checkedKeys, setCheckedKeys] = useState<string[]>(selectedKeys);
const [expandedKeys, setExpandedKeys] = useState<string[]>([]); const [expandedKeys, setExpandedKeys] = useState<string[]>([]);
useEffect(() => { useEffect(() => {
setCheckedKeys(selectedKeys); setCheckedKeys(selectedKeys);
}, [selectedKeys]); }, [selectedKeys]);
@ -94,7 +94,7 @@ export const TreeFilterDropdown: React.FC<TreeFilterDropdownProps> = ({
}; };
return ( return (
<div style={{ padding: 12, width: 380}}> <div style={{ padding: 12, width: 380 }}>
<div style={{ fontWeight: 600, marginBottom: 12, fontSize: 16 }}>{title}</div> <div style={{ fontWeight: 600, marginBottom: 12, fontSize: 16 }}>{title}</div>
<div style={{ fontSize: 12, marginBottom: 4 }}>Поиск</div> <div style={{ fontSize: 12, marginBottom: 4 }}>Поиск</div>
@ -107,12 +107,8 @@ export const TreeFilterDropdown: React.FC<TreeFilterDropdownProps> = ({
/> />
<Space style={{ marginBottom: 12, justifyContent: 'space-between', width: '100%' }}> <Space style={{ marginBottom: 12, justifyContent: 'space-between', width: '100%' }}>
<Button onClick={handleSelectAll}> <Button onClick={handleSelectAll}>Выбрать все</Button>
Выбрать все <Button onClick={handleClear}>Очистить</Button>
</Button>
<Button onClick={handleClear}>
Очистить
</Button>
</Space> </Space>
<div style={{ maxHeight: 280, overflowY: 'auto', border: '1px solid #f0f0f0', borderRadius: 8, padding: 8 }}> <div style={{ maxHeight: 280, overflowY: 'auto', border: '1px solid #f0f0f0', borderRadius: 8, padding: 8 }}>
@ -131,9 +127,7 @@ export const TreeFilterDropdown: React.FC<TreeFilterDropdownProps> = ({
</div> </div>
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8, marginTop: 10 }}> <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8, marginTop: 10 }}>
<Button onClick={handleCancel}> <Button onClick={handleCancel}>Отмена</Button>
Отмена
</Button>
<Button <Button
type='primary' type='primary'
style={{ background: '#2a8c3a' }} style={{ background: '#2a8c3a' }}
@ -146,4 +140,4 @@ export const TreeFilterDropdown: React.FC<TreeFilterDropdownProps> = ({
</div> </div>
</div> </div>
); );
}; };

View File

@ -1,5 +1,5 @@
import { Space, message } from 'antd'; import { Space, message } from 'antd';
import { ReactNode, useEffect, useState } from 'react'; import { type ReactNode, useEffect, useState } from 'react';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import { ApiError } from '../api/client'; import { ApiError } from '../api/client';
import PageTitle from '../components/PageTitle'; import PageTitle from '../components/PageTitle';
@ -10,9 +10,9 @@ import type { ConsRussianStatus } from '../types/consolidations/common_types';
import type { VersionCard } from '../types/consolidations/version_members'; import type { VersionCard } from '../types/consolidations/version_members';
import { quarterShortLabel } from '../utils/labels'; import { quarterShortLabel } from '../utils/labels';
import { statusColorMapping, statusMapping } from '../widgets/ConsoleTable/consts'; import { statusColorMapping, statusMapping } from '../widgets/ConsoleTable/consts';
import { ChessContent } from '../widgets/ConsolidatedChessContent/ConsolidatedChessContent';
import { OsvTabs } from '../widgets/ConsolidatedOsv/OsvTabs'; import { OsvTabs } from '../widgets/ConsolidatedOsv/OsvTabs';
import { OsvContent } from '../widgets/ConsolidatedOsvContent/ConsolidatedOsvContent'; import { OsvContent } from '../widgets/ConsolidatedOsvContent/ConsolidatedOsvContent';
import { ChessContent } from '../widgets/ConsolidatedChessContent/ConsolidatedChessContent';
function ReportContent() { function ReportContent() {
const [card, setCard] = useState<VersionCard | null>(null); const [card, setCard] = useState<VersionCard | null>(null);
@ -21,10 +21,10 @@ function ReportContent() {
const TAB_COMPONENTS: Record<string, ReactNode> = { const TAB_COMPONENTS: Record<string, ReactNode> = {
osv: <OsvContent card={card} />, osv: <OsvContent card={card} />,
chess: <ChessContent />, chess: <ChessContent />,
}; };
const ActiveTab = TAB_COMPONENTS[tab as string] ?? <></>; const ActiveTab = TAB_COMPONENTS[tab as string] ?? null;
const russianStatus = card ? (statusMapping[card.status] ?? card.status) : ''; const russianStatus = card ? (statusMapping[card.status] ?? card.status) : '';
const preset = statusColorMapping[russianStatus as ConsRussianStatus] ?? TAG_PRESETS.default; const preset = statusColorMapping[russianStatus as ConsRussianStatus] ?? TAG_PRESETS.default;

View File

@ -1,5 +1,5 @@
import { api, apiFetchBlob } from '../../api/client'; import { api, apiFetchBlob } from '../../api/client';
import { ChessManualRequest, IntercompanyMatrixResponse } from '../../types/chess/chess'; import type { ChessManualRequest, IntercompanyMatrixResponse } from '../../types/chess/chess';
export const fetchChessConsolidations = (id: string) => export const fetchChessConsolidations = (id: string) =>
api.get<IntercompanyMatrixResponse>(`/consolidations/versions/${id}/intercompany-matrix/all`); api.get<IntercompanyMatrixResponse>(`/consolidations/versions/${id}/intercompany-matrix/all`);

View File

@ -1,23 +1,23 @@
export interface MatrixCompany { export interface MatrixCompany {
id: string; id: string;
name: string; name: string;
} }
export interface MatrixCell { export interface MatrixCell {
company_a_id: string; company_a_id: string;
company_b_id: string; company_b_id: string;
amount_a: string; amount_a: string;
amount_b: string; amount_b: string;
variance_amount: string; variance_amount: string;
exceeds_threshold:boolean exceeds_threshold: boolean;
one_sided:boolean one_sided: boolean;
variance_percent:string| null variance_percent: string | null;
skipped_osv_entry_count:number skipped_osv_entry_count: number;
} }
export interface IntercompanyMatrixResponse { export interface IntercompanyMatrixResponse {
companies: MatrixCompany[]; companies: MatrixCompany[];
cells: MatrixCell[]; cells: MatrixCell[];
} }
export type ChessManualRequest = { export type ChessManualRequest = {

View File

@ -1,7 +1,7 @@
import { useEffect, useMemo, useState, useRef } from 'react'; import { DownloadOutlined, FullscreenExitOutlined, FullscreenOutlined } from '@ant-design/icons';
import { useParams } from 'react-router-dom';
import { Button } from 'antd'; import { Button } from 'antd';
import { FullscreenOutlined, FullscreenExitOutlined, DownloadOutlined } from '@ant-design/icons'; import { useEffect, useMemo, useRef, useState } from 'react';
import { useParams } from 'react-router-dom';
import { ApiError } from '../../api/client'; import { ApiError } from '../../api/client';
import { import {
exportChessTableAll, exportChessTableAll,
@ -10,13 +10,10 @@ import {
fetchChessConsolidationsManual, fetchChessConsolidationsManual,
} from '../../services/consodilation/chess'; } from '../../services/consodilation/chess';
import type { IntercompanyMatrixResponse } from '../../types/chess/chess'; import type { IntercompanyMatrixResponse } from '../../types/chess/chess';
import { saveBlob } from '../../utils/download';
import { useNotificationApi } from '../../utils/useNotification'; import { useNotificationApi } from '../../utils/useNotification';
import { ConsolidationChessTable } from '../ConsolidationChessTable/ConsolidationChessTable'; import { ConsolidationChessTable } from '../ConsolidationChessTable/ConsolidationChessTable';
import { ConsolidationChessTableFilter } from '../ConsolidationChessTable/ConsolidationChessTableFilter'; import { ConsolidationChessTableFilter } from '../ConsolidationChessTable/ConsolidationChessTableFilter';
import { saveBlob } from '../../utils/download';
export const ChessContent = () => { export const ChessContent = () => {
const { version_id } = useParams<{ version_id: string }>(); const { version_id } = useParams<{ version_id: string }>();
@ -50,7 +47,7 @@ export const ChessContent = () => {
const loadData = async (vId: string, dIds: string[], cIds: string[]) => { const loadData = async (vId: string, dIds: string[], cIds: string[]) => {
setLoading(true); setLoading(true);
try { try {
let res; let res: IntercompanyMatrixResponse;
if (dIds?.length && cIds?.length) { if (dIds?.length && cIds?.length) {
res = await fetchChessConsolidationsManual(vId, { debit_account_ids: dIds, credit_account_ids: cIds }); res = await fetchChessConsolidationsManual(vId, { debit_account_ids: dIds, credit_account_ids: cIds });
} else { } else {
@ -163,7 +160,6 @@ export const ChessContent = () => {
</Button> </Button>
</div> </div>
} }
/> />
</div> </div>
); );

View File

@ -53,6 +53,7 @@ export function OsvTabs() {
const label = ( const label = (
<span <span
key={tabItem.key}
style={{ style={{
color: isActive ? '#2a8c3a' : undefined, color: isActive ? '#2a8c3a' : undefined,
fontWeight: isActive ? 600 : 400, fontWeight: isActive ? 600 : 400,

View File

@ -1,19 +1,18 @@
.empty-cell { .empty-cell {
background-color: #f2f4f7; background-color: #f2f4f7;
} }
.ant-table-tbody > tr:hover > td.empty-cell { .ant-table-tbody > tr:hover > td.empty-cell {
background-color: #f2f4f7 !important; background-color: #f2f4f7 !important;
} }
.ant-table-summary{ .ant-table-summary {
border-end-end-radius: 8px !important; border-end-end-radius: 8px !important;
border-end-start-radius: 8px !important; border-end-start-radius: 8px !important;
} }
.ant-table-cell-fix { .ant-table-cell-fix {
background-color: #f8fafc !important; background-color: #f8fafc !important;
} }
.resRow{ .resRow {
background-color:#e6f4ea !important ; background-color: #e6f4ea !important ;
} }

View File

@ -1,6 +1,7 @@
import React, { useMemo } from 'react';
import { Table, Typography } from 'antd'; import { Table, Typography } from 'antd';
import type { ColumnsType } from 'antd/es/table'; import type { ColumnsType } from 'antd/es/table';
import type React from 'react';
import { useMemo } from 'react';
import type { IntercompanyMatrixResponse, MatrixCell, MatrixCompany } from '../../types/chess/chess'; import type { IntercompanyMatrixResponse, MatrixCell, MatrixCompany } from '../../types/chess/chess';
import './ConsolidatedChessTable.css'; import './ConsolidatedChessTable.css';
@ -37,8 +38,8 @@ export const ConsolidationChessTable: React.FC<ConsolidationChessTableI> = ({
const formatValue = (val: string | number) => { const formatValue = (val: string | number) => {
if (val === undefined || val === null || val === '') return ''; if (val === undefined || val === null || val === '') return '';
let num = typeof val === 'string' ? parseFloat(val) : val; let num = typeof val === 'string' ? Number.parseFloat(val) : val;
if (isNaN(num)) return String(val); if (Number.isNaN(num)) return String(val);
if (units === 'thousand') { if (units === 'thousand') {
num /= 1000; num /= 1000;
@ -57,9 +58,9 @@ export const ConsolidationChessTable: React.FC<ConsolidationChessTableI> = ({
const isNegative: boolean = val.startsWith('(') && val.endsWith(')'); const isNegative: boolean = val.startsWith('(') && val.endsWith(')');
const cleanVal = isNegative ? val.slice(1, -1) : val; const cleanVal = isNegative ? val.slice(1, -1) : val;
const num = parseFloat(cleanVal); const num = Number.parseFloat(cleanVal);
if (isNaN(num)) { if (Number.isNaN(num)) {
return 0; return 0;
} }
@ -146,7 +147,7 @@ export const ConsolidationChessTable: React.FC<ConsolidationChessTableI> = ({
return ( return (
<Table<TableRow> <Table<TableRow>
dataSource={data?.companies || []} dataSource={data?.companies || []}
columns={columns} columns={columns}
rowKey='id' rowKey='id'
pagination={false} pagination={false}
@ -159,7 +160,7 @@ export const ConsolidationChessTable: React.FC<ConsolidationChessTableI> = ({
if (!totalRowData) return null; if (!totalRowData) return null;
return ( return (
<Table.Summary fixed > <Table.Summary fixed>
<Table.Summary.Row className='resRow'> <Table.Summary.Row className='resRow'>
<Table.Summary.Cell className='resRow' index={0} align='left'> <Table.Summary.Cell className='resRow' index={0} align='left'>
<div style={{ backgroundColor: '#e6f4ea', width: '100%', height: '100%' }}> <div style={{ backgroundColor: '#e6f4ea', width: '100%', height: '100%' }}>
@ -180,4 +181,4 @@ export const ConsolidationChessTable: React.FC<ConsolidationChessTableI> = ({
}} }}
/> />
); );
}; };

View File

@ -1,8 +1,9 @@
import React, { useEffect, useState } from 'react'; import { Button, Select, Space } from 'antd';
import { Space, Select, Button } from 'antd'; import type React from 'react';
import { useEffect, useState } from 'react';
import { ApiError, api } from '../../api/client';
import { type OptionType, TreeFilterDropdown } from '../../components/TreeFilterDropdown/TreeFilterDropdown';
import type { MatrixCompany } from '../../types/chess/chess'; 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'; import { useNotificationApi } from '../../utils/useNotification';
interface Account { interface Account {