e721b33911
- Replace lt-chip priority badges with lt-badge lt-badge-p[1-4] across DashboardView, TemplatesView (matches web_template sticky table pattern) - Add lt-theme-btn theme toggle to header-right; wire lt.theme.toggle() - Replace ASCII art empty state with lt-empty-state component in dashboard - Standardize tab wrapper lt-tabs → lt-tab-bar in Dashboard and TicketView - Add missing lt-keys-help modal to layout_footer (fixes ? key doing nothing) - Add lt-cmd-overlay command palette container + lt.cmdPalette.init() nav - Add .lt-timeline-action CSS rule (used in TicketView, was undefined) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
166 lines
9.2 KiB
PHP
166 lines
9.2 KiB
PHP
<?php
|
|
/**
|
|
* 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';
|
|
?>
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<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">
|
|
<title><?= htmlspecialchars($pageTitle ?? 'Dashboard', ENT_QUOTES, 'UTF-8') ?> — Tinker Tickets</title>
|
|
<meta name="robots" content="noindex, nofollow">
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:ital,wght@0,400;0,600;0,700;1,400&display=swap" rel="stylesheet">
|
|
<link rel="stylesheet" href="/assets/css/base.css">
|
|
<?php if (!empty($pageStyles)): ?>
|
|
<?php foreach ($pageStyles as $_lt_css): ?>
|
|
<link rel="stylesheet" href="<?= htmlspecialchars($_lt_css, ENT_QUOTES, 'UTF-8') ?>">
|
|
<?php endforeach; ?>
|
|
<?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 -->
|
|
<script nonce="<?= htmlspecialchars($nonce, ENT_QUOTES, 'UTF-8') ?>" src="/assets/js/base.js"></script>
|
|
<script nonce="<?= htmlspecialchars($nonce, ENT_QUOTES, 'UTF-8') ?>" src="/assets/js/utils.js"></script>
|
|
<!-- Inline JS globals (CSRF, timezone, user) available immediately -->
|
|
<script nonce="<?= htmlspecialchars($nonce, ENT_QUOTES, 'UTF-8') ?>">
|
|
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) ?>;
|
|
window.APP_TIMEZONE_OFFSET = <?= (int)($GLOBALS['config']['TIMEZONE_OFFSET'] ?? 0) ?>;
|
|
window.CURRENT_USER = <?= json_encode([
|
|
'id' => (int)($GLOBALS['currentUser']['user_id'] ?? 0),
|
|
'username'=> $GLOBALS['currentUser']['username'] ?? '',
|
|
'isAdmin' => !empty($GLOBALS['currentUser']['is_admin']),
|
|
], JSON_UNESCAPED_UNICODE | JSON_HEX_TAG) ?>;
|
|
</script>
|
|
</head>
|
|
<body>
|
|
|
|
<!-- 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 -->
|
|
<div id="lt-boot" class="lt-boot-overlay" data-app-name="TINKER TICKETS" style="display:none" aria-hidden="true">
|
|
<pre id="lt-boot-text" class="lt-boot-text"></pre>
|
|
</div>
|
|
|
|
<!-- 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">
|
|
<span class="lt-brand-title">TINKER TICKETS</span>
|
|
<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>
|
|
<?php if ($_lt_isAdmin): ?>
|
|
<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>
|
|
<?php endif; ?>
|
|
</nav>
|
|
</div><!-- /.lt-nav-drawer -->
|
|
<!-- Overlay: outside drawer, full-screen; JS toggles .open class -->
|
|
<div id="lt-nav-overlay" class="lt-nav-drawer-overlay"></div>
|
|
|
|
<!-- 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"
|
|
data-text="TINKER TICKETS"
|
|
style="text-decoration:none"
|
|
aria-label="Tinker Tickets home">TINKER TICKETS</a>
|
|
<span class="lt-brand-subtitle">LotusGuild Infrastructure</span>
|
|
</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>
|
|
<?php if ($_lt_isAdmin): ?>
|
|
<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">
|
|
<?php if (!empty($_lt_user)): ?>
|
|
<span class="lt-header-user"><?= htmlspecialchars($_lt_user['display_name'] ?? $_lt_user['username'] ?? '', ENT_QUOTES, 'UTF-8') ?></span>
|
|
<?php if ($_lt_isAdmin): ?>
|
|
<span class="lt-badge lt-badge-admin" aria-label="Administrator">ADMIN</span>
|
|
<?php endif; ?>
|
|
<?php endif; ?>
|
|
<button type="button" class="lt-theme-btn" id="lt-theme-btn"
|
|
aria-label="Switch to light mode" title="Switch to light mode">☀</button>
|
|
</div><!-- /.lt-header-right -->
|
|
|
|
</header><!-- /.lt-header -->
|
|
|
|
<main class="lt-main lt-container" id="main-content">
|