Заявка на услуги (#18)
Tests / frontend-test (push) Successful in 1m15s

Co-authored-by: gorbenko.pavel <gorbenko.pavel@tansu.kz>
Reviewed-on: http://git.tnsu.kz/timur.gorodetskiy/request-portal/pulls/18
This commit was merged in pull request #18.
This commit is contained in:
2026-09-01 12:48:41 +00:00
parent ddce6b4f25
commit cc2efd8771
56 changed files with 2557 additions and 97 deletions
@@ -5,6 +5,7 @@
overflow-x: auto;
}
.table {
width: 100%;
border-collapse: collapse;
@@ -164,3 +165,11 @@
display: flex;
gap: $sp-2;
}
// На печати прокрутки не существует: то, что не поместилось по ширине, просто
// пропало бы с листа, а контейнер вышел бы с полосой прокрутки.
@media print {
.tableWrap {
overflow: visible;
}
}
@@ -1,4 +1,4 @@
import { useState } from 'react';
import { useCallback, useMemo, useRef, useState } from 'react';
// Общая машина сохранения заявки для ВСЕХ форм портала.
// Отвечает ТОЛЬКО за транзиентное состояние операции (идёт сохранение / ошибка /
@@ -15,6 +15,7 @@ export interface IRequestSubmit {
/**
* Запустить сохранение. validate (опц.) возвращает текст ошибки или undefined.
* Повторный вызов во время сохранения игнорируется (защита от дабл-клика).
* Ссылка на функцию неизменна — её можно класть в зависимости хуков.
*/
run: (
kind: SubmitKind,
@@ -29,13 +30,17 @@ export function useRequestSubmit(): IRequestSubmit {
const [kind, setKind] = useState<SubmitKind | undefined>(undefined);
const [savedKind, setSavedKind] = useState<SubmitKind | undefined>(undefined);
const [error, setError] = useState<string | undefined>(undefined);
// Тот же признак «идёт сохранение», но читаемый синхронно. Нужен, чтобы run
// не зависела от состояния и сохраняла неизменную ссылку: формы кладут её
// (и весь submit) в зависимости эффектов.
const savingRef = useRef<boolean>(false);
const run = async (
const run = useCallback(async (
k: SubmitKind,
save: () => Promise<void>,
validate?: () => string | undefined
): Promise<void> => {
if (saving) {
if (savingRef.current) {
return;
}
setKind(k);
@@ -45,6 +50,7 @@ export function useRequestSubmit(): IRequestSubmit {
return;
}
setError(undefined);
savingRef.current = true;
setSaving(true);
try {
await save();
@@ -52,9 +58,20 @@ export function useRequestSubmit(): IRequestSubmit {
} catch (e) {
setError(`Ошибка сохранения: ${(e as Error).message}`);
} finally {
// require-atomic-updates ловит присваивание после await, но здесь это не
// гонка: savingRef — не производное состояние, а флаг «занято» с одним
// владельцем, и второй вызов run до этой строки просто не проходит.
// eslint-disable-next-line require-atomic-updates
savingRef.current = false;
setSaving(false);
}
};
}, []);
return { saving, kind, savedKind, error, run, setError };
// Объект пересоздаётся только при РЕАЛЬНОЙ смене состояния операции, а не на
// каждом рендере формы: run и setError стабильны, поэтому эффекты, которым
// нужен только setError, не перезапускаются вовсе.
return useMemo(
() => ({ saving, kind, savedKind, error, run, setError }),
[saving, kind, savedKind, error, run]
);
}