/* Key register — printable reports. Everything renders to plain HTML so the browser's own Print / Save as PDF produces the document. No PDF library, nothing to keep up to date. */ "use strict"; let S = null, view = "out", whoId = null; const $out = document.getElementById("out"); const $view = document.getElementById("view"); const $who = document.getElementById("who"); const esc = (s) => String(s ?? "").replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ }[c])); const fmt = (d) => { if (!d) return "—"; const [y, m, dd] = d.split("-"); return `${dd} ${T._months[+m - 1]} ${y}`; }; const days = (a, b) => Math.round((new Date(b) - new Date(a)) / 864e5); const person = (id) => S.people.find((p) => p.id === +id); const door = (id) => S.doors.find((d) => d.id === +id); const cab = (id) => S.cabinets.find((c) => c.id === id) || { name: "—", full_name: "—" }; const doorName = (i) => (i.door_id ? door(i.door_id).name : i.zone); const isLate = (i) => i.loan && i.loan.due_date && i.loan.due_date < S.today; /* Hook order — the audit sheet is meant to be read down the cabinet. */ const byHook = (a, b) => (a.hook || "").localeCompare(b.hook || "", undefined, { numeric: true }) || a.tag.localeCompare(b.tag, undefined, { numeric: true }); function head(title, sub) { const now = new Date(); return `
${esc(T.brand)}
${esc(T.repNothing)}
`; return head(onlyLate ? T.repOverdue : T.repOut) + `${esc(p.dept)}${p.role ? " · " + esc(p.role) : ""}${ p.phone ? " · " + esc(p.phone) : ""}
| ${esc(T.cTag)} | ${esc(T.cDoor)} | ${esc(T.cBuilding)} | ${esc(T.cCabinet)} | ${esc(T.cOut)} | ${esc(T.cDue)} |
|---|---|---|---|---|---|
| ${esc(i.tag)} | ${esc(doorName(i))} | ${esc(i.building)} | ${esc(cab(i.cabinet_id).name)} | ${fmt(i.loan.out_date)} | ${i.loan.due_date ? fmt(i.loan.due_date) : "—"}${ late ? ` (+${days(i.loan.due_date, S.today)})` : ""} |
${esc(T.repChoosePerson)}
`; let rows = []; try { rows = await fetch("/api/report/person/" + id).then((r) => r.json()); } catch (e) { /* offline */ } if (!rows.length) return head(T.repReceiptTitle, p.name) + `${esc(T.repNothing)}
`; return head(T.repReceiptTitle, `${p.name}${p.dept ? " · " + p.dept : ""}`) + `${esc(T.repReceiptIntro(rows.length))}
| ${esc(T.cTag)} | ${esc(T.cDoor)} | ${esc(T.cBuilding)} | ${esc(T.cCabinet)} | ${esc(T.cOut)} | ${esc(T.cDue)} |
|---|---|---|---|---|---|
| ${esc(h.tag)} | ${esc(h.door || "")} | ${esc(h.building)} | ${esc(cab(h.cabinet_id).name)} | ${fmt(h.out_date)} | ${h.due_date ? fmt(h.due_date) : "—"} |
${esc(T.repNoSig)}
`} ${signOff(p.name)}`; } /* ------------------------------------------------------------- inventory */ function viewInventory() { const live = S.items.filter((i) => i.status !== "retired"); const groups = {}; S.cabinets.forEach((c) => { const list = live.filter((i) => i.cabinet_id === c.id).sort(byHook); if (list.length) groups[c.id] = list; }); const statusWord = (i) => (T.statuses && T.statuses[i.status]) || i.status; return head(T.repInventory) + `${esc(c.note || "")}
| ${esc(T.cTag)} | ${esc(T.cDoor)} | ${esc(T.cBuilding)} | ${esc(T.cHook)} | ${esc(T.cStatus)} | ${esc(T.cPerson)} |
|---|---|---|---|---|---|
| ${esc(i.tag)} | ${esc(doorName(i))} | ${esc(i.building)} | ${esc(i.hook)} | ${esc(statusWord(i))} | ${i.loan && i.status === "out" ? esc(person(i.loan.person_id).name) : ""} |
${esc(T.repAuditIntro)}
` + cabs.map((c) => { const list = S.items.filter((i) => i.cabinet_id === c.id && i.status !== "retired").sort(byHook); if (!list.length) return ""; return `${esc(c.note || "")}
| ${esc(T.cCheck)} | ${esc(T.cTag)} | ${esc(T.cHook)} | ${esc(T.cDoor)} | ${esc(T.cStatus)} | ${esc(T.cPerson)} |
|---|---|---|---|---|---|
| ${esc(i.tag)} | ${esc(i.hook)} | ${esc(doorName(i))} | ${esc((T.statuses && T.statuses[i.status]) || i.status)} | ${i.loan && i.status === "out" ? esc(person(i.loan.person_id).name) : ""} |
${esc(T ? T.noServer : "offline")}
`; return; } setLang(S.lang); document.getElementById("printbtn").textContent = T.printPdf; document.getElementById("backlink").title = T.back; $view.innerHTML = [["out", T.repOut], ["overdue", T.repOverdue], ["person", T.repPerson], ["inventory", T.repInventory], ["audit", T.repAudit]] .map(([v, l]) => ``).join(""); /* deep links: /report?view=person&id=3 */ const q = new URLSearchParams(location.search); if (q.get("view")) view = q.get("view"); if (q.get("id")) whoId = q.get("id"); $view.value = view; $view.onchange = () => { view = $view.value; whoId = view === "audit" ? "ALL" : null; render(); }; $who.onchange = () => { whoId = $who.value; render(); }; document.getElementById("printbtn").onclick = () => window.print(); await render(); })();