| 1 |
|
| 2 |
* GoingsOn - Task Board Module |
| 3 |
* View mode toggling (list vs board) and Kanban board rendering. |
| 4 |
* Loaded before tasks.js -- populates GoingsOn.taskBoard namespace. |
| 5 |
|
| 6 |
|
| 7 |
(function() { |
| 8 |
'use strict'; |
| 9 |
const esc = GoingsOn.utils.escapeHtml; |
| 10 |
|
| 11 |
|
| 12 |
let viewMode = 'list'; |
| 13 |
|
| 14 |
|
| 15 |
* Get the current view mode. |
| 16 |
* @returns {string} 'list' or 'board' |
| 17 |
|
| 18 |
function getViewMode() { |
| 19 |
return viewMode; |
| 20 |
} |
| 21 |
|
| 22 |
|
| 23 |
* Switch between list and board view modes. |
| 24 |
* @param {string} mode - 'list' or 'board' |
| 25 |
|
| 26 |
function setViewMode(mode) { |
| 27 |
|
| 28 |
|
| 29 |
if (mode === 'board' && GoingsOn.touch?.isTouchDevice) { |
| 30 |
mode = 'list'; |
| 31 |
} |
| 32 |
viewMode = mode; |
| 33 |
|
| 34 |
|
| 35 |
document.querySelectorAll('#task-view-toggle .view-toggle-btn').forEach(btn => { |
| 36 |
btn.classList.toggle('active', btn.dataset.mode === mode); |
| 37 |
}); |
| 38 |
|
| 39 |
const taskTable = document.getElementById('task-table'); |
| 40 |
const kanbanBoard = document.getElementById('task-kanban-board'); |
| 41 |
const statusFilter = document.getElementById('filter-status'); |
| 42 |
const pagination = document.getElementById('task-pagination'); |
| 43 |
|
| 44 |
if (mode === 'board') { |
| 45 |
taskTable.classList.add('hidden'); |
| 46 |
kanbanBoard.classList.remove('hidden'); |
| 47 |
if (statusFilter) statusFilter.style.display = 'none'; |
| 48 |
if (pagination) pagination.classList.add('hidden'); |
| 49 |
renderBoard(); |
| 50 |
} else { |
| 51 |
taskTable.classList.remove('hidden'); |
| 52 |
kanbanBoard.classList.add('hidden'); |
| 53 |
if (statusFilter) statusFilter.style.display = ''; |
| 54 |
GoingsOn.tasks.renderFilteredTasks(); |
| 55 |
} |
| 56 |
} |
| 57 |
|
| 58 |
|
| 59 |
* Fetch tasks and render the Kanban board with current filters applied. |
| 60 |
|
| 61 |
async function renderBoard() { |
| 62 |
const uiFilters = GoingsOn.tasksFilter.getFilters(); |
| 63 |
|
| 64 |
|
| 65 |
const backendFilters = { |
| 66 |
showSnoozed: uiFilters.showSnoozed, |
| 67 |
waitingOnly: uiFilters.waitingOnly, |
| 68 |
offset: 0, |
| 69 |
limit: 500, |
| 70 |
sortColumn: GoingsOn.tasksFilter.getSortColumn(), |
| 71 |
sortDirection: GoingsOn.tasksFilter.getSortDirection(), |
| 72 |
}; |
| 73 |
|
| 74 |
|
| 75 |
if (uiFilters.projectId) backendFilters.projectId = uiFilters.projectId; |
| 76 |
if (uiFilters.milestoneId) backendFilters.milestoneId = uiFilters.milestoneId; |
| 77 |
if (uiFilters.priority) { |
| 78 |
const priorityMap = { H: 'High', M: 'Medium', L: 'Low' }; |
| 79 |
backendFilters.priority = priorityMap[uiFilters.priority] || uiFilters.priority; |
| 80 |
} |
| 81 |
|
| 82 |
try { |
| 83 |
const response = await GoingsOn.api.tasks.listFiltered(backendFilters); |
| 84 |
let displayTasks = response.tasks; |
| 85 |
displayTasks = displayTasks.map(t => ({ ...t, displayDescription: t.description })); |
| 86 |
|
| 87 |
GoingsOn.state.set('tasks', displayTasks); |
| 88 |
GoingsOn.tasksKanban.render(displayTasks); |
| 89 |
} catch (err) { |
| 90 |
const board = document.getElementById('task-kanban-board'); |
| 91 |
if (board) { |
| 92 |
board.innerHTML = `<div class="loading loading--error">Failed to load board: ${esc(err.message || String(err))}. <button class="btn-link" onclick="GoingsOn.taskBoard.renderBoard()">Try again</button></div>`; |
| 93 |
} |
| 94 |
} |
| 95 |
} |
| 96 |
|
| 97 |
|
| 98 |
|
| 99 |
GoingsOn.taskBoard = { |
| 100 |
getViewMode, |
| 101 |
setViewMode, |
| 102 |
renderBoard, |
| 103 |
}; |
| 104 |
|
| 105 |
})(); |
| 106 |
|