Backend: real endpoints for role-permission overview, MQTT broker settings (with live reachability check), and SQLite backup download/restore (new system.manage permission, super-admin only; restore validates the SQLite file header and keeps a pre-restore safety copy). Desktop frontend: Roles (read-only permissions view), MQTT Settings, Backup & Restore wired to those endpoints; plus static AdminLTE-matching shells for Profile, Settings (embeds the real 2FA flow in its Security tab), Invoice, Calendar (FullCalendar), Chat, File Manager, and Projects. Mobile frontend: new frontend/src/styles/mobile.css, a from-scratch design system (purple theme, cards, slide-out sidebar, bottom tab bar) scoped entirely under a .mob-app root and mob- prefixed classes so it can never collide with the desktop Bootstrap/AdminLTE styling. Rebuilt TopBar/ BottomNav, added a new SlideOutNav, and reskinned the Dashboard and Users pages to it with real data.
49 lines
1.6 KiB
TypeScript
49 lines
1.6 KiB
TypeScript
import { NavLink } from 'react-router-dom'
|
|
import { NAV_GROUPS } from '../../layouts/shell/navConfig'
|
|
import { usePermission } from '../../hooks/usePermission'
|
|
|
|
interface SlideOutNavProps {
|
|
open: boolean
|
|
onClose: () => void
|
|
}
|
|
|
|
export function SlideOutNav({ open, onClose }: SlideOutNavProps) {
|
|
const { can } = usePermission()
|
|
|
|
return (
|
|
<>
|
|
<div className={`mob-sidebar-backdrop ${open ? 'mob-show' : ''}`} onClick={onClose} />
|
|
<nav className={`mob-sidebar ${open ? 'mob-open' : ''}`} aria-label="Main navigation">
|
|
<div className="mob-sidebar-header">
|
|
<span className="mob-sidebar-title">webappAIO</span>
|
|
<button className="mob-sidebar-close" onClick={onClose} aria-label="Close menu">
|
|
✕
|
|
</button>
|
|
</div>
|
|
|
|
{NAV_GROUPS.map((group) => {
|
|
const items = group.items.filter((item) => !item.permission || can(item.permission))
|
|
if (items.length === 0) return null
|
|
|
|
return (
|
|
<div key={group.header}>
|
|
<div className="mob-sidebar-section-label">{group.header}</div>
|
|
{items.map((item) => (
|
|
<NavLink
|
|
key={item.to}
|
|
to={item.to}
|
|
onClick={onClose}
|
|
className={({ isActive }) => `mob-sidebar-link ${isActive ? 'mob-active' : ''}`}
|
|
>
|
|
<span className={`mob-nav-icon bi ${item.icon}`}></span>
|
|
{item.label}
|
|
</NavLink>
|
|
))}
|
|
</div>
|
|
)
|
|
})}
|
|
</nav>
|
|
</>
|
|
)
|
|
}
|