Шахматка: при выборе одной стороны Дт/Кт не шлём запрос (ревью #207)
- режим запроса вынесен в utils/chessMatrixMode (all / manual / half) с тестами; при «одна сторона» запрос матрицы не уходит, индикатор загрузки снимается; - подсказка по спеке intercompany-matrix-ui: один текст в таблице и у фильтров, у фильтров — только в состоянии «одна сторона»; - высота шахматки: min-height только для состояния подсказки, обёртка снова auto (в обычном режиме «Итого» сразу под строками); - откат pairs?.length в shouldWarnCounterPairs (маскировал старый API); - вернули комментарий про сумму, лишний columns || [] и дубль CSS убраны; опечатка CataloguGroupTabsI. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
65c44978bd
commit
d3c4d35dca
17
web/src/utils/chessMatrixMode.test.ts
Normal file
17
web/src/utils/chessMatrixMode.test.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import { chessMatrixMode } from './chessMatrixMode';
|
||||||
|
|
||||||
|
describe('chessMatrixMode', () => {
|
||||||
|
it('фильтры пусты — полная матрица', () => {
|
||||||
|
expect(chessMatrixMode([], [])).toBe('all');
|
||||||
|
});
|
||||||
|
it('выбраны Дт и Кт — ручной расчёт', () => {
|
||||||
|
expect(chessMatrixMode(['62'], ['60'])).toBe('manual');
|
||||||
|
});
|
||||||
|
it('только Дт — одна сторона', () => {
|
||||||
|
expect(chessMatrixMode(['62'], [])).toBe('half');
|
||||||
|
});
|
||||||
|
it('только Кт — одна сторона', () => {
|
||||||
|
expect(chessMatrixMode([], ['60'])).toBe('half');
|
||||||
|
});
|
||||||
|
});
|
||||||
16
web/src/utils/chessMatrixMode.ts
Normal file
16
web/src/utils/chessMatrixMode.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* Режим запроса шахматки по фильтрам счетов:
|
||||||
|
* - `all` — фильтры пусты, берём полную матрицу;
|
||||||
|
* - `manual` — выбраны и Дт, и Кт, считаем по выбранным счетам;
|
||||||
|
* - `half` — выбрана только одна сторона: запрос не шлём, показываем подсказку.
|
||||||
|
*/
|
||||||
|
export type ChessMatrixMode = 'all' | 'manual' | 'half';
|
||||||
|
|
||||||
|
/** Текст для состояния «выбрана одна сторона» — в таблице и у фильтров один и тот же (openspec intercompany-matrix-ui). */
|
||||||
|
export const CHESS_HALF_FILTER_HINT = 'Для расчёта по счетам выберите счета и в Дт, и в Кт';
|
||||||
|
|
||||||
|
export const chessMatrixMode = (debit: readonly string[], credit: readonly string[]): ChessMatrixMode => {
|
||||||
|
if (debit.length > 0 && credit.length > 0) return 'manual';
|
||||||
|
if (debit.length === 0 && credit.length === 0) return 'all';
|
||||||
|
return 'half';
|
||||||
|
};
|
||||||
@ -140,7 +140,7 @@ export const shouldWarnCounterPairs = (
|
|||||||
pairs: readonly CounterPairOut[],
|
pairs: readonly CounterPairOut[],
|
||||||
storage: Pick<Storage, 'getItem'> | null,
|
storage: Pick<Storage, 'getItem'> | null,
|
||||||
): boolean => {
|
): boolean => {
|
||||||
if (pairs?.length === 0) return false;
|
if (pairs.length === 0) return false;
|
||||||
if (status && FINISHED_STATUSES.includes(status)) return false;
|
if (status && FINISHED_STATUSES.includes(status)) return false;
|
||||||
try {
|
try {
|
||||||
return storage?.getItem(`${COUNTER_PAIRS_STORAGE_PREFIX}${versionId}`) !== counterPairsKey(pairs);
|
return storage?.getItem(`${COUNTER_PAIRS_STORAGE_PREFIX}${versionId}`) !== counterPairsKey(pairs);
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { Tabs } from 'antd';
|
|||||||
import { useAppSelector } from '../../../store/store';
|
import { useAppSelector } from '../../../store/store';
|
||||||
import type { RelatedGroupOut } from '../../../types/api';
|
import type { RelatedGroupOut } from '../../../types/api';
|
||||||
import './CatalogueCompanyGroupTabs.css';
|
import './CatalogueCompanyGroupTabs.css';
|
||||||
interface CataloguGroupTabsI {
|
interface CatalogueCompanyGroupTabsI {
|
||||||
activeTab: CatalogueCompanyGroupTabKey;
|
activeTab: CatalogueCompanyGroupTabKey;
|
||||||
setActiveTab: (val: CatalogueCompanyGroupTabKey) => void;
|
setActiveTab: (val: CatalogueCompanyGroupTabKey) => void;
|
||||||
gskData: RelatedGroupOut[];
|
gskData: RelatedGroupOut[];
|
||||||
@ -11,7 +11,7 @@ interface CataloguGroupTabsI {
|
|||||||
|
|
||||||
export type CatalogueCompanyGroupTabKey = 'legal_entities' | 'gsk' | 'gsz';
|
export type CatalogueCompanyGroupTabKey = 'legal_entities' | 'gsk' | 'gsz';
|
||||||
|
|
||||||
export const CatalogueCompanyGroupTabs = ({ activeTab, setActiveTab, gskData, gszData }: CataloguGroupTabsI) => {
|
export const CatalogueCompanyGroupTabs = ({ activeTab, setActiveTab, gskData, gszData }: CatalogueCompanyGroupTabsI) => {
|
||||||
const activeCompanyGroup = useAppSelector((state) => state.catalogue_jur.activeCompanyGroup);
|
const activeCompanyGroup = useAppSelector((state) => state.catalogue_jur.activeCompanyGroup);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import {
|
|||||||
} from '../../../services/api/intercompanyMatrix';
|
} from '../../../services/api/intercompanyMatrix';
|
||||||
import type { IntercompanyMatrixResponse, IntercompanyMatrixResultResponse } from '../../../types/api';
|
import type { IntercompanyMatrixResponse, IntercompanyMatrixResultResponse } from '../../../types/api';
|
||||||
import type { VersionCard } from '../../../types/consolidations/version_members';
|
import type { VersionCard } from '../../../types/consolidations/version_members';
|
||||||
|
import { chessMatrixMode } from '../../../utils/chessMatrixMode';
|
||||||
import { saveBlob, xlsxFilename } from '../../../utils/download';
|
import { saveBlob, xlsxFilename } from '../../../utils/download';
|
||||||
import { useNotificationApi } from '../../../utils/useNotification';
|
import { useNotificationApi } from '../../../utils/useNotification';
|
||||||
import { ConsolidationChessFormatTableFilter } from '../ConsolidationChessFormatTableFilter/ConsolidationChessFormatTableFilter';
|
import { ConsolidationChessFormatTableFilter } from '../ConsolidationChessFormatTableFilter/ConsolidationChessFormatTableFilter';
|
||||||
@ -72,14 +73,15 @@ export const ChessContent = ({ card }: { card: VersionCard | null }) => {
|
|||||||
return () => document.removeEventListener('fullscreenchange', handleFullscreenChange);
|
return () => document.removeEventListener('fullscreenchange', handleFullscreenChange);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const isHalfFiltered = (debit.length > 0 && credit.length === 0) || (debit.length === 0 && credit.length > 0);
|
const matrixMode = chessMatrixMode(debit, credit);
|
||||||
|
const isHalfFiltered = matrixMode === 'half';
|
||||||
|
|
||||||
const loadData = useCallback(
|
const loadData = useCallback(
|
||||||
async (vId: string, dIds: string[], cIds: string[], isActual: () => boolean) => {
|
async (vId: string, dIds: string[], cIds: string[], isActual: () => boolean) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
let res: IntercompanyMatrixResponse;
|
let res: IntercompanyMatrixResponse;
|
||||||
if (dIds?.length && cIds?.length) {
|
if (chessMatrixMode(dIds, cIds) === 'manual') {
|
||||||
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 {
|
||||||
res = await fetchChessConsolidations(vId);
|
res = await fetchChessConsolidations(vId);
|
||||||
@ -97,7 +99,7 @@ export const ChessContent = ({ card }: { card: VersionCard | null }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[notification, isHalfFiltered],
|
[notification],
|
||||||
);
|
);
|
||||||
const loadResData = useCallback(
|
const loadResData = useCallback(
|
||||||
async (vid: string) => {
|
async (vid: string) => {
|
||||||
@ -124,7 +126,7 @@ export const ChessContent = ({ card }: { card: VersionCard | null }) => {
|
|||||||
}
|
}
|
||||||
setMatrixExportLoading(true);
|
setMatrixExportLoading(true);
|
||||||
try {
|
try {
|
||||||
if (debit?.length && credit?.length) {
|
if (matrixMode === 'manual') {
|
||||||
const { blob, filename } = await exportChessTableManual(version_id, { debit_account_ids: debit, credit_account_ids: credit });
|
const { blob, filename } = await exportChessTableManual(version_id, { debit_account_ids: debit, credit_account_ids: credit });
|
||||||
saveBlob(blob, filename ?? xlsxFilename('Шахматка', versionInfo));
|
saveBlob(blob, filename ?? xlsxFilename('Шахматка', versionInfo));
|
||||||
} else {
|
} else {
|
||||||
@ -167,7 +169,8 @@ export const ChessContent = ({ card }: { card: VersionCard | null }) => {
|
|||||||
}, [data, companies]);
|
}, [data, companies]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!version_id) return;
|
// выбрана только одна сторона Дт/Кт — запрос не шлём, таблица показывает подсказку
|
||||||
|
if (!version_id || isHalfFiltered) return;
|
||||||
|
|
||||||
let isMounted = true;
|
let isMounted = true;
|
||||||
const isActual = () => isMounted;
|
const isActual = () => isMounted;
|
||||||
@ -178,7 +181,7 @@ export const ChessContent = ({ card }: { card: VersionCard | null }) => {
|
|||||||
isMounted = false;
|
isMounted = false;
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
};
|
};
|
||||||
}, [version_id, debit, credit, loadData]);
|
}, [version_id, debit, credit, isHalfFiltered, loadData]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!version_id) return;
|
if (!version_id) return;
|
||||||
@ -202,7 +205,7 @@ export const ChessContent = ({ card }: { card: VersionCard | null }) => {
|
|||||||
style={{
|
style={{
|
||||||
backgroundColor: isChessFullscreen ? '#fff' : 'transparent',
|
backgroundColor: isChessFullscreen ? '#fff' : 'transparent',
|
||||||
padding: isChessFullscreen ? 6 : 0,
|
padding: isChessFullscreen ? 6 : 0,
|
||||||
height: isChessFullscreen ? '100vh' : '100%',
|
height: isChessFullscreen ? '100vh' : 'auto',
|
||||||
overflow: isChessFullscreen ? 'hidden' : 'visible',
|
overflow: isChessFullscreen ? 'hidden' : 'visible',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
|
|||||||
@ -17,15 +17,6 @@
|
|||||||
background-color: #e6f4ea !important ;
|
background-color: #e6f4ea !important ;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chessTable .ant-table-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chessTable.customHeight .ant-table-container {
|
.chessTable.customHeight .ant-table-container {
|
||||||
height: 550px !important;
|
min-height: 550px;
|
||||||
}
|
|
||||||
|
|
||||||
.chessTable .ant-table-summary {
|
|
||||||
margin-top: auto;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import type React from 'react';
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import type { IntercompanyMatrixResponse, MatrixCell, MatrixCompany } from '../../../types/api';
|
import type { IntercompanyMatrixResponse, MatrixCell, MatrixCompany } from '../../../types/api';
|
||||||
import './ConsolidatedChessTable.css';
|
import './ConsolidatedChessTable.css';
|
||||||
|
import { CHESS_HALF_FILTER_HINT } from '../../../utils/chessMatrixMode';
|
||||||
import { chessTableCellFormatValue } from '../../../utils/chessTableCellFormatValue';
|
import { chessTableCellFormatValue } from '../../../utils/chessTableCellFormatValue';
|
||||||
import { sumAmounts } from '../../../utils/money';
|
import { sumAmounts } from '../../../utils/money';
|
||||||
|
|
||||||
@ -111,6 +112,7 @@ export const ConsolidationChessTable: React.FC<ConsolidationChessTableI> = ({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// сумму, которую не удалось сложить, не выдумываем нулём — ячейка покажет «—»
|
||||||
totalRow[colCompany.id] = sumAmounts(amountsToSum) ?? '';
|
totalRow[colCompany.id] = sumAmounts(amountsToSum) ?? '';
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -121,9 +123,9 @@ export const ConsolidationChessTable: React.FC<ConsolidationChessTableI> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Table<MatrixCompany>
|
<Table<MatrixCompany>
|
||||||
className={!isFullscreen ? 'chessTable customHeight' : 'chessTable'}
|
className={isHalfFiltered && !isFullscreen ? 'chessTable customHeight' : 'chessTable'}
|
||||||
dataSource={isHalfFiltered ? [] : data?.companies || []}
|
dataSource={isHalfFiltered ? [] : data?.companies || []}
|
||||||
columns={isHalfFiltered ? [] : columns || []}
|
columns={isHalfFiltered ? [] : columns}
|
||||||
rowKey='id'
|
rowKey='id'
|
||||||
pagination={false}
|
pagination={false}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
@ -133,7 +135,7 @@ export const ConsolidationChessTable: React.FC<ConsolidationChessTableI> = ({
|
|||||||
title={() => toolbar}
|
title={() => toolbar}
|
||||||
locale={{
|
locale={{
|
||||||
emptyText: isHalfFiltered ? (
|
emptyText: isHalfFiltered ? (
|
||||||
<div style={{ padding: '40px 0', color: '#8c8c8c', fontSize: 16 }}>Для получения данных выберите счета по Дт и Кт</div>
|
<div style={{ padding: '40px 0', color: '#8c8c8c', fontSize: 16 }}>{CHESS_HALF_FILTER_HINT}</div>
|
||||||
) : (
|
) : (
|
||||||
'Нет данных'
|
'Нет данных'
|
||||||
),
|
),
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { useEffect, useState } from 'react';
|
|||||||
import { ApiError, api } from '../../../api/client';
|
import { ApiError, api } from '../../../api/client';
|
||||||
import { type OptionType, TreeFilterDropdown } from '../../../components/TreeFilterDropdown/TreeFilterDropdown';
|
import { type OptionType, TreeFilterDropdown } from '../../../components/TreeFilterDropdown/TreeFilterDropdown';
|
||||||
import type { Account, MatrixCompany, Quarter } from '../../../types/api';
|
import type { Account, MatrixCompany, Quarter } from '../../../types/api';
|
||||||
|
import { CHESS_HALF_FILTER_HINT, chessMatrixMode } from '../../../utils/chessMatrixMode';
|
||||||
import { quarterShortLabel } from '../../../utils/labels';
|
import { quarterShortLabel } from '../../../utils/labels';
|
||||||
import { useNotificationApi } from '../../../utils/useNotification';
|
import { useNotificationApi } from '../../../utils/useNotification';
|
||||||
|
|
||||||
@ -109,7 +110,8 @@ export const ConsolidationChessTableFilter: React.FC<ConsolidationChessTableFilt
|
|||||||
loadAccounts();
|
loadAccounts();
|
||||||
}, [notification]);
|
}, [notification]);
|
||||||
|
|
||||||
const warningBlock = <span style={{ color: '#d46b08', fontSize: 13 }}>Выберите счета по Дт и Кт для фильтрации</span>;
|
const warningBlock =
|
||||||
|
chessMatrixMode(debit, credit) === 'half' ? <span style={{ color: '#d46b08', fontSize: 13 }}>{CHESS_HALF_FILTER_HINT}</span> : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Space size='middle' wrap>
|
<Space size='middle' wrap>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user