Files
multiarch-docker-images/archivist/pb_public/detail.js
T

295 lines
9.4 KiB
JavaScript

"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();
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;
$("pdf").src = "/api/archivist/documents/" + docId + "/pdf";
}
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();