feat(dashboard): add entity detail modal and fixed-layout tables

Replace Inputs.table() with buildEntityTable() across workstreams and
tasks pages. Add click-to-detail modal (openEntityModal) on all entity
list views: workstreams, tasks, extension points, and technical debt.

- New component: src/components/entity-modal.js
  - openEntityModal(entity, type) — full-detail overlay (Esc/click-outside to close)
  - buildEntityTable(rows, cols, onRowClick) — table-layout:fixed, overflow-safe wrapper
  - CSS injected lazily; no separate stylesheet required

- Tables: table-layout:fixed keeps content within the content column;
  title col 32%, workstream col 14%, all cells ellipsis + title tooltip
- Cards (EP, TD): onclick → modal; workstream name span gets title tooltip
- Blocked task cards also wired to modal

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-27 18:28:44 +01:00
parent bd6e16394a
commit 0546a1bb2a
5 changed files with 479 additions and 36 deletions

View File

@@ -83,8 +83,9 @@ const filtered = data.filter(t =>
# Tasks
```js
import {injectTocTop} from "./components/toc-sidebar.js";
import {withDocHelp} from "./components/doc-overlay.js";
import {injectTocTop} from "./components/toc-sidebar.js";
import {withDocHelp} from "./components/doc-overlay.js";
import {openEntityModal, buildEntityTable} from "./components/entity-modal.js";
// ── KPI sidebar card ─────────────────────────────────────────────────────────
const _open = data.filter(t => ["todo", "in_progress", "blocked"].includes(t.status));
@@ -179,7 +180,7 @@ if (_blockedInFilter.length === 0) {
display(html`<p class="dim">No blocked tasks in current filter. ✓</p>`);
} else {
display(html`<div class="task-blocked-list">${_blockedInFilter.map(t => html`
<div class="task-blocked-item">
<div class="task-blocked-item entity-row" onclick=${() => openEntityModal(t, "task")}>
<div class="task-item-header">
<span class="task-badge task-priority-${t.priority}">${t.priority}</span>
<span class="task-context">${t.domain}</span>
@@ -209,15 +210,19 @@ const sorted = [...filtered].sort((a, b) => {
return (PRIORITY_ORDER[a.priority] ?? 9) - (PRIORITY_ORDER[b.priority] ?? 9);
});
display(Inputs.table(sorted.map(t => ({
Status: t.status,
Priority: t.priority,
Title: t.title,
Domain: t.domain,
Workstream: t.workstream_title,
Assignee: t.assignee ?? "—",
Due: t.due_date ?? "—",
})), {rows: 25}));
display(buildEntityTable(
sorted,
[
{label: "Status", key: "status"},
{label: "Priority", key: "priority"},
{label: "Title", key: "title", cls: "et-title-col et-title-cell"},
{label: "Domain", key: "domain"},
{label: "Workstream", key: "workstream_title", cls: "et-ws-col et-ws-cell"},
{label: "Assignee", render: t => t.assignee ?? "—"},
{label: "Due", render: t => t.due_date ?? "—"},
],
t => openEntityModal(t, "task"),
));
```
<style>