Управление заявками (#7)
Tests / frontend-test (push) Successful in 43s
Tests / backend-test (push) Successful in 2m36s

Co-authored-by: gorbenko.pavel <gorbenko.pavel@tansu.kz>
Reviewed-on: http://git.tnsu.kz/gorbenko.pavel/tnsu-approval-route/pulls/7
This commit was merged in pull request #7.
This commit is contained in:
2026-08-07 09:49:38 +00:00
parent 9d9f390548
commit 0ffa032ade
42 changed files with 2436 additions and 65 deletions
@@ -11,6 +11,7 @@ import { usePortalEmployees } from '../../hooks/usePortalEmployees';
import { useSharePointFields } from '../../hooks/useSharePointFields';
import { useLocalStorageState } from '../../hooks/useLocalStorageState';
import { findEmployeeByEmail, toEmployeeOption } from '../../utils/employee';
import { IResolveRouteParams, replaceRoute } from '../../routing';
import {
Alert,
AsyncSearchableSelect,
@@ -35,6 +36,9 @@ export interface IResolveScreenProps {
dictionary?: PortalDictionaryClient;
sharePoint: SharePointListsClient;
currentUserEmail?: string;
/** Заполнение из URL (глубокая ссылка) — есть, только если в адресе заданы
* параметры проверки маршрута (aartype и т.д., см. routing.ts). */
route?: IResolveRouteParams;
}
const STATUS_OPTIONS = [RouteStepStatus.Approval, RouteStepStatus.Execution]
@@ -63,14 +67,39 @@ const EMPTY_RESOLVE_FORM: IResolveFormState = {
fieldValues: []
};
export const ResolveScreen: React.FC<IResolveScreenProps> = ({ api, dictionary, sharePoint, currentUserEmail }) => {
export const ResolveScreen: React.FC<IResolveScreenProps> = ({ api, dictionary, sharePoint, currentUserEmail, route }) => {
const requestTypes = useRequestTypes(api);
const [form, setForm] = useLocalStorageState<IResolveFormState>(RESOLVE_FORM_STORAGE_KEY, EMPTY_RESOLVE_FORM);
const { requestTypeId, userEmail, projectId, projectLabel, status, fieldValues = [] } = form;
// Открыли по глубокой ссылке (aartype и т.д. в URL) — параметры оттуда
// перекрывают то, что было в localStorage. Только на монтировании: route
// меняется при каждом собственном же replaceRoute ниже, а не только при
// переходе по внешней ссылке — сравнивать его на равенство и держать в
// зависимостях смысла нет.
React.useEffect(() => {
if (!route) return;
setForm({
requestTypeId: route.requestTypeId,
userEmail: route.userEmail,
projectId: route.projectId,
projectLabel: route.projectLabel,
status: route.status,
fieldValues: route.fieldValues
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// Держим URL синхронным с формой — правки сразу становятся частью ссылки,
// которую можно скопировать/сохранить. replaceRoute, а не navigate: иначе
// каждый введённый символ добавлял бы шаг в историю браузера.
React.useEffect(() => {
replaceRoute({ tab: 'resolve', resolve: { requestTypeId, userEmail, projectId, projectLabel, status, fieldValues } });
}, [requestTypeId, userEmail, projectId, projectLabel, status, fieldValues]);
const requestType = requestTypes.items.find(rt => rt.id === Number(requestTypeId));
const hasListId = Boolean(requestType?.listId);
const sharePointFields = useSharePointFields(sharePoint, requestType?.siteUrl, requestType?.listId);
const sharePointFields = useSharePointFields(sharePoint, requestType?.siteUrl ?? undefined, requestType?.listId ?? undefined);
const fieldNameOptions = sharePointFields.items.map(f => ({ value: f.internalName, label: f.title }));
const resolved = useResolveRoute(api);
@@ -243,7 +272,7 @@ export const ResolveScreen: React.FC<IResolveScreenProps> = ({ api, dictionary,
key: 'executor',
header: texts.resolve.columnExecutor,
render: r => {
const employee = findEmployeeByEmail(employees.items, r.executorEmail);
const employee = findEmployeeByEmail(employees.items, r.executorEmail ?? undefined);
return (
<div>
<PrimaryCell>{employee ? employee.fio : (r.executorEmail ?? texts.common.empty)}</PrimaryCell>