generated from coulomb/repo-seed
feat(dashboard): Interventions page improvements and action-confirm modal
- Move Interventions under Workstreams in the navigator - Add action-confirm.js: shared modal component for actions requiring a mandatory comment (survives live-poll re-renders, unlike inline DOM mutation) - Wire action-confirm into Interventions (Mark done) and Decisions (Resolve) - Fix Interventions completed section: fetch all tasks and filter client-side so resolved interventions (needs_human=false) still appear under Completed - Add docs/interventions.md help page with ? button on the h1 - Replace all hardcoded "Bernd" with "human" across dashboard src and docs Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -103,8 +103,9 @@ function fmtDuration(ms) {
|
||||
# Decisions
|
||||
|
||||
```js
|
||||
import {withDocHelp} from "./components/doc-overlay.js";
|
||||
import {injectTocTop} from "./components/toc-sidebar.js";
|
||||
import {withDocHelp} from "./components/doc-overlay.js";
|
||||
import {injectTocTop} from "./components/toc-sidebar.js";
|
||||
import {openActionConfirm} from "./components/action-confirm.js";
|
||||
|
||||
// ── KPI computation (uses full data, not filtered) ──────────────────────────
|
||||
const _resolved5 = data
|
||||
@@ -331,6 +332,24 @@ if (filtered.length === 0) {
|
||||
const _ageText = d.decided_at ? `took ${fmtDuration(_ageMs)}` : `open ${fmtDuration(_ageMs)}`;
|
||||
const _ageWarn = _isOpen && _meanResolveMs !== null && _ageMs > _meanResolveMs;
|
||||
|
||||
function onResolve() {
|
||||
openActionConfirm({
|
||||
title: "Resolve Decision",
|
||||
entityTitle: d.title,
|
||||
label: "Rationale",
|
||||
placeholder: "Why is this resolved, and what was decided?",
|
||||
confirmLabel: "Resolve",
|
||||
onConfirm: async (rationale) => {
|
||||
const res = await fetch(`${API}/decisions/${d.id}/`, {
|
||||
method: "PATCH",
|
||||
headers: {"Content-Type": "application/json"},
|
||||
body: JSON.stringify({status: "resolved", rationale, decided_by: "human"}),
|
||||
});
|
||||
if (!res.ok) throw new Error(`API error ${res.status}`);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return html`<div class="dec-item" style="border-left-color:${border}">
|
||||
<div class="dec-item-header">
|
||||
<span class="dec-badge ${TYPE_CLASS[d.decision_type] ?? ''}">${d.decision_type}</span>
|
||||
@@ -343,6 +362,7 @@ if (filtered.length === 0) {
|
||||
</span>` : ""}
|
||||
<span class="dec-age ${_ageWarn ? 'dec-age-warn' : ''}">${_ageText}</span>
|
||||
<span class="dec-date">${fmtDate(d.created_at)}</span>
|
||||
${_isOpen ? html`<button class="dec-resolve-btn" onclick=${onResolve}>Resolve</button>` : ""}
|
||||
</div>
|
||||
<div class="dec-title">${d.title}</div>
|
||||
${snippet ? html`<div class="dec-snippet">${snippet}${snippet.length < (d.description || d.rationale || "").length ? "…" : ""}</div>` : ""}
|
||||
@@ -453,6 +473,10 @@ if (escalated.length > 0) {
|
||||
.dec-resolved-by { font-size: 0.78rem; color: #22c55e; margin-top: 0.3rem; }
|
||||
.dec-escalation-note { font-size: 0.78rem; color: #b45309; margin-top: 0.3rem; background: #fef3c7; border-radius: 4px; padding: 0.25rem 0.5rem; }
|
||||
|
||||
/* ── Resolve button ───────────────────────────────────────────────────────── */
|
||||
.dec-resolve-btn { margin-left: auto; padding: 0.15rem 0.6rem; border-radius: 6px; border: 1px solid #22c55e; background: #f0fdf4; color: #166534; font-size: 0.7rem; font-weight: 600; cursor: pointer; }
|
||||
.dec-resolve-btn:hover { background: #dcfce7; }
|
||||
|
||||
/* ── Escalation warning ───────────────────────────────────────────────────── */
|
||||
.escalation-box { background: #fff3cd; border: 2px solid orange; border-radius: 8px; padding: 1rem; margin-top: 1rem; }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user