роутинг пагинации

This commit is contained in:
gorbenko.pavel
2026-08-10 17:12:23 +05:00
parent 36ee7ec009
commit a80dc17c3e
5 changed files with 230 additions and 16 deletions
@@ -118,6 +118,14 @@ export interface IRequestsTableViewProps {
/** Фильтр по статусу заявки; только для view='own:created'. '' — все статусы. */
statusFilter?: string;
onStatusFilterChange?: (status: string) => void;
/**
* Текущая страница (1-based) и её смена. Страница живёт в маршруте, а не
* внутри компонента: так она попадает в адрес (ссылку можно передать),
* переживает открытие/закрытие заявки и запоминается для этой таблицы
* (см. persistPage в shared/routing.ts).
*/
page: number;
onPageChange: (page: number) => void;
/** Клик по строке — открыть заявку (в выезжающей панели). */
onOpen: (request: IMyRequest) => void;
}
@@ -230,12 +238,13 @@ export const RequestsTableView: React.FC<IRequestsTableViewProps> = ({
view,
statusFilter = '',
onStatusFilterChange,
page,
onPageChange,
onOpen,
}) => {
const [types, setTypes] = React.useState<IRequestType[] | undefined>(undefined);
const [rows, setRows] = React.useState<IMyRequest[] | undefined>(undefined);
const [totalRows, setTotalRows] = React.useState<number>(0);
const [page, setPage] = React.useState<number>(1);
const [query, setQuery] = usePersistedQuery(view);
const [error, setError] = React.useState<string>('');
// query — то, что в поле ввода (меняется сразу); debouncedQuery — то, что
@@ -243,7 +252,18 @@ export const RequestsTableView: React.FC<IRequestsTableViewProps> = ({
// иначе поиск бил бы запросом по каждой набранной букве.
const debouncedQuery = useDebouncedQuery(query);
React.useEffect(() => setPage(1), [debouncedQuery, view, statusFilter]);
// Смена ЗАПРОСА сбрасывает страницу: набор результатов другой, и «страница 3»
// от прошлого запроса к нему отношения не имеет. Смену вкладки/фильтра здесь
// НЕ обрабатываем — там страница приходит уже правильной из маршрута
// (см. RequestPortal: gotoOwnTab/setOwnStatus). Сравниваем с предыдущим
// значением, а не просто вешаемся на изменение: иначе эффект сбрасывал бы
// страницу и на первом рендере, затирая ту, что пришла из адреса.
const prevQuery = React.useRef<string>(debouncedQuery);
React.useEffect(() => {
if (prevQuery.current === debouncedQuery) return;
prevQuery.current = debouncedQuery;
if (page !== 1) onPageChange(1);
}, [debouncedQuery, page, onPageChange]);
React.useEffect(() => {
let cancelled = false;
@@ -348,6 +368,14 @@ export const RequestsTableView: React.FC<IRequestsTableViewProps> = ({
const visible = rows || [];
const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE));
// Страница может оказаться за пределами набора: пришла из старой ссылки или
// из запомненной, а заявок с тех пор стало меньше (часть согласовали, и они
// ушли из этой очереди). Показывать пустую таблицу с непустым счётчиком —
// выглядит как поломка, поэтому возвращаемся на последнюю существующую.
React.useEffect(() => {
if (rows && page > totalPages) onPageChange(totalPages);
}, [rows, page, totalPages, onPageChange]);
return (
<section className={styles.view}>
<div className={styles.head}>
@@ -392,7 +420,7 @@ export const RequestsTableView: React.FC<IRequestsTableViewProps> = ({
{!error && rows && total > 0 && (
<>
<Table rows={visible} view={view} onOpen={onOpen} />
<Pager page={page} totalPages={totalPages} onChange={setPage} />
<Pager page={page} totalPages={totalPages} onChange={onPageChange} />
</>
)}
</section>
@@ -20,6 +20,12 @@ const AUTO_REFRESH_MS = 10 * 60 * 1000;
export interface ISearchViewProps {
context: WebPartContext;
/**
* Текущая страница (1-based) и её смена — как и в RequestsTableView, страница
* живёт в маршруте, а не внутри компонента (см. shared/routing.ts).
*/
page: number;
onPageChange: (page: number) => void;
/** Клик по строке — открыть заявку (в выезжающей панели). */
onOpen: (request: IMyRequest) => void;
}
@@ -94,11 +100,10 @@ const Table: React.FC<{ rows: IMyRequest[]; onOpen: (request: IMyRequest) => voi
// после решения согласующего) — но т.к. это по большей части Search, свежая
// правка может проявиться не мгновенно (частично компенсируется довеском
// последних 10 минут через REST — см. myRequests.service.ts).
export const SearchView: React.FC<ISearchViewProps> = ({ context, onOpen }) => {
export const SearchView: React.FC<ISearchViewProps> = ({ context, page, onPageChange, onOpen }) => {
const [types, setTypes] = React.useState<IRequestType[] | undefined>(undefined);
const [rows, setRows] = React.useState<IMyRequest[] | undefined>(undefined);
const [totalRows, setTotalRows] = React.useState<number>(0);
const [page, setPage] = React.useState<number>(1);
const [query, setQuery] = usePersistedQuery('search');
// query — то, что в поле ввода (меняется сразу); debouncedQuery — то, что
// уходит в запрос (после паузы в наборе). Здесь это особенно важно: поиск
@@ -107,7 +112,15 @@ export const SearchView: React.FC<ISearchViewProps> = ({ context, onOpen }) => {
const debouncedQuery = useDebouncedQuery(query);
const [error, setError] = React.useState<string>('');
React.useEffect(() => setPage(1), [debouncedQuery]);
// Новый запрос — новый набор результатов, страница от прошлого к нему не
// относится. Сравниваем с предыдущим значением, чтобы не сбросить страницу,
// пришедшую из адреса, на первом же рендере (см. RequestsTableView).
const prevQuery = React.useRef<string>(debouncedQuery);
React.useEffect(() => {
if (prevQuery.current === debouncedQuery) return;
prevQuery.current = debouncedQuery;
if (page !== 1) onPageChange(1);
}, [debouncedQuery, page, onPageChange]);
React.useEffect(() => {
let cancelled = false;
@@ -198,7 +211,7 @@ export const SearchView: React.FC<ISearchViewProps> = ({ context, onOpen }) => {
{!error && hasQuery && rows && totalRows > 0 && (
<>
<Table rows={rows} onOpen={onOpen} />
<Pager page={page} totalPages={totalPages} onChange={setPage} />
<Pager page={page} totalPages={totalPages} onChange={onPageChange} />
</>
)}
</section>