diff --git a/openspec/changes/files-pagination/proposal.md b/openspec/changes/files-pagination/proposal.md new file mode 100644 index 0000000..34134bf --- /dev/null +++ b/openspec/changes/files-pagination/proposal.md @@ -0,0 +1,36 @@ +# Proposal + +## Why + +AURORA-1816 (PR arm-core #242). Панель «Первичные документы» запрашивала файлы ЮЛ с `limit=100` и не догружала +остальные: в режиме «Все документы организации» у ЮЛ с сотней файлов и больше лишние не видны ни в панели, ни в +переключателе документа на экране проверки, и пользователь не знает, что список неполный. Сервер пагинацию уже +поддерживает (`total`, `offset`, `limit` до 200). + +## What Changes + +- Панель «Первичные документы»: над списком «Всего N файлов» (`total` ответа); файлы страницами по 200, следующая — + при прокрутке до конца списка. +- Опрос раз в 5 секунд и перечитывание после удаления, загрузки и подтверждения перечитывают все показанные страницы, + список не сворачивается к первой. +- Переключатель «Документ» на экране проверки берёт тот же список: догрузка при прокрутке выпадающего списка, открытый + документ есть в списке, даже если он дальше загруженных страниц. + +## Capabilities + +### Modified Capabilities + +- `primary-files-ui` — новое требование «Догрузка списка файлов»; «Опрос до окончания распознавания» перечитывает все + показанные страницы. +- `document-review-ui` — «Переключатель документа»: догрузка и открытый документ в списке. + +## Impact + +Только фронт arm-core: `web/src/hooks/useLegalEntityFiles.ts` (перенесён из `widgets/FilesDrawer/`), +`web/src/hooks/useScrollSentinel.ts`, `web/src/services/api/files.ts`, `web/src/widgets/FilesDrawer/*`, +`web/src/layouts/DocumentPage.tsx`. API, DBML, правила `05-domain-rules.md` не меняются. + +## Non-goals + +- Кнопка «Показать ещё» — выбрана подгрузка при прокрутке (задача допускала оба варианта). +- Серверный поиск и фильтры по файлам. diff --git a/openspec/changes/files-pagination/specs/document-review-ui/spec.md b/openspec/changes/files-pagination/specs/document-review-ui/spec.md new file mode 100644 index 0000000..03f2998 --- /dev/null +++ b/openspec/changes/files-pagination/specs/document-review-ui/spec.md @@ -0,0 +1,22 @@ +## MODIFIED Requirements + +### Requirement: Переключатель документа +В шапке экрана документа SHALL быть выпадающий список «Документ» с документами файлов того же ЮЛ в той же консолидации, +что и файл открытого документа (файл вне консолидации — все файлы ЮЛ): у пункта имя файла, вторая строка (тип · формат · +дата) и статус; пункты, которые нельзя открыть, неактивны. Список читается теми же страницами, что панель «Первичные документы» +(требование «Догрузка списка файлов» `primary-files-ui`): следующая — при прокрутке выпадающего списка до конца; +открытый документ SHALL быть в списке и показываться по имени, даже если его файл дальше загруженных страниц. Выбор пункта SHALL открывать `/documents/:document_id` +выбранного документа заменой текущей записи истории браузера, без новой записи: кнопка «Назад» экрана и браузера +возвращает на экран, с которого открыт первый документ, а не к предыдущему выбранному. Если первый документ открыт из +панели «Первичные документы» консолидации, «Назад» SHALL открывать экран консолидации с этой панелью той же организации. + +#### Scenario: Назад после перебора документов +- **WHEN** аналитик открыл документ из панели «Первичные документы» организации в карточке консолидации, выбрал в списке + «Документ» подряд ещё два документа и нажал «Назад» +- **THEN** открыт экран консолидации с той же выбранной версией и открытой панелью «Первичные документы» той же + организации + +#### Scenario: Документ открыт прямой ссылкой +- **WHEN** документ открыт прямой ссылкой в новой вкладке, аналитик выбрал в списке «Документ» другой документ и нажал + «Назад» экрана +- **THEN** открыт список групп компаний `/groups` diff --git a/openspec/changes/files-pagination/specs/primary-files-ui/spec.md b/openspec/changes/files-pagination/specs/primary-files-ui/spec.md new file mode 100644 index 0000000..ac07699 --- /dev/null +++ b/openspec/changes/files-pagination/specs/primary-files-ui/spec.md @@ -0,0 +1,29 @@ +## ADDED Requirements + +### Requirement: Догрузка списка файлов +Панель SHALL читать список файлов активного режима (стол или все документы) страницами по 200 (`limit=200&offset=…`, +`MAX_PAGE_LIMIT` бэка): первую — при открытии, следующую — когда аналитик прокрутил список до конца, пока показано +меньше `total`. Над списком SHALL стоять «Всего N файлов» с `total` ответа. Опрос, удаление, загрузка и подтверждение +документа SHALL перечитывать все показанные файлы (столько страниц, сколько было показано), а не только первую. +Перечитывание отменяет идущую догрузку: её ответ не применяется, индикатор догрузки гаснет. + +#### Scenario: 450 файлов в режиме «все» +- **WHEN** у ЮЛ 450 файлов, панель открыта в режиме «Все документы организации» +- **THEN** над списком «Всего 450 файлов», показаны первые 200; прокрутка до конца догружает 201–400, ещё одна — + 401–450, дальше запросов нет + +#### Scenario: Опрос не сворачивает список +- **WHEN** показаны 400 файлов и среди них есть файл в `PROCESSING` +- **THEN** раз в 5 секунд перечитываются обе показанные страницы, после ответа в списке по-прежнему 400 файлов + +## MODIFIED Requirements + +### Requirement: Опрос до окончания распознавания +Пока панель открыта и среди файлов активного списка (стол или все документы) есть `UPLOADED` или `PROCESSING`, панель +SHALL перечитывать этот список раз в 5 секунд — все показанные страницы, не сворачивая список к первой (требование «Догрузка списка файлов»), и SHALL прекратить опрос, когда таких файлов нет или панель закрыта. +Переключение режима или смена ЮЛ SHALL сбрасывать список и перечитывать его. При закрытии панели карточка версии SHALL +перечитываться (обновить ячейки чек-листа). + +#### Scenario: Опрос останавливается +- **WHEN** все файлы активного списка перешли в `PROCESSED` или `ERROR` +- **THEN** запросы списка файлов прекращаются diff --git a/openspec/changes/files-pagination/tasks.md b/openspec/changes/files-pagination/tasks.md new file mode 100644 index 0000000..ea7feb0 --- /dev/null +++ b/openspec/changes/files-pagination/tasks.md @@ -0,0 +1,10 @@ +# Tasks + +- [x] 1.1 arm-core `web/src/services/api/files.test.ts` — несколько страниц: перечитывание показанного, догрузка с + позиции, конец по `total`, пустая страница раньше `total` +- [x] 1.2 arm-core `web/src/services/api/files.ts` — `listLegalEntityFiles(offset, limit)`, `listLegalEntityFilesFrom`, + `FILES_PAGE_LIMIT = 200` +- [x] 1.3 arm-core `web/` — хук `useLegalEntityFiles` со страницами, `useScrollSentinel`, «Всего N файлов» в панели, + переключатель документа на экране проверки (PR #242, автор) +- [x] 1.4 Ревью: перечитывание во время догрузки сбрасывает `loadingMore` (иначе спиннер под списком не гаснет); + `biome-ignore` для `trigger` в `useScrollSentinel` с причиной diff --git a/web/src/hooks/useLegalEntityFiles.ts b/web/src/hooks/useLegalEntityFiles.ts index 60c7e8d..41763fe 100644 --- a/web/src/hooks/useLegalEntityFiles.ts +++ b/web/src/hooks/useLegalEntityFiles.ts @@ -30,6 +30,8 @@ export function useLegalEntityFiles(legalEntityId: string | null, consolidationI const setBusy = append ? setLoadingMore : setLoading; const requestId = ++requestRef.current; inFlightRef.current = true; + // перечитывание отменяет идущую догрузку: её ответ отбросится, а finally не сбросит флаг чужого запроса + if (!append) setLoadingMore(false); setBusy(true); try { const { items, total } = await listLegalEntityFilesFrom(legalEntityId, consolidationId, from, count); diff --git a/web/src/hooks/useScrollSentinel.ts b/web/src/hooks/useScrollSentinel.ts index 500d62c..20af470 100644 --- a/web/src/hooks/useScrollSentinel.ts +++ b/web/src/hooks/useScrollSentinel.ts @@ -8,6 +8,7 @@ export function useScrollSentinel(enabled: boolean, trigger: unknown, onReach: ( const ref = useRef(null); const onReachRef = useRef(onReach); onReachRef.current = onReach; + // biome-ignore lint/correctness/useExhaustiveDependencies: trigger — переподписка после новой порции: если метка всё ещё на экране, наблюдатель сразу вызовет onReach useEffect(() => { const node = ref.current; if (!enabled || !node) return;