import { useEffect, useMemo, useRef, useState } from "react"; import { useSearchParams } from "react-router"; import { isClosed, useClosedStatuses } from "../api/closed.ts"; import { useSettings } from "../../../api/queries.ts"; import { ISSUE_FILTER_NAMES, useBoard, useIssues, type IssueFilters as Filters } from "../api/queries.ts"; import { Button, Notice, PageHeader } from "../../../ui/index.ts"; import { IssueFilters, SELECTS } from "./IssueFilters.tsx"; import { useOpenIssue } from "./IssueLink.tsx"; import { RefreshButton } from "../RefreshButton.tsx"; import { IssueTable } from "./IssueTable.tsx"; import { epicNames, filterOptions } from "./options.ts"; import "./issues.css"; const Q_DELAY = 300; // поиск — после паузы ввода (R-7) export function IssuesPage({ project }: { project: string }) { const [params, setParams] = useSearchParams(); const filters = useMemo(() => Object.fromEntries(ISSUE_FILTER_NAMES.map((n) => [n, params.get(n) ?? ""]).filter(([, v]) => v)), [params]); const set = (name: string, value: string) => setParams((p) => { const n = new URLSearchParams(p); if (value) n.set(name, value); else n.delete(name); return n; }); const reset = () => setParams((p) => { const n = new URLSearchParams(p); SELECTS.forEach(([name]) => n.delete(name)); return n; }); const [q, setQ] = useState(filters.q ?? ""); const written = useRef(filters.q ?? ""); // последнее значение q, согласованное с адресом (записанное нами или принятое из него) useEffect(() => { // адрес изменён извне (назад, щелчок по «Задачи»): поле поиска следует за ним const fromUrl = filters.q ?? ""; if (fromUrl === written.current) return; written.current = fromUrl; setQ(fromUrl); }, [filters.q]); useEffect(() => { if (q === (filters.q ?? "")) return; const t = setTimeout(() => { written.current = q; setParams((p) => { const n = new URLSearchParams(p); if (q) n.set("q", q); else n.delete("q"); return n; }, { replace: true }); }, Q_DELAY); return () => clearTimeout(t); }, [q, filters.q, setParams]); const board = useBoard(project), settings = useSettings(); const opts = useMemo(() => filterOptions(board.data, settings.data), [board.data, settings.data]); const names = useMemo(() => epicNames(board.data), [board.data]); // закрытые скрыты, пока не выбрано «Все» (closed=1); выбранный закрытый статус Jira скрытие отменяет const showClosed = params.get("closed") === "1"; const closed = useClosedStatuses(); const query = useMemo(() => (showClosed || isClosed(filters.status, closed) ? { ...filters, project } : { ...filters, project, hide_closed: "1" }), [filters, project, showClosed, closed]); const list = useIssues(query); const rows = list.data?.pages.flat() ?? []; const open = useOpenIssue(); return (
} /> {list.isError && ( void list.refetch()}>Повторить}>{(list.error as Error).message} )} {rows.length > 0 && } {list.isPending &&

Загрузка…

} {!list.isPending && !rows.length && !list.isError && По этим фильтрам задач нет} {list.hasNextPage && (
)}
); }