4.1 KiB
4.1 KiB
title
| title |
|---|
| Workstream |
import {API} from "../components/config.js";
import {fieldRow} from "../components/field-help.js";
const wsId = observable.params.id;
const [raw, taskRows, workplanIndex] = await Promise.all([
fetch(`${API}/workstreams/${wsId}`)
.then(r => r.ok ? r.json() : r.json().then(e => ({error: e.detail ?? `HTTP ${r.status}`})))
.catch(e => ({error: String(e)})),
fetch(`${API}/tasks/?workstream_id=${wsId}&limit=1000`)
.then(r => r.ok ? r.json() : [])
.catch(() => []),
fetch(`${API}/workstreams/workplan-index`)
.then(r => r.ok ? r.json() : {workstreams: {}})
.catch(() => ({workstreams: {}})),
]);
if (raw.error) {
display(html`<div style="color:red;padding:1rem">⚠️ ${raw.error}</div>`);
} else {
const workplan = (workplanIndex.workstreams ?? {})[wsId] ?? {};
const name = raw.title || raw.slug || wsId;
const shortName = name.length > 60 ? name.slice(0, 60) + "…" : name;
display(html`<h1 style="font-size:1.1rem;margin-bottom:0.25rem">Workstream · <em>${shortName}</em></h1>`);
display(html`<p style="margin-top:0"><a href="/">← Overview</a> | <a href="/workstreams">← Workstreams</a> | <a href="/token-cost">← Token Cost</a></p>`);
display(html`<div class="ws-summary">
<div><span>Status</span><strong>${raw.status ?? "—"}</strong></div>
<div><span>Workplan</span><strong>${workplan.filename ?? "not file-backed"}</strong></div>
<div><span>Tasks</span><strong>${taskRows.length}</strong></div>
</div>`);
const statusOrder = {blocked: 0, in_progress: 1, todo: 2, done: 3, cancelled: 4};
const sortedTasks = [...taskRows].sort((a, b) => {
const statusCompare = (statusOrder[a.status] ?? 9) - (statusOrder[b.status] ?? 9);
if (statusCompare !== 0) return statusCompare;
return (a.title ?? "").localeCompare(b.title ?? "");
});
display(html`<h2>Tasks</h2>`);
if (sortedTasks.length === 0) {
display(html`<p style="color:gray">No tasks are attached to this workstream.</p>`);
} else {
display(html`<table class="task-table">
<thead><tr><th>Status</th><th>Priority</th><th>Task</th><th>Human</th></tr></thead>
<tbody>${sortedTasks.map(t => html`<tr>
<td><span class="task-status task-status-${t.status}">${t.status}</span></td>
<td>${t.priority ?? "—"}</td>
<td><a href="/tasks/${t.id}">${t.title ?? t.id}</a></td>
<td>${t.needs_human ? "yes" : ""}</td>
</tr>`)}</tbody>
</table>`);
}
const FIELD_ORDER = [
"id","slug","title","status","topic_id","repo_id","repo_goal_id",
"created_at","updated_at",
];
const rows = FIELD_ORDER.map(k => fieldRow(k, raw[k] ?? null));
for (const k of Object.keys(raw)) {
if (!FIELD_ORDER.includes(k)) rows.push(fieldRow(k, raw[k]));
}
display(html`<table style="border-collapse:collapse;width:100%;max-width:640px">
<colgroup><col style="width:160px"><col></colgroup>
<tbody>${rows}</tbody>
</table>`);
}