<?php
/* ============================================================================
   WBMCZ ADMIN — PREMIUM SIDEBAR  (shared by every page in /admin)
   ----------------------------------------------------------------------------
   Usage inside any admin page, in place of the whole old inline <aside>…</aside>:

        include __DIR__ . '/includes/sidebar.php';

   The page must already be running Alpine.js and must expose `sidebarOpen`
   on the <body> x-data (every admin page already does).

   It renders the complete <aside> — brand header, scrollable nav and footer.
   ========================================================================== */

if (!defined('WB_SIDEBAR_LOADED')) {
    define('WB_SIDEBAR_LOADED', 1);
}

/* Permission engine — decides which menu entries this staff account may see */
if (!function_exists('rbac_can')) { @require_once __DIR__ . '/../../includes/rbac.php'; }
/* names the per-category product pages, for the menu below */
if (!function_exists('pmp_file')) { @require_once __DIR__ . '/../../includes/product_pages.php'; }

/* ---------------------------------------------------------------------------
   1. CURRENT PAGE + QUERY CONTEXT
   ------------------------------------------------------------------------ */
$WB_FILE  = basename($_SERVER['PHP_SELF'] ?? 'index.php');
$WB_ROLE  = isset($_GET['role'])   ? strtolower(trim((string)$_GET['role']))   : '';
$WB_CAT   = isset($_GET['cat_id']) ? (int)$_GET['cat_id']                      : 0;
$WB_HASH  = '';   // resolved client side for #anchor links

/* ---------------------------------------------------------------------------
   2. SERVICE CATEGORIES (DB driven — falls back to a static list)
   ------------------------------------------------------------------------ */
$WB_CATS = [];
if (isset($db) && is_object($db)) {
    try {
        $WB_CATS = $db->select(
            "SELECT id, title, icon_class, category_key FROM service_categories
             WHERE COALESCE(status,1) = 1
             ORDER BY display_order ASC, id ASC"
        );
    } catch (\Throwable $e) { $WB_CATS = []; }
}
if (empty($WB_CATS)) {
    $WB_CATS = [
        ['id' => 1,  'title' => 'Government Portals',        'icon_class' => 'fa-landmark', 'category_key' => 'cat_gov_portals'],
        ['id' => 2,  'title' => 'Important Tools',           'icon_class' => 'fa-tools', 'category_key' => 'cat_imp_tools'],
        ['id' => 3,  'title' => 'Service Booking',           'icon_class' => 'fa-calendar-check', 'category_key' => 'cat_service_booking'],
        ['id' => 4,  'title' => 'Print & Order',             'icon_class' => 'fa-print', 'category_key' => 'cat_print_order'],
        ['id' => 5,  'title' => 'Agency & Software',         'icon_class' => 'fa-laptop-code', 'category_key' => 'cat_agency_software'],
        ['id' => 6,  'title' => 'Office & Cyber Accessories','icon_class' => 'fa-keyboard', 'category_key' => 'cat_cyber_accessories'],
        ['id' => 7,  'title' => 'Online Courses',            'icon_class' => 'fa-graduation-cap', 'category_key' => 'cat_online_courses'],
        ['id' => 8,  'title' => 'Website & App Development', 'icon_class' => 'fa-code', 'category_key' => 'cat_web_dev'],
        ['id' => 9,  'title' => 'Digital Marketing',         'icon_class' => 'fa-bullhorn', 'category_key' => 'cat_digi_marketing'],
        ['id' => 10, 'title' => 'Shopping',                  'icon_class' => 'fa-shopping-cart', 'category_key' => 'cat_shopping'],
        ['id' => 11, 'title' => 'B2B Shopping',              'icon_class' => 'fa-boxes-stacked', 'category_key' => 'cat_b2b_shopping'],
    ];
}

/* ---------------------------------------------------------------------------
   3. LIVE BADGE COUNTS  (cheap COUNT queries, all failure-tolerant)
   ------------------------------------------------------------------------ */
if (!function_exists('wb_count')) {
    function wb_count($db, $sql) {
        try {
            $r = $db->select($sql);
            return !empty($r) ? (int)array_values($r[0])[0] : 0;
        } catch (\Throwable $e) { return 0; }
    }
}
$WB_BADGE = ['kyc' => 0, 'upgrade' => 0, 'payout' => 0, 'ticket' => 0, 'order' => 0, 'service' => 0, 'message' => 0, 'deleted' => 0, 'lead_open' => 0, 'lead_due' => 0, 'task' => 0];
if (isset($db) && is_object($db)) {
    /* unread enquiries sent from the public contact form */
    $WB_BADGE['message'] = wb_count($db, "SELECT COUNT(*) FROM contact_messages WHERE COALESCE(status,0)=0");
    /* soft-deleted accounts waiting in the recycle bin */
    $WB_BADGE['deleted'] = wb_count($db, "SELECT COUNT(*) FROM users WHERE deleted_at IS NOT NULL");
    $WB_BADGE['kyc']     = wb_count($db, "SELECT COUNT(*) FROM kyc_details WHERE status='pending'");
    $WB_BADGE['ticket']  = wb_count($db, "SELECT COUNT(*) FROM support_tickets WHERE status IN ('Open','open','Pending','pending')");
    $WB_BADGE['order']   = wb_count($db, "SELECT COUNT(*) FROM orders WHERE status IN ('Pending','pending','Processing','processing')");
    $WB_BADGE['payout']  = wb_count($db, "SELECT COUNT(*) FROM withdrawal_requests WHERE status IN ('pending','Pending')");
    $WB_BADGE['upgrade'] = wb_count($db, "SELECT COUNT(*) FROM seller_upgrade_requests WHERE status IN ('pending','Pending')");
    $WB_BADGE['service'] = wb_count($db, "SELECT COUNT(*) FROM seller_services WHERE COALESCE(status,0)=0");
    /* leads still open, and the ones whose follow-up date has arrived */
    $WB_BADGE['lead_open'] = wb_count($db, "SELECT COUNT(*) FROM leads WHERE status IN ('new','contacted','interested','follow_up')");
    $WB_BADGE['lead_due']  = wb_count($db, "SELECT COUNT(*) FROM leads WHERE next_follow_up IS NOT NULL AND next_follow_up <= CURDATE() AND status IN ('new','contacted','interested','follow_up')");
    /* the open tasks sitting with whoever is looking at this menu */
    $__me_id = isset($_SESSION['user_id']) ? (int)$_SESSION['user_id'] : 0;
    if ($__me_id > 0) {
        $WB_BADGE['task'] = wb_count($db, "SELECT COUNT(*) FROM tasks WHERE assigned_to = $__me_id AND status IN ('pending','in_progress','followup')");
    }
}

/* ---------------------------------------------------------------------------
   4. ROLE SUB-MENU BUILDER
      Every "management" group in the checklist repeats the same audience
      breakdown, so it is generated once here.
   ------------------------------------------------------------------------ */
