sdlc-hub/web/src/issues/IssuePage.tsx

70 lines
3.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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>
);
}