Files
site11/services/news-engine-console/frontend/src/components/MainLayout.tsx
jungwoo choi 30fe4d0368 feat: Implement Keywords management page with DataGrid
Frontend Phase 2 - Keywords Management:
- Add MainLayout component with sidebar navigation
- Implement Keywords page with MUI DataGrid
- Add Keywords CRUD operations (Create, Edit, Delete dialogs)
- Add search and filter functionality (Category, Status)
- Install @mui/x-data-grid package for table component
- Update routing to include Keywords page
- Update Dashboard to use MainLayout
- Add navigation menu items for all planned pages

Features implemented:
- Keywords list with DataGrid table
- Add/Edit keyword dialog with form validation
- Delete confirmation dialog
- Category filter (People, Topics, Companies)
- Status filter (Active, Inactive)
- Search functionality
- Priority management

Tested in browser:
- Page loads successfully
- API integration working (200 OK)
- Layout and navigation functional
- All UI components rendering correctly

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-04 21:47:45 +09:00

176 lines
4.4 KiB
TypeScript

import { ReactNode, useState } from 'react'
import {
Box,
Drawer,
AppBar,
Toolbar,
List,
Typography,
Divider,
IconButton,
ListItem,
ListItemButton,
ListItemIcon,
ListItemText,
Button,
} from '@mui/material'
import {
Menu as MenuIcon,
Dashboard as DashboardIcon,
Label as KeywordIcon,
AccountTree as PipelineIcon,
People as PeopleIcon,
Apps as AppsIcon,
Article as ArticleIcon,
BarChart as MonitoringIcon,
} from '@mui/icons-material'
import { useNavigate, useLocation } from 'react-router-dom'
import { useAuthStore } from '../stores/authStore'
const drawerWidth = 240
interface MainLayoutProps {
children: ReactNode
}
interface MenuItem {
text: string
icon: ReactNode
path: string
}
const menuItems: MenuItem[] = [
{ text: 'Dashboard', icon: <DashboardIcon />, path: '/dashboard' },
{ text: 'Keywords', icon: <KeywordIcon />, path: '/keywords' },
{ text: 'Pipelines', icon: <PipelineIcon />, path: '/pipelines' },
{ text: 'Users', icon: <PeopleIcon />, path: '/users' },
{ text: 'Applications', icon: <AppsIcon />, path: '/applications' },
{ text: 'Articles', icon: <ArticleIcon />, path: '/articles' },
{ text: 'Monitoring', icon: <MonitoringIcon />, path: '/monitoring' },
]
export default function MainLayout({ children }: MainLayoutProps) {
const [mobileOpen, setMobileOpen] = useState(false)
const navigate = useNavigate()
const location = useLocation()
const { user, logout } = useAuthStore()
const handleDrawerToggle = () => {
setMobileOpen(!mobileOpen)
}
const handleLogout = async () => {
await logout()
navigate('/login')
}
const handleNavigate = (path: string) => {
navigate(path)
setMobileOpen(false)
}
const drawer = (
<div>
<Toolbar>
<Typography variant="h6" noWrap component="div">
News Engine
</Typography>
</Toolbar>
<Divider />
<List>
{menuItems.map((item) => (
<ListItem key={item.text} disablePadding>
<ListItemButton
selected={location.pathname === item.path}
onClick={() => handleNavigate(item.path)}
>
<ListItemIcon>{item.icon}</ListItemIcon>
<ListItemText primary={item.text} />
</ListItemButton>
</ListItem>
))}
</List>
</div>
)
return (
<Box sx={{ display: 'flex' }}>
<AppBar
position="fixed"
sx={{
width: { sm: `calc(100% - ${drawerWidth}px)` },
ml: { sm: `${drawerWidth}px` },
}}
>
<Toolbar>
<IconButton
color="inherit"
aria-label="open drawer"
edge="start"
onClick={handleDrawerToggle}
sx={{ mr: 2, display: { sm: 'none' } }}
>
<MenuIcon />
</IconButton>
<Typography variant="h6" noWrap component="div" sx={{ flexGrow: 1 }}>
News Engine Console
</Typography>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
<Typography variant="body2">
{user?.full_name} ({user?.role})
</Typography>
<Button color="inherit" onClick={handleLogout}>
Logout
</Button>
</Box>
</Toolbar>
</AppBar>
<Box
component="nav"
sx={{ width: { sm: drawerWidth }, flexShrink: { sm: 0 } }}
aria-label="navigation"
>
{/* Mobile drawer */}
<Drawer
variant="temporary"
open={mobileOpen}
onClose={handleDrawerToggle}
ModalProps={{
keepMounted: true, // Better open performance on mobile.
}}
sx={{
display: { xs: 'block', sm: 'none' },
'& .MuiDrawer-paper': { boxSizing: 'border-box', width: drawerWidth },
}}
>
{drawer}
</Drawer>
{/* Desktop drawer */}
<Drawer
variant="permanent"
sx={{
display: { xs: 'none', sm: 'block' },
'& .MuiDrawer-paper': { boxSizing: 'border-box', width: drawerWidth },
}}
open
>
{drawer}
</Drawer>
</Box>
<Box
component="main"
sx={{
flexGrow: 1,
p: 3,
width: { sm: `calc(100% - ${drawerWidth}px)` },
minHeight: '100vh',
bgcolor: '#f5f5f5',
}}
>
<Toolbar />
{children}
</Box>
</Box>
)
}