Files
Projectkiln/ProjectKiln/app/js/home/task_list.js

182 lines
6.6 KiB
JavaScript

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 = '<div class="version-task-loading">Loading tasks...</div>';
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 = '<div class="alert alert-danger mb-0">Could not load tasks.</div>';
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 = `
<span class="version-task-id">${escapeHtml(task.id)}</span>
<span class="version-task-title">${escapeHtml(task.title || '-')}</span>
<span>${renderMetaBadge(task.typeOption)}</span>
<span>${renderMetaBadge(task.priorityOption)}</span>
<span>${renderUser(task.assigneeUser)}</span>
<span>${renderTaskTableStatus(task)}</span>
`;
button.addEventListener('click', () => loadTask(task.id));
list.appendChild(button);
});
renderTaskTablePagination(kind);
}
function renderTaskTableStatus(task) {
if (!task.statusName) return '-';
const color = task.statusColor || '#6c757d';
return `
<span class="version-task-status" style="--task-status-color: ${escapeHtml(color)}">
${escapeHtml(task.statusName)}
</span>
`;
}
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 = `
<span>${escapeHtml(start)}-${escapeHtml(end)} of ${escapeHtml(pagination.total)}</span>
<div class="version-task-page-actions">
<button class="btn btn-sm btn-outline-secondary" type="button" data-task-page="${escapeHtml(kind)}" data-page="${escapeHtml(pagination.page - 1)}" ${pagination.page <= 1 ? 'disabled' : ''}>
<i class="fa-solid fa-chevron-left"></i>
</button>
<button class="btn btn-sm btn-outline-secondary" type="button" data-task-page="${escapeHtml(kind)}" data-page="${escapeHtml(pagination.page + 1)}" ${pagination.page >= pagination.total_pages ? 'disabled' : ''}>
<i class="fa-solid fa-chevron-right"></i>
</button>
</div>
`;
}
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';
});
}