diff --git a/src/main/resources/static/assets/viewer/demo.js b/src/main/resources/static/assets/viewer/demo.js index 51466a8..854e89a 100644 --- a/src/main/resources/static/assets/viewer/demo.js +++ b/src/main/resources/static/assets/viewer/demo.js @@ -7,7 +7,7 @@ const ACTIVE_STATUSES = new Set(["ACCEPTED", "SUBMITTED", "PROCESSING"]); const DEMO_AUTH_HEADERS = { "X-Clearfolio-Tenant-Id": "buyer-demo", "X-Clearfolio-Subject-Id": "buyer-demo-operator", - "X-Clearfolio-Permissions": "job:create,job:read,job:retry,viewer:read,artifact-link:create,artifact-link:revoke,audit:read,analytics:read", + "X-Clearfolio-Permissions": "job:create,job:read,job:retry,job:delete,viewer:read,artifact-link:create,artifact-link:revoke,audit:read,analytics:read", }; const el = { @@ -81,13 +81,16 @@ function updateJob(jobId, patch, { refreshKpisAfterUpdate = true } = {}) { } } -function createLink(href, label) { +function createLink(href, label, ariaLabel) { const link = document.createElement("a"); link.href = href; link.textContent = label; link.className = "table-link"; link.target = "_blank"; link.rel = "noopener noreferrer"; + if (ariaLabel) { + link.setAttribute("aria-label", ariaLabel); + } return link; } @@ -110,11 +113,14 @@ async function openJsonDocument(url, title) { : "Unable to load JSON evidence with the current tenant claim."; } -function createActionButton(label, onClick) { +function createActionButton(label, onClick, ariaLabel) { const button = document.createElement("button"); button.type = "button"; button.textContent = label; button.className = "btn btn-secondary btn-compact"; + if (ariaLabel) { + button.setAttribute("aria-label", ariaLabel); + } button.addEventListener("click", onClick); return button; } @@ -138,7 +144,8 @@ function renderHistory(history = loadHistory()) { const submittedCell = document.createElement("td"); const actionsCell = document.createElement("td"); - fileCell.textContent = job.fileName || "Document"; + const fileName = job.fileName || "Document"; + fileCell.textContent = fileName; statusCell.textContent = job.status || "SUBMITTED"; submittedCell.textContent = job.submittedAt || ""; actionsCell.className = "table-actions"; @@ -153,13 +160,46 @@ function renderHistory(history = loadHistory()) { btn.replaceChildren(...initialChildren); btn.disabled = false; }); - })); + }, `Details for ${fileName}`)); actionsCell.appendChild(createActionButton("Status JSON", () => { void openJsonDocument(job.statusUrl, "Clearfolio status JSON"); - })); + }, `Status JSON for ${fileName}`)); } if (job.jobId) { - actionsCell.appendChild(createLink(`/viewer/${encodeURIComponent(job.jobId)}`, "Open viewer")); + actionsCell.appendChild(createLink(`/viewer/${encodeURIComponent(job.jobId)}`, "Open viewer", `Open viewer for ${fileName}`)); + if (job.status === "SUCCEEDED") { + actionsCell.appendChild(createLink(`/api/v1/convert/jobs/${encodeURIComponent(job.jobId)}/download`, "Download", `Download ${fileName}`)); + } + actionsCell.appendChild(createActionButton("Delete", async (e) => { + if (!window.confirm(`Are you sure you want to delete ${fileName}?`)) { + return; + } + const btn = e.currentTarget; + const initialChildren = Array.from(btn.childNodes); + btn.disabled = true; + btn.textContent = "Deleting..."; + try { + const res = await fetch(`/api/v1/convert/jobs/${encodeURIComponent(job.jobId)}`, { + method: "DELETE", + headers: jsonHeaders() + }); + if (res.ok || res.status === 404) { + const currentHistory = loadHistory(); + const newHistory = currentHistory.filter(j => j.jobId !== job.jobId); + saveHistory(newHistory); + renderHistory(newHistory); + void refreshKpis(); + } else { + const data = await res.json().catch(() => null); + setError((data && data.message) || `Failed to delete ${fileName}.`); + } + } catch (err) { + setError(`Network error while deleting ${fileName}.`); + } finally { + btn.replaceChildren(...initialChildren); + btn.disabled = false; + } + }, `Delete ${fileName}`)); } row.append(fileCell, statusCell, submittedCell, actionsCell);