182 lines
6.6 KiB
JavaScript
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';
|
|
});
|
|
}
|