Grotti 3.3.0: Projekte mit Ordner, Datei-Löschen/Verschieben, Material-Archiv, UI-Polish.

This commit is contained in:
Dominic Gundel committed 2026-10-18 20:10:32 +02:00
1 parent 4ddffbbeb1
commit 67a89d1794
45 files changed
+2856 -320

No files matched your search

+28 -16
View File
@@ -29,7 +29,8 @@ function sourceLabel(source, estimate) {
return source || "";
}
const ENERGY_PART_COLORS = {
const COST_PART_COLORS = {
filament: "#0d9488",
base: "#94a3b8",
nozzle: "#ea580c",
bed: "#2563eb",
@@ -37,19 +38,20 @@ const ENERGY_PART_COLORS = {
};
function kwhLabel(value) {
if (value == null || value === "") return "—";
return (Number(value) || 0).toLocaleString("de-DE", { maximumFractionDigits: 3 }) + " kWh";
}
function energyPieSvg(parts) {
const items = (parts || []).filter((p) => Number(p.kwh) > 0);
const total = items.reduce((sum, p) => sum + Number(p.kwh || 0), 0);
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.kwh) / total;
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);
@@ -57,38 +59,48 @@ function energyPieSvg(parts) {
const x2 = cx + r * Math.cos(angle);
const y2 = cy + r * Math.sin(angle);
const large = sweep > Math.PI ? 1 : 0;
const color = ENERGY_PART_COLORS[p.key] || "#64748b";
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="Stromanteile">${slices}</svg>`;
return `<svg class="energy-pie" viewBox="0 0 84 84" role="img" aria-label="Kostenanteile">${slices}</svg>`;
}
function energyBreakdownHtml(energy) {
const parts = energy.parts || [];
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 src = energy.source === "ed" ? "aus ED" : "pauschal";
const rate = energy.euro_per_kwh != null
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 = ENERGY_PART_COLORS[p.key] || "#64748b";
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">${energyPieSvg(parts)}</div>
<div class="energy-break-chart">${costPieSvg(parts)}</div>
<div class="energy-break-table">
<div class="lib-head"><h3>Strom</h3><span class="tiny">${escapeHtml(src)}${rate ? " · " + rate : ""}</span></div>
<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">${kwhLabel(energy.kwh)}</td><td class="num">${euro(energy.cost)}</td></tr>
<tr class="energy-total"><td>Summe</td><td class="num">${sumKwh}</td><td class="num">${euro(totalEuro)}</td></tr>
</tbody>
</table></div>
</div>
@@ -151,7 +163,7 @@ function render(data) {
${run.message ? "<p class=\"msg\">" + escapeHtml(run.message) + "</p>" : ""}
${data.project ? "<p class=\"tiny\">Projekt: " + escapeHtml(data.project.name) + "</p>" : ""}
</div>
${energyBreakdownHtml(energy)}
${costBreakdownHtml(energy, run.filament_cost)}
</div>
<div class="card">
<h2>Spulen</h2>