Архивация списков (#9)
Tests / frontend-test (push) Successful in 1m12s

Co-authored-by: gorbenko.pavel <gorbenko.pavel@tansu.kz>
Reviewed-on: http://git.tnsu.kz/timur.gorodetskiy/request-portal/pulls/9
This commit was merged in pull request #9.
This commit is contained in:
2026-08-19 09:07:53 +00:00
parent 3a71d0df1f
commit 6f789f8f1c
67 changed files with 2617 additions and 901 deletions
@@ -16,6 +16,7 @@ import { IMyRequest } from './models/IMyRequest';
import { DataTable, IDataTableColumn } from '@tnsu/ui-kit';
import { REQUEST_STATUS, REQUEST_STATUS_CHOICES, IRequestType, StatusBadge } from '../../shared/approval';
import { onRequestsChanged } from '../../shared/requestEvents';
import { traced } from './perfTrace';
import { useRoutedQuery } from '../../shared/usePersistedQuery';
import { IPage, pageKey, pageSignature, readPage, writePage } from './pageCache';
import { Pager } from './Pager';
@@ -208,9 +209,13 @@ const Table: React.FC<{
// комментарий — по columnsFor (зависит от раздела и фильтра). Кастомные ячейки
// (статус → StatusBadge, дата → формат) задаются через render.
const columns: IDataTableColumn<IMyRequest>[] = [
{ key: 'id', title: 'ID', width: '70px' },
// Код заявки, а не Id элемента: Id уникален только внутри своей партиции
// (в каждом списке SharePoint нумерация с единицы), поэтому после архивации
// в таблице встречались разные заявки под одним номером. Кода нет у заявок,
// созданных до появления колонки — для них показываем Id, как раньше.
{ key: 'code', title: 'Код', width: '130px', render: (r) => r.code || `#${r.id}` },
{ key: 'type', title: 'Тип', width: '200px', render: (r) => r.requestTypeName },
{ key: 'title', title: 'Название', render: (r) => r.title || `#${r.id}` },
{ key: 'title', title: 'Название', render: (r) => r.title || r.code || `#${r.id}` },
{ key: 'date', title: 'Дата', width: '110px', render: (r) => formatDate(r.created) }
];
if (cols.author) {
@@ -392,16 +397,20 @@ export const RequestsTableView: React.FC<IRequestsTableViewProps> = ({
return result;
};
const load = async (): Promise<void> => {
const load = async (forceRender = false): Promise<void> => {
try {
const fresh = await fetchPage();
const fresh = await traced(`ИТОГО таблица:${view}#${page}`, fetchPage);
if (cancelled) return;
// Перерисовываем, только если данные реально изменились — иначе при
// каждом возврате на вкладку таблица моргала бы, перерисовываясь тем же
// самым содержимым.
// самым содержимым. forceRender — исключение: строки уже спрятаны со
// экрана (см. force ниже), и их обязательно нужно вернуть, даже если
// свежий ответ побайтово совпал с тем, что было в кеше, — иначе таблица
// так и останется в состоянии «Загрузка…», хотя данные давно пришли
// (баг: ушёл на этом самом совпадении сигнатур, найден 2026-08-18).
const prev = readPage(cacheKey);
if (!prev || pageSignature(prev) !== pageSignature(fresh)) {
if (forceRender || !prev || pageSignature(prev) !== pageSignature(fresh)) {
writePage(cacheKey, fresh);
setRows(fresh.rows);
setTotalRows(fresh.totalRows);
@@ -419,12 +428,12 @@ export const RequestsTableView: React.FC<IRequestsTableViewProps> = ({
const unsubscribe = onRequestsChanged((force) => {
// Явный повторный клик по уже открытому пункту меню — пользователь ждёт
// видимого обновления, а не молчаливой подмены данных за спиной. Прежние
// строки прячем сразу; свежие подставит load().
// строки прячем сразу; свежие подставит load(force).
if (force) {
setRows(undefined);
setLoadedKey('');
}
load().catch(() => undefined);
load(force).catch(() => undefined);
});
// Пока вкладка браузера скрыта, обновляться незачем — при возврате к ней
// сработает ближайший тик.