Some checks failed
CI / PHP Lint (push) Waiting to run
CI / PHP Lint-1 (push) Waiting to run
CI / Unit Tests & Static Analysis (push) Waiting to run
CI / PHP Lint (pull_request) Has been cancelled
CI / PHP Lint-1 (pull_request) Has been cancelled
CI / Unit Tests & Static Analysis (pull_request) Has been cancelled
Frontend, Login/Installer/Updater und der komplette Admin-Bereich nutzen jetzt ein einziges Stylesheet (assets/global.css) statt pro Seite dupliziertem Inline-CSS. Design-System - Tokens für Flächen, Text, Linien, Marke, Status, Radien, Schatten und Layout-Maße; Dark Mode ausschließlich über Tokens (keine !important- Overrides mehr) - Komponenten: Buttons, Formularfelder, Cards, Tabellen, Badges, Alerts, Tabs, Pagination, Modals, Toasts, Statistik-Kacheln, Empty States - Schrift Inter mit System-Fallback Oberfläche - Admin-Shell neu: durchgehende Sidebar mit Marke, gruppierter Navigation und Benutzerbereich; schlanke Topbar mit Breadcrumb - Dashboard: ruhige KPI-Kacheln mit Icon-Chips, Charts an Theme-Farben gekoppelt - Öffentliche Voucher-Seite: App-Topbar, klare Formularstruktur und Ticket-Darstellung des erstellten Codes inkl. QR-Code - Login/Passwort/2FA: zweispaltiges Auth-Layout bzw. Fokus-Karten - Updater und Wartungsmodus im gleichen Look (Wartungsseite bleibt bewusst eigenständig ohne externe Abhängigkeiten) - Emoji-Icons in der UI durch Font-Awesome-Icons ersetzt Nebenbei behoben - Falscher SRI-Hash blockierte qrcode.min.js – QR-Codes wurden auf der Voucher-Seite und bei der 2FA-Einrichtung nie gerendert - assets/global.css wurde in mehreren Admin-Seiten über einen falschen Pfad eingebunden ($adminBase = '' statt '../') - TinyMCE lädt ohne API-Key jetzt die GPL-Variante von cdnjs – kein "valid API key required"-Banner mehr im Einstellungs-Editor - Tabellen in Cards scrollen horizontal statt zu überlaufen Screenshots in docs/screenshots neu erstellt, README aktualisiert (neuer Abschnitt "Design-System", Version 2.5.0). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
122 lines
3.9 KiB
JavaScript
122 lines
3.9 KiB
JavaScript
/* === DARK MODE === */
|
||
(function() {
|
||
const saved = localStorage.getItem('theme') || 'light';
|
||
document.documentElement.setAttribute('data-theme', saved);
|
||
})();
|
||
|
||
function toggleDarkMode() {
|
||
const html = document.documentElement;
|
||
const current = html.getAttribute('data-theme') || 'light';
|
||
const next = current === 'dark' ? 'light' : 'dark';
|
||
html.setAttribute('data-theme', next);
|
||
localStorage.setItem('theme', next);
|
||
updateDarkModeBtn();
|
||
}
|
||
|
||
function updateDarkModeBtn() {
|
||
const btn = document.getElementById('darkModeBtn');
|
||
if (!btn) return;
|
||
const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
|
||
const icon = btn.querySelector('i');
|
||
if (icon) {
|
||
icon.className = isDark ? 'fas fa-sun' : 'fas fa-moon';
|
||
} else {
|
||
btn.textContent = isDark ? '☀' : '☾';
|
||
}
|
||
btn.title = isDark ? 'Light Mode' : 'Dark Mode';
|
||
}
|
||
|
||
document.addEventListener('DOMContentLoaded', updateDarkModeBtn);
|
||
|
||
/* === TOAST NOTIFICATIONS === */
|
||
(function() {
|
||
let container = null;
|
||
|
||
function getContainer() {
|
||
if (!container) {
|
||
container = document.getElementById('toast-container');
|
||
if (!container) {
|
||
container = document.createElement('div');
|
||
container.id = 'toast-container';
|
||
document.body.appendChild(container);
|
||
}
|
||
}
|
||
return container;
|
||
}
|
||
|
||
const icons = {
|
||
success: '✓',
|
||
error: '✕',
|
||
info: 'i',
|
||
warning: '!'
|
||
};
|
||
|
||
window.showToast = function(type, title, message, duration) {
|
||
duration = duration || 4000;
|
||
const c = getContainer();
|
||
const el = document.createElement('div');
|
||
el.className = 'toast ' + type;
|
||
el.innerHTML = `
|
||
<span class="toast-icon">${icons[type] || 'ℹ'}</span>
|
||
<div class="toast-body">
|
||
<div class="toast-title">${title}</div>
|
||
${message ? `<div class="toast-msg">${message}</div>` : ''}
|
||
</div>
|
||
<button class="toast-close" onclick="this.parentElement.remove()">×</button>
|
||
`;
|
||
c.appendChild(el);
|
||
requestAnimationFrame(() => {
|
||
requestAnimationFrame(() => el.classList.add('show'));
|
||
});
|
||
setTimeout(() => {
|
||
el.classList.remove('show');
|
||
setTimeout(() => el.remove(), 350);
|
||
}, duration);
|
||
return el;
|
||
};
|
||
})();
|
||
|
||
/* === MOBILE SIDEBAR === */
|
||
function toggleMobileSidebar() {
|
||
const sidebar = document.querySelector('.sidebar');
|
||
const overlay = document.querySelector('.sidebar-overlay');
|
||
if (!sidebar) return;
|
||
sidebar.classList.toggle('mobile-open');
|
||
if (overlay) overlay.classList.toggle('active');
|
||
}
|
||
|
||
function closeMobileSidebar() {
|
||
const sidebar = document.querySelector('.sidebar');
|
||
const overlay = document.querySelector('.sidebar-overlay');
|
||
if (sidebar) sidebar.classList.remove('mobile-open');
|
||
if (overlay) overlay.classList.remove('active');
|
||
}
|
||
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
const overlay = document.querySelector('.sidebar-overlay');
|
||
if (overlay) overlay.addEventListener('click', closeMobileSidebar);
|
||
|
||
document.addEventListener('keydown', function(e) {
|
||
if (e.key === 'Escape') closeMobileSidebar();
|
||
});
|
||
});
|
||
|
||
/* === LANGUAGE SWITCHER === */
|
||
function switchLanguage(lang) {
|
||
fetch('?set_lang=' + lang, { method: 'GET' }).then(() => location.reload());
|
||
}
|
||
|
||
/* === CLIPBOARD === */
|
||
function copyToClipboard(text, successMsg) {
|
||
navigator.clipboard.writeText(text).then(() => {
|
||
showToast('success', successMsg || 'Kopiert!', '');
|
||
}).catch(() => {
|
||
const ta = document.createElement('textarea');
|
||
ta.value = text;
|
||
document.body.appendChild(ta);
|
||
ta.select();
|
||
document.execCommand('copy');
|
||
document.body.removeChild(ta);
|
||
showToast('success', successMsg || 'Kopiert!', '');
|
||
});
|
||
}
|