Feature/request manage server load (#22)
Tests / frontend-test (push) Successful in 45s
Tests / backend-test (push) Successful in 1m43s

Co-authored-by: gorbenko.pavel <gorbenko.pavel@tansu.kz>
Reviewed-on: http://git.tnsu.kz/gorbenko.pavel/tnsu-approval-route/pulls/22
This commit was merged in pull request #22.
This commit is contained in:
2026-09-25 06:30:59 +00:00
parent 2b8fe77fac
commit 1bf8d4ca11
12 changed files with 612 additions and 479 deletions
@@ -5,7 +5,7 @@ import { ApprovalRouteApi } from '../../services/api';
import { PortalDictionaryClient } from '../../services/portalDictionary';
import { RequestManageService } from '../../services/requestManageService';
import { ApiClientError } from '../../services/httpClient';
import { REQUEST_STATUS, RequestListPartition, UserApprovalRow, UserTemplateStepRow } from '../../models';
import { REQUEST_STATUS, UserApprovalRow, UserTemplateStepRow } from '../../models';
import { routeStepStatusLabels, texts } from '../../texts';
import { shortGuid } from '../../utils/guid';
import { describeStepCondition } from '../../utils/stepExecutor';
@@ -15,7 +15,6 @@ import { mapWithLimit } from '../../utils/parallel';
import { IManageByUserRouteParams, replaceRoute } from '../../routing';
import { useLocalStorageState } from '../../hooks/useLocalStorageState';
import { useRequestTypes } from '../../hooks/useRequestTypes';
import { useRequestTypeListsAll } from '../../hooks/useRequestTypeListsAll';
import { useTemplateRouteStepsByExecutor } from '../../hooks/useTemplateRouteStepsByExecutor';
import { useTemplateRoutesLookup } from '../../hooks/useTemplateRoutesLookup';
import { usePortalEmployees } from '../../hooks/usePortalEmployees';
@@ -95,7 +94,6 @@ interface IReplaceScope {
export const ManageByUserScreen: React.FC<IManageByUserScreenProps> = ({ api, dictionary, requestManage, route }) => {
const requestTypes = useRequestTypes(api);
const listSeries = useRequestTypeListsAll(api);
const employees = usePortalEmployees(dictionary);
const dictionaries = usePortalDictionaries(dictionary);
@@ -125,27 +123,21 @@ export const ManageByUserScreen: React.FC<IManageByUserScreenProps> = ({ api, di
});
}, [employeeEmail, targetEmail]);
const partitionsByType = React.useMemo(() => {
const map = new Map<number, RequestListPartition[]>();
listSeries.items.forEach(s => map.set(s.requestTypeId, s.partitions));
return map;
}, [listSeries.items]);
const [liveRows, setLiveRows] = React.useState<UserApprovalRow[]>([]);
const [liveLoading, setLiveLoading] = React.useState(false);
const [liveError, setLiveError] = React.useState<string | undefined>();
const [checkedLiveKeys, setCheckedLiveKeys] = React.useState<Set<string>>(new Set());
const reloadLiveRows = React.useCallback(() => {
if (!employeeEmail || requestTypes.isLoading || listSeries.isLoading) return;
if (!employeeEmail || requestTypes.isLoading) return;
setLiveLoading(true);
setLiveError(undefined);
requestManage
.findActiveApprovalRowsForApprover(requestTypes.items, partitionsByType, employeeEmail, ACTIVE_STATUSES)
api.requestManage
.byApprover(employeeEmail, ACTIVE_STATUSES)
.then(setLiveRows)
.catch((e: Error) => setLiveError(reportError(e, 'ManageByUserScreen: findActiveApprovalRowsForApprover')))
.catch((e: Error) => setLiveError(reportError(e, 'ManageByUserScreen: byApprover')))
.finally(() => setLiveLoading(false));
}, [requestManage, requestTypes.items, requestTypes.isLoading, partitionsByType, listSeries.isLoading, employeeEmail]);
}, [api, requestTypes.isLoading, employeeEmail]);
React.useEffect(() => {
setLiveRows([]);
@@ -214,13 +214,23 @@ export const ManageRequestsPhaseTable: React.FC<IManageRequestsPhaseTableProps>
const isBlockStart = block.start === i;
const movable = blockIndex > lastLockedBlockIndex;
const rowEditable = isEditableRowStatus(r.status);
// Уведомлять можно только активный шаг (флоу шлёт письмо тому,
// чей RowID мы передали, независимо от статуса — сам он не
// Уведомлять можно активный шаг (флоу шлёт письмо тому, чей
// RowID мы передали, независимо от статуса — сам он не
// проверяет очередь). «Ждёт очереди» — шаг ещё не наступил,
// слать по нему нечего: согласующий/исполнитель пока не
// должен действовать (см. request-portal/notify.service.ts,
// где по той же причине уведомляется только активный шаг).
const notifiable = r.status === REQUEST_STATUS.onApproval || r.status === REQUEST_STATUS.onExecution;
//
// «Возвращена на доработку» — тоже уведомляемый статус, хотя
// и не активный: для него тот же поток шлёт письмо не
// согласующему строки, а АВТОРУ заявки (см.
// RequestManageService.notifyRow) — так интерфейс
// редактирования заявок получает кнопку «сообщить автору о
// доработке».
const notifiable =
r.status === REQUEST_STATUS.onApproval ||
r.status === REQUEST_STATUS.onExecution ||
r.status === REQUEST_STATUS.returned;
// Переключение параллельности — тоже перестановка (меняет order
// относительно соседней строки), поэтому смотрим на isMovableRowStatus,
// а не на rowEditable: у «Возвращена на доработку» кнопка
@@ -235,12 +235,12 @@ export const ManageRequestsScreen: React.FC<IManageRequestsScreenProps> = ({ api
const searchRequests = React.useCallback(
(query: string, page: number) => {
if (!requestType || !requestType.siteUrl || partitions.items.length === 0) {
if (!requestType || !requestType.siteUrl) {
return Promise.resolve({ items: [], hasMore: false });
}
return requestManage.searchRequests(partitions.items, requestType.siteUrl, query, page);
return api.requestManage.search(requestType.id, query, page);
},
[requestManage, requestType, partitions.items]
[api, requestType]
);
// silent=true — фоновое обновление (после своего же действия, автообновление
@@ -270,8 +270,8 @@ export const ManageRequestsScreen: React.FC<IManageRequestsScreenProps> = ({ api
}
if (!silent) setMatrixLoading(true);
setMatrixError(undefined);
requestManage
.getMatrix(requestType.siteUrl, listId, selectedRequest.id)
api.requestManage
.matrix(requestType.id, listId, selectedRequest.id)
.then(m => {
setMatrix(m);
// title/status/listId в комбобоксе (а значит, и в localStorage) держим
@@ -296,12 +296,12 @@ export const ManageRequestsScreen: React.FC<IManageRequestsScreenProps> = ({ api
return same ? prev : { ...prev, selectedRequest: m.request };
});
})
.catch((e: Error) => setMatrixError(reportError(e, 'reloadMatrix: getMatrix')))
.catch((e: Error) => setMatrixError(reportError(e, 'reloadMatrix: matrix')))
.finally(() => {
if (!silent) setMatrixLoading(false);
});
},
[requestManage, requestType, selectedRequest, partitions.items, partitions.isLoading]
[api, requestType, selectedRequest, partitions.items, partitions.isLoading]
);
React.useEffect(() => {