Bedarf in Sidebar/Material, Drucker-Zeitfenster und Tarif in Einstellungen, helles Theme; Legacy-Token und /api/drucker entfernt; SSE/SW-Fixes gegen hängende Navigation in Chrome.
141 lines
5.9 KiB
HTML
141 lines
5.9 KiB
HTML
{% extends "layout.html" %}
|
|
{% block body %}
|
|
<div class="page-head">
|
|
<div>
|
|
<h1>Wartung</h1>
|
|
<p class="tiny">Laufstunden und Filament je Drucker — Verschleißteile im Blick.</p>
|
|
</div>
|
|
<div class="page-head-side">
|
|
<button type="button" class="ghost" id="wartung-refresh"><svg class="ico"><use href="#i-refresh"/></svg>Neu laden</button>
|
|
</div>
|
|
</div>
|
|
<div id="wartung-summary" class="kpi-row" hidden></div>
|
|
<div id="wartung-root"></div>
|
|
<p id="wartung-note"></p>
|
|
{% endblock %}
|
|
{% block script %}
|
|
<script>
|
|
function partRow(part) {
|
|
const overdue = part.overdue_hours || part.overdue_meters;
|
|
return `<tr class="${overdue ? "warn" : ""}">
|
|
<td>${escapeHtml(part.label || part.part_key)}</td>
|
|
<td class="num">${(part.hours_since || 0).toLocaleString("de-DE", { maximumFractionDigits: 1 })} h</td>
|
|
<td class="num">${Math.round(part.meters_since || 0).toLocaleString("de-DE")} mm</td>
|
|
<td class="num">${part.life_hours ? part.life_hours + " h" : "—"}</td>
|
|
<td class="num">${part.life_meters ? Math.round(part.life_meters) + " mm" : "—"}</td>
|
|
<td><button type="button" class="ghost tiny-btn" data-reset="${part.id}">Zurücksetzen</button></td>
|
|
</tr>`;
|
|
}
|
|
|
|
function printerCard(block) {
|
|
const c = block.counters || {};
|
|
const overdue = (block.parts || []).filter((p) => p.overdue_hours || p.overdue_meters).length;
|
|
const parts = (block.parts || []).map(partRow).join("")
|
|
|| '<tr><td colspan="6" class="empty">Noch keine Teile — unten anlegen.</td></tr>';
|
|
return `<section class="card wartung-card">
|
|
<div class="lib-head">
|
|
<h2>${escapeHtml(block.name || block.host)}</h2>
|
|
${overdue ? chipHtml("error", overdue + " fällig") : chipHtml("idle", "ok")}
|
|
</div>
|
|
<p class="hint">${escapeHtml(block.host)} · ${(c.hours || 0).toLocaleString("de-DE", { maximumFractionDigits: 1 })} h gesamt · ${Math.round(c.meters || 0).toLocaleString("de-DE")} mm · ${c.runs || 0} Läufe</p>
|
|
<div class="table-wrap"><table>
|
|
<thead><tr><th>Teil</th><th>Seit Reset</th><th>Filament</th><th>Intervall h</th><th>Intervall mm</th><th></th></tr></thead>
|
|
<tbody>${parts}</tbody>
|
|
</table></div>
|
|
<details class="mat-more section-gap">
|
|
<summary><svg class="ico caret"><use href="#i-down"/></svg><span class="grow">Teil anlegen</span></summary>
|
|
<form class="grid-2 wartung-add" data-host="${escapeHtml(block.host)}">
|
|
<div class="field"><label>Schlüssel</label><input name="part_key" required placeholder="nozzle"></div>
|
|
<div class="field"><label>Bezeichnung</label><input name="label" placeholder="Düse 0.4"></div>
|
|
<div class="field"><label>Intervall h</label><input name="life_hours" type="number" step="1" min="0" value="0"></div>
|
|
<div class="field"><label>Intervall mm</label><input name="life_meters" type="number" step="1000" min="0" value="0"></div>
|
|
<div class="field" style="grid-column:1/-1"><label>Notiz</label><input name="note"></div>
|
|
<div class="actions"><button type="submit">Teil anlegen</button></div>
|
|
</form>
|
|
</details>
|
|
</section>`;
|
|
}
|
|
|
|
function renderSummary(data) {
|
|
const box = document.getElementById("wartung-summary");
|
|
if (!data.length) {
|
|
box.hidden = true;
|
|
box.innerHTML = "";
|
|
return;
|
|
}
|
|
let hours = 0;
|
|
let meters = 0;
|
|
let overdue = 0;
|
|
data.forEach((block) => {
|
|
hours += Number((block.counters || {}).hours) || 0;
|
|
meters += Number((block.counters || {}).meters) || 0;
|
|
overdue += (block.parts || []).filter((p) => p.overdue_hours || p.overdue_meters).length;
|
|
});
|
|
box.hidden = false;
|
|
box.innerHTML = `
|
|
<div class="kpi"><div class="kpi-val">${data.length}</div><div class="kpi-label">Drucker</div></div>
|
|
<div class="kpi"><div class="kpi-val">${hours.toLocaleString("de-DE", { maximumFractionDigits: 0 })} h</div><div class="kpi-label">Laufzeit</div></div>
|
|
<div class="kpi"><div class="kpi-val">${Math.round(meters / 1000).toLocaleString("de-DE")} m</div><div class="kpi-label">Filament</div></div>
|
|
<div class="kpi"><div class="kpi-val">${overdue}</div><div class="kpi-label">Fällig</div></div>`;
|
|
}
|
|
|
|
async function load() {
|
|
const data = await api("/wartung");
|
|
renderSummary(data || []);
|
|
document.getElementById("wartung-root").innerHTML = (data || []).map(printerCard).join("")
|
|
|| `<div class="empty-state">
|
|
<svg class="ico"><use href="#i-nozzle"/></svg>
|
|
<p>Keine Drucker angelegt.</p>
|
|
<a class="ghost" href="/einstellungen">Einstellungen →</a>
|
|
</div>`;
|
|
}
|
|
|
|
document.getElementById("wartung-refresh")?.addEventListener("click", () => {
|
|
load().catch((error) => showNote("wartung-note", error));
|
|
});
|
|
|
|
document.getElementById("wartung-root").addEventListener("submit", async (event) => {
|
|
const form = event.target.closest("form.wartung-add");
|
|
if (!form) return;
|
|
event.preventDefault();
|
|
const fd = new FormData(form);
|
|
try {
|
|
await api("/wartung/teile", {
|
|
method: "POST",
|
|
json: {
|
|
printer_host: form.dataset.host,
|
|
part_key: fd.get("part_key"),
|
|
label: fd.get("label"),
|
|
life_hours: Number(fd.get("life_hours") || 0),
|
|
life_meters: Number(fd.get("life_meters") || 0),
|
|
note: fd.get("note"),
|
|
},
|
|
});
|
|
form.reset();
|
|
showNote("wartung-note", null, "Teil gespeichert.");
|
|
toast("Wartungsteil angelegt.");
|
|
load();
|
|
} catch (error) {
|
|
showNote("wartung-note", error);
|
|
}
|
|
});
|
|
|
|
document.getElementById("wartung-root").addEventListener("click", (event) => {
|
|
const btn = event.target.closest("[data-reset]");
|
|
if (!btn) return;
|
|
armButton(btn, "Wirklich zurücksetzen?", async () => {
|
|
try {
|
|
await api("/wartung/teile/" + btn.dataset.reset + "/reset", { method: "POST", json: {} });
|
|
showNote("wartung-note", null, "Teil zurückgesetzt.");
|
|
toast("Zähler zurückgesetzt.");
|
|
load();
|
|
} catch (error) {
|
|
showNote("wartung-note", error);
|
|
}
|
|
});
|
|
});
|
|
|
|
load().catch((error) => showNote("wartung-note", error));
|
|
</script>
|
|
{% endblock %}
|