70 lines
3.9 KiB
TypeScript
70 lines
3.9 KiB
TypeScript
import { Link, useParams } from "react-router";
|
||
import { ApiError } from "../api/client.ts";
|
||
import { useIssue, useSettings } from "../api/queries.ts";
|
||
import { JIRA_URL } from "../board/constants.ts";
|
||
import { fmtStamp } from "../board/model/util.ts";
|
||
import { NoteForm } from "./NoteForm.tsx";
|
||
import { dueOf } from "./options.ts";
|
||
import "./issues.css";
|
||
|
||
export function IssuePage() {
|
||
const { key = "" } = useParams();
|
||
const issue = useIssue(key);
|
||
const settings = useSettings();
|
||
// подпись типа связи: логический тип из link_types настроек, иначе имя типа Jira
|
||
const typeName = (jira: string) =>
|
||
Object.entries(settings.data?.link_types ?? {}).find(([, j]) => j === jira)?.[0] ?? jira;
|
||
|
||
if (issue.isPending) return <p className="hint" data-testid="page-issue">загрузка {key}…</p>;
|
||
if (issue.error instanceof ApiError && issue.error.status === 404) {
|
||
return <p className="issue" data-testid="page-issue">Задачи {key} нет в зеркале хаба. <Link to="/issues">к списку задач</Link></p>;
|
||
}
|
||
if (issue.isError && !issue.data) {
|
||
return (
|
||
<p className="warn" role="alert" data-testid="page-issue">{issue.error.message}{" "}
|
||
<button className="btn sm" type="button" onClick={() => void issue.refetch()}>Повторить</button></p>
|
||
);
|
||
}
|
||
const i = issue.data!;
|
||
const fields: [string, string | null][] = [
|
||
["Тип", i.issue_type], ["Статус Jira", i.status], ["Приоритет", i.priority], ["Исполнитель", i.assignee],
|
||
["Автор", i.reporter], ["Спринт", i.sprint], ["Срок", dueOf(i) || null], ["Плановое начало", i.planned_start],
|
||
["Эпик", i.epic_key], ["Метки", i.labels.join(", ") || null], ["Сосед по связям", i.neighbor ? "да" : null],
|
||
["Обновлена в Jira", i.jira_updated ? fmtStamp(i.jira_updated) : null],
|
||
];
|
||
return (
|
||
<div data-testid="page-issue" className="issue">
|
||
{issue.isError && (
|
||
<p className="warn" role="alert">{issue.error.message}{" "}
|
||
<button className="btn sm" type="button" onClick={() => void issue.refetch()}>Повторить</button></p>
|
||
)}
|
||
<p><Link to="/issues">← задачи</Link></p>
|
||
<h1>{i.key} · {i.summary}</h1>
|
||
<p><a href={`${JIRA_URL}/browse/${i.key}`} target="_blank" rel="noopener">Открыть в Jira</a></p>
|
||
<div className="cols">
|
||
<div>
|
||
<dl className="fields">{fields.filter(([, v]) => v).map(([k, v]) => <div key={k}><dt>{k}</dt><dd>{v}</dd></div>)}</dl>
|
||
{i.description && <pre className="description">{i.description}</pre>}
|
||
<h3>Связи</h3>
|
||
<ul className="links">
|
||
{i.links_out.map((l) => <li key={"o" + l.target_key + l.link_type}><span>{typeName(l.link_type)}</span> → <Link to={`/issues/${l.target_key}`}>{l.target_key}</Link></li>)}
|
||
{i.links_in.map((l) => <li key={"i" + l.source_key + l.link_type}><Link to={`/issues/${l.source_key}`}>{l.source_key}</Link> → <span>{typeName(l.link_type)}</span> → эта задача</li>)}
|
||
{!i.links_out.length && !i.links_in.length && <li className="hint">связей нет</li>}
|
||
</ul>
|
||
<h3>Последние команды</h3>
|
||
{i.commands.length ? (
|
||
<table className="grid">
|
||
<thead><tr><th>Команда</th><th>Пакет</th><th>Тип</th><th>Статус</th><th>Завершена</th><th>Ошибка</th></tr></thead>
|
||
<tbody>{i.commands.map((c) => (
|
||
<tr key={c.id}><td>#{c.id}</td><td>#{c.batch_id}</td><td>{c.type}</td><td>{c.status}</td>
|
||
<td>{c.finished_at ? fmtStamp(c.finished_at) : ""}</td><td>{c.error}</td></tr>
|
||
))}</tbody>
|
||
</table>
|
||
) : <p className="hint">команд по задаче не было</p>}
|
||
</div>
|
||
<NoteForm key={i.key} issue={i} />
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|