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 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 currentWikiPage = null;
|
||||||
let allAgents = [];
|
let allAgents = [];
|
||||||
let usageStats = null;
|
let usageStats = null;
|
||||||
@@ -225,6 +331,7 @@ function initTasksPage() {
|
|||||||
loadTasks();
|
loadTasks();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
initTaskFilters();
|
||||||
populateAgentDropdown();
|
populateAgentDropdown();
|
||||||
loadTasks();
|
loadTasks();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -938,3 +938,65 @@ label {
|
|||||||
.activity-desc {
|
.activity-desc {
|
||||||
flex: 1;
|
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">
|
<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">
|
<div class="composer">
|
||||||
<h2>Create Task</h2>
|
<h2>Create Task</h2>
|
||||||
<form id="task-form">
|
<form id="task-form">
|
||||||
|
|||||||
Reference in New Issue
Block a user