2026-03-27 19:05:42 -04:00
|
|
|
<?php
|
2026-04-13 20:56:10 -04:00
|
|
|
|
2026-03-27 19:05:42 -04:00
|
|
|
/**
|
|
|
|
|
* layout_header.php — Shared top-of-page partial for all views.
|
|
|
|
|
*
|
|
|
|
|
* Expected variables set by the including view before require:
|
|
|
|
|
* string $pageTitle Page title suffix (e.g. "Dashboard", "Ticket #42")
|
|
|
|
|
* string $activeNav Active nav key: 'dashboard', 'tickets', 'admin-*'
|
|
|
|
|
* array|null $pageStyles Optional extra CSS hrefs to load
|
|
|
|
|
* string $nonce CSP nonce from SecurityHeadersMiddleware::getNonce()
|
|
|
|
|
*
|
|
|
|
|
* Globals used:
|
|
|
|
|
* $GLOBALS['currentUser'] — user array (username, display_name, is_admin, groups)
|
|
|
|
|
* $GLOBALS['config'] — app config array
|
|
|
|
|
* CsrfMiddleware::getToken() — returns current CSRF token string
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
$_lt_user = $GLOBALS['currentUser'] ?? [];
|
|
|
|
|
$_lt_isAdmin = !empty($_lt_user['is_admin']);
|
|
|
|
|
$_lt_navActive = $activeNav ?? 'dashboard';
|
2026-03-29 17:02:40 -04:00
|
|
|
$_lt_appName = $GLOBALS['config']['APP_NAME'] ?? 'TINKER TICKETS';
|
|
|
|
|
$_lt_subtitle = $GLOBALS['config']['APP_SUBTITLE'] ?? 'LotusGuild Infrastructure';
|
|
|
|
|
$_lt_assetVer = $GLOBALS['config']['ASSET_VERSION'] ?? '20260329';
|
2026-03-27 19:05:42 -04:00
|
|
|
?>
|
|
|
|
|
<!DOCTYPE html>
|
2026-03-29 17:02:40 -04:00
|
|
|
<html lang="en" data-theme="dark">
|
2026-03-27 19:05:42 -04:00
|
|
|
<head>
|
|
|
|
|
<meta charset="UTF-8">
|
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
|
|
|
|
|
<meta name="theme-color" content="#030508">
|
2026-03-29 17:02:40 -04:00
|
|
|
<title><?= htmlspecialchars($pageTitle ?? 'Dashboard', ENT_QUOTES, 'UTF-8') ?> — <?= htmlspecialchars($_lt_appName, ENT_QUOTES, 'UTF-8') ?></title>
|
2026-03-27 19:05:42 -04:00
|
|
|
<meta name="robots" content="noindex, nofollow">
|
|
|
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
|
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
2026-04-01 16:55:12 -04:00
|
|
|
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:ital,wght@0,400;0,600;0,700;1,400&family=VT323&display=swap" rel="stylesheet">
|
2026-03-29 17:02:40 -04:00
|
|
|
<link rel="stylesheet" href="/assets/css/base.css?v=<?= $_lt_assetVer ?>">
|
2026-04-13 20:56:10 -04:00
|
|
|
<?php if (!empty($pageStyles)) : ?>
|
|
|
|
|
<?php foreach ($pageStyles as $_lt_css) : ?>
|
2026-03-27 19:05:42 -04:00
|
|
|
<link rel="stylesheet" href="<?= htmlspecialchars($_lt_css, ENT_QUOTES, 'UTF-8') ?>">
|
2026-04-13 20:56:10 -04:00
|
|
|
<?php endforeach; ?>
|
2026-03-27 19:05:42 -04:00
|
|
|
<?php endif; ?>
|
|
|
|
|
<link rel="icon" href="/assets/images/favicon.png" type="image/png">
|
|
|
|
|
<!-- Base JS loaded in head so lt.* is available for inline view scripts -->
|
2026-03-29 17:02:40 -04:00
|
|
|
<script nonce="<?= htmlspecialchars($nonce, ENT_QUOTES, 'UTF-8') ?>" src="/assets/js/base.js?v=<?= $_lt_assetVer ?>"></script>
|
|
|
|
|
<script nonce="<?= htmlspecialchars($nonce, ENT_QUOTES, 'UTF-8') ?>" src="/assets/js/utils.js?v=<?= $_lt_assetVer ?>"></script>
|
2026-03-27 19:05:42 -04:00
|
|
|
<!-- Inline JS globals (CSRF, timezone, user) available immediately -->
|
|
|
|
|
<script nonce="<?= htmlspecialchars($nonce, ENT_QUOTES, 'UTF-8') ?>">
|
2026-03-28 12:43:24 -04:00
|
|
|
window.CSRF_TOKEN = <?= json_encode(CsrfMiddleware::getToken(), JSON_UNESCAPED_UNICODE | JSON_HEX_TAG) ?>;
|
|
|
|
|
window.APP_TIMEZONE = <?= json_encode($GLOBALS['config']['TIMEZONE'] ?? 'UTC', JSON_UNESCAPED_UNICODE | JSON_HEX_TAG) ?>;
|
|
|
|
|
window.APP_TIMEZONE_ABBREV = <?= json_encode($GLOBALS['config']['TIMEZONE_ABBREV'] ?? 'UTC', JSON_UNESCAPED_UNICODE | JSON_HEX_TAG) ?>;
|
2026-03-27 19:05:42 -04:00
|
|
|
window.APP_TIMEZONE_OFFSET = <?= (int)($GLOBALS['config']['TIMEZONE_OFFSET'] ?? 0) ?>;
|
|
|
|
|
window.CURRENT_USER = <?= json_encode([
|
|
|
|
|
'id' => (int)($GLOBALS['currentUser']['user_id'] ?? 0),
|
2026-04-13 20:56:10 -04:00
|
|
|
'username' => $GLOBALS['currentUser']['username'] ?? '',
|
2026-03-27 19:05:42 -04:00
|
|
|
'isAdmin' => !empty($GLOBALS['currentUser']['is_admin']),
|
2026-03-28 12:43:24 -04:00
|
|
|
], JSON_UNESCAPED_UNICODE | JSON_HEX_TAG) ?>;
|
2026-03-27 19:05:42 -04:00
|
|
|
</script>
|
|
|
|
|
</head>
|
2026-04-01 17:12:07 -04:00
|
|
|
<body>
|
2026-03-27 19:05:42 -04:00
|
|
|
<!-- SKIP LINK -->
|
|
|
|
|
<a class="lt-skip-link" href="#main-content">Skip to main content</a>
|
|
|
|
|
|
|
|
|
|
<!-- BOOT OVERLAY — controlled by lt.boot() in base.js; shown once per session -->
|
2026-03-29 17:02:40 -04:00
|
|
|
<div id="lt-boot" class="lt-boot-overlay" data-app-name="<?= htmlspecialchars($_lt_appName, ENT_QUOTES, 'UTF-8') ?>" style="display:none" aria-hidden="true">
|
2026-03-27 19:05:42 -04:00
|
|
|
<pre id="lt-boot-text" class="lt-boot-text"></pre>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-03-28 12:43:24 -04:00
|
|
|
<!-- MOBILE NAV DRAWER — matches web_template structure exactly -->
|
|
|
|
|
<div id="lt-nav-drawer" class="lt-nav-drawer" aria-hidden="true" role="dialog" aria-modal="true" aria-label="Navigation menu">
|
|
|
|
|
<div class="lt-nav-drawer-header">
|
2026-03-29 17:02:40 -04:00
|
|
|
<span class="lt-brand-title"><?= htmlspecialchars($_lt_appName, ENT_QUOTES, 'UTF-8') ?></span>
|
2026-03-28 12:43:24 -04:00
|
|
|
<button type="button" class="lt-nav-drawer-close" id="lt-nav-drawer-close" aria-label="Close navigation">✕</button>
|
|
|
|
|
</div>
|
|
|
|
|
<nav class="lt-nav-drawer-links" aria-label="Mobile navigation">
|
|
|
|
|
<a href="/"
|
|
|
|
|
class="lt-nav-drawer-link<?= $_lt_navActive === 'dashboard' ? ' active' : '' ?>"
|
|
|
|
|
<?= $_lt_navActive === 'dashboard' ? 'aria-current="page"' : '' ?>>Dashboard</a>
|
2026-04-13 20:56:10 -04:00
|
|
|
<?php if ($_lt_isAdmin) : ?>
|
2026-03-28 12:43:24 -04:00
|
|
|
<div class="lt-nav-drawer-section">Admin</div>
|
|
|
|
|
<a href="/admin/templates" class="lt-nav-drawer-link lt-nav-drawer-link--indent<?= $_lt_navActive === 'admin-templates' ? ' active' : '' ?>">Templates</a>
|
|
|
|
|
<a href="/admin/workflow" class="lt-nav-drawer-link lt-nav-drawer-link--indent<?= $_lt_navActive === 'admin-workflow' ? ' active' : '' ?>">Workflow</a>
|
|
|
|
|
<a href="/admin/recurring-tickets" class="lt-nav-drawer-link lt-nav-drawer-link--indent<?= $_lt_navActive === 'admin-recurring' ? ' active' : '' ?>">Recurring</a>
|
|
|
|
|
<a href="/admin/custom-fields" class="lt-nav-drawer-link lt-nav-drawer-link--indent<?= $_lt_navActive === 'admin-custom-fields' ? ' active' : '' ?>">Custom Fields</a>
|
|
|
|
|
<a href="/admin/user-activity" class="lt-nav-drawer-link lt-nav-drawer-link--indent<?= $_lt_navActive === 'admin-user-activity' ? ' active' : '' ?>">User Activity</a>
|
|
|
|
|
<a href="/admin/audit-log" class="lt-nav-drawer-link lt-nav-drawer-link--indent<?= $_lt_navActive === 'admin-audit-log' ? ' active' : '' ?>">Audit Log</a>
|
|
|
|
|
<a href="/admin/api-keys" class="lt-nav-drawer-link lt-nav-drawer-link--indent<?= $_lt_navActive === 'admin-api-keys' ? ' active' : '' ?>">API Keys</a>
|
2026-03-27 19:05:42 -04:00
|
|
|
<?php endif; ?>
|
|
|
|
|
</nav>
|
|
|
|
|
</div><!-- /.lt-nav-drawer -->
|
2026-03-28 12:43:24 -04:00
|
|
|
<!-- Overlay: outside drawer, full-screen; JS toggles .open class -->
|
|
|
|
|
<div id="lt-nav-overlay" class="lt-nav-drawer-overlay"></div>
|
2026-03-27 19:05:42 -04:00
|
|
|
|
|
|
|
|
<!-- PRIMARY HEADER -->
|
|
|
|
|
<header class="lt-header" role="banner">
|
|
|
|
|
<div class="lt-header-left">
|
|
|
|
|
|
|
|
|
|
<!-- Hamburger — opens mobile nav drawer -->
|
|
|
|
|
<button type="button"
|
|
|
|
|
class="lt-menu-btn"
|
|
|
|
|
id="lt-menu-btn"
|
|
|
|
|
data-action="open-nav-drawer"
|
|
|
|
|
aria-label="Open navigation menu"
|
|
|
|
|
aria-expanded="false"
|
|
|
|
|
aria-controls="lt-nav-drawer">
|
|
|
|
|
<span class="lt-menu-btn-bar"></span>
|
|
|
|
|
<span class="lt-menu-btn-bar"></span>
|
|
|
|
|
<span class="lt-menu-btn-bar"></span>
|
|
|
|
|
</button>
|
|
|
|
|
|
|
|
|
|
<!-- Brand -->
|
|
|
|
|
<div class="lt-brand">
|
|
|
|
|
<a href="/"
|
|
|
|
|
class="lt-brand-title lt-glitch"
|
2026-03-29 17:02:40 -04:00
|
|
|
data-text="<?= htmlspecialchars($_lt_appName, ENT_QUOTES, 'UTF-8') ?>"
|
2026-03-27 19:05:42 -04:00
|
|
|
style="text-decoration:none"
|
2026-03-29 17:02:40 -04:00
|
|
|
aria-label="<?= htmlspecialchars($_lt_appName, ENT_QUOTES, 'UTF-8') ?> home"><?= htmlspecialchars($_lt_appName, ENT_QUOTES, 'UTF-8') ?></a>
|
|
|
|
|
<span class="lt-brand-subtitle"><?= htmlspecialchars($_lt_subtitle, ENT_QUOTES, 'UTF-8') ?></span>
|
2026-03-27 19:05:42 -04:00
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Desktop navigation -->
|
|
|
|
|
<nav class="lt-nav" aria-label="Main navigation">
|
|
|
|
|
<a href="/"
|
|
|
|
|
class="lt-nav-link<?= $_lt_navActive === 'dashboard' ? ' active' : '' ?>"
|
|
|
|
|
<?= $_lt_navActive === 'dashboard' ? 'aria-current="page"' : '' ?>>
|
|
|
|
|
Dashboard
|
|
|
|
|
</a>
|
2026-04-13 20:56:10 -04:00
|
|
|
<?php if ($_lt_isAdmin) : ?>
|
2026-03-27 19:05:42 -04:00
|
|
|
<div class="lt-nav-dropdown" data-action="toggle-nav-dropdown">
|
|
|
|
|
<a href="#"
|
|
|
|
|
class="lt-nav-link<?= str_starts_with($_lt_navActive, 'admin') ? ' active' : '' ?>"
|
|
|
|
|
role="button"
|
|
|
|
|
aria-haspopup="true"
|
|
|
|
|
aria-expanded="false"
|
|
|
|
|
aria-controls="lt-admin-dropdown-menu">
|
|
|
|
|
Admin ▾
|
|
|
|
|
</a>
|
|
|
|
|
<ul class="lt-nav-dropdown-menu"
|
|
|
|
|
id="lt-admin-dropdown-menu"
|
|
|
|
|
role="menu"
|
|
|
|
|
aria-label="Admin menu">
|
|
|
|
|
<li role="none"><a href="/admin/templates" role="menuitem" class="<?= $_lt_navActive === 'admin-templates' ? 'active' : '' ?>">Templates</a></li>
|
|
|
|
|
<li role="none"><a href="/admin/workflow" role="menuitem" class="<?= $_lt_navActive === 'admin-workflow' ? 'active' : '' ?>">Workflow</a></li>
|
|
|
|
|
<li role="none"><a href="/admin/recurring-tickets" role="menuitem" class="<?= $_lt_navActive === 'admin-recurring' ? 'active' : '' ?>">Recurring</a></li>
|
|
|
|
|
<li role="none"><a href="/admin/custom-fields" role="menuitem" class="<?= $_lt_navActive === 'admin-custom-fields' ? 'active' : '' ?>">Custom Fields</a></li>
|
|
|
|
|
<li role="none"><a href="/admin/user-activity" role="menuitem" class="<?= $_lt_navActive === 'admin-user-activity' ? 'active' : '' ?>">User Activity</a></li>
|
|
|
|
|
<li role="none"><a href="/admin/audit-log" role="menuitem" class="<?= $_lt_navActive === 'admin-audit-log' ? 'active' : '' ?>">Audit Log</a></li>
|
|
|
|
|
<li role="none"><a href="/admin/api-keys" role="menuitem" class="<?= $_lt_navActive === 'admin-api-keys' ? 'active' : '' ?>">API Keys</a></li>
|
|
|
|
|
</ul>
|
|
|
|
|
</div>
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
</nav><!-- /.lt-nav -->
|
|
|
|
|
|
|
|
|
|
</div><!-- /.lt-header-left -->
|
|
|
|
|
|
|
|
|
|
<div class="lt-header-right">
|
2026-04-13 20:56:10 -04:00
|
|
|
<?php if (!empty($_lt_user)) : ?>
|
2026-03-28 20:47:08 -04:00
|
|
|
<?php
|
|
|
|
|
$_lt_displayName = $_lt_user['display_name'] ?? $_lt_user['username'] ?? '';
|
|
|
|
|
$_lt_words = array_filter(explode(' ', $_lt_displayName));
|
|
|
|
|
$_lt_initials = strtoupper(implode('', array_map(fn($w) => $w[0], array_slice($_lt_words, 0, 2))));
|
|
|
|
|
$_lt_userId = (int)($_lt_user['user_id'] ?? 0);
|
|
|
|
|
$_lt_avatarColors = ['lt-avatar--orange', 'lt-avatar--green', 'lt-avatar--purple', ''];
|
|
|
|
|
$_lt_avatarColor = $_lt_avatarColors[abs(crc32($_lt_displayName)) % count($_lt_avatarColors)];
|
2026-04-13 20:56:10 -04:00
|
|
|
?>
|
2026-03-28 20:47:08 -04:00
|
|
|
<div class="lt-avatar lt-avatar--sm <?= $_lt_avatarColor ?>" aria-hidden="true" title="<?= htmlspecialchars($_lt_displayName, ENT_QUOTES, 'UTF-8') ?>">
|
2026-04-13 20:56:10 -04:00
|
|
|
<?php if ($_lt_userId > 0) : ?>
|
2026-03-28 20:47:08 -04:00
|
|
|
<img src="/api/user_avatar.php?user_id=<?= $_lt_userId ?>"
|
|
|
|
|
alt=""
|
2026-04-04 22:38:39 -04:00
|
|
|
class="lt-avatar-img">
|
2026-03-28 20:47:08 -04:00
|
|
|
<?php endif ?>
|
|
|
|
|
<span class="lt-avatar-initials"><?= htmlspecialchars($_lt_initials) ?></span>
|
|
|
|
|
</div>
|
|
|
|
|
<span class="lt-header-user"><?= htmlspecialchars($_lt_displayName, ENT_QUOTES, 'UTF-8') ?></span>
|
2026-04-13 20:56:10 -04:00
|
|
|
<?php if ($_lt_isAdmin) : ?>
|
2026-03-27 19:05:42 -04:00
|
|
|
<span class="lt-badge lt-badge-admin" aria-label="Administrator">ADMIN</span>
|
2026-04-13 20:56:10 -04:00
|
|
|
<?php endif; ?>
|
2026-04-04 17:21:21 -04:00
|
|
|
<?php endif; ?>
|
|
|
|
|
<!-- Notification Bell -->
|
2026-04-13 20:56:10 -04:00
|
|
|
<?php if (!empty($_lt_user)) : ?>
|
2026-04-04 17:21:21 -04:00
|
|
|
<div class="lt-notif-dropdown-wrap" id="lt-notif-wrap">
|
|
|
|
|
<button type="button"
|
|
|
|
|
class="lt-btn lt-btn-ghost lt-btn-sm lt-notif-wrap"
|
|
|
|
|
id="lt-notif-bell"
|
|
|
|
|
aria-label="Notifications"
|
|
|
|
|
aria-expanded="false"
|
|
|
|
|
aria-controls="lt-notif-panel"
|
|
|
|
|
title="Notifications">
|
|
|
|
|
🔔
|
|
|
|
|
</button>
|
|
|
|
|
<div class="lt-notif-panel" id="lt-notif-panel" aria-hidden="true" role="dialog" aria-label="Notifications">
|
|
|
|
|
<div class="lt-notif-panel-header">
|
|
|
|
|
<span>Notifications</span>
|
|
|
|
|
<button type="button" class="lt-notif-panel-clear" id="lt-notif-clear-btn">Mark all read</button>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="lt-notif-panel-list" id="lt-notif-list">
|
|
|
|
|
<div style="padding:0.75rem;font-size:0.75rem;color:var(--text-muted);text-align:center">Loading…</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="lt-notif-panel-footer">
|
2026-04-04 22:38:39 -04:00
|
|
|
<a href="/admin/audit-log" class="lt-btn lt-btn-ghost lt-btn-sm lt-w-full lt-text-center">View activity log</a>
|
2026-04-04 17:21:21 -04:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-03-27 19:05:42 -04:00
|
|
|
<?php endif; ?>
|
2026-04-05 12:15:40 -04:00
|
|
|
<button type="button" id="lt-cmd-trigger"
|
|
|
|
|
class="lt-btn lt-btn-ghost lt-btn-sm"
|
|
|
|
|
title="Command palette (Ctrl+K)"
|
|
|
|
|
aria-label="Open command palette"
|
|
|
|
|
onclick="if(window.lt&<.cmdPalette)lt.cmdPalette.open()"
|
|
|
|
|
style="font-size:0.65rem;opacity:0.65;letter-spacing:0.03em;padding:0.2rem 0.45rem">⌕ K</button>
|
2026-03-28 13:06:40 -04:00
|
|
|
<button type="button" class="lt-theme-btn" id="lt-theme-btn"
|
|
|
|
|
aria-label="Switch to light mode" title="Switch to light mode">☀</button>
|
2026-03-27 19:05:42 -04:00
|
|
|
</div><!-- /.lt-header-right -->
|
|
|
|
|
|
|
|
|
|
</header><!-- /.lt-header -->
|
|
|
|
|
|
2026-04-05 11:39:23 -04:00
|
|
|
<!-- ── COMMAND PALETTE OVERLAY (Ctrl+K / ⌘K) ──────────────────── -->
|
|
|
|
|
<div id="lt-cmd-overlay" class="lt-cmd-overlay" role="dialog" aria-modal="true" aria-label="Command palette" aria-hidden="true">
|
|
|
|
|
<div id="lt-cmd-palette" class="lt-cmd-palette" role="combobox" aria-expanded="true" aria-haspopup="listbox">
|
|
|
|
|
<div class="lt-cmd-input-wrap">
|
|
|
|
|
<span aria-hidden="true" style="opacity:0.45;margin-right:0.4rem;font-size:0.9em">⌕</span>
|
|
|
|
|
<input class="lt-cmd-input" type="text" placeholder="Type a command or search…"
|
|
|
|
|
autocomplete="off" spellcheck="false" aria-label="Command search" aria-autocomplete="list"
|
|
|
|
|
aria-controls="lt-cmd-results-list">
|
|
|
|
|
<kbd style="font-size:0.6rem;opacity:0.4;white-space:nowrap">ESC</kbd>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="lt-cmd-results" id="lt-cmd-results-list" role="listbox"></div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<script nonce="<?= htmlspecialchars($nonce, ENT_QUOTES, 'UTF-8') ?>">
|
|
|
|
|
(function() {
|
|
|
|
|
var isAdmin = <?= json_encode($_lt_isAdmin) ?>;
|
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
|
|
|
var commands = [
|
|
|
|
|
{ id: 'nav-dashboard', label: 'Dashboard', icon: '⌂', group: 'Navigate', action: function(){ location.href = '/'; } },
|
|
|
|
|
{ id: 'nav-new-ticket', label: 'New Ticket', icon: '+', group: 'Navigate', kbd: 'N', action: function(){ location.href = '/create'; } },
|
|
|
|
|
{ id: 'filter-mine', label: 'My Open Tickets', icon: '◈', group: 'Filter', action: function(){ location.href = '/?assigned_to=me&status=Open,In+Progress,Pending'; } },
|
2026-04-11 14:30:31 -04:00
|
|
|
{ id: 'filter-unassigned', label: 'Unassigned Tickets', icon: '◌', group: 'Filter', action: function(){ location.href = '/?assigned_to=unassigned'; } },
|
2026-04-05 11:39:23 -04:00
|
|
|
{ id: 'filter-critical', label: 'P1 Critical Tickets', icon: '!', group: 'Filter', action: function(){ location.href = '/?priority=1'; } },
|
|
|
|
|
];
|
|
|
|
|
if (isAdmin) {
|
|
|
|
|
[
|
|
|
|
|
{ id: 'admin-templates', label: 'Admin: Templates', icon: '▤', href: '/admin/templates' },
|
|
|
|
|
{ id: 'admin-workflow', label: 'Admin: Workflow', icon: '⇌', href: '/admin/workflow' },
|
|
|
|
|
{ id: 'admin-audit', label: 'Admin: Audit Log', icon: '📋', href: '/admin/audit-log' },
|
|
|
|
|
{ id: 'admin-api-keys', label: 'Admin: API Keys', icon: '🔑', href: '/admin/api-keys' },
|
|
|
|
|
{ id: 'admin-users', label: 'Admin: User Activity', icon: '👤', href: '/admin/user-activity' },
|
|
|
|
|
{ id: 'admin-recurring', label: 'Admin: Recurring', icon: '↻', href: '/admin/recurring-tickets' },
|
|
|
|
|
{ id: 'admin-fields', label: 'Admin: Custom Fields', icon: '⊞', href: '/admin/custom-fields' },
|
|
|
|
|
].forEach(function(c) {
|
|
|
|
|
commands.push({ id: c.id, label: c.label, icon: c.icon, group: 'Admin', action: function(href){ return function(){ location.href = href; }; }(c.href) });
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
// Inject recent ticket IDs from localStorage
|
|
|
|
|
try {
|
|
|
|
|
var recent = JSON.parse(localStorage.getItem('lt_recent_tickets') || '[]');
|
|
|
|
|
recent.slice(0, 5).forEach(function(id) {
|
|
|
|
|
commands.push({ id: 'recent-' + id, label: 'Ticket #' + id, icon: '◷', group: 'Recent', tags: ['ticket'], action: function(tid){ return function(){ location.href = '/ticket/' + tid; }; }(id) });
|
|
|
|
|
});
|
|
|
|
|
} catch(_) {}
|
|
|
|
|
if (window.lt && lt.cmdPalette) lt.cmdPalette.init(commands);
|
|
|
|
|
});
|
|
|
|
|
// Keyboard shortcut: Ctrl+K / Cmd+K
|
|
|
|
|
document.addEventListener('keydown', function(e) {
|
|
|
|
|
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
if (window.lt && lt.cmdPalette) lt.cmdPalette.open();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
})();
|
|
|
|
|
</script>
|
|
|
|
|
|
2026-04-01 17:52:22 -04:00
|
|
|
<main class="lt-main lt-container" id="main-content" style="padding-top: calc(var(--header-height, 56px) + var(--space-lg, 1.5rem))">
|