import { AppBar, Box, Button, Divider, Drawer, IconButton, List, ListItemButton, ListItemIcon, ListItemText, Toolbar, Tooltip, Typography, useMediaQuery, useTheme, } from '@mui/material'; import AdminPanelSettingsOutlinedIcon from '@mui/icons-material/AdminPanelSettingsOutlined'; import CircleOutlinedIcon from '@mui/icons-material/CircleOutlined'; import Inventory2OutlinedIcon from '@mui/icons-material/Inventory2Outlined'; import DashboardOutlinedIcon from '@mui/icons-material/DashboardOutlined'; import MenuIcon from '@mui/icons-material/Menu'; import LogoutIcon from '@mui/icons-material/Logout'; import PeopleOutlinedIcon from '@mui/icons-material/PeopleOutlined'; import SecurityOutlinedIcon from '@mui/icons-material/SecurityOutlined'; import TuneOutlinedIcon from '@mui/icons-material/TuneOutlined'; import { useEffect, useState } from 'react'; import { useLocation } from 'react-router-dom'; import { getNavigation } from '../../api/client'; import NotificationMenu from '../notifications/NotificationMenu'; const DRAWER_WIDTH = 224; const DEFAULT_HEADER_LABEL = 'Workspace'; const NAVIGATION_ICONS = { Demo: DashboardOutlinedIcon, Admin: AdminPanelSettingsOutlinedIcon, Roles: SecurityOutlinedIcon, 'PHPEngine Settings': TuneOutlinedIcon, Users: PeopleOutlinedIcon, 'Inventory Monitor': Inventory2OutlinedIcon, }; /** * Converts a menu URL into an internal React route. * * @param {string} url Legacy menu URL. * @returns {string} Route path. * @sideEffects None. */ function toRoute(url) { return url.startsWith('/') ? url : `/${url}`; } /** * Splits the flat, legacy menu payload into root links and populated groups. * * @param {Array>} navigation Authorized navigation entries. * @returns {{topLevelItems: Array>, groups: Array>}} Display-ready navigation structure. * @sideEffects None. */ function getNavigationStructure(navigation) { const groups = navigation .filter(item => item.mainMenu && !item.url) .map(group => ({ ...group, items: navigation.filter(item => item.mainMenu === group.name && item.url), })) .filter(group => group.items.length > 0); return { topLevelItems: navigation.filter(item => !item.mainMenu && item.url), groups, }; } /** * Renders one compact navigation entry with its selected and disabled states. * * @param {{item: Record, selected: boolean, onClick: () => void}} props Navigation entry properties. * @returns {JSX.Element} Navigation button. * @sideEffects None. */ function NavigationItem({ item, selected, onClick }) { const Icon = NAVIGATION_ICONS[item.name] ?? CircleOutlinedIcon; return ( ); } /** * Renders the authenticated responsive application shell. * * @param {{session: Record, onNavigate: (path: string) => void, onLogout: () => void, children: import('react').ReactNode}} props Shell properties. * @returns {JSX.Element} Application shell. * @sideEffects Loads permission-filtered navigation and renders authenticated controls. */ function AppShell({ session, onNavigate, onLogout, children }) { const theme = useTheme(); const location = useLocation(); const isMobile = useMediaQuery(theme.breakpoints.down('md')); const [mobileOpen, setMobileOpen] = useState(false); const [navigation, setNavigation] = useState([]); const [navigationError, setNavigationError] = useState(''); const { topLevelItems, groups } = getNavigationStructure(navigation); const activeItem = navigation.find(item => item.url && toRoute(item.url) === location.pathname); const activeGroup = groups.find(group => group.items.some(item => toRoute(item.url) === location.pathname), ); const headerLabel = activeGroup?.name ?? DEFAULT_HEADER_LABEL; useEffect(() => { let isMounted = true; getNavigation() .then(items => { if (isMounted) { setNavigation(items); } }) .catch(() => { if (isMounted) { setNavigationError('Navigation is temporarily unavailable.'); } }); return () => { isMounted = false; }; }, [session]); /** * Navigates through the route controller and closes a mobile drawer. * * @param {string} url Authorized menu URL. * @returns {void} * @sideEffects Changes the application route through the parent controller. */ function handleNavigation(url) { setMobileOpen(false); onNavigate(toRoute(url)); } const drawer = ( PHPEngine Console {topLevelItems.map(item => ( handleNavigation(item.url)} /> ))} {groups.map(group => ( {group.name} {group.items.map(item => ( handleNavigation(item.url)} /> ))} ))} {navigationError && ( {navigationError} )} ); return ( {isMobile && ( setMobileOpen(true)}> )} {headerLabel} {isMobile ? 'PHPEngine' : activeItem?.name ?? 'PHPEngine'} {isMobile ? ( setMobileOpen(false)} PaperProps={{ sx: { width: DRAWER_WIDTH } }}> {drawer} ) : ( {drawer} )} {children} ); } export default AppShell;