2026-10-24 15:38:46 +04:00

33 lines
2.1 KiB
TypeScript

import type { JiraSettings } from "../api/types.ts";
import type { BoardSnapshot } from "../api/types.ts";
export type Opt = { value: string; label: string };
const uniq = (xs: (string | null | undefined)[]) => [...new Set(xs.filter((x): x is string => !!x))].sort((a, b) => a.localeCompare(b, "ru"));
const isEpic = (t: string | null) => /^(Фича|Epic|Эпик)$/i.test(t ?? "");
export const epicNames = (snap: BoardSnapshot | undefined): Record<string, string> =>
Object.fromEntries((snap?.issues ?? []).filter((i) => isEpic(i.issue_type)).map((i) => [i.key, i.summary || i.key]));
export const dueOf = (i: { planned_end: string | null; duedate: string | null }) => (i.planned_end || i.duedate || "").slice(0, 10);
// T13.1: значение фильтра из адреса, которого нет среди вариантов (закрытый спринт, ушедший исполнитель), видно в списке;
// пока снимок доски не загружен, пометку не ставим (R-4)
export const withCurrent = (opts: Opt[], value: string | undefined, loaded: boolean): Opt[] =>
!value || opts.some((o) => o.value === value) ? opts
: [{ value, label: loaded ? `${value} (нет на доске)` : value }, ...opts];
// варианты выпадающих списков фильтров: из снимка /board и словарей настроек, без новых маршрутов API
export function filterOptions(snap: BoardSnapshot | undefined, s: JiraSettings | undefined) {
const issues = snap?.issues ?? [];
const names = epicNames(snap);
const opt = (xs: string[]): Opt[] => xs.map((v) => ({ value: v, label: v }));
return {
sprint: opt(uniq(issues.map((i) => i.sprint))),
assignee: opt(uniq(issues.map((i) => i.assignee))),
work_kind: opt(uniq([...Object.keys(s?.kind_labels ?? {}), ...issues.map((i) => i.work_kind)])),
status: opt(uniq(issues.map((i) => i.status))),
label: opt(uniq(issues.flatMap((i) => i.labels))),
epic: uniq(issues.map((i) => i.epic_key)).map((k) => ({ value: k, label: names[k] ?? k })),
};
}