if (!function_exists('wb_role_children')) {
    /**
     * @param string $page   target php file
     * @param array  $roles  which audiences to include
     */
    function wb_role_children($page, $roles = ['all','free','pro','seller','employee','admin']) {
        $map = [
            'all'      => ['All',        'fa-layer-group',  'text-slate-300'],
            'free'     => ['Free User',  'fa-user',         'text-sky-300'],
            'pro'      => ['Pro User',   'fa-gem',          'text-fuchsia-300'],
            'seller'   => ['Seller',     'fa-store',        'text-amber-300'],
            'employee' => ['Employees',  'fa-user-tie',     'text-teal-300'],
            'admin'    => ['Admin',      'fa-shield-halved','text-rose-300'],
        ];
        $out = [];
        foreach ($roles as $r) {
            if (!isset($map[$r])) continue;
            $out[] = [
                'label' => $map[$r][0],
                'icon'  => $map[$r][1],
                'tone'  => $map[$r][2],
                'url'   => $page . '?role=' . $r,
                'match' => ['file' => $page, 'role' => $r],
            ];
        }
        return $out;
    }
}

/* Category children for Service / Order management */
if (!function_exists('wb_cat_children')) {
    /* ---------------------------------------------------------------------
       ONE MENU ENTRY PER CATEGORY

       The catalogue is split one page per category, so the menu lists the
       categories rather than a single "Product Management" link. Each entry
       points at that category's own file; if the file could not be written
       (a read-only folder), it falls back to the shared ?cat= door.
       --------------------------------------------------------------------- */
    function wb_product_children($cats) {
        $out = [];
        if (function_exists('pmp_file')) {
            foreach ($cats as $c) {
                $out[] = [
                    'label' => html_entity_decode($c['title'], ENT_QUOTES, 'UTF-8'),
                    'icon'  => !empty($c['icon_class']) ? $c['icon_class'] : 'fa-box-open',
                    'tone'  => ((int)$c['id'] === 1) ? 'text-amber-300' : 'text-slate-300',
                    'url'   => pmp_url($c, __DIR__ . '/..'),
                    'match' => ['file' => pmp_file($c)],
                    'perm'  => 'manage_services',
                    /* the broad catalogue key, or this category's own — whoever
                       holds either sees this line, and nobody else does */
                    'acl'   => ['products.view', 'gov_products.view', pmp_perm($c)],
                ];
            }
        }
        return $out;
    }

    function wb_cat_children($page, $cats, $allLabel = 'All Service') {
        $out = [[
            'label' => $allLabel,
            'icon'  => 'fa-layer-group',
            'tone'  => 'text-slate-300',
            'url'   => $page,
            'match' => ['file' => $page, 'cat' => 0],
        ]];
        foreach ($cats as $c) {
            $out[] = [
                'label' => html_entity_decode($c['title'], ENT_QUOTES, 'UTF-8'),
                'icon'  => !empty($c['icon_class']) ? $c['icon_class'] : 'fa-circle-dot',
                'tone'  => 'text-emerald-300',
                'url'   => $page . '?cat_id=' . (int)$c['id'],
                'match' => ['file' => $page, 'cat' => (int)$c['id']],
            ];
        }
        return $out;
    }
}

/* ---------------------------------------------------------------------------
   5. THE MENU  (exactly the WBMCZ checklist, in order)
   ------------------------------------------------------------------------ */
