105 lines
4.8 KiB
JavaScript
105 lines
4.8 KiB
JavaScript
import { getUser, clearToken } from '../../lib/auth.mjs';
|
||
|
||
const BREADCRUMBS = {
|
||
'/': ['Dashboard'],
|
||
'/work-orders': ['Work Orders'],
|
||
'/work-orders/new': ['Work Orders', 'New'],
|
||
'/registry/people': ['Resources', 'People'],
|
||
'/registry/vehicles': ['Resources', 'Vehicles'],
|
||
'/registry/equipment': ['Resources', 'Equipment'],
|
||
'/registry/materials': ['Resources', 'Materials'],
|
||
'/reports': ['Reports'],
|
||
'/users': ['Admin', 'Users'],
|
||
'/settings': ['Admin', 'Settings'],
|
||
};
|
||
|
||
class AppTopbar extends HTMLElement {
|
||
#menuOpen = false;
|
||
|
||
connectedCallback() {
|
||
this.#render();
|
||
window.addEventListener('hashchange', () => this.#render());
|
||
document.addEventListener('click', e => {
|
||
if (!this.contains(e.target) && this.#menuOpen) {
|
||
this.#menuOpen = false;
|
||
this.#render();
|
||
}
|
||
});
|
||
}
|
||
|
||
#render() {
|
||
const user = getUser();
|
||
const path = decodeURIComponent(location.hash.slice(1)) || '/';
|
||
const crumb = BREADCRUMBS[path] || [path.split('/').filter(Boolean).map(s => s.replace(/-/g, ' ')).join(' › ')];
|
||
const initials = (user?.displayName || user?.username || 'U')
|
||
.split(' ').map(w => w[0]).join('').slice(0, 2).toUpperCase();
|
||
|
||
this.innerHTML = `
|
||
<style>
|
||
app-topbar { position: relative; }
|
||
.breadcrumb { flex: 1; display: flex; align-items: center; gap: .4rem; font-size: .875rem; }
|
||
.crumb { color: var(--text-muted); }
|
||
.crumb:last-child { color: var(--text); font-weight: 600; }
|
||
.sep { color: var(--border); }
|
||
.right { display: flex; align-items: center; gap: .5rem; margin-left: auto; }
|
||
.avatar-btn { background: none; border: none; cursor: pointer; display: flex; align-items: center; gap: .5rem; padding: .3rem .5rem; border-radius: var(--radius); transition: background .15s; }
|
||
.avatar-btn:hover { background: var(--surface-2); }
|
||
.avatar { width: 30px; height: 30px; border-radius: 50%; background: var(--teal); color: #fff; display: flex; align-items: center; justify-content: center; font-size: .75rem; font-weight: 700; flex-shrink: 0; }
|
||
.user-name { font-size: .813rem; font-weight: 500; color: var(--text); }
|
||
.dropdown { position: absolute; top: calc(100% + .25rem); right: 1.25rem; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow-md); min-width: 180px; z-index: 100; overflow: hidden; }
|
||
.dd-item { display: flex; align-items: center; gap: .6rem; padding: .65rem 1rem; font-size: .875rem; color: var(--text); cursor: pointer; border: none; background: none; width: 100%; text-align: left; transition: background .15s; }
|
||
.dd-item:hover { background: var(--surface-2); }
|
||
.dd-item.danger { color: var(--danger); }
|
||
.dd-divider { height: 1px; background: var(--border); margin: .25rem 0; }
|
||
.mobile-menu-btn { display: none; background: none; border: none; cursor: pointer; color: var(--text); padding: .4rem; }
|
||
@media (max-width: 768px) { .mobile-menu-btn { display: flex; } .breadcrumb { font-size: .813rem; } }
|
||
</style>
|
||
|
||
<button class="mobile-menu-btn" id="mobile-menu">
|
||
<i data-lucide="menu" style="width:22px;height:22px"></i>
|
||
</button>
|
||
|
||
<nav class="breadcrumb">
|
||
${crumb.map((c, i) => `
|
||
<span class="crumb">${c}</span>
|
||
${i < crumb.length - 1 ? '<span class="sep">/</span>' : ''}
|
||
`).join('')}
|
||
</nav>
|
||
|
||
<div class="right">
|
||
<button class="avatar-btn" id="user-menu-btn">
|
||
<div class="avatar">${initials}</div>
|
||
<span class="user-name">${user?.displayName || user?.username || ''}</span>
|
||
<i data-lucide="chevron-down" style="width:14px;height:14px;color:var(--text-muted)"></i>
|
||
</button>
|
||
</div>
|
||
|
||
${this.#menuOpen ? `
|
||
<div class="dropdown" id="dropdown">
|
||
<button class="dd-item">
|
||
<i data-lucide="user" style="width:15px;height:15px"></i> My Profile
|
||
</button>
|
||
<div class="dd-divider"></div>
|
||
<button class="dd-item danger" id="dd-logout">
|
||
<i data-lucide="log-out" style="width:15px;height:15px"></i> Sign Out
|
||
</button>
|
||
</div>` : ''}`;
|
||
|
||
if (window.lucide) lucide.createIcons({ nodes: [this] });
|
||
|
||
this.querySelector('#user-menu-btn')?.addEventListener('click', e => {
|
||
e.stopPropagation();
|
||
this.#menuOpen = !this.#menuOpen;
|
||
this.#render();
|
||
});
|
||
this.querySelector('#dd-logout')?.addEventListener('click', () => {
|
||
clearToken();
|
||
window.dispatchEvent(new CustomEvent('auth:logout'));
|
||
});
|
||
this.querySelector('#mobile-menu')?.addEventListener('click', () => {
|
||
window.dispatchEvent(new CustomEvent('sidebar:toggle'));
|
||
});
|
||
}
|
||
}
|
||
customElements.define('app-topbar', AppTopbar);
|