From 41700cdd54c7e526e5f6a5b7d26a39754254c80f Mon Sep 17 00:00:00 2001 From: "gorbenko.pavel" Date: Fri, 7 Aug 2026 16:51:01 +0500 Subject: [PATCH] =?UTF-8?q?=D0=BF=D0=B0=D0=B9=D0=BF=20=D0=B8=20=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=BC=D0=B0=D1=86=D0=B8=D1=8F=20=D0=B3=D0=BE=D1=82?= =?UTF-8?q?=D0=BE=D0=B2=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/workflows/test.yml | 40 +++++ .../shared/approval/ui/ApprovalMatrix.tsx | 42 ++++- .../shared/approval/ui/approval.module.scss | 7 +- .../shared/approval/ui/useFlipRows.ts | 165 ++++++++++++------ 4 files changed, 190 insertions(+), 64 deletions(-) create mode 100644 .gitea/workflows/test.yml diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml new file mode 100644 index 0000000..2977005 --- /dev/null +++ b/.gitea/workflows/test.yml @@ -0,0 +1,40 @@ +name: Tests + +on: + push: + branches: + - main + pull_request: + workflow_dispatch: + +jobs: + frontend-test: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Node.js 22 + uses: actions/setup-node@v3 + with: + node-version: '22' + cache: 'npm' + cache-dependency-path: package-lock.json + + - name: Install dependencies + run: | + npm config set fetch-retries 5 + npm config set fetch-retry-mintimeout 10000 + npm config set fetch-retry-maxtimeout 120000 + for i in 1 2 3; do + npm install --legacy-peer-deps && break + echo "Attempt $i failed, retrying in 20s..." + sleep 20 + done + + # heft test = сборка (sass + typescript + lint) и следом Jest. Это НЕ то же + # самое, что npm run build: там дополнительно пакуется .sppkg, а для проверки + # на PR упаковка не нужна. + - name: Run unit tests (Jest via heft) + run: npx heft test --clean diff --git a/src/webparts/requestPortal/shared/approval/ui/ApprovalMatrix.tsx b/src/webparts/requestPortal/shared/approval/ui/ApprovalMatrix.tsx index dc23434..0ed724e 100644 --- a/src/webparts/requestPortal/shared/approval/ui/ApprovalMatrix.tsx +++ b/src/webparts/requestPortal/shared/approval/ui/ApprovalMatrix.tsx @@ -35,7 +35,12 @@ export const ApprovalMatrix: React.FC = ({ // (не индекс) — иначе React переписывает строки на месте, и «переезжать» // при перестановке шага будет нечему (см. matrixRowKeys / useFlipRows). const rowKeys = matrixRowKeys(rows); - const registerRow = useFlipRows(); + // Шаг, который двигает пользователь: при обмене переезжают ОБА соседних шага, + // но подсветить и поднять надо только тот, по чьей стрелке кликнули, — иначе + // подсвечивается пол-таблицы и непонятно, что именно переехало. Запоминаем + // ключи блока ДО перестановки: после неё индексы уже другие. + const [activeKeys, setActiveKeys] = React.useState([]); + const registerRow = useFlipRows(activeKeys); // Строка → шаг, которому она принадлежит. Нужно, чтобы отрисовать ячейку // «Шаг» один раз на весь шаг (rowSpan) и понять, можно ли двигать его выше/ниже. @@ -46,6 +51,31 @@ export const ApprovalMatrix: React.FC = ({ } }); + const moveBlock = (rowIndex: number, direction: 'up' | 'down'): void => { + const b = blockByRow[rowIndex]; + setActiveKeys(rowKeys.slice(b.start, b.start + b.size)); + if (direction === 'up') editor.moveBlockUp(rowIndex); + else editor.moveBlockDown(rowIndex); + }; + + // Остальные правки состава — не перестановка: сбрасываем «активный» шаг, + // иначе подъём и подсветка достались бы случайной строке, которая сдвинулась + // из-за перенумерации после удаления. + const removeRow = (rowIndex: number): void => { + setActiveKeys([]); + editor.remove(rowIndex); + }; + + const toggleParallel = (rowIndex: number): void => { + setActiveKeys([]); + editor.toggleParallel(rowIndex); + }; + + const addApprover = (user: ISiteUser): void => { + setActiveKeys([]); + editor.addApprover(user); + }; + // Пустые колонки не показываем — столбец из одних прочерков только шумит. const hasStatuses = rows.some((r) => r.status && r.status !== '—'); const hasDates = rows.some((r) => !!r.date); @@ -115,7 +145,7 @@ export const ApprovalMatrix: React.FC = ({ type="button" className={styles.iconBtn} disabled={block.index === 0} - onClick={() => editor.moveBlockUp(i)} + onClick={() => moveBlock(i, 'up')} title="Шаг выше" > @@ -124,7 +154,7 @@ export const ApprovalMatrix: React.FC = ({ type="button" className={styles.iconBtn} disabled={block.index === blocks.length - 1} - onClick={() => editor.moveBlockDown(i)} + onClick={() => moveBlock(i, 'down')} title="Шаг ниже" > @@ -137,7 +167,7 @@ export const ApprovalMatrix: React.FC = ({ type="button" className={`${styles.iconBtn} ${r.parallelWithPrev ? styles.iconBtnActive : ''}`} disabled={i === 0} - onClick={() => editor.toggleParallel(i)} + onClick={() => toggleParallel(i)} title={ r.parallelWithPrev ? 'Отцепить: выполнять после предыдущего шага' @@ -149,7 +179,7 @@ export const ApprovalMatrix: React.FC = ({