feat: add task filtering and search (#16)

This commit is contained in:
2026-03-04 18:08:12 -08:00
parent 1f17d44ee2
commit fa5e1887f4
3 changed files with 196 additions and 0 deletions

View File

@@ -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 = '<option value="">All Assignees</option>' +
assignees.map(a => `<option value="${a}">${a}</option>`).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();
}

View File

@@ -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;
}
}

View File

@@ -1,4 +1,31 @@
<section id="page-tasks" class="page active">
<div class="task-controls">
<div class="task-search">
<input type="text" id="task-search" placeholder="🔍 Search tasks..." />
</div>
<div class="task-filters">
<select id="filter-status">
<option value="">All Statuses</option>
<option value="Backlog">Backlog</option>
<option value="Todo">Todo</option>
<option value="In Progress">In Progress</option>
<option value="Review">Review</option>
<option value="Done">Done</option>
</select>
<select id="filter-assignee">
<option value="">All Assignees</option>
</select>
<select id="filter-priority">
<option value="">All Priorities</option>
<option value="Critical">Critical</option>
<option value="High">High</option>
<option value="Medium">Medium</option>
<option value="Low">Low</option>
</select>
<button id="clear-filters" class="btn-secondary">Clear Filters</button>
</div>
</div>
<div class="composer">
<h2>Create Task</h2>
<form id="task-form">