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>
176 lines
4.4 KiB
TypeScript
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>
|
|
)
|
|
}
|