From 29b3f954041f7269719aabf8ac90c26da7928f34 Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Sun, 25 Oct 2026 22:17:32 +0400 Subject: [PATCH] =?UTF-8?q?fix(web):=20=C2=AB=D0=A1=D0=B1=D1=80=D0=BE?= =?UTF-8?q?=D1=81=D0=B8=D1=82=D1=8C=20=D1=84=D0=B8=D0=BB=D1=8C=D1=82=D1=80?= =?UTF-8?q?=D1=8B=C2=BB=20=D0=B2=D1=81=D0=B5=D0=B3=D0=B4=D0=B0=20=D0=BD?= =?UTF-8?q?=D0=B0=20=D0=BC=D0=B5=D1=81=D1=82=D0=B5,=20=D0=B1=D0=B5=D0=B7?= =?UTF-8?q?=20=C2=AB=D0=A1=D0=BA=D1=80=D1=8B=D1=82=D1=8C=20=D0=B2=D1=8B?= =?UTF-8?q?=D0=BF=D0=BE=D0=BB=D0=BD=D0=B5=D0=BD=D0=BD=D1=8B=D0=B5=C2=BB=20?= =?UTF-8?q?=D0=BD=D0=B0=20=C2=AB=D0=A1=D0=BF=D1=80=D0=B8=D0=BD=D1=82=D0=B5?= =?UTF-8?q?=C2=BB=20(#118)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Кнопка сброса больше не появляется и не исчезает — неактивна, пока фильтров нет, панель не сдвигается. На «Спринте» выполненное скрывают колонкой «Выполнено»: флажка нет, сохранённое hideDone не применяется (как на «Kanban»). Co-Authored-By: Claude Opus 5.5 --- openspec/specs/web-app/spec.md | 13 ++++++++++--- web/src/modules/jira/agile/AgileToolbar.test.tsx | 10 +++++----- web/src/modules/jira/agile/AgileToolbar.tsx | 3 ++- web/src/modules/jira/agile/KanbanPage.test.tsx | 2 +- web/src/modules/jira/agile/SprintPage.test.tsx | 16 +++++++++------- web/src/modules/jira/agile/SprintPage.tsx | 6 ++++-- 6 files changed, 31 insertions(+), 19 deletions(-) diff --git a/openspec/specs/web-app/spec.md b/openspec/specs/web-app/spec.md index 799c22f..20cf86e 100644 --- a/openspec/specs/web-app/spec.md +++ b/openspec/specs/web-app/spec.md @@ -852,9 +852,11 @@ Jira» — `POST /refresh {full: true, author: "viewer"}`; после пакет **Панель фильтров** — над содержимым «Бэклога», «Спринта» и «Kanban»: поиск «Ключ или тема» (подстрока без учёта регистра в ключе или теме), выбор «Исполнитель» («все», «Без исполнителя», исполнители задач страницы по алфавиту), -переключатель «Скрыть выполненные» (только «Бэклог» и «Спринт»); на «Бэклоге» — кнопка «Эпики» (показать или скрыть -панель эпиков); на «Спринте» — выбор спринтов; на «Спринте» и «Kanban» — меню «Колонки». Если задан хотя бы один фильтр -(поиск, исполнитель, эпик, «Скрыть выполненные») — кнопка «Сбросить фильтры», снимающая их все. Фильтры действуют вместе +переключатель «Скрыть выполненные» (только «Бэклог»; на «Спринте» и «Kanban» выполненное скрывают колонкой, сохранённое +значение там не применяется); на «Бэклоге» — кнопка «Эпики» (показать или скрыть панель эпиков); на «Спринте» — выбор +спринтов; на «Спринте» и «Kanban» — меню «Колонки». Кнопка «Сбросить фильтры» SHALL быть в панели всегда и снимать все +фильтры (поиск, исполнитель, эпик, «Скрыть выполненные»); пока ни один не задан, она неактивна — панель не сдвигается, +когда фильтр появляется или снимается. Фильтры действуют вместе (И). Сохранённое значение исполнителя или эпика, которого нет в данных, SHALL оставаться выбранным и видимым в списке. **Запоминание:** свёрнутые секции, выбранный эпик, исполнитель, «Скрыть выполненные», видимость панели эпиков и скрытые @@ -947,6 +949,11 @@ Jira» — `POST /refresh {full: true, author: "viewer"}`; после пакет - **WHEN** у доски активный спринт 101 и будущий 102; в выборе спринтов отмечен и 102 - **THEN** адрес — `?sprint=101,102`; над доской два заголовка, «осталось N дн.» только у 101; на доске задачи обоих +#### Scenario: Сброс фильтров на месте +- **WHEN** открыт «Спринт» без фильтров, затем выбран исполнитель +- **THEN** «Сбросить фильтры» видна и неактивна до выбора, после — активна на том же месте; флажка «Скрыть выполненные» + на «Спринте» нет + #### Scenario: Скрытая колонка - **WHEN** на «Спринте» в меню «Колонки» снята отметка «Выполнено», страница перезагружена - **THEN** колонки «Выполнено» нет, на кнопке «Колонки» — 1; остальные колонки на месте diff --git a/web/src/modules/jira/agile/AgileToolbar.test.tsx b/web/src/modules/jira/agile/AgileToolbar.test.tsx index a5882ef..8e24270 100644 --- a/web/src/modules/jira/agile/AgileToolbar.test.tsx +++ b/web/src/modules/jira/agile/AgileToolbar.test.tsx @@ -42,9 +42,9 @@ describe("AgileToolbar", () => { expect(stored().AWPCA.backlog).not.toHaveProperty("q"); }); - it("«Сбросить фильтры» появляется после ввода и снимает всё", () => { + it("«Сбросить фильтры» всегда на месте: неактивна без фильтров, после ввода снимает всё (#118)", () => { render(); - expect(screen.queryByText("Сбросить фильтры")).toBeNull(); + expect(screen.getByRole("button", { name: "Сбросить фильтры" })).toBeDisabled(); fireEvent.change(search(), { target: { value: "abc" } }); fireEvent.change(assignee(), { target: { value: "petrov" } }); fireEvent.click(screen.getByLabelText("Скрыть выполненные")); @@ -52,7 +52,7 @@ describe("AgileToolbar", () => { expect(search().value).toBe(""); expect(assignee().value).toBe(""); expect((screen.getByLabelText("Скрыть выполненные") as HTMLInputElement).checked).toBe(false); - expect(screen.queryByText("Сбросить фильтры")).toBeNull(); + expect(screen.getByRole("button", { name: "Сбросить фильтры" })).toBeDisabled(); expect(stored().AWPCA.backlog).toMatchObject({ assignee: "", epic: "", hideDone: false }); }); @@ -108,9 +108,9 @@ describe("AgileToolbar", () => { assignee: "", epic: "", hideDone: false, collapsed: ["E-1"], hiddenColumns: ["Done"], epicsPanel: false }); }); - it("showHideDone=false: сохранённый hideDone не показывает «Сбросить фильтры»", () => { + it("showHideDone=false: сохранённый hideDone не делает «Сбросить фильтры» активной", () => { localStorage.setItem(AGILE_KEY, JSON.stringify({ AWPCA: { backlog: { hideDone: true } } })); render(); - expect(screen.queryByText("Сбросить фильтры")).toBeNull(); + expect(screen.getByRole("button", { name: "Сбросить фильтры" })).toBeDisabled(); }); }); diff --git a/web/src/modules/jira/agile/AgileToolbar.tsx b/web/src/modules/jira/agile/AgileToolbar.tsx index c0b93c1..831cd3c 100644 --- a/web/src/modules/jira/agile/AgileToolbar.tsx +++ b/web/src/modules/jira/agile/AgileToolbar.tsx @@ -22,7 +22,8 @@ export function AgileToolbar({ filter, setQ, set, onReset, assignees, showHideDo set({ hideDone: e.target.checked })} /> )} {children} - {hasFilters({ ...filter, hideDone: filter.hideDone && showHideDone }) && } + {/* всегда на месте, без фильтров неактивна: панель не сдвигается при первом фильтре (#118) */} + ); } diff --git a/web/src/modules/jira/agile/KanbanPage.test.tsx b/web/src/modules/jira/agile/KanbanPage.test.tsx index 1e536b0..f60c958 100644 --- a/web/src/modules/jira/agile/KanbanPage.test.tsx +++ b/web/src/modules/jira/agile/KanbanPage.test.tsx @@ -139,7 +139,7 @@ describe("«Kanban»", () => { open("/app/jira/kanban?project=KAN"); const p = await page(); expect(await within(p).findByText("Тема AURORA-4")).toBeInTheDocument(); - expect(within(p).queryByRole("button", { name: "Сбросить фильтры" })).toBeNull(); + expect(within(p).getByRole("button", { name: "Сбросить фильтры" })).toBeDisabled(); }); it("скрытая колонка: снять «Готово» — колонки нет; имя колонки, которой нет, в хранилище не ломает меню", async () => { diff --git a/web/src/modules/jira/agile/SprintPage.test.tsx b/web/src/modules/jira/agile/SprintPage.test.tsx index 348c543..aaf2e62 100644 --- a/web/src/modules/jira/agile/SprintPage.test.tsx +++ b/web/src/modules/jira/agile/SprintPage.test.tsx @@ -216,16 +216,18 @@ describe("«Спринт»", () => { expect(await within(b).findByLabelText("Исполнитель")).toHaveValue(""); }); - it("«Скрыть выполненные» убирает выполненные карточки", async () => { + it("флажка «Скрыть выполненные» нет, сохранённый hideDone не применяется; «Сбросить фильтры» неактивна (#118)", async () => { + localStorage.setItem("sdlc-hub-agile", JSON.stringify({ AWPCA: { sprint: { hideDone: true } } })); hub(agile()); open("/app/jira/sprint?project=AWPCA"); const p = await page(); - await within(p).findByText("Тема AURORA-2"); - await userEvent.click(within(p).getByRole("checkbox", { name: "Скрыть выполненные" })); - expect(within(p).queryByText("Тема AURORA-2")).toBeNull(); - expect(within(p).getByText("Тема AURORA-1")).toBeInTheDocument(); - const cols = Array.from(p.querySelectorAll(".agile-col")); - expect(cols.map((c) => c.querySelector(".agile-col__count")!.textContent)).toEqual(["1", "0", "1"]); + expect(await within(p).findByText("Тема AURORA-2")).toBeInTheDocument(); + expect(within(p).queryByRole("checkbox", { name: "Скрыть выполненные" })).toBeNull(); + const reset = within(p).getByRole("button", { name: "Сбросить фильтры" }); + expect(reset).toBeDisabled(); + await userEvent.type(within(p).getByLabelText("Поиск по ключу или теме"), "AURORA-1"); + expect(within(p).getByRole("button", { name: "Сбросить фильтры" })).toBe(reset); // тот же элемент — панель не перестраивается + expect(reset).toBeEnabled(); }); it("заголовки колонок — h3 (T10.3)", async () => { diff --git a/web/src/modules/jira/agile/SprintPage.tsx b/web/src/modules/jira/agile/SprintPage.tsx index c943003..da47677 100644 --- a/web/src/modules/jira/agile/SprintPage.tsx +++ b/web/src/modules/jira/agile/SprintPage.tsx @@ -31,7 +31,9 @@ function SprintHead({ s }: { s: AgileSprint }) { // «Спринт» (spec web-app): колонки доски выбранных спринтов (по умолчанию все активные); только просмотр export function SprintPage({ project }: { project: Project }) { const [params, setParams] = useSearchParams(); - const { view, set, setQ, filter, reset } = useAgileView(project.label, "sprint"); + const { view, set, setQ, filter: saved, reset } = useAgileView(project.label, "sprint"); + // «Скрыть выполненные» здесь нет — то же делает скрытие колонки; сохранённое раньше hideDone не применяется (#118) + const filter = { ...saved, hideDone: false }; return (
} /> @@ -48,7 +50,7 @@ export function SprintPage({ project }: { project: Project }) { const pick = (next: number[]) => setParams((p) => { const n = new URLSearchParams(p); n.set("sprint", next.join(",")); return n; }, { replace: true }); return ( <> - + {selectable.length > 0 && } {chosen.length > 0 &&