Files
deskhub/DeskHub-Webseite/staff/_header.php
T
2026-10-23 11:14:26 +00:00

417 lines
25 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/** @var array $staff (current_staff(), wird von der aufrufenden Seite gesetzt) */
$staff = current_staff();
$activePage = $activePage ?? '';
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title><?= e($pageTitle ?? 'DeskHub – Mitarbeiterbereich') ?></title>
<meta name="csrf" content="<?= csrf_token() ?>">
<?php
// VAPID Public Key – aus Config oder automatisch aus DB
if (file_exists(__DIR__ . '/../includes/push.php')) {
require_once __DIR__ . '/../includes/push.php';
$__vapidKeys = get_or_create_vapid_keys();
$pushKey = $__vapidKeys['public_key'] ?? '';
} else {
$pushKey = config()['push']['public_key'] ?? '';
}
?>
<?php if ($pushKey): ?>
<meta name="push-key" content="<?= e($pushKey) ?>">
<?php endif; ?>
<link rel="stylesheet" href="/assets/css/style.css?v=<?= @filemtime(__DIR__ . "/../assets/css/style.css") ?: time() ?>">
<?= site_icon_links() ?>
<link rel="manifest" href="/manifest-staff.php">
<meta name="theme-color" content="#0f6f6a">
<meta name="apple-mobile-web-app-capable" content="yes">
<script>
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/staff/sw-staff.js', {scope: '/staff/'}).catch(function(){});
}
</script>
</head>
<body>
<div class="app-shell">
<aside class="sidebar">
<div class="brand">
<?php $logo = get_setting('logo_path'); ?>
<?php if ($logo): ?>
<img src="<?= e($logo) ?>" alt="<?= e(get_setting('display_name', 'DeskHub')) ?>" style="max-height:34px; max-width:180px; display:block">
<?php else: ?>
Desk<span>Hub</span>
<?php endif; ?>
</div>
<nav>
<?php // Übersicht + ehemals separates "Berichte" wurden zu einer Seite zusammengelegt
// (siehe staff/reports.php) – der Login landete ohnehin schon immer dort. ?>
<a href="/staff/reports.php" class="<?= $activePage==='reports'?'active':'' ?>">Übersicht</a>
<a href="/staff/report_builder.php" class="<?= $activePage==='report_builder'?'active':'' ?>">Bericht-Baukasten</a>
<a href="/staff/tickets.php" class="<?= $activePage==='tickets'?'active':'' ?>">Tickets</a>
<a href="/staff/tasks.php" class="<?= $activePage==='tasks'?'active':'' ?>">Aufgaben</a>
<a href="/staff/banlist.php" class="<?= $activePage==='banlist'?'active':'' ?>">Sperrliste</a>
<a href="/staff/agent_directory.php" class="<?= $activePage==='agent_directory'?'active':'' ?>">Verzeichnis</a>
<a href="/staff/apps.php" class="<?= $activePage==='apps'?'active':'' ?>">Apps</a>
<?php foreach (plugin_menu_items($staff ?? null) as $__pm): ?>
<a href="<?= e($__pm['url']) ?>" class="<?= ($activePage ?? '') === 'plugin_' . $__pm['plugin'] ? 'active' : '' ?>"><?= e($__pm['label']) ?></a>
<?php endforeach; ?>
<?php if (get_setting('live_chat_enabled', '0') === '1' && $staff): ?>
<?php
// Badge = Anzahl Live-Chats, deren letzte Nachricht vom Kunden kam (also unbeantwortet),
// auf die eigenen Abteilungen begrenzt (wie die Ticketliste selbst).
[$__lcDeptSql, $__lcDeptParams] = dept_filter_sql(staff_department_ids($staff), 't.department_id');
$__lcSql = "SELECT COUNT(*) FROM tickets t WHERE t.source = 'chat' AND t.status != 'closed'
AND (SELECT tt.author_type FROM ticket_threads tt WHERE tt.ticket_id = t.id ORDER BY tt.created_at DESC, tt.id DESC LIMIT 1) = 'customer'"
. ($__lcDeptSql !== '' ? " AND $__lcDeptSql" : '');
$__lcStmt = get_db()->prepare($__lcSql);
$__lcStmt->execute($__lcDeptParams);
$__lcPending = (int) $__lcStmt->fetchColumn();
?>
<a href="/staff/live_chat.php" class="<?= $activePage==='live_chat'?'active':'' ?>">
Live-Chat<?php if ($__lcPending): ?> <span class="nav-badge" style="background:var(--danger, #dc2626); color:#fff; border-radius:10px; min-width:18px; height:18px; font-size:.68rem; font-weight:700; display:inline-flex; align-items:center; justify-content:center; padding:0 5px; margin-left:4px; vertical-align:middle"><?= $__lcPending > 9 ? '9+' : $__lcPending ?></span><?php endif; ?>
</a>
<?php endif; ?>
<?php
// Adminportal auch für "Co-Admins" zeigen: Agenten, denen per benutzerdefinierter Rolle
// gezielt einzelne Adminportal-Rechte zugewiesen wurden (admin_section_permissions()).
// has_permission() liefert für echte Admins ohnehin immer true, daher genügt die reine
// Rechteprüfung je Link – kein zusätzliches role==='admin' nötig.
$hasAnyAdminAccess = false;
if ($staff) {
foreach (array_keys(admin_section_permissions()) as $__perm) {
if (has_permission($staff, $__perm)) { $hasAnyAdminAccess = true; break; }
}
}
?>
<?php if ($hasAnyAdminAccess): ?>
<div class="nav-section-label" id="admin-toggle" role="button" tabindex="0">
Adminportal <span class="nav-toggle-chevron">▾</span>
</div>
<div class="nav-admin-block" id="admin-block">
<span class="nav-group-label">Verwalten</span>
<?php if (has_permission($staff, 'manage_departments')): ?><a href="/staff/departments.php" class="<?= $activePage==='departments'?'active':'' ?>">Abteilungen</a><?php endif; ?>
<?php if (has_permission($staff, 'manage_priorities')): ?><a href="/staff/priorities.php" class="<?= $activePage==='priorities'?'active':'' ?>">Prioritäten / SLA</a><a href="/staff/sla_plans.php" class="<?= $activePage==='sla_plans'?'active':'' ?>">SLA-Pläne</a><?php endif; ?>
<?php if (has_permission($staff, 'manage_help_topics')): ?><a href="/staff/help_topics.php" class="<?= $activePage==='help_topics'?'active':'' ?>">Hilfethemen</a><?php endif; ?>
<?php if ($staff['role'] === 'admin'): ?><a href="/staff/custom_fields.php" class="<?= $activePage==='custom_fields'?'active':'' ?>">Ticket-Felder</a><?php endif; ?>
<?php if ($staff['role'] === 'admin'): ?><a href="/staff/api_keys.php" class="<?= $activePage==='api_keys'?'active':'' ?>">API-Schlüssel</a><a href="/staff/webhooks.php" class="<?= $activePage==='webhooks'?'active':'' ?>">Webhooks</a><?php endif; ?>
<?php if ($staff['role'] === 'admin'): ?><a href="/staff/custom_lists.php" class="<?= $activePage==='custom_lists'?'active':'' ?>">Benutzerdefinierte Listen</a><?php endif; ?>
<?php if ($staff['role'] === 'admin'): ?><a href="/staff/canned_responses.php" class="<?= $activePage==='canned'?'active':'' ?>">Vorlagen</a><?php endif; ?>
<?php if ($staff['role'] === 'admin'): ?><a href="/staff/site_pages.php" class="<?= $activePage==='site_pages'?'active':'' ?>">Seiten</a><?php endif; ?>
<?php if (has_permission($staff, 'manage_email_addresses')): ?><a href="/staff/email_addresses.php" class="<?= $activePage==='email_addresses'?'active':'' ?>">E-Mail-Adressen</a><?php endif; ?>
<?php if (has_permission($staff, 'manage_email_templates')): ?><a href="/staff/email_templates.php" class="<?= $activePage==='email_templates'?'active':'' ?>">E-Mail-Vorlagen</a><?php endif; ?>
<?php if (has_permission($staff, 'manage_kb')): ?><a href="/staff/kb_manage.php" class="<?= $activePage==='kb'?'active':'' ?>">Wissensdatenbank</a><?php endif; ?>
<?php if ($staff['role'] === 'admin'): ?><a href="/staff/plugins.php" class="<?= $activePage==='plugins'?'active':'' ?>">Plugins</a><?php endif; ?>
<span class="nav-group-label">Kunden</span>
<?php if ($staff['role'] === 'admin'): ?><a href="/staff/customers.php" class="<?= $activePage==='customers'?'active':'' ?>">Kundenverzeichnis</a><?php endif; ?>
<?php if (has_permission($staff, 'manage_organizations')): ?><a href="/staff/organizations.php" class="<?= $activePage==='organizations'?'active':'' ?>">Organisationen</a><?php endif; ?>
<span class="nav-group-label">Agenten</span>
<?php if (has_permission($staff, 'manage_staff')): ?><a href="/staff/staff_manage.php" class="<?= $activePage==='staff'?'active':'' ?>">Mitarbeiter</a><?php endif; ?>
<?php if (has_permission($staff, 'manage_teams')): ?><a href="/staff/teams.php" class="<?= $activePage==='teams'?'active':'' ?>">Teams</a><?php endif; ?>
<?php if (has_permission($staff, 'manage_roles')): ?><a href="/staff/roles.php" class="<?= $activePage==='roles'?'active':'' ?>">Rollen</a><?php endif; ?>
<?php if ($staff['role'] === 'admin'): ?><a href="/staff/availability.php" class="<?= $activePage==='availability'?'active':'' ?>">Status &amp; Schichten</a><?php endif; ?>
<span class="nav-group-label">System</span>
<?php if ($staff['role'] === 'admin'): ?><a href="/staff/security_check.php" class="<?= $activePage==='security_check'?'active':'' ?>">Sicherheitsprüfung</a><?php endif; ?>
<?php if (has_permission($staff, 'view_audit_log')): ?><a href="/staff/audit_log.php" class="<?= $activePage==='audit_log'?'active':'' ?>">Admin-Protokoll</a><?php endif; ?>
<?php if ($staff['role'] === 'admin'): ?><a href="/staff/ticket_rules.php" class="<?= $activePage==='ticket_rules'?'active':'' ?>">Ticket-Regeln</a><?php endif; ?>
<?php if (has_permission($staff, 'manage_escalations')): ?><a href="/staff/escalation_rules.php" class="<?= $activePage==='escalation_rules'?'active':'' ?>">Eskalationsregeln</a><?php endif; ?>
<?php if ($staff['role'] === 'admin'): ?><a href="/staff/recurring_tickets.php" class="<?= $activePage==='recurring'?'active':'' ?>">Wiederkehrende Tickets</a><?php endif; ?>
<?php if (has_permission($staff, 'view_time_entries')): ?><a href="/staff/time_entries.php" class="<?= $activePage==='time_entries'?'active':'' ?>">Zeiterfassung</a><?php endif; ?>
<?php if (has_permission($staff, 'manage_business_hours')): ?><a href="/staff/business_hours.php" class="<?= $activePage==='business_hours'?'active':'' ?>">Geschäftszeiten</a><?php endif; ?>
<?php if ($staff['role'] === 'admin'): ?><a href="/staff/settings.php" class="<?= $activePage==='settings'?'active':'' ?>">Einstellungen</a><?php endif; ?>
<?php if ($staff['role'] === 'admin'): ?><a href="/staff/backup.php" class="<?= $activePage==='backup'?'active':'' ?>">Backup</a><?php endif; ?>
<?php if ($staff['role'] === 'admin'): ?><a href="/staff/update.php" class="<?= $activePage==='update'?'active':'' ?>">System-Update</a><?php endif; ?>
</div>
<script>
(function () {
var toggle = document.getElementById('admin-toggle');
var block = document.getElementById('admin-block');
var KEY = 'deskhub_admin_nav_collapsed';
// In diesen Adminportal-Unterseiten immer ausgeklappt zeigen, damit die aktive Seite sichtbar bleibt
var forceOpen = block.querySelector('a.active') !== null;
function apply(collapsed) {
block.style.display = collapsed ? 'none' : '';
toggle.querySelector('.nav-toggle-chevron').textContent = collapsed ? '▸' : '▾';
}
var stored = localStorage.getItem(KEY);
apply(!forceOpen && stored === '1');
toggle.addEventListener('click', function () {
var collapsed = block.style.display !== 'none';
apply(collapsed);
localStorage.setItem(KEY, collapsed ? '1' : '0');
});
toggle.addEventListener('keydown', function (e) {
if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); toggle.click(); }
});
})();
</script>
<?php endif; ?>
</nav>
<div class="sidebar-footer">
<?php if ($staff): ?>
<div style="display:flex; align-items:center; gap:10px; margin-bottom:8px">
<?php if (!empty($staff['avatar_path'])): ?>
<img src="<?= e($staff['avatar_path']) ?>" alt="" style="width:32px; height:32px; border-radius:50%; object-fit:cover; flex-shrink:0; border:1px solid #2a3744">
<?php else: ?>
<div style="width:32px; height:32px; border-radius:50%; background:rgba(15,111,106,.3); display:flex; align-items:center; justify-content:center; font-size:.85rem; font-weight:700; color:#8bc4c2; flex-shrink:0">
<?= mb_strtoupper(mb_substr($staff['name'], 0, 1)) ?>
</div>
<?php endif; ?>
<div style="min-width:0">
<div style="font-size:.82rem; font-weight:600; color:#fff; overflow:hidden; text-overflow:ellipsis; white-space:nowrap"><?= e($staff['name']) ?></div>
<div style="font-size:.72rem; color:#4e6278"><?= $staff['role']==='admin'?'Administrator':'Mitarbeiter' ?></div>
<?php if (!empty($staff['is_on_vacation'])): ?>
<div style="font-size:.7rem; color:var(--accent)">🌴 Im Urlaub</div>
<?php endif; ?>
</div>
</div>
<div style="display:flex; gap:14px; align-items:center">
<?php
$__unreadMentions = 0;
$__unreadChat = 0;
if ($staff) {
$__ms = get_db()->prepare('SELECT COUNT(*) FROM staff_mentions WHERE mentioned_staff_id=? AND is_read=0');
$__ms->execute([$staff['id']]);
$__unreadMentions = (int)$__ms->fetchColumn();
$__cs = get_db()->prepare('SELECT COUNT(*) FROM staff_chat_messages WHERE to_staff_id=? AND is_read=0');
$__cs->execute([$staff['id']]);
$__unreadChat = (int)$__cs->fetchColumn();
}
?>
<a href="/staff/chat.php" title="Interner Chat" style="position:relative; color:#8b97a3; font-size:.78rem; text-decoration:none">
💬<?php if ($__unreadChat > 0): ?><span style="position:absolute;top:-6px;right:-8px;background:#dc2626;color:#fff;border-radius:50%;font-size:.6rem;font-weight:700;width:15px;height:15px;display:flex;align-items:center;justify-content:center;line-height:1"><?= $__unreadChat > 9 ? '9+' : $__unreadChat ?></span><?php endif; ?>
</a>
<a href="/staff/mentions.php" title="Erwähnungen" style="position:relative; color:#8b97a3; font-size:.78rem; text-decoration:none">
🔔<?php if ($__unreadMentions > 0): ?><span style="position:absolute;top:-6px;right:-8px;background:#dc2626;color:#fff;border-radius:50%;font-size:.6rem;font-weight:700;width:15px;height:15px;display:flex;align-items:center;justify-content:center;line-height:1"><?= $__unreadMentions > 9 ? '9+' : $__unreadMentions ?></span><?php endif; ?>
</a>
<a href="/staff/profile.php" style="color:#8b97a3; font-size:.78rem">Profil</a>
<a href="/staff/logout.php" style="color:#8b97a3; font-size:.78rem">Abmelden</a>
</div>
<?php endif; ?>
</div>
</aside>
<?php
// Timeout in JS übergeben
$_jsTimeout = (int)(get_setting('staff_session_timeout', '30') ?: 30);
$_jsLastAct = $_SESSION['staff_last_activity'] ?? time();
if (!isset($_SESSION['staff_last_activity'])) {
$_SESSION['staff_last_activity'] = time();
$_jsLastAct = time();
}
$_jsRemaining = max(10, ($_jsLastAct + $_jsTimeout * 60) - time());
?>
<script>
window.sessionTimerRemaining = <?= (int)$_jsRemaining ?>;
window.sessionTimerTimeout = <?= (int)$_jsTimeout ?>;
</script>
<script>
(function() {
var remaining = window.sessionTimerRemaining || 0;
var timeoutMin = window.sessionTimerTimeout || 30;
function init() {
var el = document.getElementById('session-timer');
var bar = document.getElementById('session-timer-bar');
if (!el || !bar || remaining <= 0) return;
function fmt(s) {
var m = Math.floor(s / 60);
var sec = s % 60;
return m + ':' + (sec < 10 ? '0' : '') + sec;
}
function tick() {
if (remaining <= 0) {
window.location.href = '/staff/login.php';
return;
}
el.textContent = fmt(remaining);
// Immer sichtbar
bar.style.opacity = '1';
bar.style.pointerEvents = 'auto';
// Farbe nach Dringlichkeit
if (remaining <= 60) {
bar.style.background = 'rgba(239,68,68,.9)';
bar.style.borderColor = 'rgba(239,68,68,.5)';
el.style.color = '#fff';
} else if (remaining <= 300) {
bar.style.background = 'rgba(245,158,11,.9)';
bar.style.borderColor = 'rgba(245,158,11,.5)';
el.style.color = '#fff';
} else {
bar.style.background = 'rgba(15,23,42,.72)';
bar.style.borderColor = 'rgba(255,255,255,.1)';
el.style.color = '#cbd5e1';
}
remaining--;
setTimeout(tick, 1000);
}
tick();
// Bei jeder Nutzeraktion (Klick, Tastatur) Timer via fetch zurücksetzen
var resetDebounce;
function resetTimer() {
clearTimeout(resetDebounce);
resetDebounce = setTimeout(function() {
fetch('/staff/session_ping.php', { method:'POST',
headers:{'X-CSRF-Token': document.querySelector('meta[name=csrf]')?.content || ''},
credentials:'same-origin' })
.then(function(r){ return r.json(); })
.then(function(d){ if (d.remaining) remaining = d.remaining; })
.catch(function(){});
}, 2000); // max. alle 2 Sek. ein Ping
}
['click','keydown','mousemove','scroll','touchstart'].forEach(function(ev) {
document.addEventListener(ev, resetTimer, { passive: true });
});
// Restzeit regelmäßig beim Server erfragen (verlängert nichts): Aktivität in einem anderen Tab verlängert die Zeit hier mit,
// und ist die Sitzung wirklich abgelaufen, geht es sofort zur Anmeldung
function syncTimer() {
fetch('/staff/session_ping.php?check=1', { credentials:'same-origin', cache:'no-store' })
.then(function(r){ if (r.status === 401) { window.location.href = '/staff/login.php'; return null; } return r.json(); })
.then(function(d){ if (d && typeof d.remaining === 'number') remaining = d.remaining; })
.catch(function(){});
}
setInterval(syncTimer, 30000);
} // end init
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();
</script>
<script>
// ── Browser Push Notifications ───────────────────────────────────────────
(function() {
var pushKey = document.querySelector('meta[name=push-key]')?.content || document.getElementById('push-vapid-key')?.value;
if (!pushKey || !('serviceWorker' in navigator) || !('PushManager' in window)) return;
var btn = document.getElementById('push-toggle-btn');
function urlB64ToUint8(b64) {
var pad = '='.repeat((4 - b64.length % 4) % 4);
var raw = atob(b64.replace(/-/g,'+').replace(/_/g,'/') + pad);
return Uint8Array.from([...raw].map(function(ch){ return ch.charCodeAt(0); }));
}
function setBtn(active) {
if (!btn) return;
btn.textContent = active ? '🔔' : '🔕';
btn.style.color = active ? '#22c55e' : '#8b97a3';
btn.title = active ? 'Push aktiv – klicken zum Deaktivieren' : 'Push-Benachrichtigungen aktivieren';
}
navigator.serviceWorker.ready.then(function(reg) {
reg.pushManager.getSubscription().then(function(s) {
setBtn(!!(s && Notification.permission === 'granted'));
});
});
window.togglePushNotifications = function() {
navigator.serviceWorker.ready.then(function(reg) {
reg.pushManager.getSubscription().then(function(sub) {
if (sub) {
sub.unsubscribe().then(function() {
fetch('/staff/push_subscribe.php', { method:'POST', credentials:'same-origin',
headers:{'Content-Type':'application/json'},
body: JSON.stringify({action:'unsubscribe', endpoint: sub.endpoint}) });
setBtn(false);
});
} else {
Notification.requestPermission().then(function(perm) {
if (perm !== 'granted') { alert('Bitte Benachrichtigungen im Browser erlauben.'); return; }
reg.pushManager.subscribe({ userVisibleOnly:true, applicationServerKey: urlB64ToUint8(pushKey) })
.then(function(ns) {
var j = ns.toJSON();
fetch('/staff/push_subscribe.php', { method:'POST', credentials:'same-origin',
headers:{'Content-Type':'application/json'},
body: JSON.stringify({action:'subscribe', endpoint:j.endpoint, keys:j.keys}) })
.then(function() { setBtn(true); });
}).catch(function(err) { alert('Push fehlgeschlagen: ' + err.message); });
});
}
});
});
};
})();
</script>
<main class="main">
<!-- Session-Timer: kompakte Pille oben rechts -->
<div id="session-timer-bar" style="position:fixed;top:12px;right:16px;z-index:9999;display:flex;align-items:center;gap:6px;background:rgba(15,23,42,.75);backdrop-filter:blur(6px);border:1px solid rgba(255,255,255,.1);border-radius:20px;padding:4px 12px 4px 8px;pointer-events:none;opacity:0;transition:opacity .3s">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#94a3b8" stroke-width="2.5"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
<span id="session-timer" style="font-size:.72rem;font-weight:700;color:#cbd5e1;font-variant-numeric:tabular-nums;letter-spacing:.02em"></span>
</div>
<?php foreach (get_flashes() as $f): ?>
<div class="flash <?= e($f['type']) ?>"><?= e($f['message']) ?></div>
<?php endforeach; ?>
<?php
if (isset($staff) && has_permission($staff, 'manage_email_addresses')) {
foreach (system_health(get_db()) as $__issue): ?>
<div class="flash <?= $__issue['level'] === 'error' ? 'error' : 'warning' ?>" style="display:flex;align-items:center;gap:10px;flex-wrap:wrap">
<strong><?= $__issue['level'] === 'error' ? 'Störung:' : 'Hinweis:' ?></strong> <?= e($__issue['message']) ?>
<a href="/staff/email_addresses.php" style="margin-left:auto">E-Mail-Adressen öffnen</a>
</div>
<?php endforeach; } ?>
<?php
if (isset($staff) && ($staff['role'] ?? '') === 'admin' && ($activePage ?? '') !== 'update') {
require_once __DIR__ . '/../database/migrate.php';
$__pending = migrations_pending(get_db());
if ($__pending): ?>
<div class="flash warning" style="display:flex;align-items:center;gap:10px;flex-wrap:wrap">
<strong>Datenbank veraltet:</strong> <?= count($__pending) ?> ausstehende Änderung(en) – der Code ist neuer als die Datenbank.
<a href="/staff/update.php" style="margin-left:auto">Jetzt aktualisieren</a>
</div>
<?php endif; } ?>
<?php
if (isset($staff) && ($staff['role'] ?? '') === 'admin' && ($activePage ?? '') !== 'plugins' && plugin_official_configured()) {
$__upd = plugin_updates_available(get_db());
if ($__upd): ?>
<div class="flash warning" style="display:flex;align-items:center;gap:10px;flex-wrap:wrap">
<strong>Plugin-Updates verfügbar:</strong> <?= e(implode(', ', array_map(fn($u) => $u['name'] . ' ' . $u['available'], $__upd))) ?>
<a href="/staff/plugins.php" style="margin-left:auto">Zu den Plugins</a>
</div>
<?php endif; } ?>
<?php $lockedOverride = get_locked_ticket_override(); ?>
<?php if ($lockedOverride && isset($staff) && $staff['role'] === 'admin'): ?>
<div class="flash warning" style="display:flex;align-items:center;gap:10px;flex-wrap:wrap">
🔓 Als Admin können Sie die Sperre von „<?= e($lockedOverride['name']) ?>“ auf Ticket #<?= e($lockedOverride['number']) ?> sofort aufheben, statt auf den automatischen Timeout zu warten.
<form method="post" action="/staff/ticket_view.php?n=<?= urlencode($lockedOverride['number']) ?>" style="margin:0">
<?= csrf_field() ?>
<input type="hidden" name="action" value="admin_force_unlock">
<input type="hidden" name="locked_by_name" value="<?= e($lockedOverride['name']) ?>">
<button type="submit" class="btn btn-sm" style="padding:4px 10px;font-size:.78rem">Sperre jetzt aufheben</button>
</form>
</div>
<?php endif; ?>
<?php if (isset($staff) && $staff['role'] === 'admin' && (config()['db']['driver'] ?? 'sqlite') === 'sqlite'): ?>
<div class="flash warning" style="display:flex;align-items:center;gap:10px">
⚠️ <strong>SQLite-Modus aktiv:</strong> Nicht für den Produktivbetrieb geeignet.
Bitte in <code>config/config.php</code> auf <code>driver&nbsp;=&nbsp;'mysql'</code> umstellen und MySQL-Zugangsdaten eintragen.
</div>
<?php endif; ?>
<?php if (isset($staff) && $staff['role'] === 'admin' && empty(config()['app_key']) && !get_setting('app_key')): ?>
<div class="flash warning" style="display:flex;align-items:center;gap:10px">
🔑 <strong>Kein App-Key gesetzt:</strong> Passwörter werden nur schwach verschlüsselt.
Bitte in <code>config/config.php</code> den Wert <code>app_key</code> setzen.
Generieren: <code>php&nbsp;-r&nbsp;"echo&nbsp;bin2hex(random_bytes(32));"</code>
</div>
<?php endif; ?>