Files
deskhub/DeskHub-Webseite/includes/footer_customer.php
T
2026-10-23 17:22:17 +00:00

553 lines
26 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
$footerPages = get_db()->query("SELECT title, slug FROM site_pages WHERE is_active=1 AND show_in_nav=1 ORDER BY sort_order, title")->fetchAll();
$privacyLabel = get_setting('footer_privacy_label', 'Datenschutz');
$privacyUrl = get_setting('footer_privacy_url', '');
$privacyHtml = get_setting('footer_privacy_html', '');
$imprintLabel = get_setting('footer_imprint_label', 'Impressum');
$imprintUrl = get_setting('footer_imprint_url', '');
$imprintHtml = get_setting('footer_imprint_html', '');
$hasPrivacy = $privacyUrl || $privacyHtml;
$hasImprint = $imprintUrl || $imprintHtml;
$hasPopup = (!$privacyUrl && $privacyHtml) || (!$imprintUrl && $imprintHtml);
?>
</div><!-- /.page-content -->
<?php if ($hasPopup): ?>
<!-- Popup-Overlay für Datenschutz / Impressum -->
<div id="legal-overlay" style="display:none; position:fixed; inset:0; background:rgba(0,0,0,.55); z-index:9999; overflow-y:auto; padding:40px 16px">
<div style="background:#fff; max-width:760px; margin:0 auto; border-radius:10px; padding:32px 36px; position:relative; font-size:.9rem; line-height:1.7">
<button onclick="closeLegalPopup()" style="position:absolute; top:14px; right:18px; background:none; border:none; font-size:1.4rem; cursor:pointer; color:#6b7280">&times;</button>
<div id="legal-popup-content"></div>
</div>
</div>
<script>
function openLegalPopup(html) {
document.getElementById('legal-popup-content').innerHTML = html;
document.getElementById('legal-overlay').style.display = 'block';
document.body.style.overflow = 'hidden';
}
function closeLegalPopup() {
document.getElementById('legal-overlay').style.display = 'none';
document.body.style.overflow = '';
}
document.getElementById('legal-overlay').addEventListener('click', function(e){
if (e.target === this) closeLegalPopup();
});
document.addEventListener('keydown', function(e){ if(e.key==='Escape') closeLegalPopup(); });
</script>
<?php endif; ?>
<footer class="public-footer">
<nav style="margin-bottom:8px; display:flex; gap:20px; justify-content:center; flex-wrap:wrap">
<?php if ($footerPages): ?>
<?php foreach ($footerPages as $fp): ?>
<a href="/page.php?slug=<?= e($fp['slug']) ?>" style="color:#6b7280; font-size:.78rem"><?= e($fp['title']) ?></a>
<?php endforeach; ?>
<?php endif; ?>
<?php if ($hasPrivacy): ?>
<?php if ($privacyUrl): ?>
<a href="<?= e($privacyUrl) ?>" target="_blank" rel="noopener" style="color:#6b7280; font-size:.78rem"><?= e($privacyLabel) ?></a>
<?php else: ?>
<a href="#" onclick="openLegalPopup(<?= htmlspecialchars(json_encode($privacyHtml), ENT_QUOTES) ?>); return false;" style="color:#6b7280; font-size:.78rem"><?= e($privacyLabel) ?></a>
<?php endif; ?>
<?php endif; ?>
<?php if ($hasImprint): ?>
<?php if ($imprintUrl): ?>
<a href="<?= e($imprintUrl) ?>" target="_blank" rel="noopener" style="color:#6b7280; font-size:.78rem"><?= e($imprintLabel) ?></a>
<?php else: ?>
<a href="#" onclick="openLegalPopup(<?= htmlspecialchars(json_encode($imprintHtml), ENT_QUOTES) ?>); return false;" style="color:#6b7280; font-size:.78rem"><?= e($imprintLabel) ?></a>
<?php endif; ?>
<?php endif; ?>
</nav>
Copyright &copy; <?= date('Y') ?> <?= e(get_setting('display_name', config()['site']['name'])) ?> &ndash; Alle Rechte vorbehalten.
</footer>
<?php
// Digitaler Assistent: nur wenn der Live-Chat ausgeschaltet oder gerade niemand erreichbar ist; dann ersetzt er das Live-Chat-Fenster
require_once __DIR__ . '/assistant.php';
$__assistantOn = false;
try { $__assistantOn = assistant_active(get_db()); } catch (Throwable $e) { /* Datenbank noch nicht aktualisiert: kein Assistent */ }
if ($__assistantOn) require __DIR__ . '/assistant_widget.php';
?>
<?php if (get_setting('live_chat_enabled', '0') === '1' && !$__assistantOn): ?>
<!-- Live-Chat-Widget: kann jederzeit unter Einstellungen → Allgemein wieder deaktiviert werden -->
<style>
@keyframes lc-pop { 0%{transform:scale(.8);opacity:0} 100%{transform:scale(1);opacity:1} }
@keyframes lc-pulse { 0%,100%{transform:scale(1)} 50%{transform:scale(1.08)} }
#lc-bubble.lc-unread{animation:lc-pulse 1.4s ease-in-out infinite}
.lc-msg-row{animation:lc-pop .18s ease-out}
</style>
<div id="lc-bubble" style="position:fixed;bottom:20px;right:20px;width:56px;height:56px;border-radius:50%;background:var(--primary);color:#fff;display:flex;align-items:center;justify-content:center;font-size:1.5rem;cursor:pointer;box-shadow:0 4px 16px rgba(0,0,0,.25);z-index:9998;user-select:none">
💬
<span id="lc-badge" style="display:none;position:absolute;top:-3px;right:-3px;background:#dc2626;color:#fff;border-radius:10px;min-width:20px;height:20px;font-size:.68rem;font-weight:700;align-items:center;justify-content:center;padding:0 5px;border:2px solid #fff"></span>
</div>
<div id="lc-panel" style="display:none;position:fixed;bottom:88px;right:20px;width:340px;max-width:calc(100vw - 32px);max-height:min(560px, calc(100vh - 120px));background:#fff;border-radius:12px;box-shadow:0 8px 32px rgba(0,0,0,.28);z-index:9998;overflow:hidden;flex-direction:column">
<div style="background:var(--primary);color:#fff;padding:14px 16px;flex-shrink:0">
<strong style="font-size:.92rem">💬 Live-Chat</strong>
<span id="lc-close" style="cursor:pointer;font-size:1.2rem;line-height:1;opacity:.85;float:right">&times;</span>
<div style="font-size:.72rem;opacity:.9;margin-top:3px;display:flex;align-items:center;gap:5px">
<span id="lc-status-dot" style="width:7px;height:7px;border-radius:50%;background:#9ca3af;display:inline-block;flex-shrink:0"></span>
<span id="lc-status-text">Verfügbarkeit wird geprüft…</span>
</div>
</div>
<div id="lc-messages" style="flex:1;overflow-y:auto;padding:12px 14px;display:none;background:#f7f8fa"></div>
<div id="lc-typing" style="display:none;padding:0 14px 8px;font-size:.76rem;color:#9ca3af;background:#f7f8fa;font-style:italic">Mitarbeiter tippt …</div>
<div id="lc-ended" style="display:none;padding:16px;font-size:.85rem;color:#6b7280;text-align:center">
Dieser Chat wurde beendet.<br><a href="#" id="lc-restart" style="color:var(--primary);font-weight:600">Neuen Chat starten</a>
</div>
<div id="lc-converted" style="display:none;padding:20px 16px;font-size:.85rem;color:#374151;text-align:center">
<div style="font-size:1.6rem;margin-bottom:8px">✅</div>
<div style="font-weight:600;margin-bottom:4px">Vielen Dank! Ihr Ticket wurde erstellt.</div>
<div id="lc-converted-number" style="color:#6b7280;margin-bottom:8px"></div>
<div style="color:#6b7280">Da gerade niemand direkt verfügbar ist, antworten wir Ihnen per E-Mail, sobald wir Ihre Anfrage bearbeitet haben.</div>
</div>
<form id="lc-start-form" style="padding:14px 16px;display:flex;flex-direction:column;gap:8px">
<p style="margin:0 0 2px;font-size:.83rem;color:#374151">👋 Hallo! Wie können wir Ihnen helfen?</p>
<div id="lc-unavailable-hint" style="display:none;background:#fffbeb;border:1px solid #fde68a;color:#92400e;font-size:.78rem;padding:8px 10px;border-radius:6px">Gerade ist niemand verfügbar – wir antworten aber, sobald wir wieder da sind.</div>
<div id="lc-error" style="display:none;color:#dc2626;font-size:.78rem"></div>
<input type="text" id="lc-name" placeholder="Ihr Name" required style="padding:8px 10px;border:1px solid #d1d5db;border-radius:6px;font-size:.85rem">
<input type="email" id="lc-email" placeholder="Ihre E-Mail-Adresse" required style="padding:8px 10px;border:1px solid #d1d5db;border-radius:6px;font-size:.85rem">
<div style="position:absolute;left:-9999px" aria-hidden="true"><input type="text" id="lc-website" tabindex="-1" autocomplete="off"></div>
<textarea id="lc-message" placeholder="Ihre Nachricht…" required rows="3" style="padding:8px 10px;border:1px solid #d1d5db;border-radius:6px;font-size:.85rem;resize:vertical"></textarea>
<div style="display:flex;align-items:center;gap:8px">
<label for="lc-start-attachment" style="cursor:pointer;font-size:.78rem;color:#374151;display:flex;align-items:center;gap:4px;padding:5px 9px;border:1px solid #d1d5db;border-radius:6px">📎 Datei</label>
<input type="file" id="lc-start-attachment" style="position:absolute;left:-9999px" accept="image/*,.pdf,.doc,.docx,.txt">
<span id="lc-start-attachment-name" style="font-size:.76rem;color:#6b7280"></span>
</div>
<button type="submit" class="btn" id="lc-start-btn" style="justify-content:center">Chat starten</button>
</form>
<form id="lc-send-form" style="display:none;padding:10px 12px;gap:8px;border-top:1px solid #eee;flex-shrink:0;flex-direction:column">
<div id="lc-send-attachment-name" style="display:none;font-size:.76rem;color:#6b7280;padding:0 2px"></div>
<div style="display:flex;gap:8px">
<label for="lc-send-attachment" style="cursor:pointer;flex-shrink:0;display:flex;align-items:center;justify-content:center;width:36px;border:1px solid #d1d5db;border-radius:6px;font-size:1rem">📎</label>
<input type="file" id="lc-send-attachment" style="position:absolute;left:-9999px" accept="image/*,.pdf,.doc,.docx,.txt">
<input type="text" id="lc-send-input" placeholder="Nachricht…" style="flex:1;padding:9px 10px;border:1px solid #d1d5db;border-radius:6px;font-size:.85rem">
<button type="submit" class="btn" id="lc-send-btn" style="padding:8px 14px">Senden</button>
</div>
</form>
</div>
<script>
(function () {
var CSRF = <?= json_encode(csrf_token()) ?>;
var bubble = document.getElementById('lc-bubble');
var badge = document.getElementById('lc-badge');
var panel = document.getElementById('lc-panel');
var closeBtn = document.getElementById('lc-close');
var messagesEl = document.getElementById('lc-messages');
var typingEl = document.getElementById('lc-typing');
var endedEl = document.getElementById('lc-ended');
var convertedEl = document.getElementById('lc-converted');
var convertedNumberEl = document.getElementById('lc-converted-number');
var startForm = document.getElementById('lc-start-form');
var sendForm = document.getElementById('lc-send-form');
var sendInput = document.getElementById('lc-send-input');
var errorEl = document.getElementById('lc-error');
var statusDot = document.getElementById('lc-status-dot');
var statusText = document.getElementById('lc-status-text');
var pollTimer = null;
var renderedIds = {};
var opened = false;
var unreadCount = 0;
var lastTypingPingAt = 0;
var caughtUp = false; // wird nach dem ersten Laden true – erst danach lösen neue Nachrichten Ton/Badge aus
function playNotifySound() {
try {
var ctx = new (window.AudioContext || window.webkitAudioContext)();
var osc = ctx.createOscillator();
var gain = ctx.createGain();
osc.connect(gain); gain.connect(ctx.destination);
osc.frequency.value = 660;
gain.gain.setValueAtTime(0.12, ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.3);
osc.start(); osc.stop(ctx.currentTime + 0.3);
} catch (e) {}
}
function showError(msg) {
errorEl.textContent = msg;
errorEl.style.display = 'block';
}
function setUnread(n) {
unreadCount = n;
if (n > 0 && !opened) {
badge.textContent = n > 9 ? '9+' : String(n);
badge.style.display = 'flex';
bubble.classList.add('lc-unread');
} else {
badge.style.display = 'none';
bubble.classList.remove('lc-unread');
}
}
function updateStatus(available) {
if (available === false) {
statusDot.style.background = '#9ca3af';
statusText.textContent = 'Gerade nicht erreichbar';
} else {
statusDot.style.background = '#22c55e';
statusText.textContent = 'Wir sind für Sie da';
}
}
function setStaffTyping(isTyping) {
typingEl.style.display = isTyping ? 'block' : 'none';
}
// Signalisiert dem Mitarbeiter "Kunde tippt gerade" – höchstens alle 3s, damit nicht
// bei jedem einzelnen Tastenanschlag eine Anfrage rausgeht. Fehler werden bewusst
// ignoriert (verpasstes Signal ist folgenlos, siehe live_chat.php Aktion "typing").
function pingTyping() {
var now = Date.now();
if (now - lastTypingPingAt < 3000) return;
lastTypingPingAt = now;
var body = new URLSearchParams();
body.set('action', 'typing');
body.set('csrf_token', CSRF);
fetch('/live_chat.php', { method: 'POST', body: body }).catch(function () {});
}
if (sendInput) sendInput.addEventListener('input', pingTyping);
/** Hängt nur wirklich neue Nachrichten an (statt alles neu zu rendern) – dadurch
* bleibt die Scroll-Position stabil und neue Blasen können sanft einblenden. */
function appendNewMessages(messages) {
var appended = 0;
messages.forEach(function (m) {
if (renderedIds[m.id]) return;
renderedIds[m.id] = true;
appended++;
var row = document.createElement('div');
row.className = 'lc-msg-row';
row.style.marginBottom = '4px';
row.style.display = 'flex';
row.style.flexDirection = 'column';
row.style.alignItems = m.authorType === 'customer' ? 'flex-end' : 'flex-start';
if (m.authorType === 'staff' && m.authorName) {
var nameEl = document.createElement('div');
nameEl.style.fontSize = '.68rem';
nameEl.style.color = '#9ca3af';
nameEl.style.margin = '6px 2px 2px';
nameEl.textContent = m.authorName;
row.appendChild(nameEl);
}
if (m.message) {
var bubbleEl = document.createElement('div');
bubbleEl.style.maxWidth = '78%';
bubbleEl.style.padding = '8px 11px';
bubbleEl.style.borderRadius = '10px';
bubbleEl.style.fontSize = '.85rem';
bubbleEl.style.whiteSpace = 'pre-wrap';
bubbleEl.style.wordBreak = 'break-word';
bubbleEl.style.marginBottom = '2px';
if (m.authorType === 'customer') {
bubbleEl.style.background = 'var(--primary)';
bubbleEl.style.color = '#fff';
} else {
bubbleEl.style.background = '#fff';
bubbleEl.style.border = '1px solid #e5e7eb';
bubbleEl.style.color = '#1f2937';
}
bubbleEl.textContent = m.message; // textContent = automatisch XSS-sicher, kein HTML-Parsing
row.appendChild(bubbleEl);
}
var imgExts = ['jpg','jpeg','png','gif','webp'];
(m.attachments || []).forEach(function (a) {
var ext = (a.name.split('.').pop() || '').toLowerCase();
if (imgExts.indexOf(ext) !== -1) {
var link = document.createElement('a');
link.href = '/download.php?id=' + a.id + '&preview=1';
link.target = '_blank';
link.style.cssText = 'display:block;margin-bottom:4px;border-radius:8px;overflow:hidden;max-width:180px';
var img = document.createElement('img');
img.src = '/download.php?id=' + a.id + '&preview=1';
img.alt = a.name;
img.style.cssText = 'display:block;max-width:100%;max-height:160px;object-fit:cover';
link.appendChild(img);
row.appendChild(link);
} else {
var fileLink = document.createElement('a');
fileLink.href = '/download.php?id=' + a.id;
fileLink.target = '_blank';
fileLink.style.cssText = 'display:inline-flex;align-items:center;gap:5px;font-size:.78rem;padding:5px 10px;background:#fff;border:1px solid #e5e7eb;border-radius:8px;text-decoration:none;color:#1f2937;margin-bottom:4px';
fileLink.textContent = '📎 ' + a.name;
row.appendChild(fileLink);
}
});
var timeEl = document.createElement('div');
timeEl.style.fontSize = '.65rem';
timeEl.style.color = '#9ca3af';
timeEl.textContent = (m.createdAt || '').substring(11, 16);
row.appendChild(timeEl);
messagesEl.appendChild(row);
// Beim allerersten Laden (Historie "nachladen") nie Ton/Badge auslösen – nur für
// Nachrichten, die tatsächlich NACH diesem Zeitpunkt neu reinkommen.
if (caughtUp && m.authorType === 'staff') {
if (!opened) setUnread(unreadCount + 1);
playNotifySound();
}
});
if (appended) messagesEl.scrollTop = messagesEl.scrollHeight;
caughtUp = true;
}
function showActiveChat(data) {
// Wurde der Chat (z.B. im Backend) inzwischen beendet, direkt den "Beendet"-Zustand
// zeigen, statt ein scheinbar aktives Eingabefeld für ein bereits geschlossenes Ticket.
// "Beendet" hat Vorrang vor "Ticket erstellt" – ein abgeschlossenes Ticket ist der
// endgültigere Zustand, egal ob es vorher ein normaler Chat war oder nicht.
if (data.status === 'closed') {
showEnded();
return;
}
// War gerade niemand verfügbar, wurde aus dem Chat ein ganz normales Ticket (kein
// Live-Chat mehr) – statt der eigenen Nachricht als Chat-Bubble zeigen wir dem Kunden
// eine klare Bestätigung, dass sein Anliegen als Ticket angekommen ist. Live-Polling
// lohnt hier nicht: das Ticket wird nicht nachträglich wieder zum Chat.
if (data.convertedToTicket) {
showConverted(data);
return;
}
startForm.style.display = 'none';
endedEl.style.display = 'none';
convertedEl.style.display = 'none';
messagesEl.style.display = 'block';
sendForm.style.display = 'flex';
updateStatus(data.staffAvailable);
setStaffTyping(!!data.staffTyping);
appendNewMessages(data.messages || []);
startPolling();
}
function showConverted(data) {
startForm.style.display = 'none';
messagesEl.style.display = 'none';
sendForm.style.display = 'none';
endedEl.style.display = 'none';
convertedEl.style.display = 'block';
convertedNumberEl.textContent = data.number ? ('Ticket #' + data.number) : '';
setStaffTyping(false);
stopPolling();
}
function showEnded() {
startForm.style.display = 'none';
messagesEl.style.display = 'none';
sendForm.style.display = 'none';
convertedEl.style.display = 'none';
endedEl.style.display = 'block';
setStaffTyping(false);
stopPolling();
}
function showStartForm(staffAvailable) {
startForm.style.display = 'flex';
messagesEl.style.display = 'none';
sendForm.style.display = 'none';
endedEl.style.display = 'none';
convertedEl.style.display = 'none';
updateStatus(staffAvailable);
setStaffTyping(false);
document.getElementById('lc-unavailable-hint').style.display = (staffAvailable === false) ? 'block' : 'none';
stopPolling();
}
function updateCsrf(data) {
if (data && data.csrfToken) CSRF = data.csrfToken;
}
function checkState() {
fetch('/live_chat.php?action=state')
.then(function (r) { return r.json(); })
.then(function (data) {
updateCsrf(data);
// Wurde der vorherige Chat bereits (z.B. beim letzten Besuch, ohne dass der Kunde
// live dabei war) beendet ODER zu einem normalen Ticket, nicht extra die "Beendet"-
// bzw. "Ticket erstellt"-Meldung zeigen – der Kunde müsste sonst erst auf "Neuen
// Chat starten" klicken. Stattdessen beim erneuten Laden der Seite (bzw. erneuten
// Öffnen des Bubbles) direkt wieder das normale Startformular anzeigen, damit er bei
// Bedarf sofort einen neuen Chat/eine neue Anfrage starten kann. Passiert das
// Beenden/Umwandeln dagegen WÄHREND der Kunde live zusieht (Panel offen), bleibt die
// jeweilige Meldung bestehen (siehe startPolling() bzw. showActiveChat()) – da ist
// sie sinnvolles Feedback.
if (data.active && (data.status === 'closed' || data.convertedToTicket)) {
renderedIds = {};
messagesEl.innerHTML = '';
showStartForm(data.staffAvailable);
return;
}
if (data.active) showActiveChat(data);
else showStartForm(data.staffAvailable);
})
.catch(function () { showStartForm(); });
}
// Hält die Verfügbarkeitsanzeige ("Wir sind für Sie da" / "Gerade nicht erreichbar") live,
// unabhängig davon, ob gerade ein Chat läuft oder nur das Startformular gezeigt wird – ändert
// ein Mitarbeiter seinen Status, wird das hier binnen weniger Sekunden sichtbar, ohne dass der
// Kunde die Seite neu laden muss. Rührt bewusst nur Status/Hinweisbanner an, nicht das Formular.
function pollAvailability() {
fetch('/live_chat.php?action=state')
.then(function (r) { return r.json(); })
.then(function (data) {
updateCsrf(data);
updateStatus(data.staffAvailable);
var hint = document.getElementById('lc-unavailable-hint');
if (hint && startForm.style.display !== 'none') {
hint.style.display = (data.staffAvailable === false) ? 'block' : 'none';
}
})
.catch(function () {});
}
setInterval(pollAvailability, 15000);
// Läuft dauerhaft im Hintergrund weiter (auch bei eingeklapptem Widget), damit Antworten
// per Ton + Zähler-Badge am Bubble-Icon auffallen, statt erst beim nächsten Öffnen bemerkt zu werden.
function startPolling() {
stopPolling();
pollTimer = setInterval(function () {
fetch('/live_chat.php?action=poll')
.then(function (r) { return r.json(); })
.then(function (data) {
updateCsrf(data);
if (!data.active) { stopPolling(); return; }
if (data.status === 'closed') { if (opened) showEnded(); stopPolling(); return; }
if (opened) { updateStatus(data.staffAvailable); setStaffTyping(!!data.staffTyping); }
appendNewMessages(data.messages || []);
})
.catch(function () {});
}, 8000);
}
function stopPolling() {
if (pollTimer) { clearInterval(pollTimer); pollTimer = null; }
}
function setBusy(btn, busy, busyLabel, idleLabel) {
btn.disabled = busy;
btn.textContent = busy ? busyLabel : idleLabel;
}
bubble.addEventListener('click', function () {
opened = panel.style.display !== 'flex';
panel.style.display = opened ? 'flex' : 'none';
if (opened) {
setUnread(0);
checkState();
}
});
closeBtn.addEventListener('click', function () {
opened = false;
panel.style.display = 'none';
});
// Dateiauswahl anzeigen/zurücksetzen – gemeinsam für Start- und Send-Formular.
function wireAttachmentPreview(inputEl, nameEl, isSpan) {
inputEl.addEventListener('change', function () {
var file = inputEl.files[0];
if (!file) { nameEl.textContent = ''; if (!isSpan) nameEl.style.display = 'none'; return; }
if (file.size > 15 * 1024 * 1024) {
showError('Die Datei ist zu groß (max. 15 MB).');
inputEl.value = '';
nameEl.textContent = '';
if (!isSpan) nameEl.style.display = 'none';
return;
}
nameEl.textContent = '📎 ' + file.name;
if (!isSpan) nameEl.style.display = 'block';
});
}
var startAttachment = document.getElementById('lc-start-attachment');
var startAttachmentName = document.getElementById('lc-start-attachment-name');
var sendAttachment = document.getElementById('lc-send-attachment');
var sendAttachmentName = document.getElementById('lc-send-attachment-name');
wireAttachmentPreview(startAttachment, startAttachmentName, true);
wireAttachmentPreview(sendAttachment, sendAttachmentName, false);
startForm.addEventListener('submit', function (e) {
e.preventDefault();
errorEl.style.display = 'none';
var startBtn = document.getElementById('lc-start-btn');
var body = new FormData();
body.set('action', 'start');
body.set('csrf_token', CSRF);
body.set('name', document.getElementById('lc-name').value);
body.set('email', document.getElementById('lc-email').value);
body.set('message', document.getElementById('lc-message').value);
body.set('website', document.getElementById('lc-website').value);
if (startAttachment.files[0]) body.set('attachment', startAttachment.files[0]);
setBusy(startBtn, true, 'Wird gestartet…', 'Chat starten');
fetch('/live_chat.php', { method: 'POST', body: body })
.then(function (r) { return r.json(); })
.then(function (data) {
updateCsrf(data);
if (data.error) { showError(data.error); return; }
document.getElementById('lc-message').value = '';
startAttachment.value = '';
startAttachmentName.textContent = '';
showActiveChat(data);
})
.catch(function () { showError('Verbindung fehlgeschlagen. Bitte versuchen Sie es erneut.'); })
.finally(function () { setBusy(startBtn, false, 'Wird gestartet…', 'Chat starten'); });
});
sendForm.addEventListener('submit', function (e) {
e.preventDefault();
var input = document.getElementById('lc-send-input');
var sendBtn = document.getElementById('lc-send-btn');
var text = input.value.trim();
var file = sendAttachment.files[0];
if (!text && !file) return;
var body = new FormData();
body.set('action', 'send');
body.set('csrf_token', CSRF);
body.set('message', text);
if (file) body.set('attachment', file);
input.value = '';
setBusy(sendBtn, true, '…', 'Senden');
fetch('/live_chat.php', { method: 'POST', body: body })
.then(function (r) { return r.json(); })
.then(function (data) {
updateCsrf(data);
if (data.ended) { showEnded(); return; }
if (data.error) { return; }
sendAttachment.value = '';
sendAttachmentName.textContent = '';
sendAttachmentName.style.display = 'none';
appendNewMessages(data.messages || []);
})
.catch(function () {})
.finally(function () { setBusy(sendBtn, false, '…', 'Senden'); input.focus(); });
});
document.getElementById('lc-restart').addEventListener('click', function (e) {
e.preventDefault();
renderedIds = {};
messagesEl.innerHTML = '';
showStartForm();
});
// Beim Laden der Seite bereits prüfen, ob ein Chat weiterläuft, damit Antworten auch
// erkannt werden (Ton + Badge), ohne dass der Kunde das Widget vorher öffnen muss.
checkState();
})();
</script>
<?php endif; ?>