🎛️ Admin: Pill-Buttons + Schnellfilter | 💬 Chat: TTS-Speaker-Button (Web Speech API)
This commit is contained in:
@@ -881,7 +881,13 @@ def admin_dashboard():
|
|||||||
body.light .admin-header {{ background:rgba(242,242,247,0.72); }}
|
body.light .admin-header {{ background:rgba(242,242,247,0.72); }}
|
||||||
.admin-header-inner {{ max-width:1200px; margin:0 auto; display:flex; align-items:center; justify-content:space-between; flex-wrap:wrap; gap:0.5rem; }}
|
.admin-header-inner {{ max-width:1200px; margin:0 auto; display:flex; align-items:center; justify-content:space-between; flex-wrap:wrap; gap:0.5rem; }}
|
||||||
.admin-header h1 {{ font-size:1.25rem; font-weight:700; letter-spacing:-0.02em; color:var(--text); }}
|
.admin-header h1 {{ font-size:1.25rem; font-weight:700; letter-spacing:-0.02em; color:var(--text); }}
|
||||||
.admin-header .meta {{ font-size:0.75rem; color:var(--text2); display:flex; gap:0.5rem; flex-wrap:wrap; align-items:center; }}
|
.admin-header .meta {{ font-size:0.75rem; color:var(--text2); display:flex; gap:0.35rem; flex-wrap:wrap; align-items:center; }}
|
||||||
|
.count-badge {{ background:var(--surface2); color:var(--text2); padding:0.3rem 0.7rem; border-radius:999px; font-size:0.7rem; font-weight:600; white-space:nowrap; }}
|
||||||
|
.admin-btn {{ display:inline-block; padding:0.35rem 0.85rem; border-radius:999px; font-size:0.72rem; font-weight:600; text-decoration:none; transition:all 0.15s; white-space:nowrap; background:var(--surface2); color:var(--text2); border:1.5px solid transparent; }}
|
||||||
|
.admin-btn:hover {{ background:var(--surface); color:var(--text); border-color:var(--border); }}
|
||||||
|
.admin-btn.active {{ background:var(--accent); color:#fff; border-color:var(--accent); }}
|
||||||
|
.admin-btn.secondary {{ background:transparent; color:var(--text3); }}
|
||||||
|
.admin-btn.secondary:hover {{ color:var(--text); background:var(--surface2); }}
|
||||||
.admin-header a {{ color:var(--accent); text-decoration:none; font-weight:500; }}
|
.admin-header a {{ color:var(--accent); text-decoration:none; font-weight:500; }}
|
||||||
.admin-header a:hover {{ opacity:0.8; }}
|
.admin-header a:hover {{ opacity:0.8; }}
|
||||||
|
|
||||||
@@ -946,7 +952,15 @@ function toggleTheme(){{document.body.classList.toggle('light');localStorage.set
|
|||||||
<header class="admin-header">
|
<header class="admin-header">
|
||||||
<div class="admin-header-inner">
|
<div class="admin-header-inner">
|
||||||
<h1>📨 BSN Intake</h1>
|
<h1>📨 BSN Intake</h1>
|
||||||
<div class="meta">{len(rows)} Submissions | <a href="/test">🧪 Test</a> | <a href="/">🌐 Frontend</a> | <a href="/health">⚙️ Health</a></div>
|
<div class="meta">
|
||||||
|
<span class="count-badge">{len(rows)} Submissions</span>
|
||||||
|
<a href="/admin" class="admin-btn{' active' if not status_filter else ''}">📋 Alle</a>
|
||||||
|
<a href="/admin?status=new" class="admin-btn{' active' if status_filter=='new' else ''}">🆕 Neu</a>
|
||||||
|
<a href="/admin?status=published" class="admin-btn{' active' if status_filter=='published' else ''}">✅ Publiziert</a>
|
||||||
|
<a href="/admin?status=flagged" class="admin-btn{' active' if status_filter=='flagged' else ''}">🚩 Markiert</a>
|
||||||
|
<a href="/test" class="admin-btn secondary">🧪 Test</a>
|
||||||
|
<a href="/" class="admin-btn secondary">🌐 Frontend</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<main class="admin-main">
|
<main class="admin-main">
|
||||||
@@ -1826,7 +1840,10 @@ def public_frontend():
|
|||||||
<button id="chatClose" aria-label="Schließen">✕</button>
|
<button id="chatClose" aria-label="Schließen">✕</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="chat-body" id="chatBody">
|
<div class="chat-body" id="chatBody">
|
||||||
<div class="chat-msg assistant">Hey! 👋 Ich bin der BSN Community Assistant. Frag mich nach Spielen, Ständen oder was auf der SPIEL Essen los ist!</div>
|
<div style="display:flex;align-items:flex-end;gap:0.3rem;align-self:flex-start;">
|
||||||
|
<div class="chat-msg assistant">Hey! 👋 Ich bin der BSN Community Assistant. Frag mich nach Spielen, Ständen oder was auf der SPIEL Essen los ist!</div>
|
||||||
|
<button onclick="(function(t){var u=new SpeechSynthesisUtterance(t);u.lang='de-DE';u.rate=0.95;var v=speechSynthesis.getVoices();var d=v.find(function(x){return x.lang.startsWith('de')});if(d)u.voice=d;speechSynthesis.cancel();speechSynthesis.speak(u)})('Hey! Ich bin der BSN Community Assistant. Frag mich nach Spielen, Ständen oder was auf der SPIEL Essen los ist!')" title="Vorlesen" style="background:none;border:none;cursor:pointer;font-size:0.85rem;opacity:0.5;padding:0 0 0.15rem 0;transition:opacity 0.15s;flex-shrink:0;" onmouseenter="this.style.opacity='1'" onmouseleave="this.style.opacity='0.5'">🔊</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="chat-footer">
|
<div class="chat-footer">
|
||||||
<input type="text" id="chatInput" placeholder="Frag was..." aria-label="Chat-Nachricht">
|
<input type="text" id="chatInput" placeholder="Frag was..." aria-label="Chat-Nachricht">
|
||||||
@@ -1855,14 +1872,50 @@ def public_frontend():
|
|||||||
closeBtn.addEventListener('click', () => { open = false; overlay.classList.remove('open'); });
|
closeBtn.addEventListener('click', () => { open = false; overlay.classList.remove('open'); });
|
||||||
|
|
||||||
function addMsg(role, text) {
|
function addMsg(role, text) {
|
||||||
|
const wrapper = document.createElement('div');
|
||||||
|
wrapper.style.display = 'flex';
|
||||||
|
wrapper.style.alignItems = 'flex-end';
|
||||||
|
wrapper.style.gap = '0.3rem';
|
||||||
|
if (role === 'assistant') wrapper.style.alignSelf = 'flex-start';
|
||||||
|
else wrapper.style.alignSelf = 'flex-end';
|
||||||
|
|
||||||
const div = document.createElement('div');
|
const div = document.createElement('div');
|
||||||
div.className = 'chat-msg ' + role;
|
div.className = 'chat-msg ' + role;
|
||||||
div.textContent = text;
|
div.textContent = text;
|
||||||
body.appendChild(div);
|
wrapper.appendChild(div);
|
||||||
|
|
||||||
|
if (role === 'assistant') {
|
||||||
|
const btn = document.createElement('button');
|
||||||
|
btn.innerHTML = '🔊';
|
||||||
|
btn.title = 'Vorlesen';
|
||||||
|
btn.style.cssText = 'background:none;border:none;cursor:pointer;font-size:0.85rem;opacity:0.5;padding:0 0 0.15rem 0;transition:opacity 0.15s;flex-shrink:0;';
|
||||||
|
btn.onmouseenter = () => btn.style.opacity = '1';
|
||||||
|
btn.onmouseleave = () => btn.style.opacity = '0.5';
|
||||||
|
btn.onclick = () => speak(text);
|
||||||
|
wrapper.appendChild(btn);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.appendChild(wrapper);
|
||||||
body.scrollTop = body.scrollHeight;
|
body.scrollTop = body.scrollHeight;
|
||||||
return div;
|
return div;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function speak(text) {
|
||||||
|
if (!window.speechSynthesis) return;
|
||||||
|
window.speechSynthesis.cancel();
|
||||||
|
const u = new SpeechSynthesisUtterance(text);
|
||||||
|
u.lang = 'de-DE';
|
||||||
|
u.rate = 0.95;
|
||||||
|
// Prefer German voice
|
||||||
|
const voices = speechSynthesis.getVoices();
|
||||||
|
const de = voices.find(v => v.lang.startsWith('de'));
|
||||||
|
if (de) u.voice = de;
|
||||||
|
speechSynthesis.speak(u);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Preload voices
|
||||||
|
if (window.speechSynthesis) speechSynthesis.getVoices();
|
||||||
|
|
||||||
function addTyping() {
|
function addTyping() {
|
||||||
const div = document.createElement('div');
|
const div = document.createElement('div');
|
||||||
div.className = 'chat-msg assistant typing';
|
div.className = 'chat-msg assistant typing';
|
||||||
|
|||||||
Reference in New Issue
Block a user