Redesign: gemeinsames Design-System für Frontend und Backend
Some checks are pending
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) Waiting to run
CI / PHP Lint-1 (pull_request) Waiting to run
CI / Unit Tests & Static Analysis (pull_request) Waiting to run
Some checks are pending
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) Waiting to run
CI / PHP Lint-1 (pull_request) Waiting to run
CI / Unit Tests & Static Analysis (pull_request) Waiting to run
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>
This commit is contained in:
parent
bad70a631f
commit
498c7e28e0
43 changed files with 2165 additions and 1740 deletions
|
|
@ -68,28 +68,12 @@ $adminBase = '';
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>API-Schlüssel – <?= htmlspecialchars($appTitle) ?></title>
|
||||
<?php require __DIR__ . '/../includes/admin_nav.php'; ?>
|
||||
<style>
|
||||
.card { background: var(--bg-card); border:1px solid var(--border-color); border-radius:14px; padding:24px; margin-bottom:22px; box-shadow:0 4px 14px var(--shadow); }
|
||||
.card h2 { font-size:16px; margin-bottom:16px; color:var(--text-primary); }
|
||||
table { width:100%; border-collapse:collapse; }
|
||||
th,td { text-align:left; padding:11px 8px; font-size:14px; border-bottom:1px solid var(--border-color); color:var(--text-primary); }
|
||||
th { color:var(--text-muted); font-weight:600; }
|
||||
code { font-family:monospace; background:var(--bg-hover); padding:2px 6px; border-radius:5px; }
|
||||
.btn { padding:10px 16px; border:none; border-radius:8px; font-weight:600; cursor:pointer; font-size:14px; text-decoration:none; display:inline-block; }
|
||||
.btn-primary { background:var(--accent,#667eea); color:#fff; }
|
||||
.input { width:100%; padding:11px; border:2px solid var(--border-color); border-radius:8px; background:var(--bg-input,#fff); color:var(--text-primary); font-size:14px; }
|
||||
.alert { padding:12px 14px; border-radius:9px; font-size:14px; margin-bottom:18px; }
|
||||
.alert-error { background:#fee; border:1px solid #fcc; color:#c33; }
|
||||
.alert-ok { background:#efe; border:1px solid #cfc; color:#2a7; }
|
||||
.keybox { background:#0f1117; color:#7CFFB2; font-family:monospace; padding:14px; border-radius:8px; word-break:break-all; font-size:15px; margin-top:10px; }
|
||||
.badge { padding:3px 9px; border-radius:6px; font-size:12px; font-weight:600; }
|
||||
.b-on { background:#e3f6ea; color:#2a7; } .b-off { background:#fdeaea; color:#c33; }
|
||||
.a-link { color:var(--accent,#667eea); text-decoration:none; margin-right:10px; font-size:13px; }
|
||||
.muted { color:var(--text-muted); font-size:13px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1 style="font-size:24px;margin-bottom:20px;color:var(--text-primary);">🔑 API-Schlüssel</h1>
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<h1 class="page-title">API-Schlüssel</h1>
|
||||
<p class="page-subtitle">Zugänge für externe Systeme – mit Scope und Rate-Limit.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($error): ?><div class="alert alert-error"><?= htmlspecialchars($error) ?></div><?php endif; ?>
|
||||
<?php if ($success): ?><div class="alert alert-ok"><?= htmlspecialchars($success) ?></div><?php endif; ?>
|
||||
|
|
@ -130,6 +114,7 @@ code { font-family:monospace; background:var(--bg-hover); padding:2px 6px; borde
|
|||
<?php if (empty($keys)): ?>
|
||||
<p class="muted">Noch keine API-Schlüssel angelegt.</p>
|
||||
<?php else: ?>
|
||||
<div class="table-container">
|
||||
<table>
|
||||
<tr><th>Name</th><th>Präfix</th><th>Scope</th><th>Limit</th><th>Status</th><th>Zuletzt genutzt</th><th>Erstellt von</th><th></th></tr>
|
||||
<?php foreach ($keys as $k): ?>
|
||||
|
|
@ -139,15 +124,16 @@ code { font-family:monospace; background:var(--bg-hover); padding:2px 6px; borde
|
|||
<td><?= ($k['scope'] ?? 'write') === 'read' ? 'nur Lesen' : 'Lesen+Erstellen' ?></td>
|
||||
<td><?= (int)($k['rate_limit'] ?? 0) === 0 ? '∞' : (int)$k['rate_limit'] . '/min' ?></td>
|
||||
<td><span class="badge <?= $k['is_active'] ? 'b-on' : 'b-off' ?>"><?= $k['is_active'] ? 'aktiv' : 'gesperrt' ?></span></td>
|
||||
<td class="muted"><?= $k['last_used_at'] ? htmlspecialchars($k['last_used_at']) : '–' ?></td>
|
||||
<td class="muted"><?= $k['last_used_at'] ? date('d.m.Y H:i', strtotime($k['last_used_at'])) : '–' ?></td>
|
||||
<td class="muted"><?= htmlspecialchars($k['creator'] ?? '–') ?></td>
|
||||
<td style="text-align:right;white-space:nowrap;">
|
||||
<a class="a-link" href="?toggle=<?= (int)$k['id'] ?>&token=<?= urlencode($csrf) ?>"><?= $k['is_active'] ? 'Sperren' : 'Aktivieren' ?></a>
|
||||
<a class="a-link" style="color:#e25555;" href="?delete=<?= (int)$k['id'] ?>&token=<?= urlencode($csrf) ?>" onclick="return confirm('Schlüssel löschen?');">Löschen</a>
|
||||
<a class="a-link" style="color:var(--danger);" href="?delete=<?= (int)$k['id'] ?>&token=<?= urlencode($csrf) ?>" onclick="return confirm('Schlüssel löschen?');">Löschen</a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</table>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
|
|
@ -165,7 +151,7 @@ curl https://IHRE-DOMAIN/api/sites.php -H "X-API-Key: uvt_…"</pre>
|
|||
<p class="muted" style="margin-top:12px;">OpenAPI-Spezifikation (Import in Postman/Swagger): <a href="../api/openapi.php" target="_blank">/api/openapi.php</a></p>
|
||||
</div>
|
||||
|
||||
</div><!-- /main-content -->
|
||||
</main>
|
||||
<script src="../assets/global.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -46,21 +46,21 @@ $currentPage = 'audit_log';
|
|||
$adminBase = '';
|
||||
|
||||
$actionLabels = [
|
||||
'voucher_created' => '🎫 Voucher erstellt',
|
||||
'voucher_bulk' => '🎫 Bulk Voucher',
|
||||
'user_login' => '🔐 Login',
|
||||
'user_logout' => '🚪 Logout',
|
||||
'user_created' => '👤 Benutzer erstellt',
|
||||
'user_updated' => '👤 Benutzer geändert',
|
||||
'user_deleted' => '👤 Benutzer gelöscht',
|
||||
'site_added' => '🌐 Site hinzugefügt',
|
||||
'site_updated' => '🌐 Site geändert',
|
||||
'site_deleted' => '🌐 Site gelöscht',
|
||||
'settings_saved' => '⚙️ Einstellungen gespeichert',
|
||||
'password_reset' => '🔑 Passwort-Reset',
|
||||
'template_created' => '📋 Profil erstellt',
|
||||
'template_updated' => '📋 Profil geändert',
|
||||
'template_deleted' => '📋 Profil gelöscht',
|
||||
'voucher_created' => 'Voucher erstellt',
|
||||
'voucher_bulk' => 'Bulk Voucher',
|
||||
'user_login' => 'Login',
|
||||
'user_logout' => 'Logout',
|
||||
'user_created' => 'Benutzer erstellt',
|
||||
'user_updated' => 'Benutzer geändert',
|
||||
'user_deleted' => 'Benutzer gelöscht',
|
||||
'site_added' => 'Site hinzugefügt',
|
||||
'site_updated' => 'Site geändert',
|
||||
'site_deleted' => 'Site gelöscht',
|
||||
'settings_saved' => 'Einstellungen gespeichert',
|
||||
'password_reset' => 'Passwort-Reset',
|
||||
'template_created' => 'Profil erstellt',
|
||||
'template_updated' => 'Profil geändert',
|
||||
'template_deleted' => 'Profil gelöscht',
|
||||
];
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
|
|
@ -70,42 +70,12 @@ $actionLabels = [
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title><?= __('audit_title') ?> - <?= htmlspecialchars($appTitle) ?></title>
|
||||
<?php include __DIR__ . '/../includes/admin_nav.php'; ?>
|
||||
<style>
|
||||
.page-header { margin-bottom: 25px; }
|
||||
.page-title { font-size: 28px; font-weight: 600; color: var(--text-primary); margin-bottom: 6px; }
|
||||
.card { background: var(--bg-card); border-radius: 15px; box-shadow: 0 2px 10px var(--shadow); border: 1px solid var(--border-color); overflow: hidden; margin-bottom: 20px; }
|
||||
.card-header { padding: 18px 25px; border-bottom: 1px solid var(--border-color); display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 10px; }
|
||||
.card-title { font-size: 16px; font-weight: 600; color: var(--text-primary); }
|
||||
.table { width: 100%; border-collapse: collapse; }
|
||||
.table th { text-align: left; padding: 11px 15px; background: var(--bg-table-head); color: var(--text-secondary); font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; }
|
||||
.table td { padding: 12px 15px; border-bottom: 1px solid var(--border-color); font-size: 13px; color: var(--text-primary); }
|
||||
.table tr:last-child td { border-bottom: none; }
|
||||
.table tr:hover td { background: var(--bg-hover); }
|
||||
.badge { display: inline-block; padding: 3px 9px; border-radius: 5px; font-size: 11px; font-weight: 500; }
|
||||
.filter-bar { display: flex; gap: 12px; flex-wrap: wrap; align-items: flex-end; }
|
||||
.filter-bar select { padding: 9px 12px; border: 2px solid var(--border-color); border-radius: 8px; font-size: 13px; background: var(--bg-input); color: var(--text-primary); }
|
||||
.filter-bar select:focus { outline: none; border-color: var(--accent); }
|
||||
.btn-primary { background: var(--accent); color: white; }
|
||||
.btn-primary:hover { background: var(--accent-hover); }
|
||||
.btn-small { padding: 6px 12px; font-size: 12px; }
|
||||
.ip-cell { font-family: monospace; font-size: 12px; color: var(--text-muted); }
|
||||
.details-cell { max-width: 250px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text-secondary); }
|
||||
.pagination { display: flex; align-items: center; justify-content: space-between; padding: 14px 25px; border-top: 1px solid var(--border-color); flex-wrap: wrap; gap: 10px; }
|
||||
.page-info { font-size: 13px; color: var(--text-muted); }
|
||||
.page-btns { display: flex; gap: 5px; flex-wrap: wrap; }
|
||||
.page-btn { padding: 6px 12px; border-radius: 6px; border: 1px solid var(--border-color); background: var(--bg-card); color: var(--text-secondary); cursor: pointer; font-size: 13px; text-decoration: none; transition: all 0.2s; }
|
||||
.page-btn:hover { border-color: var(--accent); color: var(--accent); }
|
||||
.page-btn.active { background: var(--accent); color: white; border-color: var(--accent); }
|
||||
.page-btn:disabled { opacity: 0.4; cursor: not-allowed; }
|
||||
.empty-state { text-align: center; padding: 60px 20px; color: var(--text-muted); }
|
||||
.empty-state i { font-size: 42px; margin-bottom: 15px; display: block; opacity: 0.3; }
|
||||
.action-chip { display: inline-flex; align-items: center; gap: 5px; padding: 3px 10px; border-radius: 20px; font-size: 12px; font-weight: 500; background: var(--bg-hover); color: var(--text-secondary); }
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<div class="page-header">
|
||||
<h1 class="page-title"><?= __('audit_title') ?></h1>
|
||||
<p style="color: var(--text-muted); font-size: 14px;"><?= __('audit_subtitle') ?></p>
|
||||
<div>
|
||||
<h1 class="page-title"><?= __('audit_title') ?></h1>
|
||||
<p class="page-subtitle"><?= __('audit_subtitle') ?></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filter -->
|
||||
|
|
@ -151,6 +121,7 @@ $actionLabels = [
|
|||
<div class="empty-state"><i class="fas fa-history"></i><p><?= __('audit_none') ?></p></div>
|
||||
<?php else: ?>
|
||||
<div style="overflow-x:auto;">
|
||||
<div class="table-container">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
|
|
@ -197,6 +168,7 @@ $actionLabels = [
|
|||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($pages > 1): ?>
|
||||
|
|
@ -221,7 +193,7 @@ $actionLabels = [
|
|||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
</div><!-- main-content -->
|
||||
</main>
|
||||
<script src="../assets/global.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -114,22 +114,12 @@ $adminBase = '';
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Backup & Restore – <?= htmlspecialchars($appTitle) ?></title>
|
||||
<?php require __DIR__ . '/../includes/admin_nav.php'; ?>
|
||||
<style>
|
||||
.card { background:var(--bg-card); border:1px solid var(--border-color); border-radius:14px; padding:24px; margin-bottom:22px; box-shadow:0 4px 14px var(--shadow); max-width:680px; }
|
||||
.card h2 { font-size:16px; margin-bottom:12px; color:var(--text-primary); }
|
||||
.muted { color:var(--text-muted); font-size:13px; margin-bottom:14px; }
|
||||
.btn { padding:11px 18px; border:none; border-radius:8px; font-weight:600; cursor:pointer; font-size:14px; text-decoration:none; display:inline-block; }
|
||||
.btn-primary { background:var(--accent,#667eea); color:#fff; }
|
||||
.btn-secondary { background:var(--bg-hover); color:var(--text-primary); border:1px solid var(--border-color); }
|
||||
.alert { padding:12px 14px; border-radius:9px; font-size:14px; margin-bottom:18px; max-width:680px; }
|
||||
.alert-error { background:#fee; border:1px solid #fcc; color:#c33; }
|
||||
.alert-ok { background:#efe; border:1px solid #cfc; color:#2a7; }
|
||||
label.chk { display:flex; align-items:center; gap:9px; margin:8px 0; color:var(--text-primary); font-size:14px; }
|
||||
input[type=file] { margin:10px 0; color:var(--text-primary); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1 style="font-size:24px;margin-bottom:20px;color:var(--text-primary);">💾 Backup & Restore</h1>
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<h1 class="page-title">Backup & Restore</h1>
|
||||
<p class="page-subtitle">Konfiguration und Daten sichern oder wiederherstellen.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($error): ?><div class="alert alert-error"><?= htmlspecialchars($error) ?></div><?php endif; ?>
|
||||
<?php if ($success): ?><div class="alert alert-ok"><?= htmlspecialchars($success) ?></div><?php endif; ?>
|
||||
|
|
@ -153,7 +143,7 @@ input[type=file] { margin:10px 0; color:var(--text-primary); }
|
|||
</form>
|
||||
</div>
|
||||
|
||||
</div><!-- /main-content -->
|
||||
</main>
|
||||
<script src="../assets/global.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -93,22 +93,12 @@ $adminBase = '';
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>CSV-Import – <?= htmlspecialchars($appTitle) ?></title>
|
||||
<?php require __DIR__ . '/../includes/admin_nav.php'; ?>
|
||||
<style>
|
||||
.card { background:var(--bg-card); border:1px solid var(--border-color); border-radius:14px; padding:24px; margin-bottom:20px; box-shadow:0 4px 14px var(--shadow); max-width:760px; }
|
||||
.card h2 { font-size:15px; margin-bottom:12px; color:var(--text-primary); }
|
||||
.muted { color:var(--text-muted); font-size:13px; margin-bottom:12px; }
|
||||
label { display:block; font-size:13px; color:var(--text-secondary); margin:12px 0 6px; }
|
||||
.input,textarea,select { width:100%; padding:11px; border:2px solid var(--border-color); border-radius:8px; background:var(--bg-input,#fff); color:var(--text-primary); font-size:14px; font-family:inherit; }
|
||||
textarea { min-height:140px; font-family:monospace; }
|
||||
.btn { padding:11px 18px; border:none; border-radius:8px; font-weight:600; font-size:14px; cursor:pointer; background:var(--accent,#667eea); color:#fff; }
|
||||
.alert { padding:12px 14px; border-radius:9px; font-size:14px; margin-bottom:18px; max-width:760px; }
|
||||
.alert-error { background:#fee; border:1px solid #fcc; color:#c33; } .alert-ok { background:#efe; border:1px solid #cfc; color:#2a7; }
|
||||
table { width:100%; border-collapse:collapse; } th,td { text-align:left; padding:8px; font-size:13px; border-bottom:1px solid var(--border-color); color:var(--text-primary); }
|
||||
code { font-family:monospace; background:var(--bg-hover); padding:2px 6px; border-radius:5px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1 style="font-size:24px;margin-bottom:18px;color:var(--text-primary);">📥 Voucher-Import (CSV)</h1>
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<h1 class="page-title">Voucher-Import</h1>
|
||||
<p class="page-subtitle">Mehrere Vouchers auf einmal aus einer CSV-Liste erstellen.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($error): ?><div class="alert alert-error"><?= htmlspecialchars($error) ?></div><?php endif; ?>
|
||||
<?php if ($success): ?><div class="alert alert-ok"><?= htmlspecialchars($success) ?></div><?php endif; ?>
|
||||
|
|
@ -142,7 +132,7 @@ code { font-family:monospace; background:var(--bg-hover); padding:2px 6px; borde
|
|||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
</div><!-- /main-content -->
|
||||
</main>
|
||||
<script src="../assets/global.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
146
admin/index.php
146
admin/index.php
|
|
@ -92,74 +92,6 @@ $currentPage = 'dashboard';
|
|||
<title><?= __('dashboard_title') ?> – <?= htmlspecialchars($appTitle) ?></title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
|
||||
<?php include __DIR__ . '/../includes/admin_nav.php'; ?>
|
||||
<style>
|
||||
.page-header { margin-bottom: 30px; display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 15px; }
|
||||
.page-title { font-size: 26px; font-weight: 700; color: var(--text-primary); }
|
||||
.page-subtitle { color: var(--text-muted); font-size: 14px; margin-top: 4px; }
|
||||
.live-badge { display: inline-flex; align-items: center; gap: 8px; background: #d4edda; color: #155724; padding: 8px 16px; border-radius: 20px; font-size: 13px; font-weight: 500; }
|
||||
.live-badge .dot { width: 8px; height: 8px; background: #28a745; border-radius: 50%; animation: pulse 2s infinite; }
|
||||
.live-badge.loading { background: #fff3cd; color: #856404; }
|
||||
.live-badge.loading .dot { background: #ffc107; }
|
||||
.live-badge.error { background: #f8d7da; color: #721c24; }
|
||||
.live-badge.error .dot { background: var(--danger); animation: none; }
|
||||
@keyframes pulse { 0%,100%{opacity:1}50%{opacity:.5} }
|
||||
.stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 20px; margin-bottom: 30px; }
|
||||
.stat-card { background: var(--bg-card); padding: 22px; border-radius: 14px; box-shadow: 0 2px 10px var(--shadow); border: 1px solid var(--border-color); }
|
||||
.stat-card.live { border-color: var(--success); border-width: 2px; }
|
||||
.stat-card-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; }
|
||||
.stat-card-title { color: var(--text-muted); font-size: 13px; font-weight: 500; }
|
||||
.stat-card-icon { width: 38px; height: 38px; border-radius: 10px; display: flex; align-items: center; justify-content: center; font-size: 17px; }
|
||||
.stat-card-value { font-size: 30px; font-weight: 700; color: var(--text-primary); }
|
||||
.stat-card-value.valid { color: var(--success); }
|
||||
.stat-card-value.used { color: var(--warning); }
|
||||
.stat-card-value.expired { color: var(--danger); }
|
||||
.stat-card-sub { font-size: 11px; color: var(--text-muted); margin-top: 4px; }
|
||||
.card { background: var(--bg-card); border-radius: 14px; box-shadow: 0 2px 10px var(--shadow); border: 1px solid var(--border-color); margin-bottom: 28px; overflow: hidden; }
|
||||
.card-header { padding: 18px 22px; border-bottom: 1px solid var(--border-color); display: flex; justify-content: space-between; align-items: center; }
|
||||
.card-title { font-size: 17px; font-weight: 600; color: var(--text-primary); }
|
||||
.card-body { padding: 22px; }
|
||||
.table { width: 100%; border-collapse: collapse; }
|
||||
.table th { text-align: left; padding: 11px 14px; background: var(--bg-table-head); color: var(--text-muted); font-weight: 600; font-size: 12px; text-transform: uppercase; letter-spacing: .5px; }
|
||||
.table td { padding: 13px 14px; border-bottom: 1px solid var(--border-color); color: var(--text-primary); font-size: 14px; }
|
||||
.table tr:last-child td { border-bottom: none; }
|
||||
.badge { display: inline-block; padding: 3px 9px; border-radius: 5px; font-size: 11px; font-weight: 500; }
|
||||
.badge-success { background: #d4edda; color: #155724; }
|
||||
.badge-warning { background: #fff3cd; color: #856404; }
|
||||
.badge-danger { background: #f8d7da; color: #721c24; }
|
||||
.badge-info { background: var(--bg-badge-info); color: var(--text-badge-info); }
|
||||
.btn-primary { background: var(--accent); color: white; }
|
||||
.btn-primary:hover { background: var(--accent-hover); }
|
||||
.btn-success { background: var(--success); color: white; }
|
||||
.btn-success:hover { opacity: .9; }
|
||||
.btn-sm { padding: 6px 12px; font-size: 12px; }
|
||||
.chart-container { position: relative; height: 280px; }
|
||||
.site-status { display: grid; grid-template-columns: repeat(auto-fill, minmax(270px, 1fr)); gap: 14px; }
|
||||
.site-status-card { background: var(--bg-card); border: 2px solid var(--border-color); border-radius: 12px; padding: 18px; transition: border-color .2s; }
|
||||
.site-status-card:hover { border-color: var(--accent); }
|
||||
.site-status-card.error { border-color: var(--danger); }
|
||||
.site-status-name { font-weight: 600; color: var(--text-primary); font-size: 15px; }
|
||||
.site-voucher-count { background: var(--accent); color: white; padding: 3px 11px; border-radius: 20px; font-size: 13px; font-weight: 600; }
|
||||
.site-voucher-count.zero { background: var(--text-muted); }
|
||||
.site-status-stats { display: grid; grid-template-columns: repeat(3,1fr); gap: 8px; margin-top: 12px; }
|
||||
.site-stat { text-align: center; padding: 9px; background: var(--bg-hover); border-radius: 8px; }
|
||||
.site-stat-value { font-size: 18px; font-weight: 700; }
|
||||
.site-stat-value.valid { color: var(--success); }
|
||||
.site-stat-value.used { color: var(--warning); }
|
||||
.site-stat-value.expired { color: var(--danger); }
|
||||
.site-stat-label { font-size: 11px; color: var(--text-muted); margin-top: 2px; }
|
||||
.site-error { color: var(--danger); font-size: 12px; margin-top: 8px; }
|
||||
.top-users-list { list-style: none; }
|
||||
.top-users-list li { display: flex; justify-content: space-between; align-items: center; padding: 11px 0; border-bottom: 1px solid var(--border-color); }
|
||||
.top-users-list li:last-child { border-bottom: none; }
|
||||
.user-info-row { display: flex; align-items: center; gap: 10px; }
|
||||
.user-avatar-sm { width: 30px; height: 30px; border-radius: 50%; background: linear-gradient(135deg,#667eea,#764ba2); display: flex; align-items: center; justify-content: center; color: white; font-weight: 700; font-size: 13px; }
|
||||
.user-count { background: var(--bg-hover); padding: 3px 10px; border-radius: 12px; font-weight: 600; color: var(--text-primary); font-size: 13px; }
|
||||
.refresh-indicator { display: flex; align-items: center; gap: 10px; }
|
||||
.last-update { color: var(--text-muted); font-size: 12px; }
|
||||
.empty-state { text-align: center; padding: 50px 20px; color: var(--text-muted); }
|
||||
.empty-state i { font-size: 40px; margin-bottom: 15px; opacity: .3; display: block; }
|
||||
@media(max-width:768px){ .main-content{ margin-left:0!important; } .stats-grid{ grid-template-columns:1fr 1fr; } }
|
||||
</style>
|
||||
|
||||
<div class="page-header">
|
||||
<div>
|
||||
|
|
@ -171,7 +103,7 @@ $currentPage = 'dashboard';
|
|||
<span class="dot"></span>
|
||||
<span id="liveStatus">DB</span>
|
||||
</span>
|
||||
<button onclick="refreshData('live')" class="btn btn-success btn-sm" id="refreshBtn">
|
||||
<button onclick="refreshData('live')" class="btn btn-secondary btn-sm" id="refreshBtn">
|
||||
<i class="fas fa-sync-alt"></i> <?= __('dashboard_live_refresh') ?>
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -181,43 +113,43 @@ $currentPage = 'dashboard';
|
|||
<div class="stat-card">
|
||||
<div class="stat-card-header">
|
||||
<div class="stat-card-title"><?= __('dashboard_active_sites') ?></div>
|
||||
<div class="stat-card-icon" style="background:#e3f2fd;color:#1976d2;"><i class="fas fa-map-marker-alt"></i></div>
|
||||
<div class="stat-card-icon info"><i class="fas fa-location-dot"></i></div>
|
||||
</div>
|
||||
<div class="stat-card-value"><?= $stats['total_sites'] ?></div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-card-header">
|
||||
<div class="stat-card-title"><?= __('dashboard_users') ?></div>
|
||||
<div class="stat-card-icon" style="background:#f3e5f5;color:#7b1fa2;"><i class="fas fa-users"></i></div>
|
||||
<div class="stat-card-icon accent"><i class="fas fa-users"></i></div>
|
||||
</div>
|
||||
<div class="stat-card-value"><?= $stats['total_users'] ?></div>
|
||||
</div>
|
||||
<div class="stat-card live">
|
||||
<div class="stat-card-header">
|
||||
<div class="stat-card-title">🟢 <?= __('dashboard_valid') ?></div>
|
||||
<div class="stat-card-icon" style="background:#e8f5e9;color:#388e3c;"><i class="fas fa-check-circle"></i></div>
|
||||
<div class="stat-card-title"><?= __('dashboard_valid') ?></div>
|
||||
<div class="stat-card-icon success"><i class="fas fa-circle-check"></i></div>
|
||||
</div>
|
||||
<div class="stat-card-value valid" id="liveValid"><?= (int)($voucherStats['valid']??0) ?></div>
|
||||
<div class="stat-card-sub" id="subValid"><?= $lastCronSync ? date('H:i', strtotime($lastCronSync)) : __('never') ?></div>
|
||||
</div>
|
||||
<div class="stat-card live">
|
||||
<div class="stat-card-header">
|
||||
<div class="stat-card-title">🟡 <?= __('dashboard_used') ?></div>
|
||||
<div class="stat-card-icon" style="background:#fff3e0;color:#f57c00;"><i class="fas fa-user-check"></i></div>
|
||||
<div class="stat-card-title"><?= __('dashboard_used') ?></div>
|
||||
<div class="stat-card-icon warning"><i class="fas fa-user-check"></i></div>
|
||||
</div>
|
||||
<div class="stat-card-value used" id="liveUsed"><?= (int)($voucherStats['used']??0) ?></div>
|
||||
</div>
|
||||
<div class="stat-card live">
|
||||
<div class="stat-card-header">
|
||||
<div class="stat-card-title">🔴 <?= __('dashboard_expired') ?></div>
|
||||
<div class="stat-card-icon" style="background:#ffebee;color:#d32f2f;"><i class="fas fa-times-circle"></i></div>
|
||||
<div class="stat-card-title"><?= __('dashboard_expired') ?></div>
|
||||
<div class="stat-card-icon danger"><i class="fas fa-circle-xmark"></i></div>
|
||||
</div>
|
||||
<div class="stat-card-value expired" id="liveExpired"><?= (int)($voucherStats['expired']??0) ?></div>
|
||||
</div>
|
||||
<div class="stat-card live">
|
||||
<div class="stat-card-header">
|
||||
<div class="stat-card-title">📊 <?= __('dashboard_total') ?></div>
|
||||
<div class="stat-card-icon" style="background:var(--bg-hover);color:var(--text-muted);"><i class="fas fa-ticket-alt"></i></div>
|
||||
<div class="stat-card-title"><?= __('dashboard_total') ?></div>
|
||||
<div class="stat-card-icon"><i class="fas fa-ticket"></i></div>
|
||||
</div>
|
||||
<div class="stat-card-value" id="liveTotal"><?= (int)($voucherStats['total']??0) ?></div>
|
||||
</div>
|
||||
|
|
@ -225,16 +157,16 @@ $currentPage = 'dashboard';
|
|||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h2 class="card-title">🔴 <?= __('dashboard_vouchers_per_site') ?></h2>
|
||||
<h2 class="card-title"><?= __('dashboard_vouchers_per_site') ?></h2>
|
||||
<span class="last-update" id="lastUpdate"><?= $lastCronSync ? date('d.m.Y H:i', strtotime($lastCronSync)) : __('never') ?></span>
|
||||
</div>
|
||||
<div class="card-body" style="padding:18px;">
|
||||
<div class="card-body">
|
||||
<div class="site-status" id="siteStatusContainer">
|
||||
<?php foreach ($sites as $site):
|
||||
$ss = $siteStats[$site['id']] ?? ['total'=>0,'valid'=>0,'used'=>0,'expired'=>0];
|
||||
?>
|
||||
<div class="site-status-card" id="site-<?= $site['id'] ?>">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;gap:10px;">
|
||||
<div class="site-status-name"><?= htmlspecialchars($site['name']) ?></div>
|
||||
<span class="site-voucher-count <?= (int)$ss['total']===0 ? 'zero' : '' ?>" id="site-total-<?= $site['id'] ?>">
|
||||
<?= (int)$ss['total'] ?>
|
||||
|
|
@ -249,8 +181,8 @@ $currentPage = 'dashboard';
|
|||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($sites)): ?>
|
||||
<div style="grid-column:1/-1;text-align:center;padding:40px;color:var(--text-muted);">
|
||||
<i class="fas fa-map-marker-alt" style="font-size:32px;margin-bottom:15px;opacity:.3;display:block;"></i>
|
||||
<div class="empty-state" style="grid-column:1/-1;">
|
||||
<div class="empty-icon"><i class="fas fa-location-dot"></i></div>
|
||||
<p><?= __('dashboard_no_data') ?></p>
|
||||
<a href="sites.php" class="btn btn-primary" style="margin-top:15px;"><i class="fas fa-plus"></i> <?= __('sites_add') ?></a>
|
||||
</div>
|
||||
|
|
@ -260,16 +192,16 @@ $currentPage = 'dashboard';
|
|||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header"><h2 class="card-title">📊 <?= __('dashboard_trend') ?></h2></div>
|
||||
<div class="card-header"><h2 class="card-title"><?= __('dashboard_trend') ?></h2></div>
|
||||
<div class="card-body">
|
||||
<div class="chart-container"><canvas id="voucherChart"></canvas></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:28px;margin-bottom:28px;" class="two-col-grid">
|
||||
<div class="card" style="margin-bottom:0;">
|
||||
<div class="card-header"><h2 class="card-title">🏆 <?= __('dashboard_top_users') ?></h2></div>
|
||||
<div class="card-body" style="padding:18px 22px;">
|
||||
<div class="two-col-grid">
|
||||
<div class="card">
|
||||
<div class="card-header"><h2 class="card-title"><?= __('dashboard_top_users') ?></h2></div>
|
||||
<div class="card-body">
|
||||
<?php if (empty($topUsers)): ?>
|
||||
<div class="empty-state"><i class="fas fa-users"></i><p><?= __('dashboard_no_data') ?></p></div>
|
||||
<?php else: ?>
|
||||
|
|
@ -291,19 +223,20 @@ $currentPage = 'dashboard';
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card" style="margin-bottom:0;">
|
||||
<div class="card-header"><h2 class="card-title">📋 <?= __('dashboard_recent') ?></h2><a href="vouchers.php" class="btn btn-secondary btn-sm"><i class="fas fa-external-link-alt"></i> Live</a></div>
|
||||
<div class="card">
|
||||
<div class="card-header"><h2 class="card-title"><?= __('dashboard_recent') ?></h2><a href="vouchers.php" class="btn btn-secondary btn-sm"><i class="fas fa-arrow-up-right-from-square"></i> Live</a></div>
|
||||
<div class="card-body" style="padding:0;">
|
||||
<?php if (empty($recentVouchers)): ?>
|
||||
<div class="empty-state"><i class="fas fa-ticket-alt"></i><p><?= __('dashboard_no_vouchers') ?></p></div>
|
||||
<?php else: ?>
|
||||
<div class="table-container">
|
||||
<table class="table">
|
||||
<thead><tr><th><?= __('label_created') ?></th><th><?= __('label_code') ?></th><th><?= __('label_site') ?></th><th><?= __('label_status') ?></th></tr></thead>
|
||||
<tbody>
|
||||
<?php foreach ($recentVouchers as $v): ?>
|
||||
<tr>
|
||||
<td style="font-size:12px;"><?= date('d.m H:i', strtotime($v['created_at'])) ?></td>
|
||||
<td><code style="font-size:12px;background:var(--bg-hover);padding:3px 6px;border-radius:4px;"><?= htmlspecialchars($v['voucher_code']) ?></code></td>
|
||||
<td><code><?= htmlspecialchars($v['voucher_code']) ?></code></td>
|
||||
<td><span class="badge badge-info"><?= htmlspecialchars($v['site_name']??'') ?></span></td>
|
||||
<td>
|
||||
<?php $st=$v['status']??'valid'; ?>
|
||||
|
|
@ -313,17 +246,26 @@ $currentPage = 'dashboard';
|
|||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div><!-- /main-content -->
|
||||
</main>
|
||||
|
||||
<div id="toast-container"></div>
|
||||
<script src="../assets/global.js"></script>
|
||||
<script>
|
||||
const rootStyles = getComputedStyle(document.documentElement);
|
||||
const accentColor = rootStyles.getPropertyValue('--accent').trim();
|
||||
const surfaceColor = rootStyles.getPropertyValue('--bg-card').trim();
|
||||
const gridColor = rootStyles.getPropertyValue('--border-color').trim();
|
||||
const mutedColor = rootStyles.getPropertyValue('--text-muted').trim();
|
||||
const ctx = document.getElementById('voucherChart').getContext('2d');
|
||||
const accentFill = ctx.createLinearGradient(0, 0, 0, 280);
|
||||
accentFill.addColorStop(0, accentColor + '33');
|
||||
accentFill.addColorStop(1, accentColor + '00');
|
||||
const chartData = <?= json_encode($chartData) ?>;
|
||||
new Chart(ctx, {
|
||||
type: 'line',
|
||||
|
|
@ -332,17 +274,20 @@ new Chart(ctx, {
|
|||
datasets: [{
|
||||
label: 'Vouchers',
|
||||
data: chartData.map(d => d.count),
|
||||
borderColor: '#667eea',
|
||||
backgroundColor: 'rgba(102,126,234,0.1)',
|
||||
borderColor: accentColor,
|
||||
backgroundColor: accentFill,
|
||||
tension: 0.4, fill: true,
|
||||
pointBackgroundColor: '#667eea', pointBorderColor: '#fff',
|
||||
pointBorderWidth: 2, pointRadius: 5, pointHoverRadius: 7
|
||||
pointBackgroundColor: accentColor, pointBorderColor: surfaceColor,
|
||||
pointBorderWidth: 2, pointRadius: 4, pointHoverRadius: 6
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true, maintainAspectRatio: false,
|
||||
plugins: { legend: { display: false } },
|
||||
scales: { y: { beginAtZero: true, ticks: { stepSize: 1, color: getComputedStyle(document.documentElement).getPropertyValue('--text-muted').trim() } }, x: { ticks: { color: getComputedStyle(document.documentElement).getPropertyValue('--text-muted').trim() } } }
|
||||
scales: {
|
||||
y: { beginAtZero: true, border: { display: false }, grid: { color: gridColor, drawTicks: false }, ticks: { stepSize: 1, color: mutedColor, padding: 10, font: { size: 11 } } },
|
||||
x: { border: { display: false }, grid: { display: false }, ticks: { color: mutedColor, padding: 8, font: { size: 11 } } }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -396,11 +341,6 @@ async function refreshData(mode='db') {
|
|||
refreshBtn.disabled = false;
|
||||
refreshBtn.innerHTML = '<i class="fas fa-sync-alt"></i> <?= __('dashboard_live_refresh') ?>';
|
||||
}
|
||||
|
||||
// Responsive two-column grid
|
||||
const twoCol = document.querySelector('.two-col-grid');
|
||||
function checkGrid() { if (twoCol) twoCol.style.gridTemplateColumns = window.innerWidth < 900 ? '1fr' : '1fr 1fr'; }
|
||||
checkGrid(); window.addEventListener('resize', checkGrid);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -94,22 +94,12 @@ $adminBase = '';
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Integration & Wartung – <?= htmlspecialchars($appTitle) ?></title>
|
||||
<?php require __DIR__ . '/../includes/admin_nav.php'; ?>
|
||||
<style>
|
||||
.card { background:var(--bg-card); border:1px solid var(--border-color); border-radius:14px; padding:24px; margin-bottom:22px; box-shadow:0 4px 14px var(--shadow); max-width:680px; }
|
||||
.card h2 { font-size:16px; margin-bottom:6px; color:var(--text-primary); }
|
||||
.muted { color:var(--text-muted); font-size:13px; margin-bottom:14px; }
|
||||
label { display:block; font-size:14px; color:var(--text-secondary); margin:14px 0 6px; }
|
||||
.input { width:100%; padding:11px; border:2px solid var(--border-color); border-radius:8px; background:var(--bg-input,#fff); color:var(--text-primary); font-size:14px; }
|
||||
.row { display:grid; grid-template-columns:1fr 1fr 1fr; gap:12px; }
|
||||
.chk { display:flex; align-items:center; gap:9px; margin-top:14px; color:var(--text-primary); font-size:14px; }
|
||||
.btn { padding:11px 18px; border:none; border-radius:8px; font-weight:600; cursor:pointer; font-size:14px; text-decoration:none; display:inline-block; }
|
||||
.btn-primary { background:var(--accent,#667eea); color:#fff; } .btn-secondary { background:var(--bg-hover); color:var(--text-primary); border:1px solid var(--border-color); }
|
||||
.alert { padding:12px 14px; border-radius:9px; font-size:14px; margin-bottom:18px; max-width:680px; }
|
||||
.alert-error { background:#fee; border:1px solid #fcc; color:#c33; } .alert-ok { background:#efe; border:1px solid #cfc; color:#2a7; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1 style="font-size:24px;margin-bottom:20px;color:var(--text-primary);">🔧 Integration & Wartung</h1>
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<h1 class="page-title">Integration & Wartung</h1>
|
||||
<p class="page-subtitle">SSO, Webhooks, SMS-Versand und Aufbewahrungsfristen.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($error): ?><div class="alert alert-error"><?= htmlspecialchars($error) ?></div><?php endif; ?>
|
||||
<?php if ($success): ?><div class="alert alert-ok"><?= htmlspecialchars($success) ?></div><?php endif; ?>
|
||||
|
|
@ -124,12 +114,12 @@ label { display:block; font-size:14px; color:var(--text-secondary); margin:14px
|
|||
<label>Tageslimit Voucher pro Nicht-Admin-Benutzer (0 = unbegrenzt)</label>
|
||||
<input class="input" type="number" min="0" name="user_daily_voucher_limit" value="<?= $dailyLimit ?>" style="max-width:200px;">
|
||||
<label>Session-Speicher</label>
|
||||
<select class="input" name="session_driver" style="max-width:240px;">
|
||||
<select class="input" name="session_driver" style="max-width:340px;">
|
||||
<option value="php" <?= $sessionDriver==='php'?'selected':'' ?>>PHP-Standard (Dateien)</option>
|
||||
<option value="db" <?= $sessionDriver==='db'?'selected':'' ?>>Datenbank (ermöglicht „überall abmelden")</option>
|
||||
</select>
|
||||
<label>Captcha im öffentlichen Modus</label>
|
||||
<select class="input" name="captcha_mode" style="max-width:240px;">
|
||||
<select class="input" name="captcha_mode" style="max-width:340px;">
|
||||
<option value="off" <?= $captchaMode==='off'?'selected':'' ?>>Aus</option>
|
||||
<option value="math" <?= $captchaMode==='math'?'selected':'' ?>>Rechenaufgabe (ohne externen Dienst)</option>
|
||||
<option value="hcaptcha" <?= $captchaMode==='hcaptcha'?'selected':'' ?>>hCaptcha</option>
|
||||
|
|
@ -198,7 +188,7 @@ label { display:block; font-size:14px; color:var(--text-secondary); margin:14px
|
|||
<button class="btn btn-primary" type="submit" name="save">Speichern</button>
|
||||
</form>
|
||||
|
||||
</div><!-- /main-content -->
|
||||
</main>
|
||||
<script src="../assets/global.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -98,36 +98,26 @@ $adminBase = '';
|
|||
<title>Reporting – <?= htmlspecialchars($appTitle) ?></title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
|
||||
<?php require __DIR__ . '/../includes/admin_nav.php'; ?>
|
||||
<style>
|
||||
.card { background:var(--bg-card); border:1px solid var(--border-color); border-radius:14px; padding:22px; margin-bottom:20px; box-shadow:0 4px 14px var(--shadow); }
|
||||
.card h2 { font-size:15px; margin-bottom:14px; color:var(--text-primary); }
|
||||
.grid4 { display:grid; grid-template-columns:repeat(4,1fr); gap:16px; margin-bottom:20px; }
|
||||
.stat { background:var(--bg-card); border:1px solid var(--border-color); border-radius:14px; padding:20px; }
|
||||
.stat .n { font-size:28px; font-weight:700; color:var(--text-primary); } .stat .l { color:var(--text-muted); font-size:12.5px; margin-top:3px; }
|
||||
table { width:100%; border-collapse:collapse; } th,td { text-align:left; padding:10px 8px; font-size:14px; border-bottom:1px solid var(--border-color); color:var(--text-primary); } th { color:var(--text-muted); }
|
||||
.btn { padding:9px 15px; border:none; border-radius:8px; font-weight:600; font-size:13px; text-decoration:none; display:inline-block; cursor:pointer; }
|
||||
.btn-s { background:var(--bg-hover); color:var(--text-primary); border:1px solid var(--border-color); }
|
||||
.toolbar { display:flex; gap:10px; align-items:center; flex-wrap:wrap; margin-bottom:18px; }
|
||||
select.input { padding:9px; border:2px solid var(--border-color); border-radius:8px; background:var(--bg-input,#fff); color:var(--text-primary); }
|
||||
@media print { .sidebar,.header,.toolbar,.no-print { display:none !important; } .main-content { margin:0 !important; } }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1 style="font-size:24px;margin-bottom:18px;color:var(--text-primary);">📊 Reporting</h1>
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<h1 class="page-title">Reporting</h1>
|
||||
<p class="page-subtitle">Auswertungen nach Zeitraum, Site und Benutzer.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="toolbar no-print">
|
||||
<form method="get" style="display:flex;gap:8px;align-items:center;">
|
||||
<label style="color:var(--text-muted);font-size:13px;">Zeitraum:</label>
|
||||
<label style="margin:0;">Zeitraum</label>
|
||||
<select class="input" name="days" onchange="this.form.submit()">
|
||||
<?php foreach ([7,30,90,365] as $d): ?>
|
||||
<option value="<?= $d ?>" <?= $days===$d?'selected':'' ?>><?= $d ?> Tage</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</form>
|
||||
<a class="btn btn-s" href="?export=daily&days=<?= $days ?>">⬇️ CSV (täglich)</a>
|
||||
<a class="btn btn-s" href="?export=per_site">⬇️ CSV (pro Site)</a>
|
||||
<a class="btn btn-s" href="?export=per_user">⬇️ CSV (pro Nutzer)</a>
|
||||
<button class="btn btn-s" onclick="window.print()">🖨️ Drucken/PDF</button>
|
||||
<a class="btn btn-secondary" href="?export=daily&days=<?= $days ?>"><i class="fas fa-download"></i> CSV (täglich)</a>
|
||||
<a class="btn btn-secondary" href="?export=per_site"><i class="fas fa-download"></i> CSV (pro Site)</a>
|
||||
<a class="btn btn-secondary" href="?export=per_user"><i class="fas fa-download"></i> CSV (pro Nutzer)</a>
|
||||
<button class="btn btn-secondary" onclick="window.print()"><i class="fas fa-print"></i> Drucken/PDF</button>
|
||||
</div>
|
||||
|
||||
<div class="grid4">
|
||||
|
|
@ -161,13 +151,23 @@ select.input { padding:9px; border:2px solid var(--border-color); border-radius:
|
|||
</table>
|
||||
</div>
|
||||
|
||||
</div><!-- /main-content -->
|
||||
</main>
|
||||
<script src="../assets/global.js"></script>
|
||||
<script>
|
||||
const styles = getComputedStyle(document.documentElement);
|
||||
const accent = styles.getPropertyValue('--accent').trim();
|
||||
const grid = styles.getPropertyValue('--border-color').trim();
|
||||
const muted = styles.getPropertyValue('--text-muted').trim();
|
||||
new Chart(document.getElementById('chart'), {
|
||||
type:'line',
|
||||
data:{ labels: <?= json_encode($chartLabels) ?>, datasets:[{ label:'Voucher', data: <?= json_encode($chartData) ?>, borderColor:'#667eea', backgroundColor:'rgba(102,126,234,.15)', fill:true, tension:.3 }] },
|
||||
options:{ plugins:{legend:{display:false}}, scales:{y:{beginAtZero:true,ticks:{precision:0}}} }
|
||||
data:{ labels: <?= json_encode($chartLabels) ?>, datasets:[{ label:'Voucher', data: <?= json_encode($chartData) ?>, borderColor: accent, backgroundColor: accent + '22', fill:true, tension:.35, pointRadius:0, pointHoverRadius:4, borderWidth:2 }] },
|
||||
options:{
|
||||
plugins:{legend:{display:false}},
|
||||
scales:{
|
||||
y:{ beginAtZero:true, border:{display:false}, grid:{color:grid, drawTicks:false}, ticks:{precision:0, color:muted, padding:10, font:{size:11}} },
|
||||
x:{ border:{display:false}, grid:{display:false}, ticks:{color:muted, padding:8, font:{size:11}} }
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -93,37 +93,24 @@ $activeSessions = $dbSessions ? $auth->activeSessionCount() : 0;
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Zwei-Faktor-Authentifizierung – <?= htmlspecialchars($appTitle) ?></title>
|
||||
<?php if (!$totpEnabled && $hasPassword): ?>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js" integrity="sha512-CNgIRecGo7nphbeZ04Sc13ka07paqdeTu0WR1IM4kNcpmBAUSHSe2keRB6Q5pBUtIxCY7bQMsVB0ANBpd6JDg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js" integrity="sha512-CNgIRecGo7nphbeZ04Sc13ka07paqdeTu0WR1IM4kNcpmBAUSHSQX0FslNhTDadL4O5SAGapGt4FodqL8My0mA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
<?php endif; ?>
|
||||
<style>
|
||||
* { margin:0; padding:0; box-sizing:border-box; font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif; }
|
||||
body { background:linear-gradient(135deg,#667eea 0%,#764ba2 100%); min-height:100vh; display:flex; align-items:center; justify-content:center; padding:20px; }
|
||||
.card { background:#fff; border-radius:18px; box-shadow:0 20px 60px rgba(0,0,0,.3); max-width:480px; width:100%; padding:36px; }
|
||||
h1 { font-size:22px; color:#333; margin-bottom:6px; }
|
||||
.sub { color:#777; font-size:14px; margin-bottom:24px; }
|
||||
.alert { padding:12px 14px; border-radius:9px; font-size:14px; margin-bottom:18px; }
|
||||
.alert-error { background:#fee; border:1px solid #fcc; color:#c33; }
|
||||
.alert-ok { background:#efe; border:1px solid #cfc; color:#2a7; }
|
||||
.status { display:inline-flex; align-items:center; gap:8px; padding:6px 12px; border-radius:8px; font-size:13px; font-weight:600; margin-bottom:20px; }
|
||||
.on { background:#e3f6ea; color:#2a7; } .off { background:#fdeaea; color:#c33; }
|
||||
.qr { display:flex; justify-content:center; margin:18px 0; }
|
||||
.secret { font-family:monospace; background:#f5f6fa; padding:10px; border-radius:8px; text-align:center; letter-spacing:2px; word-break:break-all; font-size:14px; margin-bottom:18px; }
|
||||
ol { margin:0 0 18px 18px; color:#555; font-size:14px; line-height:1.7; }
|
||||
label { display:block; font-size:14px; color:#555; margin-bottom:8px; font-weight:500; }
|
||||
input[type=text] { width:100%; padding:13px; border:2px solid #e0e0e0; border-radius:10px; font-size:18px; letter-spacing:6px; text-align:center; }
|
||||
.btn { width:100%; padding:14px; border:none; border-radius:10px; font-size:15px; font-weight:600; cursor:pointer; margin-top:14px; }
|
||||
.btn-primary { background:#667eea; color:#fff; } .btn-danger { background:#e25555; color:#fff; }
|
||||
.back { display:block; text-align:center; margin-top:20px; color:#667eea; text-decoration:none; font-size:14px; }
|
||||
.codes-box { background:#fff8e6; border:1px solid #ffe3a3; border-radius:10px; padding:16px; margin-bottom:18px; }
|
||||
.codes-box strong { font-size:15px; } .codes-box p { color:#8a6d2f; font-size:13px; margin:6px 0 12px; }
|
||||
.codes { display:grid; grid-template-columns:1fr 1fr; gap:8px; }
|
||||
.codes span { font-family:monospace; background:#fff; border:1px solid #ffe3a3; border-radius:6px; padding:8px; text-align:center; letter-spacing:2px; font-size:14px; }
|
||||
</style>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
<link rel="stylesheet" href="../assets/global.css">
|
||||
<script>(function(){ const t=localStorage.getItem('theme')||'light'; document.documentElement.setAttribute('data-theme',t); })();</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
<h1>🔐 Zwei-Faktor-Authentifizierung</h1>
|
||||
<p class="sub">Konto: <?= htmlspecialchars($user['email']) ?></p>
|
||||
<body class="app-body focus-page">
|
||||
<div class="focus-card card">
|
||||
<div class="focus-head">
|
||||
<span class="focus-icon"><i class="fas fa-shield-halved"></i></span>
|
||||
<div>
|
||||
<h1>Zwei-Faktor-Authentifizierung</h1>
|
||||
<p class="sub">Konto: <?= htmlspecialchars($user['email']) ?></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($setupRequired && !$totpEnabled): ?>
|
||||
<div class="alert alert-error">Aus Sicherheitsgründen ist 2FA für Administratoren verpflichtend. Bitte jetzt einrichten.</div>
|
||||
|
|
@ -133,7 +120,7 @@ input[type=text] { width:100%; padding:13px; border:2px solid #e0e0e0; border-ra
|
|||
|
||||
<?php if (!empty($backupCodes)): ?>
|
||||
<div class="codes-box">
|
||||
<strong>🔑 Recovery-Codes</strong>
|
||||
<strong><i class="fas fa-key"></i> Recovery-Codes</strong>
|
||||
<p>Bewahren Sie diese sicher auf. Jeder Code funktioniert <em>einmal</em>, falls Sie keinen Zugriff auf Ihre App haben.</p>
|
||||
<div class="codes">
|
||||
<?php foreach ($backupCodes as $c): ?><span><?= htmlspecialchars($c) ?></span><?php endforeach; ?>
|
||||
|
|
@ -142,22 +129,22 @@ input[type=text] { width:100%; padding:13px; border:2px solid #e0e0e0; border-ra
|
|||
<?php endif; ?>
|
||||
|
||||
<?php if (!$hasPassword): ?>
|
||||
<div class="status off">● Nicht verfügbar</div>
|
||||
<div class="status off"><i class="fas fa-circle-minus"></i> Nicht verfügbar</div>
|
||||
<p class="sub">Ihr Konto meldet sich über Microsoft 365 an. 2FA wird dort in Ihrem Microsoft-Konto verwaltet.</p>
|
||||
<?php elseif ($totpEnabled): ?>
|
||||
<div class="status on">● Aktiv</div>
|
||||
<div class="status on"><i class="fas fa-circle-check"></i> Aktiv</div>
|
||||
<p class="sub">Bei jeder Anmeldung wird zusätzlich ein Code aus Ihrer Authenticator-App abgefragt.<br>
|
||||
Verbleibende Recovery-Codes: <strong><?= (int)$auth->backupCodesRemaining($user) ?></strong></p>
|
||||
<form method="post" style="margin-bottom:10px;">
|
||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrf) ?>">
|
||||
<button type="submit" name="regen_codes" class="btn btn-secondary" style="background:#eef0ff;color:#5a63d6;width:100%;">Recovery-Codes neu erzeugen</button>
|
||||
<button type="submit" name="regen_codes" class="btn btn-secondary btn-lg btn-block">Recovery-Codes neu erzeugen</button>
|
||||
</form>
|
||||
<form method="post" onsubmit="return confirm('2FA wirklich deaktivieren?');">
|
||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrf) ?>">
|
||||
<button type="submit" name="disable_totp" class="btn btn-danger">2FA deaktivieren</button>
|
||||
<button type="submit" name="disable_totp" class="btn btn-danger btn-lg btn-block">2FA deaktivieren</button>
|
||||
</form>
|
||||
<?php else: ?>
|
||||
<div class="status off">● Inaktiv</div>
|
||||
<div class="status off"><i class="fas fa-circle-minus"></i> Inaktiv</div>
|
||||
<ol>
|
||||
<li>Authenticator-App öffnen (Google Authenticator, Authy, Microsoft Authenticator …)</li>
|
||||
<li>QR-Code scannen <em>oder</em> Secret manuell eingeben</li>
|
||||
|
|
@ -168,27 +155,28 @@ input[type=text] { width:100%; padding:13px; border:2px solid #e0e0e0; border-ra
|
|||
<form method="post">
|
||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrf) ?>">
|
||||
<label for="code">6-stelliger Code</label>
|
||||
<input type="text" id="code" name="code" inputmode="numeric" pattern="[0-9]*" maxlength="6" autocomplete="one-time-code" required>
|
||||
<button type="submit" name="enable_totp" class="btn btn-primary">2FA aktivieren</button>
|
||||
<input type="text" id="code" name="code" class="code-input" inputmode="numeric" pattern="[0-9]*" maxlength="6" autocomplete="one-time-code" required placeholder="123456">
|
||||
<button type="submit" name="enable_totp" class="btn btn-primary btn-lg btn-block" style="margin-top:14px;">2FA aktivieren</button>
|
||||
</form>
|
||||
<script>
|
||||
new QRCode(document.getElementById('qrcode'), {
|
||||
text: <?= json_encode($otpUri) ?>, width: 180, height: 180,
|
||||
text: <?= json_encode($otpUri) ?>, width: 168, height: 168,
|
||||
colorDark: '#101625', colorLight: '#ffffff',
|
||||
correctLevel: QRCode.CorrectLevel.M
|
||||
});
|
||||
</script>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($dbSessions): ?>
|
||||
<hr style="margin:20px 0;border:none;border-top:1px solid #eee;">
|
||||
<hr>
|
||||
<p class="sub">Aktive Sitzungen: <strong><?= (int)$activeSessions ?></strong></p>
|
||||
<form method="post" onsubmit="return confirm('Alle anderen Sitzungen abmelden?');">
|
||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrf) ?>">
|
||||
<button type="submit" name="logout_others" class="btn" style="background:#eef0ff;color:#5a63d6;width:100%;">Auf allen anderen Geräten abmelden</button>
|
||||
<button type="submit" name="logout_others" class="btn btn-secondary btn-lg btn-block">Auf allen anderen Geräten abmelden</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
|
||||
<a class="back" href="../index.php">← Zurück</a>
|
||||
<div class="auth-links"><a class="back-link" href="../index.php"><i class="fas fa-arrow-left"></i> Zurück</a></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -207,54 +207,18 @@ $adminBase = '';
|
|||
<?php if (!empty($cs['tinymce_api_key'])): ?>
|
||||
<script src="https://cdn.tiny.cloud/1/<?= htmlspecialchars($cs['tinymce_api_key']) ?>/tinymce/6/tinymce.min.js"></script>
|
||||
<?php else: ?>
|
||||
<script src="https://cdn.tiny.cloud/1/no-api-key/tinymce/6/tinymce.min.js"></script>
|
||||
<!-- Ohne API-Key die GPL-Variante von cdnjs laden: gleiche Funktionen, kein Hinweis-Banner. -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/tinymce/6.8.3/tinymce.min.js" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
<script>window.TINYMCE_BASE_URL = 'https://cdnjs.cloudflare.com/ajax/libs/tinymce/6.8.3';</script>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php include __DIR__ . '/../includes/admin_nav.php'; ?>
|
||||
|
||||
<style>
|
||||
.page-header { margin-bottom: 30px; }
|
||||
.page-title { font-size: 28px; font-weight: 600; color: var(--text-primary); margin-bottom: 8px; }
|
||||
.alert { padding: 14px 20px; border-radius: 10px; margin-bottom: 25px; font-size: 14px; display: flex; align-items: center; gap: 10px; }
|
||||
.alert-error { background: #fee; border: 1px solid #fcc; color: #c33; }
|
||||
.alert-success { background: #efe; border: 1px solid #cfc; color: #3c3; }
|
||||
.tab-container { background: var(--bg-card); border-radius: 15px; box-shadow: 0 2px 10px var(--shadow); border: 1px solid var(--border-color); overflow: hidden; }
|
||||
.tab-navigation { display: flex; background: var(--bg-table-head); border-bottom: 2px solid var(--border-color); overflow-x: auto; position: sticky; top: 70px; z-index: 50; }
|
||||
.tab-button { padding: 15px 20px; background: transparent; border: none; border-bottom: 3px solid transparent; cursor: pointer; font-size: 13px; font-weight: 500; color: var(--text-secondary); transition: all 0.3s; white-space: nowrap; display: flex; align-items: center; gap: 7px; }
|
||||
.tab-button:hover { background: rgba(102,126,234,0.1); color: var(--accent); }
|
||||
.tab-button.active { color: var(--accent); border-bottom-color: var(--accent); background: var(--bg-card); }
|
||||
.tab-content { display: none; padding: 30px; animation: fadeIn 0.2s; }
|
||||
.tab-content.active { display: block; }
|
||||
@keyframes fadeIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }
|
||||
.form-group { margin-bottom: 20px; }
|
||||
label { display: block; margin-bottom: 7px; color: var(--text-secondary); font-weight: 500; font-size: 14px; }
|
||||
input[type="text"], input[type="url"], input[type="password"], input[type="number"], input[type="email"], select, textarea { width: 100%; padding: 11px 14px; border: 2px solid var(--border-color); border-radius: 8px; font-size: 14px; transition: border-color 0.2s; font-family: inherit; background: var(--bg-input); color: var(--text-primary); }
|
||||
input:focus, textarea:focus, select:focus { outline: none; border-color: var(--accent); }
|
||||
textarea { resize: vertical; min-height: 100px; }
|
||||
.checkbox-group { display: flex; align-items: center; gap: 10px; }
|
||||
.checkbox-group input { width: auto; accent-color: var(--accent); }
|
||||
.help-text { font-size: 12px; color: var(--text-muted); margin-top: 5px; }
|
||||
.info-box { background: #e7f3ff; border: 1px solid #b3d9ff; border-radius: 8px; padding: 15px; margin-bottom: 20px; }
|
||||
.info-box h4 { color: #0066cc; margin-bottom: 8px; font-size: 14px; }
|
||||
.info-box p { color: #004d99; font-size: 13px; line-height: 1.5; }
|
||||
.section-divider { border: none; border-top: 2px solid var(--border-color); margin: 28px 0; }
|
||||
.form-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 20px; }
|
||||
.placeholder-info { background: #fff9e6; border: 1px solid #ffe066; border-radius: 8px; padding: 14px; margin: 14px 0; }
|
||||
.placeholder-info h4 { color: #996600; margin-bottom: 8px; font-size: 14px; }
|
||||
.placeholder-info code { background: #fff; padding: 2px 6px; border-radius: 3px; font-size: 12px; color: #d63384; }
|
||||
.placeholder-list { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; }
|
||||
.btn-primary { background: var(--accent); color: white; }
|
||||
.btn-primary:hover { background: var(--accent-hover); }
|
||||
.btn-danger { background: #dc3545; color: white; }
|
||||
.btn-small { padding: 6px 12px; font-size: 13px; }
|
||||
.token-box { background: var(--bg-hover); padding: 14px; border-radius: 6px; font-family: monospace; word-break: break-all; color: var(--text-primary); }
|
||||
code { background: var(--code-bg); color: var(--accent); padding: 2px 6px; border-radius: 4px; font-size: 13px; }
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<div class="page-header">
|
||||
<h1 class="page-title"><?= __('settings_title') ?></h1>
|
||||
<p style="color: var(--text-muted); font-size: 14px;"><?= __('settings_subtitle') ?></p>
|
||||
<div>
|
||||
<h1 class="page-title"><?= __('settings_title') ?></h1>
|
||||
<p class="page-subtitle"><?= __('settings_subtitle') ?></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($error): ?>
|
||||
|
|
@ -486,7 +450,7 @@ $adminBase = '';
|
|||
</div>
|
||||
</div>
|
||||
|
||||
</div><!-- main-content -->
|
||||
</main>
|
||||
|
||||
<script src="../assets/global.js"></script>
|
||||
<script>
|
||||
|
|
@ -551,15 +515,25 @@ async function testCronJob() {
|
|||
}
|
||||
|
||||
function initTinyMCE() {
|
||||
tinymce.init({
|
||||
if (typeof tinymce === 'undefined') return;
|
||||
const dark = document.documentElement.getAttribute('data-theme') === 'dark';
|
||||
const config = {
|
||||
selector: '.tinymce-editor',
|
||||
height: 350,
|
||||
menubar: false,
|
||||
plugins: ['advlist','autolink','lists','link','searchreplace','visualblocks','code','fullscreen','table','help','wordcount'],
|
||||
toolbar: 'undo redo | blocks | bold italic forecolor | alignleft aligncenter alignright | bullist numlist | removeformat | help',
|
||||
content_style: 'body { font-family: -apple-system, sans-serif; font-size: 14px; line-height: 1.6; }',
|
||||
content_style: "body { font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; font-size: 14px; line-height: 1.6; }",
|
||||
skin: dark ? 'oxide-dark' : 'oxide',
|
||||
content_css: dark ? 'dark' : 'default',
|
||||
branding: false, promotion: false
|
||||
});
|
||||
};
|
||||
if (window.TINYMCE_BASE_URL) {
|
||||
tinymce.baseURL = window.TINYMCE_BASE_URL;
|
||||
config.base_url = window.TINYMCE_BASE_URL;
|
||||
config.suffix = '.min';
|
||||
}
|
||||
tinymce.init(config);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -101,50 +101,6 @@ $currentPage = 'sites';
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title><?= __('sites_title') ?> – <?= htmlspecialchars($appTitle) ?></title>
|
||||
<?php include __DIR__ . '/../includes/admin_nav.php'; ?>
|
||||
<style>
|
||||
.page-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 28px; flex-wrap: wrap; gap: 12px; }
|
||||
.page-title { font-size: 26px; font-weight: 700; color: var(--text-primary); }
|
||||
.alert { padding: 13px 18px; border-radius: 10px; font-size: 14px; margin-bottom: 20px; }
|
||||
.alert-error { background: #fee; border: 1px solid #fcc; color: #c33; }
|
||||
.alert-success { background: #efe; border: 1px solid #cfc; color: #3c3; }
|
||||
.sites-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(330px, 1fr)); gap: 18px; }
|
||||
.site-card { background: var(--bg-card); border: 2px solid var(--border-color); border-radius: 14px; padding: 20px; transition: border-color .2s, box-shadow .2s; }
|
||||
.site-card:hover { border-color: var(--accent); box-shadow: 0 4px 14px rgba(102,126,234,.15); }
|
||||
.site-card-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 14px; }
|
||||
.site-name { font-size: 17px; font-weight: 600; color: var(--text-primary); margin-bottom: 4px; }
|
||||
.site-id-label { font-size: 12px; color: var(--text-muted); font-family: monospace; }
|
||||
.site-info { margin: 14px 0; font-size: 13px; color: var(--text-secondary); }
|
||||
.site-info-item { display: flex; align-items: center; gap: 8px; margin-bottom: 7px; }
|
||||
.site-actions { display: flex; gap: 7px; margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--border-color); flex-wrap: wrap; }
|
||||
.badge { display: inline-block; padding: 3px 9px; border-radius: 5px; font-size: 11px; font-weight: 500; margin: 2px; }
|
||||
.badge-success { background: #d4edda; color: #155724; }
|
||||
.badge-warning { background: #fff3cd; color: #856404; }
|
||||
.badge-info { background: var(--bg-badge-info); color: var(--text-badge-info); }
|
||||
.btn { padding: 8px 15px; border-radius: 8px; border: none; font-weight: 500; cursor: pointer; text-decoration: none; display: inline-flex; align-items: center; gap: 7px; transition: all .2s; font-size: 13px; }
|
||||
.btn-primary { background: var(--accent); color: white; }
|
||||
.btn-primary:hover { background: var(--accent-hover); }
|
||||
.btn-secondary { background: var(--bg-hover); color: var(--text-secondary); border: 1px solid var(--border-color); }
|
||||
.btn-secondary:hover { background: var(--border-color); }
|
||||
.btn-danger { background: var(--danger); color: white; }
|
||||
.btn-success { background: var(--success); color: white; }
|
||||
.btn-sm { padding: 6px 11px; font-size: 12px; }
|
||||
.modal { display: none; position: fixed; inset: 0; background: var(--modal-overlay); z-index: 1000; align-items: center; justify-content: center; }
|
||||
.modal.active { display: flex; }
|
||||
.modal-content { background: var(--bg-card); border-radius: 14px; max-width: 580px; width: 90%; max-height: 90vh; overflow-y: auto; border: 1px solid var(--border-color); }
|
||||
.modal-header { padding: 22px 25px; border-bottom: 1px solid var(--border-color); display: flex; justify-content: space-between; align-items: center; }
|
||||
.modal-title { font-size: 19px; font-weight: 600; color: var(--text-primary); }
|
||||
.modal-close { background: none; border: none; font-size: 22px; cursor: pointer; color: var(--text-muted); }
|
||||
.modal-body { padding: 22px 25px; }
|
||||
.form-group { margin-bottom: 17px; }
|
||||
.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
|
||||
label { display: block; margin-bottom: 7px; color: var(--text-secondary); font-weight: 500; font-size: 14px; }
|
||||
input[type="text"], input[type="password"], input[type="url"] { width: 100%; padding: 11px; border: 2px solid var(--border-color); border-radius: 8px; font-size: 14px; background: var(--bg-input); color: var(--text-primary); transition: border-color .2s; }
|
||||
input:focus { outline: none; border-color: var(--accent); }
|
||||
.checkbox-group { display: flex; align-items: center; gap: 10px; }
|
||||
.checkbox-group input { width: auto; }
|
||||
.empty-card { background: var(--bg-card); border-radius: 14px; border: 1px solid var(--border-color); padding: 60px 20px; text-align: center; color: var(--text-muted); }
|
||||
@media(max-width:768px){ .main-content{ margin-left:0!important; } .form-grid{ grid-template-columns:1fr; } }
|
||||
</style>
|
||||
|
||||
<div class="page-header">
|
||||
<h1 class="page-title"><?= __('sites_title') ?></h1>
|
||||
|
|
@ -210,7 +166,7 @@ $currentPage = 'sites';
|
|||
<?= $site['is_active'] ? __('sites_deactivate') : __('sites_activate') ?>
|
||||
</a>
|
||||
<a href="?delete=<?= $site['id'] ?>&token=<?= $auth->getCsrfToken() ?>"
|
||||
class="btn btn-danger btn-sm"
|
||||
class="btn btn-danger-soft btn-sm"
|
||||
onclick="return confirm('Möchten Sie diese Site wirklich löschen?')">
|
||||
<i class="fas fa-trash"></i>
|
||||
</a>
|
||||
|
|
@ -220,7 +176,7 @@ $currentPage = 'sites';
|
|||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
</div><!-- /main-content -->
|
||||
</main>
|
||||
|
||||
<!-- Add Site Modal -->
|
||||
<div id="addSiteModal" class="modal">
|
||||
|
|
|
|||
|
|
@ -100,51 +100,11 @@ $adminBase = '';
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title><?= __('templates_title') ?> - <?= htmlspecialchars($appTitle) ?></title>
|
||||
<?php include __DIR__ . '/../includes/admin_nav.php'; ?>
|
||||
<style>
|
||||
.page-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 30px; flex-wrap: wrap; gap: 15px; }
|
||||
.page-title { font-size: 28px; font-weight: 600; color: var(--text-primary); }
|
||||
.alert { padding: 14px 20px; border-radius: 10px; margin-bottom: 25px; font-size: 14px; }
|
||||
.alert-error { background: #fee; border: 1px solid #fcc; color: #c33; }
|
||||
.alert-success { background: #efe; border: 1px solid #cfc; color: #3c3; }
|
||||
.card { background: var(--bg-card); border-radius: 15px; box-shadow: 0 2px 10px var(--shadow); border: 1px solid var(--border-color); overflow: hidden; margin-bottom: 25px; }
|
||||
.card-header { padding: 20px 25px; border-bottom: 1px solid var(--border-color); display: flex; justify-content: space-between; align-items: center; }
|
||||
.card-title { font-size: 18px; font-weight: 600; color: var(--text-primary); }
|
||||
.table { width: 100%; border-collapse: collapse; }
|
||||
.table th { text-align: left; padding: 12px 15px; background: var(--bg-table-head); color: var(--text-secondary); font-weight: 600; font-size: 12px; text-transform: uppercase; letter-spacing: 0.5px; }
|
||||
.table td { padding: 14px 15px; border-bottom: 1px solid var(--border-color); color: var(--text-primary); font-size: 14px; }
|
||||
.table tr:last-child td { border-bottom: none; }
|
||||
.table tr:hover td { background: var(--bg-hover); }
|
||||
.badge { display: inline-block; padding: 3px 10px; border-radius: 6px; font-size: 11px; font-weight: 500; }
|
||||
.badge-success { background: #d4edda; color: #155724; }
|
||||
.badge-secondary { background: var(--bg-hover); color: var(--text-muted); }
|
||||
.btn-primary { background: var(--accent); color: white; }
|
||||
.btn-primary:hover { background: var(--accent-hover); }
|
||||
.btn-danger { background: var(--danger); color: white; }
|
||||
.btn-small { padding: 6px 12px; font-size: 12px; }
|
||||
.modal { display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: var(--modal-overlay); z-index: 500; align-items: center; justify-content: center; }
|
||||
.modal.active { display: flex; }
|
||||
.modal-content { background: var(--bg-card); border-radius: 15px; max-width: 520px; width: 90%; max-height: 90vh; overflow-y: auto; }
|
||||
.modal-header { padding: 22px 25px; border-bottom: 1px solid var(--border-color); display: flex; justify-content: space-between; align-items: center; }
|
||||
.modal-title { font-size: 18px; font-weight: 600; color: var(--text-primary); }
|
||||
.modal-close { background: none; border: none; font-size: 22px; cursor: pointer; color: var(--text-muted); }
|
||||
.modal-body { padding: 25px; }
|
||||
.form-group { margin-bottom: 18px; }
|
||||
label { display: block; margin-bottom: 7px; color: var(--text-secondary); font-weight: 500; font-size: 14px; }
|
||||
input[type="text"], input[type="number"], textarea, select { width: 100%; padding: 11px 14px; border: 2px solid var(--border-color); border-radius: 8px; font-size: 14px; background: var(--bg-input); color: var(--text-primary); transition: border-color 0.2s; font-family: inherit; }
|
||||
input:focus, textarea:focus { outline: none; border-color: var(--accent); }
|
||||
.checkbox-group { display: flex; align-items: center; gap: 10px; }
|
||||
.checkbox-group input { width: auto; accent-color: var(--accent); }
|
||||
.help-text { font-size: 12px; color: var(--text-muted); margin-top: 4px; }
|
||||
.empty-state { text-align: center; padding: 60px 20px; color: var(--text-muted); }
|
||||
.empty-state i { font-size: 48px; margin-bottom: 20px; opacity: 0.3; display: block; }
|
||||
.duration-badge { display: inline-flex; align-items: center; gap: 5px; background: var(--bg-hover); padding: 3px 10px; border-radius: 20px; font-size: 12px; color: var(--text-secondary); }
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<h1 class="page-title"><?= __('templates_title') ?></h1>
|
||||
<p style="color: var(--text-muted); font-size: 14px; margin-top: 5px;"><?= __('templates_subtitle') ?></p>
|
||||
<p class="page-subtitle"><?= __('templates_subtitle') ?></p>
|
||||
</div>
|
||||
<button onclick="openAddModal()" class="btn btn-primary">
|
||||
<i class="fas fa-plus"></i> <?= __('templates_add') ?>
|
||||
|
|
@ -172,6 +132,7 @@ $adminBase = '';
|
|||
</div>
|
||||
<?php else: ?>
|
||||
<div style="overflow-x: auto;">
|
||||
<div class="table-container">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
|
|
@ -216,17 +177,18 @@ $adminBase = '';
|
|||
class="btn btn-secondary btn-small"><i class="fas fa-edit"></i></button>
|
||||
<a href="?delete=<?= $t['id'] ?>&token=<?= $auth->getCsrfToken() ?>"
|
||||
onclick="return confirm('Profil wirklich löschen?')"
|
||||
class="btn btn-danger btn-small"><i class="fas fa-trash"></i></a>
|
||||
class="btn btn-danger-soft btn-small"><i class="fas fa-trash"></i></a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
</div><!-- main-content -->
|
||||
</main>
|
||||
|
||||
<!-- Modal: Hinzufügen -->
|
||||
<div id="addModal" class="modal">
|
||||
|
|
|
|||
|
|
@ -171,52 +171,6 @@ $currentPage = 'users';
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title><?= __('users_title') ?> – <?= htmlspecialchars($appTitle) ?></title>
|
||||
<?php include __DIR__ . '/../includes/admin_nav.php'; ?>
|
||||
<style>
|
||||
.page-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 28px; flex-wrap: wrap; gap: 12px; }
|
||||
.page-title { font-size: 26px; font-weight: 700; color: var(--text-primary); }
|
||||
.card { background: var(--bg-card); border-radius: 14px; box-shadow: 0 2px 10px var(--shadow); border: 1px solid var(--border-color); overflow: hidden; margin-bottom: 24px; }
|
||||
.card-header { padding: 18px 22px; border-bottom: 1px solid var(--border-color); }
|
||||
.card-title { font-size: 16px; font-weight: 600; color: var(--text-primary); }
|
||||
.alert { padding: 13px 18px; border-radius: 10px; font-size: 14px; margin-bottom: 20px; }
|
||||
.alert-error { background: #fee; border: 1px solid #fcc; color: #c33; }
|
||||
.alert-success { background: #efe; border: 1px solid #cfc; color: #3c3; }
|
||||
.table { width: 100%; border-collapse: collapse; }
|
||||
.table th { text-align: left; padding: 12px 15px; background: var(--bg-table-head); color: var(--text-muted); font-weight: 600; font-size: 12px; text-transform: uppercase; letter-spacing: .5px; }
|
||||
.table td { padding: 13px 15px; border-bottom: 1px solid var(--border-color); color: var(--text-primary); font-size: 14px; }
|
||||
.table tr:last-child td { border-bottom: none; }
|
||||
.table tr:hover { background: var(--bg-hover); }
|
||||
.badge { display: inline-block; padding: 3px 9px; border-radius: 5px; font-size: 11px; font-weight: 500; margin: 2px; }
|
||||
.badge-success { background: #d4edda; color: #155724; }
|
||||
.badge-warning { background: #fff3cd; color: #856404; }
|
||||
.badge-danger { background: #f8d7da; color: #721c24; }
|
||||
.badge-info { background: var(--bg-badge-info); color: var(--text-badge-info); }
|
||||
.btn { padding: 8px 16px; border-radius: 8px; border: none; font-weight: 500; cursor: pointer; text-decoration: none; display: inline-flex; align-items: center; gap: 7px; transition: all .2s; font-size: 13px; }
|
||||
.btn-primary { background: var(--accent); color: white; }
|
||||
.btn-primary:hover { background: var(--accent-hover); }
|
||||
.btn-secondary { background: var(--bg-hover); color: var(--text-secondary); border: 1px solid var(--border-color); }
|
||||
.btn-secondary:hover { background: var(--border-color); }
|
||||
.btn-danger { background: var(--danger); color: white; }
|
||||
.btn-warning { background: var(--warning); color: #333; }
|
||||
.btn-sm { padding: 5px 10px; font-size: 12px; }
|
||||
.modal { display: none; position: fixed; inset: 0; background: var(--modal-overlay); z-index: 1000; align-items: center; justify-content: center; }
|
||||
.modal.active { display: flex; }
|
||||
.modal-content { background: var(--bg-card); border-radius: 14px; max-width: 580px; width: 90%; max-height: 90vh; overflow-y: auto; border: 1px solid var(--border-color); }
|
||||
.modal-header { padding: 22px 25px; border-bottom: 1px solid var(--border-color); display: flex; justify-content: space-between; align-items: center; }
|
||||
.modal-title { font-size: 19px; font-weight: 600; color: var(--text-primary); }
|
||||
.modal-close { background: none; border: none; font-size: 22px; cursor: pointer; color: var(--text-muted); }
|
||||
.modal-body { padding: 22px 25px; }
|
||||
.form-group { margin-bottom: 18px; }
|
||||
label { display: block; margin-bottom: 7px; color: var(--text-secondary); font-weight: 500; font-size: 14px; }
|
||||
input[type="text"], input[type="email"], input[type="password"] { width: 100%; padding: 11px; border: 2px solid var(--border-color); border-radius: 8px; font-size: 14px; background: var(--bg-input); color: var(--text-primary); transition: border-color .2s; }
|
||||
input:focus { outline: none; border-color: var(--accent); }
|
||||
.checkbox-group { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
|
||||
.checkbox-group input { width: auto; }
|
||||
.site-selection { border: 1px solid var(--border-color); border-radius: 8px; padding: 12px; max-height: 180px; overflow-y: auto; background: var(--bg-hover); }
|
||||
.empty-state { text-align: center; padding: 50px 20px; color: var(--text-muted); }
|
||||
.empty-state i { font-size: 40px; margin-bottom: 15px; opacity: .3; display: block; }
|
||||
.action-btns { display: flex; gap: 5px; flex-wrap: wrap; }
|
||||
@media(max-width:768px){ .main-content{ margin-left:0!important; } .table th:nth-child(5),.table td:nth-child(5){ display:none; } }
|
||||
</style>
|
||||
|
||||
<div class="page-header">
|
||||
<h1 class="page-title"><?= __('users_title') ?></h1>
|
||||
|
|
@ -239,6 +193,7 @@ $currentPage = 'users';
|
|||
<div class="empty-state"><i class="fas fa-users"></i><p><?= __('users_none_found') ?></p></div>
|
||||
<?php else: ?>
|
||||
<div style="overflow-x:auto;">
|
||||
<div class="table-container">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
|
|
@ -320,7 +275,7 @@ $currentPage = 'users';
|
|||
</a>
|
||||
<?php endif; ?>
|
||||
<a href="?delete=<?= $user['id'] ?>&token=<?= $auth->getCsrfToken() ?>"
|
||||
class="btn btn-danger btn-sm" title="<?= __('btn_delete') ?>"
|
||||
class="btn btn-danger-soft btn-sm" title="<?= __('btn_delete') ?>"
|
||||
onclick="return confirm('Benutzer wirklich löschen?')">
|
||||
<i class="fas fa-trash"></i>
|
||||
</a>
|
||||
|
|
@ -332,11 +287,12 @@ $currentPage = 'users';
|
|||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div><!-- /main-content -->
|
||||
</main>
|
||||
|
||||
<!-- Add User Modal -->
|
||||
<div id="addUserModal" class="modal">
|
||||
|
|
|
|||
|
|
@ -130,80 +130,20 @@ $currentPage = 'vouchers';
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title><?= __('vouchers_title') ?> – <?= htmlspecialchars($appTitle) ?></title>
|
||||
<?php include __DIR__ . '/../includes/admin_nav.php'; ?>
|
||||
<style>
|
||||
.page-header { margin-bottom: 28px; }
|
||||
.page-title { font-size: 26px; font-weight: 700; color: var(--text-primary); display: flex; align-items: center; gap: 12px; }
|
||||
.page-subtitle { color: var(--text-muted); font-size: 14px; margin-top: 4px; }
|
||||
.live-indicator { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--success); }
|
||||
.live-indicator .dot { width: 8px; height: 8px; background: var(--success); border-radius: 50%; animation: pulse 2s infinite; }
|
||||
@keyframes pulse { 0%,100%{opacity:1}50%{opacity:.5} }
|
||||
.card { background: var(--bg-card); border-radius: 14px; box-shadow: 0 2px 10px var(--shadow); border: 1px solid var(--border-color); margin-bottom: 22px; overflow: hidden; }
|
||||
.card-header { padding: 18px 22px; border-bottom: 1px solid var(--border-color); display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 10px; }
|
||||
.card-title { font-size: 16px; font-weight: 600; color: var(--text-primary); }
|
||||
.card-body { padding: 22px; }
|
||||
.stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px,1fr)); gap: 16px; margin-bottom: 22px; }
|
||||
.stat-card { background: var(--bg-card); padding: 18px; border-radius: 12px; box-shadow: 0 2px 8px var(--shadow); border: 1px solid var(--border-color); }
|
||||
.stat-label { color: var(--text-muted); font-size: 13px; margin-bottom: 6px; }
|
||||
.stat-value { font-size: 28px; font-weight: 700; color: var(--text-primary); }
|
||||
.stat-value.valid { color: var(--success); }
|
||||
.stat-value.used { color: var(--warning); }
|
||||
.stat-value.expired { color: var(--danger); }
|
||||
.site-selector { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; }
|
||||
.site-selector select { padding: 10px 14px; border: 2px solid var(--border-color); border-radius: 8px; font-size: 14px; min-width: 220px; cursor: pointer; background: var(--bg-input); color: var(--text-primary); }
|
||||
.site-selector select:focus { outline: none; border-color: var(--accent); }
|
||||
.search-bar { padding: 10px 14px; border: 2px solid var(--border-color); border-radius: 8px; font-size: 14px; background: var(--bg-input); color: var(--text-primary); min-width: 200px; }
|
||||
.search-bar:focus { outline: none; border-color: var(--accent); }
|
||||
.btn { padding: 9px 18px; border-radius: 8px; border: none; font-weight: 500; cursor: pointer; text-decoration: none; display: inline-flex; align-items: center; gap: 7px; transition: all .2s; font-size: 13px; }
|
||||
.btn-primary { background: var(--accent); color: white; }
|
||||
.btn-primary:hover { background: var(--accent-hover); }
|
||||
.btn-secondary { background: var(--bg-hover); color: var(--text-secondary); border: 1px solid var(--border-color); }
|
||||
.btn-secondary:hover { background: var(--border-color); }
|
||||
.btn-danger { background: var(--danger); color: white; }
|
||||
.btn-danger:hover { opacity: .88; }
|
||||
.btn-success { background: var(--success); color: white; }
|
||||
.btn-sm { padding: 5px 11px; font-size: 12px; }
|
||||
.btn:disabled { opacity: .55; cursor: not-allowed; }
|
||||
.table-container { overflow-x: auto; }
|
||||
.table { width: 100%; border-collapse: collapse; }
|
||||
.table th { text-align: left; padding: 11px 14px; background: var(--bg-table-head); color: var(--text-muted); font-weight: 600; font-size: 11px; text-transform: uppercase; letter-spacing: .5px; white-space: nowrap; }
|
||||
.table td { padding: 13px 14px; border-bottom: 1px solid var(--border-color); color: var(--text-primary); font-size: 13px; }
|
||||
.table tr:last-child td { border-bottom: none; }
|
||||
.table tr:hover { background: var(--bg-hover); }
|
||||
.table tr.deleting { opacity: .45; pointer-events: none; }
|
||||
.badge { display: inline-block; padding: 3px 9px; border-radius: 5px; font-size: 11px; font-weight: 500; }
|
||||
.badge-success { background: #d4edda; color: #155724; }
|
||||
.badge-warning { background: #fff3cd; color: #856404; }
|
||||
.badge-danger { background: #f8d7da; color: #721c24; }
|
||||
.badge-info { background: var(--bg-badge-info); color: var(--text-badge-info); }
|
||||
code { background: var(--bg-hover); padding: 4px 8px; border-radius: 4px; font-family: 'Courier New', monospace; font-size: 12px; letter-spacing: 1px; }
|
||||
.empty-state { text-align: center; padding: 60px 20px; color: var(--text-muted); }
|
||||
.empty-state i { font-size: 42px; margin-bottom: 18px; opacity: .3; display: block; }
|
||||
.loading { display: flex; align-items: center; justify-content: center; padding: 60px; color: var(--text-muted); gap: 12px; }
|
||||
.loading i { font-size: 22px; animation: spin 1s linear infinite; }
|
||||
@keyframes spin { from{transform:rotate(0)}to{transform:rotate(360deg)} }
|
||||
.filter-row { display: flex; gap: 8px; flex-wrap: wrap; }
|
||||
.filter-btn { padding: 7px 14px; border: 2px solid var(--border-color); background: var(--bg-card); color: var(--text-secondary); border-radius: 8px; cursor: pointer; font-size: 13px; transition: all .2s; }
|
||||
.filter-btn:hover { border-color: var(--accent); }
|
||||
.filter-btn.active { border-color: var(--accent); background: var(--accent); color: white; }
|
||||
.usage-info { display: flex; align-items: center; gap: 6px; }
|
||||
.usage-bar { width: 50px; height: 5px; background: var(--border-color); border-radius: 3px; overflow: hidden; }
|
||||
.usage-bar-fill { height: 100%; background: var(--accent); border-radius: 3px; }
|
||||
.voucher-note { max-width: 180px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.no-sites-warning { background: #fff3cd; border: 1px solid #ffc107; border-radius: 12px; padding: 30px; text-align: center; color: #856404; }
|
||||
@media(max-width:768px){ .main-content{ margin-left:0!important; } }
|
||||
</style>
|
||||
|
||||
<div class="page-header">
|
||||
<h1 class="page-title">
|
||||
<?= __('vouchers_title') ?>
|
||||
<span class="live-indicator"><span class="dot"></span>LIVE</span>
|
||||
</h1>
|
||||
<p class="page-subtitle"><?= __('vouchers_subtitle') ?></p>
|
||||
<div>
|
||||
<h1 class="page-title">
|
||||
<?= __('vouchers_title') ?>
|
||||
<span class="live-indicator"><span class="dot"></span>LIVE</span>
|
||||
</h1>
|
||||
<p class="page-subtitle"><?= __('vouchers_subtitle') ?></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if (empty($sites)): ?>
|
||||
<div class="no-sites-warning">
|
||||
<i class="fas fa-exclamation-triangle" style="font-size:48px;margin-bottom:15px;opacity:.7;display:block;"></i>
|
||||
<i class="fas fa-triangle-exclamation" style="font-size:26px;margin-bottom:12px;display:block;"></i>
|
||||
<h3><?= __('vouchers_no_sites') ?></h3>
|
||||
<a href="sites.php" class="btn btn-primary" style="margin-top:20px;"><i class="fas fa-plus"></i> <?= __('nav_sites') ?></a>
|
||||
</div>
|
||||
|
|
@ -227,7 +167,7 @@ $currentPage = 'vouchers';
|
|||
<?php endforeach; ?>
|
||||
</select>
|
||||
<input type="search" id="searchInput" class="search-bar" placeholder="<?= __('vouchers_search') ?>" oninput="filterAndRender()" style="display:none;">
|
||||
<button id="refreshBtn" class="btn btn-success" onclick="loadVouchers(true)" disabled>
|
||||
<button id="refreshBtn" class="btn btn-secondary" onclick="loadVouchers(true)" disabled>
|
||||
<i class="fas fa-sync-alt"></i> <?= __('btn_refresh') ?>
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -258,7 +198,7 @@ $currentPage = 'vouchers';
|
|||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
</div><!-- /main-content -->
|
||||
</main>
|
||||
|
||||
<div id="toast-container"></div>
|
||||
<script src="../assets/global.js"></script>
|
||||
|
|
@ -404,7 +344,7 @@ function renderVouchers() {
|
|||
<td>${remaining?`<span style="color:var(--success)"><i class="fas fa-clock"></i> ${remaining}</span><br>`:''}<small style="color:var(--text-muted)">${v.duration} Min.</small></td>
|
||||
<td style="white-space:nowrap;">
|
||||
<button onclick="resendVoucher('${v._id}','${escapeHtml(v.formatted_code||'')}')" class="btn btn-secondary btn-sm" title="Per E-Mail senden"><i class="fas fa-envelope"></i></button>
|
||||
<button onclick="deleteVoucher('${v._id}')" class="btn btn-danger btn-sm" title="<?= __('btn_delete') ?>"><i class="fas fa-trash"></i></button>
|
||||
<button onclick="deleteVoucher('${v._id}')" class="btn btn-danger-soft btn-sm" title="<?= __('btn_delete') ?>"><i class="fas fa-trash"></i></button>
|
||||
</td>
|
||||
</tr>`;
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue