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';
});
}