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 = '
Loading tasks...
'; 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 = '
Could not load tasks.
'; 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 = ` `; 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; }