Rebuild desktop shell on the real AdminLTE 4 package
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.
This commit is contained in:
1 parent
7198895444
commit
84cd474bcb
19 files changed
+528
-162
No files matched your search
@@ -1,27 +1,132 @@
|
||||
import { Link } from 'react-router-dom'
|
||||
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 (
|
||||
<header className="flex h-14 items-center justify-between border-b border-gray-200 bg-white px-4 dark:border-gray-800 dark:bg-gray-900">
|
||||
<span className="font-semibold text-gray-900 dark:text-gray-100">webappAIO</span>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-sm text-gray-600 dark:text-gray-400">{user?.name}</span>
|
||||
<Link
|
||||
to="/settings/2fa"
|
||||
className="rounded-md px-2 py-1 text-sm text-gray-600 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-800"
|
||||
>
|
||||
2FA Settings
|
||||
</Link>
|
||||
<button
|
||||
onClick={() => logout()}
|
||||
className="rounded-md px-2 py-1 text-sm text-gray-600 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-800"
|
||||
>
|
||||
Sign out
|
||||
</button>
|
||||
<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>
|
||||
</header>
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
Reference in new issue
Block a user