summaryrefslogtreecommitdiff
path: root/web
diff options
context:
space:
mode:
Diffstat (limited to 'web')
-rw-r--r--web/app.js60
1 files changed, 60 insertions, 0 deletions
diff --git a/web/app.js b/web/app.js
index b22d352..249b054 100644
--- a/web/app.js
+++ b/web/app.js
@@ -1390,6 +1390,11 @@ function renderActiveTab(allTasks) {
// drag ends will pick up any server-side change.
if (!draggingStoryId) renderStoriesPanel();
break;
+ case 'tasks': {
+ const panel = document.querySelector('[data-panel="tasks"]');
+ if (panel) renderTasksBoard(allTasks, panel);
+ break;
+ }
case 'stats':
Promise.all([
fetchRecentExecutions(BASE_PATH, fetch),
@@ -3743,6 +3748,61 @@ function renderStoriesBoard(stories, container) {
container.appendChild(board);
}
+// ── Tasks tab: unified board ─────────────────────────────────────────────────
+
+function renderTasksBoard(tasks, container) {
+ const groups = groupTasksByColumn(tasks);
+ let board = container.querySelector('.tasks-board');
+ if (!board) {
+ board = document.createElement('div');
+ board.className = 'tasks-board';
+ container.appendChild(board);
+ }
+
+ for (const col of TASK_COLUMNS) {
+ let colEl = board.querySelector(`[data-column-key="${col.key}"]`);
+ if (!colEl) {
+ colEl = document.createElement('div');
+ colEl.className = 'tasks-column';
+ colEl.dataset.columnKey = col.key;
+
+ const header = document.createElement('div');
+ header.className = 'tasks-column-header';
+ const title = document.createElement('span');
+ title.textContent = col.label;
+ const count = document.createElement('span');
+ count.className = 'tasks-column-count';
+ header.append(title, count);
+ colEl.appendChild(header);
+
+ const list = document.createElement('div');
+ list.className = 'tasks-column-list';
+ colEl.appendChild(list);
+
+ board.appendChild(colEl);
+ }
+
+ const countEl = colEl.querySelector('.tasks-column-count');
+ countEl.textContent = String(groups[col.key].length);
+
+ const list = colEl.querySelector('.tasks-column-list');
+ renderTasksIntoContainer(groups[col.key], list, `No tasks in ${col.label.toLowerCase()}.`);
+ }
+
+ // Attach/refresh the inline log tail for every visible card that has one
+ // (createTaskCard omits it for PENDING/QUEUED — see Task 4).
+ board.querySelectorAll('.task-log-tail').forEach((logArea) => {
+ const taskId = logArea.dataset.logTarget;
+ if (taskId) ensureTaskLogStream(taskId, logArea);
+ });
+
+ // Elapsed timers update on every poll tick (same convention the old
+ // Running-tab card used — no separate ticking interval).
+ board.querySelectorAll('.task-elapsed[data-started-at]').forEach((el) => {
+ el.textContent = formatElapsed(el.dataset.startedAt || null);
+ });
+}
+
// ── Stories tab: epic swimlanes ───────────────────────────────────────────────
function renderSwimlane(lane) {