Редактирование заявок у конкретного пользователя (#14)
Tests / frontend-test (push) Successful in 45s
Tests / backend-test (push) Successful in 7m18s

Co-authored-by: gorbenko.pavel <gorbenko.pavel@tansu.kz>
Reviewed-on: http://git.tnsu.kz/gorbenko.pavel/tnsu-approval-route/pulls/14
This commit was merged in pull request #14.
This commit is contained in:
2026-08-19 12:58:07 +00:00
parent 82512eb1db
commit 1ee1b71e3e
36 changed files with 1862 additions and 333 deletions
@@ -0,0 +1,20 @@
import * as React from 'react';
import { RequestListPartition } from '../models';
import { ApprovalRouteApi } from '../services/api';
import { AsyncList, useAsyncList } from './useAsyncList';
/**
* Серия партиций (архив + текущая) ОДНОГО типа заявки — нужна экрану
* редактирования заявок: заявка может лежать в любой из них, не только в
* текущей (см. requestManageService.ts).
*/
export function useRequestTypeListPartitions(
api: ApprovalRouteApi,
requestTypeId: number | undefined
): AsyncList<RequestListPartition> {
const load = React.useCallback(
() => (requestTypeId ? api.requestTypes.lists(requestTypeId) : Promise.resolve([])),
[api, requestTypeId]
);
return useAsyncList(load);
}
@@ -0,0 +1,16 @@
import * as React from 'react';
import { TemplateRouteStep } from '../models';
import { ApprovalRouteApi } from '../services/api';
import { AsyncList, useAsyncList } from './useAsyncList';
/** Шаги ВСЕХ маршрутов с данным ExecutorEmail — для экрана «Заявки по согласующему». */
export function useTemplateRouteStepsByExecutor(
api: ApprovalRouteApi,
executorEmail: string | undefined
): AsyncList<TemplateRouteStep> {
const load = React.useCallback(
() => (executorEmail ? api.templateRouteSteps.listByExecutorEmail(executorEmail) : Promise.resolve([])),
[api, executorEmail]
);
return useAsyncList(load);
}
@@ -0,0 +1,54 @@
import * as React from 'react';
import { TemplateRoute } from '../models';
import { ApprovalRouteApi } from '../services/api';
export interface TemplateRoutesLookup {
byId: Map<number, TemplateRoute>;
isLoading: boolean;
error?: string;
}
/**
* Дочитывает TemplateRoute по списку id (например, найденных через
* useTemplateRouteStepsByExecutor) — bulk-эндпоинта нет, поэтому по одному
* запросу на маршрут параллельно. Нужен для отображения (тип заявки/компания/
* отдел маршрута), сам TemplateRouteStepDto хранит только routeId.
*/
export function useTemplateRoutesLookup(api: ApprovalRouteApi, routeIds: number[]): TemplateRoutesLookup {
const [byId, setById] = React.useState<Map<number, TemplateRoute>>(new Map());
const [isLoading, setIsLoading] = React.useState(false);
const [error, setError] = React.useState<string | undefined>();
// Стабильный ключ вместо самого массива routeIds — иначе (новая ссылка на
// массив с тем же содержимым при каждом рендере вызывающего экрана) эффект
// перезапускался бы бесконечно.
const key = React.useMemo(() => Array.from(new Set(routeIds)).sort((a, b) => a - b).join(','), [routeIds]);
React.useEffect(() => {
const ids = key ? key.split(',').map(Number) : [];
if (ids.length === 0) {
setById(new Map());
return;
}
let alive = true;
setIsLoading(true);
setError(undefined);
Promise.all(ids.map(id => api.templateRoutes.get(id)))
.then(routes => {
if (!alive) return;
setById(new Map(routes.map(r => [r.id, r])));
})
.catch((e: Error) => {
if (alive) setError(e.message);
})
.finally(() => {
if (alive) setIsLoading(false);
});
return () => {
alive = false;
};
}, [api, key]);
return { byId, isLoading, error };
}