11import { useEffect , useState } from 'react' ;
22import { Outlet , useNavigate , useLocation , useParams } from 'react-router-dom' ;
33import {
4- AppBar , Box , Drawer , IconButton , List , ListItemButton , ListItemIcon ,
5- ListItemText , Toolbar , Typography , Select , MenuItem ,
4+ AppBar , Box , Chip , Drawer , IconButton , List , ListItemButton , ListItemIcon ,
5+ ListItemText , ListSubheader , Toolbar , Typography , Select , MenuItem ,
66 Divider , useTheme ,
77} from '@mui/material' ;
88import MenuIcon from '@mui/icons-material/Menu' ;
@@ -19,7 +19,7 @@ import PsychologyIcon from '@mui/icons-material/Psychology';
1919import AutoAwesomeIcon from '@mui/icons-material/AutoAwesome' ;
2020import BuildIcon from '@mui/icons-material/Build' ;
2121import MenuBookIcon from '@mui/icons-material/MenuBook' ;
22- import { useProjects } from '@/entities/project/index.ts' ;
22+ import { useProjects , type WorkspaceInfo } from '@/entities/project/index.ts' ;
2323import { useThemeMode } from '@/shared/lib/ThemeModeContext.tsx' ;
2424import { WsProvider } from '@/shared/lib/useWebSocket.ts' ;
2525
@@ -73,15 +73,54 @@ function buildDocumentTitle(pathname: string): string {
7373 return parts . join ( ' :: ' ) ;
7474}
7575
76+ /** Build grouped menu items: workspace subheaders + project items, then standalone projects. */
77+ function buildGroupedItems (
78+ projects : { id : string ; workspaceId : string | null } [ ] ,
79+ workspaces : WorkspaceInfo [ ] ,
80+ ) : React . ReactNode [ ] {
81+ const items : React . ReactNode [ ] = [ ] ;
82+ const placed = new Set < string > ( ) ;
83+
84+ for ( const ws of workspaces ) {
85+ const wsProjects = projects . filter ( p => p . workspaceId === ws . id ) ;
86+ if ( wsProjects . length === 0 ) continue ;
87+ items . push (
88+ < ListSubheader key = { `ws-${ ws . id } ` } sx = { { lineHeight : '32px' , fontSize : '0.7rem' , fontWeight : 700 , textTransform : 'uppercase' , letterSpacing : '0.05em' } } >
89+ { ws . id }
90+ </ ListSubheader >
91+ ) ;
92+ for ( const p of wsProjects ) {
93+ items . push ( < MenuItem key = { p . id } value = { p . id } > { p . id } </ MenuItem > ) ;
94+ placed . add ( p . id ) ;
95+ }
96+ }
97+
98+ const standalone = projects . filter ( p => ! placed . has ( p . id ) ) ;
99+ if ( standalone . length > 0 && workspaces . length > 0 ) {
100+ items . push (
101+ < ListSubheader key = "ws-standalone" sx = { { lineHeight : '32px' , fontSize : '0.7rem' , fontWeight : 700 , textTransform : 'uppercase' , letterSpacing : '0.05em' } } >
102+ Standalone
103+ </ ListSubheader >
104+ ) ;
105+ }
106+ for ( const p of standalone ) {
107+ items . push ( < MenuItem key = { p . id } value = { p . id } > { p . id } </ MenuItem > ) ;
108+ }
109+
110+ return items ;
111+ }
112+
76113export default function Layout ( ) {
77114 const [ mobileOpen , setMobileOpen ] = useState ( false ) ;
78- const { projects, loading } = useProjects ( ) ;
115+ const { projects, workspaces , loading } = useProjects ( ) ;
79116 const navigate = useNavigate ( ) ;
80117 const location = useLocation ( ) ;
81118 const { projectId } = useParams ( ) ;
82119 const { mode, toggle } = useThemeMode ( ) ;
83120 const { palette } = useTheme ( ) ;
84121
122+ const currentProject = projects . find ( p => p . id === projectId ) ;
123+
85124 const pageTitle = getPageTitle ( location . pathname ) ;
86125 const documentTitle = buildDocumentTitle ( location . pathname ) ;
87126
@@ -117,9 +156,7 @@ export default function Layout() {
117156 </ Typography >
118157 ) }
119158 >
120- { projects . map ( ( p ) => (
121- < MenuItem key = { p . id } value = { p . id } > { p . id } </ MenuItem >
122- ) ) }
159+ { buildGroupedItems ( projects , workspaces ) }
123160 </ Select >
124161 </ Box >
125162 < Divider />
@@ -187,9 +224,20 @@ export default function Layout() {
187224 >
188225 < MenuIcon />
189226 </ IconButton >
190- < Typography variant = "h6" noWrap sx = { { flexGrow : 1 } } >
191- { pageTitle }
192- </ Typography >
227+ < Box sx = { { display : 'flex' , alignItems : 'center' , gap : 1 , flexGrow : 1 } } >
228+ < Typography variant = "h6" noWrap >
229+ { pageTitle }
230+ </ Typography >
231+ { currentProject ?. workspaceId && (
232+ < Chip
233+ label = { currentProject . workspaceId }
234+ size = "small"
235+ variant = "outlined"
236+ color = "primary"
237+ sx = { { fontWeight : 600 } }
238+ />
239+ ) }
240+ </ Box >
193241 < IconButton color = "inherit" onClick = { toggle } title = { `Switch to ${ mode === 'dark' ? 'light' : 'dark' } mode` } >
194242 { mode === 'dark' ? < LightModeIcon /> : < DarkModeIcon /> }
195243 </ IconButton >
0 commit comments