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:
ghassan committed 2026-08-04 13:47:02 +03:00
1 parent 7198895444
commit 84cd474bcb
19 files changed
+528 -162

No files matched your search

+123 -18
View File
@@ -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>
)
}