BROOKO icon
BROOKO UK NETWORK
Where code meets creativity & adventure
File viewer

header.php

Type
php
Size
18.77 KB
Modified
15 May
header.php 18.77 KB
<?php
require_once __DIR__ . '/../lib/feature_modules.php';
if (!isLoggedIn()) {
    app_redirect('login');
}

$currentUser = getCurrentUser();
$companyName = 'Company Name';
try {
    global $pdo;
    $stmt = $pdo->query("SELECT value FROM system_info WHERE `key`='company_name'");
    $r = $stmt->fetch();
    if ($r && !empty($r['value'])) {
        $companyName = $r['value'];
    }
} catch (Throwable $e) {}

$logoAsset = function_exists('wp_logo_asset') ? wp_logo_asset() : null;
$logoSrc = $logoAsset['url'] ?? '';
$logoExists = ($logoSrc !== '');

$favAsset = function_exists('wp_favicon_asset') ? wp_favicon_asset(true) : null;
$faviconSrc = $favAsset['url'] ?? '';

$route = app_request_path();
$isAdminArea = (strpos($route, '/admin') === 0);
$isManagementArea = (strpos($route, '/management') === 0);
$isAdmin = isAdminRole();
$canManagement = ($isAdmin || hasPermission('management.access'));

$managementSubnav = [];
if ($canManagement) {
    $managementSubnav[] = ['label' => 'Dashboard', 'path' => 'management', 'match' => ['/management']];
    if ($isAdmin || hasPermission('vehicles.view')) {
        $managementSubnav[] = ['label' => 'Vehicles', 'path' => 'management/vehicles', 'match' => ['/management/vehicles', '/management/vehicals']];
    }
    if ($isAdmin || hasAnyPermission(['rota.view','rota.edit'])) {
        $managementSubnav[] = ['label' => 'Rota', 'path' => 'management/rota', 'match' => ['/management/rota']];
    }
    if ($isAdmin || hasPermission('clock.view_team')) {
        $managementSubnav[] = ['label' => 'Clock Records', 'path' => 'management/clock', 'match' => ['/management/clock']];
    }
    if ($isAdmin || hasAnyPermission(['holidays.view','holidays.manage'])) {
        $managementSubnav[] = ['label' => 'Holiday Requests', 'path' => 'management/holidays', 'match' => ['/management/holidays']];
    }
    if ($isAdmin || hasAnyPermission(['incidents.view','incidents.manage'])) {
        $managementSubnav[] = ['label' => 'Incidents', 'path' => 'management/incidents', 'match' => ['/management/incidents']];
    }
    if ($isAdmin || hasAnyPermission(['contacts.view','contacts.manage'])) {
        $managementSubnav[] = ['label' => 'Contacts', 'path' => 'management/contacts', 'match' => ['/management/contacts']];
    }
}

$adminSubnav = [];
if ($isAdmin) {
    $adminSubnav = [
        ['label' => 'Overview', 'path' => 'admin', 'match' => ['/admin']],
        ['label' => 'Staff Management', 'path' => 'admin/staff', 'match' => ['/admin/staff']],
        ['label' => 'Calendar Setup', 'path' => 'admin/calendar', 'match' => ['/admin/calendar']],
        ['label' => 'TruTrak Admin', 'path' => 'admin/trutrak', 'match' => ['/admin/trutrak']],
        ['label' => 'Exports', 'path' => 'admin/exports', 'match' => ['/admin/exports']],
        ['label' => 'Integrations', 'path' => 'admin/integrations', 'match' => ['/admin/integrations']],
        ['label' => 'Activity Logs', 'path' => 'admin/logs', 'match' => ['/admin/logs']],
        ['label' => 'Maintenance', 'path' => 'admin/maintenance', 'match' => ['/admin/maintenance']],
    ];
}

$v = APP_VERSION;
$clockNavStatus = wp_clock_available_for_current_user() ? wp_clock_status() : ['status' => 'not_available'];
$clockNavLabel = match ($clockNavStatus['status'] ?? 'not_available') {
    'clocked_in' => 'End Shift / Status',
    'shift_ended' => 'Shift Complete',
    'not_clocked_in' => 'Start Shift',
    default => 'Clock',
};
$alertsMenu = (hasPermission('alerts.view') || $isAdmin) ? wp_feature_fetch_user_alerts(6) : ['unread' => 0, 'items' => []];
$headerCanRota = (hasPermission('rota.view') || $isAdmin);
$headerMyRota = $headerCanRota ? wp_feature_fetch_rota_for_current_user(date('Y-m-d'), 12) : [];
$avatarLetter = strtoupper(substr((string)($currentUser['display_name'] ?? 'U'), 0, 1));

