копирование есть

This commit is contained in:
gorbenko.pavel
2026-08-14 14:03:38 +05:00
parent 10f70609ad
commit d052fd41b8
10 changed files with 540 additions and 10 deletions
@@ -1,4 +1,5 @@
import * as React from 'react';
import styles from './RouteStepFormModal.module.scss';
import {
ComparisonOperator,
Employee,
@@ -12,7 +13,7 @@ import {
} from '../../models';
import { comparisonOperatorLabels, headTypeLabels, projectRoleLabels, routeStepStatusLabels, texts } from '../../texts';
import { detectExecutorKind } from '../../utils/stepExecutor';
import { toEmployeeOption } from '../../utils/employee';
import { matchEmployeesByPosition, toEmployeeOption } from '../../utils/employee';
import { useAsyncSubmit } from '../../hooks/useAsyncSubmit';
import { useSharePointFields } from '../../hooks/useSharePointFields';
import { ApprovalRouteApi } from '../../services/api';
@@ -186,6 +187,75 @@ export const RouteStepFormModal: React.FC<IRouteStepFormModalProps> = ({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isManagerField]);
// Подсказка «кого найдёт эта должность»: бэкенд на резолве ищет ТОЧНОЕ
// совпадение должности в справочнике (/Employee/ByPosition), и опечатка тихо
// оставляет шаг без исполнителя — до самого резолва об этом узнать негде.
// Считаем по уже загруженному справочнику сотрудников, без запросов на ввод.
const positionMatches = React.useMemo(
() => matchEmployeesByPosition(employees, form.position),
[employees, form.position]
);
const positionPreview = ((): React.ReactNode => {
if (!form.position.trim()) return undefined;
if (employeesLoading) {
return <div className={styles.positionPreview}>{texts.routeSteps.positionPreviewLoading}</div>;
}
// Справочник не приехал (нет клиента/ошибка) — молчим о «не найдено»: это
// сказало бы неправду про корректную должность.
if (employees.length === 0) {
return <div className={styles.positionPreview}>{texts.routeSteps.positionPreviewNoDictionary}</div>;
}
if (positionMatches.exact.length > 0) {
return (
<div className={`${styles.positionPreview} ${styles.previewOk}`}>
<span className={styles.previewTitle}>
{texts.routeSteps.positionPreviewFound(positionMatches.exact.length)}
</span>
<ul className={styles.previewList}>
{positionMatches.exact.map(e => (
<li key={e.email}>
<span className={styles.previewPerson}>{e.fio}</span>
{e.company && <span className={styles.previewCompany}> · {e.company}</span>}
</li>
))}
</ul>
</div>
);
}
return (
<div className={`${styles.positionPreview} ${styles.previewMiss}`}>
<span className={styles.previewTitle}>{texts.routeSteps.positionPreviewNone}</span>
{positionMatches.similar.length > 0 && (
<>
<div className={styles.previewTitle}>{texts.routeSteps.positionPreviewSimilar}</div>
<ul className={styles.previewList}>
{positionMatches.similar.map(s => (
<li key={s.position}>
<button
type="button"
className={styles.previewSuggest}
onClick={() => setForm(f => ({ ...f, position: s.position }))}
>
{s.position} — {s.fio}
{s.count > 1 && (
<span className={styles.previewSuggestCount}>
{' '}
({texts.routeSteps.positionPreviewMore(s.count - 1)})
</span>
)}
</button>
</li>
))}
</ul>
</>
)}
</div>
);
})();
const submitForm = (): void => {
const orderNo = Number(form.orderNo);
if (!orderNo || orderNo < 1) {
@@ -304,6 +374,7 @@ export const RouteStepFormModal: React.FC<IRouteStepFormModalProps> = ({
onChange={e => setForm({ ...form, position: e.target.value })}
placeholder={texts.routeSteps.positionPlaceholder}
/>
{positionPreview}
</Field>
)}
{form.kind === 'projectRole' && (