255 lines
7.5 KiB
JavaScript
255 lines
7.5 KiB
JavaScript
import { PureComponent } from 'react';
|
|
import Box from '@mui/material/Box';
|
|
import List from '@mui/material/List';
|
|
import ListItemButton from '@mui/material/ListItemButton';
|
|
import ListItemIcon from '@mui/material/ListItemIcon';
|
|
import ListItemText from '@mui/material/ListItemText';
|
|
import ButtonBase from '@mui/material/ButtonBase';
|
|
import Typography from '@mui/material/Typography';
|
|
import { Outlet, useLocation, useNavigate } from 'react-router-dom';
|
|
import UserMenu from './UserMenu';
|
|
import { NAV_ITEMS, SUB_NAV, resolveActiveNav, resolveActiveSub } from '../nav';
|
|
import { artikelQueryPath } from '../pages/artikel/artikelQuery';
|
|
import { defaultCategoryPath } from '../pages/artikel/categoryRoute';
|
|
import { navigationAutomat } from '../automats';
|
|
|
|
const SIDEBAR_WIDTH = 144;
|
|
const SUB_NAV_WIDTH = 88;
|
|
|
|
class AppSidebar extends PureComponent {
|
|
constructor(props) {
|
|
super(props);
|
|
this.state = {
|
|
lastDeliveryPath: navigationAutomat.state.lastDeliveryPath,
|
|
lastArticleSku: navigationAutomat.state.lastArticleSku,
|
|
};
|
|
}
|
|
|
|
componentDidMount() {
|
|
this.unsub = navigationAutomat.subscribe(this, (state) => ({
|
|
lastDeliveryPath: state.lastDeliveryPath,
|
|
lastArticleSku: state.lastArticleSku,
|
|
}));
|
|
}
|
|
|
|
componentWillUnmount() {
|
|
this.unsub?.();
|
|
}
|
|
|
|
render() {
|
|
const { activeNav, navigate } = this.props;
|
|
const { lastDeliveryPath, lastArticleSku } = this.state;
|
|
|
|
return (
|
|
<Box
|
|
component="nav"
|
|
sx={(theme) => ({
|
|
width: SIDEBAR_WIDTH,
|
|
flexShrink: 0,
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
bgcolor: theme.nav.background,
|
|
position: 'sticky',
|
|
top: 0,
|
|
alignSelf: 'flex-start',
|
|
height: '100vh',
|
|
})}
|
|
>
|
|
<List disablePadding sx={{ flexGrow: 1 }}>
|
|
{NAV_ITEMS.map(({ path, label, icon: Icon }) => {
|
|
const active = path === activeNav;
|
|
|
|
return (
|
|
<ListItemButton
|
|
key={path}
|
|
type="button"
|
|
selected={active}
|
|
onMouseDown={(e) => e.preventDefault()}
|
|
onClick={() => {
|
|
if (path === '/artikel') {
|
|
if (lastArticleSku) {
|
|
navigate(`/artikel/${lastArticleSku}`);
|
|
} else {
|
|
navigate(artikelQueryPath());
|
|
}
|
|
} else if (path === '/lieferung') {
|
|
if (lastDeliveryPath) {
|
|
navigate(lastDeliveryPath);
|
|
} else {
|
|
navigate(path);
|
|
}
|
|
} else {
|
|
navigate(path);
|
|
}
|
|
}}
|
|
sx={(theme) => ({
|
|
borderRadius: 0,
|
|
px: 1.5,
|
|
py: 1.25,
|
|
borderLeft: '3px solid transparent',
|
|
color: theme.nav.item,
|
|
'& .MuiListItemIcon-root': { color: 'inherit' },
|
|
'&:hover': {
|
|
bgcolor: theme.nav.itemHover,
|
|
color: theme.nav.itemHoverText,
|
|
},
|
|
'&.Mui-selected': {
|
|
bgcolor: theme.nav.itemActive,
|
|
color: theme.nav.itemActiveText,
|
|
fontWeight: 600,
|
|
borderLeftColor: theme.nav.itemActiveAccent,
|
|
'&:hover': {
|
|
bgcolor: theme.nav.itemActive,
|
|
color: theme.nav.itemActiveText,
|
|
},
|
|
},
|
|
})}
|
|
>
|
|
<ListItemIcon sx={{ minWidth: 36 }}>
|
|
<Icon />
|
|
</ListItemIcon>
|
|
<ListItemText primary={label} />
|
|
</ListItemButton>
|
|
);
|
|
})}
|
|
</List>
|
|
|
|
<UserMenu />
|
|
</Box>
|
|
);
|
|
}
|
|
}
|
|
|
|
class AppSubNav extends PureComponent {
|
|
render() {
|
|
const { activeNav, activeSub, navigate } = this.props;
|
|
const subNav = activeNav ? SUB_NAV[activeNav] : null;
|
|
|
|
if (!subNav?.length) {
|
|
return (
|
|
<Box
|
|
sx={{
|
|
width: SUB_NAV_WIDTH,
|
|
flexShrink: 0,
|
|
}}
|
|
/>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Box
|
|
component="nav"
|
|
aria-label="Unternavigation"
|
|
sx={{
|
|
width: SUB_NAV_WIDTH,
|
|
flexShrink: 0,
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
alignItems: 'center',
|
|
gap: 1.5,
|
|
position: 'sticky',
|
|
top: 0,
|
|
alignSelf: 'flex-start',
|
|
py: 4,
|
|
}}
|
|
>
|
|
{subNav.map(({ segment, label, icon: Icon }) => {
|
|
const active = segment === activeSub;
|
|
|
|
return (
|
|
<ButtonBase
|
|
key={segment || 'index'}
|
|
type="button"
|
|
aria-label={label}
|
|
aria-current={active ? 'page' : undefined}
|
|
disableRipple
|
|
onMouseDown={(e) => e.preventDefault()}
|
|
onClick={() => {
|
|
if (!segment && activeNav === '/artikel') {
|
|
navigate(artikelQueryPath());
|
|
return;
|
|
}
|
|
if (segment === 'kategorien') {
|
|
navigate(defaultCategoryPath());
|
|
return;
|
|
}
|
|
navigate(segment ? `${activeNav}/${segment}` : activeNav);
|
|
}}
|
|
sx={(theme) => ({
|
|
width: SUB_NAV_WIDTH,
|
|
py: 1,
|
|
px: 0.5,
|
|
borderRadius: 2,
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
gap: 0.5,
|
|
color: active
|
|
? theme.palette.primary.main
|
|
: theme.palette.text.secondary,
|
|
bgcolor: active ? theme.palette.action.selected : 'transparent',
|
|
'&:hover': {
|
|
bgcolor: theme.palette.action.hover,
|
|
color: theme.palette.primary.main,
|
|
},
|
|
})}
|
|
>
|
|
<Icon sx={{ fontSize: 32 }} />
|
|
{label ? (
|
|
<Typography
|
|
variant="caption"
|
|
noWrap
|
|
sx={{
|
|
fontSize: '0.68rem',
|
|
fontWeight: 500,
|
|
lineHeight: 1.2,
|
|
textAlign: 'center',
|
|
maxWidth: '100%',
|
|
}}
|
|
>
|
|
{label}
|
|
</Typography>
|
|
) : null}
|
|
</ButtonBase>
|
|
);
|
|
})}
|
|
</Box>
|
|
);
|
|
}
|
|
}
|
|
|
|
export default function AppShell() {
|
|
const navigate = useNavigate();
|
|
const { pathname } = useLocation();
|
|
const activeNav = resolveActiveNav(pathname);
|
|
const activeSub = activeNav ? resolveActiveSub(pathname, activeNav) : null;
|
|
|
|
return (
|
|
<Box sx={{ display: 'flex', minHeight: '100vh', bgcolor: 'background.default' }}>
|
|
<AppSidebar activeNav={activeNav} navigate={navigate} />
|
|
<Box
|
|
component="main"
|
|
sx={(theme) => ({
|
|
flexGrow: 1,
|
|
display: 'flex',
|
|
gap: 2,
|
|
py: 4,
|
|
px: 3,
|
|
bgcolor: theme.nav.mainBackground,
|
|
minWidth: 0,
|
|
})}
|
|
>
|
|
<AppSubNav
|
|
activeNav={activeNav}
|
|
activeSub={activeSub}
|
|
navigate={navigate}
|
|
/>
|
|
<Box sx={{ flexGrow: 1, minWidth: 0 }}>
|
|
<Outlet />
|
|
</Box>
|
|
</Box>
|
|
</Box>
|
|
);
|
|
}
|