feat: added optin to add delegant for executor and change delegant
This commit is contained in:
@@ -24,6 +24,13 @@
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
// Блок переназначения (делегирования) — отделён от кнопок решения тонкой линией.
|
||||
.delegate {
|
||||
margin-top: 16px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid $gray-200;
|
||||
}
|
||||
|
||||
.state {
|
||||
margin-top: 16px;
|
||||
padding: 24px;
|
||||
|
||||
@@ -2,8 +2,10 @@ import * as React from 'react';
|
||||
import { useState, useEffect } from 'react';
|
||||
import styles from './DecisionPanel.module.scss';
|
||||
import { Button, Alert, FormField, TextArea, Icon } from '../../ui';
|
||||
import { ApproverService, ApproverDecision } from '../approverDecision.service';
|
||||
import { ApproverService, ApproverDecision, IPendingRow } from '../approverDecision.service';
|
||||
import { APPROVAL_PHASE } from '../models/statuses';
|
||||
import { ISiteUser } from '../models/ISiteUser';
|
||||
import { UserPicker } from './UserPicker';
|
||||
|
||||
interface IDecisionPanelProps {
|
||||
service: ApproverService;
|
||||
@@ -14,22 +16,31 @@ interface IDecisionPanelProps {
|
||||
/** Выполнить ПЕРЕД применением решения (напр. сохранить поля, заполненные
|
||||
* согласующим). Если бросит — решение не применяется, показывается ошибка. */
|
||||
onBeforeDecide?: () => Promise<void>;
|
||||
/** Поиск пользователей — нужен для переназначения (делегирования). Без него
|
||||
* кнопка «Переназначить» не показывается. */
|
||||
search?: (query: string) => Promise<ISiteUser[]>;
|
||||
/** Вызывается после успешного переназначения (обновить экран/матрицу). */
|
||||
onDelegated?: () => void;
|
||||
}
|
||||
|
||||
// Панель решения согласующего: проверяет, его ли сейчас очередь, и если да —
|
||||
// показывает комментарий и кнопки Согласовать / Вернуть / Отклонить.
|
||||
// Панель решения текущего шага: согласующий (Согласовать/Вернуть/Отклонить) или
|
||||
// исполнитель («Выполнено»). Если у шага разрешено переназначение (allowReassign)
|
||||
// — можно передать шаг другому сотруднику (делегат встаёт перед текущим).
|
||||
export const DecisionPanel: React.FC<IDecisionPanelProps> = ({
|
||||
service,
|
||||
requestId,
|
||||
onDecided,
|
||||
onBeforeDecide,
|
||||
search,
|
||||
onDelegated,
|
||||
}) => {
|
||||
const [checking, setChecking] = useState<boolean>(true);
|
||||
const [rowId, setRowId] = useState<number | undefined>(undefined);
|
||||
const [phase, setPhase] = useState<string>(APPROVAL_PHASE.approval);
|
||||
const [pendingRow, setPendingRow] = useState<IPendingRow | undefined>(undefined);
|
||||
const [comment, setComment] = useState<string>('');
|
||||
const [submitting, setSubmitting] = useState<boolean>(false);
|
||||
const [resultStatus, setResultStatus] = useState<string | undefined>(undefined);
|
||||
const [delegated, setDelegated] = useState<boolean>(false);
|
||||
const [showDelegate, setShowDelegate] = useState<boolean>(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -39,16 +50,13 @@ export const DecisionPanel: React.FC<IDecisionPanelProps> = ({
|
||||
.getPendingRowForCurrentUser(requestId)
|
||||
.then((row) => {
|
||||
if (alive) {
|
||||
setRowId(row?.id);
|
||||
if (row) {
|
||||
setPhase(row.phase);
|
||||
}
|
||||
setPendingRow(row);
|
||||
setChecking(false);
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
if (alive) {
|
||||
setError(`Не удалось проверить очередь согласования: ${(e as Error).message}`);
|
||||
setError(`Не удалось проверить очередь: ${(e as Error).message}`);
|
||||
setChecking(false);
|
||||
}
|
||||
});
|
||||
@@ -58,7 +66,7 @@ export const DecisionPanel: React.FC<IDecisionPanelProps> = ({
|
||||
}, [requestId, service]);
|
||||
|
||||
async function decide(decision: ApproverDecision): Promise<void> {
|
||||
if (rowId === undefined) {
|
||||
if (!pendingRow) {
|
||||
return;
|
||||
}
|
||||
if ((decision === 'return' || decision === 'reject') && comment.trim().length === 0) {
|
||||
@@ -68,11 +76,10 @@ export const DecisionPanel: React.FC<IDecisionPanelProps> = ({
|
||||
setError(null);
|
||||
setSubmitting(true);
|
||||
try {
|
||||
// Сохраняем правки согласующего (если есть) ДО применения решения.
|
||||
if (onBeforeDecide) {
|
||||
await onBeforeDecide();
|
||||
}
|
||||
const status = await service.decide(requestId, rowId, decision, comment.trim());
|
||||
const status = await service.decide(requestId, pendingRow.id, decision, comment.trim());
|
||||
setResultStatus(status);
|
||||
setSubmitting(false);
|
||||
if (onDecided) {
|
||||
@@ -84,14 +91,46 @@ export const DecisionPanel: React.FC<IDecisionPanelProps> = ({
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDelegate(user: ISiteUser): Promise<void> {
|
||||
if (!pendingRow) {
|
||||
return;
|
||||
}
|
||||
setError(null);
|
||||
setSubmitting(true);
|
||||
try {
|
||||
await service.delegate(requestId, pendingRow.id, user);
|
||||
setDelegated(true);
|
||||
setSubmitting(false);
|
||||
if (onDelegated) {
|
||||
onDelegated();
|
||||
}
|
||||
} catch (e) {
|
||||
setError(`Не удалось переназначить: ${(e as Error).message}`);
|
||||
setSubmitting(false);
|
||||
}
|
||||
}
|
||||
|
||||
const phase = pendingRow?.phase ?? APPROVAL_PHASE.approval;
|
||||
const isExecution = phase === APPROVAL_PHASE.execution;
|
||||
// Делегировавший переназначает вне зависимости от allowReassign строки — это
|
||||
// его собственное право (создал шаг — может его поправить), а не право делегата.
|
||||
const canDelegate = !!search && !!pendingRow && (pendingRow.isDelegator || pendingRow.allowReassign);
|
||||
const isDelegator = !!pendingRow?.isDelegator;
|
||||
|
||||
// Пока проверяем очередь или если действий нет (не твоя очередь / заявка
|
||||
// обработана / тебя нет в маршруте) — панель не показываем вовсе.
|
||||
if (checking || rowId === undefined) {
|
||||
if (checking || !pendingRow) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (delegated) {
|
||||
return (
|
||||
<div className={styles.panel}>
|
||||
<Alert variant="ok">Шаг переназначен — заявка передана выбранному сотруднику.</Alert>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (resultStatus) {
|
||||
return (
|
||||
<div className={styles.panel}>
|
||||
@@ -103,9 +142,42 @@ export const DecisionPanel: React.FC<IDecisionPanelProps> = ({
|
||||
);
|
||||
}
|
||||
|
||||
// Фаза исполнения: одно действие «Выполнено» (без обязательного комментария),
|
||||
// строка помечается как «Согласовано» → computeRequestStatus переведёт заявку в
|
||||
// «Выполнение»/«Закрыта».
|
||||
// Блок переназначения — общий для согласующего и исполнителя (когда разрешено).
|
||||
const delegateBlock = canDelegate ? (
|
||||
<div className={styles.delegate}>
|
||||
{showDelegate ? (
|
||||
<FormField label="Кому переназначить">
|
||||
<UserPicker
|
||||
search={search}
|
||||
onPick={handleDelegate}
|
||||
placeholder="Начните вводить ФИО сотрудника…"
|
||||
/>
|
||||
</FormField>
|
||||
) : (
|
||||
<Button variant="secondary" disabled={submitting} onClick={() => setShowDelegate(true)}>
|
||||
<Icon name="people" size="sm" />
|
||||
Переназначить
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
) : null;
|
||||
|
||||
// Строка сейчас у делегата, а не у самого пользователя — решать за делегата
|
||||
// нельзя, только переназначить (если он, например, недоступен — в отпуске).
|
||||
if (isDelegator) {
|
||||
return (
|
||||
<div className={styles.panel}>
|
||||
<div className={styles.title}>{isExecution ? 'Исполнение' : 'Ваше решение'}</div>
|
||||
<Alert variant="warn">
|
||||
Этот шаг сейчас у сотрудника, которому вы его переназначили. Если он недоступен —
|
||||
переназначьте шаг ещё раз.
|
||||
</Alert>
|
||||
{error && <Alert variant="err">{error}</Alert>}
|
||||
{delegateBlock}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (isExecution) {
|
||||
return (
|
||||
<div className={styles.panel}>
|
||||
@@ -125,6 +197,7 @@ export const DecisionPanel: React.FC<IDecisionPanelProps> = ({
|
||||
Выполнено
|
||||
</Button>
|
||||
</div>
|
||||
{delegateBlock}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -155,6 +228,7 @@ export const DecisionPanel: React.FC<IDecisionPanelProps> = ({
|
||||
Отклонить
|
||||
</Button>
|
||||
</div>
|
||||
{delegateBlock}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user