feat: add drag-and-drop task reordering (#14, #15, #16)

This commit is contained in:
2026-03-04 18:09:34 -08:00
parent fa5e1887f4
commit c310b22e8e
2 changed files with 96 additions and 2 deletions

View File

@@ -270,6 +270,7 @@ function renderBoard() {
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ status: 'Done' }), body: JSON.stringify({ status: 'Done' }),
}); });
initDragAndDrop();
loadTasks(); loadTasks();
}); });
@@ -328,11 +329,13 @@ function initTasksPage() {
}); });
e.target.reset(); e.target.reset();
initDragAndDrop();
loadTasks(); loadTasks();
}); });
initTaskFilters(); initTaskFilters();
populateAgentDropdown(); populateAgentDropdown();
initDragAndDrop();
loadTasks(); loadTasks();
} }
@@ -1375,3 +1378,69 @@ function renderGiteaActivity() {
</div> </div>
`).join(''); `).join('');
} }
// ============ DRAG AND DROP ============
let draggedTask = null;
function initDragAndDrop() {
const board = document.getElementById('board');
if (!board) return;
// Add drag listeners to columns
document.querySelectorAll('.column').forEach(column => {
column.addEventListener('dragover', handleDragOver);
column.addEventListener('drop', handleDrop);
column.addEventListener('dragleave', handleDragLeave);
});
}
function handleDragOver(e) {
e.preventDefault();
e.currentTarget.classList.add('drag-over');
}
function handleDragLeave(e) {
e.currentTarget.classList.remove('drag-over');
}
async function handleDrop(e) {
e.preventDefault();
const column = e.currentTarget;
column.classList.remove('drag-over');
if (!draggedTask) return;
const newStatus = column.dataset.status;
if (newStatus === draggedTask.status) return;
// Update task status via API
try {
await fetch(`/api/tasks/${draggedTask.id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ status: newStatus })
});
// Reload tasks
await initDragAndDrop();
loadTasks();
} catch (err) {
console.error('Failed to update task:', err);
}
draggedTask = null;
}
function makeTaskCardDraggable(cardEl, task) {
cardEl.draggable = true;
cardEl.addEventListener('dragstart', (e) => {
draggedTask = task;
cardEl.classList.add('dragging');
e.dataTransfer.effectAllowed = 'move';
});
cardEl.addEventListener('dragend', () => {
cardEl.classList.remove('dragging');
});
}

View File

@@ -1000,3 +1000,28 @@ label {
flex: 1; flex: 1;
} }
} }
/* Drag and Drop */
.task-card {
cursor: grab;
transition: transform 0.2s, box-shadow 0.2s, opacity 0.2s;
}
.task-card:active {
cursor: grabbing;
}
.task-card.dragging {
opacity: 0.5;
transform: scale(1.02);
}
.column.drag-over {
background: var(--hover-bg);
border: 2px dashed var(--primary);
}
.column.drag-over .column-header {
background: var(--primary);
color: white;
}