196 lines
7.3 KiB
JavaScript
196 lines
7.3 KiB
JavaScript
import { useNavigate, useLocation, Outlet, Link as RouterLink } from 'react-router-dom';
|
|
import {
|
|
Box, Drawer, List, ListItem, ListItemButton, ListItemIcon, ListItemText,
|
|
AppBar, Toolbar, Typography, Divider, Avatar, Tooltip, IconButton, Link
|
|
} from '@mui/material';
|
|
import PeopleIcon from '@mui/icons-material/People';
|
|
import ArticleIcon from '@mui/icons-material/Article';
|
|
import NotificationsActiveIcon from '@mui/icons-material/NotificationsActive';
|
|
import CorporateFareIcon from '@mui/icons-material/CorporateFare';
|
|
import LogoutIcon from '@mui/icons-material/Logout';
|
|
import DashboardIcon from '@mui/icons-material/Dashboard';
|
|
import LocationOnIcon from '@mui/icons-material/LocationOn';
|
|
import CalendarMonthIcon from '@mui/icons-material/CalendarMonth';
|
|
import HomeIcon from '@mui/icons-material/Home';
|
|
import StoreIcon from '@mui/icons-material/Store';
|
|
import { useAuth } from '../context/AuthContext';
|
|
import ErrorBoundary from './ErrorBoundary';
|
|
|
|
const DRAWER_WIDTH = 240;
|
|
|
|
const navCategories = [
|
|
{
|
|
label: null,
|
|
items: [
|
|
{ label: 'Home', path: '/', icon: <HomeIcon />, roles: null },
|
|
],
|
|
},
|
|
{
|
|
label: 'Administration',
|
|
items: [
|
|
{ label: 'Users', path: '/users', icon: <PeopleIcon />, roles: ['ADMIN'] },
|
|
{ label: 'Push', path: '/push', icon: <NotificationsActiveIcon />, roles: ['ADMIN'] },
|
|
],
|
|
},
|
|
{
|
|
label: 'Reporter',
|
|
items: [
|
|
{ label: 'Neuigkeiten', path: '/posts', icon: <ArticleIcon />, roles: ['ADMIN', 'REPORTER'] },
|
|
{ label: 'Kalendereinträge', path: '/calenderPosts', icon: <CalendarMonthIcon />, roles: ['ADMIN', 'REPORTER'] },
|
|
{ label: 'Sehenswürdigkeiten', path: '/attractions', icon: <LocationOnIcon />, roles: ['ADMIN', 'REPORTER'] },
|
|
],
|
|
},
|
|
{
|
|
label: 'Seiten',
|
|
items: [
|
|
{ label: 'Unternehmen', path: '/companies', icon: <StoreIcon />, roles: ['ADMIN', 'SITE_OWNER'] },
|
|
{ label: 'Organization', path: '/organizations', icon: <CorporateFareIcon />, roles: ['ADMIN', 'SITE_OWNER'] },
|
|
],
|
|
},
|
|
];
|
|
|
|
const allNavItems = navCategories.flatMap((c) => c.items);
|
|
|
|
function getActiveLabel(pathname) {
|
|
const exact = allNavItems.find((i) => i.path === pathname);
|
|
if (exact) return exact.label;
|
|
const partial = allNavItems.find((i) => i.path !== '/' && pathname.startsWith(i.path));
|
|
return partial?.label ?? 'Admin';
|
|
}
|
|
|
|
export default function Layout() {
|
|
const navigate = useNavigate();
|
|
const location = useLocation();
|
|
const { logout, role } = useAuth();
|
|
|
|
const visibleCategories = navCategories
|
|
.map((cat) => ({
|
|
...cat,
|
|
items: cat.items.filter((item) => !item.roles || item.roles.includes(role)),
|
|
}))
|
|
.filter((cat) => cat.items.length > 0);
|
|
|
|
return (
|
|
<Box sx={{ display: 'flex', minHeight: '100vh', bgcolor: 'grey.50' }}>
|
|
<Drawer
|
|
variant="permanent"
|
|
sx={{
|
|
width: DRAWER_WIDTH,
|
|
flexShrink: 0,
|
|
'& .MuiDrawer-paper': {
|
|
width: DRAWER_WIDTH,
|
|
boxSizing: 'border-box',
|
|
borderRight: '1px solid',
|
|
borderColor: 'divider',
|
|
'&::-webkit-scrollbar': { width: 4 },
|
|
'&::-webkit-scrollbar-track': { background: 'transparent' },
|
|
'&::-webkit-scrollbar-thumb': {
|
|
background: 'transparent',
|
|
borderRadius: 4,
|
|
transition: 'background 0.2s',
|
|
},
|
|
'&:hover::-webkit-scrollbar-thumb': {
|
|
background: 'rgba(0,0,0,0.15)',
|
|
},
|
|
},
|
|
}}
|
|
>
|
|
{/* Logo */}
|
|
<Toolbar sx={{ px: 2 }}>
|
|
<DashboardIcon sx={{ mr: 1, color: 'primary.main' }} />
|
|
<Typography variant="h6" fontWeight={600} color="primary.main">
|
|
Admin Panel
|
|
</Typography>
|
|
</Toolbar>
|
|
<Divider />
|
|
|
|
{/* Navigation */}
|
|
<List sx={{ px: 1, pt: 1, pb: 1 }}>
|
|
{visibleCategories.map((cat, catIdx) => (
|
|
<Box key={catIdx} sx={{ mt: catIdx === 0 ? 0 : 2 }}>
|
|
{cat.label && (
|
|
<Typography
|
|
variant="caption"
|
|
sx={{
|
|
px: 1.5,
|
|
pb: 0.5,
|
|
display: 'block',
|
|
color: 'text.disabled',
|
|
fontWeight: 600,
|
|
textTransform: 'uppercase',
|
|
letterSpacing: '0.08em',
|
|
}}
|
|
>
|
|
{cat.label}
|
|
</Typography>
|
|
)}
|
|
{cat.items.map((item) => (
|
|
<ListItem key={item.path} disablePadding sx={{ mb: 0.5 }}>
|
|
<ListItemButton
|
|
selected={
|
|
item.path === '/'
|
|
? location.pathname === '/'
|
|
: location.pathname.startsWith(item.path)
|
|
}
|
|
onClick={() => navigate(item.path)}
|
|
sx={{ borderRadius: 2 }}
|
|
>
|
|
<ListItemIcon sx={{ minWidth: 36 }}>{item.icon}</ListItemIcon>
|
|
<ListItemText primary={item.label} />
|
|
</ListItemButton>
|
|
</ListItem>
|
|
))}
|
|
</Box>
|
|
))}
|
|
</List>
|
|
|
|
<Box sx={{ flexGrow: 1 }} />
|
|
<Divider />
|
|
|
|
{/* Logout */}
|
|
<List sx={{ px: 1, py: 1 }}>
|
|
<ListItem disablePadding>
|
|
<ListItemButton onClick={logout} sx={{ borderRadius: 2 }}>
|
|
<ListItemIcon sx={{ minWidth: 36 }}><LogoutIcon /></ListItemIcon>
|
|
<ListItemText primary="Logout" />
|
|
</ListItemButton>
|
|
</ListItem>
|
|
</List>
|
|
<Box sx={{ px: 2, py: 1.5, borderTop: '1px solid', borderColor: 'divider' }}>
|
|
<Link component={RouterLink} to="/impressum" variant="caption" color="text.disabled" sx={{ mr: 2 }}>
|
|
Impressum
|
|
</Link>
|
|
<Link component={RouterLink} to="/datenschutz" variant="caption" color="text.disabled">
|
|
Datenschutz
|
|
</Link>
|
|
</Box>
|
|
</Drawer>
|
|
|
|
{/* Main content */}
|
|
<Box component="main" sx={{ flexGrow: 1, minHeight: '100vh', bgcolor: 'grey.50' }}>
|
|
<AppBar
|
|
position="static"
|
|
elevation={0}
|
|
sx={{ bgcolor: 'white', borderBottom: '1px solid', borderColor: 'divider' }}
|
|
>
|
|
<Toolbar>
|
|
<Typography variant="h6" color="text.primary" sx={{ flexGrow: 1 }}>
|
|
{getActiveLabel(location.pathname)}
|
|
</Typography>
|
|
<Tooltip title="Profil">
|
|
<IconButton onClick={() => navigate('/profile')} sx={{ p: 0 }}>
|
|
<Avatar sx={{ width: 32, height: 32, bgcolor: 'primary.main', fontSize: 14 }}>A</Avatar>
|
|
</IconButton>
|
|
</Tooltip>
|
|
</Toolbar>
|
|
</AppBar>
|
|
|
|
<Box sx={{ p: 3 }}>
|
|
<ErrorBoundary>
|
|
<Outlet />
|
|
</ErrorBoundary>
|
|
</Box>
|
|
</Box>
|
|
</Box>
|
|
);
|
|
} |