@ -20,6 +20,7 @@ import {
Menu ,
MenuItem ,
Badge ,
Collapse ,
} from '@mui/material' ;
import {
Menu as MenuIcon ,
@ -31,10 +32,16 @@ import {
Logout ,
NotificationsNone ,
SearchRounded ,
Group ,
Home ,
Groups ,
RssFeed ,
PostAdd ,
Message ,
Chat ,
ExpandLess ,
ExpandMore ,
Hub ,
List as ListIcon ,
AccountTree ,
Map ,
} from '@mui/icons-material' ;
import BottomNavigation from '../navigation/BottomNavigation' ;
@ -45,6 +52,7 @@ interface NavItem {
icon : ReactNode ;
path : string ;
badge? : number ;
children? : NavItem [ ] ;
}
interface DashboardLayoutProps {
@ -56,18 +64,16 @@ const DashboardLayout = ({ children }: DashboardLayoutProps) => {
const isMobile = useMediaQuery ( theme . breakpoints . down ( 'md' ) ) ;
const [ mobileOpen , setMobileOpen ] = useState ( false ) ;
const [ profileMenuAnchor , setProfileMenuAnchor ] = useState < null | HTMLElement > ( null ) ;
const [ expandedItems , setExpandedItems ] = useState < Set < string > > ( new Set ( [ 'Network' ] ) ) ;
const location = useLocation ( ) ;
const navigate = useNavigate ( ) ;
const navItems : NavItem [ ] = [
{ text : 'Feed' , icon : < Home / > , path : '/feed' } ,
{ text : 'Contacts' , icon : < People / > , path : '/contacts' } ,
{ text : 'Groups' , icon : < Group / > , path : '/groups' } ,
{ text : 'Posts & Offers' , icon : < PostAdd / > , path : '/posts' } ,
{ text : 'Messages' , icon : < Message / > , path : '/messages' } ,
{ text : 'Import' , icon : < CloudDownload / > , path : '/import' } ,
{ text : 'Invite' , icon : < QrCode / > , path : '/invite' } ,
{ text : 'Join Network' , icon : < PersonAdd / > , path : '/onboarding' } ,
{ text : 'Feed' , icon : < RssFeed / > , path : '/feed' } ,
{ text : 'Network' , icon : < Hub / > , path : '/contacts' } ,
{ text : 'Groups' , icon : < Groups / > , path : '/groups' } ,
{ text : 'Post' , icon : < PostAdd / > , path : '/posts' } ,
{ text : 'Chat' , icon : < Chat / > , path : '/messages' } ,
] ;
const handleDrawerToggle = ( ) = > {
@ -89,66 +95,111 @@ const DashboardLayout = ({ children }: DashboardLayoutProps) => {
}
} ;
const toggleExpanded = ( itemText : string ) = > {
setExpandedItems ( prev = > {
const newSet = new Set ( prev ) ;
if ( newSet . has ( itemText ) ) {
newSet . delete ( itemText ) ;
} else {
newSet . add ( itemText ) ;
}
return newSet ;
} ) ;
} ;
const isActiveRoute = ( path : string ) = > {
if ( path === '/feed' && ( location . pathname === '/' || location . pathname === '/feed' ) ) return true ;
if ( path !== '/feed' && location . pathname . startsWith ( path ) ) return true ;
return false ;
} ;
const isParentActive = ( item : NavItem ) = > {
if ( item . children ) {
return item . children . some ( child = > isActiveRoute ( child . path ) ) ;
}
return false ;
} ;
const renderNavItem = ( item : NavItem , level : number = 0 ) = > {
const hasChildren = item . children && item . children . length > 0 ;
const isExpanded = expandedItems . has ( item . text ) ;
const isActive = isActiveRoute ( item . path ) ;
const isParentOfActive = isParentActive ( item ) ;
return (
< div key = { item . text } >
< ListItem disablePadding >
< ListItemButton
onClick = { ( ) = > {
if ( hasChildren ) {
toggleExpanded ( item . text ) ;
} else {
handleNavigation ( item . path ) ;
}
} }
selected = { isActive || isParentOfActive }
sx = { {
mx : 1 ,
ml : level > 0 ? 3 : 1 ,
borderRadius : 2 ,
minHeight : 48 ,
'&.Mui-selected' : {
backgroundColor : 'primary.main' ,
color : 'primary.contrastText' ,
ml : level > 0 ? 3 : 1 ,
mr : 2 ,
'&:hover' : {
backgroundColor : 'primary.dark' ,
} ,
'& .MuiListItemIcon-root' : {
color : 'primary.contrastText' ,
} ,
} ,
} }
>
< ListItemIcon sx = { { minWidth : 40 } } >
{ item . badge ? (
< Badge badgeContent = { item . badge } color = "error" >
{ item . icon }
< / Badge >
) : (
item . icon
) }
< / ListItemIcon >
< ListItemText
primary = { item . text }
primaryTypographyProps = { {
fontSize : '0.875rem' ,
fontWeight : isActive || isParentOfActive ? 600 : 500 ,
noWrap : true ,
} }
/ >
{ hasChildren && (
isExpanded ? < ExpandLess / > : < ExpandMore / >
) }
< / ListItemButton >
< / ListItem >
{ hasChildren && (
< Collapse in = { isExpanded } timeout = "auto" unmountOnExit >
< List component = "div" disablePadding >
{ item . children ? . map ( ( child ) = > renderNavItem ( child , level + 1 ) ) }
< / List >
< / Collapse >
) }
< / div >
) ;
} ;
const drawerContent = (
< Box sx = { { height : '100%' , display : 'flex' , flexDirection : 'column' } } >
< Box sx = { { p : 3 , borderBottom : 1 , borderColor : 'divider' } } >
< Box sx = { { height : '100%' , display : 'flex' , flexDirection : 'column' , overflow : 'hidden' } } >
< Box sx = { { p : 3 , borderBottom : 1 , borderColor : 'divider' , flexShrink : 0 } } >
< Typography variant = "h6" sx = { { fontWeight : 700 , color : 'primary.main' } } >
NAO
< / Typography >
< Typography variant = "body2" color = "text.secondary" >
Personal Network Hub
< / Typography >
< / Box >
< List sx = { { flex : 1 , py : 2 } } >
{ navItems . map ( ( item ) = > (
< ListItem key = { item . text } disablePadding >
< ListItemButton
onClick = { ( ) = > handleNavigation ( item . path ) }
selected = { isActiveRoute ( item . path ) }
sx = { {
mx : 1 ,
borderRadius : 2 ,
minHeight : 48 ,
'&.Mui-selected' : {
backgroundColor : 'primary.main' ,
color : 'primary.contrastText' ,
ml : 1 ,
mr : 2 ,
'&:hover' : {
backgroundColor : 'primary.dark' ,
} ,
'& .MuiListItemIcon-root' : {
color : 'primary.contrastText' ,
} ,
} ,
} }
>
< ListItemIcon sx = { { minWidth : 40 } } >
{ item . badge ? (
< Badge badgeContent = { item . badge } color = "error" >
{ item . icon }
< / Badge >
) : (
item . icon
) }
< / ListItemIcon >
< ListItemText
primary = { item . text }
primaryTypographyProps = { {
fontSize : '0.875rem' ,
fontWeight : isActiveRoute ( item . path ) ? 600 : 500 ,
} }
/ >
< / ListItemButton >
< / ListItem >
) ) }
< List sx = { { flex : 1 , py : 2 , overflow : 'hidden' } } >
{ navItems . map ( ( item ) = > renderNavItem ( item ) ) }
< / List >
< Divider / >