Files

259 lines
8.1 KiB
JavaScript

function openVersionCreatePopup(projectId) {
resetVersionPopup();
document.getElementById('versionFormProjectId').value = projectId;
openPopup('createVersion');
}
function openVersionEditPopup() {
if (!currentVersion) return;
resetVersionPopup();
const form = document.getElementById('createVersionForm');
if (!form) return;
form.dataset.mode = 'edit';
document.getElementById('versionPopupTitle').textContent = `Edit ${currentVersion.name}`;
document.getElementById('versionPopupSubmit').textContent = 'Update Version';
document.getElementById('versionFormVersionId').value = currentVersion.id;
document.getElementById('versionFormProjectId').value = currentVersion.project;
document.getElementById('versionFormName').value = currentVersion.name ?? '';
document.getElementById('versionFormDescription').value = currentVersion.description ?? '';
document.getElementById('versionFormDueDate').value = currentVersion.due_date ?? '';
document.getElementById('versionFormReleasedDate').value = currentVersion.released_date ?? '';
openPopup('createVersion');
}
async function loadVersion(versionId, pushHistory = true) {
const view = showView('versionView');
if (!view) return;
if (pushHistory) {
const url = new URL(window.location.href);
url.searchParams.set('page', 'home');
url.searchParams.set('version', versionId);
url.searchParams.delete('task');
url.searchParams.delete('project');
url.searchParams.delete('tasks');
url.searchParams.delete('profile');
url.searchParams.delete('admin');
window.history.pushState({}, '', url);
}
const result = await apiGet('/api/version.php', {
api: 'VersionInfo',
version_id: versionId
});
if (!result.success) {
setText('versionName', 'Version not found');
setText('versionDescription', '');
return;
}
await renderVersion(result.version);
await loadVersionTasks(versionId);
await ensureProjectOpen(result.version.project);
}
async function renderVersion(version) {
currentVersion = version;
setVersionEditableText('versionKey', `Version ${version.id}`);
setVersionEditableText('versionName', version.name, version.name);
setVersionEditableText('versionDescription', version.description, version.description, 'No description provided.');
setText('versionProject', version.project);
setText('versionCreated', version.created_date || '-');
setVersionEditableText('versionDueDate', version.due_date, version.due_date, 'No due date');
setVersionEditableText('versionReleasedDate', version.released_date, version.released_date, 'Not released');
}
async function loadVersionTasks(versionId, page = 1) {
const list = document.getElementById('versionTaskList');
const empty = document.getElementById('versionTaskEmpty');
if (!list || !empty) return;
list.innerHTML = '<div class="version-task-loading">Loading tasks...</div>';
empty.hidden = true;
currentVersionTasks = [];
versionTaskPagination = null;
const result = await apiGet('/api/task.php', {
api: 'ListTasksByVersion',
version_id: versionId,
page,
per_page: taskTablePageSize,
sort: versionTaskSort.field,
direction: versionTaskSort.direction
});
if (!result.success) {
list.innerHTML = '<div class="alert alert-danger mb-0">Could not load tasks.</div>';
return;
}
if (!result.tasks.length) {
list.innerHTML = '';
empty.hidden = false;
renderTaskTablePagination('version');
return;
}
currentVersionTasks = await normalizeTaskTableRows(result.tasks);
versionTaskPagination = result.pagination ?? null;
renderTaskTable('version');
}
function initVersionInlineEditing() {
if (!canEditVersions()) return;
document.querySelectorAll('.version-editable').forEach((element) => {
if (element.dataset.inlineReady === 'true') return;
element.dataset.inlineReady = 'true';
element.title = 'Click to edit';
element.addEventListener('click', () => openVersionInlineEditor(element));
element.addEventListener('keydown', (event) => {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
openVersionInlineEditor(element);
}
});
});
}
async function openVersionInlineEditor(element) {
if (!canEditVersions() || !currentVersion || element.querySelector('.version-inline-form')) return;
const field = element.dataset.versionField;
const currentValue = element.dataset.currentValue ?? '';
const originalHtml = element.innerHTML;
const originalClasses = Array.from(element.classList);
element.classList.remove('is-empty');
element.innerHTML = '';
const form = document.createElement('form');
form.className = 'version-inline-form';
const input = createVersionInlineInput(field, currentValue);
form.appendChild(input);
const actions = document.createElement('div');
actions.className = 'version-inline-actions';
actions.innerHTML = `
<button class="btn btn-sm btn-primary" type="submit">
<i class="fa-solid fa-check"></i>
</button>
<button class="btn btn-sm btn-outline-secondary" type="button" data-version-inline-cancel>
<i class="fa-solid fa-xmark"></i>
</button>
`;
form.appendChild(actions);
element.appendChild(form);
input.focus();
if (input.select && input.type !== 'date') {
input.select();
}
let editorClosed = false;
const restore = () => {
if (editorClosed) return;
editorClosed = true;
element.innerHTML = originalHtml;
element.className = originalClasses.join(' ');
};
let saving = false;
const save = async () => {
if (saving) return;
const nextValue = input.value;
if (String(nextValue) === String(currentValue)) {
restore();
return;
}
saving = true;
element.classList.add('is-saving');
const result = await apiPost('/api/version.php', {
api: 'Edit',
version_id: currentVersion.id
}, {
[field]: nextValue
});
if (!result.success) {
alert(result.error || 'Could not update version.');
saving = false;
restore();
return;
}
editorClosed = true;
element.classList.remove('is-saving');
currentVersion = {
...currentVersion,
[field]: nextValue === '' ? null : nextValue
};
taskLookupCache.versionsByProject.delete(currentVersion.project);
await renderVersion(currentVersion);
projectTreePromise = loadProjectTree();
await projectTreePromise;
};
form.addEventListener('submit', async (event) => {
event.preventDefault();
await save();
});
form.querySelector('[data-version-inline-cancel]')?.addEventListener('click', (event) => {
event.preventDefault();
event.stopPropagation();
restore();
});
input.addEventListener('keydown', async (event) => {
if (event.key === 'Escape') {
event.preventDefault();
restore();
}
if (event.key === 'Enter' && input.tagName === 'TEXTAREA' && (event.ctrlKey || event.metaKey)) {
event.preventDefault();
await save();
}
});
}
function createVersionInlineInput(field, currentValue) {
if (field === 'description') {
const textarea = document.createElement('textarea');
textarea.className = 'form-control version-inline-input';
textarea.rows = 5;
textarea.value = currentValue;
return textarea;
}
const input = document.createElement('input');
input.className = 'form-control version-inline-input';
input.type = field === 'due_date' || field === 'released_date' ? 'date' : 'text';
input.value = currentValue;
input.required = field === 'name';
return input;
}