$WB_MENU = [

    ['type' => 'heading', 'label' => 'Overview'],

    [
        'type' => 'link', 'key' => 'dashboard', 'acl' => 'dashboard.view',
        'label' => 'Dashboard', 'icon' => 'fa-gauge-high', 'grad' => 'from-emerald-400 to-teal-500',
        'url' => 'index.php', 'match' => ['file' => 'index.php'],
    ],
    [
        'type' => 'group', 'key' => 'profile',
        'label' => 'Profile', 'icon' => 'fa-id-badge', 'grad' => 'from-sky-400 to-blue-500',
        'children' => [
            ['label' => 'My Profile',      'icon' => 'fa-user',        'tone' => 'text-slate-300', 'url' => 'profile.php#profile',   'match' => ['file' => 'profile.php']],
            ['label' => 'Edit Profile',    'icon' => 'fa-pen',         'tone' => 'text-slate-300', 'url' => 'profile.php#edit',      'match' => ['file' => 'profile.php']],
            ['label' => 'KYC',             'icon' => 'fa-fingerprint', 'tone' => 'text-slate-300', 'url' => 'profile.php#kyc',       'match' => ['file' => 'profile.php']],
            ['label' => 'Change Password', 'icon' => 'fa-key',         'tone' => 'text-slate-300', 'url' => 'profile.php#password',  'match' => ['file' => 'profile.php']],
            ['label' => 'ID Card',         'icon' => 'fa-address-card','tone' => 'text-slate-300', 'url' => 'profile.php#id_card',   'match' => ['file' => 'profile.php']],
        ],
    ],
    [
        'type' => 'link', 'key' => 'activity', 'perm' => 'manage_staff_roles', 'acl' => 'activity_log.view',
        'label' => 'Activity Log', 'icon' => 'fa-clock-rotate-left', 'grad' => 'from-violet-400 to-purple-500',
        'url' => 'activity_log.php', 'match' => ['file' => 'activity_log.php'],
    ],
    /* ONE entry, not a folder of four.

       Everything that used to hang off this — KYC requests, seller requests,
       product requests, the office desks — is a tab inside the page, and the
       page opens on whichever tab the person actually has. A second copy of
       those tabs in the sidebar only made them look like separate screens. */
    [
        'type' => 'link', 'key' => 'tasks', 'acl' => 'tasks.view',
        'label' => 'Activity & Task', 'icon' => 'fa-list-check', 'grad' => 'from-indigo-400 to-violet-600',
        'url' => 'activity_task.php', 'match' => ['file' => 'activity_task.php'],
        'badge' => $WB_BADGE['task'], 'badge_tone' => 'indigo',
    ],

    ['type' => 'heading', 'label' => 'Management'],

    /* USER MANAGEMENT IS THE SIX LISTS. Nothing else.

       What used to be piled in here has gone where it belongs: KYC Pending and
       Upgrade Requests are queues waiting on a decision, so they are desks on
       the Activity & Task board (Upgrade Requests was reading the very same
       table as the Seller Requests desk); KYC Questions sets up the catalogue,
       so it sits with the categories; staff and roles are their own group
       below; and Notifications is the bell in the top right of every page. */
    [
        'type' => 'group', 'key' => 'users', 'perm' => 'manage_users',
        'label' => 'User Management', 'icon' => 'fa-users', 'grad' => 'from-indigo-400 to-blue-600',
        'badge' => $WB_BADGE['kyc'] + $WB_BADGE['upgrade'], 'badge_tone' => 'amber',
        'children' => [
            /* One page per kind of account, and that is the whole split. Free,
               Pro and Deleted are no longer pages of their own — they are tiles
               across the top of the User page, and clicking one filters the
               list you are already looking at. Same rows, one screen. */
            ['label' => 'User',      'icon' => 'fa-user',          'tone' => 'text-sky-300',   'url' => 'users_user.php',     'match' => ['file' => 'users_user.php'],     'perm' => 'manage_users', 'acl' => ['users.view', 'users.view_user']],
            ['label' => 'Seller',    'icon' => 'fa-store',         'tone' => 'text-amber-300', 'url' => 'users_seller.php',   'match' => ['file' => 'users_seller.php'],   'perm' => 'manage_users', 'acl' => ['users.view', 'users.view_seller']],
            ['label' => 'Employees', 'icon' => 'fa-user-tie',      'tone' => 'text-teal-300',  'url' => 'users_employee.php', 'match' => ['file' => 'users_employee.php'], 'perm' => 'manage_users', 'acl' => ['users.view', 'users.view_employee']],
            ['label' => 'Admin',     'icon' => 'fa-shield-halved', 'tone' => 'text-rose-300',  'url' => 'users_admin.php',    'match' => ['file' => 'users_admin.php'],    'perm' => 'manage_users', 'acl' => ['users.view', 'users.view_admin']],
        ],
    ],
    /* Sellers are the only accounts that hold company money, so they get their
       own heading. The page carries Seller Collection, Verify Collection and
       Seller Dues across the top, each leading to the screen where that money
       is actually dealt with. */
    [
        'type' => 'group', 'key' => 'sellermgmt', 'perm' => ['manage_sellers', 'manage_users'],
        'label' => 'Seller Management', 'icon' => 'fa-store', 'grad' => 'from-amber-400 to-orange-600',
        'children' => [
            ['label' => 'Sellers',           'icon' => 'fa-store',                'tone' => 'text-amber-300',  'url' => 'users_seller.php', 'match' => ['file' => 'users_seller.php'], 'acl' => ['users.view', 'users.view_seller']],
            ['label' => 'Seller Profiles',   'icon' => 'fa-id-badge',             'tone' => 'text-slate-300',  'url' => 'sellers.php',      'match' => ['file' => 'sellers.php'],      'acl' => 'sellers.view'],
            ['label' => 'Seller Collection', 'icon' => 'fa-sack-dollar',          'tone' => 'text-emerald-300','url' => 'order_manage.php?tab=collections', 'match' => ['file' => 'order_manage.php'], 'acl' => 'orders.view'],
        ],
    ],
    /* Staff are not customers. Who works here, what they may touch, and the
       form used to take one on — three screens about the same subject, which
       is why they now have their own heading instead of sitting at the bottom
       of the customer lists. */
    [
        'type' => 'group', 'key' => 'staff', 'perm' => 'manage_staff_roles',
        'label' => 'Staff & Roles', 'icon' => 'fa-users-gear', 'grad' => 'from-violet-400 to-indigo-600',
        'children' => [
            ['label' => 'Staff Accounts',   'icon' => 'fa-user-gear',  'tone' => 'text-slate-300',  'url' => 'staff.php',        'match' => ['file' => 'staff.php'],        'perm' => 'manage_staff_roles', 'acl' => 'staff.view'],
            ['label' => 'Custom Roles',     'icon' => 'fa-user-lock',  'tone' => 'text-slate-300',  'url' => 'roles.php',        'match' => ['file' => 'roles.php'],        'perm' => 'manage_staff_roles', 'acl' => 'staff.role_view'],
            ['label' => 'Staff Form Fields','icon' => 'fa-list-check', 'tone' => 'text-violet-300', 'url' => 'staff_fields.php', 'match' => ['file' => 'staff_fields.php'], 'perm' => 'manage_staff_roles', 'acl' => 'staff.fields'],
        ],
    ],
    [
        'type' => 'group', 'key' => 'leads', 'perm' => 'manage_leads',
        'label' => 'Lead Management', 'icon' => 'fa-bullseye', 'grad' => 'from-rose-400 to-pink-600',
        'badge' => $WB_BADGE['lead_due'], 'badge_tone' => 'rose',
        'children' => [
            ['label' => 'Potential Leads', 'icon' => 'fa-user-clock',  'tone' => 'text-indigo-300', 'url' => 'leads.php?tab=potential', 'match' => ['file' => 'leads.php'], 'acl' => 'leads.view'],
            ['label' => 'Active Leads',    'icon' => 'fa-fire',        'tone' => 'text-amber-300',  'url' => 'leads.php?tab=active',    'match' => ['file' => 'leads.php'], 'acl' => 'leads.view', 'badge' => $WB_BADGE['lead_open']],
            ['label' => 'All Leads',       'icon' => 'fa-list',        'tone' => 'text-slate-300',  'url' => 'leads.php?tab=all',       'match' => ['file' => 'leads.php'], 'acl' => 'leads.view'],
            ['label' => 'Closed Leads',    'icon' => 'fa-flag-checkered','tone' => 'text-slate-300','url' => 'leads.php?tab=closed',    'match' => ['file' => 'leads.php'], 'acl' => 'leads.view'],
        ],
    ],
    [
        'type' => 'group', 'key' => 'wallet', 'perm' => 'manage_transactions',
        'label' => 'Wallet Management', 'icon' => 'fa-wallet', 'grad' => 'from-emerald-400 to-green-600',
        'children' => array_merge(
            wb_role_children('wallet.php'),
            [
                ['label' => 'Full Wallet History', 'icon' => 'fa-clock-rotate-left', 'tone' => 'text-slate-300', 'url' => 'wallet_history.php', 'match' => ['file' => 'wallet_history.php'], 'acl' => 'wallet.history'],
                ['label' => 'Transactions',        'icon' => 'fa-right-left',        'tone' => 'text-slate-300', 'url' => 'transactions.php',   'match' => ['file' => 'transactions.php'],   'acl' => 'wallet.view'],
            ]
        ),
    ],
    /* THE CATALOGUE — one line per category, and that is the whole list.

       It used to carry the categories twice (once for the catalogue, once for
       what sellers had submitted) plus four loose tools. The submissions are a
       queue, so they belong on the Activity & Task board with the other
       queues; bulk upload and sub categories are now inside each category page,
       where the thing you are uploading into is already decided. */
    [
        'type' => 'group', 'key' => 'services', 'perm' => 'manage_services',
        'label' => 'Service Management', 'icon' => 'fa-screwdriver-wrench', 'grad' => 'from-cyan-400 to-sky-600',
        'badge' => $WB_BADGE['service'], 'badge_tone' => 'sky',
        'children' => array_merge(
            wb_product_children($WB_CATS),
            [
                /* the two screens that belong to no single category */
                ['label' => 'Add / Edit Categories', 'icon' => 'fa-sitemap', 'tone' => 'text-slate-300',
                 'url' => 'services.php', 'match' => ['file' => 'services.php'], 'acl' => 'services.create'],
                /* which documents a product may ask a customer for — it sets up
                   the catalogue, so it belongs beside the categories */
                ['label' => 'KYC Questions', 'icon' => 'fa-list-check', 'tone' => 'text-violet-300',
                 'url' => 'kyc_fields.php', 'match' => ['file' => 'kyc_fields.php'], 'acl' => 'kyc.fields'],
            ]
        ),
    ],
    [
        'type' => 'group', 'key' => 'subs', 'perm' => ['manage_subscriptions', 'manage_settings'],
        'label' => 'Subscription Management', 'icon' => 'fa-crown', 'grad' => 'from-amber-400 to-yellow-600',
        'children' => [
            ['label' => 'All Plans',     'icon' => 'fa-layer-group', 'tone' => 'text-slate-300',  'url' => 'subscription_plans.php',          'match' => ['file' => 'subscription_plans.php'], 'acl' => 'subscriptions.view'],
            ['label' => 'Plan Products', 'icon' => 'fa-box-open',    'tone' => 'text-emerald-300','url' => 'subscription_plans.php#products', 'match' => ['file' => 'subscription_plans.php'], 'acl' => 'subscriptions.product_add'],
            /* "Subscribers" used to sit here as well as under User Management —
               same page, two names. It is Pro User, up there. */
        ],
    ],
    [
        'type' => 'group', 'key' => 'orders', 'perm' => 'manage_orders',
        'label' => 'Order Management', 'icon' => 'fa-box', 'grad' => 'from-orange-400 to-amber-600',
        'badge' => $WB_BADGE['order'], 'badge_tone' => 'orange',
        'children' => array_merge([
            /* One line, not three. The visits / collections / dues tabs are
               inside this screen, and deciding on them is done from the desks
               on the Activity & Task board. */
            ['label' => 'Visits & Collections', 'icon' => 'fa-person-walking', 'tone' => 'text-amber-300',
             'url' => 'order_manage.php', 'match' => ['file' => 'order_manage.php'], 'acl' => 'orders.view'],
        ], wb_cat_children('orders.php', $WB_CATS, 'All Orders')),
    ],
    [
        'type' => 'group', 'key' => 'settings', 'perm' => 'manage_settings',
        'label' => 'Website Settings', 'icon' => 'fa-sliders', 'grad' => 'from-slate-400 to-slate-600',
        'children' => [
            ['label' => 'All',             'icon' => 'fa-layer-group', 'tone' => 'text-slate-300',  'url' => 'settings.php',        'match' => ['file' => 'settings.php', 'role' => ''], 'acl' => 'website.view'],
            ['label' => 'Hero Slider',     'icon' => 'fa-images',      'tone' => 'text-amber-300',  'url' => 'hero_slides.php',     'match' => ['file' => 'hero_slides.php'],            'acl' => 'website.carousel'],
            /* SMS & OTP was listed twice — once here, once further down. Once. */
            ['label' => 'SMS & OTP',       'icon' => 'fa-comment-sms', 'tone' => 'text-sky-300',    'url' => 'sms_settings.php',    'match' => ['file' => 'sms_settings.php'],           'acl' => 'website.general'],
            ['label' => 'Payment Gateway', 'icon' => 'fa-credit-card', 'tone' => 'text-indigo-300', 'url' => 'payment_settings.php','match' => ['file' => 'payment_settings.php'],       'acl' => 'website.payment'],
            ['label' => 'Free User',  'icon' => 'fa-user',          'tone' => 'text-sky-300',    'url' => 'settings.php?role=free#customer_settings',  'match' => ['file' => 'settings.php', 'role' => 'free'],     'acl' => 'website.view'],
            ['label' => 'Pro User',   'icon' => 'fa-gem',           'tone' => 'text-fuchsia-300','url' => 'settings.php?role=pro#customer_settings',   'match' => ['file' => 'settings.php', 'role' => 'pro'],      'acl' => 'website.view'],
            ['label' => 'Seller',     'icon' => 'fa-store',         'tone' => 'text-amber-300',  'url' => 'settings.php?role=seller#seller_settings',  'match' => ['file' => 'settings.php', 'role' => 'seller'],   'acl' => 'website.view'],
            ['label' => 'Employees',  'icon' => 'fa-user-tie',      'tone' => 'text-teal-300',   'url' => 'settings.php?role=employee#staff_settings', 'match' => ['file' => 'settings.php', 'role' => 'employee'], 'acl' => 'website.view'],
            ['label' => 'Admin',      'icon' => 'fa-shield-halved', 'tone' => 'text-rose-300',   'url' => 'settings.php?role=admin#home_settings',     'match' => ['file' => 'settings.php', 'role' => 'admin'],    'acl' => 'website.view'],
        ],
    ],
    [
        'type' => 'group', 'key' => 'payouts', 'perm' => 'manage_transactions',
        'label' => 'Payout & Earning', 'icon' => 'fa-money-bill-transfer', 'grad' => 'from-lime-400 to-emerald-600',
        'badge' => $WB_BADGE['payout'], 'badge_tone' => 'rose',
        'children' => array_merge(
            wb_role_children('payouts.php'),
            [
                ['label' => 'Bank Requests',   'icon' => 'fa-building-columns', 'tone' => 'text-rose-300',  'url' => 'withdrawals.php#bank_requests', 'match' => ['file' => 'withdrawals.php'], 'acl' => 'payouts.view', 'badge' => $WB_BADGE['payout']],
                ['label' => 'Earning Reports', 'icon' => 'fa-chart-pie',        'tone' => 'text-slate-300', 'url' => 'reports.php',                   'match' => ['file' => 'reports.php'],      'acl' => 'reports.view'],
            ]
        ),
    ],
    [
        'type' => 'group', 'key' => 'commission', 'perm' => 'manage_settings',
        'label' => 'Commission Management', 'icon' => 'fa-percent', 'grad' => 'from-pink-400 to-rose-600',
        'children' => wb_role_children('commissions.php'),
    ],
    [
        'type' => 'group', 'key' => 'helpdesk', 'perm' => 'manage_tickets',
        'label' => 'Helpdesk Management', 'icon' => 'fa-headset', 'grad' => 'from-teal-400 to-cyan-600',
        'badge' => $WB_BADGE['ticket'], 'badge_tone' => 'teal',
        'children' => wb_role_children('tickets.php'),
    ],
    [
        /* Enquiries sent from the public contact form (contact_messages) */
        'type' => 'link', 'key' => 'messages', 'perm' => 'manage_users', 'acl' => 'tickets.view',
        'label' => 'Messages', 'icon' => 'fa-envelope', 'grad' => 'from-amber-400 to-orange-500',
        'url' => 'messages.php', 'match' => ['file' => 'messages.php'],
        'badge' => $WB_BADGE['message'], 'badge_tone' => 'amber',
    ],

    ['type' => 'heading', 'label' => 'More'],

    [
        'type' => 'link', 'key' => 'webreq', 'perm' => 'manage_services', 'acl' => 'website.requests',
        'label' => 'Website Requests', 'icon' => 'fa-globe', 'grad' => 'from-blue-400 to-indigo-600',
        'url' => 'website_requests.php', 'match' => ['file' => 'website_requests.php'],
    ],
    [
        'type' => 'link', 'key' => 'refer', 'perm' => 'manage_settings', 'acl' => 'refer.view',
        'label' => 'Refer & Earn', 'icon' => 'fa-gift', 'grad' => 'from-fuchsia-400 to-purple-600',
        'url' => 'refer.php', 'match' => ['file' => 'refer.php'],
    ],
];


