Install [email protected] (+ bootstrap 5.3.8, bootstrap-icons as peer deps) and rebuild every desktop component against actual AdminLTE markup pulled from its live docs/demo, rather than a Tailwind approximation: - Real app-wrapper/app-header/app-sidebar/app-main/app-footer structure - Working PushMenu (sidebar collapse), Treeview, ColorMode (light/dark/auto) - Navbar: functional search (wired to Users list ?search=), notifications dropdown (MQTT-driven), user menu dropdown - Sidebar: grouped nav-header sections, active-route highlighting - Breadcrumbs, footer, Bootstrap card/table/pagination/progress widgets Tailwind is now imported utilities-only (no preflight) so it coexists with Bootstrap's reset; mobile shell and modals are untouched and still Tailwind.
133 lines
5.1 KiB
TypeScript
133 lines
5.1 KiB
TypeScript
import { useState, type FormEvent } from 'react'
|
|
import { Link, useNavigate } from 'react-router-dom'
|
|
import { useAuth } from '../../features/auth/AuthContext'
|
|
import { useNotifications } from './useNotifications'
|
|
|
|
export function Navbar() {
|
|
const { user, logout } = useAuth()
|
|
const navigate = useNavigate()
|
|
const [search, setSearch] = useState('')
|
|
const notifications = useNotifications()
|
|
|
|
function handleSearch(e: FormEvent) {
|
|
e.preventDefault()
|
|
if (search.trim()) {
|
|
navigate(`/users?search=${encodeURIComponent(search.trim())}`)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<nav className="app-header navbar navbar-expand bg-body">
|
|
<div className="container-fluid">
|
|
<ul className="navbar-nav">
|
|
<li className="nav-item">
|
|
<a className="nav-link" data-lte-toggle="sidebar" href="#" role="button" aria-label="Toggle sidebar">
|
|
<i className="bi bi-list"></i>
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
|
|
<form className="d-none d-md-flex ms-3" role="search" onSubmit={handleSearch}>
|
|
<input
|
|
type="search"
|
|
value={search}
|
|
onChange={(e) => setSearch(e.target.value)}
|
|
className="form-control form-control-sm"
|
|
placeholder="Search users…"
|
|
aria-label="Search"
|
|
/>
|
|
</form>
|
|
|
|
<ul className="navbar-nav ms-auto">
|
|
<li className="nav-item dropdown">
|
|
<a
|
|
className="nav-link"
|
|
data-bs-toggle="dropdown"
|
|
href="#"
|
|
aria-label={`Notifications: ${notifications.length} unread`}
|
|
>
|
|
<i className="bi bi-bell-fill"></i>
|
|
{notifications.length > 0 && (
|
|
<span className="navbar-badge badge text-bg-warning">{notifications.length}</span>
|
|
)}
|
|
</a>
|
|
<div className="dropdown-menu dropdown-menu-lg dropdown-menu-end">
|
|
<span className="dropdown-item dropdown-header">{notifications.length} Notifications</span>
|
|
<div className="dropdown-divider"></div>
|
|
{notifications.length === 0 ? (
|
|
<span className="dropdown-item text-secondary">No new notifications</span>
|
|
) : (
|
|
notifications.map((n, i) => (
|
|
<div key={i}>
|
|
<a href="#" className="dropdown-item">
|
|
<i className="bi bi-person-fill me-2"></i>
|
|
{n.message}
|
|
</a>
|
|
{i < notifications.length - 1 && <div className="dropdown-divider"></div>}
|
|
</div>
|
|
))
|
|
)}
|
|
</div>
|
|
</li>
|
|
|
|
<li className="nav-item dropdown">
|
|
<a
|
|
className="nav-link"
|
|
href="#"
|
|
id="bd-theme"
|
|
aria-label="Toggle color scheme"
|
|
data-bs-toggle="dropdown"
|
|
aria-expanded="false"
|
|
>
|
|
<i className="bi bi-sun-fill d-none" data-lte-theme-icon="light"></i>
|
|
<i className="bi bi-moon-fill" data-lte-theme-icon="dark"></i>
|
|
<i className="bi bi-circle-half d-none" data-lte-theme-icon="auto"></i>
|
|
</a>
|
|
<ul className="dropdown-menu dropdown-menu-end" aria-labelledby="bd-theme">
|
|
<li>
|
|
<button type="button" className="dropdown-item d-flex align-items-center" data-bs-theme-value="light">
|
|
<i className="bi bi-sun-fill me-2"></i>Light
|
|
</button>
|
|
</li>
|
|
<li>
|
|
<button type="button" className="dropdown-item d-flex align-items-center" data-bs-theme-value="dark">
|
|
<i className="bi bi-moon-fill me-2"></i>Dark
|
|
</button>
|
|
</li>
|
|
<li>
|
|
<button type="button" className="dropdown-item d-flex align-items-center" data-bs-theme-value="auto">
|
|
<i className="bi bi-circle-half me-2"></i>Auto
|
|
</button>
|
|
</li>
|
|
</ul>
|
|
</li>
|
|
|
|
<li className="nav-item dropdown user-menu">
|
|
<a href="#" className="nav-link dropdown-toggle" data-bs-toggle="dropdown">
|
|
<i className="bi bi-person-circle fs-5"></i>
|
|
<span className="d-none d-md-inline ms-1">{user?.name}</span>
|
|
</a>
|
|
<ul className="dropdown-menu dropdown-menu-lg dropdown-menu-end">
|
|
<li className="user-header text-bg-primary">
|
|
<i className="bi bi-person-circle" style={{ fontSize: '3rem' }}></i>
|
|
<p>
|
|
{user?.name}
|
|
<small>{user?.roles.join(', ')}</small>
|
|
</p>
|
|
</li>
|
|
<li className="user-footer">
|
|
<Link to="/settings/2fa" className="btn btn-outline-secondary">
|
|
2FA Settings
|
|
</Link>
|
|
<button onClick={() => logout()} className="btn btn-outline-danger float-end">
|
|
Sign out
|
|
</button>
|
|
</li>
|
|
</ul>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</nav>
|
|
)
|
|
}
|