2024-05-02 14:16:04 +02:00
|
|
|
import React, {useCallback, useContext, useEffect, useState} from 'react';
|
|
|
|
import {Link, useNavigate} from "react-router-dom";
|
2022-12-01 01:28:38 +01:00
|
|
|
import {LocaleContext} from "shared/locale";
|
2024-05-02 14:16:04 +02:00
|
|
|
import {
|
2024-05-02 15:06:00 +02:00
|
|
|
Box, Divider,
|
2024-05-02 14:16:04 +02:00
|
|
|
IconButton, List, ListItem, ListItemButton, ListItemIcon, ListItemText,
|
|
|
|
Select, Drawer,
|
2024-05-02 15:06:00 +02:00
|
|
|
styled, MenuItem, Menu, ThemeProvider, CssBaseline,
|
2024-05-02 14:16:04 +02:00
|
|
|
} from "@mui/material";
|
|
|
|
import { Dropdown } from '@mui/base/Dropdown';
|
|
|
|
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
|
|
|
|
import ChevronLeftIcon from '@mui/icons-material/ChevronLeft';
|
2024-04-14 20:31:16 +02:00
|
|
|
import ProfilePicture from "shared/elements/profile-picture";
|
2024-05-02 14:16:04 +02:00
|
|
|
import {Dns, Groups, People, QueryStats, Security, Settings, Route, ArrowBack, Translate} from "@mui/icons-material";
|
|
|
|
import useCurrentPath from "shared/hooks/current-path";
|
|
|
|
|
|
|
|
const drawerWidth = 240;
|
2024-04-06 11:52:22 +02:00
|
|
|
|
|
|
|
const ProfileLink = styled(Link)((props) => ({
|
|
|
|
"& > div": {
|
|
|
|
width: 30,
|
|
|
|
height: 30,
|
2024-05-02 14:16:04 +02:00
|
|
|
justifySelf: "center",
|
2024-04-06 11:52:22 +02:00
|
|
|
},
|
2024-05-02 14:16:04 +02:00
|
|
|
color: "inherit",
|
|
|
|
fontWeight: "bold",
|
2024-04-06 11:52:22 +02:00
|
|
|
marginTop: props.theme.spacing(1),
|
|
|
|
display: "grid",
|
2024-05-02 14:16:04 +02:00
|
|
|
gridTemplateColumns: "35px auto",
|
|
|
|
"& > span": {
|
|
|
|
alignSelf: "center",
|
|
|
|
marginLeft: props.theme.spacing(1)
|
|
|
|
},
|
|
|
|
}));
|
|
|
|
|
|
|
|
const DrawerHeader = styled('div')(({ theme }) => ({
|
|
|
|
display: 'flex',
|
|
|
|
alignItems: 'center',
|
|
|
|
justifyContent: "flex-start",
|
|
|
|
padding: theme.spacing(0, 1),
|
|
|
|
...theme.mixins.toolbar,
|
|
|
|
"& > button": {
|
|
|
|
display: 'flex',
|
|
|
|
marginLeft: "auto",
|
|
|
|
},
|
|
|
|
"& > img": {
|
|
|
|
width: 30,
|
|
|
|
height: 30,
|
|
|
|
},
|
2024-04-06 11:52:22 +02:00
|
|
|
"& > span": {
|
2024-05-02 14:16:04 +02:00
|
|
|
marginLeft: theme.spacing(2),
|
|
|
|
fontSize: "1.5em",
|
2024-04-06 11:52:22 +02:00
|
|
|
}
|
|
|
|
}));
|
2022-11-29 14:17:11 +01:00
|
|
|
|
2024-05-02 14:16:04 +02:00
|
|
|
const openedMixin = (theme) => ({
|
|
|
|
width: drawerWidth,
|
|
|
|
transition: theme.transitions.create('width', {
|
|
|
|
easing: theme.transitions.easing.sharp,
|
|
|
|
duration: theme.transitions.duration.enteringScreen,
|
|
|
|
}),
|
|
|
|
overflowX: 'hidden',
|
|
|
|
});
|
|
|
|
|
|
|
|
const closedMixin = (theme) => ({
|
|
|
|
transition: theme.transitions.create('width', {
|
|
|
|
easing: theme.transitions.easing.sharp,
|
|
|
|
duration: theme.transitions.duration.leavingScreen,
|
|
|
|
}),
|
|
|
|
overflowX: 'hidden',
|
|
|
|
width: `calc(${theme.spacing(7)} + 1px)`,
|
|
|
|
[theme.breakpoints.up('sm')]: {
|
|
|
|
width: `calc(${theme.spacing(8)} + 1px)`,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
const StyledDrawer = styled(Drawer, { shouldForwardProp: (prop) => prop !== 'open' })(
|
|
|
|
({ theme, open }) => ({
|
|
|
|
width: drawerWidth,
|
|
|
|
flexShrink: 0,
|
|
|
|
whiteSpace: 'nowrap',
|
|
|
|
boxSizing: 'border-box',
|
|
|
|
...(open && {
|
|
|
|
...openedMixin(theme),
|
|
|
|
'& .MuiDrawer-paper': openedMixin(theme),
|
|
|
|
}),
|
|
|
|
...(!open && {
|
|
|
|
...closedMixin(theme),
|
|
|
|
'& .MuiDrawer-paper': closedMixin(theme),
|
|
|
|
}),
|
|
|
|
}),
|
|
|
|
);
|
|
|
|
|
2022-11-29 14:17:11 +01:00
|
|
|
export default function Sidebar(props) {
|
|
|
|
|
2024-05-02 15:06:00 +02:00
|
|
|
const {api, showDialog, theme, children, ...other} = props;
|
|
|
|
|
2024-05-02 14:16:04 +02:00
|
|
|
const {translate: L, currentLocale, setLanguageByCode} = useContext(LocaleContext);
|
|
|
|
const [languages, setLanguages] = useState(null);
|
|
|
|
const [fetchLanguages, setFetchLanguages] = useState(true);
|
|
|
|
const [drawerOpen, setDrawerOpen] = useState(window.screen.width >= 1000);
|
|
|
|
const [anchorEl, setAnchorEl] = useState(null);
|
|
|
|
const navigate = useNavigate();
|
|
|
|
const currentPath = useCurrentPath();
|
2022-11-29 14:17:11 +01:00
|
|
|
|
2022-12-01 01:28:38 +01:00
|
|
|
const onLogout = useCallback(() => {
|
|
|
|
api.logout().then(obj => {
|
2022-11-29 14:17:11 +01:00
|
|
|
if (obj.success) {
|
|
|
|
document.location = "/admin";
|
|
|
|
} else {
|
2022-12-01 01:28:38 +01:00
|
|
|
showDialog("Error logging out: " + obj.msg, "Error logging out");
|
2022-11-29 14:17:11 +01:00
|
|
|
}
|
|
|
|
});
|
2022-12-01 01:28:38 +01:00
|
|
|
}, [api, showDialog]);
|
2022-11-29 14:17:11 +01:00
|
|
|
|
2024-05-02 14:16:04 +02:00
|
|
|
const onSetLanguage = useCallback((code) => {
|
|
|
|
setLanguageByCode(api, code).then((res) => {
|
|
|
|
if (!res.success) {
|
|
|
|
showDialog(res.msg, L("general.error_language_set"));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}, [api, showDialog]);
|
|
|
|
|
|
|
|
const onFetchLanguages = useCallback((force = false) => {
|
|
|
|
if (force || fetchLanguages) {
|
|
|
|
setFetchLanguages(false);
|
|
|
|
api.getLanguages().then((res) => {
|
|
|
|
if (res.success) {
|
|
|
|
setLanguages(res.languages);
|
|
|
|
} else {
|
|
|
|
setLanguages({});
|
|
|
|
showDialog(res.msg, L("general.error_language_fetch"));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}, [api, fetchLanguages, showDialog]);
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
onFetchLanguages();
|
|
|
|
}, []);
|
|
|
|
|
2022-11-29 14:17:11 +01:00
|
|
|
const menuItems = {
|
|
|
|
"dashboard": {
|
2022-12-01 01:28:38 +01:00
|
|
|
"name": "admin.dashboard",
|
2024-05-02 14:16:04 +02:00
|
|
|
"icon": <QueryStats />
|
2022-11-29 14:17:11 +01:00
|
|
|
},
|
|
|
|
"users": {
|
2024-03-27 20:50:57 +01:00
|
|
|
"name": "admin.users",
|
2024-05-02 14:16:04 +02:00
|
|
|
"icon": <People />
|
2022-11-29 14:17:11 +01:00
|
|
|
},
|
2024-03-27 20:50:57 +01:00
|
|
|
"groups": {
|
|
|
|
"name": "admin.groups",
|
2024-05-02 14:16:04 +02:00
|
|
|
"icon": <Groups />
|
2024-03-27 20:50:57 +01:00
|
|
|
},
|
2024-03-28 11:56:17 +01:00
|
|
|
"routes": {
|
2022-12-01 01:28:38 +01:00
|
|
|
"name": "admin.page_routes",
|
2024-05-02 14:16:04 +02:00
|
|
|
"icon": <Route />
|
2022-11-29 14:17:11 +01:00
|
|
|
},
|
|
|
|
"settings": {
|
2022-12-01 01:28:38 +01:00
|
|
|
"name": "admin.settings",
|
2024-05-02 14:16:04 +02:00
|
|
|
"icon": <Settings />
|
2022-11-29 14:17:11 +01:00
|
|
|
},
|
2024-03-29 18:44:31 +01:00
|
|
|
"permissions": {
|
2024-03-27 13:05:37 +01:00
|
|
|
"name": "admin.acl",
|
2024-05-02 14:16:04 +02:00
|
|
|
"icon": <Security />
|
2024-03-27 13:05:37 +01:00
|
|
|
},
|
2022-11-29 14:17:11 +01:00
|
|
|
"logs": {
|
2022-12-01 01:28:38 +01:00
|
|
|
"name": "admin.logs",
|
2024-05-02 14:16:04 +02:00
|
|
|
"icon": <Dns />
|
2022-11-29 14:17:11 +01:00
|
|
|
},
|
|
|
|
};
|
2024-05-02 14:16:04 +02:00
|
|
|
|
|
|
|
const NavbarItem = (props) => <ListItem disablePadding sx={{ display: 'block' }}>
|
|
|
|
<ListItemButton onClick={props.onClick} selected={props.active} sx={{
|
|
|
|
minHeight: 48,
|
|
|
|
justifyContent: drawerOpen ? 'initial' : 'center',
|
|
|
|
px: 2.5,
|
|
|
|
}}>
|
|
|
|
<ListItemIcon sx={{
|
|
|
|
minWidth: 0,
|
|
|
|
mr: drawerOpen ? 2 : 'auto',
|
|
|
|
justifyContent: 'center',
|
|
|
|
}}>
|
|
|
|
{props.icon}
|
|
|
|
</ListItemIcon>
|
|
|
|
<ListItemText primary={L(props.name)} sx={{ display: drawerOpen ? "block" : "none" }} />
|
|
|
|
</ListItemButton>
|
|
|
|
</ListItem>
|
|
|
|
|
2022-11-29 14:17:11 +01:00
|
|
|
let li = [];
|
2024-05-02 14:16:04 +02:00
|
|
|
for (const [id, menuItem] of Object.entries(menuItems)) {
|
|
|
|
const match= /^\/admin\/(.*)$/.exec(currentPath);
|
|
|
|
const active = match?.length >= 2 && match[1] === id;
|
|
|
|
li.push(<NavbarItem key={id} {...menuItem} active={active} onClick={() => navigate(`/admin/${id}`)} />);
|
2022-11-29 14:17:11 +01:00
|
|
|
}
|
|
|
|
|
2024-05-02 14:16:04 +02:00
|
|
|
li.push(<NavbarItem key={"logout"} name={"general.logout"} icon={<ArrowBack />} onClick={onLogout}/>);
|
|
|
|
|
2024-05-02 15:06:00 +02:00
|
|
|
return <ThemeProvider theme={theme}>
|
2024-05-02 14:16:04 +02:00
|
|
|
<CssBaseline />
|
2024-05-02 15:06:00 +02:00
|
|
|
<Box sx={{ display: 'flex' }} {...other}>
|
|
|
|
<StyledDrawer variant={"permanent"} open={drawerOpen}>
|
|
|
|
<DrawerHeader>
|
|
|
|
{drawerOpen && <>
|
|
|
|
<img src={"/img/icons/logo.png"} alt={"Logo"} />
|
|
|
|
<span>WebBase</span>
|
|
|
|
</>}
|
|
|
|
<IconButton onClick={() => setDrawerOpen(!drawerOpen)}>
|
|
|
|
{drawerOpen ? <ChevronLeftIcon/> : <ChevronRightIcon/>}
|
|
|
|
</IconButton>
|
|
|
|
</DrawerHeader>
|
|
|
|
<Divider/>
|
|
|
|
<ListItem sx={{display: 'block'}}>
|
|
|
|
<Box sx={{opacity: drawerOpen ? 1 : 0}}>{L("account.logged_in_as")}:</Box>
|
|
|
|
<ProfileLink to={"/admin/profile"}>
|
|
|
|
<ProfilePicture user={api.user}/>
|
|
|
|
{drawerOpen && <span>{api.user?.name || L("account.not_logged_in")}</span>}
|
|
|
|
</ProfileLink>
|
|
|
|
</ListItem>
|
|
|
|
<Divider/>
|
|
|
|
<List>
|
|
|
|
{li}
|
|
|
|
</List>
|
|
|
|
<Divider/>
|
|
|
|
<ListItem sx={{display: 'block'}}>
|
|
|
|
{ drawerOpen ?
|
|
|
|
<Select native value={currentLocale} size={"small"} fullWidth={true}
|
|
|
|
onChange={e => onSetLanguage(e.target.value)}>
|
|
|
|
{Object.values(languages || {}).map(language =>
|
|
|
|
<option key={language.code} value={language.code}>
|
|
|
|
{language.name}
|
|
|
|
</option>)
|
|
|
|
}
|
|
|
|
</Select>
|
|
|
|
: <ListItemButton sx={{
|
|
|
|
minHeight: 48,
|
|
|
|
justifyContent: 'center',
|
|
|
|
px: 2.5,
|
|
|
|
}}>
|
|
|
|
<Dropdown>
|
|
|
|
<ListItemIcon onClick={e => setAnchorEl(e.currentTarget)} sx={{
|
|
|
|
minWidth: 0,
|
|
|
|
mr: 'auto',
|
|
|
|
justifyContent: 'center',
|
|
|
|
}}>
|
|
|
|
<Translate />
|
|
|
|
</ListItemIcon>
|
|
|
|
<Menu open={!!anchorEl}
|
|
|
|
anchorEl={anchorEl}
|
|
|
|
onClose={() => setAnchorEl(null)}
|
|
|
|
onClick={() => setAnchorEl(null)}
|
|
|
|
transformOrigin={{ horizontal: 'left', vertical: 'bottom' }}
|
|
|
|
anchorOrigin={{ horizontal: 'left', vertical: 'bottom' }}>
|
|
|
|
{Object.values(languages || {}).map(language =>
|
|
|
|
<MenuItem key={language.code} onClick={() => onSetLanguage(language.code)}>
|
|
|
|
{language.name}
|
|
|
|
</MenuItem>)
|
|
|
|
}
|
|
|
|
</Menu>
|
|
|
|
</Dropdown>
|
|
|
|
</ListItemButton>
|
|
|
|
}
|
|
|
|
</ListItem>
|
|
|
|
</StyledDrawer>
|
|
|
|
<Box component="main" sx={{flexGrow: 1, p: 1}}>
|
|
|
|
{children}
|
|
|
|
</Box>
|
2024-05-02 14:16:04 +02:00
|
|
|
</Box>
|
2024-05-02 15:06:00 +02:00
|
|
|
</ThemeProvider>
|
2022-11-29 14:17:11 +01:00
|
|
|
}
|