async function loadProjectTasks(projectId, page = 1, pushHistory = true) { const view = showView('taskListView'); if (!view) return; if (pushHistory) { const url = new URL(window.location.href); url.searchParams.set('page', 'home'); url.searchParams.set('project', projectId); url.searchParams.set('tasks', '1'); url.searchParams.delete('task'); url.searchParams.delete('version'); url.searchParams.delete('profile'); url.searchParams.delete('admin'); window.history.pushState({}, '', url); } currentProjectTaskProject = projectId; setText('taskListProjectKey', projectId); setText('taskListTitle', `${projectId} Tasks`); const container = document.getElementById('taskListContainer'); const empty = document.getElementById('taskListEmpty'); container.innerHTML = '
Loading tasks...
'; empty.hidden = true; currentProjectTasks = []; projectTaskPagination = null; const result = await apiGet('/api/task.php', { api: 'ListTasksByProject', project_id: projectId, page, per_page: taskTablePageSize, sort: projectTaskSort.field, direction: projectTaskSort.direction }); if (!result.success) { container.innerHTML = '
Could not load tasks.
'; return; } if (!result.tasks.length) { container.innerHTML = ''; empty.hidden = false; renderTaskTablePagination('project'); return; } currentProjectTasks = await normalizeTaskTableRows(result.tasks); projectTaskPagination = result.pagination ?? null; renderTaskTable('project'); } async function normalizeTaskTableRows(tasks) { const [types, priorities] = await Promise.all([ getTaskTypes(), getTaskPriorities() ]); return tasks.map((task) => { const normalizedTask = typeof task === 'string' ? { id: task, title: '', type: null, priority: null } : task; const type = types.find((item) => String(item.id) === String(normalizedTask.type)) ?? null; const priority = priorities.find((item) => String(item.id) === String(normalizedTask.priority)) ?? null; return { ...normalizedTask, typeOption: type, priorityOption: priority, typeName: type?.name ?? '', priorityName: priority?.name ?? '', statusName: normalizedTask.status_state?.name ?? '', statusColor: normalizedTask.status_state?.color ?? '', assigneeUser: normalizedTask.assignee ? { id: normalizedTask.assignee, name: normalizedTask.assignee_name ?? `User ${normalizedTask.assignee}`, picture: normalizedTask.assignee_picture ?? null } : null, assigneeName: normalizedTask.assignee_name ?? '' }; }); } function renderTaskTable(kind) { const isVersion = kind === 'version'; const list = document.getElementById(isVersion ? 'versionTaskList' : 'taskListContainer'); const empty = document.getElementById(isVersion ? 'versionTaskEmpty' : 'taskListEmpty'); const tasks = isVersion ? currentVersionTasks : currentProjectTasks; if (!list || !empty) return; updateTaskTableSortButtons(kind); list.innerHTML = ''; empty.hidden = tasks.length > 0; tasks.forEach((task) => { const button = document.createElement('button'); button.type = 'button'; button.className = 'version-task-row version-task-item'; button.innerHTML = ` ${escapeHtml(task.id)} ${escapeHtml(task.title || '-')} ${renderMetaBadge(task.typeOption)} ${renderMetaBadge(task.priorityOption)} ${renderUser(task.assigneeUser)} ${renderTaskTableStatus(task)} `; button.addEventListener('click', () => loadTask(task.id)); list.appendChild(button); }); renderTaskTablePagination(kind); } function renderTaskTableStatus(task) { if (!task.statusName) return '-'; const color = task.statusColor || '#6c757d'; return ` ${escapeHtml(task.statusName)} `; } function renderTaskTablePagination(kind) { const isVersion = kind === 'version'; const pagination = isVersion ? versionTaskPagination : projectTaskPagination; const container = document.getElementById(isVersion ? 'versionTaskPagination' : 'projectTaskPagination'); if (!container) return; if (!pagination || pagination.total <= pagination.per_page) { container.innerHTML = ''; return; } const start = ((pagination.page - 1) * pagination.per_page) + 1; const end = Math.min(pagination.total, pagination.page * pagination.per_page); container.innerHTML = ` ${escapeHtml(start)}-${escapeHtml(end)} of ${escapeHtml(pagination.total)}
`; } function updateTaskTableSortButtons(kind) { const isVersion = kind === 'version'; const selector = isVersion ? '[data-version-task-sort]' : '[data-project-task-sort]'; const state = isVersion ? versionTaskSort : projectTaskSort; document.querySelectorAll(selector).forEach((button) => { const field = isVersion ? button.dataset.versionTaskSort : button.dataset.projectTaskSort; const isActive = field === state.field; const icon = button.querySelector('i'); button.classList.toggle('is-active', isActive); if (!icon) return; icon.className = isActive ? `fa-solid ${state.direction === 'asc' ? 'fa-sort-up' : 'fa-sort-down'}` : 'fa-solid fa-sort'; }); }