company + layout verbessert

This commit is contained in:
2026-04-12 18:52:25 +02:00
parent 90a15b7165
commit a8e155f5d5
4 changed files with 667 additions and 32 deletions

View File

@@ -1,7 +1,7 @@
import { useNavigate, useLocation, Outlet } from 'react-router-dom';
import {
Box, Drawer, List, ListItem, ListItemButton, ListItemIcon, ListItemText,
AppBar, Toolbar, Typography, IconButton, Divider, Avatar, Tooltip,
AppBar, Toolbar, Typography, Divider, Avatar, Tooltip,
} from '@mui/material';
import PeopleIcon from '@mui/icons-material/People';
import ArticleIcon from '@mui/icons-material/Article';
@@ -11,24 +11,47 @@ import LogoutIcon from '@mui/icons-material/Logout';
import DashboardIcon from '@mui/icons-material/Dashboard';
import LocationOnIcon from '@mui/icons-material/LocationOn';
import HomeIcon from '@mui/icons-material/Home';
import StoreIcon from '@mui/icons-material/Store';
import { useAuth } from '../context/AuthContext';
const DRAWER_WIDTH = 240;
const navItems = [
{ label: 'Home', path: '/', icon: <HomeIcon />, roles: null },
{ label: 'Users', path: '/users', icon: <PeopleIcon />, roles: ['ADMIN'] },
{ label: 'Neuigkeiten', path: '/posts', icon: <ArticleIcon />, roles: ['ADMIN', 'REPORTER'] },
{ label: 'Push', path: '/push', icon: <NotificationsActiveIcon />, roles: ['ADMIN'] },
{ label: 'Organization', path: '/organizations', icon: <CorporateFareIcon />, roles: ['ADMIN', 'SITE_OWNER'] },
{ label: 'Sehenswürdigkeiten', path: '/attractions', icon: <LocationOnIcon />, roles: ['ADMIN', 'REPORTER'] },
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: '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) {
// Exakter Match für "/" zuerst prüfen, dann startsWith für den Rest
const exact = navItems.find((i) => i.path === pathname);
const exact = allNavItems.find((i) => i.path === pathname);
if (exact) return exact.label;
const partial = navItems.find((i) => i.path !== '/' && pathname.startsWith(i.path));
const partial = allNavItems.find((i) => i.path !== '/' && pathname.startsWith(i.path));
return partial?.label ?? 'Admin';
}
@@ -37,13 +60,15 @@ export default function Layout() {
const location = useLocation();
const { logout, role } = useAuth();
const visibleNavItems = navItems.filter(
(item) => !item.roles || item.roles.includes(role)
);
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' }}>
{/* Sidebar */}
<Box sx={{ display: 'flex', minHeight: '100vh', bgcolor: 'grey.50' }}>
<Drawer
variant="permanent"
sx={{
@@ -54,9 +79,20 @@ export default function Layout() {
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">
@@ -64,26 +100,51 @@ export default function Layout() {
</Typography>
</Toolbar>
<Divider />
<List sx={{ px: 1, pt: 1 }}>
{visibleNavItems.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>
{/* 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 }}>