/* ---------------------------------------------------------------------------
   5b. PERMISSION FILTER
   A staff account only ever sees the modules its role was given. The owner
   account (no role attached) and any role holding 'all' see everything.
   ------------------------------------------------------------------------ */
if (!function_exists('wb_menu_can')) {
    /**
     * May this account open this line?
     *
     * Two systems are in play. The old one has six coarse permissions
     * (manage_users, manage_services…); the new one has a key per action and a
     * key per column. A role saved before the new system existed is expanded
     * into every key of the modules it already held, so nothing is taken away
     * from anybody by reading the new keys here — a role that was given
     * "manage_services" still opens all eleven categories.
     *
     * What it does add is the ability to give LESS: a role that ticked only
     * "Open the Shopping page" now sees exactly that line and no other.
     *
     * 'acl' may be one key or a list, and a list means any one of them will do
     * — that is how a category line accepts either the whole-catalogue key or
     * its own.
     */
    function wb_menu_can($item) {
        if (function_exists('acl_can') && !empty($item['acl'])) { return acl_can($item['acl']); }
        return !function_exists('rbac_can') || rbac_can($item['perm'] ?? '');
    }
}

if (!function_exists('wb_filter_menu')) {
    /**
     * Drop what this account cannot open, then drop the folders and headings
     * that are left with nothing inside them. An employee should not be looking
     * at an empty "Wallet Management" wondering what is supposed to be in it.
     */
    function wb_filter_menu($menu) {
        if (function_exists('acl_is_super') && acl_is_super()) { return $menu; }
        if (function_exists('rbac_is_super') && rbac_is_super()) { return $menu; }

        $kept = [];
        foreach ($menu as $item) {
            $type = $item['type'] ?? 'link';
            if ($type === 'heading') { $kept[] = $item; continue; }

            if ($type === 'group') {
                /* the group's own permission still guards it, but a group is
                   only worth showing if something inside survived */
                if (!wb_menu_can(['perm' => $item['perm'] ?? '']) && empty($item['acl'])) { continue; }
                $kids = [];
                foreach ($item['children'] ?? [] as $c) {
                    /* a child with no key of its own inherits the group's */
                    if (empty($c['acl']) && empty($c['perm'])) { $c['perm'] = $item['perm'] ?? ''; }
                    if (wb_menu_can($c)) { $kids[] = $c; }
                }
                if (empty($kids)) { continue; }
                $item['children'] = $kids;
                $kept[] = $item;
                continue;
            }

            if (wb_menu_can($item)) { $kept[] = $item; }
        }

        /* a heading with no items after it is just a word floating in the dark */
        $out = [];
        for ($i = 0; $i < count($kept); $i++) {
            if (($kept[$i]['type'] ?? '') === 'heading') {
                $has = false;
                for ($j = $i + 1; $j < count($kept); $j++) {
                    if (($kept[$j]['type'] ?? '') === 'heading') { break; }
                    $has = true; break;
                }
                if (!$has) { continue; }
            }
            $out[] = $kept[$i];
        }
        return $out;
    }
}

