"use strict"; // Document detail page (US-34 to US-36): PDF preview, typed form, // pipeline proposals, live rendered-name dry-run, event history, and // the three distinct exits - draft save, full correction, validation. var docId = new URLSearchParams(window.location.search).get("id"); var cfg = null; var record = null; var controls = {}; // field name -> control element var originals = {}; // field name -> control value at load var dirty = {}; // field name -> boolean var dryErrors = []; var dryTimer = null; function $(id) { return document.getElementById(id); } async function init() { if (!docId) { window.location.href = "/"; return; } var rc = await api("GET", "/api/archivist/config"); if (rc.status !== 200) return; var rr = await api("GET", "/api/collections/documents/records/" + docId); if (rr.status !== 200) { alert("Document introuvable."); window.location.href = "/"; return; } cfg = rc.data; record = rr.data; initTheme("themeBtn"); renderHead(); loadPdf(); var evs = await loadEvents(); renderImportErrors(evs); renderHistory(evs); renderForm(); bindActions(); if (record.status === "processed") $("validateBtn").hidden = false; $("targetname").textContent = "Fichier actuel : " + (record.file_key || "—"); loadProposals(); } async function loadEvents() { var q = "/api/collections/document_events/records?perPage=200&sort=-timestamp&filter=" + encodeURIComponent("(document='" + docId + "')"); var r = await api("GET", q); return (r.status === 200 && r.data.items) ? r.data.items : []; } function renderHead() { document.title = "Archivist — " + (record.file_key || docId); $("docname").textContent = record.file_key || ("Document " + docId); var st = $("docstatus"); st.textContent = record.status || ""; st.className = "badge st-" + record.status; } async function loadPdf() { var iframe = $("pdf"); var error = $("pdferror"); try { var response = await apiBlob("/api/archivist/documents/" + docId + "/pdf"); if (!response.ok) { var data = null; try { data = await response.json(); } catch (e) {} error.textContent = "Aperçu indisponible : " + (data && data.message || "HTTP " + response.status); error.hidden = false; iframe.hidden = true; return; } var objectUrl = URL.createObjectURL(await response.blob()); iframe.src = objectUrl; window.addEventListener("pagehide", function () { URL.revokeObjectURL(objectUrl); }, { once: true }); } catch (e) { var reason = e && e.message ? e.message : String(e); console.error("PDF preview failed:", e); error.textContent = "Impossible de charger l'aperçu PDF : " + reason; error.hidden = false; iframe.hidden = true; } } function payloadOf(ev) { var p = ev.payload; if (typeof p === "string") { try { p = JSON.parse(p); } catch (e) { p = {}; } } return p || {}; } function renderImportErrors(evs) { var box = $("importerrors"); for (var i = 0; i < evs.length; i++) { var p = payloadOf(evs[i]); if (evs[i].event_type === "imported" && p.errors && p.errors.length) { box.hidden = false; box.innerHTML = ""; var head = document.createElement("div"); head.textContent = "Échec de la classification à l'import :"; box.appendChild(head); var ul = document.createElement("ul"); p.errors.forEach(function (e) { var li = document.createElement("li"); li.textContent = e; ul.appendChild(li); }); box.appendChild(ul); return; } } box.hidden = true; } function renderForm() { var form = $("docform"); form.innerHTML = ""; cfg.fields.forEach(function (f) { var row = document.createElement("div"); row.className = "fieldrow"; var label = document.createElement("label"); label.textContent = f.name + (f.mandatory ? " *" : ""); var ctl = buildValueControl(f, (cfg.enums || {})[f.name] || [], record[f.name]); controls[f.name] = ctl; originals[f.name] = ctl.value; dirty[f.name] = false; ctl.addEventListener("input", function () { fieldChanged(f); }); ctl.addEventListener("change", function () { fieldChanged(f); }); var prop = document.createElement("div"); prop.className = "proposal"; prop.hidden = true; prop.id = "prop-" + f.name; row.appendChild(label); row.appendChild(ctl); row.appendChild(prop); form.appendChild(row); }); } function fieldChanged(f) { dirty[f.name] = controls[f.name].value !== originals[f.name]; if (dryTimer) clearTimeout(dryTimer); dryTimer = setTimeout(runDryRun, 400); } // dirtyBody collects the modified fields for a dry-run, a draft save or // a full correction; empty or null clears the field. function dirtyBody() { var body = {}; var any = false; for (var i = 0; i < cfg.fields.length; i++) { var f = cfg.fields[i]; if (!dirty[f.name]) continue; var r; try { r = readValueControl(f, controls[f.name]); } catch (e) { return { error: e.message }; } body[f.name] = r.clear ? null : r.value; any = true; } return { body: body, any: any }; } async function runDryRun() { var d = dirtyBody(); if (d.error) { renderDryErrors([d.error]); return; } var res = await api("POST", "/api/archivist/documents/" + docId + "/dry-run", d.body); if (res.status !== 200) { renderDryErrors(["dry-run : " + errMsg(res)]); return; } dryErrors = res.data.errors || []; renderDryErrors(dryErrors); if (dryErrors.length) { $("targetname").textContent = ""; } else { $("targetname").textContent = "Nom cible : files/" + (res.data.rendered || ""); } } function renderDryErrors(errs) { var box = $("dryerrors"); if (!errs || !errs.length) { box.hidden = true; box.innerHTML = ""; $("correctBtn").disabled = false; return; } box.hidden = false; box.innerHTML = ""; errs.forEach(function (e) { var div = document.createElement("div"); div.textContent = e; box.appendChild(div); }); $("correctBtn").disabled = true; // US-35: no full correction while errors remain } // loadProposals runs the classification dry-run (no body) and offers, // under each field whose proposed value differs from the current one, // the pipeline value with one-click adoption (US-35). async function loadProposals() { var res = await api("POST", "/api/archivist/documents/" + docId + "/dry-run"); if (res.status !== 200) return; var props = res.data.fields || {}; cfg.fields.forEach(function (f) { var p = props[f.name]; if (p === null || p === undefined) return; var raw; switch (f.type) { case "date": raw = pbDateInput(p); break; case "boolean": raw = p ? "true" : "false"; break; default: raw = String(p); } if (raw === originals[f.name]) return; var slot = document.getElementById("prop-" + f.name); slot.hidden = false; slot.innerHTML = ""; var span = document.createElement("span"); span.textContent = "Pipeline : " + fmtVal(p); slot.appendChild(span); var btn = document.createElement("button"); btn.type = "button"; btn.className = "btn secondary"; btn.textContent = "Adopter"; btn.addEventListener("click", function () { controls[f.name].value = raw; slot.hidden = true; fieldChanged(f); }); slot.appendChild(btn); }); } function bindActions() { $("draftBtn").addEventListener("click", draftSave); $("correctBtn").addEventListener("click", fullCorrect); $("validateBtn").addEventListener("click", validate); } // Draft save: a standard PATCH, no re-filing, no status change (US-22). async function draftSave() { var d = dirtyBody(); if (d.error) { renderDryErrors([d.error]); return; } if (!d.any) return; var res = await api("PATCH", "/api/collections/documents/records/" + docId, d.body); if (res.status !== 200) { renderDryErrors([errMsg(res)]); return; } window.location.reload(); } // Full correction: re-filing and validation in one step (US-36). async function fullCorrect() { if (dryErrors.length) return; var d = dirtyBody(); if (d.error) { renderDryErrors([d.error]); return; } var res = await api("POST", "/api/archivist/documents/" + docId + "/correct", d.body); if (res.status === 200) { window.location.reload(); return; } renderDryErrors((res.data && res.data.errors && res.data.errors.length) ? res.data.errors : [errMsg(res)]); } // Simple validation of a processed document (US-23). async function validate() { var res = await api("PATCH", "/api/collections/documents/records/" + docId, { status: "validated" }); if (res.status === 200) { window.location.reload(); return; } renderDryErrors([errMsg(res)]); } function renderHistory(evs) { var hist = $("history"); hist.innerHTML = ""; evs.forEach(function (ev) { var p = payloadOf(ev); var item = document.createElement("div"); item.className = "hist"; var head = document.createElement("div"); head.className = "head"; var b = document.createElement("span"); b.className = "badge"; b.textContent = ev.event_type; head.appendChild(b); var time = document.createElement("time"); time.textContent = fmtDate(ev.timestamp); head.appendChild(time); item.appendChild(head); if (p.errors && p.errors.length) { var ul = document.createElement("ul"); p.errors.forEach(function (e) { var li = document.createElement("li"); li.textContent = e; ul.appendChild(li); }); item.appendChild(ul); } var before = p.before || {}; var after = p.after || {}; var keys = {}; Object.keys(before).forEach(function (k) { keys[k] = true; }); Object.keys(after).forEach(function (k) { keys[k] = true; }); Object.keys(keys).forEach(function (k) { var line = document.createElement("div"); line.className = "diff"; var ks = document.createElement("span"); ks.className = "k"; ks.textContent = k + " : "; var bv = document.createElement("span"); bv.textContent = fmtVal(before[k]); var ar = document.createElement("span"); ar.className = "arrow"; ar.textContent = " → "; var av = document.createElement("span"); av.textContent = fmtVal(after[k]); line.appendChild(ks); line.appendChild(bv); line.appendChild(ar); line.appendChild(av); item.appendChild(line); }); hist.appendChild(item); }); } init();