From d4d578e10ef553fcf3e5e11157de529a0204b639 Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Sun, 25 Oct 2026 20:44:06 +0400 Subject: [PATCH] =?UTF-8?q?fix(web):=20=C2=AB=D0=A1=D0=BF=D1=80=D0=B8?= =?UTF-8?q?=D0=BD=D1=82=C2=BB=20=E2=80=94=20=D1=8D=D0=BF=D0=B8=D0=BA=D0=B8?= =?UTF-8?q?=20=D0=BE=D1=81=D1=82=D0=B0=D1=8E=D1=82=D1=81=D1=8F=20=D0=BA?= =?UTF-8?q?=D0=B0=D1=80=D1=82=D0=BE=D1=87=D0=BA=D0=B0=D0=BC=D0=B8,=20?= =?UTF-8?q?=D0=BF=D0=BE=D0=B4=D0=BF=D0=B8=D1=81=D1=8C=20=D0=BF=D1=83=D1=81?= =?UTF-8?q?=D1=82=D0=BE=D0=B3=D0=BE=20=D0=B2=D1=8B=D0=B1=D0=BE=D1=80=D0=B0?= =?UTF-8?q?,=20=D1=87=D0=B8=D1=81=D0=BB=D0=B0=20=D0=BA=D0=BE=D0=BB=D0=BE?= =?UTF-8?q?=D0=BD=D0=BE=D0=BA=20=D0=B2=20=D1=82=D0=B5=D1=81=D1=82=D0=B5=20?= =?UTF-8?q?(#112)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 5.5 --- web/src/modules/jira/agile/SprintPage.test.tsx | 11 +++++++++++ web/src/modules/jira/agile/SprintPage.tsx | 4 ++-- web/src/modules/jira/agile/SprintPicker.tsx | 2 +- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/web/src/modules/jira/agile/SprintPage.test.tsx b/web/src/modules/jira/agile/SprintPage.test.tsx index cac26ee..9f8f09a 100644 --- a/web/src/modules/jira/agile/SprintPage.test.tsx +++ b/web/src/modules/jira/agile/SprintPage.test.tsx @@ -224,6 +224,17 @@ describe("«Спринт»", () => { 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"]); + }); + + it("эпик на доске спринта — карточка, учитывается в числе колонки", async () => { + hub(agile([s102({ issues: [iss("AURORA-1"), iss("AURORA-E", { issue_type: "Эпик" })] })])); + open("/app/jira/sprint?project=AWPCA"); + const p = await page(); + expect(await within(p).findByText("Тема AURORA-E")).toBeInTheDocument(); + const cols = Array.from(p.querySelectorAll(".agile-col")); + expect(cols[0].querySelector(".agile-col__count")!.textContent).toBe("2"); }); it("нет активного — «Нет активного спринта» и ссылка «Бэклог»", async () => { diff --git a/web/src/modules/jira/agile/SprintPage.tsx b/web/src/modules/jira/agile/SprintPage.tsx index 6858646..0c20af2 100644 --- a/web/src/modules/jira/agile/SprintPage.tsx +++ b/web/src/modules/jira/agile/SprintPage.tsx @@ -10,7 +10,7 @@ import { columnsOf } from "./columns.ts"; import { assigneeOptions, matches, parseSprintIds, selectableSprints, sprintIssues } from "./filters.ts"; import { SprintPicker } from "./SprintPicker.tsx"; import { useAgileView } from "./useAgileView.ts"; -import { daysLeft, fmtRange, fmtSp, isEpic, tasksWord } from "./model.ts"; +import { daysLeft, fmtRange, fmtSp, tasksWord } from "./model.ts"; import "./agile.css"; function SprintHead({ s }: { s: AgileSprint }) { @@ -39,7 +39,7 @@ export function SprintPage({ project }: { project: Project }) { const selectable = selectableSprints(d.sprints); const ids = parseSprintIds(params.get("sprint"), d.sprints); const chosen = selectable.filter((s) => ids.includes(s.id)); - const issues = sprintIssues(chosen).filter((i) => !isEpic(i)); + const issues = sprintIssues(chosen); const shown = issues.filter((i) => matches(i, filter)); // выбор спринтов — в адресе, без новой записи истории const pick = (next: number[]) => setParams((p) => { const n = new URLSearchParams(p); n.set("sprint", next.join(",")); return n; }, { replace: true }); diff --git a/web/src/modules/jira/agile/SprintPicker.tsx b/web/src/modules/jira/agile/SprintPicker.tsx index 87e60b3..fec0ada 100644 --- a/web/src/modules/jira/agile/SprintPicker.tsx +++ b/web/src/modules/jira/agile/SprintPicker.tsx @@ -6,7 +6,7 @@ export function SprintPicker({ sprints, selected, onChange }: { sprints: AgileSprint[]; selected: number[]; onChange(ids: number[]): void; }) { const chosen = sprints.filter((s) => selected.includes(s.id)); - const label = chosen.length === 1 ? chosen[0].name : `Спринты: ${chosen.length}`; + const label = chosen.length === 1 ? chosen[0].name : chosen.length === 0 ? "Спринты" : `Спринты: ${chosen.length}`; const flip = (id: number, on: boolean) => onChange(sprints.map((s) => s.id).filter((x) => (x === id ? on : selected.includes(x)))); return ( (