Disclaimer re-added, added several documentation files for Codex

This commit is contained in:
2026-07-03 09:48:01 +02:00
parent c13e19dd13
commit d6d441fa69
12 changed files with 207 additions and 1650 deletions
+6
View File
@@ -0,0 +1,6 @@
- Vor größeren Änderungen immer einen Plan erstellen.
- Keine neuen Python-Pakete ohne Zustimmung.
- Bestehende APIs nicht ohne Rückfrage ändern.
- Python-Code nach PEP8.
- JavaScript möglichst modern (ES6).
- Kommentare sparsam verwenden.
+26
View File
@@ -0,0 +1,26 @@
## Rollengeometrie
Die Berechnungen basieren auf einer ideal zylindrischen Wicklung.
Annahmen:
- konstante Materialdicke
- keine Kompression
- keine Luftzwischenräume
Abweichungen:
- Bei Artikeln der Gruppe Secugrid, Combigrid, Carbofol MF/MF, Carbofol F/F, Carbofol s/F, Carbofol BF/s darf die Dicke aus der articel-data.json nicht verwendet werden
## Artikeldaten
Die JSON-Datei wird automatisch aus dem ERP erzeugt.
Felder:
- Artikelnummer
- Dicke
- Flächengewicht
- Standardabweichung
- Kern
Diese Werte dürfen niemals manuell verändert werden.
View File
+13
View File
@@ -0,0 +1,13 @@
## Version 0.3
- Mehrsprachigkeit
- Filter der Artikelauswahl auf Basis der gewählten Produktgruppe: Secutex, Carbofol, Bentofix, Secumat, Secudrain
- PDF-Export
## Version 0.4
- Benutzermanagement auf sichere Methode umstellen, keine Klartext Passworte mehr
- Produktionsdaten aus SQL per Query direkt beziehen
## Version 0.5
-
+35
View File
@@ -537,5 +537,40 @@
"endpoint": "/", "endpoint": "/",
"method": "GET", "method": "GET",
"status": 200 "status": 200
},
{
"timestamp": "2026-07-02T16:07:56.334163",
"username": "mtazl",
"endpoint": "/",
"method": "GET",
"status": 200
},
{
"timestamp": "2026-07-02T16:08:38.097844",
"username": "mtazl",
"endpoint": "/",
"method": "GET",
"status": 200
},
{
"timestamp": "2026-07-03T08:39:44.284544",
"username": "mtazl",
"endpoint": "/",
"method": "GET",
"status": 200
},
{
"timestamp": "2026-07-03T08:41:57.673023",
"username": "mtazl",
"endpoint": "/",
"method": "GET",
"status": 200
},
{
"timestamp": "2026-07-03T08:42:19.877155",
"username": "mtazl",
"endpoint": "/",
"method": "GET",
"status": 200
} }
] ]
-849
View File
@@ -1,849 +0,0 @@
<!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>
-694
View File
@@ -1,694 +0,0 @@
<!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 - Einfach</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 {
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 {
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;
}
</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>
<!-- 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, 5,00 x 40 m">
</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²) - Optional</label>
<input type="number" id="artAreaWeight" placeholder="z.B. 5300" step="1" min="0">
</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>Area Weight</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody id="articleTableBody">
<tr><td colspan="5" 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>
// ═════════════════════════════════════════════════════════════════════════════
// CONFIG
// ═════════════════════════════════════════════════════════════════════════════
const ADMIN_PASSWORD = 'admin123'; // ÄNDERN!
// Beispiel article-data.json (wird von Server geladen)
let articlesData = [];
// Beispiel config.json (wird von Server geladen)
let configData = {};
// ═════════════════════════════════════════════════════════════════════════════
// INIT
// ═════════════════════════════════════════════════════════════════════════════
document.addEventListener('DOMContentLoaded', () => {
loadDataFromServer();
document.getElementById('passwordInput').addEventListener('keypress', (e) => {
if (e.key === 'Enter') checkPassword();
});
});
function loadDataFromServer() {
// Versuche, Daten vom Server zu laden
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, config vorhanden');
});
}
// ═════════════════════════════════════════════════════════════════════════════
// AUTH
// ═════════════════════════════════════════════════════════════════════════════
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();
}
// ═════════════════════════════════════════════════════════════════════════════
// UI
// ═════════════════════════════════════════════════════════════════════════════
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;
}
// ═════════════════════════════════════════════════════════════════════════════
// ARTICLES
// ═════════════════════════════════════════════════════════════════════════════
function addArticle() {
const nr = document.getElementById('artNr').value.trim();
const name = document.getElementById('artName').value.trim();
const thickness = parseFloat(document.getElementById('artThickness').value) || 0;
const area_weight = parseFloat(document.getElementById('artAreaWeight').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;
}
articlesData.push({ nr, name, thickness, area_weight });
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="5" 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.area_weight ? a.area_weight : '—'}</td>
<td>
<div class="action-buttons">
<button class="edit-btn" onclick="editArticle('${a.nr}')">✎ Bearbeiten</button>
<button class="delete-btn" onclick="deleteArticle('${a.nr}')">✕ Löschen</button>
</div>
</td>
</tr>
`).join('');
}
function editArticle(nr) {
const article = articlesData.find(a => a.nr === nr);
if (!article) return;
const newThickness = prompt('Produktdicke (mm):', article.thickness);
if (newThickness !== null) {
article.thickness = parseFloat(newThickness) || 0;
showAlert('articleAlert', '✅ Aktualisiert!', 'success');
refreshArticleTable();
}
}
function clearArticleForm() {
document.getElementById('artNr').value = '';
document.getElementById('artName').value = '';
document.getElementById('artThickness').value = '';
document.getElementById('artAreaWeight').value = '';
}
// ═════════════════════════════════════════════════════════════════════════════
// CONFIG
// ═════════════════════════════════════════════════════════════════════════════
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');
}
// ═════════════════════════════════════════════════════════════════════════════
// DOWNLOADS / UPLOADS
// ═════════════════════════════════════════════════════════════════════════════
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; // alternate key
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 = ''; // Reset file input
}
function resetAll() {
articlesData = [];
configData = {};
showAlert('toolsAlert', '🔄 Auf Standardwerte zurückgesetzt', 'success');
refreshUI();
}
</script>
</body>
</html>
View File
View File
View File
-107
View File
@@ -1,107 +0,0 @@
#!/usr/bin/env python3
"""
Automatischer Fix für article-data.json Fetch-URLs in Flask
Ersetzt relative URLs durch /static/ URLs
"""
import re
import os
from pathlib import Path
def fix_html_file(html_path):
"""Ersetzt die alten Fetch-URLs durch die neuen Flask-URLs"""
if not os.path.exists(html_path):
print(f"❌ Datei nicht gefunden: {html_path}")
return False
print(f"📖 Öffne: {html_path}")
with open(html_path, 'r', encoding='utf-8') as f:
content = f.read()
original_content = content
changes = []
# Fix 1: fetch('article-data.json') → fetch('/static/article-data.json')
if "fetch('article-data.json')" in content or 'fetch("article-data.json")' in content:
content = content.replace("fetch('article-data.json')", "fetch('/static/article-data.json')")
content = content.replace('fetch("article-data.json")', 'fetch("/static/article-data.json")')
changes.append("✓ article-data.json URL aktualisiert")
# Fix 2: fetch('config.json') → fetch('/static/config.json')
if "fetch('config.json')" in content or 'fetch("config.json")' in content:
content = content.replace("fetch('config.json')", "fetch('/static/config.json')")
content = content.replace('fetch("config.json")', 'fetch("/static/config.json")')
changes.append("✓ config.json URL aktualisiert")
# Fix 3: register('service-worker.js') → register('/static/service-worker.js')
if "register('service-worker.js')" in content or 'register("service-worker.js")' in content:
content = content.replace("register('service-worker.js')", "register('/static/service-worker.js')")
content = content.replace('register("service-worker.js")', 'register("/static/service-worker.js")')
changes.append("✓ service-worker.js URL aktualisiert")
if content != original_content:
# Backup erstellen
backup_path = html_path + ".backup"
with open(backup_path, 'w', encoding='utf-8') as f:
f.write(original_content)
print(f"💾 Backup erstellt: {backup_path}")
# Neue Version speichern
with open(html_path, 'w', encoding='utf-8') as f:
f.write(content)
print(f"✅ HTML aktualisiert: {html_path}")
for change in changes:
print(f" {change}")
return True
else:
print("⚠️ Keine Änderungen notwendig (URLs sind bereits korrekt)")
return False
def check_static_files():
"""Prüft ob die benötigten Dateien im static/ Ordner sind"""
print("\n🔍 Prüfe static/ Verzeichnis...")
files_to_check = ['article-data.json', 'config.json']
found = []
missing = []
for filename in files_to_check:
path = os.path.join('static', filename)
if os.path.exists(path):
size = os.path.getsize(path)
found.append(f" ✓ {filename} ({size} bytes)")
else:
missing.append(f" ❌ {filename} (NICHT GEFUNDEN)")
for msg in found:
print(msg)
for msg in missing:
print(msg)
return len(missing) == 0
if __name__ == "__main__":
print("=" * 60)
print("Fix für article-data.json Flask-URLs")
print("=" * 60)
html_file = "templates/roll_calculator.html"
success = fix_html_file(html_file)
files_ok = check_static_files()
print("\n" + "=" * 60)
if success or files_ok:
print("✅ Fix abgeschlossen!")
print("\n📝 Nächste Schritte:")
print(" 1. Browser neuladen (Ctrl+Shift+R)")
print(" 2. Öffne Browser Console (F12)")
print(" 3. Du solltest sehen:")
print(" 'Article data loaded: XXX items'")
print(" 'Config updated from file'")
else:
print("⚠️ Keine Änderungen nötig")
print("=" * 60)
+127
View File
@@ -60,6 +60,85 @@
.fc-req-nok { color: #dc3545; font-weight: 600; } .fc-req-nok { color: #dc3545; font-weight: 600; }
.formula-box { background: #f8f9fa; border: 1px solid #e0e0e0; border-radius: 6px; padding: 10px 14px; font-size: 12px; color: #666; margin-top: 18px; font-family: 'Courier New', monospace; } .formula-box { background: #f8f9fa; border: 1px solid #e0e0e0; border-radius: 6px; padding: 10px 14px; font-size: 12px; color: #666; margin-top: 18px; font-family: 'Courier New', monospace; }
.footer { text-align: center; font-size: 11px; color: #aaa; padding: 18px; margin-top: 30px; } .footer { text-align: center; font-size: 11px; color: #aaa; padding: 18px; margin-top: 30px; }
.disclaimer-overlay {
position: fixed;
inset: 0;
z-index: 9999;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
background: rgba(20, 28, 34, 0.72);
}
.disclaimer-overlay.is-hidden { display: none; }
.disclaimer-dialog {
width: min(760px, 100%);
max-height: min(86vh, 820px);
display: flex;
flex-direction: column;
background: #fff;
border-radius: 8px;
box-shadow: 0 18px 45px rgba(0, 0, 0, 0.28);
overflow: hidden;
}
.disclaimer-header {
padding: 18px 22px;
background: #1F5438;
color: #fff;
}
.disclaimer-header h2 { font-size: 18px; margin: 0; }
.disclaimer-content {
padding: 20px 22px;
overflow-y: auto;
line-height: 1.5;
font-size: 14px;
}
.disclaimer-content h3 {
margin: 0 0 8px;
color: #1F5438;
font-size: 15px;
}
.disclaimer-content p { margin: 0 0 12px; }
.disclaimer-language + .disclaimer-language {
margin-top: 18px;
padding-top: 18px;
border-top: 1px solid #e0e8f0;
}
.disclaimer-actions {
padding: 16px 22px;
border-top: 1px solid #e0e8f0;
background: #f8fafa;
}
.disclaimer-confirm {
display: flex;
align-items: flex-start;
gap: 10px;
margin-bottom: 14px;
font-size: 13px;
color: #333;
}
.disclaimer-confirm input {
width: 18px;
height: 18px;
margin-top: 1px;
flex: 0 0 auto;
}
.disclaimer-btn {
width: 100%;
border: none;
border-radius: 6px;
padding: 11px 18px;
background: #2BAC70;
color: #fff;
font-size: 14px;
font-weight: 700;
cursor: pointer;
}
.disclaimer-btn:hover { background: #1F5438; }
.disclaimer-btn:disabled {
background: #b8c7bf;
cursor: not-allowed;
}
/* ============================================ /* ============================================
LOAD OPTIMIZER STYLES LOAD OPTIMIZER STYLES
@@ -336,6 +415,37 @@
</head> </head>
<body> <body>
<div class="disclaimer-overlay" id="disclaimerOverlay" role="dialog" aria-modal="true" aria-labelledby="disclaimerTitle">
<div class="disclaimer-dialog">
<div class="disclaimer-header">
<h2 id="disclaimerTitle">Disclaimer</h2>
</div>
<div class="disclaimer-content">
<section class="disclaimer-language" aria-label="Deutscher Disclaimer">
<h3>Deutsch</h3>
<p><strong>⚠️ Disclaimer</strong></p>
<p>Dieses Tool ist ausschließlich zur persönlichen, internen Verwendung bestimmt. Die Weitergabe von Logins ist nicht gestattet.</p>
<p>Berechtigt sind nur: Produktmanagement, Produktionsleitung, R&amp;D, Controlling und Logistik.</p>
<p>Wichtig: Alle Eingaben und Ausgaben müssen vor der Verwendung auf Plausibilität geprüft werden. Die Datenbasis für Materialdicke und Flächengewicht wird automatisch erstellt und ist weder geprüft noch erhebt sie Anspruch auf Vollständigkeit.</p>
</section>
<section class="disclaimer-language" aria-label="English disclaimer">
<h3>English</h3>
<p><strong>⚠️ Disclaimer</strong></p>
<p>This tool is exclusively for personal, internal use. Sharing login credentials is prohibited.</p>
<p>Authorized departments only: Product Management, Production Management, R&amp;D, Controlling, and Logistics.</p>
<p>Important: All inputs and outputs must be verified for plausibility before use. The material thickness and area weight data is automatically generated and has not been reviewed. It does not claim to be complete or comprehensive.</p>
</section>
</div>
<div class="disclaimer-actions">
<label class="disclaimer-confirm" for="disclaimerAccepted">
<input type="checkbox" id="disclaimerAccepted">
<span>I have read and understood the disclaimer / Ich habe den Disclaimer gelesen und verstanden.</span>
</label>
<button class="disclaimer-btn" id="disclaimerConfirmBtn" type="button" disabled>Confirm / Bestätigen</button>
</div>
</div>
</div>
<div class="header"> <div class="header">
<h1>Naue Roll Diameter Calculator</h1> <h1>Naue Roll Diameter Calculator</h1>
<p>Roll Diameter • Product Length • Roll Weight • Load Optimizer</p> <p>Roll Diameter • Product Length • Roll Weight • Load Optimizer</p>
@@ -1301,6 +1411,23 @@ class LoadOptimizerUI {
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
window.loadOptimizerUI = new LoadOptimizerUI(); window.loadOptimizerUI = new LoadOptimizerUI();
}); });
document.addEventListener('DOMContentLoaded', () => {
const overlay = document.getElementById('disclaimerOverlay');
const checkbox = document.getElementById('disclaimerAccepted');
const confirmBtn = document.getElementById('disclaimerConfirmBtn');
if (!overlay || !checkbox || !confirmBtn) return;
checkbox.addEventListener('change', () => {
confirmBtn.disabled = !checkbox.checked;
});
confirmBtn.addEventListener('click', () => {
if (!checkbox.checked) return;
overlay.classList.add('is-hidden');
});
});
</script> </script>
</body> </body>