196 lines
8.0 KiB
HTML
196 lines
8.0 KiB
HTML
{% extends "layout.html" %}
|
|
{% block body %}
|
|
<div class="page-head">
|
|
<div>
|
|
<h1>Projekte</h1>
|
|
<p class="tiny">Gruppiere Dateien und Läufe.</p>
|
|
</div>
|
|
</div>
|
|
<div class="split">
|
|
<section class="card">
|
|
<h2>Liste</h2>
|
|
<div class="table-wrap">
|
|
<table>
|
|
<thead><tr><th>Name</th><th>Status</th><th class="num">Dateien</th></tr></thead>
|
|
<tbody id="project-rows"></tbody>
|
|
</table>
|
|
</div>
|
|
</section>
|
|
<section class="card">
|
|
<h2 id="project-title">Neu</h2>
|
|
<form id="project-form">
|
|
<input id="project-id" type="hidden">
|
|
<div class="field"><label for="project-name">Name</label><input id="project-name" required></div>
|
|
<div class="field"><label for="project-notes">Notiz</label><textarea id="project-notes"></textarea></div>
|
|
<div class="field"><label for="project-status">Status</label>
|
|
<select id="project-status">
|
|
<option value="idee">Idee</option>
|
|
<option value="bereit">Bereit</option>
|
|
<option value="druckt">Druckt</option>
|
|
<option value="fertig">Fertig</option>
|
|
</select></div>
|
|
<div class="actions">
|
|
<button type="submit">Merken</button>
|
|
<button type="button" class="ghost" id="project-new">Neu</button>
|
|
<button type="button" class="warn" id="project-delete">Löschen</button>
|
|
</div>
|
|
</form>
|
|
<p id="project-note"></p>
|
|
<h2 class="section-gap">Kalkulation</h2>
|
|
<label>Spule</label>
|
|
<select id="quote-spool"></select>
|
|
<div class="grid-2">
|
|
<div><label>Teilegewicht g</label><input id="quote-weight" type="number" min="0" step="1" value="0"></div>
|
|
<div><label>Stück</label><input id="quote-qty" type="number" min="1" step="1" value="1"></div>
|
|
<div><label>Ausschuss %</label><input id="quote-waste" type="number" min="0" step="1" value="0"></div>
|
|
<div><label>Auslastung %</label><input id="quote-util" type="number" min="1" max="100" step="1" value="50"></div>
|
|
<div><label>Preis €</label><input id="quote-price" type="number" min="0" step="0.01" value="0"></div>
|
|
<div>
|
|
<label>Preis gilt</label>
|
|
<select id="quote-mode">
|
|
<option value="perPart">pro Stück</option>
|
|
<option value="perKg">pro kg Teile</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="actions"><button type="button" id="quote-go">Rechnen</button></div>
|
|
<div class="nums" id="quote-result"></div>
|
|
</section>
|
|
</div>
|
|
{% endblock %}
|
|
{% block script %}
|
|
<script>
|
|
const statusLabel = { idee: "Idee", bereit: "Bereit", druckt: "Druckt", fertig: "Fertig" };
|
|
let projects = [];
|
|
let spools = [];
|
|
|
|
function fill(project) {
|
|
document.getElementById("project-id").value = project ? project.id : "";
|
|
document.getElementById("project-name").value = project ? project.name : "";
|
|
document.getElementById("project-notes").value = project ? project.notes : "";
|
|
document.getElementById("project-status").value = project ? project.status : "idee";
|
|
document.getElementById("project-title").textContent = project ? project.name : "Neu";
|
|
document.getElementById("project-delete").hidden = !project;
|
|
const quote = project && project.quote && project.quote.inputs;
|
|
if (quote) {
|
|
document.getElementById("quote-weight").value = quote.weight_g;
|
|
document.getElementById("quote-qty").value = quote.qty;
|
|
document.getElementById("quote-waste").value = quote.waste_percent;
|
|
document.getElementById("quote-util").value = quote.utilization;
|
|
document.getElementById("quote-price").value = quote.price;
|
|
document.getElementById("quote-mode").value = quote.price_mode || "perPart";
|
|
}
|
|
if (project && project.result) paint(project.result);
|
|
else if (project && project.quote && project.quote.result) paint(project.quote.result);
|
|
else document.getElementById("quote-result").innerHTML = "";
|
|
}
|
|
|
|
function paint(result) {
|
|
document.getElementById("quote-result").innerHTML = `
|
|
<div class="num"><span>bleibt gesamt</span><b>${euro(result.margin)}</b></div>
|
|
<div class="num"><span>bleibt pro Stunde</span><b>${euro(result.per_hour)}</b></div>
|
|
<div class="num"><span>Filament</span><b>${euro(result.filament_cost)}</b></div>
|
|
<div class="num"><span>Strom</span><b>${euro(result.energy_cost)}</b></div>
|
|
<div class="num"><span>Zeit</span><b>${hours(result.hours * 3600) || "0 min"}</b></div>
|
|
<div class="num"><span>Filamentmenge</span><b>${grams(result.filament_g)}</b></div>`;
|
|
}
|
|
|
|
function render() {
|
|
const body = document.getElementById("project-rows");
|
|
body.innerHTML = "";
|
|
if (!projects.length) {
|
|
body.innerHTML = '<tr><td class="empty" colspan="3"><div class="empty-state">' + icon("folder") + '<p>Noch kein Projekt.</p></div></td></tr>';
|
|
}
|
|
projects.forEach((project) => {
|
|
const tr = document.createElement("tr");
|
|
tr.className = "pick";
|
|
tr.innerHTML = "<td></td><td></td><td></td>";
|
|
tr.children[0].textContent = project.name;
|
|
tr.children[1].textContent = statusLabel[project.status] || project.status;
|
|
tr.children[2].textContent = project.file_count || 0;
|
|
tr.addEventListener("click", async () => {
|
|
const full = await api("/projekte/" + project.id);
|
|
fill(full);
|
|
if (full.files && full.files[0] && full.files[0].weight_g && !full.quote.inputs) {
|
|
document.getElementById("quote-weight").value = full.files[0].weight_g;
|
|
}
|
|
});
|
|
body.appendChild(tr);
|
|
});
|
|
const select = document.getElementById("quote-spool");
|
|
select.innerHTML = '<option value="">ohne Spule</option>';
|
|
spools.forEach((spool) => {
|
|
const option = document.createElement("option");
|
|
option.value = spool.id;
|
|
option.textContent = spool.name;
|
|
select.appendChild(option);
|
|
});
|
|
}
|
|
|
|
async function load() {
|
|
[projects, spools] = await Promise.all([api("/projekte"), api("/spulen")]);
|
|
const settings = await api("/einstellungen");
|
|
document.getElementById("quote-waste").value = settings.energy.waste_percent;
|
|
document.getElementById("quote-util").value = settings.energy.utilization;
|
|
render();
|
|
fill(null);
|
|
}
|
|
|
|
document.getElementById("project-form").addEventListener("submit", async (event) => {
|
|
event.preventDefault();
|
|
const id = document.getElementById("project-id").value;
|
|
const payload = {
|
|
name: document.getElementById("project-name").value,
|
|
notes: document.getElementById("project-notes").value,
|
|
status: document.getElementById("project-status").value,
|
|
};
|
|
try {
|
|
const saved = id
|
|
? await api("/projekte/" + id, { method: "PATCH", json: payload })
|
|
: await api("/projekte", { method: "POST", json: payload });
|
|
showNote("project-note", null);
|
|
document.getElementById("project-note").textContent = "Gemerkt.";
|
|
await load();
|
|
fill(saved);
|
|
} catch (error) {
|
|
showNote("project-note", error);
|
|
}
|
|
});
|
|
document.getElementById("project-new").addEventListener("click", () => fill(null));
|
|
document.getElementById("project-delete").addEventListener("click", (event) => {
|
|
const id = document.getElementById("project-id").value;
|
|
if (!id) return;
|
|
armButton(event.currentTarget, "Dateien bleiben — löschen?", async () => {
|
|
try {
|
|
await api("/projekte/" + id, { method: "DELETE" });
|
|
toast("Projekt gelöscht.");
|
|
load();
|
|
} catch (error) {
|
|
showNote("project-note", error);
|
|
}
|
|
});
|
|
});
|
|
document.getElementById("quote-go").addEventListener("click", async () => {
|
|
try {
|
|
const payload = await api("/kalkulation", {
|
|
method: "POST",
|
|
json: {
|
|
project_id: document.getElementById("project-id").value || null,
|
|
spool_id: document.getElementById("quote-spool").value || null,
|
|
weight_g: Number(document.getElementById("quote-weight").value),
|
|
qty: Number(document.getElementById("quote-qty").value),
|
|
waste_percent: Number(document.getElementById("quote-waste").value),
|
|
utilization: Number(document.getElementById("quote-util").value),
|
|
price: Number(document.getElementById("quote-price").value),
|
|
price_mode: document.getElementById("quote-mode").value,
|
|
},
|
|
});
|
|
paint(payload.result);
|
|
} catch (error) {
|
|
showNote("project-note", error);
|
|
}
|
|
});
|
|
load().catch((error) => showNote("project-note", error));
|
|
</script>
|
|
{% endblock %}
|