let kanbanData = null; async function renderKanban() { const content = document.getElementById('pageContent'); content.innerHTML = `
`; await loadKanbanData(); } async function loadKanbanData() { try { const data = await api.getKanbanBoard(); kanbanData = data; renderKanbanBoard(); } catch (err) { const board = document.getElementById('kanbanBoard'); if (board) board.innerHTML = `
📋
Failed to load kanban
${escapeHtml(err.message)}
`; } } function renderKanbanBoard() { const board = document.getElementById('kanbanBoard'); if (!board || !kanbanData) return; const columnsObj = kanbanData.columns || {}; const columns = Object.keys(columnsObj); const allTasks = kanbanData.tasks || Object.values(columnsObj).flat(); const filterText = (document.getElementById('kanbanFilterInput')?.value || '').toLowerCase(); const filterPriority = document.getElementById('kanbanFilterPriority')?.value || 'all'; const filterCategory = document.getElementById('kanbanFilterCategory')?.value || 'all'; const columnLabels = { triage: 'Triage', todo: 'To Do', ready: 'Ready', in_progress: 'In Progress', blocked: 'Blocked', done: 'Done', backlog: 'Backlog', review: 'Review' }; const columnIcons = { triage: '🔍', todo: '📝', ready: '✅', in_progress: '🔄', blocked: '🚫', done: '🎉', backlog: '📋', review: '🔍' }; board.innerHTML = columns.map(col => { let colTasks = (columnsObj[col] || []).filter(t => { if (filterText) return (t.title || '').toLowerCase().includes(filterText) || (t.body || t.description || '').toLowerCase().includes(filterText); return true; }).filter(t => filterPriority === 'all' || (t.priority || 'medium') === filterPriority) .filter(t => filterCategory === 'all' || (t.category || 'general') === filterCategory); return `
${columnIcons[col] || '📌'} ${columnLabels[col] || col} ${colTasks.length}
${colTasks.length === 0 ? `
No tasks
` : colTasks.map(t => `
${t.priority || 'medium'}
${escapeHtml(t.title)}
${t.body ? `
${escapeHtml(t.body.substring(0, 80))}${t.body.length > 80 ? '...' : ''}
` : ''}
${t.assignee ? `👤 ${escapeHtml(t.assignee)}` : ''}
${t.status === 'blocked' ? `
🚫 Blocked
` : ''}
`).join('')}
`; }).join(''); } function showAddKanbanTask() { const modal = document.getElementById('modalContainer'); modal.innerHTML = ` `; } async function createKanbanTask() { const title = document.getElementById('kanbanTitle').value.trim(); if (!title) { showToast('Title is required', 'error'); return; } const tagsStr = document.getElementById('kanbanTags').value.trim(); const tags = tagsStr ? tagsStr.split(',').map(t => t.trim()).filter(Boolean) : []; try { await api.createKanbanTask({ title, body: document.getElementById('kanbanDescription').value.trim(), status: document.getElementById('kanbanStatus').value, priority: document.getElementById('kanbanPriority').value, assignee: document.getElementById('kanbanAssigned').value.trim(), }); showToast('Task added to kanban board!', 'success'); closeModal(); renderKanban(); } catch (err) { showToast('Failed to create task: ' + err.message, 'error'); } } let kanbanDraggedId = null; function onKanbanDrag(e, id) { kanbanDraggedId = id; e.dataTransfer.effectAllowed = 'move'; } async function onKanbanDrop(e, status) { e.preventDefault(); if (!kanbanDraggedId) return; try { await api.updateKanbanTask(kanbanDraggedId, { status }); kanbanDraggedId = null; renderKanban(); } catch (err) { showToast('Failed to move task: ' + err.message, 'error'); } } function showKanbanDetail(id) { const task = kanbanData?.tasks?.find(t => t.id === id); if (!task) return; const modal = document.getElementById('modalContainer'); modal.innerHTML = ` `; } async function completeKanbanTask(id) { try { await api.completeKanbanTask(id); closeModal(); renderKanban(); showToast('Task completed! ✅', 'success'); } catch (err) { showToast('Failed: ' + err.message, 'error'); } } async function blockKanbanTask(id) { try { await api.blockKanbanTask(id); closeModal(); renderKanban(); showToast('Task blocked 🚫', 'warning'); } catch (err) { showToast('Failed: ' + err.message, 'error'); } } async function unblockKanbanTask(id) { try { await api.unblockKanbanTask(id); closeModal(); renderKanban(); showToast('Task unblocked 🔓', 'success'); } catch (err) { showToast('Failed: ' + err.message, 'error'); } } async function deleteKanbanTask(id) { if (!confirm('Delete this task?')) return; try { await api.deleteKanbanTask(id); closeModal(); renderKanban(); showToast('Task deleted', 'info'); } catch (err) { showToast('Failed: ' + err.message, 'error'); } } function filterKanbanTasks() { renderKanbanBoard(); }