Параллельные шаги (#3)
Co-authored-by: gorbenko.pavel <gorbenko.pavel@tansu.kz> Reviewed-on: http://git.tnsu.kz/timur.gorodetskiy/request-portal/pulls/3
This commit was merged in pull request #3.
This commit is contained in:
@@ -28,7 +28,23 @@ export const ApprovalMatrix: React.FC<IApprovalMatrixProps> = ({
|
||||
emptyText = 'Согласующие не заданы.',
|
||||
pickerPlaceholder = 'Добавить согласующего: начните вводить ФИО…',
|
||||
}) => {
|
||||
const { rows, loading, error, editable } = editor;
|
||||
const { rows, loading, error, editable, blocks } = editor;
|
||||
|
||||
// Строка → шаг, которому она принадлежит. Нужно, чтобы отрисовать ячейку
|
||||
// «Шаг» один раз на весь шаг (rowSpan) и понять, можно ли двигать его выше/ниже.
|
||||
const blockByRow: Array<{ index: number; start: number; size: number }> = [];
|
||||
blocks.forEach((b, bi) => {
|
||||
for (let i = b.start; i <= b.end; i += 1) {
|
||||
blockByRow[i] = { index: bi, start: b.start, size: b.end - b.start + 1 };
|
||||
}
|
||||
});
|
||||
|
||||
// Пустые колонки не показываем — столбец из одних прочерков только шумит.
|
||||
// Условия РАЗНЫЕ, потому что заполняются эти поля в разные моменты: статус
|
||||
// есть у строки сразу (у черновика — «Черновик»), а дата появляется только
|
||||
// вместе с решением согласующего. У черновика поэтому виден статус, но не дата.
|
||||
const hasStatuses = rows.some((r) => r.status && r.status !== '—');
|
||||
const hasDates = rows.some((r) => !!r.date);
|
||||
|
||||
return (
|
||||
<div className={styles.matrixWrap}>
|
||||
@@ -42,52 +58,89 @@ export const ApprovalMatrix: React.FC<IApprovalMatrixProps> = ({
|
||||
<table className={styles.matrix}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th className={styles.matrixStepCol}>Шаг</th>
|
||||
<th>{roleLabel}</th>
|
||||
<th>Должность</th>
|
||||
<th>Статус</th>
|
||||
<th>Дата</th>
|
||||
{hasStatuses && <th>Статус</th>}
|
||||
{hasDates && <th>Дата</th>}
|
||||
{editable && <th />}
|
||||
{editable && <th />}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{rows.map((r, i) => (
|
||||
<tr key={i}>
|
||||
<td>{r.who}</td>
|
||||
<td>{r.position}</td>
|
||||
<td>{r.status && r.status !== '—' ? <StatusBadge status={r.status} /> : '—'}</td>
|
||||
<td>{r.date}</td>
|
||||
{editable && (
|
||||
<td className={styles.matrixActions}>
|
||||
<button
|
||||
type="button"
|
||||
className={styles.iconBtn}
|
||||
disabled={i === 0}
|
||||
onClick={() => editor.moveUp(i)}
|
||||
title="Выше"
|
||||
>
|
||||
<Icon name="chevron-down" size="sm" className={styles.flipY} />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={styles.iconBtn}
|
||||
disabled={i === rows.length - 1}
|
||||
onClick={() => editor.moveDown(i)}
|
||||
title="Ниже"
|
||||
>
|
||||
<Icon name="chevron-down" size="sm" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`${styles.iconBtn} ${styles.iconBtnDanger}`}
|
||||
onClick={() => editor.remove(i)}
|
||||
title="Удалить"
|
||||
>
|
||||
<Icon name="trash" size="sm" />
|
||||
</button>
|
||||
</td>
|
||||
)}
|
||||
</tr>
|
||||
))}
|
||||
{rows.map((r, i) => {
|
||||
const block = blockByRow[i];
|
||||
// Ячейка «Шаг» рисуется один раз на весь шаг и растягивается на его
|
||||
// строки. Кнопки перемещения живут в ней же: двигать параллельные
|
||||
// строки поодиночке бессмысленно — шаг переезжает целиком.
|
||||
const isBlockStart = block.start === i;
|
||||
return (
|
||||
<tr key={i} className={block.size > 1 ? styles.parallelRow : undefined}>
|
||||
{isBlockStart && (
|
||||
<td className={styles.matrixStepCol} rowSpan={block.size}>
|
||||
<span className={styles.stepNo}>{r.order}</span>
|
||||
</td>
|
||||
)}
|
||||
<td>{r.who}</td>
|
||||
<td>{r.position}</td>
|
||||
{hasStatuses && (
|
||||
<td>{r.status && r.status !== '—' ? <StatusBadge status={r.status} /> : '—'}</td>
|
||||
)}
|
||||
{hasDates && <td>{r.date}</td>}
|
||||
{/* Перемещение действует на ШАГ целиком (параллельные строки
|
||||
переезжают вместе), поэтому стрелки живут в отдельной
|
||||
объединённой по вертикали ячейке: одна пара на весь шаг,
|
||||
по центру его строк. */}
|
||||
{editable && isBlockStart && (
|
||||
<td className={styles.matrixMoveCol} rowSpan={block.size}>
|
||||
<button
|
||||
type="button"
|
||||
className={styles.iconBtn}
|
||||
disabled={block.index === 0}
|
||||
onClick={() => editor.moveBlockUp(i)}
|
||||
title="Шаг выше"
|
||||
>
|
||||
<Icon name="chevron-down" size="sm" className={styles.flipY} />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={styles.iconBtn}
|
||||
disabled={block.index === blocks.length - 1}
|
||||
onClick={() => editor.moveBlockDown(i)}
|
||||
title="Шаг ниже"
|
||||
>
|
||||
<Icon name="chevron-down" size="sm" />
|
||||
</button>
|
||||
</td>
|
||||
)}
|
||||
{editable && (
|
||||
<td className={styles.matrixActions}>
|
||||
<button
|
||||
type="button"
|
||||
className={`${styles.iconBtn} ${r.parallelWithPrev ? styles.iconBtnActive : ''}`}
|
||||
disabled={i === 0}
|
||||
onClick={() => editor.toggleParallel(i)}
|
||||
title={
|
||||
r.parallelWithPrev
|
||||
? 'Отцепить: выполнять после предыдущего шага'
|
||||
: 'Объединить: выполнять одновременно с предыдущим шагом'
|
||||
}
|
||||
>
|
||||
<Icon name="user-check" size="sm" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`${styles.iconBtn} ${styles.iconBtnDanger}`}
|
||||
onClick={() => editor.remove(i)}
|
||||
title="Удалить"
|
||||
>
|
||||
<Icon name="trash" size="sm" />
|
||||
</button>
|
||||
</td>
|
||||
)}
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
|
||||
@@ -113,16 +113,43 @@
|
||||
}
|
||||
}
|
||||
|
||||
// ВАЖНО: не display:flex — это сняло бы с ячейки display:table-cell, и она
|
||||
// перестала бы вставать в строку рядом с соседней (кнопки уезжали на вторую
|
||||
// строку). Ячейка остаётся табличной, кнопки держатся в строке через nowrap.
|
||||
.matrixActions {
|
||||
display: flex;
|
||||
gap: $sp-1;
|
||||
justify-content: flex-end;
|
||||
white-space: nowrap;
|
||||
text-align: right;
|
||||
vertical-align: middle;
|
||||
|
||||
// Отступ между любыми соседними элементами: кроме кнопок тут бывают распорки
|
||||
// (см. .iconBtnSpacer), на которые селектор .iconBtn + .iconBtn не сработал бы.
|
||||
> * + * {
|
||||
margin-left: $sp-1;
|
||||
}
|
||||
}
|
||||
|
||||
// Стрелки перемещения шага. Ячейка объединена по вертикали (rowSpan) на все
|
||||
// строки шага, поэтому пара кнопок стоит по центру блока, а не у его первой
|
||||
// строки. Как и .matrixActions — остаётся табличной ячейкой (см. комментарий
|
||||
// там), кнопки держатся в строке через nowrap.
|
||||
.matrixMoveCol {
|
||||
width: 1%; // ужимает колонку по содержимому, ширину забирают текстовые колонки
|
||||
white-space: nowrap;
|
||||
text-align: right;
|
||||
vertical-align: middle;
|
||||
|
||||
> * + * {
|
||||
margin-left: $sp-1;
|
||||
}
|
||||
}
|
||||
|
||||
.iconBtn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
// Инлайновые кнопки иначе выравниваются по baseline и «прыгают» относительно
|
||||
// кнопок соседней ячейки.
|
||||
vertical-align: middle;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
padding: 0;
|
||||
@@ -151,6 +178,41 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Включённый режим-переключатель (шаг помечен как параллельный) — в отличие от
|
||||
// остальных кнопок это состояние, а не разовое действие, поэтому оно залито.
|
||||
.iconBtnActive {
|
||||
border-color: $gold-500;
|
||||
background: $gold-500;
|
||||
color: $navy-950;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
border-color: $gold-600;
|
||||
background: $gold-600;
|
||||
color: $navy-950;
|
||||
}
|
||||
}
|
||||
|
||||
// Колонка с номером шага. Объединяется по вертикали (rowSpan) на все строки
|
||||
// одного шага, поэтому содержимое центрируем — иначе номер и кнопки прижимались
|
||||
// бы к верхней строке блока.
|
||||
.matrixStepCol {
|
||||
width: 48px;
|
||||
white-space: nowrap;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.stepNo {
|
||||
font-weight: 600;
|
||||
color: $navy-700;
|
||||
}
|
||||
|
||||
// Строки, входящие в шаг из нескольких параллельных согласующих — лёгкая
|
||||
// заливка, чтобы блок читался как единое целое (границы ячейки «Шаг» его уже
|
||||
// объединяют, но без фона строки визуально распадаются).
|
||||
.parallelRow > td {
|
||||
background: rgba($gold-500, .06);
|
||||
}
|
||||
|
||||
.flipY {
|
||||
transform: scaleY(-1);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user