From ea6915c52fe77f488ea0ad9d544488f4b72820fc Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Thu, 29 Oct 2026 13:18:13 +0400 Subject: [PATCH] =?UTF-8?q?fix(svod2):=20=D0=BF=D1=80=D0=B8=20=D0=BF=D0=BE?= =?UTF-8?q?=D0=B8=D1=81=D0=BA=D0=B5/=D1=84=D0=B8=D0=BB=D1=8C=D1=82=D1=80?= =?UTF-8?q?=D0=B5=20=D0=BD=D0=B0=20=D0=B3=D0=BB=D1=83=D0=B1=D0=B8=D0=BD?= =?UTF-8?q?=D0=B5=20total=20=D0=B1=D0=B5=D1=80=D1=91=D1=82=D1=81=D1=8F=20?= =?UTF-8?q?=D0=B8=D0=B7=20=D0=B7=D0=B0=D0=BF=D0=B0=D1=81=D0=BD=D0=BE=D0=B3?= =?UTF-8?q?=D0=BE=20offset=3D0,=20=D0=B0=20=D0=BD=D0=B5=20=D0=B8=D0=B7=20?= =?UTF-8?q?=D0=B4=D0=BE=D0=BB=D0=B3=D0=BE=D0=B3=D0=BE=20=D0=BE=D1=82=D0=B2?= =?UTF-8?q?=D0=B5=D1=82=D0=B0=20=D0=B7=D0=B0=20=D0=BA=D0=BE=D0=BD=D1=86?= =?UTF-8?q?=D0=BE=D0=BC=20=D1=81=D0=BF=D0=B8=D1=81=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- web/src/pages/Svod2Page/useSvod2Pages.js | 40 +++++++++++++++++++++--- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/web/src/pages/Svod2Page/useSvod2Pages.js b/web/src/pages/Svod2Page/useSvod2Pages.js index af0a00e..2c02353 100644 --- a/web/src/pages/Svod2Page/useSvod2Pages.js +++ b/web/src/pages/Svod2Page/useSvod2Pages.js @@ -17,6 +17,7 @@ import { getVisiblePage, getWindowPages, getWindowRange, + shouldFetchFirstPageFallback, shouldRefine, svod2PagingReducer, } from './svod2Paging'; @@ -59,7 +60,7 @@ export function useSvod2Pages({ sheet, year, orgIds, queryKey, search, columnFil if (mountedRef.current) setState(next); }, []); - const fetchPage = useCallback(async (ctx, pageIndex) => { + const fetchPage = useCallback(async (ctx, pageIndex, signal = ctx.controller.signal) => { ctx.inflight.add(pageIndex); try { const response = await SummaryApi.getSvod2Page( @@ -71,7 +72,7 @@ export function useSvod2Pages({ sheet, year, orgIds, queryKey, search, columnFil columnFilters: ctx.columnFilters, offset: pageIndex * PAGE_SIZE, }), - { signal: ctx.controller.signal }, + { signal }, ); const rows = response.result || []; return { rows, total: response.total ?? rows.length }; @@ -138,14 +139,43 @@ export function useSvod2Pages({ sheet, year, orgIds, queryKey, search, columnFil const loadPages = async () => { try { - const first = await fetchPage(ctx, candidate); + // Страница по прокрутке и (на глубине) запасная offset=0 летят параллельно: если страница по прокрутке + // за концом нового списка, сервер отвечает на неё долго и без строк, а страница 0 уже даёт total. + const candidateController = new AbortController(); + const zeroController = new AbortController(); + const abortRequests = () => { + candidateController.abort(); + zeroController.abort(); + }; + ctx.controller.signal.addEventListener('abort', abortRequests); + const settle = (request, index) => + request.then( + (result) => ({ result, index }), + (error) => ({ error, index }), + ); + const candidateRequest = settle(fetchPage(ctx, candidate, candidateController.signal), candidate); + const zeroRequest = candidate > 0 ? settle(fetchPage(ctx, 0, zeroController.signal), 0) : null; + let answer = zeroRequest ? await Promise.race([candidateRequest, zeroRequest]) : await candidateRequest; + if ( + answer.index === 0 && + candidate > 0 && + !answer.error && + shouldFetchFirstPageFallback({ candidate, total: answer.result.total }) + ) { + candidateController.abort(); + } else { + zeroController.abort(); + answer = await candidateRequest; + if (answer.error) throw answer.error; + } if (ctx.controller.signal.aborted) return; + const first = answer.result; const { total } = first; const pages = getWindowFor(total); const pageCount = getPageCount(total); let cache = new Map(); - if (total === 0 || candidate < pageCount) { - const pageIndex = total === 0 ? 0 : candidate; + if (total === 0 || answer.index < pageCount) { + const pageIndex = total === 0 ? 0 : answer.index; dispatch({ type: 'page', sheet: sheetKey, generation, pageIndex, rows: first.rows, total, keepPages: pages }); cache = stateRef.current[sheetKey].pages; }