разные правки

This commit is contained in:
gorbenko.pavel
2026-08-14 14:25:15 +05:00
parent d052fd41b8
commit f2c36506ed
6 changed files with 149 additions and 10 deletions
@@ -32,6 +32,15 @@ import {
export interface IRouteStepFormModalProps {
api: ApprovalRouteApi;
route: TemplateRoute;
/**
* Название компании маршрута, если она задана (route.companyId) — undefined
* для запасного маршрута («любая компания»). Резолв шага «по должности»
* ищет исполнителя ИМЕННО в этой компании (initiatorCompany = компания
* инициатора, а маршрут с заданной компанией применяется только к таким
* инициаторам), поэтому подсказка должности должна сужаться под неё же —
* иначе показала бы человека, которого этот маршрут никогда не найдёт.
*/
routeCompanyName?: string;
/** Undefined, пока грузится тип заявки маршрута. */
requestType?: RequestType;
sharePoint: SharePointListsClient;
@@ -132,6 +141,7 @@ function toFormState(editing: TemplateRouteStep | undefined, nextOrderNo: number
export const RouteStepFormModal: React.FC<IRouteStepFormModalProps> = ({
api,
route,
routeCompanyName,
requestType,
sharePoint,
editing,
@@ -208,11 +218,42 @@ export const RouteStepFormModal: React.FC<IRouteStepFormModalProps> = ({
}
if (positionMatches.exact.length > 0) {
// У маршрута задана конкретная компания — резолв ищет исполнителя ИМЕННО
// в ней (route.companyId сужает круг инициаторов до этой компании, а
// бэкенд ищет по компании инициатора). Найденные в других компаниях
// этому маршруту не подойдут — их надо не прятать, а объяснить, что они не в счёт.
const inRouteCompany = routeCompanyName
? positionMatches.exact.filter(e => e.company === routeCompanyName)
: positionMatches.exact;
if (inRouteCompany.length > 0) {
return (
<div className={`${styles.positionPreview} ${styles.previewOk}`}>
<span className={styles.previewTitle}>
{routeCompanyName
? texts.routeSteps.positionPreviewFoundInCompany(routeCompanyName, inRouteCompany.length)
: texts.routeSteps.positionPreviewFound(inRouteCompany.length)}
</span>
<ul className={styles.previewList}>
{inRouteCompany.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>
);
}
// routeCompanyName задан, но среди найденных нет никого из этой компании —
// должность существует, но не там, где её будет искать этот маршрут.
return (
<div className={`${styles.positionPreview} ${styles.previewOk}`}>
<div className={`${styles.positionPreview} ${styles.previewMiss}`}>
<span className={styles.previewTitle}>
{texts.routeSteps.positionPreviewFound(positionMatches.exact.length)}
{texts.routeSteps.positionPreviewWrongCompany(routeCompanyName as string)}
</span>
<div className={styles.previewTitle}>{texts.routeSteps.positionPreviewOtherCompanies}</div>
<ul className={styles.previewList}>
{positionMatches.exact.map(e => (
<li key={e.email}>
@@ -11,6 +11,33 @@
text-align: center !important;
}
// Колонка «№» закреплена слева при горизонтальном скролле — на узких экранах/
// в редакторе SharePoint таблица шире контейнера, и без этого номер шага
// (единственный ориентир при длинном горизонтальном скролле) уезжал за край
// вместе с остальными колонками. Зеркало правого .stickyCell в
// DataTable.module.scss (там — action-колонка у правого края).
.stepColSticky {
position: sticky;
left: 0;
background: #fff;
box-shadow: 6px 0 6px -6px rgba(15, 36, 64, .12);
}
.table thead th.stepColSticky {
background: $gray-50;
z-index: 2;
}
.table tbody td.stepColSticky {
z-index: 1;
}
// Явно, а не полагаясь на специфичность: .rowHover td и .stepColSticky иначе
// шли бы вровень, и было бы неочевидно, какой фон победит при следующей правке.
.rowHover td.stepColSticky {
background: $gold-50;
}
// Шаг, идущий одновременно с соседним (общий orderNo) — помечаем знаком ∥,
// иначе два одинаковых номера подряд выглядят как ошибка данных.
.parallelMark {
@@ -174,7 +174,7 @@ export const RouteStepsScreen: React.FC<IRouteStepsScreenProps> = ({ api, route,
<table className={tableStyles.table}>
<thead>
<tr>
<th className={styles.stepCol}>{texts.routeSteps.columnOrderNo}</th>
<th className={`${styles.stepCol} ${styles.stepColSticky}`}>{texts.routeSteps.columnOrderNo}</th>
<th>{texts.routeSteps.columnExecutor}</th>
<th>{texts.routeSteps.columnStatus}</th>
<th style={{ textAlign: 'center' }}>{texts.routeSteps.columnReassign}</th>
@@ -198,7 +198,7 @@ export const RouteStepsScreen: React.FC<IRouteStepsScreenProps> = ({ api, route,
onMouseLeave={() => setHoveredBlock(undefined)}
>
{isBlockStart && (
<td className={styles.stepCol} rowSpan={block.end - block.start + 1}>
<td className={`${styles.stepCol} ${styles.stepColSticky}`} rowSpan={block.end - block.start + 1}>
{block.end > block.start ? (
<span className={styles.parallelMark} title={texts.routeSteps.mergeWithPrev}>
{r.order} ∥
@@ -280,6 +280,10 @@ export const RouteStepsScreen: React.FC<IRouteStepsScreenProps> = ({ api, route,
{showForm && (
<RouteStepFormModal
// Undefined, если у маршрута компания не задана (запасной маршрут,
// применяется к любой компании инициатора) — тогда подсказка
// должности показывает всех, как раньше, а не сужает по компании.
routeCompanyName={route.companyId ? companyName : undefined}
api={api}
route={route}
requestType={requestType}