From 61a2c92d287ebfa955638a52896a0b6cbd2fef7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=A1=D0=BE?= =?UTF-8?q?=D0=BB=D0=BE=D0=B2=D1=8C=D0=B5=D0=B2?= Date: Wed, 17 Jun 2026 10:20:19 +0300 Subject: [PATCH] =?UTF-8?q?feat(tgu-ui):=20Shift+=D0=BA=D0=BB=D0=B8=D0=BA?= =?UTF-8?q?=20=D0=B1=D0=BB=D0=B8=D0=B6=D0=B5=20=D0=BA=20=D1=8F=D0=BA=D0=BE?= =?UTF-8?q?=D1=80=D1=8E=20=D1=81=D0=B6=D0=B8=D0=BC=D0=B0=D0=B5=D1=82=20?= =?UTF-8?q?=D0=B2=D1=8B=D0=B4=D0=B5=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BF?= =?UTF-8?q?=D0=BE=D0=BB=D0=BE=D1=81=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/features/tgu-slots/TguSlotsTab.tsx | 3 ++- .../features/tgu-slots/slotSelection.test.ts | 14 ++++++++++++++ .../src/features/tgu-slots/slotSelection.ts | 19 ++++++++++++------- 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/services/pcp-tgu-ui-service/src/features/tgu-slots/TguSlotsTab.tsx b/services/pcp-tgu-ui-service/src/features/tgu-slots/TguSlotsTab.tsx index 1cd97b4..fc11bd0 100644 --- a/services/pcp-tgu-ui-service/src/features/tgu-slots/TguSlotsTab.tsx +++ b/services/pcp-tgu-ui-service/src/features/tgu-slots/TguSlotsTab.tsx @@ -781,7 +781,8 @@ export function TguSlotsTab({ range }: TguSlotsTabProps) { return !(slot && slotIsBusy(slot)); }); setSelectedKeys((current) => selectStripRange(current, members, anchorKey, targetKey)); - setAnchorKey(targetKey); + // Якорь НЕ двигаем: остаётся первым кликом, чтобы повторный Shift+клик задавал диапазон + // от того же якоря — ближе к нему «сжимал» выделение, дальше — расширял. return; } setSelectedKeys((current) => diff --git a/services/pcp-tgu-ui-service/src/features/tgu-slots/slotSelection.test.ts b/services/pcp-tgu-ui-service/src/features/tgu-slots/slotSelection.test.ts index 617f364..30d94e4 100644 --- a/services/pcp-tgu-ui-service/src/features/tgu-slots/slotSelection.test.ts +++ b/services/pcp-tgu-ui-service/src/features/tgu-slots/slotSelection.test.ts @@ -60,6 +60,20 @@ describe("selectStripRange", () => { expect([...next]).toEqual(["c"]); }); + it("сжимает выделение: повторный диапазон ближе к якорю снимает хвост полосы", () => { + // якорь "a", сначала растянули до "d" (a..d), потом Shift+клик по "b" → остаётся a..b, c и d сняты. + const expanded = selectStripRange(new Set(), members, "a", "d"); + expect([...expanded].sort()).toEqual(["a", "b", "c", "d"]); + const shrunk = selectStripRange(expanded, members, "a", "b"); + expect([...shrunk].sort()).toEqual(["a", "b"]); + }); + + it("снимает хвост полосы, но не трогает выбор в других полосах", () => { + const current = new Set(["a", "b", "c", "d", "x"]); + const next = selectStripRange(current, members, "a", "b"); + expect([...next].sort()).toEqual(["a", "b", "x"]); + }); + it("сохраняет уже выбранное за пределами полосы", () => { const next = selectStripRange(new Set(["x"]), members, "a", "b"); expect([...next].sort()).toEqual(["a", "b", "x"]); diff --git a/services/pcp-tgu-ui-service/src/features/tgu-slots/slotSelection.ts b/services/pcp-tgu-ui-service/src/features/tgu-slots/slotSelection.ts index 2f3b97c..c636508 100644 --- a/services/pcp-tgu-ui-service/src/features/tgu-slots/slotSelection.ts +++ b/services/pcp-tgu-ui-service/src/features/tgu-slots/slotSelection.ts @@ -40,15 +40,17 @@ export function selectionAfterCheck( } /** - * Новое множество выбора после жеста «полоса от первого к последнему»: добавляет всех членов - * полосы между fromKey и toKey включительно (в порядке времени). Это ВЫДЕЛЕНИЕ, не toggle — - * существующий выбор сохраняется. Жест ограничен одной полосой: вызывающий передаёт members - * именно её (один КА, один крен, подряд по времени); граница полосы — гарантия «нельзя выделить - * не полосу». Если fromKey или toKey не из members — выбор не меняется (страховка). Вход не мутируется. + * Новое множество выбора после жеста «полоса от первого к последнему»: выбор полосы становится + * РОВНО диапазоном [fromKey..toKey] (в порядке времени). Члены полосы вне диапазона снимаются — + * так Shift+клик по слоту ближе к якорю «сжимает» выделение. Выбор за пределами полосы (другие + * полосы/КА) сохраняется: снимаем/добавляем только ключи из members. Жест ограничен одной полосой: + * вызывающий передаёт members именно её (один КА, один крен, подряд по времени); граница полосы — + * гарантия «нельзя выделить не полосу». Если fromKey или toKey не из members — выбор не меняется + * (страховка). Вход не мутируется. * * @param current Текущее множество выбранных ключей. * @param members Ключи членов полосы в порядке времени. - * @param fromKey Ключ первого клика (якорь). + * @param fromKey Ключ якоря (первый клик). * @param toKey Ключ второго клика. */ export function selectStripRange( @@ -63,7 +65,10 @@ export function selectStripRange( if (from < 0 || to < 0) return next const lo = Math.min(from, to) const hi = Math.max(from, to) - for (let i = lo; i <= hi; i++) next.add(members[i]!) + members.forEach((key, i) => { + if (i >= lo && i <= hi) next.add(key) + else next.delete(key) + }) return next }