From fa5e1887f41cf3e865ffc3a32470549c94262cc3 Mon Sep 17 00:00:00 2001 From: Christopher Mayor Date: Wed, 4 Mar 2026 18:08:12 -0800 Subject: [PATCH] feat: add task filtering and search (#16) --- public/app.js | 107 ++++++++++++++++++++++++++++++++++++++++++++++ public/styles.css | 62 +++++++++++++++++++++++++++ views/tasks.html | 27 ++++++++++++ 3 files changed, 196 insertions(+) diff --git a/public/app.js b/public/app.js index 3b2883e..89a631f 100644 --- a/public/app.js +++ b/public/app.js @@ -68,6 +68,112 @@ const COLUMNS = { }; let wikiPages = []; +let allTasks = []; +let taskFilters = { search: '', status: '', assignee: '', priority: '' }; + +async function loadTasks() { + const res = await fetch('/api/tasks'); + allTasks = await res.json(); + + // Populate assignee filter dropdown + const assigneeFilter = document.getElementById('filter-assignee'); + if (assigneeFilter) { + const assignees = [...new Set(allTasks.map(t => t.assignee).filter(Boolean))]; + assigneeFilter.innerHTML = '' + + assignees.map(a => ``).join(''); + } + + applyTaskFilters(); +} + +function applyTaskFilters() { + let filtered = [...allTasks]; + + // Search filter + if (taskFilters.search) { + const search = taskFilters.search.toLowerCase(); + filtered = filtered.filter(t => + t.title.toLowerCase().includes(search) || + (t.description && t.description.toLowerCase().includes(search)) + ); + } + + // Status filter + if (taskFilters.status) { + filtered = filtered.filter(t => t.status === taskFilters.status); + } + + // Assignee filter + if (taskFilters.assignee) { + filtered = filtered.filter(t => t.assignee === taskFilters.assignee); + } + + // Priority filter + if (taskFilters.priority) { + filtered = filtered.filter(t => t.priority === taskFilters.priority); + } + + // Reset columns + Object.keys(COLUMNS).forEach((status) => { + COLUMNS[status].tasks = []; + }); + + // Distribute filtered tasks to columns + filtered.forEach((task) => { + if (COLUMNS[task.status]) { + COLUMNS[task.status].tasks.push(task); + } + }); + + renderBoard(); +} + +function initTaskFilters() { + const searchInput = document.getElementById('task-search'); + const statusFilter = document.getElementById('filter-status'); + const assigneeFilter = document.getElementById('filter-assignee'); + const priorityFilter = document.getElementById('filter-priority'); + const clearBtn = document.getElementById('clear-filters'); + + if (searchInput) { + searchInput.addEventListener('input', (e) => { + taskFilters.search = e.target.value; + applyTaskFilters(); + }); + } + + if (statusFilter) { + statusFilter.addEventListener('change', (e) => { + taskFilters.status = e.target.value; + applyTaskFilters(); + }); + } + + if (assigneeFilter) { + assigneeFilter.addEventListener('change', (e) => { + taskFilters.assignee = e.target.value; + applyTaskFilters(); + }); + } + + if (priorityFilter) { + priorityFilter.addEventListener('change', (e) => { + taskFilters.priority = e.target.value; + applyTaskFilters(); + }); + } + + if (clearBtn) { + clearBtn.addEventListener('click', () => { + taskFilters = { search: '', status: '', assignee: '', priority: '' }; + if (searchInput) searchInput.value = ''; + if (statusFilter) statusFilter.value = ''; + if (assigneeFilter) assigneeFilter.value = ''; + if (priorityFilter) priorityFilter.value = ''; + applyTaskFilters(); + }); + } +} let currentWikiPage = null; let allAgents = []; let usageStats = null; @@ -225,6 +331,7 @@ function initTasksPage() { loadTasks(); }); + initTaskFilters(); populateAgentDropdown(); loadTasks(); } diff --git a/public/styles.css b/public/styles.css index 1c15ae8..bca0ed1 100644 --- a/public/styles.css +++ b/public/styles.css @@ -938,3 +938,65 @@ label { .activity-desc { flex: 1; } + +/* Task Controls */ +.task-controls { + display: flex; + gap: 1rem; + margin-bottom: 1.5rem; + flex-wrap: wrap; +} + +.task-search { + flex: 1; + min-width: 200px; +} + +.task-search input { + width: 100%; + padding: 0.75rem 1rem; + background: var(--card-bg); + border: 1px solid var(--border-color); + border-radius: 8px; + color: var(--text-primary); + font-size: 0.95rem; +} + +.task-search input:focus { + outline: none; + border-color: var(--primary); +} + +.task-filters { + display: flex; + gap: 0.5rem; + flex-wrap: wrap; +} + +.task-filters select { + padding: 0.75rem 1rem; + background: var(--card-bg); + border: 1px solid var(--border-color); + border-radius: 8px; + color: var(--text-primary); + cursor: pointer; +} + +.task-filters select:focus { + outline: none; + border-color: var(--primary); +} + +@media (max-width: 768px) { + .task-controls { + flex-direction: column; + } + + .task-filters { + width: 100%; + } + + .task-filters select { + flex: 1; + } +} diff --git a/views/tasks.html b/views/tasks.html index ecf88fe..2001064 100644 --- a/views/tasks.html +++ b/views/tasks.html @@ -1,4 +1,31 @@
+
+ +
+ + + + +
+
+

Create Task