feat: add task filtering and search (#16)
This commit is contained in:
107
public/app.js
107
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 = '<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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user