$WB_MENU = wb_filter_menu($WB_MENU);

/* ---------------------------------------------------------------------------
   6. ACTIVE-STATE RESOLUTION
   ------------------------------------------------------------------------ */
if (!function_exists('wb_is_active')) {
    function wb_is_active($match, $file, $role, $cat) {
        if (empty($match['file']) || $match['file'] !== $file) return false;
        if (array_key_exists('role', $match) && $match['role'] !== $role) return false;
        if (array_key_exists('cat',  $match) && (int)$match['cat'] !== (int)$cat) return false;
        return true;
    }
}

$WB_OPEN_KEY = '';
foreach ($WB_MENU as $mi) {
    if (($mi['type'] ?? '') !== 'group') continue;
    foreach ($mi['children'] as $ch) {
        if (!empty($ch['match']['file']) && $ch['match']['file'] === $WB_FILE) { $WB_OPEN_KEY = $mi['key']; break 2; }
    }
}

/* Small helper for the badge pill colours */
if (!function_exists('wb_badge_class')) {
    function wb_badge_class($tone) {
        $m = [
            'amber'  => 'bg-amber-500/20 text-amber-300 ring-amber-400/30',
            'rose'   => 'bg-rose-500/20 text-rose-300 ring-rose-400/30',
            'sky'    => 'bg-sky-500/20 text-sky-300 ring-sky-400/30',
            'teal'   => 'bg-teal-500/20 text-teal-300 ring-teal-400/30',
            'orange' => 'bg-orange-500/20 text-orange-300 ring-orange-400/30',
        ];
        return $m[$tone] ?? 'bg-emerald-500/20 text-emerald-300 ring-emerald-400/30';
    }
}

/* ---------------------------------------------------------------------------
   7. BRAND (logo / name) — reuse the page's values, otherwise resolve them
   ------------------------------------------------------------------------ */
if (!isset($company_logo) || $company_logo === '') {
    $__raw = function_exists('getSetting') ? getSetting('company_logo', 'assets/images/logo.png') : 'assets/images/logo.png';
    $company_logo = (strpos($__raw, 'http') === 0) ? $__raw : '../' . ltrim($__raw, '/');
}
if (!isset($company_name) || $company_name === '') {
    $company_name = function_exists('getSetting') ? getSetting('company_name', 'WBMCZ') : 'WBMCZ';
}
$WB_ME = isset($user) && is_array($user) ? $user : null;
?>
<style>
/* ==========  WBMCZ PREMIUM SIDEBAR  ========== */
.wb-nav{scrollbar-width:none;-ms-overflow-style:none;}
.wb-nav::-webkit-scrollbar{width:0;height:0;display:none;}

.wb-item{position:relative;display:flex;align-items:center;width:100%;gap:.7rem;
  padding:.6rem .65rem;border-radius:.85rem;color:#cbd5e1;font-weight:600;font-size:.8125rem;
  line-height:1.15;text-align:left;transition:background .18s ease,color .18s ease,transform .18s ease;}
