generated from coulomb/repo-seed
Overview shows workstreams by repo now and allows drilldown
This commit is contained in:
@@ -9,19 +9,56 @@ import {fieldRow} from "../components/field-help.js";
|
||||
|
||||
```js
|
||||
const wsId = observable.params.id;
|
||||
const raw = await 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)}));
|
||||
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: {}})),
|
||||
]);
|
||||
```
|
||||
|
||||
```js
|
||||
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="/workstreams">← Workstreams</a> | <a href="/token-cost">← Token Cost</a></p>`);
|
||||
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",
|
||||
@@ -39,3 +76,52 @@ if (raw.error) {
|
||||
</table>`);
|
||||
}
|
||||
```
|
||||
|
||||
<style>
|
||||
.ws-summary {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 0.75rem;
|
||||
margin: 1rem 0 1.25rem;
|
||||
max-width: 760px;
|
||||
}
|
||||
.ws-summary div {
|
||||
background: var(--theme-background-alt);
|
||||
border-radius: 6px;
|
||||
padding: 0.75rem 0.9rem;
|
||||
}
|
||||
.ws-summary span {
|
||||
display: block;
|
||||
color: gray;
|
||||
font-size: 0.72rem;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 0.2rem;
|
||||
}
|
||||
.ws-summary strong {
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.task-table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
max-width: 900px;
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
.task-table th, .task-table td {
|
||||
border-bottom: 1px solid var(--theme-foreground-faint);
|
||||
padding: 0.42rem 0.5rem;
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
}
|
||||
.task-status {
|
||||
border-radius: 4px;
|
||||
display: inline-block;
|
||||
font-size: 0.72rem;
|
||||
padding: 0.12rem 0.38rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.task-status-done { background: #e8f5e9; color: #1b5e20; }
|
||||
.task-status-in_progress { background: #e3f2fd; color: #0d47a1; }
|
||||
.task-status-blocked { background: #fff3e0; color: #bf360c; }
|
||||
.task-status-todo { background: #f1f5f9; color: #334155; }
|
||||
.task-status-cancelled { background: #f3f4f6; color: #6b7280; }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user