34 lines
2.6 KiB
TypeScript
34 lines
2.6 KiB
TypeScript
import { isClosed } from "../../../api/closed.ts";
|
||
import type { IssueDetail } from "../../../api/types.ts";
|
||
|
||
export type LinkGroupId = "depends" | "blocks" | "related" | "other";
|
||
export interface CardLink { key: string; summary: string | null; status: string | null; tag: string | null }
|
||
|
||
const GROUPS: [LinkGroupId, string][] = [["depends", "Зависит от"], ["blocks", "Блокирует"], ["related", "Связана с"], ["other", "Прочие"]];
|
||
const BLOCK_TAGS: Record<string, string> = { "блокирует начало": "начало", "блокирует завершение": "завершение" };
|
||
const byKey = (a: CardLink, b: CardLink) =>
|
||
a.key.localeCompare(b.key, "ru", { numeric: true }) || (a.tag ?? "").localeCompare(b.tag ?? "", "ru");
|
||
|
||
// связи карточки по смыслу (design 17): логическое имя типа — из link_types настроек (имя типа Jira → логическое)
|
||
export function groupLinks(d: Pick<IssueDetail, "links_out" | "links_in">, linkTypes: Record<string, string>): [LinkGroupId, string, CardLink[]][] {
|
||
const logical = new Map(Object.entries(linkTypes).map(([name, jira]) => [jira, name]));
|
||
const groups: Record<LinkGroupId, CardLink[]> = { depends: [], blocks: [], related: [], other: [] };
|
||
const add = (dir: "out" | "in", key: string, jiraType: string, summary: string | null, status: string | null) => {
|
||
const name = logical.get(jiraType);
|
||
if (name && BLOCK_TAGS[name]) groups[dir === "in" ? "depends" : "blocks"].push({ key, summary, status, tag: BLOCK_TAGS[name] });
|
||
else if (name === "связанные") { if (!groups.related.some((l) => l.key === key)) groups.related.push({ key, summary, status, tag: null }); }
|
||
else groups.other.push({ key, summary, status, tag: `${dir === "out" ? "→" : "←"} ${name ?? jiraType}` });
|
||
};
|
||
for (const l of d.links_out) add("out", l.target_key, l.link_type, l.summary, l.status);
|
||
for (const l of d.links_in) add("in", l.source_key, l.link_type, l.summary, l.status);
|
||
return GROUPS.filter(([id]) => groups[id].length).map(([id, title]) => [id, title, groups[id].sort(byKey)]);
|
||
}
|
||
|
||
// просрочено — срок раньше сегодня, а статус Jira не из closed_statuses (hide-closed; было — по статусу по факту, R-2 #37)
|
||
export function isOverdue(due: string, status: string | null | undefined, closed: Set<string>, today: string): boolean {
|
||
return !!due && due < today && !isClosed(status, closed);
|
||
}
|
||
|
||
export const todayISO = (d = new Date()): string =>
|
||
`${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|