Files
grotti/web/templates/bericht.html
T

207 lines
8.9 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 || "";
}
const COST_PART_COLORS = {
filament: "#0d9488",
base: "#94a3b8",
nozzle: "#ea580c",
bed: "#2563eb",
dryer: "#16a34a",
};
function kwhLabel(value) {
if (value == null || value === "") return "—";
return (Number(value) || 0).toLocaleString("de-DE", { maximumFractionDigits: 3 }) + " kWh";
}
function costPieSvg(parts) {
const items = (parts || []).filter((p) => Number(p.cost) > 0);
const total = items.reduce((sum, p) => sum + Number(p.cost || 0), 0);
if (!items.length || total <= 0) return "";
const cx = 42;
const cy = 42;
const r = 36;
let angle = -Math.PI / 2;
const slices = items.map((p) => {
const share = Number(p.cost) / total;
const sweep = share * Math.PI * 2;
const x1 = cx + r * Math.cos(angle);
const y1 = cy + r * Math.sin(angle);
angle += sweep;
const x2 = cx + r * Math.cos(angle);
const y2 = cy + r * Math.sin(angle);
const large = sweep > Math.PI ? 1 : 0;
const color = COST_PART_COLORS[p.key] || "#64748b";
if (share >= 0.999) {
return `<circle cx="${cx}" cy="${cy}" r="${r}" fill="${color}"/>`;
}
return `<path d="M ${cx} ${cy} L ${x1.toFixed(2)} ${y1.toFixed(2)} A ${r} ${r} 0 ${large} 1 ${x2.toFixed(2)} ${y2.toFixed(2)} Z" fill="${color}"/>`;
}).join("");
return `<svg class="energy-pie" viewBox="0 0 84 84" role="img" aria-label="Kostenanteile">${slices}</svg>`;
}
function costBreakdownHtml(energy, filamentCost) {
const fil = Number(filamentCost) || 0;
const energyParts = (energy && energy.parts) || [];
const parts = [];
if (fil > 1e-6) {
parts.push({ key: "filament", label: "Filament", kwh: null, cost: fil });
}
energyParts.forEach((p) => parts.push(p));
if (!parts.length) return "";
const energyEuro = Number((energy && energy.cost) || 0);
const totalEuro = fil + energyEuro;
const src = energy && energy.source === "ed" ? "Strom aus ED" : (energyParts.length ? "Strom pauschal" : "");
const rate = energy && energy.euro_per_kwh != null
? Number(energy.euro_per_kwh).toLocaleString("de-DE", { style: "currency", currency: "EUR", maximumFractionDigits: 3 }) + "/kWh"
: "";
const rows = parts.map((p) => {
const color = COST_PART_COLORS[p.key] || "#64748b";
return `<tr>
<td><span class="energy-swatch" style="background:${color}"></span>${escapeHtml(p.label)}</td>
<td class="num">${kwhLabel(p.kwh)}</td>
<td class="num">${euro(p.cost)}</td>
</tr>`;
}).join("");
const sumKwh = energy && energy.kwh != null ? kwhLabel(energy.kwh) : "—";
const hint = [src, rate].filter(Boolean).join(" · ");
return `<div class="energy-break">
<div class="energy-break-chart">${costPieSvg(parts)}</div>
<div class="energy-break-table">
<div class="lib-head"><h3>Kosten</h3><span class="tiny">${escapeHtml(hint)}</span></div>
<div class="table-wrap"><table>
<thead><tr><th>Anteil</th><th class="num">kWh</th><th class="num">€</th></tr></thead>
<tbody>${rows}
<tr class="energy-total"><td>Summe</td><td class="num">${sumKwh}</td><td class="num">${euro(totalEuro)}</td></tr>
</tbody>
</table></div>
</div>
</div>`;
}
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 energy = data.energy || {};
const energyEuro = energy.cost != null ? Number(energy.cost) : Number(run.energy_cost || 0);
const cost = Number(run.filament_cost || 0) + energyEuro;
const energyHint = energy.source === "ed"
? (Number(energy.kwh) || 0).toLocaleString("de-DE", { maximumFractionDigits: 2 }) + " kWh aus ED"
: (energy.kwh != null
? (Number(energy.kwh) || 0).toLocaleString("de-DE", { maximumFractionDigits: 2 }) + " kWh pauschal"
: "");
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(formatDateTime(e.created_at))}</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(formatDateRange(run.started_at, 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(energyEuro)}${energyHint ? " · " + energyHint : ""})${run.cost_per_part != null ? " · <b>" + euro(run.cost_per_part) + "</b>/Teil" : ""}</p>
${run.pieces_ok || run.pieces_bad ? `<p>Stück <b>${run.pieces_ok || 0}</b> gut${run.pieces_bad ? " · " + run.pieces_bad + " schlecht" : ""}</p>` : ""}
${run.message ? "<p class=\"msg\">" + escapeHtml(run.message) + "</p>" : ""}
${data.project ? "<p class=\"tiny\">Projekt: " + escapeHtml(data.project.name) + "</p>" : ""}
</div>
${costBreakdownHtml(energy, run.filament_cost)}
</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>
${tempChartHtml(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 %}