Flask-Web-App statt Tkinter: Live-Berechnung, PDF-Rechnung, Dark Mode, Tabler CSS
This commit is contained in:
142
static/script.js
Normal file
142
static/script.js
Normal file
@@ -0,0 +1,142 @@
|
||||
/**
|
||||
* 3D-Druck Kostenkalkulation — Frontend-Logik.
|
||||
*
|
||||
* - Live-Berechnung bei jeder Eingabe (via POST /calculate)
|
||||
* - Automatisches Speichern in YAML (debounced 500ms via POST /save_config)
|
||||
* - Dark-Mode-Umschaltung (persistiert in YAML)
|
||||
* - Export als TXT (POST /export -> Blob-Download)
|
||||
*/
|
||||
|
||||
let saveTimeout = null;
|
||||
|
||||
/** Sammelt alle Formularwerte anhand ihrer data-var Attribute. */
|
||||
function getFormData() {
|
||||
const data = {};
|
||||
document.querySelectorAll('[data-var]').forEach(el => {
|
||||
const key = el.dataset.var;
|
||||
const type = el.dataset.type || 'text';
|
||||
let val = el.value;
|
||||
if (type === 'number') val = parseFloat(val) || 0;
|
||||
data[key] = val;
|
||||
});
|
||||
data.dark_mode = document.documentElement.getAttribute('data-bs-theme') === 'dark';
|
||||
return data;
|
||||
}
|
||||
|
||||
/** Sendet Formulardaten an /calculate und aktualisiert die Ergebnisanzeige. */
|
||||
async function recalculate() {
|
||||
const data = getFormData();
|
||||
try {
|
||||
const res = await fetch('/calculate', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
const calc = await res.json();
|
||||
renderResult(calc);
|
||||
} catch (e) {
|
||||
console.error('Calculate error', e);
|
||||
}
|
||||
}
|
||||
|
||||
/** Baut die textuelle Ergebnisanzeige aus den JSON-Berechnungswerten. */
|
||||
function renderResult(calc) {
|
||||
const lines = [
|
||||
`Materialkosten: ${String(calc.materialkosten.toFixed(2)).padStart(10)} EUR`,
|
||||
`Stromkosten: ${String(calc.stromkosten.toFixed(2)).padStart(10)} EUR`,
|
||||
`Abschreibung Drucker: ${String(calc.abschreibung.toFixed(2)).padStart(10)} EUR`,
|
||||
`Arbeitskosten: ${String(calc.arbeitskosten.toFixed(2)).padStart(10)} EUR`,
|
||||
`${'\u2500'.repeat(40)}`,
|
||||
`Gesamtkosten: ${String(calc.gesamtkosten_netto.toFixed(2)).padStart(10)} EUR`,
|
||||
'',
|
||||
`Gewinn (${((document.querySelector('[data-var="gewinnmarge"]')?.value) || 20)}%): ${String(calc.gewinn.toFixed(2)).padStart(10)} EUR`,
|
||||
`${'\u2500'.repeat(40)}`,
|
||||
`Verkaufspreis (netto): ${String(calc.verkaufspreis.toFixed(2)).padStart(10)} EUR`,
|
||||
`MwSt. (19%): ${String(calc.mwst.toFixed(2)).padStart(10)} EUR`,
|
||||
`${'\u2550'.repeat(40)}`,
|
||||
`GESAMT (brutto): ${String(calc.gesamt_brutto.toFixed(2)).padStart(10)} EUR`,
|
||||
];
|
||||
document.getElementById('result-text').textContent = lines.join('\n');
|
||||
|
||||
const details = [
|
||||
`Druckzeit: ${calc.druckzeit_stunden} Stunden`,
|
||||
`Materialgewicht: ${calc.material_kg} kg`,
|
||||
`Abschreibung pro Stunde: ${calc.abschreibung_pro_stunde} EUR`,
|
||||
`Kosten pro Gramm: ${calc.kosten_pro_gramm} EUR`,
|
||||
`Stromkosten pro Stunde: ${calc.strom_pro_stunde} EUR`,
|
||||
];
|
||||
document.getElementById('detail-text').textContent = details.join('\n');
|
||||
}
|
||||
|
||||
/** Plant ein Auto-Save in die YAML-Konfiguration (debounced 500ms). */
|
||||
function scheduleSave() {
|
||||
clearTimeout(saveTimeout);
|
||||
saveTimeout = setTimeout(async () => {
|
||||
const data = getFormData();
|
||||
try {
|
||||
await fetch('/save_config', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('Save error', e);
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
|
||||
/** Wird bei jeder Eingabe aufgerufen: speichert + berechnet neu. */
|
||||
async function onInputChange() {
|
||||
scheduleSave();
|
||||
await recalculate();
|
||||
}
|
||||
|
||||
/** Schaltet zwischen Light- und Dark-Mode um und persistiert die Einstellung. */
|
||||
function toggleDarkMode() {
|
||||
const html = document.documentElement;
|
||||
const isDark = html.getAttribute('data-bs-theme') === 'dark';
|
||||
const newTheme = isDark ? 'light' : 'dark';
|
||||
html.setAttribute('data-bs-theme', newTheme);
|
||||
|
||||
const btn = document.getElementById('dark-mode-btn');
|
||||
const icon = btn.querySelector('i');
|
||||
icon.className = `ti ti-${isDark ? 'sun' : 'moon'} me-1`;
|
||||
btn.childNodes[2].textContent = isDark ? 'Light' : 'Dark';
|
||||
|
||||
scheduleSave();
|
||||
}
|
||||
|
||||
/** Sendet die aktuellen Werte an /export und lost einen TXT-Download aus. */
|
||||
async function exportData() {
|
||||
const data = getFormData();
|
||||
try {
|
||||
const res = await fetch('/export', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
const blob = await res.blob();
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = 'kalkulation.txt';
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
showNotification('Export erfolgreich!');
|
||||
} catch (e) {
|
||||
console.error('Export error', e);
|
||||
}
|
||||
}
|
||||
|
||||
/** Zeigt eine kurze Toast-Notification oben rechts an. */
|
||||
function showNotification(msg) {
|
||||
const container = document.getElementById('notification');
|
||||
document.getElementById('notification-msg').textContent = msg;
|
||||
container.style.display = 'block';
|
||||
setTimeout(() => { container.style.display = 'none'; }, 2500);
|
||||
}
|
||||
|
||||
/** Initiale Berechnung beim Seitenladen. */
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
recalculate();
|
||||
});
|
||||
13
static/style.css
Normal file
13
static/style.css
Normal file
@@ -0,0 +1,13 @@
|
||||
/* Grossere Schrift in Ueberschriften */
|
||||
.card-header, .navbar-brand span {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
/* Monospace-Ergebnisblock (Kostenaufstellung) */
|
||||
.result-box {
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
font-size: 14px;
|
||||
line-height: 1.8;
|
||||
white-space: pre;
|
||||
overflow-x: auto;
|
||||
}
|
||||
Reference in New Issue
Block a user