feat: Globale Farbsteuerung & Textsteuerung im Admin Panel und dynamisches Rendering auf der Karte
This commit is contained in:
22
db.js
22
db.js
@@ -42,7 +42,7 @@ db.exec(`
|
||||
);
|
||||
`);
|
||||
|
||||
// Standard Admin Initialisieren: admin / Kathi2022!
|
||||
// Standard Admin Initialisieren
|
||||
const adminUser = db.prepare('SELECT value FROM config WHERE key = ?').get('admin_user');
|
||||
if (!adminUser) {
|
||||
const hash = bcrypt.hashSync('Kathi2022!', 10);
|
||||
@@ -51,4 +51,24 @@ if (!adminUser) {
|
||||
db.prepare('INSERT INTO config (key, value) VALUES (?, ?)').run('jwt_secret', 'secret_' + Math.random().toString(36).substring(2));
|
||||
}
|
||||
|
||||
// Standard Theme & Texte initialisieren
|
||||
const initConfig = (k, v) => {
|
||||
const existing = db.prepare('SELECT value FROM config WHERE key = ?').get(k);
|
||||
if (!existing) {
|
||||
db.prepare('INSERT INTO config (key, value) VALUES (?, ?)').run(k, v);
|
||||
}
|
||||
};
|
||||
|
||||
initConfig('site_title', 'GETRÄNKEKARTE');
|
||||
initConfig('site_subtitle', 'Frisch zubereitete Drinks, Spezialitäten & Erfrischungen');
|
||||
initConfig('site_badge', '✦ EXKLUSIVE AUSWAHL ✦');
|
||||
initConfig('site_footer', '© Bar & Lounge • Live POS Sync');
|
||||
|
||||
initConfig('theme_bg', '#090b10');
|
||||
initConfig('theme_card_bg', 'rgba(22, 27, 34, 0.75)');
|
||||
initConfig('theme_primary', '#f59e0b');
|
||||
initConfig('theme_accent', '#ec4899');
|
||||
initConfig('theme_text', '#f8fafc');
|
||||
initConfig('theme_text_muted', '#94a3b8');
|
||||
|
||||
module.exports = db;
|
||||
|
||||
@@ -75,6 +75,20 @@
|
||||
input:focus, textarea:focus {
|
||||
border-color: var(--primary);
|
||||
}
|
||||
input[type="color"] {
|
||||
height: 44px;
|
||||
padding: 4px 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.color-picker-row {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
.color-picker-row input[type="color"] {
|
||||
width: 50px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@@ -115,8 +129,13 @@
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 16px;
|
||||
}
|
||||
.grid-3 {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
gap: 16px;
|
||||
}
|
||||
@media (max-width: 640px) {
|
||||
.grid-2 { grid-template-columns: 1fr; }
|
||||
.grid-2, .grid-3 { grid-template-columns: 1fr; }
|
||||
}
|
||||
.header-bar {
|
||||
display: flex;
|
||||
@@ -244,7 +263,95 @@
|
||||
|
||||
<div id="dash-alert" class="notification"></div>
|
||||
|
||||
<!-- QR-Code Scanner / Setup -->
|
||||
<!-- 1. Globale Farbsteuerung & Design -->
|
||||
<div class="card" style="border-left: 4px solid var(--primary);">
|
||||
<h3 style="margin-bottom: 16px; color: var(--primary);">🎨 Globale Farb- & Designsteuerung</h3>
|
||||
<form id="theme-form">
|
||||
<div class="grid-3">
|
||||
<div class="form-group">
|
||||
<label>Hintergrundfarbe (Body)</label>
|
||||
<div class="color-picker-row">
|
||||
<input type="color" id="theme-bg" value="#090b10" />
|
||||
<input type="text" id="theme-bg-text" value="#090b10" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Kartenfarbe (Cards)</label>
|
||||
<div class="color-picker-row">
|
||||
<input type="color" id="theme-card-bg" value="#161b22" />
|
||||
<input type="text" id="theme-card-bg-text" value="rgba(22, 27, 34, 0.75)" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Primärfarbe (Akzente / Preise)</label>
|
||||
<div class="color-picker-row">
|
||||
<input type="color" id="theme-primary" value="#f59e0b" />
|
||||
<input type="text" id="theme-primary-text" value="#f59e0b" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid-3">
|
||||
<div class="form-group">
|
||||
<label>Glow / Sekundärfarbe</label>
|
||||
<div class="color-picker-row">
|
||||
<input type="color" id="theme-accent" value="#ec4899" />
|
||||
<input type="text" id="theme-accent-text" value="#ec4899" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Textfarbe Haupt</label>
|
||||
<div class="color-picker-row">
|
||||
<input type="color" id="theme-text" value="#f8fafc" />
|
||||
<input type="text" id="theme-text-text" value="#f8fafc" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Textfarbe Gedämpft</label>
|
||||
<div class="color-picker-row">
|
||||
<input type="color" id="theme-text-muted" value="#94a3b8" />
|
||||
<input type="text" id="theme-text-muted-text" value="#94a3b8" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 8px;">
|
||||
<button type="submit" class="btn">🎨 Farben speichern</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- 2. Globale Text- & Titelsteuerung -->
|
||||
<div class="card" style="border-left: 4px solid var(--accent);">
|
||||
<h3 style="margin-bottom: 16px; color: var(--accent);">✍️ Globale Titel & Texte</h3>
|
||||
<form id="texts-form">
|
||||
<div class="grid-2">
|
||||
<div class="form-group">
|
||||
<label>Haupttitel (z.B. GETRÄNKEKARTE)</label>
|
||||
<input type="text" id="site-title" placeholder="GETRÄNKEKARTE" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Badge / Oberzeile (z.B. EXKLUSIVE AUSWAHL)</label>
|
||||
<input type="text" id="site-badge" placeholder="✦ EXKLUSIVE AUSWAHL ✦" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid-2">
|
||||
<div class="form-group">
|
||||
<label>Untertitel / Beschreibung</label>
|
||||
<input type="text" id="site-subtitle" placeholder="Frisch zubereitete Drinks..." />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Footer Text</label>
|
||||
<input type="text" id="site-footer" placeholder="© Bar & Lounge • Live POS Sync" />
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-top: 8px;">
|
||||
<button type="submit" class="btn btn-sync">✍️ Texte speichern</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- 3. QR-Code Scanner / Setup -->
|
||||
<div class="card" style="border-left: 4px solid #8b5cf6;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; flex-wrap: wrap; gap: 10px;">
|
||||
<h3 style="color: #a78bfa;">📷 QR-Code Setup (Kamera / Bild / JSON)</h3>
|
||||
@@ -280,7 +387,7 @@
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- POS Cloud Konfiguration -->
|
||||
<!-- 4. POS Cloud Konfiguration -->
|
||||
<div class="card">
|
||||
<h3 style="margin-bottom: 16px; color: var(--primary);">⚙️ Aktive POS Cloud Verbindungsparameter</h3>
|
||||
<form id="config-form">
|
||||
@@ -320,7 +427,7 @@
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Gespeicherte Artikel & Preise -->
|
||||
<!-- 5. Gespeicherte Artikel & Preise -->
|
||||
<div class="card">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<h3 style="color: var(--primary);">📋 Importierte Artikel (Live aus DB)</h3>
|
||||
@@ -345,7 +452,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sync Logs -->
|
||||
<!-- 6. Sync Logs -->
|
||||
<div class="card">
|
||||
<h3 style="margin-bottom: 12px; color: var(--text-muted);">📜 Sync Protokoll</h3>
|
||||
<table id="logs-table">
|
||||
@@ -433,7 +540,7 @@
|
||||
showLogin();
|
||||
});
|
||||
|
||||
// Config laden & speichern
|
||||
// Config laden
|
||||
async function loadConfig() {
|
||||
const res = await fetch('/api/admin/config');
|
||||
if (res.status === 401) return showLogin();
|
||||
@@ -443,6 +550,95 @@
|
||||
document.getElementById('cfg-app-id').value = cfg.pos_app_id || '';
|
||||
document.getElementById('cfg-pass-id').value = cfg.pos_pass_id || '';
|
||||
document.getElementById('cfg-register').value = cfg.pos_cash_register || '';
|
||||
|
||||
// Texte
|
||||
document.getElementById('site-title').value = cfg.site_title || 'GETRÄNKEKARTE';
|
||||
document.getElementById('site-subtitle').value = cfg.site_subtitle || '';
|
||||
document.getElementById('site-badge').value = cfg.site_badge || '';
|
||||
document.getElementById('site-footer').value = cfg.site_footer || '';
|
||||
|
||||
// Theme
|
||||
syncColorField('theme-bg', cfg.theme_bg || '#090b10');
|
||||
syncColorField('theme-card-bg', cfg.theme_card_bg || 'rgba(22, 27, 34, 0.75)');
|
||||
syncColorField('theme-primary', cfg.theme_primary || '#f59e0b');
|
||||
syncColorField('theme-accent', cfg.theme_accent || '#ec4899');
|
||||
syncColorField('theme-text', cfg.theme_text || '#f8fafc');
|
||||
syncColorField('theme-text-muted', cfg.theme_text_muted || '#94a3b8');
|
||||
}
|
||||
}
|
||||
|
||||
function syncColorField(fieldId, val) {
|
||||
const input = document.getElementById(fieldId);
|
||||
const text = document.getElementById(fieldId + '-text');
|
||||
if (text) text.value = val;
|
||||
if (input && val.startsWith('#') && val.length === 7) {
|
||||
input.value = val;
|
||||
}
|
||||
}
|
||||
|
||||
function setupColorSync(fieldId) {
|
||||
const picker = document.getElementById(fieldId);
|
||||
const text = document.getElementById(fieldId + '-text');
|
||||
picker.addEventListener('input', () => { text.value = picker.value; });
|
||||
text.addEventListener('input', () => {
|
||||
if (text.value.startsWith('#') && text.value.length === 7) picker.value = text.value;
|
||||
});
|
||||
}
|
||||
|
||||
['theme-bg', 'theme-card-bg', 'theme-primary', 'theme-accent', 'theme-text', 'theme-text-muted'].forEach(setupColorSync);
|
||||
|
||||
// Theme Speichern
|
||||
document.getElementById('theme-form').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const body = {
|
||||
theme_bg: document.getElementById('theme-bg-text').value,
|
||||
theme_card_bg: document.getElementById('theme-card-bg-text').value,
|
||||
theme_primary: document.getElementById('theme-primary-text').value,
|
||||
theme_accent: document.getElementById('theme-accent-text').value,
|
||||
theme_text: document.getElementById('theme-text-text').value,
|
||||
theme_text_muted: document.getElementById('theme-text-muted-text').value
|
||||
};
|
||||
await saveSettings(body, 'Farbdesign erfolgreich gespeichert!');
|
||||
});
|
||||
|
||||
// Texte Speichern
|
||||
document.getElementById('texts-form').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const body = {
|
||||
site_title: document.getElementById('site-title').value,
|
||||
site_subtitle: document.getElementById('site-subtitle').value,
|
||||
site_badge: document.getElementById('site-badge').value,
|
||||
site_footer: document.getElementById('site-footer').value
|
||||
};
|
||||
await saveSettings(body, 'Texte & Titel erfolgreich gespeichert!');
|
||||
});
|
||||
|
||||
// POS Config Speichern
|
||||
document.getElementById('config-form').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const body = {
|
||||
pos_url: document.getElementById('cfg-url').value,
|
||||
pos_app_id: document.getElementById('cfg-app-id').value,
|
||||
pos_pass_id: document.getElementById('cfg-pass-id').value,
|
||||
pos_cash_register: document.getElementById('cfg-register').value,
|
||||
new_admin_password: document.getElementById('cfg-admin-pass').value
|
||||
};
|
||||
await saveSettings(body, 'POS-Parameter erfolgreich gespeichert!');
|
||||
document.getElementById('cfg-admin-pass').value = '';
|
||||
});
|
||||
|
||||
async function saveSettings(body, successMsg) {
|
||||
const res = await fetch('/api/admin/config', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
if (res.status === 401) return showLogin();
|
||||
const data = await res.json();
|
||||
if (res.ok) {
|
||||
showAlert('dash-alert', successMsg);
|
||||
} else {
|
||||
showAlert('dash-alert', data.error || 'Fehler beim Speichern', true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -494,7 +690,6 @@
|
||||
});
|
||||
|
||||
if (code && code.data) {
|
||||
console.log('QR Code erkannt:', code.data);
|
||||
document.getElementById('qr-json').value = code.data;
|
||||
stopCamera();
|
||||
submitQrPayload(code.data);
|
||||
@@ -580,31 +775,6 @@
|
||||
submitQrPayload(qrContent);
|
||||
});
|
||||
|
||||
// Manuelle Config
|
||||
document.getElementById('config-form').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const body = {
|
||||
pos_url: document.getElementById('cfg-url').value,
|
||||
pos_app_id: document.getElementById('cfg-app-id').value,
|
||||
pos_pass_id: document.getElementById('cfg-pass-id').value,
|
||||
pos_cash_register: document.getElementById('cfg-register').value,
|
||||
new_admin_password: document.getElementById('cfg-admin-pass').value
|
||||
};
|
||||
const res = await fetch('/api/admin/config', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
if (res.status === 401) return showLogin();
|
||||
const data = await res.json();
|
||||
if (res.ok) {
|
||||
showAlert('dash-alert', 'Einstellungen erfolgreich gespeichert!');
|
||||
document.getElementById('cfg-admin-pass').value = '';
|
||||
} else {
|
||||
showAlert('dash-alert', data.error || 'Fehler', true);
|
||||
}
|
||||
});
|
||||
|
||||
// POS Sync Button
|
||||
async function triggerSync(type) {
|
||||
showAlert('dash-alert', `Synchronisiere mit POS Cloud (${type})...`);
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>GETRÄNKEKARTE</title>
|
||||
<title id="html-title">GETRÄNKEKARTE</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&family=Space+Grotesk:wght@500;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
<style id="dynamic-theme">
|
||||
:root {
|
||||
--bg: #090b10;
|
||||
--card-bg: rgba(22, 27, 34, 0.75);
|
||||
@@ -20,7 +20,8 @@
|
||||
--text-muted: #94a3b8;
|
||||
--tag-bg: rgba(245, 158, 11, 0.12);
|
||||
}
|
||||
|
||||
</style>
|
||||
<style>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
@@ -38,6 +39,7 @@
|
||||
padding: 20px 16px 40px;
|
||||
position: relative;
|
||||
overflow-x: hidden;
|
||||
transition: background-color 0.3s ease, color 0.3s ease;
|
||||
}
|
||||
|
||||
/* Ambient Glow */
|
||||
@@ -92,7 +94,7 @@
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.5px;
|
||||
background: linear-gradient(135deg, #fff 30%, #cbd5e1 100%);
|
||||
background: linear-gradient(135deg, var(--text) 30%, var(--text-muted) 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
margin-bottom: 8px;
|
||||
@@ -113,10 +115,10 @@
|
||||
.search-input {
|
||||
width: 100%;
|
||||
padding: 14px 42px 14px 44px;
|
||||
background: rgba(22, 27, 34, 0.85);
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--card-border);
|
||||
border-radius: 14px;
|
||||
color: #fff;
|
||||
color: var(--text);
|
||||
font-size: 14px;
|
||||
outline: none;
|
||||
backdrop-filter: blur(16px);
|
||||
@@ -126,7 +128,7 @@
|
||||
|
||||
.search-input:focus {
|
||||
border-color: var(--primary);
|
||||
box-shadow: 0 0 16px rgba(245, 158, 11, 0.2);
|
||||
box-shadow: 0 0 16px var(--primary-glow);
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
@@ -164,12 +166,12 @@
|
||||
gap: 8px;
|
||||
overflow-x: auto;
|
||||
padding-bottom: 4px;
|
||||
scrollbar-width: none; /* Firefox */
|
||||
scrollbar-width: none;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.category-swipe::-webkit-scrollbar {
|
||||
display: none; /* Chrome, Safari */
|
||||
display: none;
|
||||
}
|
||||
|
||||
.category-pill {
|
||||
@@ -188,13 +190,13 @@
|
||||
}
|
||||
|
||||
.category-pill:hover {
|
||||
color: #fff;
|
||||
color: var(--text);
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.category-pill.active {
|
||||
background: var(--primary);
|
||||
color: #090b10;
|
||||
color: var(--bg);
|
||||
border-color: var(--primary);
|
||||
box-shadow: 0 0 12px var(--primary-glow);
|
||||
}
|
||||
@@ -250,7 +252,7 @@
|
||||
.card-name {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.card-price {
|
||||
@@ -266,13 +268,13 @@
|
||||
|
||||
.card-ingredients {
|
||||
font-size: 12.5px;
|
||||
color: #cbd5e1;
|
||||
color: var(--text-muted);
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.highlight {
|
||||
background: rgba(245, 158, 11, 0.3);
|
||||
color: #fff;
|
||||
color: var(--text);
|
||||
padding: 0 2px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
@@ -306,9 +308,9 @@
|
||||
<div class="container">
|
||||
|
||||
<header>
|
||||
<div class="badge">✦ EXKLUSIVE AUSWAHL ✦</div>
|
||||
<h1>GETRÄNKEKARTE</h1>
|
||||
<p>Frisch zubereitete Drinks, Spezialitäten & Erfrischungen</p>
|
||||
<div id="site-badge" class="badge">✦ EXKLUSIVE AUSWAHL ✦</div>
|
||||
<h1 id="site-title">GETRÄNKEKARTE</h1>
|
||||
<p id="site-subtitle">Frisch zubereitete Drinks, Spezialitäten & Erfrischungen</p>
|
||||
</header>
|
||||
|
||||
<!-- Volltextsuche -->
|
||||
@@ -331,7 +333,7 @@
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<p>© Bar & Lounge • Live POS Sync</p>
|
||||
<p id="site-footer">© Bar & Lounge • Live POS Sync</p>
|
||||
<p style="margin-top: 6px;"><a href="/admin">⚙️ Admin Bereich</a></p>
|
||||
</footer>
|
||||
|
||||
@@ -349,6 +351,11 @@
|
||||
const data = await res.json();
|
||||
allCategories = data.categories || [];
|
||||
|
||||
// Globale Texte & Theme anwenden
|
||||
if (data.settings) {
|
||||
applySettings(data.settings);
|
||||
}
|
||||
|
||||
renderCategoryPills();
|
||||
renderMenu();
|
||||
} catch (err) {
|
||||
@@ -356,6 +363,31 @@
|
||||
}
|
||||
}
|
||||
|
||||
function applySettings(s) {
|
||||
if (s.site_title) {
|
||||
document.getElementById('site-title').textContent = s.site_title;
|
||||
document.getElementById('html-title').textContent = s.site_title;
|
||||
}
|
||||
if (s.site_subtitle) document.getElementById('site-subtitle').textContent = s.site_subtitle;
|
||||
if (s.site_badge) document.getElementById('site-badge').textContent = s.site_badge;
|
||||
if (s.site_footer) document.getElementById('site-footer').textContent = s.site_footer;
|
||||
|
||||
// Theme CSS Variablen dynamisch setzen
|
||||
const root = document.documentElement;
|
||||
if (s.theme_bg) root.style.setProperty('--bg', s.theme_bg);
|
||||
if (s.theme_card_bg) root.style.setProperty('--card-bg', s.theme_card_bg);
|
||||
if (s.theme_primary) {
|
||||
root.style.setProperty('--primary', s.theme_primary);
|
||||
root.style.setProperty('--primary-glow', s.theme_primary + '40');
|
||||
}
|
||||
if (s.theme_accent) {
|
||||
root.style.setProperty('--accent', s.theme_accent);
|
||||
root.style.setProperty('--accent-glow', s.theme_accent + '40');
|
||||
}
|
||||
if (s.theme_text) root.style.setProperty('--text', s.theme_text);
|
||||
if (s.theme_text_muted) root.style.setProperty('--text-muted', s.theme_text_muted);
|
||||
}
|
||||
|
||||
function renderCategoryPills() {
|
||||
const swipeEl = document.getElementById('category-swipe');
|
||||
let pillsHtml = `<button class="category-pill ${activeCategoryId === 'all' ? 'active' : ''}" data-id="all">Alle Artikel</button>`;
|
||||
@@ -382,12 +414,10 @@
|
||||
const query = searchQuery.trim().toLowerCase();
|
||||
|
||||
let filteredCategories = allCategories.map(cat => {
|
||||
// Kategorie Filter
|
||||
if (activeCategoryId !== 'all' && cat.id !== activeCategoryId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Suche Filter
|
||||
const matchingItems = cat.items.filter(item => {
|
||||
if (!query) return true;
|
||||
const t1 = (item.text1 || '').toLowerCase();
|
||||
@@ -481,4 +511,4 @@
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
39
server.js
39
server.js
@@ -42,11 +42,20 @@ function authMiddleware(req, res, next) {
|
||||
|
||||
// ==================== Öffentliche API ====================
|
||||
|
||||
// 1. Produkte & Gruppen für die Speisekarte
|
||||
// 1. Produkte, Gruppen & Theme Settings für die Speisekarte
|
||||
app.get('/api/menu', (req, res) => {
|
||||
const groups = db.prepare('SELECT * FROM product_groups ORDER BY sort_order ASC, name ASC').all();
|
||||
const products = db.prepare('SELECT id, text1, text2, group_id, preis1, unit FROM products WHERE active = 1 ORDER BY sort_order ASC, text1 ASC').all();
|
||||
|
||||
// Globale Einstellungen laden
|
||||
const configRows = db.prepare('SELECT key, value FROM config').all();
|
||||
const settings = {};
|
||||
for (const r of configRows) {
|
||||
if (r.key.startsWith('site_') || r.key.startsWith('theme_')) {
|
||||
settings[r.key] = r.value;
|
||||
}
|
||||
}
|
||||
|
||||
const groupMap = {};
|
||||
for (const g of groups) {
|
||||
groupMap[g.id] = {
|
||||
@@ -74,7 +83,7 @@ app.get('/api/menu', (req, res) => {
|
||||
});
|
||||
}
|
||||
|
||||
res.json({ categories: result, allProducts: products });
|
||||
res.json({ categories: result, allProducts: products, settings });
|
||||
});
|
||||
|
||||
// ==================== Admin Auth ====================
|
||||
@@ -108,7 +117,7 @@ app.get('/api/admin/check', authMiddleware, (req, res) => {
|
||||
res.json({ authenticated: true, user: req.user.user });
|
||||
});
|
||||
|
||||
// ==================== Admin POS Settings, QR Setup & Sync ====================
|
||||
// ==================== Admin POS Settings, Theme & Sync ====================
|
||||
|
||||
// 5. Config abrufen
|
||||
app.get('/api/admin/config', authMiddleware, (req, res) => {
|
||||
@@ -118,23 +127,23 @@ app.get('/api/admin/config', authMiddleware, (req, res) => {
|
||||
res.json(config);
|
||||
});
|
||||
|
||||
// 6. Config manuell speichern
|
||||
// 6. Config / Theme / Texte speichern
|
||||
app.post('/api/admin/config', authMiddleware, (req, res) => {
|
||||
const { pos_url, pos_pass_id, pos_app_id, pos_cash_register, new_admin_password } = req.body;
|
||||
|
||||
const updates = req.body;
|
||||
const upsert = db.prepare('INSERT INTO config (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value');
|
||||
|
||||
if (pos_url !== undefined) upsert.run('pos_url', pos_url);
|
||||
if (pos_pass_id !== undefined) upsert.run('pos_pass_id', pos_pass_id);
|
||||
if (pos_app_id !== undefined) upsert.run('pos_app_id', pos_app_id);
|
||||
if (pos_cash_register !== undefined) upsert.run('pos_cash_register', pos_cash_register);
|
||||
|
||||
if (new_admin_password && new_admin_password.trim().length > 0) {
|
||||
const newHash = bcrypt.hashSync(new_admin_password.trim(), 10);
|
||||
upsert.run('admin_password_hash', newHash);
|
||||
for (const [key, val] of Object.entries(updates)) {
|
||||
if (key === 'new_admin_password') {
|
||||
if (val && val.trim().length > 0) {
|
||||
const newHash = bcrypt.hashSync(val.trim(), 10);
|
||||
upsert.run('admin_password_hash', newHash);
|
||||
}
|
||||
} else if (val !== undefined && val !== null) {
|
||||
upsert.run(key, String(val));
|
||||
}
|
||||
}
|
||||
|
||||
res.json({ success: true, message: 'Einstellungen gespeichert' });
|
||||
res.json({ success: true, message: 'Einstellungen erfolgreich gespeichert' });
|
||||
});
|
||||
|
||||
// 7. QR-Code Setup Handshake
|
||||
|
||||
Reference in New Issue
Block a user