docs: add pagination loading overlay design spec (C3)
This commit is contained in:
parent
49ee364856
commit
cf208e059e
@ -0,0 +1,116 @@
|
|||||||
|
# Индикация загрузки при переключении страниц в таблицах брокера
|
||||||
|
|
||||||
|
Дата: 2026-06-18
|
||||||
|
Статус: черновик
|
||||||
|
|
||||||
|
## Контекст
|
||||||
|
|
||||||
|
Страница детального просмотра брокерского счёта (`BrokerAccountDetailPage.tsx`) содержит несколько таблиц с пагинацией:
|
||||||
|
|
||||||
|
- **PositionGroupTable** — Акции, Облигации, ETF, Фонды (4 независимые таблицы с курсорной пагинацией)
|
||||||
|
- **BrokerOperationsTable** — Операции (курсорная пагинация, управляемая из родительского компонента)
|
||||||
|
|
||||||
|
Текущее поведение при переключении страниц: `isLoading === true` → таблица скрывается, показывается `TableSkeleton` (shimmer-строки). Это создаёт визуальный flash: контент исчезает → скелетон → новые данные. При этом `placeholderData: keepPreviousData` уже настроен в хуках, но компоненты его не используют — они проверяют `isLoading`, а не `data`.
|
||||||
|
|
||||||
|
## Цель
|
||||||
|
|
||||||
|
Добавить плавную индикацию загрузки при переключении страниц, чтобы пользователь видел, что данные обновляются, но не терял визуальный контекст.
|
||||||
|
|
||||||
|
## Дизайн (выбран C3)
|
||||||
|
|
||||||
|
### Визуальное поведение
|
||||||
|
|
||||||
|
1. При нажатии «→» (вперед) или «←» (назад):
|
||||||
|
- Текущее содержимое таблицы **остаётся видимым** (предыдущая страница)
|
||||||
|
- Поверх таблицы появляется **полупрозрачный overlay** с центрированным спиннером
|
||||||
|
- Кнопка пагинации показывает спиннер и блокируется
|
||||||
|
2. Когда новые данные загружены:
|
||||||
|
- Overlay исчезает с fade-out
|
||||||
|
- Таблица обновляется новыми данными
|
||||||
|
3. При первой загрузке (initial load):
|
||||||
|
- Overlay не используется (нет старых данных для показа)
|
||||||
|
- Показывается `TableSkeleton` (как сейчас)
|
||||||
|
|
||||||
|
### Как это работает технически
|
||||||
|
|
||||||
|
TanStack Query v5 предоставляет два флага:
|
||||||
|
|
||||||
|
- `isLoading` — true, когда данных **нет** и идёт первый запрос (initial load)
|
||||||
|
- `isFetching` — true при любом запросе (включая фоновые refetch'и при смене cursor)
|
||||||
|
|
||||||
|
Логика рендеринга для таблиц:
|
||||||
|
|
||||||
|
```
|
||||||
|
if isLoading → TableSkeleton (первая загрузка, данных нет)
|
||||||
|
if isFetching && data → TableLoadingOverlay + старые данные (переключение страниц)
|
||||||
|
иначе → рендер таблицы с данными
|
||||||
|
```
|
||||||
|
|
||||||
|
### Компонент TableLoadingOverlay
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
interface TableLoadingOverlayProps {
|
||||||
|
pageNumber?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function TableLoadingOverlay({ pageNumber }: TableLoadingOverlayProps) {
|
||||||
|
return (
|
||||||
|
<div style={{
|
||||||
|
position: 'absolute', inset: 0,
|
||||||
|
background: 'rgba(255,255,255,0.65)',
|
||||||
|
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||||
|
flexDirection: 'column', gap: 12,
|
||||||
|
transition: 'opacity 0.2s ease',
|
||||||
|
}}>
|
||||||
|
<div className="loading-spinner" />
|
||||||
|
{pageNumber !== undefined && (
|
||||||
|
<span style={{ fontSize: 13, color: 'var(--color-text-secondary)' }}>
|
||||||
|
Загрузка страницы {pageNumber}…
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Пагинация: спиннер в кнопке
|
||||||
|
|
||||||
|
При `isFetching` кнопка «→» или «←» показывает спиннер вместо стрелки и становится disabled.
|
||||||
|
|
||||||
|
```css
|
||||||
|
@keyframes loading-spin {
|
||||||
|
to { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-spinner {
|
||||||
|
width: 20px; height: 20px;
|
||||||
|
border: 2px solid var(--color-border);
|
||||||
|
border-top-color: var(--color-accent);
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: loading-spin 0.8s linear infinite;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Где применяется
|
||||||
|
|
||||||
|
| Компонент | Что меняется |
|
||||||
|
|---|---|
|
||||||
|
| `BrokerPositionsSection.tsx` (PositionGroupTable) | Overlay вместо TableSkeleton при isFetching. Спиннер в кнопках пагинации |
|
||||||
|
| `BrokerOperationsTable.tsx` | Overlay вместо TableSkeleton при isFetching. Спиннер в кнопках пагинации |
|
||||||
|
|
||||||
|
## Файлы для изменения
|
||||||
|
|
||||||
|
| Файл | Изменение |
|
||||||
|
|---|---|
|
||||||
|
| `apps/frontend/src/styles.css` | Добавить `@keyframes loading-spin`, `.loading-spinner`, `.table-loading-overlay` |
|
||||||
|
| `apps/frontend/src/pages/broker/BrokerPositionsSection.tsx` | Overlay + спиннер в пагинации. Использовать `isFetching` из хука |
|
||||||
|
| `apps/frontend/src/pages/broker/BrokerOperationsTable.tsx` | Overlay + спиннер в пагинации. Использовать `isFetching` из хука |
|
||||||
|
| `apps/frontend/src/pages/broker/BrokerAccountDetailPage.tsx` | Прокинуть `isFetching` для операций (из useBrokerOperations) |
|
||||||
|
| `apps/frontend/src/pages/broker/BrokerPages.test.tsx` | Обновить тесты для overlay-логики |
|
||||||
|
|
||||||
|
## Тестирование
|
||||||
|
|
||||||
|
- `npm run test:frontend` — существующие тесты проходят с учётом изменений
|
||||||
|
- Ручная проверка: переключение страниц в Акциях, Облигациях, Операциях — overlay появляется/исчезает
|
||||||
|
- Ручная проверка: при первой загрузке — skeleton (не overlay)
|
||||||
|
- Ручная проверка: при быстром переключении (быстрее, чем загрузка) — overlay остаётся, данные не мигают
|
||||||
Loading…
x
Reference in New Issue
Block a user