Sla (#11)
Tests / backend-test (push) Successful in 1m50s
Tests / frontend-test (push) Successful in 49s

Co-authored-by: gorbenko.pavel <gorbenko.pavel@tansu.kz>
Reviewed-on: http://git.tnsu.kz/gorbenko.pavel/tnsu-approval-route/pulls/11
This commit was merged in pull request #11.
This commit is contained in:
2026-08-13 04:44:08 +00:00
parent 7f2aabbf29
commit d076f4e98d
103 changed files with 2606 additions and 1265 deletions
@@ -6,11 +6,23 @@ import { texts } from '../../texts';
import { toEmployeeOption } from '../../utils/employee';
import { blockIndexOf, blockRanges, markParallel, withOrdersFrom } from '../../utils/orderBlocks';
import { ActionsCell, Badge, BadgeVariant, CellNote, IconButton, PrimaryCell, SearchableSelect } from '../ui';
import { effectiveSlaHours, formatSlaHours, isRowOverdue } from '../../utils/sla';
export interface IManageRequestsPhaseTableProps {
title: string;
/** Строки ОДНОЙ фазы, уже отсортированные по order (см. ManageRequestsScreen). */
rows: ManagedApprovalRow[];
/**
* ВСЕ строки заявки (обе фазы) — нужны для расчёта момента активации шага
* (см. utils/sla.ts/activatedAt): первый шаг исполнения отсчитывается от
* последнего решения фазы согласования, а той фазы в `rows` этой таблицы нет.
*/
allRows: ManagedApprovalRow[];
/** Дата создания заявки (ISO) — точка отсчёта для самого первого шага согласования. */
requestCreatedIso: string;
/** SLA типа заявки в часах — то, что действует, если на строке своё значение не задано. */
// eslint-disable-next-line @rushstack/no-new-null -- зеркалит nullable-контракт бэкенда
requestTypeSlaHours: number | null;
/** Заявка целиком только для просмотра (закрыта) — контролов нет вообще. */
readOnly: boolean;
/**
@@ -75,6 +87,9 @@ function diffOrders(original: ManagedApprovalRow[], renumbered: Row[]): Array<{
export const ManageRequestsPhaseTable: React.FC<IManageRequestsPhaseTableProps> = ({
title,
rows,
allRows,
requestCreatedIso,
requestTypeSlaHours,
readOnly,
requestRejected,
statusVariant,
@@ -187,6 +202,7 @@ export const ManageRequestsPhaseTable: React.FC<IManageRequestsPhaseTableProps>
<th>{texts.manageRequests.columnPosition}</th>
<th>{texts.manageRequests.columnStatus}</th>
<th>{texts.manageRequests.columnDate}</th>
<th>{texts.manageRequests.columnSla}</th>
{!readOnly && <th />}
{!readOnly && <th />}
</tr>
@@ -244,6 +260,22 @@ export const ManageRequestsPhaseTable: React.FC<IManageRequestsPhaseTableProps>
<td>{r.position || texts.common.empty}</td>
<td><Badge variant={statusVariant(r.status)}>{r.status || texts.common.empty}</Badge></td>
<td>{r.date || texts.common.empty}</td>
{(() => {
const hours = effectiveSlaHours(r, requestTypeSlaHours);
// Просроченным может быть только шаг, у которого прямо сейчас
// идёт очередь — решённый уже не тикает, ждущий ещё не начался.
const overdue =
(r.status === REQUEST_STATUS.onApproval || r.status === REQUEST_STATUS.onExecution) &&
isRowOverdue(r, allRows, requestCreatedIso, requestTypeSlaHours, new Date());
return (
<td className={overdue ? styles.slaOverdue : undefined}>
{formatSlaHours(hours)}
{r.slaHours === null && hours !== null && (
<CellNote>{texts.manageRequests.slaInherited}</CellNote>
)}
</td>
);
})()}
{/* Стрелки перестановки — СЛЕВА от действий над строкой (объединить/
переназначить/удалить), одна пара на весь блок (rowSpan): двигают
шаг целиком, а не отдельного участника параллельной группы. */}