259 lines
8.1 KiB
JavaScript
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;
|
|
}
|
|
|