diff --git a/web/src/widgets/ConsolidatedOsv/CounterPairsView.tsx b/web/src/widgets/ConsolidatedOsv/CounterPairsView.tsx index 44b0895..6831517 100644 --- a/web/src/widgets/ConsolidatedOsv/CounterPairsView.tsx +++ b/web/src/widgets/ConsolidatedOsv/CounterPairsView.tsx @@ -8,7 +8,7 @@ import { getConsolidatedOsv } from '../../services/api/osv'; import type { ConsolidatedRowOut, CounterPairOut, OsvFilters } from '../../types/api'; import { CounterpartyMapModal } from './CounterpartyMapModal'; import { group, money } from './OsvTable'; -import { clampPage, missingSideEmptyText, pairLabel } from './counterPairs'; +import { clampPage, currentPage, missingSideEmptyText, pairLabel } from './counterPairs'; import { mapEntryId } from './counterpartyMapping'; import { OSV_PAGE_SIZE, osvErrorText } from './useConsolidatedOsv'; @@ -33,15 +33,14 @@ const PANEL_TITLE: React.CSSProperties = { fontWeight: 700, fontSize: 13, color: function usePagedRows(versionId: string, filters: OsvFilters | null, reloadKey: number) { const [rows, setRows] = useState([]); const [total, setTotal] = useState(0); - const [page, setPage] = useState(1); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const requestRef = useRef(0); const key = JSON.stringify(filters); - - // смена фильтра — снова первая страница - // biome-ignore lint/correctness/useExhaustiveDependencies: key — сериализованный filters - useEffect(() => setPage(1), [key]); + // ключ и страница — одно состояние: смена фильтра даёт первую страницу и один запрос, а не два + const [pageState, setPageState] = useState({ key, page: 1 }); + const page = currentPage(pageState, key); + const setPage = useCallback((p: number) => setPageState({ key, page: p }), [key]); // biome-ignore lint/correctness/useExhaustiveDependencies: key — сериализованный filters; reloadKey — перечитать после сопоставления useEffect(() => { @@ -99,7 +98,7 @@ export function CounterPairsView({ versionId, pairs, selected, numFormat, onSele }, [selectedKey]); const centerFilters: OsvFilters | null = selected ? { legal_entity_id: selected.holder_id, counterparty_id: selected.missing_id } : null; - // таблицу справа не запрашиваем, если у Б нет строк или нет строк без ИНН: показываем текст + // нижнюю таблицу не запрашиваем, если у Б нет строк или нет строк без ИНН: показываем текст const rightHasRows = !!selected && missingSideEmptyText(selected) === null; const rightFilters: OsvFilters | null = selected && rightHasRows ? { legal_entity_id: selected.missing_id, unmapped: true, ...(debouncedQ ? { q: debouncedQ } : {}) } : null; @@ -146,7 +145,7 @@ export function CounterPairsView({ versionId, pairs, selected, numFormat, onSele const rightEmpty = missingSideEmptyText(selected); return ( -
+
{pairs.map((p) => { const active = p.holder_id === selected.holder_id && p.missing_id === selected.missing_id; @@ -186,6 +185,7 @@ export function CounterPairsView({ versionId, pairs, selected, numFormat, onSele columns={baseColumns} dataSource={center.rows} tableLayout='fixed' + scroll={{ x: 750 }} pagination={{ current: center.page, pageSize: OSV_PAGE_SIZE, @@ -219,6 +219,7 @@ export function CounterPairsView({ versionId, pairs, selected, numFormat, onSele columns={rightColumns} dataSource={right.rows} tableLayout='fixed' + scroll={{ x: 850 }} locale={{ emptyText: debouncedQ ? 'По поиску строк нет' : 'Строк нет' }} pagination={{ current: right.page, diff --git a/web/src/widgets/ConsolidatedOsv/counterPairs.test.ts b/web/src/widgets/ConsolidatedOsv/counterPairs.test.ts index ff3d26b..7b0e3d8 100644 --- a/web/src/widgets/ConsolidatedOsv/counterPairs.test.ts +++ b/web/src/widgets/ConsolidatedOsv/counterPairs.test.ts @@ -1,6 +1,15 @@ import { describe, expect, it } from 'vitest'; import type { CounterPairOut } from '../../types/api'; -import { clampPage, missingSideEmptyText, pairLabel, pairsViewSearch, readPairsView, selectPair, switchPairsView } from './counterPairs'; +import { + clampPage, + currentPage, + missingSideEmptyText, + pairLabel, + pairsViewSearch, + readPairsView, + selectPair, + switchPairsView, +} from './counterPairs'; const pair = (holder: string, missing: string): CounterPairOut => ({ holder_id: holder, @@ -86,3 +95,15 @@ describe('clampPage (T5.6)', () => { expect(clampPage(4, 0, 500)).toBe(1); }); }); + +describe('currentPage (M-8)', () => { + it('ключ тот же — сохранённая страница', () => { + expect(currentPage({ key: 'a', page: 3 }, 'a')).toBe(3); + }); + it('ключ сменился — первая страница без отдельного обновления состояния', () => { + expect(currentPage({ key: 'a', page: 3 }, 'b')).toBe(1); + }); + it('вернулись к прежнему ключу после смены страницы под новым — страница нового', () => { + expect(currentPage({ key: 'b', page: 2 }, 'b')).toBe(2); + }); +}); diff --git a/web/src/widgets/ConsolidatedOsv/counterPairs.ts b/web/src/widgets/ConsolidatedOsv/counterPairs.ts index 1ff4e9d..1c9c90b 100644 --- a/web/src/widgets/ConsolidatedOsv/counterPairs.ts +++ b/web/src/widgets/ConsolidatedOsv/counterPairs.ts @@ -29,7 +29,7 @@ export const selectPair = (pairs: readonly CounterPairOut[], holder: string | nu export const pairLabel = (p: CounterPairOut): string => `${p.missing_name} ← ${p.holder_name}`; -/** Текст вместо правой таблицы (строки Б без ИНН); `null` — таблицу показываем. */ +/** Текст вместо нижней таблицы (строки Б без ИНН); `null` — таблицу показываем. */ export const missingSideEmptyText = (p: CounterPairOut): string | null => { if (p.missing_rows_total === 0) return `У ${p.missing_name} в своде нет строк: ОСВ не привязаны или по ним пробелы`; if (p.missing_unmapped_rows === 0) @@ -40,3 +40,6 @@ export const missingSideEmptyText = (p: CounterPairOut): string | null => { /** Страница после ответа: не дальше последней существующей (последнюю строку сопоставили), не меньше первой. */ export const clampPage = (page: number, total: number, pageSize: number): number => Math.min(page, Math.max(1, Math.ceil(total / pageSize))); + +/** Страница, привязанная к ключу фильтра: другой ключ — первая страница сразу, без отдельного обновления состояния (один запрос). */ +export const currentPage = (state: { key: string; page: number }, key: string): number => (state.key === key ? state.page : 1);