recreate project

This commit is contained in:
root
2026-02-10 22:11:06 -05:00
commit 663c0cdbda
10149 changed files with 1379710 additions and 0 deletions
+441
View File
@@ -0,0 +1,441 @@
<?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>