.wb-item:hover{background:rgba(255,255,255,.06);color:#fff;transform:translateX(2px);}
.wb-item.is-active{background:linear-gradient(90deg,rgba(16,185,129,.22),rgba(16,185,129,.04));color:#fff;}
.wb-item.is-active::before{content:"";position:absolute;left:-.75rem;top:50%;transform:translateY(-50%);
  width:3px;height:60%;border-radius:0 4px 4px 0;background:linear-gradient(180deg,#34d399,#0d9488);
  box-shadow:0 0 12px 1px rgba(52,211,153,.75);}

.wb-ico{flex:0 0 auto;display:grid;place-items:center;width:2rem;height:2rem;border-radius:.6rem;
  background:rgba(255,255,255,.06);font-size:.8rem;color:#e2e8f0;
  transition:background .2s ease,color .2s ease,box-shadow .2s ease;}
.wb-item:hover .wb-ico{background:rgba(255,255,255,.12);}
.wb-item.is-active .wb-ico,.wb-grp.is-open .wb-ico{color:#fff;box-shadow:0 4px 14px -4px rgba(0,0,0,.6);}

.wb-label{flex:1 1 auto;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.wb-chev{flex:0 0 auto;font-size:.6rem;color:#94a3b8;transition:transform .25s ease;}
.wb-grp.is-open .wb-chev{transform:rotate(90deg);color:#34d399;}

/* small dot shown instead of the count pill when the rail is collapsed */
.wb-dot{position:absolute;top:.35rem;right:.55rem;width:.5rem;height:.5rem;border-radius:50%;
  background:#fbbf24;box-shadow:0 0 0 2px rgba(15,23,42,.9),0 0 10px 1px rgba(251,191,36,.8);}

.wb-pill{flex:0 0 auto;min-width:1.15rem;height:1.15rem;padding:0 .35rem;border-radius:999px;
  display:inline-grid;place-items:center;font-size:.625rem;font-weight:800;
  --tw-ring-offset-width:0px;box-shadow:inset 0 0 0 1px currentColor;}

/* --- sub menu --- */
.wb-sub{position:relative;margin:.15rem 0 .3rem 1.05rem;padding-left:.85rem;
  border-left:1px solid rgba(148,163,184,.22);}
.wb-sub-item{position:relative;display:flex;align-items:center;gap:.5rem;
  padding:.4rem .55rem;border-radius:.6rem;font-size:.75rem;font-weight:500;color:#94a3b8;
  transition:background .16s ease,color .16s ease,transform .16s ease;}
.wb-sub-item::before{content:"";position:absolute;left:-.9rem;top:50%;transform:translateY(-50%);
  width:6px;height:6px;border-radius:50%;background:#475569;transition:background .16s ease,box-shadow .16s ease;}
.wb-sub-item:hover{background:rgba(255,255,255,.05);color:#f1f5f9;transform:translateX(2px);}
.wb-sub-item:hover::before{background:#34d399;}
.wb-sub-item.is-active{background:rgba(16,185,129,.14);color:#6ee7b7;font-weight:700;}
.wb-sub-item.is-active::before{background:#34d399;box-shadow:0 0 8px 2px rgba(52,211,153,.6);}
.wb-sub-ico{width:1rem;text-align:center;font-size:.7rem;flex:0 0 auto;}

.wb-head{padding:.9rem .75rem .35rem;font-size:.625rem;font-weight:800;letter-spacing:.14em;
  text-transform:uppercase;color:#64748b;}

/* --- collapsed (icon rail) mode --- */
.wb-collapsed .wb-item{justify-content:center;padding:.6rem 0;gap:0;}
.wb-collapsed .wb-head{text-align:center;padding:.7rem 0 .3rem;font-size:.55rem;letter-spacing:.06em;}
.wb-collapsed .wb-item.is-active::before{left:-.5rem;}

/* flyout tooltip when collapsed */
.wb-tip{position:absolute;left:calc(100% + .6rem);top:50%;transform:translateY(-50%) scale(.96);
  background:#0f172a;color:#f8fafc;border:1px solid rgba(148,163,184,.25);
  padding:.35rem .6rem;border-radius:.5rem;font-size:.7rem;font-weight:600;white-space:nowrap;
  box-shadow:0 10px 30px -8px rgba(0,0,0,.8);opacity:0;pointer-events:none;z-index:80;
  transition:opacity .15s ease,transform .15s ease;}
.wb-collapsed .wb-item:hover .wb-tip{opacity:1;transform:translateY(-50%) scale(1);}

/* search box */
.wb-search{display:flex;align-items:center;gap:.5rem;margin:0 .1rem .5rem;padding:.5rem .7rem;
  border-radius:.75rem;background:rgba(255,255,255,.05);border:1px solid rgba(148,163,184,.16);
  transition:border-color .2s ease,background .2s ease;}
.wb-search:focus-within{border-color:rgba(52,211,153,.55);background:rgba(255,255,255,.08);}
.wb-search input{flex:1;background:transparent;border:0;outline:0;color:#e2e8f0;font-size:.75rem;}
.wb-search input::placeholder{color:#64748b;}

.wb-logout{display:flex;align-items:center;gap:.7rem;padding:.65rem;border-radius:.85rem;
  color:#fda4af;font-weight:700;font-size:.8125rem;background:rgba(244,63,94,.08);
  border:1px solid rgba(244,63,94,.18);transition:all .2s ease;}
.wb-logout:hover{background:rgba(244,63,94,.18);color:#fff;box-shadow:0 8px 24px -10px rgba(244,63,94,.9);}
.wb-collapsed .wb-logout{justify-content:center;}

@media (prefers-reduced-motion:reduce){.wb-item,.wb-sub-item,.wb-chev,.wb-tip{transition:none!important;}}

/* ---------- shell ---------- */
.wb-aside{position:relative;flex-shrink:0;display:flex;flex-direction:column;z-index:50;
  color:#e2e8f0;overflow:visible;
  background:
    radial-gradient(120% 60% at 0% 0%,rgba(16,185,129,.16),transparent 60%),
    radial-gradient(90% 50% at 100% 100%,rgba(56,189,248,.10),transparent 60%),
    linear-gradient(180deg,#0f172a 0%,#0b1220 55%,#080d17 100%);
  box-shadow:1px 0 0 rgba(148,163,184,.10),18px 0 45px -30px rgba(0,0,0,.9);
  transition:width .28s cubic-bezier(.4,0,.2,1);}

/* The hamburger now puts the whole sidebar away instead of leaving an icon
   rail behind, so the working area gets the full width of the screen.
   overflow:hidden keeps the menu from spilling out while it slides shut, and
   the shadow is dropped so no dark sliver is left at the edge. */
.wb-hidden-shell{overflow:hidden !important;box-shadow:none !important;border:0 !important;min-width:0;}
.wb-hidden-shell > *{opacity:0;transition:opacity .12s linear;pointer-events:none;}

.wb-brand{position:relative;display:flex;align-items:center;gap:.7rem;height:5rem;
  padding:0 .9rem;border-bottom:1px solid rgba(148,163,184,.12);}
.wb-brand::after{content:"";position:absolute;left:1rem;right:1rem;bottom:-1px;height:1px;
  background:linear-gradient(90deg,transparent,rgba(52,211,153,.55),transparent);}
.wb-collapsed-shell .wb-brand{justify-content:center;padding:0;}
.wb-logo{width:2.6rem;height:2.6rem;flex:0 0 auto;border-radius:.8rem;padding:.3rem;background:#fff;
  object-fit:contain;box-shadow:0 8px 22px -10px rgba(52,211,153,.9);}
.wb-brand-name{font-size:1rem;font-weight:800;letter-spacing:.06em;line-height:1.1;
  background:linear-gradient(90deg,#6ee7b7,#5eead4 55%,#7dd3fc);
  -webkit-background-clip:text;background-clip:text;color:transparent;white-space:nowrap;}
.wb-brand-sub{font-size:.6rem;font-weight:700;letter-spacing:.18em;color:#64748b;text-transform:uppercase;}
</style>

<aside class="wb-aside"
       style="width:16rem"
       :style="sidebarOpen ? 'width:16rem' : 'width:0'"
       :class="sidebarOpen ? '' : 'wb-hidden-shell'">

    <!-- ============ BRAND ============ -->
    <div class="wb-brand">
        <a href="../index.php" class="flex items-center gap-3 min-w-0" title="<?php echo htmlspecialchars($company_name); ?>">
            <img src="<?php echo htmlspecialchars($company_logo); ?>" alt="Logo" class="wb-logo">
            <span class="min-w-0" x-show="sidebarOpen" x-cloak>
                <span class="wb-brand-name block truncate"><?php echo htmlspecialchars(strtoupper($company_name)); ?></span>
                <span class="wb-brand-sub">Admin Console</span>
            </span>
        </a>
    </div>

<nav class="wb-nav flex-1 px-3 py-4 overflow-y-auto overflow-x-hidden"
     :class="sidebarOpen ? '' : 'wb-collapsed'"
     x-data="wbSidebar('<?php echo htmlspecialchars($WB_OPEN_KEY, ENT_QUOTES); ?>')">

    <!-- quick filter (full width mode only) -->
    <div class="wb-search" x-show="sidebarOpen" x-cloak>
        <i class="fas fa-magnifying-glass text-[.7rem] text-slate-500"></i>
        <input type="text" x-model="q" placeholder="Search menu…" aria-label="Search menu">
        <button type="button" x-show="q" @click="q=''" class="text-slate-500 hover:text-rose-300" aria-label="Clear">
            <i class="fas fa-xmark text-[.7rem]"></i>
        </button>
    </div>

<?php foreach ($WB_MENU as $item):
    $type = $item['type'] ?? 'link';

    /* ---------- section heading ---------- */
    if ($type === 'heading'): ?>
        <div class="wb-head" x-show="!q"><?php echo htmlspecialchars($item['label']); ?></div>
    <?php continue; endif;

    /* ---------- single link ---------- */
    if ($type === 'link'):
        $active = wb_is_active($item['match'], $WB_FILE, $WB_ROLE, $WB_CAT); ?>
        <a href="<?php echo $item['url']; ?>"
           class="wb-item mb-0.5 <?php echo $active ? 'is-active' : ''; ?>"
           x-show="match('<?php echo htmlspecialchars(addslashes(strtolower($item['label'])), ENT_QUOTES); ?>')" title="<?php echo htmlspecialchars($item['label']); ?>"
           <?php if ($active) echo 'aria-current="page"'; ?>>
            <span class="wb-ico bg-gradient-to-br <?php echo $active ? $item['grad'] : ''; ?>">
                <i class="fas <?php echo $item['icon']; ?>"></i>
            </span>
            <span class="wb-label" x-show="sidebarOpen"><?php echo htmlspecialchars($item['label']); ?></span>
            <?php $lb = (int)($item['badge'] ?? 0); if ($lb > 0): ?>
                <span class="wb-pill <?php echo wb_badge_class($item['badge_tone'] ?? ''); ?>" x-show="sidebarOpen"><?php echo $lb > 99 ? '99+' : $lb; ?></span>
                <span class="wb-dot" x-show="!sidebarOpen"></span>
            <?php endif; ?>
            <span class="wb-tip"><?php echo htmlspecialchars($item['label']); ?></span>
        </a>
    <?php continue; endif;

    /* ---------- expandable group ---------- */
    $key       = $item['key'];
    $kids      = $item['children'];
    $grpActive = false;
    foreach ($kids as $ch) { if (wb_is_active($ch['match'], $WB_FILE, $WB_ROLE, $WB_CAT)) { $grpActive = true; break; } }
    $searchBlob = strtolower($item['label'] . ' ' . implode(' ', array_column($kids, 'label')));
    $badge      = (int)($item['badge'] ?? 0);
    ?>
    <div class="wb-grp mb-0.5" :class="isOpen('<?php echo $key; ?>') ? 'is-open' : ''"
         x-show="match('<?php echo htmlspecialchars(addslashes($searchBlob), ENT_QUOTES); ?>')">

        <button type="button" @click="toggle('<?php echo $key; ?>')"
                class="wb-item <?php echo $grpActive ? 'is-active' : ''; ?>"
                :aria-expanded="isOpen('<?php echo $key; ?>') ? 'true' : 'false'"
                title="<?php echo htmlspecialchars($item['label']); ?>">
            <span class="wb-ico bg-gradient-to-br <?php echo ($grpActive ? $item['grad'] : ''); ?>"
                  :class="isOpen('<?php echo $key; ?>') ? 'bg-gradient-to-br <?php echo $item['grad']; ?>' : ''">
                <i class="fas <?php echo $item['icon']; ?>"></i>
            </span>
            <span class="wb-label" x-show="sidebarOpen"><?php echo htmlspecialchars($item['label']); ?></span>
            <?php if ($badge > 0): ?>
                <span class="wb-pill <?php echo wb_badge_class($item['badge_tone'] ?? ''); ?>" x-show="sidebarOpen"><?php echo $badge > 99 ? '99+' : $badge; ?></span>
            <?php endif; ?>
            <i class="fas fa-chevron-right wb-chev" x-show="sidebarOpen"></i>
            <span class="wb-tip"><?php echo htmlspecialchars($item['label']); ?></span>
        </button>

        <div class="wb-sub" x-cloak
             x-show="sidebarOpen && (isOpen('<?php echo $key; ?>') || q)"
             x-transition:enter="transition ease-out duration-200"
             x-transition:enter-start="opacity-0 -translate-y-1"
             x-transition:enter-end="opacity-100 translate-y-0"
             x-transition:leave="transition ease-in duration-120"
             x-transition:leave-start="opacity-100"
             x-transition:leave-end="opacity-0">
            <?php foreach ($kids as $ch):
                $ca = wb_is_active($ch['match'], $WB_FILE, $WB_ROLE, $WB_CAT);
                $cb = (int)($ch['badge'] ?? 0); ?>
                <a href="<?php echo $ch['url']; ?>"
                   class="wb-sub-item <?php echo $ca ? 'is-active' : ''; ?>"
                   x-show="match('<?php echo htmlspecialchars(addslashes(strtolower($ch['label'])), ENT_QUOTES); ?>')" title="<?php echo htmlspecialchars($ch['label']); ?>"
                   <?php if ($ca) echo 'aria-current="page"'; ?>>
                    <i class="fas <?php echo $ch['icon']; ?> wb-sub-ico <?php echo $ca ? 'text-emerald-300' : $ch['tone']; ?>"></i>
                    <span class="truncate flex-1"><?php echo htmlspecialchars($ch['label']); ?></span>
                    <?php if ($cb > 0): ?>
                        <span class="wb-pill bg-rose-500/20 text-rose-300"><?php echo $cb > 99 ? '99+' : $cb; ?></span>
                    <?php endif; ?>
                </a>
            <?php endforeach; ?>
        </div>
    </div>
<?php endforeach; ?>

    <div class="pt-4 mt-3 border-t border-white/10">
        <a href="../logout.php" onclick="return confirm('Are you sure you want to logout?');" class="wb-logout">
            <span class="wb-ico bg-rose-500/15"><i class="fas fa-arrow-right-from-bracket"></i></span>
            <span class="wb-label" x-show="sidebarOpen">Logout</span>
        </a>
    </div>

    <div class="mt-4 px-1 pb-2 text-[.6rem] text-slate-600 text-center" x-show="sidebarOpen" x-cloak>
        <?php echo htmlspecialchars($company_name); ?> Admin &middot; v2.0
    </div>
</nav>

</aside>

<script>
/* ---------------------------------------------------------------------------
   Accordion behaviour: only ONE group can be expanded at a time.  Opening a
   group automatically collapses whichever group was open before it.
   `open` holds a single key ('' = everything collapsed).
   --------------------------------------------------------------------------- */
function wbSidebar(activeKey) {
    return {
        q: '',
        open: activeKey || '',
        init() {
            /* The group that matches the current page always wins.  Only fall
               back to the remembered group when the page is not inside one. */
            if (!this.open) {
                try {
                    const saved = localStorage.getItem('wb_sb_open_one');
                    if (saved) this.open = saved;
                } catch (e) {}
            }
        },
        isOpen(k) { return this.open === k; },
        toggle(k) {
            this.open = (this.open === k) ? '' : k;
            try { localStorage.setItem('wb_sb_open_one', this.open); } catch (e) {}
        },
        match(text) { return !this.q || text.indexOf(this.q.toLowerCase()) > -1; }
    };
}
</script>
<style>[x-cloak]{display:none!important;}</style>

<?php
/* ============================================================================
   THE NOTIFICATION BELL

   Notifications used to be a line half way down User Management, which is a
   strange place to look for something that has just happened. It is a bell in
   the top right of every page now, with the unread count on it.

   Every admin page includes this file, but each one writes its own <header>,
   so there is no single place in PHP to drop the bell into. It is rendered
   here, hidden, and moved into the page's header next to the profile picture
   the moment the page is ready. If a page has no header at all it pins itself
   to the top right corner instead, so the bell is never simply missing.
   ============================================================================ */
$WB_NOTIFS = []; $WB_NOTIF_N = 0;
if (isset($db) && is_object($db) && !empty($_SESSION['user_id'])) {
    $__nu = (int)$_SESSION['user_id'];
    try {
        /* `select *` on purpose: the `link` column is added by
           includes/notify.php's schema check, and a database where that has not
           run yet should still get a working bell rather than a silent one. */
        $WB_NOTIFS = $db->select(
            "SELECT * FROM notifications
              WHERE user_id = ? AND COALESCE(is_read,0) = 0
              ORDER BY id DESC LIMIT 6", [$__nu]);
        $__nr = $db->select("SELECT COUNT(*) n FROM notifications WHERE user_id = ? AND COALESCE(is_read,0) = 0", [$__nu]);
        $WB_NOTIF_N = !empty($__nr) ? (int)$__nr[0]['n'] : 0;
    } catch (\Throwable $e) { $WB_NOTIFS = []; $WB_NOTIF_N = 0; }
}
?>
<div id="wbBell" x-data="{ nOpen: false }" class="relative" style="display:none">
    <button type="button" @click="nOpen = !nOpen" @click.away="nOpen = false"
            class="relative w-10 h-10 rounded-xl text-slate-500 hover:text-emerald-600 hover:bg-slate-100 flex items-center justify-center transition-colors"
            title="Notifications">
        <i class="fas fa-bell text-lg"></i>
        <?php if ($WB_NOTIF_N > 0): ?>
        <span class="absolute -top-0.5 -right-0.5 min-w-[18px] h-[18px] px-1 rounded-full bg-rose-500 text-white text-[10px] font-extrabold flex items-center justify-center ring-2 ring-white">
            <?php echo $WB_NOTIF_N > 99 ? '99+' : $WB_NOTIF_N; ?>
        </span>
        <?php endif; ?>
    </button>

    <div x-show="nOpen" x-transition style="display:none"
         class="absolute right-0 mt-2 w-80 max-w-[calc(100vw-2rem)] bg-white rounded-xl shadow-2xl border border-slate-200 z-[60] overflow-hidden">
        <div class="px-4 py-3 border-b border-slate-100 flex items-center justify-between">
            <p class="text-sm font-bold text-slate-800">Notifications</p>
            <?php if ($WB_NOTIF_N > 0): ?>
                <span class="text-[11px] font-bold text-rose-600"><?php echo $WB_NOTIF_N; ?> unread</span>
            <?php endif; ?>
        </div>

        <?php if (empty($WB_NOTIFS)): ?>
            <div class="px-4 py-8 text-center">
                <i class="fas fa-check text-2xl text-slate-200 mb-2"></i>
                <p class="text-[12.5px] text-slate-400">Nothing unread.</p>
            </div>
        <?php else: ?>
            <div class="max-h-80 overflow-y-auto divide-y divide-slate-100">
                <?php foreach ($WB_NOTIFS as $n):
                    $href = trim((string)($n['link'] ?? ''));
                    if ($href === '') { $href = 'notifications.php'; } ?>
                    <a href="<?php echo htmlspecialchars($href); ?>" class="block px-4 py-3 hover:bg-slate-50">
                        <p class="text-[12.5px] font-bold text-slate-800"><?php echo htmlspecialchars((string)$n['title']); ?></p>
                        <?php if (trim((string)$n['message']) !== ''): ?>
                            <p class="text-[11.5px] text-slate-500 mt-0.5"><?php
                                $__m = trim(strip_tags((string)$n['message']));
                                echo htmlspecialchars(mb_strlen($__m) > 110 ? mb_substr($__m, 0, 110) . '…' : $__m); ?></p>
                        <?php endif; ?>
                        <p class="text-[10.5px] text-slate-400 mt-1"><?php echo date('d M Y, g:i a', strtotime((string)$n['created_at'])); ?></p>
                    </a>
                <?php endforeach; ?>
            </div>
        <?php endif; ?>

        <a href="notifications.php" class="block px-4 py-3 text-center text-[12px] font-bold text-emerald-600 hover:bg-emerald-50 border-t border-slate-100">
            Open all notifications
        </a>
    </div>
</div>
<script>
(function () {
    var bell = document.getElementById('wbBell');
    if (!bell) { return; }

    function mount() {
        var header = document.querySelector('header');
        if (!header) {
            /* no header on this page — pin it to the corner rather than lose it */
            bell.style.position = 'fixed';
            bell.style.top = '16px';
            bell.style.right = '88px';
            bell.style.zIndex = '60';
            document.body.appendChild(bell);
            bell.style.display = '';
            return;
        }

        /* the profile picture is the one thing every header has in the same
           place, so the bell goes just to the left of whatever holds it */
        var avatar = header.querySelector('.rounded-full');
        if (avatar) {
            var node = avatar;
            while (node.parentElement && node.parentElement !== header) { node = node.parentElement; }
            if (node.children && node.children.length > 0 && node !== avatar) {
                node.insertBefore(bell, node.firstElementChild);
            } else {
                header.insertBefore(bell, node);
                bell.style.marginRight = '14px';
            }
        } else {
            header.appendChild(bell);
            bell.style.marginLeft = 'auto';
        }
        bell.style.display = '';
    }

    if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', mount); }
    else { mount(); }
})();
</script>

<!-- ═══════════════════════════════════════════════════════════════════════
     FROZEN HEADER ROW — every management table on the panel

     The tables here are long and wide. Scroll down far enough and the column
     names are gone, so a cell full of dates or money has nothing to say what
     it is. This pins the header row to the top of its own box, the way a
     frozen pane works in a spreadsheet.

     It is deliberately shy. A table is only touched when it already sits in a
     wrapper that scrolls sideways, has a real header and more than a handful
     of rows, and is not inside a dialog. Anything else is left exactly as it
     was, so no page can be knocked out of shape by this.

     Activity & Task is skipped by name — that board was marked as one to
     leave alone. Any other page can opt out with data-wbfz="off" on the table.
     ═══════════════════════════════════════════════════════════════════════ -->
<style>
    .wbfz { overflow: auto; max-height: calc(100vh - 210px); }
    .wbfz > table > thead > tr > th,
    .wbfz > table > thead > tr > td { position: sticky; top: 0; z-index: 6; }
    .wbfz::-webkit-scrollbar { width: 11px; height: 11px; }
    .wbfz::-webkit-scrollbar-thumb { background: #94a3b8; border-radius: 99px; }
    .wbfz::-webkit-scrollbar-thumb:hover { background: #64748b; }
    .wbfz::-webkit-scrollbar-track { background: #f1f5f9; }
</style>
<script>
(function () {
    if (/activity_task/.test(location.pathname)) { return; }

    function freeze() {
        var tables = document.querySelectorAll('table');
        for (var n = 0; n < tables.length; n++) {
            var t = tables[n];
            if (!t.tHead || !t.tHead.rows.length) { continue; }
            if (t.getAttribute('data-wbfz') === 'off') { continue; }
            if (t.closest('.fixed')) { continue; }          /* a dialog */
            var box = t.parentElement;
            if (!box || box.classList.contains('wbfz')) { continue; }
            if (!t.tBodies[0] || t.tBodies[0].rows.length < 5) { continue; }
            var ov = getComputedStyle(box).overflowX;
            if (ov !== 'auto' && ov !== 'scroll') { continue; }
            if (box.style.maxHeight) { continue; }

            /* A sticky cell paints nothing of its own, so a header that took its
               colour from the row behind it would go see-through. Only the ones
               that really are transparent get a background of their own. */
            var cells = t.tHead.rows[0].cells;
            for (var c = 0; c < cells.length; c++) {
                var bg = getComputedStyle(cells[c]).backgroundColor;
                if (bg === 'rgba(0, 0, 0, 0)' || bg === 'transparent') { cells[c].style.background = '#f8fafc'; }
            }
            box.classList.add('wbfz');
        }
    }

    if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', freeze); }
    else { freeze(); }
    /* tables that arrive with an Alpine render get a second pass */
    setTimeout(freeze, 600);
})();
</script>
