From d230fb13ee0e462cbe707fb5a9c8cd021e7d9191 Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Tue, 27 Oct 2026 22:26:05 +0400 Subject: [PATCH] =?UTF-8?q?fix(web):=20=D1=81=D0=BC=D0=B5=D0=BD=D0=B0=20?= =?UTF-8?q?=D0=BA=D0=BB=D1=8E=D1=87=D0=B0=20=D1=84=D0=B8=D0=BB=D1=8C=D1=82?= =?UTF-8?q?=D1=80=D0=B0=20=D0=BF=D0=B0=D1=80=20=D1=81=D0=B1=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D1=8B=D0=B2=D0=B0=D0=B5=D1=82=20=D1=81=D1=82=D1=80=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D1=86=D1=83=20=D0=B2=20=D1=81=D0=BE=D1=81=D1=82?= =?UTF-8?q?=D0=BE=D1=8F=D0=BD=D0=B8=D0=B8=20(M-13)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Возврат A→B→A даёт первую страницу, а не прежнюю. Co-Authored-By: Claude Opus 5.5 --- .../ConsolidatedOsv/CounterPairsView.tsx | 9 ++++++--- .../ConsolidatedOsv/counterPairs.test.ts | 18 ++++++++++-------- .../widgets/ConsolidatedOsv/counterPairs.ts | 5 +++-- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/web/src/widgets/ConsolidatedOsv/CounterPairsView.tsx b/web/src/widgets/ConsolidatedOsv/CounterPairsView.tsx index 6831517..f6d9743 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, currentPage, missingSideEmptyText, pairLabel } from './counterPairs'; +import { clampPage, missingSideEmptyText, pairLabel, syncPageState } from './counterPairs'; import { mapEntryId } from './counterpartyMapping'; import { OSV_PAGE_SIZE, osvErrorText } from './useConsolidatedOsv'; @@ -38,8 +38,11 @@ function usePagedRows(versionId: string, filters: OsvFilters | null, reloadKey: const requestRef = useRef(0); const key = JSON.stringify(filters); // ключ и страница — одно состояние: смена фильтра даёт первую страницу и один запрос, а не два - const [pageState, setPageState] = useState({ key, page: 1 }); - const page = currentPage(pageState, key); + const [rawPageState, setPageState] = useState({ key, page: 1 }); + const pageState = syncPageState(rawPageState, key); + // ключ сменился — состояние сразу пересоздаётся (возврат к прежнему ключу не вернёт старую страницу) + if (pageState !== rawPageState) setPageState(pageState); + const page = pageState.page; const setPage = useCallback((p: number) => setPageState({ key, page: p }), [key]); // biome-ignore lint/correctness/useExhaustiveDependencies: key — сериализованный filters; reloadKey — перечитать после сопоставления diff --git a/web/src/widgets/ConsolidatedOsv/counterPairs.test.ts b/web/src/widgets/ConsolidatedOsv/counterPairs.test.ts index 7b0e3d8..375609e 100644 --- a/web/src/widgets/ConsolidatedOsv/counterPairs.test.ts +++ b/web/src/widgets/ConsolidatedOsv/counterPairs.test.ts @@ -2,13 +2,13 @@ import { describe, expect, it } from 'vitest'; import type { CounterPairOut } from '../../types/api'; import { clampPage, - currentPage, missingSideEmptyText, pairLabel, pairsViewSearch, readPairsView, selectPair, switchPairsView, + syncPageState, } from './counterPairs'; const pair = (holder: string, missing: string): CounterPairOut => ({ @@ -96,14 +96,16 @@ describe('clampPage (T5.6)', () => { }); }); -describe('currentPage (M-8)', () => { - it('ключ тот же — сохранённая страница', () => { - expect(currentPage({ key: 'a', page: 3 }, 'a')).toBe(3); +describe('syncPageState (M-8, M-13)', () => { + it('ключ тот же — состояние то же', () => { + const s = { key: 'a', page: 3 }; + expect(syncPageState(s, 'a')).toBe(s); }); - it('ключ сменился — первая страница без отдельного обновления состояния', () => { - expect(currentPage({ key: 'a', page: 3 }, 'b')).toBe(1); + it('ключ сменился — {новый ключ, 1}', () => { + expect(syncPageState({ key: 'a', page: 3 }, 'b')).toEqual({ key: 'b', page: 1 }); }); - it('вернулись к прежнему ключу после смены страницы под новым — страница нового', () => { - expect(currentPage({ key: 'b', page: 2 }, 'b')).toBe(2); + it('A (стр. 3) → B → A: снова первая страница, а не прежняя', () => { + const onB = syncPageState({ key: 'a', page: 3 }, 'b'); + expect(syncPageState(onB, 'a')).toEqual({ key: 'a', page: 1 }); }); }); diff --git a/web/src/widgets/ConsolidatedOsv/counterPairs.ts b/web/src/widgets/ConsolidatedOsv/counterPairs.ts index 1c9c90b..d85852a 100644 --- a/web/src/widgets/ConsolidatedOsv/counterPairs.ts +++ b/web/src/widgets/ConsolidatedOsv/counterPairs.ts @@ -41,5 +41,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); +/** Состояние страницы под ключом фильтра: другой ключ — `{ключ, 1}`; прежнее состояние не хранится (A→B→A даёт первую страницу). */ +export const syncPageState = (state: { key: string; page: number }, key: string): { key: string; page: number } => + state.key === key ? state : { key, page: 1 };