mirror of
https://github.com/napnap75/multiarch-docker-images.git
synced 2026-11-11 00:22:12 +01:00
426 lines
13 KiB
JavaScript
426 lines
13 KiB
JavaScript
"use strict";
|
||
|
||
// Document list page (US-32, US-33): server-side sort, per-column typed
|
||
// filters, status tabs, pagination, column chooser persisted in the
|
||
// browser, bulk single-field edit through one standard PATCH per
|
||
// document.
|
||
|
||
var state = {
|
||
config: null,
|
||
meta: [],
|
||
page: 1,
|
||
perPage: 50,
|
||
sort: null,
|
||
columns: [],
|
||
statusTab: "",
|
||
filters: {},
|
||
selected: {},
|
||
docs: [],
|
||
total: 0,
|
||
totalPages: 1,
|
||
counts: {}
|
||
};
|
||
|
||
function $(id) { return document.getElementById(id); }
|
||
|
||
function esc(v) { return String(v).replace(/'/g, "\\'"); }
|
||
|
||
function store(key, val) {
|
||
if (val === undefined) {
|
||
try {
|
||
var raw = localStorage.getItem(key);
|
||
return raw ? JSON.parse(raw) : null;
|
||
} catch (e) { return null; }
|
||
}
|
||
try { localStorage.setItem(key, JSON.stringify(val)); } catch (e) {}
|
||
}
|
||
|
||
function fixedMeta() {
|
||
return [
|
||
{ name: "status", type: "status", fixed: true },
|
||
{ name: "file_key", type: "text", fixed: true },
|
||
{ name: "created", type: "date", fixed: true },
|
||
{ name: "updated", type: "date", fixed: true }
|
||
];
|
||
}
|
||
|
||
function fieldMeta(name) {
|
||
for (var i = 0; i < state.meta.length; i++) {
|
||
if (state.meta[i].name === name) return state.meta[i];
|
||
}
|
||
return null;
|
||
}
|
||
|
||
async function init() {
|
||
var r = await api("GET", "/api/archivist/config");
|
||
if (r.status !== 200) return;
|
||
state.config = r.data;
|
||
state.meta = state.config.fields.map(function (f) {
|
||
return { name: f.name, type: f.type, mandatory: !!f.mandatory };
|
||
}).concat(fixedMeta());
|
||
|
||
var defaults = state.config.fields.filter(function (f) { return f.mandatory; })
|
||
.map(function (f) { return f.name; }).concat(["status", "file_key"]);
|
||
var savedCols = store("archivist.columns");
|
||
state.columns = Array.isArray(savedCols) ? savedCols : defaults;
|
||
var savedSort = store("archivist.sort");
|
||
state.sort = savedSort && savedSort.field ? savedSort : null;
|
||
|
||
initTheme("themeBtn");
|
||
buildChooser();
|
||
bindBulk();
|
||
renderTabs();
|
||
await loadCounts();
|
||
await loadDocs();
|
||
}
|
||
|
||
function buildChooser() {
|
||
var grid = $("colgrid");
|
||
grid.innerHTML = "";
|
||
state.meta.forEach(function (m) {
|
||
var label = document.createElement("label");
|
||
var cb = document.createElement("input");
|
||
cb.type = "checkbox";
|
||
cb.checked = state.columns.indexOf(m.name) >= 0;
|
||
cb.addEventListener("change", function () {
|
||
var i = state.columns.indexOf(m.name);
|
||
if (cb.checked && i < 0) state.columns.push(m.name);
|
||
if (!cb.checked && i >= 0) state.columns.splice(i, 1);
|
||
store("archivist.columns", state.columns);
|
||
state.page = 1;
|
||
loadDocs();
|
||
});
|
||
label.appendChild(cb);
|
||
label.appendChild(document.createTextNode(" " + m.name));
|
||
grid.appendChild(label);
|
||
});
|
||
}
|
||
|
||
async function loadCounts() {
|
||
var statuses = state.config.statuses || [];
|
||
var results = await Promise.all(statuses.map(function (st) {
|
||
return api("GET", "/api/collections/documents/records?perPage=1&fields=status&filter=" +
|
||
encodeURIComponent("(status='" + esc(st) + "')"));
|
||
}));
|
||
statuses.forEach(function (st, i) {
|
||
state.counts[st] = (results[i].data && results[i].data.totalItems) || 0;
|
||
});
|
||
renderTabs();
|
||
}
|
||
|
||
function renderTabs() {
|
||
var tabs = $("tabs");
|
||
tabs.innerHTML = "";
|
||
var all = document.createElement("button");
|
||
all.type = "button";
|
||
all.className = "tab" + (state.statusTab === "" ? " active" : "");
|
||
all.textContent = "Tous";
|
||
all.addEventListener("click", function () {
|
||
state.statusTab = "";
|
||
state.page = 1;
|
||
loadDocs();
|
||
});
|
||
tabs.appendChild(all);
|
||
(state.config.statuses || []).forEach(function (st) {
|
||
var b = document.createElement("button");
|
||
b.type = "button";
|
||
b.className = "tab" + (state.statusTab === st ? " active" : "");
|
||
b.appendChild(document.createTextNode(st));
|
||
var c = document.createElement("span");
|
||
c.className = "count";
|
||
c.textContent = state.counts[st] !== undefined ? String(state.counts[st]) : "";
|
||
b.appendChild(c);
|
||
b.addEventListener("click", function () {
|
||
state.statusTab = st;
|
||
state.page = 1;
|
||
loadDocs();
|
||
});
|
||
tabs.appendChild(b);
|
||
});
|
||
}
|
||
|
||
function buildFilterQuery() {
|
||
var parts = [];
|
||
if (state.statusTab) parts.push("(status='" + esc(state.statusTab) + "')");
|
||
state.columns.forEach(function (name) {
|
||
if (name === "status") return; // owned by the tabs
|
||
var m = fieldMeta(name);
|
||
var f = state.filters[name];
|
||
if (!m || !f || m.type === "number") return;
|
||
if (m.type === "date") {
|
||
if (f.from) parts.push("(" + name + ">='" + esc(f.from) + "')");
|
||
if (f.to) parts.push("(" + name + "<='" + esc(f.to) + "')");
|
||
} else if (m.type === "boolean") {
|
||
if (f.value === "true" || f.value === "false") parts.push("(" + name + "=" + f.value + ")");
|
||
} else if (m.type === "enum") {
|
||
if (f.value) parts.push("(" + name + "='" + esc(f.value) + "')");
|
||
} else {
|
||
if (f.value) parts.push("(" + name + "~'" + esc(f.value) + "')");
|
||
}
|
||
});
|
||
if (!parts.length) return "";
|
||
return "&filter=" + encodeURIComponent(parts.join(" && "));
|
||
}
|
||
|
||
async function loadDocs() {
|
||
var sort = state.sort
|
||
? ("&sort=" + (state.sort.dir === "desc" ? "-" : "") + encodeURIComponent(state.sort.field))
|
||
: "";
|
||
var q = "/api/collections/documents/records?page=" + state.page +
|
||
"&perPage=" + state.perPage + sort + buildFilterQuery();
|
||
var r = await api("GET", q);
|
||
if (r.status !== 200) return;
|
||
state.docs = r.data.items || [];
|
||
state.total = r.data.totalItems || 0;
|
||
state.totalPages = r.data.totalPages || 1;
|
||
renderHead();
|
||
renderRows();
|
||
renderPager();
|
||
renderBulkbar();
|
||
}
|
||
|
||
function renderHead() {
|
||
var thead = $("doctable").tHead;
|
||
thead.innerHTML = "";
|
||
var trh = document.createElement("tr");
|
||
var thAll = document.createElement("th");
|
||
var cbAll = document.createElement("input");
|
||
cbAll.type = "checkbox";
|
||
cbAll.addEventListener("change", function () {
|
||
state.docs.forEach(function (d) {
|
||
if (cbAll.checked) state.selected[d.id] = true;
|
||
else delete state.selected[d.id];
|
||
});
|
||
renderRows();
|
||
renderBulkbar();
|
||
});
|
||
thAll.appendChild(cbAll);
|
||
trh.appendChild(thAll);
|
||
state.columns.forEach(function (name) {
|
||
var th = document.createElement("th");
|
||
th.textContent = name + (state.sort && state.sort.field === name
|
||
? (state.sort.dir === "desc" ? " ↓" : " ↑") : "");
|
||
th.addEventListener("click", function () {
|
||
if (!state.sort || state.sort.field !== name) {
|
||
state.sort = { field: name, dir: "asc" };
|
||
} else if (state.sort.dir === "asc") {
|
||
state.sort.dir = "desc";
|
||
} else {
|
||
state.sort = null;
|
||
}
|
||
store("archivist.sort", state.sort);
|
||
state.page = 1;
|
||
loadDocs();
|
||
});
|
||
trh.appendChild(th);
|
||
});
|
||
thead.appendChild(trh);
|
||
|
||
var trf = document.createElement("tr");
|
||
trf.appendChild(document.createElement("td"));
|
||
state.columns.forEach(function (name) {
|
||
var td = document.createElement("td");
|
||
if (name !== "status") {
|
||
var m = fieldMeta(name);
|
||
if (m && m.type !== "number") {
|
||
td.appendChild(buildFilterControl(m));
|
||
}
|
||
}
|
||
trf.appendChild(td);
|
||
});
|
||
thead.appendChild(trf);
|
||
}
|
||
|
||
function buildFilterControl(m) {
|
||
var f = state.filters[m.name] || (state.filters[m.name] = {});
|
||
var wrap = document.createElement("span");
|
||
if (m.type === "date") {
|
||
var from = document.createElement("input");
|
||
from.type = "date";
|
||
from.value = f.from || "";
|
||
var to = document.createElement("input");
|
||
to.type = "date";
|
||
to.value = f.to || "";
|
||
from.addEventListener("change", function () { f.from = from.value; state.page = 1; loadDocs(); });
|
||
to.addEventListener("change", function () { f.to = to.value; state.page = 1; loadDocs(); });
|
||
wrap.appendChild(from);
|
||
wrap.appendChild(to);
|
||
} else if (m.type === "boolean") {
|
||
var sel = document.createElement("select");
|
||
addOption(sel, "", "—");
|
||
addOption(sel, "true", "oui");
|
||
addOption(sel, "false", "non");
|
||
sel.value = f.value || "";
|
||
sel.addEventListener("change", function () { f.value = sel.value; state.page = 1; loadDocs(); });
|
||
wrap.appendChild(sel);
|
||
} else if (m.type === "enum") {
|
||
var es = document.createElement("select");
|
||
addOption(es, "", "—");
|
||
((state.config.enums || {})[m.name] || []).forEach(function (v) { addOption(es, v, v); });
|
||
es.value = f.value || "";
|
||
es.addEventListener("change", function () { f.value = es.value; state.page = 1; loadDocs(); });
|
||
wrap.appendChild(es);
|
||
} else {
|
||
var inp = document.createElement("input");
|
||
inp.type = "text";
|
||
inp.placeholder = "contient…";
|
||
inp.value = f.value || "";
|
||
var timer = null;
|
||
inp.addEventListener("input", function () {
|
||
if (timer) clearTimeout(timer);
|
||
timer = setTimeout(function () { f.value = inp.value; state.page = 1; loadDocs(); }, 300);
|
||
});
|
||
wrap.appendChild(inp);
|
||
}
|
||
return wrap;
|
||
}
|
||
|
||
function renderRows() {
|
||
var tbody = $("doctable").tBodies[0];
|
||
tbody.innerHTML = "";
|
||
state.docs.forEach(function (doc) {
|
||
var tr = document.createElement("tr");
|
||
tr.className = "clickable";
|
||
var tdcb = document.createElement("td");
|
||
var cb = document.createElement("input");
|
||
cb.type = "checkbox";
|
||
cb.checked = !!state.selected[doc.id];
|
||
cb.addEventListener("click", function (ev) { ev.stopPropagation(); });
|
||
cb.addEventListener("change", function () {
|
||
if (cb.checked) state.selected[doc.id] = true;
|
||
else delete state.selected[doc.id];
|
||
renderBulkbar();
|
||
});
|
||
tdcb.appendChild(cb);
|
||
tr.appendChild(tdcb);
|
||
state.columns.forEach(function (name) {
|
||
var m = fieldMeta(name);
|
||
var td = document.createElement("td");
|
||
if (name === "status") {
|
||
var b = document.createElement("span");
|
||
b.className = "badge st-" + doc.status;
|
||
b.textContent = doc.status;
|
||
td.appendChild(b);
|
||
} else if (m && m.type === "date") {
|
||
td.textContent = fmtDate(doc[name]);
|
||
} else {
|
||
td.textContent = fmtVal(doc[name]);
|
||
}
|
||
tr.appendChild(td);
|
||
});
|
||
tr.addEventListener("click", function (ev) {
|
||
if (ev.target.tagName === "INPUT") return;
|
||
window.location.href = "/document.html?id=" + doc.id;
|
||
});
|
||
tbody.appendChild(tr);
|
||
});
|
||
}
|
||
|
||
function renderPager() {
|
||
var pager = $("pager");
|
||
pager.innerHTML = "";
|
||
var prev = document.createElement("button");
|
||
prev.type = "button";
|
||
prev.className = "btn secondary";
|
||
prev.textContent = "‹ Précédent";
|
||
prev.disabled = state.page <= 1;
|
||
prev.addEventListener("click", function () { state.page--; loadDocs(); });
|
||
var info = document.createElement("span");
|
||
info.textContent = state.total + " document(s) — page " + state.page + " / " + Math.max(1, state.totalPages);
|
||
var next = document.createElement("button");
|
||
next.type = "button";
|
||
next.className = "btn secondary";
|
||
next.textContent = "Suivant ›";
|
||
next.disabled = state.page >= state.totalPages;
|
||
next.addEventListener("click", function () { state.page++; loadDocs(); });
|
||
pager.appendChild(prev);
|
||
pager.appendChild(info);
|
||
pager.appendChild(next);
|
||
}
|
||
|
||
function bindBulk() {
|
||
var sel = $("bulkfield");
|
||
sel.innerHTML = "";
|
||
state.config.fields.forEach(function (f) { addOption(sel, f.name, f.name); });
|
||
sel.addEventListener("change", rebuildBulkValue);
|
||
$("bulkapply").addEventListener("click", applyBulk);
|
||
$("bulkclear").addEventListener("click", function () {
|
||
state.selected = {};
|
||
renderRows();
|
||
renderBulkbar();
|
||
});
|
||
$("bulkall").addEventListener("click", selectAllFiltered);
|
||
rebuildBulkValue();
|
||
}
|
||
|
||
function bulkField() {
|
||
var name = $("bulkfield").value;
|
||
for (var i = 0; i < state.config.fields.length; i++) {
|
||
if (state.config.fields[i].name === name) return state.config.fields[i];
|
||
}
|
||
return null;
|
||
}
|
||
|
||
function rebuildBulkValue() {
|
||
var holder = $("bulkvalue");
|
||
holder.innerHTML = "";
|
||
var f = bulkField();
|
||
if (!f) return;
|
||
holder.appendChild(buildValueControl(f, (state.config.enums || {})[f.name] || [], null));
|
||
}
|
||
|
||
async function selectAllFiltered() {
|
||
var page = 1;
|
||
for (;;) {
|
||
var q = "/api/collections/documents/records?page=" + page + "&perPage=200&fields=id" + buildFilterQuery();
|
||
var r = await api("GET", q);
|
||
if (r.status !== 200) break;
|
||
(r.data.items || []).forEach(function (d) { state.selected[d.id] = true; });
|
||
if (page >= (r.data.totalPages || 1)) break;
|
||
page++;
|
||
}
|
||
renderRows();
|
||
renderBulkbar();
|
||
}
|
||
|
||
async function applyBulk() {
|
||
var f = bulkField();
|
||
if (!f) { alert("Choisissez un champ."); return; }
|
||
var el = $("bulkvalue").firstChild;
|
||
var value;
|
||
try {
|
||
var r = readValueControl(f, el);
|
||
if (r.clear) {
|
||
if (f.mandatory) { alert("Ce champ est obligatoire : une valeur est requise."); return; }
|
||
value = null;
|
||
} else {
|
||
value = r.value;
|
||
}
|
||
} catch (e) { alert(e.message); return; }
|
||
var ids = Object.keys(state.selected);
|
||
if (!ids.length) { alert("Aucun document sélectionné."); return; }
|
||
$("bulkresult").textContent = "…";
|
||
var ok = 0;
|
||
var failed = [];
|
||
for (var i = 0; i < ids.length; i++) {
|
||
var body = {};
|
||
body[f.name] = value;
|
||
var res = await api("PATCH", "/api/collections/documents/records/" + ids[i], body);
|
||
if (res.status === 200) ok++;
|
||
else failed.push(ids[i] + " : " + errMsg(res));
|
||
}
|
||
$("bulkresult").textContent = ok + " document(s) modifié(s)" +
|
||
(failed.length ? ", " + failed.length + " échec(s) — " + failed[0] : "");
|
||
state.selected = {};
|
||
await loadCounts();
|
||
await loadDocs();
|
||
}
|
||
|
||
function renderBulkbar() {
|
||
var n = Object.keys(state.selected).length;
|
||
$("bulkbar").hidden = n === 0;
|
||
$("bulkcount").textContent = n + " document(s) sélectionné(s)";
|
||
}
|
||
|
||
init();
|