Migrationen 003–009, Kundaufträge mit Lieferschein, Abräumen mit Urteil, ntfy/Backup/Zeitfenster, Live-Stream und Mobile-Mehr-Menü.
209 lines
5.9 KiB
JavaScript
209 lines
5.9 KiB
JavaScript
/** Live-UI: Verbindungsbanner, Poller, gemeinsame Zustands-Labels. */
|
|
|
|
const RUN_STATES = {
|
|
completed: "fertig",
|
|
cancelled: "abgebrochen",
|
|
error: "Fehler",
|
|
in_progress: "druckt",
|
|
printing: "druckt",
|
|
paused: "pausiert",
|
|
interrupted: "unterbrochen",
|
|
standby: "bereit",
|
|
heating: "heizt",
|
|
complete: "fertig",
|
|
offline: "offline",
|
|
waiting: "wartet",
|
|
running: "läuft",
|
|
};
|
|
|
|
let connBannerOk = true;
|
|
let connBannerTimer = 0;
|
|
|
|
function setConnectionBanner(ok, text) {
|
|
let el = document.getElementById("conn-banner");
|
|
if (!el) {
|
|
el = document.createElement("div");
|
|
el.id = "conn-banner";
|
|
el.className = "conn-banner";
|
|
el.setAttribute("role", "status");
|
|
el.hidden = true;
|
|
document.body.prepend(el);
|
|
}
|
|
clearTimeout(connBannerTimer);
|
|
if (ok) {
|
|
const wasBad = !connBannerOk;
|
|
connBannerOk = true;
|
|
if (wasBad && text) {
|
|
el.textContent = text;
|
|
el.className = "conn-banner ok";
|
|
el.hidden = false;
|
|
connBannerTimer = setTimeout(() => {
|
|
el.hidden = true;
|
|
el.textContent = "";
|
|
}, 2800);
|
|
return;
|
|
}
|
|
el.hidden = true;
|
|
el.textContent = "";
|
|
el.className = "conn-banner";
|
|
return;
|
|
}
|
|
connBannerOk = false;
|
|
el.textContent = text || "Keine Verbindung zum Server.";
|
|
el.className = "conn-banner bad";
|
|
el.hidden = false;
|
|
}
|
|
|
|
/**
|
|
* @param {() => void | Promise<void>} fn
|
|
* @param {number} intervalMs
|
|
* @returns {{ start: () => void, stop: () => void }}
|
|
*/
|
|
function createPoller(fn, intervalMs) {
|
|
const base = Math.max(1000, Number(intervalMs) || 5000);
|
|
const maxMs = 60000;
|
|
let timer = 0;
|
|
let busy = false;
|
|
let delay = base;
|
|
let fails = 0;
|
|
let running = false;
|
|
|
|
function schedule() {
|
|
if (!running) return;
|
|
clearTimeout(timer);
|
|
timer = setTimeout(tick, delay);
|
|
}
|
|
|
|
async function tick() {
|
|
if (!running || document.hidden || busy) {
|
|
schedule();
|
|
return;
|
|
}
|
|
busy = true;
|
|
try {
|
|
await fn();
|
|
delay = base;
|
|
fails = 0;
|
|
if (!connBannerOk) setConnectionBanner(true, "Wieder verbunden.");
|
|
else setConnectionBanner(true, "");
|
|
} catch (error) {
|
|
fails += 1;
|
|
delay = Math.min(maxMs, Math.max(base, delay * 2));
|
|
if (fails >= 2) {
|
|
setConnectionBanner(false, (error && error.message) || "Daten konnten nicht geladen werden.");
|
|
}
|
|
} finally {
|
|
busy = false;
|
|
schedule();
|
|
}
|
|
}
|
|
|
|
function onVis() {
|
|
if (!document.hidden && running) tick();
|
|
}
|
|
|
|
return {
|
|
start() {
|
|
if (running) return;
|
|
running = true;
|
|
document.addEventListener("visibilitychange", onVis);
|
|
tick();
|
|
},
|
|
stop() {
|
|
running = false;
|
|
document.removeEventListener("visibilitychange", onVis);
|
|
clearTimeout(timer);
|
|
timer = 0;
|
|
},
|
|
};
|
|
}
|
|
|
|
const VERDICT_REASON_LABELS = {
|
|
haftung: "Haftung",
|
|
warping: "Warping",
|
|
spaghetti: "Spaghetti",
|
|
layer_shift: "Layer-Shift",
|
|
verstopfung: "Verstopfung",
|
|
farbe: "Farbe",
|
|
sonstiges: "Sonstiges",
|
|
};
|
|
|
|
/**
|
|
* Dialog vor „Platte geräumt“: Urteil, optional Grund und Stückzahlen.
|
|
* @returns {Promise<object|null>} Payload für /abraeumen oder null bei Abbruch
|
|
*/
|
|
function promptAbraeumen(hostLabel) {
|
|
return new Promise((resolve) => {
|
|
const existing = document.getElementById("abraeumen-dlg");
|
|
if (existing) existing.remove();
|
|
const dlg = document.createElement("dialog");
|
|
dlg.id = "abraeumen-dlg";
|
|
dlg.className = "abraeumen-dlg";
|
|
const reasons = Object.entries(VERDICT_REASON_LABELS)
|
|
.map(([k, label]) => `<option value="${k}">${label}</option>`)
|
|
.join("");
|
|
dlg.innerHTML = `
|
|
<form method="dialog" class="abraeumen-form">
|
|
<h2>Platte geräumt</h2>
|
|
<p class="tiny">${escapeHtml(hostLabel || "Drucker")}</p>
|
|
<div class="field"><label for="ab-verdict">Urteil</label>
|
|
<select id="ab-verdict" name="verdict">
|
|
<option value="gut">gut</option>
|
|
<option value="ausschuss">Ausschuss</option>
|
|
</select>
|
|
</div>
|
|
<div class="field" id="ab-reason-wrap" hidden>
|
|
<label for="ab-reason">Grund</label>
|
|
<select id="ab-reason">${reasons}</select>
|
|
</div>
|
|
<div class="grid-2" id="ab-pieces" hidden>
|
|
<div class="field"><label for="ab-ok">Stück gut</label><input id="ab-ok" type="number" min="0" step="1" value="0"></div>
|
|
<div class="field"><label for="ab-bad">Stück schlecht</label><input id="ab-bad" type="number" min="0" step="1" value="1"></div>
|
|
</div>
|
|
<div class="actions">
|
|
<button type="submit" value="ok" class="btn-primary">Quittieren</button>
|
|
<button type="button" class="ghost" id="ab-cancel">Abbrechen</button>
|
|
</div>
|
|
</form>`;
|
|
document.body.appendChild(dlg);
|
|
const verdict = dlg.querySelector("#ab-verdict");
|
|
const reasonWrap = dlg.querySelector("#ab-reason-wrap");
|
|
const pieces = dlg.querySelector("#ab-pieces");
|
|
function sync() {
|
|
const scrap = verdict.value === "ausschuss";
|
|
reasonWrap.hidden = !scrap;
|
|
pieces.hidden = !scrap;
|
|
}
|
|
verdict.addEventListener("change", sync);
|
|
sync();
|
|
dlg.querySelector("#ab-cancel").addEventListener("click", () => {
|
|
dlg.close();
|
|
dlg.remove();
|
|
resolve(null);
|
|
});
|
|
dlg.addEventListener("close", () => {
|
|
if (dlg.returnValue !== "ok") {
|
|
dlg.remove();
|
|
resolve(null);
|
|
return;
|
|
}
|
|
const payload = {
|
|
confirm: true,
|
|
verdict: verdict.value,
|
|
verdict_reason: verdict.value === "ausschuss" ? dlg.querySelector("#ab-reason").value : "",
|
|
pieces_ok: Number(dlg.querySelector("#ab-ok").value) || 0,
|
|
pieces_bad: Number(dlg.querySelector("#ab-bad").value) || 0,
|
|
};
|
|
dlg.remove();
|
|
resolve(payload);
|
|
});
|
|
dlg.showModal();
|
|
});
|
|
}
|
|
|
|
window.RUN_STATES = RUN_STATES;
|
|
window.setConnectionBanner = setConnectionBanner;
|
|
window.createPoller = createPoller;
|
|
window.promptAbraeumen = promptAbraeumen;
|
|
window.VERDICT_REASON_LABELS = VERDICT_REASON_LABELS;
|