Files
grotti/web/templates/bericht.html
T

147 lines
6.5 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends "layout.html" %}
{% block body %}
<div class="page-head">
<div>
<h1>Druckbericht</h1>
<p class="tiny" id="bericht-sub">Lauf wird geladen …</p>
</div>
<div class="page-head-side">
<a class="ghost" href="/protokoll">← Protokoll</a>
<a class="ghost" id="etikett-druck" href="#" hidden>Etikett Druck</a>
<a class="ghost" id="etikett-objekt" href="#" hidden>Etiketten Objekte</a>
</div>
</div>
<p id="bericht-note"></p>
<div id="bericht" class="bericht"></div>
{% endblock %}
{% block script %}
<script>
const runId = Number(location.pathname.split("/").pop());
const states = {
completed: "fertig", cancelled: "abgebrochen", error: "Fehler",
in_progress: "druckt", printing: "druckt", paused: "pausiert", interrupted: "unterbrochen",
};
function sourceLabel(source, estimate) {
if (estimate) return "Schätzwert";
if (source === "measured") return "Messwert";
if (source === "estimate") return "Schätzwert";
return source || "";
}
function chart(samples) {
if (!samples || samples.length < 2) {
return '<p class="tiny">Kein Temperaturverlauf (nur für neue Drucke).</p>';
}
const w = 640, h = 180, pad = 28;
const temps = samples.flatMap((s) => [s.nozzle, s.bed, s.chamber, s.nozzle_target, s.bed_target].filter((v) => v != null));
const minT = Math.min(...temps, 0);
const maxT = Math.max(...temps, 100);
const x = (i) => pad + (i / (samples.length - 1)) * (w - pad * 2);
const y = (v) => h - pad - ((Number(v) - minT) / Math.max(1, maxT - minT)) * (h - pad * 2);
function path(key, color) {
const pts = samples.map((s, i) => s[key] == null ? null : `${x(i)},${y(s[key])}`).filter(Boolean);
if (pts.length < 2) return "";
return `<polyline fill="none" stroke="${color}" stroke-width="1.6" points="${pts.join(" ")}" />`;
}
return `<svg class="temp-chart" viewBox="0 0 ${w} ${h}" role="img" aria-label="Temperaturverlauf">
${path("nozzle_target", "#fdba74")}
${path("bed_target", "#93c5fd")}
${path("nozzle", "#ea580c")}
${path("bed", "#2563eb")}
${path("chamber", "#16a34a")}
<text x="${pad}" y="14" class="chart-label">${Math.round(maxT)} °C</text>
<text x="${pad}" y="${h - 8}" class="chart-label">${Math.round(minT)} °C</text>
</svg>
<p class="tiny chart-legend"><span class="leg nozzle">Düse</span> <span class="leg bed">Bett</span> <span class="leg chamber">Kammer</span> · gestrichelt ≈ Soll</p>`;
}
function render(data) {
const run = data.run;
const name = (run.filename || "").split("/").pop() || "ohne Datei";
document.getElementById("bericht-sub").textContent =
(run.printer_name || run.printer_host) + " · " + name
+ (run.chargen_nr ? " · " + run.chargen_nr : "");
const planned = Number(run.weight_planned_g) || 0;
const used = Number(run.weight_used_g) || 0;
const diff = Math.round(used) - Math.round(planned);
const cost = Number(run.filament_cost || 0) + Number(run.energy_cost || 0);
const spoolRows = (data.spools || []).map((s) => `
<tr>
<td>${escapeHtml(s.name)}${s.ace_slot ? " · ACE " + s.ace_slot : ""}</td>
<td>${escapeHtml(s.material_type || "")}</td>
<td class="num">${grams(s.weight_planned_g)}</td>
<td class="num">${grams(s.weight_g)}${s.is_estimate ? ' <span class="tag">Schätzwert</span>' : ""}</td>
<td class="num">${euro(s.cost)}</td>
<td class="nowrap">${escapeHtml(s.chargen_nr || "")}</td>
</tr>`).join("") || '<tr><td class="empty" colspan="6">Keine Spule zugeordnet.</td></tr>';
const events = (data.events || []).map((e) =>
`<li><span class="tiny">${escapeHtml((e.created_at || "").replace("T", " ").slice(0, 19))}</span> ${escapeHtml(e.message || e.kind)}</li>`
).join("") || "<li class=\"tiny\">Keine Ereignisse.</li>";
const labels = (data.labels || []).filter((l) => !l.excluded);
const labelBits = labels.length
? labels.map((l) => `<li id="objekt-${encodeURIComponent(l.object_name || "")}"><code>${escapeHtml(l.chargen_nr)}</code> ${escapeHtml(l.object_name || "Druck")}</li>`).join("")
: (run.chargen_nr ? `<li><code>${escapeHtml(run.chargen_nr)}</code> gesamter Druck</li>` : "<li class=\"tiny\">Noch keine Charge.</li>");
const segments = (data.segments || []).map((s) => {
const end = s.mm_end == null ? "…" : Math.round(s.mm_end);
return `<li>${escapeHtml(s.spool_name)} · ${Math.round(s.mm_start)}–${end} mm${s.ace_slot ? " · Platz " + s.ace_slot : ""}</li>`;
}).join("");
document.getElementById("bericht").innerHTML = `
<div class="card bericht-head">
<div class="bericht-meta">
<p><b>${escapeHtml(states[run.state] || run.state)}</b>${run.verdict ? " · Urteil " + escapeHtml(run.verdict) : ""}</p>
<p class="tiny">${escapeHtml(run.started_at || "—")} → ${escapeHtml(run.ended_at || "—")}</p>
<p>Dauer <b>${hours(run.duration_s)}</b>${run.estimated_s ? " · Soll " + hours(run.estimated_s) : ""}</p>
<p>Material <b>${grams(used)}</b> von ${grams(planned)}${planned ? " (" + (diff > 0 ? "+" : "") + diff + " g)" : ""}
${run.weight_source ? ' · <span class="tag">' + sourceLabel(run.weight_source) + "</span>" : ""}</p>
<p>Kosten <b>${euro(cost)}</b> (Filament ${euro(run.filament_cost)} · Strom ${euro(run.energy_cost)})</p>
${run.message ? "<p class=\"msg\">" + escapeHtml(run.message) + "</p>" : ""}
${data.project ? "<p class=\"tiny\">Projekt: " + escapeHtml(data.project.name) + "</p>" : ""}
</div>
</div>
<div class="card">
<h2>Spulen</h2>
<div class="table-wrap"><table>
<thead><tr><th>Spule</th><th>Sorte</th><th>Soll</th><th>Ist</th><th>Kosten</th><th>Charge</th></tr></thead>
<tbody>${spoolRows}</tbody>
</table></div>
${segments ? "<h3>Abschnitte</h3><ul class=\"plain\">" + segments + "</ul>" : ""}
</div>
<div class="card">
<h2>Verlauf</h2>
${chart(data.samples)}
</div>
<div class="card grid-2">
<div>
<h2>Chargen</h2>
<ul class="plain">${labelBits}</ul>
</div>
<div>
<h2>Ereignisse</h2>
<ul class="plain">${events}</ul>
</div>
</div>`;
const druck = document.getElementById("etikett-druck");
const objekt = document.getElementById("etikett-objekt");
if (run.chargen_nr) {
druck.hidden = false;
druck.href = "/etikett/lauf/" + run.id + "?mode=druck";
objekt.hidden = false;
objekt.href = "/etikett/lauf/" + run.id + "?mode=objekt&qty=1";
} else {
druck.hidden = true;
objekt.hidden = true;
}
}
api("/laeufe/" + runId).then(render).catch((error) => showNote("bericht-note", error));
</script>
{% endblock %}