$extraHeaderCss = '<style>
.header-tools{display:flex;align-items:center;gap:10px;}
.header-drop-toggle{display:flex;align-items:center;gap:12px;border:1px solid var(--border);background:rgba(255,255,255,.02);border-radius:16px;padding:8px 12px;color:var(--text-main);text-decoration:none;}
.header-drop-toggle::after{display:none !important;}
.header-bell{position:relative;border:1px solid var(--border);background:rgba(255,255,255,.02);border-radius:14px;padding:8px 12px;color:var(--text-main);}
.header-badge{position:absolute;top:-6px;right:-6px;min-width:20px;height:20px;padding:0 6px;border-radius:999px;background:var(--accent);color:#111;font-size:.75rem;font-weight:800;display:flex;align-items:center;justify-content:center;border:2px solid var(--bg-main);}
.dropdown-menu.app-dropdown{min-width:320px;max-width:min(92vw,380px);background:#121212;border:1px solid rgba(255,255,255,.08);border-radius:16px;padding:10px;box-shadow:0 20px 40px rgba(0,0,0,.35);}
.dropdown-menu.app-dropdown .dropdown-item{border-radius:10px;color:var(--text-main);padding:10px 12px;white-space:normal;}
.dropdown-menu.app-dropdown .dropdown-item:hover{background:rgba(255,255,255,.05);}
.dropdown-menu.app-dropdown .dropdown-divider{border-color:rgba(255,255,255,.08);}
.header-alert-item{display:block;padding:10px 12px;border-radius:12px;background:rgba(255,255,255,.02);margin-bottom:8px;color:var(--text-main);text-decoration:none;}
.header-alert-item.unread{border:1px solid rgba(255,140,26,.35);background:rgba(255,140,26,.08);}
.header-alert-title{display:flex;justify-content:space-between;gap:10px;font-weight:800;margin-bottom:4px;}
.header-alert-meta{font-size:.84rem;color:var(--text-muted);}
.profile-meta{display:flex;flex-direction:column;align-items:flex-end;line-height:1.2;}
body.sidebar-open{overflow:hidden;}@media (max-width:900px){.profile-meta{display:none}.dropdown-menu.app-dropdown{min-width:280px;max-width:92vw;}.header-tools{gap:8px;}.header-drop-toggle{padding:8px 10px;}}
</style>';
?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0">
    <meta name="mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <title><?= e($pageTitle ?? 'WorkersPanel') ?> - <?= e($companyName) ?></title>
    <?php if ($faviconSrc !== ''): ?>
        <link rel="icon" href="<?= e($faviconSrc) ?>">
        <link rel="apple-touch-icon" href="<?= e($faviconSrc) ?>">
    <?php endif; ?>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
    <link rel="stylesheet" href="<?= e(app_asset_url('css/bootstrap-dark-theme.css')) ?>?v=<?= e($v) ?>">
    <link rel="stylesheet" href="<?= e(app_asset_url('css/variables.css')) ?>?v=<?= e($v) ?>">
    <link rel="stylesheet" href="<?= e(app_asset_url('css/base.css')) ?>?v=<?= e($v) ?>">
    <link rel="stylesheet" href="<?= e(app_asset_url('css/components.css')) ?>?v=<?= e($v) ?>">
    <link rel="stylesheet" href="<?= e(app_asset_url('css/layout.css')) ?>?v=<?= e($v) ?>">
    <link rel="stylesheet" href="<?= e(app_asset_url('css/desktop.css')) ?>?v=<?= e($v) ?>">
    <link rel="stylesheet" href="<?= e(app_asset_url('css/modal.css')) ?>?v=<?= e($v) ?>">
    <script src="<?= e(app_asset_url('js/modal.js')) ?>?v=<?= e($v) ?>" defer></script>
    <script>
        function wpSetSidebar(open) {
            const sidebar = document.querySelector('.sidebar');
            const backdrop = document.getElementById('sidebarBackdrop');
            if (!sidebar) return;
            sidebar.classList.toggle('open', !!open);
            if (backdrop) backdrop.classList.toggle('open', !!open);
            document.body.classList.toggle('sidebar-open', !!open);
        }
        function toggleMobileMenu() {
            const sidebar = document.querySelector('.sidebar');
            if (!sidebar) return;
            wpSetSidebar(!sidebar.classList.contains('open'));
        }
        document.addEventListener('click', function(e) {
            const sidebar = document.querySelector('.sidebar');
            const toggle = document.getElementById('mobileMenuToggle');
            const backdrop = document.getElementById('sidebarBackdrop');
            if (!sidebar || !sidebar.classList.contains('open')) return;
            if (backdrop && backdrop.contains(e.target)) {
                wpSetSidebar(false);
                return;
            }
            if (!sidebar.contains(e.target) && toggle && !toggle.contains(e.target)) {
                wpSetSidebar(false);
            }
        });
        document.addEventListener('keydown', function(e) {
            if (e.key === 'Escape') wpSetSidebar(false);
        });
        window.addEventListener('resize', function() {
            if (window.innerWidth > 900) wpSetSidebar(false);
        });
        function wpCleanupTransientUi() {
            document.querySelectorAll('.wp-modal-overlay').forEach(function(el){ el.remove(); });
            document.querySelectorAll('.modal.show').forEach(function(el){
                el.classList.remove('show');
                el.setAttribute('aria-hidden', 'true');
                el.style.display = 'none';
            });
            document.querySelectorAll('.modal-backdrop').forEach(function(el){ el.remove(); });
            document.body.classList.remove('modal-open');
            document.body.style.removeProperty('padding-right');
            document.body.style.removeProperty('overflow');
            if (window.innerWidth > 900) {
                wpSetSidebar(false);
            }
        }
        document.addEventListener('DOMContentLoaded', wpCleanupTransientUi);
        window.addEventListener('pageshow', wpCleanupTransientUi);
        document.addEventListener('hidden.bs.modal', function() {
            window.setTimeout(wpCleanupTransientUi, 0);
        });
    </script>
    <?= $extraHeaderCss ?>
    <?php if (!empty($extraHead)) echo $extraHead; ?>
</head>
<body>
<div class="app-container">
    <div class="sidebar-backdrop" id="sidebarBackdrop"></div>
    <?php include __DIR__ . '/sidebar.php'; ?>
    <div class="main-area">
        <div class="page-header-simple">
            <div class="header-left">
                <?php if ($logoExists): ?>
                    <a href="<?= e(app_url()) ?>" class="header-logo-link" aria-label="Go to dashboard"><img src="<?= e($logoSrc) ?>" alt="<?= e($companyName) ?>" class="header-logo mobile-only"></a>
                    <span class="company-name desktop-only"><?= e($companyName) ?></span>
                    <span class="page-separator desktop-only">-</span>
                <?php endif; ?>
                <span class="page-name"><?= e($pageTitle ?? 'Dashboard') ?></span>
            </div>
            <div class="header-right header-tools">
                <?php if (hasPermission('alerts.view') || $isAdmin): ?>
                    <div class="dropdown">
                        <button class="header-bell dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">๐Ÿ””
                            <?php if (($alertsMenu['unread'] ?? 0) > 0): ?><span class="header-badge"><?= (int)$alertsMenu['unread'] ?></span><?php endif; ?>
                        </button>
                        <div class="dropdown-menu dropdown-menu-end app-dropdown">
                            <div class="d-flex justify-content-between align-items-center px-2 pb-2">
                                <strong>Alerts</strong>
                                <?php if (hasPermission('alerts.send') || $isAdmin): ?><a class="btn btn-sm btn-secondary" href="<?= e(app_url('management/alerts')) ?>">Send</a><?php endif; ?>
                            </div>
                            <?php if (empty($alertsMenu['items'])): ?>
                                <div class="text-muted px-2 py-2">No active alerts right now.</div>
                            <?php else: ?>
                                <?php foreach ($alertsMenu['items'] as $alert): ?>
                                    <a class="header-alert-item <?= empty($alert['is_read']) ? 'unread' : '' ?>" href="<?= e(app_url('management/alerts')) ?>">
                                        <div class="header-alert-title"><span><?= e((string)$alert['title']) ?></span><span><?= e((string)$alert['priority']) ?></span></div>
                                        <div class="text-muted" style="font-size:.9rem;"><?= e(mb_strimwidth((string)$alert['message'], 0, 110, 'โ€ฆ')) ?></div>
                                        <div class="header-alert-meta"><?= e((string)$alert['sent_by_name']) ?> ยท <?= e(date('d/m H:i', strtotime((string)$alert['sent_at']))) ?></div>
                                    </a>
                                <?php endforeach; ?>
                            <?php endif; ?>
                            <div class="dropdown-divider"></div>
                            <a class="dropdown-item" href="<?= e(app_url('management/alerts')) ?>">Open alerts centre</a>
                        </div>
                    </div>
                <?php endif; ?>
                <div class="dropdown">
                    <button class="header-drop-toggle dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
                        <div class="profile-meta">
                            <div class="user-name"><?= e($currentUser['display_name'] ?? 'User') ?></div>
                            <div class="user-role"><?= e(ucwords(str_replace('-', ' ', (string)($_SESSION['user_role'] ?? '')))) ?></div>
                        </div>
                        <div class="user-avatar"><?= e($avatarLetter) ?></div>
                    </button>
                    <div class="dropdown-menu dropdown-menu-end app-dropdown">
                        <div class="px-2 pb-2">
                            <div class="fw-bold"><?= e($currentUser['display_name'] ?? 'User') ?></div>
                            <div class="text-muted small"><?= e(ucwords(str_replace('-', ' ', (string)($_SESSION['user_role'] ?? '')))) ?></div>
                        </div>
                        <?php if (wp_clock_available_for_current_user()): ?>
                            <a class="dropdown-item" href="<?= e(app_url('clock')) ?>">โฑ๏ธ <?= e($clockNavLabel) ?></a>
                        <?php endif; ?>
                        <?php if ($headerCanRota): ?>
                            <button class="dropdown-item" type="button" data-wp-header-rota>๐Ÿ—“๏ธ My Rota</button>
                        <?php endif; ?>
                        <a class="dropdown-item" href="<?= e(app_url('account')) ?>">๐Ÿ‘ค Profile</a>
                        <div class="dropdown-divider"></div>
                        <a class="dropdown-item" href="<?= e(app_url('logout')) ?>">๐Ÿšช Logout</a>
                    </div>
                </div>
                <button class="mobile-menu-toggle" id="mobileMenuToggle" onclick="toggleMobileMenu()">โ˜ฐ</button>
            </div>
        </div>

        <?php if ($isAdminArea && !empty($adminSubnav)): ?>
            <div class="header-nav">
                <?php foreach ($adminSubnav as $link): ?>
                    <?php $isActive = false; foreach ($link['match'] as $match) { if ($route === $match || str_starts_with($route, $match . '/')) { $isActive = true; break; } } ?>
                    <a href="<?= e(app_url($link['path'])) ?>" class="header-nav-item <?= $isActive ? 'active' : '' ?>"><?= e($link['label']) ?></a>
                <?php endforeach; ?>
            </div>
        <?php endif; ?>

        <?php if ($isManagementArea && !empty($managementSubnav)): ?>
            <div class="header-nav">
                <?php foreach ($managementSubnav as $link): ?>
                    <?php $isActive = false; foreach ($link['match'] as $match) { if ($route === $match || str_starts_with($route, $match . '/')) { $isActive = true; break; } } ?>
                    <a href="<?= e(app_url($link['path'])) ?>" class="header-nav-item <?= $isActive ? 'active' : '' ?>"><?= e($link['label']) ?></a>
                <?php endforeach; ?>
            </div>
        <?php endif; ?>

        <?php if ($headerCanRota): ?>
            <div class="wp-header-rota-template" hidden>
                <template id="wpHeaderRotaTemplate">
                    <?php if (!$headerMyRota): ?>
                        <div class="text-muted">No rota entries are attached to your account yet.</div>
                    <?php else: ?>
                        <div style="display:grid;gap:12px;max-height:min(62vh,560px);overflow:auto;">
                            <?php foreach ($headerMyRota as $entry): ?>
                                <div style="border:1px solid var(--border);border-radius:14px;padding:12px;background:rgba(255,255,255,.02);">
                                    <div class="d-flex justify-content-between gap-3 flex-wrap">
                                        <strong><?= e((string)$entry['shift_date']) ?></strong>
                                        <span class="text-muted"><?= e((string)($entry['role_label'] ?: 'Assigned')) ?></span>
                                    </div>
                                    <div style="margin-top:6px;font-weight:700;"><?= e((string)($entry['route_name'] ?: 'Route not set')) ?></div>
                                    <div class="text-muted" style="margin-top:4px;"><?= e(trim((string)($entry['van_label'] ?: 'No van') . (($entry['notes'] ?? '') ? ' ยท ' . $entry['notes'] : ''))) ?></div>
                                </div>
                            <?php endforeach; ?>
                        </div>
                    <?php endif; ?>
                    <div style="display:flex;gap:10px;justify-content:flex-end;align-items:center;margin-top:18px;flex-wrap:wrap;">
                        <button type="button" class="btn btn-secondary" data-close-header-rota>Close</button>
                        <a href="<?= e(app_url('management/rota?mine=1')) ?>" class="btn btn-primary">Open full rota</a>
                    </div>
                </template>
            </div>
            <script>
            document.addEventListener('DOMContentLoaded', function(){
                let headerRotaModal = null;
                function closeHeaderRota(){ if (headerRotaModal) { headerRotaModal.close(); headerRotaModal = null; } }
                document.addEventListener('click', function(ev){
                    const closeBtn = ev.target.closest('[data-close-header-rota]');
                    if (closeBtn) { ev.preventDefault(); closeHeaderRota(); return; }
                    const trigger = ev.target.closest('[data-wp-header-rota]');
                    if (!trigger) return;
                    ev.preventDefault();
                    const tpl = document.getElementById('wpHeaderRotaTemplate');
                    if (!tpl || typeof Modal === 'undefined') return;
                    closeHeaderRota();
                    headerRotaModal = new Modal({title:'My Rota', content:tpl.innerHTML, size:'large', buttons:[], onClose:function(){ headerRotaModal = null; }});
                    headerRotaModal.open();
                });
            });
            </script>
        <?php endif; ?>

        <main class="main-content">