Feature/request type access all requests (#22)
Tests / frontend-test (push) Successful in 1m24s

Co-authored-by: gorbenko.pavel <gorbenko.pavel@tansu.kz>
Reviewed-on: http://git.tnsu.kz/timur.gorodetskiy/request-portal/pulls/22
This commit was merged in pull request #22.
This commit is contained in:
2026-09-25 11:48:09 +00:00
parent ebc74dc46d
commit 98be3980cb
15 changed files with 590 additions and 56 deletions
+59 -4
View File
@@ -41,7 +41,7 @@ export type PortalMode = 'submit' | 'approve' | 'create' | 'edit';
* «Возвращена на доработку»); в отличие от первых двух смотрит
* на список данных по автору, а не на строки маршрута.
*/
export type NavSection = 'new' | 'approvals' | 'execution' | 'rework' | 'own' | 'search' | 'reports';
export type NavSection = 'new' | 'approvals' | 'execution' | 'rework' | 'own' | 'search' | 'reports' | 'allRequests';
/**
* Подвкладка раздела «Мои заявки». 'created' — заявки, где пользователь автор
@@ -90,6 +90,12 @@ export interface IRoute {
ownTab?: OwnTab;
/** Фильтр по статусу заявки; задан только при nav='own' и ownTab='created'. */
ownStatus?: string;
/**
* Выбранный тип заявки в разделе «Все заявки» (nav='allRequests') — числовой
* RequestTypeId approval-route. Не задан — раздел сам выберет тип (первый
* доступный текущему пользователю, см. AllRequestsFeature).
*/
allTypeId?: number;
/** Номер страницы таблицы (1-based). 1 — значение по умолчанию, в адрес не пишется. */
page?: number;
/** Текст поиска по таблице. Пустой — значение по умолчанию, в адрес не пишется. */
@@ -283,6 +289,7 @@ function readStoredNav(): NavSection {
raw === 'own' ||
raw === 'search' ||
raw === 'reports' ||
raw === 'allRequests' ||
raw === 'new'
? raw
: 'new';
@@ -291,6 +298,37 @@ function readStoredNav(): NavSection {
}
}
// Выбранный тип заявки в разделе «Все заявки» — тем же приёмом, что и
// подвкладка/фильтр «Моих заявок» (OWN_TAB_STORAGE_KEY выше): переживает
// перезагрузку страницы и новый визит, но не более того. Без этого переход в
// другой раздел и обратно (клик по пункту меню, без rpalltype в адресе)
// каждый раз сбрасывал бы выбор на первый доступный тип.
const ALL_TYPE_STORAGE_KEY = 'rp-all-type';
function readStoredAllTypeId(): number | undefined {
try {
const raw = window.localStorage.getItem(ALL_TYPE_STORAGE_KEY);
const id = Number(raw);
return raw && Number.isInteger(id) && id > 0 ? id : undefined;
} catch {
return undefined;
}
}
/** Запомненный тип «Все заявки» — как и storedOwnTab, нужен там, где маршрут СТРОИТСЯ. */
export function storedAllTypeId(): number | undefined {
return readStoredAllTypeId();
}
/** Запомнить тип «Все заявки» — вызывать только при явном выборе пользователя. */
export function persistAllTypeId(typeId: number): void {
try {
window.localStorage.setItem(ALL_TYPE_STORAGE_KEY, String(typeId));
} catch {
// localStorage недоступен (приватный режим и т.п.) — просто не сохраняем.
}
}
/** Запомнить раздел бокового меню — вызывать только при явном выборе пользователя. */
export function persistNav(nav: NavSection): void {
try {
@@ -308,6 +346,7 @@ function parseNav(raw: string | undefined): NavSection {
raw === 'own' ||
raw === 'search' ||
raw === 'reports' ||
raw === 'allRequests' ||
raw === 'new'
) {
return raw;
@@ -347,6 +386,18 @@ export function parseRoute(search: string = window.location.search): IRoute {
const ownTab = nav === 'own' ? parseOwnTab(params.get('rptab') ?? undefined) : undefined;
const ownStatus = ownTab === 'created' ? params.get('rpstatus') ?? readStoredOwnStatus() : undefined;
// Тип заявки в разделе «Все заявки» — из адреса, иначе запомненный (та же
// логика, что у ownTab/ownStatus выше). Если ни того, ни другого нет (первый
// визит) — undefined, и раздел сам выберет первый доступный тип (см.
// AllRequestsFeature), а как только он выберет — маршрут и localStorage
// подхватят выбор (см. gotoAllRequestsType в RequestPortal.tsx).
const allTypeIdParam = nav === 'allRequests' ? params.get('rpalltype') : null;
const allTypeIdFromUrl = allTypeIdParam ? Number(allTypeIdParam) : NaN;
const allTypeId =
nav === 'allRequests'
? (Number.isInteger(allTypeIdFromUrl) && allTypeIdFromUrl > 0 ? allTypeIdFromUrl : readStoredAllTypeId())
: undefined;
// Страница: из адреса, иначе — запомненная для ЭТОЙ таблицы. Мусор в
// параметре (0, минус, буквы) трактуем как первую страницу, а не как ошибку:
// ссылка приходит извне, ронять из-за неё раздел незачем.
@@ -362,11 +413,11 @@ export function parseRoute(search: string = window.location.search): IRoute {
// страницей: закрыв панель, пользователь должен вернуться ровно в тот же
// список и на то же место в нём.
if (mode === 'approve' && formKey && itemId && !isNaN(itemId)) {
return { mode: 'approve', nav, formKey, itemId, listId, ownTab, ownStatus, page, query };
return { mode: 'approve', nav, formKey, itemId, listId, ownTab, ownStatus, allTypeId, page, query };
}
// Редактирование собственной незавершённой заявки — та же панель, но с формой.
if (mode === 'edit' && formKey && itemId && !isNaN(itemId)) {
return { mode: 'edit', nav, formKey, itemId, listId, ownTab, ownStatus, page, query };
return { mode: 'edit', nav, formKey, itemId, listId, ownTab, ownStatus, allTypeId, page, query };
}
// Создание производной заявки с предзаполнением из исходной.
if (formKey && sourceId && !isNaN(sourceId)) {
@@ -383,7 +434,7 @@ export function parseRoute(search: string = window.location.search): IRoute {
if (formKey && mode !== 'approve' && mode !== 'edit') {
return { mode: 'create', nav: 'new', formKey, positionId, nomenclatureId, storeName };
}
return { mode: 'submit', nav, ownTab, ownStatus, page, query };
return { mode: 'submit', nav, ownTab, ownStatus, allTypeId, page, query };
}
// Собрать query-строку маршрута, сохранив посторонние параметры страницы.
@@ -399,6 +450,7 @@ function buildQuery(route: IRoute): string {
params.delete('rpstatus');
params.delete('rppage');
params.delete('rpq');
params.delete('rpalltype');
if (route.nav !== 'new') {
params.set('rpnav', route.nav);
@@ -410,6 +462,9 @@ function buildQuery(route: IRoute): string {
if (ownTab !== 'created') params.set('rptab', ownTab);
else if (route.ownStatus) params.set('rpstatus', route.ownStatus);
}
if (route.nav === 'allRequests' && route.allTypeId) {
params.set('rpalltype', String(route.allTypeId));
}
// Первая страница — тоже значение по умолчанию (см. выше про чистую ссылку).
if (route.page && route.page > 1) params.set('rppage', String(route.page));
if (route.query) params.set('rpq', route.query);