850 lines
30 KiB
HTML
850 lines
30 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="de">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Roll Calculator Admin</title>
|
||
<style>
|
||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||
body {
|
||
font-family: 'Segoe UI', Arial, sans-serif;
|
||
background: #f4f6f9;
|
||
color: #333;
|
||
min-height: 100vh;
|
||
}
|
||
|
||
.header {
|
||
background: linear-gradient(135deg, #003366 0%, #0066cc 100%);
|
||
color: white;
|
||
padding: 20px 30px;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
.header h1 { font-size: 20px; }
|
||
|
||
.container {
|
||
max-width: 1200px;
|
||
margin: 30px auto;
|
||
padding: 0 20px;
|
||
}
|
||
|
||
.info-box {
|
||
background: #fffbea;
|
||
border-left: 4px solid #f0b429;
|
||
padding: 16px;
|
||
border-radius: 4px;
|
||
margin-bottom: 20px;
|
||
font-size: 13px;
|
||
color: #666;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
.tabs {
|
||
display: flex;
|
||
gap: 10px;
|
||
margin-bottom: 20px;
|
||
border-bottom: 2px solid #e0e8f0;
|
||
flex-wrap: wrap;
|
||
}
|
||
.tab-btn {
|
||
padding: 12px 20px;
|
||
border: none;
|
||
background: none;
|
||
cursor: pointer;
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
color: #666;
|
||
border-bottom: 3px solid transparent;
|
||
margin-bottom: -2px;
|
||
transition: all 0.2s;
|
||
}
|
||
.tab-btn.active {
|
||
color: #0066cc;
|
||
border-bottom-color: #0066cc;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.tab-content { display: none; }
|
||
.tab-content.active { display: block; }
|
||
|
||
.card {
|
||
background: white;
|
||
border-radius: 8px;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.07);
|
||
padding: 24px;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.card h2 {
|
||
font-size: 18px;
|
||
margin-bottom: 8px;
|
||
color: #003366;
|
||
}
|
||
.card > p {
|
||
font-size: 12px;
|
||
color: #666;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.form-row {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||
gap: 16px;
|
||
margin-bottom: 16px;
|
||
}
|
||
.form-group {
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
.form-group label {
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
color: #555;
|
||
margin-bottom: 5px;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.4px;
|
||
}
|
||
.form-group input, .form-group textarea, .form-group select {
|
||
padding: 9px 11px;
|
||
border: 1px solid #ccc;
|
||
border-radius: 4px;
|
||
font-size: 13px;
|
||
font-family: 'Segoe UI', Arial, sans-serif;
|
||
transition: border 0.2s;
|
||
}
|
||
.form-group textarea {
|
||
font-family: 'Courier New', monospace;
|
||
resize: vertical;
|
||
min-height: 300px;
|
||
}
|
||
.form-group input:focus, .form-group textarea:focus, .form-group select:focus {
|
||
outline: none;
|
||
border-color: #0066cc;
|
||
box-shadow: 0 0 0 3px rgba(0,102,204,0.1);
|
||
}
|
||
|
||
.button-group {
|
||
display: flex;
|
||
gap: 10px;
|
||
margin-top: 20px;
|
||
flex-wrap: wrap;
|
||
}
|
||
.btn {
|
||
padding: 10px 20px;
|
||
border: none;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
transition: all 0.2s;
|
||
}
|
||
.btn-primary {
|
||
background: #0066cc;
|
||
color: white;
|
||
}
|
||
.btn-primary:hover { background: #0052a3; box-shadow: 0 2px 6px rgba(0,102,204,0.3); }
|
||
|
||
.btn-secondary {
|
||
background: #e0e0e0;
|
||
color: #333;
|
||
}
|
||
.btn-secondary:hover { background: #d0d0d0; }
|
||
|
||
.btn-danger {
|
||
background: #dc3545;
|
||
color: white;
|
||
}
|
||
.btn-danger:hover { background: #c82333; }
|
||
|
||
.btn-success {
|
||
background: #28a745;
|
||
color: white;
|
||
}
|
||
.btn-success:hover { background: #218838; }
|
||
|
||
table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
font-size: 13px;
|
||
}
|
||
table th {
|
||
background: #f5f5f5;
|
||
padding: 12px;
|
||
text-align: left;
|
||
font-weight: 600;
|
||
border-bottom: 2px solid #0066cc;
|
||
color: #333;
|
||
}
|
||
table td {
|
||
padding: 12px;
|
||
border-bottom: 1px solid #e0e0e0;
|
||
}
|
||
table tr:hover { background: #f9f9f9; }
|
||
|
||
.alert {
|
||
padding: 12px 16px;
|
||
border-radius: 4px;
|
||
margin-bottom: 16px;
|
||
font-size: 13px;
|
||
display: none;
|
||
}
|
||
.alert.show { display: block; }
|
||
.alert-success {
|
||
background: #d4edda;
|
||
border: 1px solid #c3e6cb;
|
||
color: #155724;
|
||
}
|
||
.alert-error {
|
||
background: #f8d7da;
|
||
border: 1px solid #f5c6cb;
|
||
color: #721c24;
|
||
}
|
||
.alert-info {
|
||
background: #d1ecf1;
|
||
border: 1px solid #bee5eb;
|
||
color: #0c5460;
|
||
}
|
||
|
||
.action-buttons {
|
||
display: flex;
|
||
gap: 6px;
|
||
}
|
||
.action-buttons button {
|
||
padding: 6px 12px;
|
||
font-size: 11px;
|
||
border: none;
|
||
border-radius: 3px;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
}
|
||
.edit-btn {
|
||
background: #0066cc;
|
||
color: white;
|
||
}
|
||
.edit-btn:hover { background: #0052a3; }
|
||
.delete-btn {
|
||
background: #dc3545;
|
||
color: white;
|
||
}
|
||
.delete-btn:hover { background: #c82333; }
|
||
|
||
.password-overlay {
|
||
position: fixed;
|
||
inset: 0;
|
||
background: rgba(0,0,0,0.7);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 9999;
|
||
}
|
||
.password-box {
|
||
background: white;
|
||
border-radius: 8px;
|
||
padding: 32px;
|
||
max-width: 400px;
|
||
box-shadow: 0 8px 40px rgba(0,0,0,0.3);
|
||
text-align: center;
|
||
}
|
||
.password-box h2 { margin-bottom: 20px; color: #003366; }
|
||
.password-box input {
|
||
width: 100%;
|
||
padding: 11px;
|
||
border: 1px solid #ccc;
|
||
border-radius: 4px;
|
||
font-size: 14px;
|
||
margin-bottom: 16px;
|
||
}
|
||
.password-box .btn { width: 100%; margin-bottom: 10px; }
|
||
|
||
.file-input {
|
||
display: none;
|
||
}
|
||
|
||
.stats {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||
gap: 16px;
|
||
margin-bottom: 20px;
|
||
}
|
||
.stat {
|
||
background: linear-gradient(135deg, #e8f4fd 0%, #d0e8fa 100%);
|
||
border: 1px solid #0066cc;
|
||
border-radius: 6px;
|
||
padding: 16px;
|
||
text-align: center;
|
||
}
|
||
.stat-value {
|
||
font-size: 24px;
|
||
font-weight: 700;
|
||
color: #003366;
|
||
}
|
||
.stat-label {
|
||
font-size: 12px;
|
||
color: #666;
|
||
margin-top: 4px;
|
||
}
|
||
|
||
.modal-overlay {
|
||
position: fixed;
|
||
inset: 0;
|
||
background: rgba(0,0,0,0.5);
|
||
display: none;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 5000;
|
||
}
|
||
.modal-overlay.active {
|
||
display: flex;
|
||
}
|
||
.modal {
|
||
background: white;
|
||
border-radius: 8px;
|
||
max-width: 600px;
|
||
max-height: 90vh;
|
||
overflow-y: auto;
|
||
padding: 30px;
|
||
box-shadow: 0 8px 40px rgba(0,0,0,0.3);
|
||
width: 100%;
|
||
}
|
||
.modal h2 {
|
||
color: #003366;
|
||
margin-bottom: 20px;
|
||
}
|
||
.modal-buttons {
|
||
display: flex;
|
||
gap: 10px;
|
||
margin-top: 30px;
|
||
justify-content: flex-end;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<!-- PASSWORD OVERLAY -->
|
||
<div id="passwordOverlay" class="password-overlay">
|
||
<div class="password-box">
|
||
<h2>🔐 Roll Calculator Admin</h2>
|
||
<p style="font-size:13px;color:#666;margin-bottom:20px;">Geben Sie das Admin-Passwort ein</p>
|
||
<input type="password" id="passwordInput" placeholder="Passwort" autofocus>
|
||
<button class="btn btn-primary" onclick="checkPassword()">🔓 Anmelden</button>
|
||
<p style="font-size:11px;color:#999;margin-top:10px;">Standard: admin123</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- EDIT MODAL -->
|
||
<div id="editModal" class="modal-overlay">
|
||
<div class="modal">
|
||
<h2>📝 Artikel bearbeiten</h2>
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label>Artikel-Nr. (nicht änderbar)</label>
|
||
<input type="text" id="editNr" disabled style="background:#f5f5f5;">
|
||
</div>
|
||
</div>
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label>Name</label>
|
||
<input type="text" id="editName">
|
||
</div>
|
||
</div>
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label>Produktdicke (mm)</label>
|
||
<input type="number" id="editThickness" step="0.001" min="0">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Area Weight (g/m²)</label>
|
||
<input type="number" id="editAreaWeight" step="1" min="0">
|
||
</div>
|
||
</div>
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label>Gewicht pro Rolle (kg)</label>
|
||
<input type="number" id="editWeightPerRoll" step="0.1" min="0">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Breite (m)</label>
|
||
<input type="number" id="editWidth" step="0.01" min="0">
|
||
</div>
|
||
</div>
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label>Preis (€)</label>
|
||
<input type="number" id="editPrice" step="0.01" min="0">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Typ</label>
|
||
<input type="text" id="editType" placeholder="z.B. Geotextil, Kunststoff">
|
||
</div>
|
||
</div>
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label>Farbe</label>
|
||
<input type="text" id="editColor" placeholder="z.B. Schwarz, Weiß">
|
||
</div>
|
||
</div>
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label>Beschreibung</label>
|
||
<textarea id="editDescription" placeholder="Zusätzliche Informationen..."></textarea>
|
||
</div>
|
||
</div>
|
||
<div class="modal-buttons">
|
||
<button class="btn btn-secondary" onclick="closeEditModal()">Abbrechen</button>
|
||
<button class="btn btn-primary" onclick="saveEditedArticle()">💾 Speichern</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- MAIN APP -->
|
||
<div id="mainApp" style="display:none;">
|
||
<div class="header">
|
||
<h1>🛠️ Roll Calculator Admin</h1>
|
||
<button class="btn btn-secondary" onclick="logout()">Abmelden</button>
|
||
</div>
|
||
|
||
<div class="container">
|
||
<div class="info-box">
|
||
💡 Tipp: Alle Änderungen werden im Browser gespeichert. Mit "Download" kannst du die neuen JSON-Dateien herunterladen und auf deinen Server kopieren.
|
||
</div>
|
||
|
||
<div class="tabs">
|
||
<button class="tab-btn active" onclick="switchTab('articles')">📄 Artikel-Daten</button>
|
||
<button class="tab-btn" onclick="switchTab('config')">⚙️ Konfiguration</button>
|
||
<button class="tab-btn" onclick="switchTab('tools')">🔧 Tools</button>
|
||
</div>
|
||
|
||
<!-- TAB: ARTICLES -->
|
||
<div id="articles" class="tab-content active">
|
||
<div class="card">
|
||
<h2>Artikel verwalten</h2>
|
||
<p>Artikel hinzufügen, bearbeiten oder löschen</p>
|
||
|
||
<div id="articleAlert" class="alert"></div>
|
||
|
||
<div class="stats">
|
||
<div class="stat">
|
||
<div class="stat-value" id="articleCount">0</div>
|
||
<div class="stat-label">Artikel</div>
|
||
</div>
|
||
</div>
|
||
|
||
<h3 style="font-size:14px;margin-bottom:12px;margin-top:20px;">Neuer Artikel</h3>
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label>Artikel-Nr. *</label>
|
||
<input type="text" id="artNr" placeholder="z.B. 180305">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Name *</label>
|
||
<input type="text" id="artName" placeholder="z.B. Bfix NSP 5300">
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label>Produktdicke (mm) *</label>
|
||
<input type="number" id="artThickness" placeholder="z.B. 6.918" step="0.001" min="0">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Area Weight (g/m²)</label>
|
||
<input type="number" id="artAreaWeight" placeholder="z.B. 5300" step="1" min="0">
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label>Gewicht pro Rolle (kg)</label>
|
||
<input type="number" id="artWeightPerRoll" placeholder="z.B. 25" step="0.1" min="0">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Breite (m)</label>
|
||
<input type="number" id="artWidth" placeholder="z.B. 5" step="0.01" min="0">
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label>Preis (€)</label>
|
||
<input type="number" id="artPrice" placeholder="z.B. 250.00" step="0.01" min="0">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Typ</label>
|
||
<input type="text" id="artType" placeholder="z.B. Geotextil">
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label>Farbe</label>
|
||
<input type="text" id="artColor" placeholder="z.B. Schwarz">
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label>Beschreibung</label>
|
||
<textarea id="artDescription" placeholder="Zusätzliche Informationen..."></textarea>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="button-group">
|
||
<button class="btn btn-primary" onclick="addArticle()">➕ Hinzufügen</button>
|
||
<button class="btn btn-secondary" onclick="clearArticleForm()">Zurücksetzen</button>
|
||
</div>
|
||
|
||
<h3 style="font-size:14px;margin-bottom:12px;margin-top:30px;">Artikel-Liste</h3>
|
||
<div style="overflow-x:auto;">
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>Nr.</th>
|
||
<th>Name</th>
|
||
<th>Dicke (mm)</th>
|
||
<th>Breite (m)</th>
|
||
<th>Preis (€)</th>
|
||
<th>Aktionen</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="articleTableBody">
|
||
<tr><td colspan="6" style="text-align:center;color:#999;">Keine Artikel</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- TAB: CONFIG -->
|
||
<div id="config" class="tab-content">
|
||
<div class="card">
|
||
<h2>Konfiguration (config.json)</h2>
|
||
<p>Gabelstapler-Regeln und Schwellenwerte - JSON direkt bearbeiten</p>
|
||
|
||
<div id="configAlert" class="alert"></div>
|
||
|
||
<textarea id="configJson" placeholder="config.json wird hier angezeigt..."></textarea>
|
||
|
||
<div class="button-group">
|
||
<button class="btn btn-primary" onclick="saveConfigJson()">💾 Aktualisieren</button>
|
||
<button class="btn btn-secondary" onclick="resetConfigJson()">🔄 Zurücksetzen</button>
|
||
</div>
|
||
|
||
<p style="font-size:11px;color:#999;margin-top:16px;">
|
||
💡 JSON wird automatisch validiert. Im Fehlerfall siehst du eine Fehlermeldung.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- TAB: TOOLS -->
|
||
<div id="tools" class="tab-content">
|
||
<div class="card">
|
||
<h2>📥 Dateien verwalten</h2>
|
||
<p>Herunterladen, hochladen oder zurücksetzen</p>
|
||
|
||
<div id="toolsAlert" class="alert"></div>
|
||
|
||
<h3 style="font-size:13px;margin-bottom:12px;">Download</h3>
|
||
<div class="button-group">
|
||
<button class="btn btn-success" onclick="downloadArticleData()">📥 article-data.json</button>
|
||
<button class="btn btn-success" onclick="downloadConfig()">📥 config.json</button>
|
||
<button class="btn btn-success" onclick="downloadAll()">📦 Alle Dateien (ZIP)</button>
|
||
</div>
|
||
|
||
<h3 style="font-size:13px;margin-bottom:12px;margin-top:20px;">Upload</h3>
|
||
<p style="font-size:12px;color:#666;margin-bottom:10px;">Dateien hochladen und überschreiben:</p>
|
||
<div class="button-group">
|
||
<button class="btn btn-secondary" onclick="document.getElementById('uploadFile').click()">📤 JSON-Datei hochladen</button>
|
||
</div>
|
||
<input type="file" id="uploadFile" class="file-input" accept=".json" onchange="handleFileUpload(event)">
|
||
|
||
<h3 style="font-size:13px;margin-bottom:12px;margin-top:20px;">Daten</h3>
|
||
<p style="font-size:12px;color:#666;margin-bottom:10px;">Alle bearbeiteten Daten als ein JSON exportieren:</p>
|
||
<button class="btn btn-primary" onclick="exportAllAsJson()">💾 Alles exportieren</button>
|
||
|
||
<h3 style="font-size:13px;margin-bottom:12px;margin-top:20px;">⚠️ Vorsicht</h3>
|
||
<p style="font-size:12px;color:#666;margin-bottom:10px;">Bearbeitete Daten zurücksetzen (Achtung: nicht rückgängig!):</p>
|
||
<button class="btn btn-danger" onclick="if(confirm('Wirklich auf Original zurücksetzen?')) resetAll()">🔄 Auf Standardwerte zurücksetzen</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
const ADMIN_PASSWORD = 'admin123';
|
||
|
||
let articlesData = [];
|
||
let configData = {};
|
||
let currentEditingArticle = null;
|
||
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
loadDataFromServer();
|
||
document.getElementById('passwordInput').addEventListener('keypress', (e) => {
|
||
if (e.key === 'Enter') checkPassword();
|
||
});
|
||
});
|
||
|
||
function loadDataFromServer() {
|
||
Promise.all([
|
||
fetch('article-data.json').then(r => r.json()).catch(() => []),
|
||
fetch('config.json').then(r => r.json()).catch(() => ({}))
|
||
]).then(([articles, config]) => {
|
||
articlesData = articles || [];
|
||
configData = config || {};
|
||
console.log('✅ Daten geladen:', articlesData.length, 'Artikel');
|
||
});
|
||
}
|
||
|
||
function checkPassword() {
|
||
const pwd = document.getElementById('passwordInput').value;
|
||
if (pwd === ADMIN_PASSWORD) {
|
||
document.getElementById('passwordOverlay').style.display = 'none';
|
||
document.getElementById('mainApp').style.display = 'block';
|
||
refreshUI();
|
||
} else {
|
||
alert('❌ Falsches Passwort!');
|
||
document.getElementById('passwordInput').value = '';
|
||
document.getElementById('passwordInput').focus();
|
||
}
|
||
}
|
||
|
||
function logout() {
|
||
document.getElementById('mainApp').style.display = 'none';
|
||
document.getElementById('passwordOverlay').style.display = 'flex';
|
||
document.getElementById('passwordInput').value = '';
|
||
document.getElementById('passwordInput').focus();
|
||
}
|
||
|
||
function switchTab(tabName) {
|
||
document.querySelectorAll('.tab-content').forEach(t => t.classList.remove('active'));
|
||
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
|
||
document.getElementById(tabName).classList.add('active');
|
||
event.target.classList.add('active');
|
||
|
||
if (tabName === 'config') loadConfigJson();
|
||
if (tabName === 'articles') refreshArticleTable();
|
||
}
|
||
|
||
function showAlert(containerId, message, type, duration = 4000) {
|
||
const alert = document.getElementById(containerId);
|
||
alert.className = `alert show alert-${type}`;
|
||
alert.textContent = message;
|
||
if (duration) setTimeout(() => { alert.classList.remove('show'); }, duration);
|
||
}
|
||
|
||
function refreshUI() {
|
||
refreshArticleTable();
|
||
loadConfigJson();
|
||
updateStats();
|
||
}
|
||
|
||
function updateStats() {
|
||
document.getElementById('articleCount').textContent = articlesData.length;
|
||
}
|
||
|
||
function openEditModal(nr) {
|
||
const article = articlesData.find(a => a.nr === nr);
|
||
if (!article) return;
|
||
|
||
currentEditingArticle = article;
|
||
|
||
document.getElementById('editNr').value = article.nr || '';
|
||
document.getElementById('editName').value = article.name || '';
|
||
document.getElementById('editThickness').value = article.thickness || '';
|
||
document.getElementById('editAreaWeight').value = article.area_weight || '';
|
||
document.getElementById('editWeightPerRoll').value = article.weight_per_roll || '';
|
||
document.getElementById('editWidth').value = article.width || '';
|
||
document.getElementById('editPrice').value = article.price || '';
|
||
document.getElementById('editType').value = article.type || '';
|
||
document.getElementById('editColor').value = article.color || '';
|
||
document.getElementById('editDescription').value = article.description || '';
|
||
|
||
document.getElementById('editModal').classList.add('active');
|
||
}
|
||
|
||
function closeEditModal() {
|
||
document.getElementById('editModal').classList.remove('active');
|
||
currentEditingArticle = null;
|
||
}
|
||
|
||
function saveEditedArticle() {
|
||
if (!currentEditingArticle) return;
|
||
|
||
currentEditingArticle.name = document.getElementById('editName').value.trim();
|
||
currentEditingArticle.thickness = parseFloat(document.getElementById('editThickness').value) || 0;
|
||
currentEditingArticle.area_weight = parseFloat(document.getElementById('editAreaWeight').value) || 0;
|
||
currentEditingArticle.weight_per_roll = parseFloat(document.getElementById('editWeightPerRoll').value) || 0;
|
||
currentEditingArticle.width = parseFloat(document.getElementById('editWidth').value) || 0;
|
||
currentEditingArticle.price = parseFloat(document.getElementById('editPrice').value) || 0;
|
||
currentEditingArticle.type = document.getElementById('editType').value.trim();
|
||
currentEditingArticle.color = document.getElementById('editColor').value.trim();
|
||
currentEditingArticle.description = document.getElementById('editDescription').value.trim();
|
||
|
||
closeEditModal();
|
||
showAlert('articleAlert', '✅ Artikel aktualisiert!', 'success');
|
||
refreshArticleTable();
|
||
}
|
||
|
||
function addArticle() {
|
||
const nr = document.getElementById('artNr').value.trim();
|
||
const name = document.getElementById('artName').value.trim();
|
||
const thickness = parseFloat(document.getElementById('artThickness').value) || 0;
|
||
|
||
if (!nr || !name || thickness === 0) {
|
||
showAlert('articleAlert', '❌ Artikel-Nr., Name und Dicke sind erforderlich!', 'error');
|
||
return;
|
||
}
|
||
|
||
if (articlesData.some(a => a.nr === nr)) {
|
||
showAlert('articleAlert', '❌ Artikel-Nr. existiert bereits!', 'error');
|
||
return;
|
||
}
|
||
|
||
const newArticle = {
|
||
nr,
|
||
name,
|
||
thickness,
|
||
area_weight: parseFloat(document.getElementById('artAreaWeight').value) || 0,
|
||
weight_per_roll: parseFloat(document.getElementById('artWeightPerRoll').value) || 0,
|
||
width: parseFloat(document.getElementById('artWidth').value) || 0,
|
||
price: parseFloat(document.getElementById('artPrice').value) || 0,
|
||
type: document.getElementById('artType').value.trim(),
|
||
color: document.getElementById('artColor').value.trim(),
|
||
description: document.getElementById('artDescription').value.trim()
|
||
};
|
||
|
||
articlesData.push(newArticle);
|
||
showAlert('articleAlert', '✅ Artikel hinzugefügt!', 'success');
|
||
clearArticleForm();
|
||
refreshArticleTable();
|
||
updateStats();
|
||
}
|
||
|
||
function deleteArticle(nr) {
|
||
if (confirm('Artikel ' + nr + ' wirklich löschen?')) {
|
||
articlesData = articlesData.filter(a => a.nr !== nr);
|
||
showAlert('articleAlert', '✅ Artikel gelöscht!', 'success');
|
||
refreshArticleTable();
|
||
updateStats();
|
||
}
|
||
}
|
||
|
||
function refreshArticleTable() {
|
||
const tbody = document.getElementById('articleTableBody');
|
||
if (articlesData.length === 0) {
|
||
tbody.innerHTML = '<tr><td colspan="6" style="text-align:center;color:#999;">Keine Artikel</td></tr>';
|
||
return;
|
||
}
|
||
|
||
tbody.innerHTML = articlesData.map(a => `
|
||
<tr>
|
||
<td><strong>${a.nr}</strong></td>
|
||
<td>${a.name}</td>
|
||
<td>${a.thickness.toFixed(3)}</td>
|
||
<td>${a.width ? a.width.toFixed(2) : '—'}</td>
|
||
<td>${a.price ? a.price.toFixed(2) + ' €' : '—'}</td>
|
||
<td>
|
||
<div class="action-buttons">
|
||
<button class="edit-btn" onclick="openEditModal('${a.nr}')">✎ Bearbeiten</button>
|
||
<button class="delete-btn" onclick="deleteArticle('${a.nr}')">✕ Löschen</button>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
`).join('');
|
||
}
|
||
|
||
function clearArticleForm() {
|
||
document.getElementById('artNr').value = '';
|
||
document.getElementById('artName').value = '';
|
||
document.getElementById('artThickness').value = '';
|
||
document.getElementById('artAreaWeight').value = '';
|
||
document.getElementById('artWeightPerRoll').value = '';
|
||
document.getElementById('artWidth').value = '';
|
||
document.getElementById('artPrice').value = '';
|
||
document.getElementById('artType').value = '';
|
||
document.getElementById('artColor').value = '';
|
||
document.getElementById('artDescription').value = '';
|
||
}
|
||
|
||
function loadConfigJson() {
|
||
document.getElementById('configJson').value = JSON.stringify(configData, null, 2);
|
||
}
|
||
|
||
function saveConfigJson() {
|
||
try {
|
||
configData = JSON.parse(document.getElementById('configJson').value);
|
||
showAlert('configAlert', '✅ Konfiguration aktualisiert!', 'success');
|
||
} catch (e) {
|
||
showAlert('configAlert', '❌ Ungültiges JSON: ' + e.message, 'error', 0);
|
||
}
|
||
}
|
||
|
||
function resetConfigJson() {
|
||
loadConfigJson();
|
||
showAlert('configAlert', '🔄 Zurückgesetzt', 'info');
|
||
}
|
||
|
||
function downloadFile(data, filename) {
|
||
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
|
||
const url = URL.createObjectURL(blob);
|
||
const a = document.createElement('a');
|
||
a.href = url;
|
||
a.download = filename;
|
||
a.click();
|
||
URL.revokeObjectURL(url);
|
||
}
|
||
|
||
function downloadArticleData() {
|
||
downloadFile(articlesData, 'article-data.json');
|
||
showAlert('toolsAlert', '✅ article-data.json heruntergeladen', 'success');
|
||
}
|
||
|
||
function downloadConfig() {
|
||
downloadFile(configData, 'config.json');
|
||
showAlert('toolsAlert', '✅ config.json heruntergeladen', 'success');
|
||
}
|
||
|
||
function downloadAll() {
|
||
const allData = { articles: articlesData, config: configData, exported_at: new Date().toISOString() };
|
||
downloadFile(allData, 'roll-calculator-backup-' + new Date().toISOString().slice(0,10) + '.json');
|
||
showAlert('toolsAlert', '✅ Alle Daten heruntergeladen', 'success');
|
||
}
|
||
|
||
function exportAllAsJson() {
|
||
downloadAll();
|
||
}
|
||
|
||
function handleFileUpload(event) {
|
||
const file = event.target.files[0];
|
||
if (!file) return;
|
||
|
||
const reader = new FileReader();
|
||
reader.onload = (e) => {
|
||
try {
|
||
const data = JSON.parse(e.target.result);
|
||
if (data.articles) articlesData = data.articles;
|
||
if (data.config) configData = data.config;
|
||
if (data.articleData) articlesData = data.articleData;
|
||
|
||
showAlert('toolsAlert', '✅ Datei hochgeladen und geladen!', 'success');
|
||
refreshUI();
|
||
} catch (err) {
|
||
showAlert('toolsAlert', '❌ Fehler beim Laden der Datei: ' + err.message, 'error', 0);
|
||
}
|
||
};
|
||
reader.readAsText(file);
|
||
event.target.value = '';
|
||
}
|
||
|
||
function resetAll() {
|
||
articlesData = [];
|
||
configData = {};
|
||
showAlert('toolsAlert', '🔄 Auf Standardwerte zurückgesetzt', 'success');
|
||
refreshUI();
|
||
}
|
||
</script>
|
||
|
||
</body>
|
||
</html>
|