import { Link, useSearchParams } from "react-router"; import type { Project } from "../../../api/types.ts"; import { PageHeader, Panel, Segmented } from "../../../ui/index.ts"; import type { AgileSprint } from "../api/types.ts"; import { RefreshButton } from "../RefreshButton.tsx"; import { AgileGate } from "./AgileGate.tsx"; import { BoardColumns } from "./BoardColumns.tsx"; import { columnsOf } from "./columns.ts"; import { daysLeft, fmtRange, fmtSp, tasksWord } from "./model.ts"; import "./agile.css"; function SprintHead({ s }: { s: AgileSprint }) { const left = daysLeft(s.end_date, new Date()); return (

{s.name}

{fmtRange(s.start_date, s.end_date)} {left !== null && {left >= 0 ? `осталось ${left} дн.` : "срок прошёл"}} {s.goal && {s.goal}} {`${s.count_done} из ${s.count} ${tasksWord(s.count)}`} {`${fmtSp(s.points_done)} из ${fmtSp(s.points)} SP`}
); } // «Спринт» (spec web-app): колонки доски активного спринта; только просмотр export function SprintPage({ project }: { project: Project }) { const [params] = useSearchParams(); const wanted = params.get("sprint"); return (
} /> {(d) => { const active = d.sprints.filter((s) => s.state === "active"); // устаревший или мусорный ?sprint= — первый активный (Review Focus 2) const sprint = active.find((s) => String(s.id) === wanted) ?? active[0]; if (!sprint) { return (

Нет активного спринта

Бэклог
); } const proj = encodeURIComponent(project.label); return ( <> {active.length > 1 && ( ({ value: String(s.id), label: s.name, href: `/jira/sprint?project=${proj}&sprint=${s.id}` }))} /> )} ); }}
); }