Files
alrahma_sunday_school/app/Views/partials/navbar_dynamic.php
T
2026-02-10 22:11:06 -05:00

442 lines
18 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/** @var array $rolesFromSession */
use App\Services\NavbarService;
$rolesFromSession = [];
$rawRole = session()->get('role'); // your app uses a single role string
if (is_array($rawRole)) $rolesFromSession = $rawRole;
else $rolesFromSession = [$rawRole ?? 'guest'];
$service = new NavbarService();
$menu = $service->getMenuForRoles($rolesFromSession);
function nav_url(string $path): string
{
$path = trim($path);
if ($path === '' || preg_match('/[<>]/', $path)) {
return '#';
}
// Treat relative paths as site_url, absolute keep as is.
if (preg_match('#^https?://#i', $path)) return $path;
return site_url($path);
}
?>
<style>
/* Mobile-friendly sidebar behavior and backdrop */
@media (max-width: 991.98px) {
#navbarManagement.mgmt-sidebar { width: min(88vw, 360px); transform: translateX(-100%); will-change: transform; }
html.mgmt-sidebar-open #navbarManagement.mgmt-sidebar { transform: translateX(0); }
#mgmtSidebarHotzone { display: none !important; }
.mgmt-sidebar-backdrop { position: fixed; inset: 0; background: rgba(0,0,0,0.35); z-index: 1043; display: none; }
html.mgmt-sidebar-open .mgmt-sidebar-backdrop { display: block; }
body.mgmt-no-scroll { overflow: hidden; touch-action: none; }
#navbarManagement.mgmt-sidebar .dropdown-menu { display: none !important; }
}
@media (min-width: 992px) { .mgmt-sidebar-backdrop { display: none !important; } }
#navbarManagement .submenu .nav-link, #navbarManagement .mobile-submenu .nav-link { padding: .4rem .5rem; }
#navbarManagement .submenu .dropdown-header, #navbarManagement .mobile-submenu .dropdown-header { padding: .35rem .5rem; }
#navbarManagement .submenu-toggle, #navbarManagement .mobile-toggle { width: 100%; text-align: left; }
/* Arrow toggle styling follow page style */
.mgmt-sidebar-toggle {
position: fixed;
left: 10px;
top: 72px; /* JS nudges based on header height */
width: 34px;
height: 34px;
display: inline-flex;
align-items: center;
justify-content: center;
border: none;
border-radius: 9999px;
background-color: var(--mgmt-primary);
color: #fff;
box-shadow: 0 2px 6px rgba(0,0,0,0.25);
z-index: 1060; /* above sidebar */
cursor: pointer;
transition: transform .2s ease, background-color .2s ease, opacity .2s ease;
}
.mgmt-sidebar-toggle:hover { background-color: var(--mgmt-primary-hover); }
html.mgmt-sidebar-open .mgmt-sidebar-toggle i { transform: rotate(180deg); }
/* Rotate submenu chevron when expanded */
#navbarManagement .submenu-toggle[aria-expanded="true"] i { transform: rotate(180deg); }
</style>
<button id="mgmtSidebarToggle" class="mgmt-sidebar-toggle" aria-label="Toggle sidebar" aria-expanded="false">
<i class="bi bi-chevron-right"></i>
</button>
<nav id="navbarManagement" class="mgmt-sidebar">
<div class="mgmt-sidebar-content">
<ul class="nav flex-column" id="mgmtMenuList">
<?php foreach ($menu as $item): ?>
<?php $hasChildren = !empty($item['children']); ?>
<?php if ($hasChildren): ?>
<li class="nav-item dropdown dropend d-none">
<a class="nav-link d-flex align-items-center justify-content-between dropdown-toggle" href="#" id="dd_<?= esc($item['id']) ?>" role="button" data-bs-toggle="dropdown" data-bs-auto-close="outside" data-bs-display="static" aria-expanded="false">
<span class="nav-label-chip flex-shrink-0"><?= esc($item['label']) ?></span>
<i class="bi bi-chevron-right ms-2"></i>
</a>
<div class="dropdown-menu shadow sidebar-dropdown-menu" aria-labelledby="dd_<?= esc($item['id']) ?>">
<?php foreach ($item['children'] as $child): ?>
<?php if ($child['url']): ?>
<a class="dropdown-item" href="<?= esc(nav_url($child['url']), 'attr') ?>" <?= $child['target'] ? 'target="' . esc($child['target']) . '"' : '' ?>>
<?= esc($child['label']) ?>
</a>
<?php else: ?>
<h6 class="dropdown-header mb-1"><?= esc($child['label']) ?></h6>
<?php endif; ?>
<?php endforeach; ?>
</div>
</li>
<?php $cid = 'mnav_'.esc($item['id']); ?>
<li class="nav-item d-block">
<button class="nav-link d-flex align-items-center justify-content-between mobile-toggle submenu-toggle" type="button" data-bs-toggle="collapse" data-bs-target="#<?= $cid ?>" aria-controls="<?= $cid ?>" aria-expanded="false">
<span class="nav-label-chip flex-shrink-0"><?= esc($item['label']) ?></span>
<i class="bi bi-chevron-down ms-2"></i>
</button>
<div id="<?= $cid ?>" class="collapse submenu" data-bs-parent="#mgmtMenuList">
<ul class="nav flex-column ms-2">
<?php foreach ($item['children'] as $child): ?>
<?php if ($child['url']): ?>
<li class="nav-item"><a class="nav-link" href="<?= esc(nav_url($child['url']), 'attr') ?>" <?= $child['target'] ? 'target="' . esc($child['target']) . '"' : '' ?>><?= esc($child['label']) ?></a></li>
<?php else: ?>
<li class="nav-item"><span class="dropdown-header mb-1 text-muted small"><?= esc($child['label']) ?></span></li>
<?php endif; ?>
<?php endforeach; ?>
</ul>
</div>
</li>
<?php else: ?>
<li class="nav-item">
<?php if (!empty($item['url'])): ?>
<a class="nav-link d-flex align-items-center" href="<?= esc(nav_url($item['url']), 'attr') ?>" <?= $item['target'] ? 'target="' . esc($item['target']) . '"' : '' ?>>
<span class="nav-label-chip flex-shrink-0"><?= esc($item['label']) ?></span>
</a>
<?php else: ?>
<span class="nav-link disabled d-flex align-items-center">
<span class="nav-label-chip flex-shrink-0"><?= esc($item['label']) ?></span>
</span>
<?php endif; ?>
</li>
<?php endif; ?>
<?php endforeach; ?>
</ul>
<div class="d-flex align-items-center">
<?php
// Build role switcher options when user has multiple roles
$allRoles = array_map(fn($r)=> strtolower(trim((string)$r)), (array) (session()->get('roles') ?? []));
$currentRole = strtolower((string) ($rawRole ?? 'guest'));
$otherRoles = array_values(array_filter($allRoles, fn($r) => $r !== '' && $r !== $currentRole));
// Style settings (with sensible defaults)
$styleCfg = config('Style');
$sess = session();
$currentAccent = $sess->get('style_color') ?? ($styleCfg->defaultStyle ?? 'blue');
$currentMenu = $sess->get('menu_color') ?? ($styleCfg->defaultMenu ?? 'white');
?>
<div class="nav-item dropdown me-2">
<div class="dropdown-menu dropdown-menu-end quick-menu" aria-labelledby="dd_tools" style="max-height: 70vh; overflow-y: auto; overflow-x: hidden;">
<?php if (!empty($otherRoles)): ?>
<h6 class="dropdown-header">Switch Role</h6>
<?php foreach ($otherRoles as $r): ?>
<form method="post" action="<?= site_url('set-role') ?>">
<?= csrf_field() ?>
<input type="hidden" name="selected_role" value="<?= esc($r) ?>">
<button type="submit" class="dropdown-item">Switch to <?= esc(ucfirst(str_replace('_', ' ', $r))) ?></button>
</form>
<?php endforeach; ?>
<div class="dropdown-divider"></div>
<?php endif; ?>
<h6 class="dropdown-header">Style</h6>
<a class="dropdown-item d-flex justify-content-between align-items-center" data-bs-toggle="collapse" href="#ddAccentMgmtSidebar" role="button" aria-expanded="true" aria-controls="ddAccentMgmtSidebar">
Accent <i class="bi bi-chevron-down ms-2"></i>
</a>
<div class="collapse show" id="ddAccentMgmtSidebar">
<div class="px-2 pb-2">
<?php foreach (array_keys($styleCfg->stylePalettes ?? []) as $opt): ?>
<?php $isActive = ($opt === $currentAccent); ?>
<a class="dropdown-item <?= $isActive ? 'active' : '' ?>" data-style-link href="<?= site_url('ui/style?accent=' . urlencode($opt)) ?>" <?= $isActive ? 'aria-current="true"' : '' ?>>
<?= ucfirst(esc($opt)) ?><?= $isActive ? ' <i class=\"bi bi-check-lg ms-2\"></i>' : '' ?>
</a>
<?php endforeach; ?>
</div>
</div>
<div class="dropdown-divider"></div>
<div class="px-2 small text-muted">Menu</div>
<?php foreach (array_keys($styleCfg->menuPalettes ?? []) as $opt): ?>
<?php $isActive = ($opt === $currentMenu); ?>
<a class="dropdown-item <?= $isActive ? 'active' : '' ?>" data-style-link href="<?= site_url('ui/style?menu=' . urlencode($opt)) ?>" <?= $isActive ? 'aria-current="true"' : '' ?>>
<?= ucfirst(esc($opt)) ?><?= $isActive ? ' <i class=\"bi bi-check-lg ms-2\"></i>' : '' ?>
</a>
<?php endforeach; ?>
</div>
</div>
</div>
</div>
</nav>
<div id="mgmtSidebarHotzone" aria-hidden="true"></div>
<div id="mgmtSidebarBackdrop" class="mgmt-sidebar-backdrop" aria-hidden="true"></div>
<script>
(function() {
function applySidebarGeometry() {
const header = document.querySelector('header.navbar.sticky-top, header.navbar.fixed-top') || document.querySelector('header.navbar');
const nav = document.getElementById('navbarManagement');
const hot = document.getElementById('mgmtSidebarHotzone');
const toggle = document.getElementById('mgmtSidebarToggle');
if (!nav) return;
let top = 0;
if (header) {
top = Math.max(header.offsetHeight || 0, (header.getBoundingClientRect && header.getBoundingClientRect().height) || 0);
}
nav.style.top = top + 'px';
nav.style.height = 'calc(100vh - ' + top + 'px)';
// Keep sidebar above backdrop (1043) but below header (1045)
nav.style.zIndex = 1044;
if (hot) {
hot.style.top = top + 'px';
hot.style.height = 'calc(100vh - ' + top + 'px)';
hot.style.zIndex = 1019;
}
if (toggle) { try { toggle.style.top = (top + 12) + 'px'; } catch(_) {} }
}
window.addEventListener('load', applySidebarGeometry);
window.addEventListener('resize', applySidebarGeometry);
document.addEventListener('DOMContentLoaded', applySidebarGeometry);
})();
</script>
<script>
// Toggle sidebar open/close (button + backdrop) and lock body scroll on mobile
(function() {
var root = document.documentElement;
var btn = document.getElementById('mgmtSidebarToggle');
var backdrop = document.getElementById('mgmtSidebarBackdrop');
if (!btn) return;
function setExpanded(expanded) {
try { btn.setAttribute('aria-expanded', expanded ? 'true' : 'false'); } catch (_) {}
try { document.body.classList.toggle('mgmt-no-scroll', expanded && !window.matchMedia('(min-width: 992px)').matches); } catch (_) {}
}
function closeSidebar() {
root.classList.remove('mgmt-sidebar-open');
setExpanded(false);
}
btn.addEventListener('click', function(e) {
e.stopPropagation();
var willOpen = !root.classList.contains('mgmt-sidebar-open');
root.classList.toggle('mgmt-sidebar-open');
setExpanded(willOpen);
});
if (backdrop) backdrop.addEventListener('click', function() { closeSidebar(); });
document.addEventListener('keydown', function(e) { if (e.key === 'Escape') closeSidebar(); });
document.addEventListener('click', function(e) {
if (!root.classList.contains('mgmt-sidebar-open')) return;
if (e.target.closest('#navbarManagement') || e.target.closest('#mgmtSidebarToggle')) return;
closeSidebar();
});
// Desktop: hover the arrow to reveal the sidebar (large screens only)
// Use width-only gating to be robust across devices that misreport pointer/hover
if (window.matchMedia('(min-width: 992px)').matches) {
btn.addEventListener('mouseenter', function() {
root.classList.add('mgmt-sidebar-open');
setExpanded(true);
});
btn.addEventListener('mouseleave', function() {
setTimeout(function() {
var navHover = document.querySelector('#navbarManagement:hover');
var btnHover = document.querySelector('#mgmtSidebarToggle:hover');
if (!navHover && !btnHover) closeSidebar();
}, 600);
});
}
})();
</script>
<script>
// Ensure style links return to the current page
(function() {
document.addEventListener('click', function(e) {
var a = e.target.closest('a[data-style-link]');
if (!a) return;
try {
var u = new URL(a.getAttribute('href'), window.location.origin);
if (!u.searchParams.get('back')) {
u.searchParams.set('back', window.location.href);
}
e.preventDefault();
window.location.assign(u.toString());
} catch (_) {
/* ignore */ }
});
})();
</script>
<script>
// Hover-to-open behavior with a thin hotzone at the left edge (desktop only)
(function() {
if (!window.matchMedia('(hover: hover) and (pointer: fine) and (min-width: 992px)').matches) return;
var root = document.documentElement;
var nav = document.getElementById('navbarManagement');
var hot = document.getElementById('mgmtSidebarHotzone');
if (!nav || !hot) return;
var closing;
var CLOSE_DELAY = 600; // allow time to move into flyout
function open() {
root.classList.add('mgmt-sidebar-open');
}
function scheduleClose() {
if (closing) clearTimeout(closing);
closing = setTimeout(function() {
root.classList.remove('mgmt-sidebar-open');
}, CLOSE_DELAY);
}
function cancelClose() {
if (closing) {
clearTimeout(closing);
closing = null;
}
}
hot.addEventListener('mouseenter', function() {
cancelClose();
open();
});
hot.addEventListener('mouseleave', function() {
scheduleClose();
});
nav.addEventListener('mouseenter', function() {
cancelClose();
open();
});
nav.addEventListener('mouseleave', function() {
scheduleClose();
});
/* touch behavior disabled; mobile uses visible toggle */
// ESC closes
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
scheduleClose();
}
});
// Keep open while interacting with dropdown flyouts
nav.addEventListener('shown.bs.dropdown', function() {
cancelClose();
open();
}, true);
nav.addEventListener('hide.bs.dropdown', function() {
scheduleClose();
}, true);
nav.addEventListener('mouseenter', function(e) {
if (e.target && e.target.classList && e.target.classList.contains('dropdown-menu')) {
cancelClose();
}
}, true);
nav.addEventListener('mouseleave', function(e) {
if (e.target && e.target.classList && e.target.classList.contains('dropdown-menu')) {
scheduleClose();
}
}, true);
// Expose helpers for other scripts
try {
window._mgmtSidebarKeepOpen = {
open: open,
scheduleClose: scheduleClose,
cancelClose: cancelClose
};
} catch (_) {}
})();
</script>
<script>
// Simple click-open guard so the sidebar is open before dropdown shows (desktop only)
(function() {
if (!window.matchMedia('(min-width: 992px)').matches) return;
var nav = document.getElementById('navbarManagement');
if (!nav) return;
nav.addEventListener('click', function(e) {
var t = e.target.closest('.dropdown-toggle');
if (!t || !nav.contains(t)) return;
var root = document.documentElement;
if (!root.classList.contains('mgmt-sidebar-open')) {
e.preventDefault();
e.stopPropagation();
root.classList.add('mgmt-sidebar-open');
setTimeout(function() {
try {
new bootstrap.Dropdown(t).show();
} catch (_) {
try {
t.click();
} catch (__) {}
}
}, 180);
}
}, true);
})();
</script>
<!-- Desktop dropdowns: robust open/close without body portal -->
<script>
(function() {
if (!window.matchMedia('(min-width: 992px)').matches) return;
var nav = document.getElementById('navbarManagement');
if (!nav) return;
function findMenu(t) {
if (!t) return null;
var id = t.id;
if (id) {
var menuById = nav.querySelector('.dropdown-menu[aria-labelledby="' + id + '"]');
if (menuById) return menuById;
}
try { return t.parentElement.querySelector(':scope > .dropdown-menu'); } catch (_) { return t.parentElement.querySelector('.dropdown-menu'); }
}
function hideAll() {
nav.querySelectorAll('.nav-item.dropend .dropdown-menu.show').forEach(function(m){ m.classList.remove('show'); m.style.visibility=''; m.style.opacity=''; });
nav.querySelectorAll('.nav-item.dropend .dropdown-toggle[aria-expanded="true"]').forEach(function(t){ t.setAttribute('aria-expanded','false'); });
}
function showDropend(t) {
var menu = findMenu(t);
if (!menu) return;
try {
new bootstrap.Dropdown(t, { autoClose: 'outside' }).show();
} catch (_) {
// Fallback without Bootstrap
t.setAttribute('aria-expanded','true');
menu.classList.add('show');
}
}
// Toggle dropdowns on click (prevent duplicate toggles)
nav.addEventListener('click', function(e) {
var t = e.target.closest('.nav-item.dropend > .dropdown-toggle');
if (!t) return;
e.preventDefault();
e.stopPropagation();
var menu = findMenu(t);
var isOpen = !!(menu && menu.classList.contains('show'));
hideAll();
if (!isOpen) showDropend(t);
}, true);
// Close when clicking outside the management sidebar
document.addEventListener('click', function(e) {
if (!e.target.closest('#navbarManagement')) hideAll();
}, true);
// Close on ESC
document.addEventListener('keydown', function(e) { if (e.key === 'Escape') hideAll(); });
})();
</script>