Заявление на увольнение #15
@@ -421,6 +421,12 @@ export const RequestsTableView: React.FC<IRequestsTableViewProps> = ({
|
||||
? undefined
|
||||
: (partial: IPage): void => {
|
||||
if (cancelled) return;
|
||||
// ПУСТОЙ промежуточный результат не показываем: смысл onPartial —
|
||||
// не держать экран пустым, пока отвечает более медленный источник,
|
||||
// а пустая страница как раз пустой экран и даёт, только вместо
|
||||
// «Загрузка…» на нём появляется «Вы ещё не создавали заявок» —
|
||||
// и через мгновение исчезает, когда придёт второй источник.
|
||||
if (partial.rows.length === 0) return;
|
||||
setRows(partial.rows);
|
||||
setTotalRows(partial.totalRows);
|
||||
};
|
||||
@@ -477,6 +483,18 @@ export const RequestsTableView: React.FC<IRequestsTableViewProps> = ({
|
||||
const q = debouncedQuery.trim();
|
||||
const total = totalRows;
|
||||
const visible = rows || [];
|
||||
|
||||
/**
|
||||
* Можно ли уже судить о содержимом таблицы.
|
||||
*
|
||||
* Пустоту («Вы ещё не создавали заявок») объявляем ТОЛЬКО по завершённой
|
||||
* загрузке именно ЭТОЙ таблицы (loadedKey), а не по любому пустому rows: до
|
||||
* этого rows может быть пустым по совершенно другой причине — часть источников
|
||||
* ещё в пути (см. onPartial выше, «Мои заявки» собираются из Search + REST).
|
||||
* Промежуточный НЕПУСТОЙ результат показывать можно и нужно — в нём уже есть
|
||||
* что смотреть.
|
||||
*/
|
||||
const loaded = loadedKey === dataKey || visible.length > 0;
|
||||
const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE));
|
||||
|
||||
// Страница может оказаться за пределами набора: пришла из старой ссылки или
|
||||
@@ -527,8 +545,8 @@ export const RequestsTableView: React.FC<IRequestsTableViewProps> = ({
|
||||
</div>
|
||||
|
||||
{error && <div className={styles.error}>Не удалось загрузить заявки: {error}</div>}
|
||||
{!error && rows === undefined && <div className={styles.empty}>Загрузка…</div>}
|
||||
{!error && rows && total === 0 && (
|
||||
{!error && !loaded && <div className={styles.empty}>Загрузка…</div>}
|
||||
{!error && loaded && total === 0 && (
|
||||
<div className={styles.empty}>
|
||||
{q ? `По запросу «${debouncedQuery}» ничего не найдено.` : EMPTY_TEXTS[view]}
|
||||
</div>
|
||||
|
||||
@@ -121,9 +121,10 @@ const DismissalView: React.FC<IDismissalViewProps> = ({ context, itemId, formKey
|
||||
// выводятся из заявления, заполнять его руками нечего — см. createClearance.ts).
|
||||
// Свой статус решения при этом уже применён: неудача автосоздания не отменяет
|
||||
// согласование, поэтому она показывается предупреждением, а не ошибкой заявки.
|
||||
const createClearance = (): void => {
|
||||
const createClearance = (): Promise<void> => {
|
||||
setClearanceError('');
|
||||
ensureClearanceForDismissal(context, itemId, listId)
|
||||
setClearance('Создаём обходной лист по этому заявлению…');
|
||||
return ensureClearanceForDismissal(context, itemId, listId)
|
||||
.then((result) => {
|
||||
setClearance(
|
||||
result.existing
|
||||
@@ -135,11 +136,12 @@ const DismissalView: React.FC<IDismissalViewProps> = ({ context, itemId, formKey
|
||||
// только после перезагрузки страницы.
|
||||
setData((prev) => ({ ...prev, ClearanceUrl: result.url }));
|
||||
})
|
||||
.catch((e) =>
|
||||
.catch((e) => {
|
||||
setClearance('');
|
||||
setClearanceError(
|
||||
`Заявление согласовано, но обходной лист создать не удалось: ${friendlyErrorMessage(e)} — заведите его вручную.`
|
||||
)
|
||||
);
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -185,10 +187,10 @@ const DismissalView: React.FC<IDismissalViewProps> = ({ context, itemId, formKey
|
||||
notify();
|
||||
// «Согласовано» = маршрут заявления пройден целиком (фазы исполнения у
|
||||
// него нет, см. README формы) — значит увольнение подтверждено и пора
|
||||
// заводить обходной лист.
|
||||
if (newStatus === REQUEST_STATUS.approved) {
|
||||
createClearance();
|
||||
}
|
||||
// заводить обходной лист. Промис ВОЗВРАЩАЕМ: пока он не разрешится,
|
||||
// панель решения не пишет «Решение сохранено» (см. её onDecided) —
|
||||
// иначе успех появлялся бы раньше, чем обходной лист создан.
|
||||
return newStatus === REQUEST_STATUS.approved ? createClearance() : undefined;
|
||||
}}
|
||||
onDelegated={() => {
|
||||
setDecisionSeq((n) => n + 1);
|
||||
|
||||
@@ -11,8 +11,20 @@ interface IDecisionPanelProps {
|
||||
service: ApproverService;
|
||||
/** Id заявки (элемент списка данных). */
|
||||
requestId: number;
|
||||
/** Уведомление после сохранения решения (например, обновить статус на экране). */
|
||||
onDecided?: (status: string) => void;
|
||||
/**
|
||||
* Уведомление после сохранения решения (например, обновить статус на экране).
|
||||
*
|
||||
* Может вернуть Promise — тогда панель ДОЖДЁТСЯ его и только потом покажет
|
||||
* «Решение сохранено». Так работают побочные действия решения, без которых
|
||||
* ответ «согласовано» неполон: по согласованному заявлению на увольнение
|
||||
* автоматически создаётся обходной лист (см. forms/Dismissal), и сообщать об
|
||||
* успехе, пока он не создан, — значит показывать успех раньше времени.
|
||||
*
|
||||
* Отказ (reject) панель НЕ считает провалом решения — решение к этому моменту
|
||||
* уже применено на сервере; сообщить о проблеме побочного действия должен сам
|
||||
* обработчик (у него для этого есть своя область экрана).
|
||||
*/
|
||||
onDecided?: (status: string) => void | Promise<void>;
|
||||
/** Выполнить ПЕРЕД применением решения (напр. сохранить поля, заполненные
|
||||
* согласующим). Если бросит — решение не применяется, показывается ошибка. */
|
||||
onBeforeDecide?: () => Promise<void>;
|
||||
@@ -83,11 +95,19 @@ export const DecisionPanel: React.FC<IDecisionPanelProps> = ({
|
||||
await onBeforeDecide();
|
||||
}
|
||||
const status = await service.decide(requestId, pendingRow.id, decision, comment.trim());
|
||||
// Сначала побочные действия решения, потом «сохранено»: пока onDecided не
|
||||
// отработал, кнопки остаются заблокированными (submitting), и пользователь не
|
||||
// видит успеха раньше, чем всё сделано. Провал onDecided на статус решения не
|
||||
// влияет — оно уже применено, поэтому сообщение показываем всё равно.
|
||||
if (onDecided) {
|
||||
try {
|
||||
await onDecided(status);
|
||||
} catch (e) {
|
||||
console.error('Побочное действие после решения не выполнилось', e);
|
||||
}
|
||||
}
|
||||
setResultStatus(status);
|
||||
setSubmitting(false);
|
||||
if (onDecided) {
|
||||
onDecided(status);
|
||||
}
|
||||
} catch (e) {
|
||||
setError(`Не удалось сохранить решение: ${(e as Error).message}`);
|
||||
setSubmitting(false);
|
||||
|
||||
Reference in New Issue
Block a user