CFE-1 create project from the tamplate project and config project
This commit is contained in:
56
src/layouts/AppLayout.jsx
Normal file
56
src/layouts/AppLayout.jsx
Normal file
@@ -0,0 +1,56 @@
|
||||
import theme from "@/core/utils/theme";
|
||||
import useLanguage from "@/lib/app/hooks/useLanguage";
|
||||
import useLoading from "@/lib/app/hooks/useLoading";
|
||||
import useUser from "@/lib/app/hooks/useUser";
|
||||
import NextNProgress from "nextjs-progressbar";
|
||||
import {useEffect} from "react";
|
||||
import {ToastContainer} from "react-toastify";
|
||||
import useDirection from "@/lib/app/hooks/useDirection";
|
||||
import "react-toastify/dist/ReactToastify.css";
|
||||
import NetworkComponent from "@/core/components/NetworkComponent";
|
||||
import GlobalHead from "@/core/components/GlobalHead";
|
||||
|
||||
function AppLayout({children, isBot}) {
|
||||
const {languageIsReady} = useLanguage();
|
||||
const {setLoadingPage} = useLoading();
|
||||
const {userChangedLanguage, token, isAuth} = useUser();
|
||||
const {directionApp} = useDirection();
|
||||
|
||||
useEffect(() => {
|
||||
if (languageIsReady) {
|
||||
if (token) {
|
||||
if (isAuth) {
|
||||
if (userChangedLanguage) {
|
||||
setLoadingPage(true);
|
||||
return;
|
||||
}
|
||||
setLoadingPage(false);
|
||||
return;
|
||||
}
|
||||
setLoadingPage(true);
|
||||
return;
|
||||
}
|
||||
setLoadingPage(false);
|
||||
return;
|
||||
}
|
||||
setLoadingPage(true);
|
||||
}, [languageIsReady, token, isAuth, userChangedLanguage]);
|
||||
|
||||
if (!isBot) {
|
||||
if (userChangedLanguage) return;
|
||||
if (!languageIsReady) return;
|
||||
}
|
||||
|
||||
return (<>
|
||||
<GlobalHead/>
|
||||
<NextNProgress
|
||||
color={theme.palette.secondary.dark}
|
||||
options={{showSpinner: false}}
|
||||
/>
|
||||
<ToastContainer position={directionApp === "ltr" ? "top-left" : "top-right"} rtl={directionApp === 'rtl'}/>
|
||||
<NetworkComponent/>
|
||||
{children}
|
||||
</>);
|
||||
}
|
||||
|
||||
export default AppLayout;
|
||||
18
src/layouts/CenterLayout.jsx
Normal file
18
src/layouts/CenterLayout.jsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import {Fade, Stack} from "@mui/material";
|
||||
|
||||
const CenterLayout = (props) => {
|
||||
return (
|
||||
<Fade in={true}>
|
||||
<Stack
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
spacing={props?.spacing}
|
||||
sx={{flex: 1, ...props?.sx, py: 3}}
|
||||
>
|
||||
{props.children}
|
||||
</Stack>
|
||||
</Fade>
|
||||
);
|
||||
};
|
||||
|
||||
export default CenterLayout;
|
||||
21
src/layouts/FullPageLayout.jsx
Normal file
21
src/layouts/FullPageLayout.jsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import {Stack} from "@mui/material";
|
||||
|
||||
const FullPageLayout = (props) => {
|
||||
return (
|
||||
<Stack
|
||||
spacing={props?.spacing}
|
||||
direction={props?.direction}
|
||||
sx={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
overflowY: "scroll",
|
||||
overflowX: "scroll",
|
||||
...props?.sx,
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default FullPageLayout;
|
||||
73
src/layouts/MuiLayout.jsx
Normal file
73
src/layouts/MuiLayout.jsx
Normal file
@@ -0,0 +1,73 @@
|
||||
import NoSsrHandler from "@/core/components/NoSsrHandler";
|
||||
import {createEmotionCacheRtl} from "@/core/utils/createEmotionCache";
|
||||
import themeRtl from "@/core/utils/theme-rtl";
|
||||
import {CacheProvider} from "@emotion/react";
|
||||
import {GlobalStyles} from "@mui/material";
|
||||
import CssBaseline from "@mui/material/CssBaseline";
|
||||
import {ThemeProvider} from "@mui/material/styles";
|
||||
import Head from "next/head";
|
||||
|
||||
const clientSideEmotionCacheRtl = createEmotionCacheRtl();
|
||||
|
||||
const MuiLayout = ({children, isBot}) => {
|
||||
const emotionCache = clientSideEmotionCacheRtl;
|
||||
const theme = themeRtl;
|
||||
|
||||
return (
|
||||
<NoSsrHandler isBot={isBot}>
|
||||
<CacheProvider value={emotionCache}>
|
||||
<Head>
|
||||
<meta name="viewport" content="initial-scale=1, width=device-width"/>
|
||||
</Head>
|
||||
<ThemeProvider theme={theme}>
|
||||
<GlobalStyles
|
||||
styles={{
|
||||
"*:not(.MuiTableContainer-root)::-webkit-scrollbar": {
|
||||
display: "none",
|
||||
},
|
||||
"*::-webkit-scrollbar": {
|
||||
height: "8px",
|
||||
},
|
||||
"*::-webkit-scrollbar-thumb": {
|
||||
background: `${theme.palette.primary.light}80`,
|
||||
borderRadius: "3px",
|
||||
},
|
||||
"*:not(.MuiTableContainer-root)": {
|
||||
scrollbarWidth: "thin",
|
||||
scrollbarColor: "transparent transparent",
|
||||
},
|
||||
|
||||
"*": {
|
||||
scrollbarWidth: "thin",
|
||||
scrollbarColor: `${theme.palette.primary.light}80 transparent`,
|
||||
},
|
||||
|
||||
"*::-moz-scrollbar-thumb": {
|
||||
backgroundColor: `${theme.palette.primary.light}80`,
|
||||
},
|
||||
[`@media (max-width: ${theme.breakpoints.values.sm}px)`]: {
|
||||
"*::-webkit-scrollbar": {
|
||||
height: "4px",
|
||||
},
|
||||
},
|
||||
|
||||
body: {
|
||||
width: "100vw",
|
||||
height: "100vh",
|
||||
},
|
||||
"#__next": {
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
<CssBaseline/>
|
||||
{children}
|
||||
</ThemeProvider>
|
||||
</CacheProvider>
|
||||
</NoSsrHandler>
|
||||
);
|
||||
};
|
||||
|
||||
export default MuiLayout;
|
||||
25
src/layouts/ScrollableLayout.jsx
Normal file
25
src/layouts/ScrollableLayout.jsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import {Box, Fade} from "@mui/material";
|
||||
|
||||
const ScrollableLayout = (props) => {
|
||||
const overflowY = props?.y || "hidden";
|
||||
const overflowX = props?.x || "hidden";
|
||||
|
||||
return (
|
||||
<Fade in={true}>
|
||||
<Box
|
||||
spacing={props?.spacing}
|
||||
sx={{
|
||||
width: "auto",
|
||||
height: "auto",
|
||||
overflowY: overflowY,
|
||||
overflowX: overflowX,
|
||||
...props?.sx,
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
</Box>
|
||||
</Fade>
|
||||
);
|
||||
};
|
||||
|
||||
export default ScrollableLayout;
|
||||
19
src/layouts/dashboardLayouts/breadcrumbs/BreadcrumbItem.jsx
Normal file
19
src/layouts/dashboardLayouts/breadcrumbs/BreadcrumbItem.jsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import LinkRouting from "@/core/components/LinkRouting";
|
||||
import {Typography} from "@mui/material";
|
||||
import {useTranslations} from "next-intl";
|
||||
|
||||
export default function BreadcrumbItem(props) {
|
||||
const t = useTranslations();
|
||||
const isLast = props.index === props.RouterArray.length - 1;
|
||||
const url = `/${props.RouterArray.slice(0, props.index + 1).join("/")}`;
|
||||
|
||||
return isLast ? (
|
||||
<Typography variant="body2" color="primary">
|
||||
{t("sidebar." + props.label)}
|
||||
</Typography>
|
||||
) : (
|
||||
<LinkRouting underline="hover" color="inherit" passHref href={url}>
|
||||
<Typography variant="body2">{t("sidebar." + props.label)}</Typography>
|
||||
</LinkRouting>
|
||||
);
|
||||
}
|
||||
49
src/layouts/dashboardLayouts/breadcrumbs/index.jsx
Normal file
49
src/layouts/dashboardLayouts/breadcrumbs/index.jsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import {useRouter} from "next/router";
|
||||
import {Box, Breadcrumbs} from "@mui/material";
|
||||
import {useTheme} from "@mui/material/styles";
|
||||
import {NavigateBefore, NavigateNext} from "@mui/icons-material";
|
||||
import BreadcrumbItem from "./BreadcrumbItem";
|
||||
|
||||
const BreadCrumbs = (props) => {
|
||||
const {isVisible} = props;
|
||||
const theme = useTheme();
|
||||
const router = useRouter();
|
||||
|
||||
if (!isVisible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const {pathname} = router;
|
||||
const RouterArray = pathname.split("/").filter((segment) => segment !== "");
|
||||
|
||||
if (RouterArray.length === 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Box p={3} component="span">
|
||||
<Breadcrumbs
|
||||
maxItems={2}
|
||||
separator={
|
||||
theme.direction === "ltr" ? (
|
||||
<NavigateNext fontSize="small"/>
|
||||
) : (
|
||||
<NavigateBefore fontSize="small"/>
|
||||
)
|
||||
}
|
||||
aria-label="breadcrumb"
|
||||
>
|
||||
{RouterArray.map((segment, index) => (
|
||||
<BreadcrumbItem
|
||||
RouterArray={RouterArray}
|
||||
label={segment}
|
||||
key={segment}
|
||||
index={index}
|
||||
/>
|
||||
))}
|
||||
</Breadcrumbs>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default BreadCrumbs;
|
||||
18
src/layouts/dashboardLayouts/header/ProfileData.jsx
Normal file
18
src/layouts/dashboardLayouts/header/ProfileData.jsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import {Avatar, Stack, Typography} from "@mui/material";
|
||||
import useUser from "@/lib/app/hooks/useUser";
|
||||
|
||||
export default function ProfileData() {
|
||||
const {user} = useUser();
|
||||
return (
|
||||
<Stack alignItems="center" spacing={2} sx={{p: 3}}>
|
||||
<Avatar
|
||||
sx={{width: "80px", height: "80px"}}
|
||||
alt="User Image"
|
||||
src={user.avatar}
|
||||
/>
|
||||
<Typography sx={{fontSize: 15, fontWeight: 600}} textAlign="center">
|
||||
{user.username}
|
||||
</Typography>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
62
src/layouts/dashboardLayouts/header/ProfileMenu.jsx
Normal file
62
src/layouts/dashboardLayouts/header/ProfileMenu.jsx
Normal file
@@ -0,0 +1,62 @@
|
||||
import {Avatar, IconButton, Menu, Tooltip} from "@mui/material";
|
||||
import {useState} from "react";
|
||||
import ProfileData from "./ProfileData";
|
||||
import ProfileOptions from "./ProfileOptions";
|
||||
import {useTranslations} from "next-intl";
|
||||
import useUser from "@/lib/app/hooks/useUser";
|
||||
|
||||
function ProfileMenu() {
|
||||
const t = useTranslations();
|
||||
const [anchorElUser, setAnchorElUser] = useState(null);
|
||||
const {user} = useUser();
|
||||
|
||||
const handleOpenUserMenu = (event) => {
|
||||
setAnchorElUser(event.currentTarget);
|
||||
};
|
||||
const handleCloseUserMenu = () => {
|
||||
setAnchorElUser(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tooltip title={t("header.open_profile")} arrow>
|
||||
<IconButton onClick={handleOpenUserMenu} sx={{p: 0}}>
|
||||
<Avatar
|
||||
sx={{
|
||||
width: 24,
|
||||
height: 24,
|
||||
backgroundColor: "#fff",
|
||||
color: "primary.main",
|
||||
}}
|
||||
alt="User Image"
|
||||
src={user.avatar}
|
||||
/>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Menu
|
||||
MenuListProps={{sx: {py: 0}}}
|
||||
sx={{
|
||||
mt: 6,
|
||||
}}
|
||||
id="menu-appbar"
|
||||
anchorEl={anchorElUser}
|
||||
anchorOrigin={{
|
||||
vertical: "top",
|
||||
horizontal: "right",
|
||||
}}
|
||||
keepMounted
|
||||
transformOrigin={{
|
||||
vertical: "top",
|
||||
horizontal: "right",
|
||||
}}
|
||||
open={Boolean(anchorElUser)}
|
||||
onClose={handleCloseUserMenu}
|
||||
>
|
||||
<ProfileData/>
|
||||
<ProfileOptions handleCloseUserMenu={handleCloseUserMenu}/>
|
||||
</Menu>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default ProfileMenu;
|
||||
49
src/layouts/dashboardLayouts/header/ProfileOptionLogOut.jsx
Normal file
49
src/layouts/dashboardLayouts/header/ProfileOptionLogOut.jsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import {Box, Button, MenuItem, Typography} from "@mui/material";
|
||||
import MeetingRoomIcon from "@mui/icons-material/MeetingRoom";
|
||||
import useUser from "@/lib/app/hooks/useUser";
|
||||
import {useTranslations} from "next-intl";
|
||||
|
||||
export default function ProfileOptionLogOut({handleCloseUserMenu}) {
|
||||
const t = useTranslations();
|
||||
const {clearToken} = useUser();
|
||||
const handleClickLogOut = () => {
|
||||
handleCloseUserMenu();
|
||||
clearToken();
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<MenuItem
|
||||
component={Button}
|
||||
to={{
|
||||
pathname: "/dashboard/logout",
|
||||
}}
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
borderTop: 1,
|
||||
px: 3,
|
||||
py: 1.5,
|
||||
borderColor: "#e1e1e1",
|
||||
textTransform: "unset",
|
||||
}}
|
||||
onClick={handleClickLogOut}
|
||||
>
|
||||
<Box sx={{display: "flex", alignItems: "center", flex: 1}}>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
color: "primary.main",
|
||||
pr: 2,
|
||||
}}
|
||||
>
|
||||
<MeetingRoomIcon/>
|
||||
</Box>
|
||||
<Typography sx={{flex: 1}} textAlign="start">
|
||||
{t("header.logout")}
|
||||
</Typography>
|
||||
</Box>
|
||||
</MenuItem>
|
||||
</>
|
||||
);
|
||||
}
|
||||
49
src/layouts/dashboardLayouts/header/ProfileOptions.jsx
Normal file
49
src/layouts/dashboardLayouts/header/ProfileOptions.jsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import {Box, MenuItem, Typography} from "@mui/material";
|
||||
import {NextLinkComposed} from "@/core/components/LinkRouting";
|
||||
import {useTranslations} from "next-intl";
|
||||
import headerProfileItems from "@/core/data/headerProfileItems";
|
||||
import ProfileOptionLogOut from "./ProfileOptionLogOut";
|
||||
|
||||
export default function ProfileOptions({handleCloseUserMenu}) {
|
||||
const t = useTranslations();
|
||||
|
||||
return (
|
||||
<>
|
||||
{headerProfileItems.map((profile_item) => (
|
||||
<MenuItem
|
||||
component={NextLinkComposed}
|
||||
to={{
|
||||
pathname: profile_item.route,
|
||||
}}
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
borderTop: 1,
|
||||
px: 3,
|
||||
py: 1.5,
|
||||
borderColor: "#e1e1e1",
|
||||
}}
|
||||
key={profile_item.key}
|
||||
onClick={handleCloseUserMenu}
|
||||
>
|
||||
<Box sx={{display: "flex", alignItems: "center", flex: 1}}>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
color: "primary.main",
|
||||
pr: 2,
|
||||
}}
|
||||
>
|
||||
{profile_item.icon}
|
||||
</Box>
|
||||
<Typography sx={{flex: 1}} textAlign="start">
|
||||
{t(profile_item.name)}
|
||||
</Typography>
|
||||
</Box>
|
||||
</MenuItem>
|
||||
))}
|
||||
<ProfileOptionLogOut handleCloseUserMenu={handleCloseUserMenu}/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
72
src/layouts/dashboardLayouts/header/index.jsx
Normal file
72
src/layouts/dashboardLayouts/header/index.jsx
Normal file
@@ -0,0 +1,72 @@
|
||||
import MenuIcon from "@mui/icons-material/Menu";
|
||||
import {AppBar, Box, Container, CssBaseline, IconButton, Stack, Toolbar, useTheme} from "@mui/material";
|
||||
import ProfileMenu from "./ProfileMenu";
|
||||
|
||||
function Header({drawerWidth, handleDrawerToggle}) {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<>
|
||||
<CssBaseline/>
|
||||
<AppBar
|
||||
position="fixed"
|
||||
sx={{
|
||||
width: {md: `calc(100% - ${drawerWidth}px)`},
|
||||
ml: {md: `${drawerWidth}px`},
|
||||
}}
|
||||
>
|
||||
<Container maxWidth="xxl">
|
||||
<Toolbar
|
||||
disableGutters
|
||||
sx={{
|
||||
display: "flex",
|
||||
}}
|
||||
>
|
||||
<Stack direction="row" justifyContent="flex-start" sx={{flex: 1}}>
|
||||
<IconButton
|
||||
color="inherit"
|
||||
aria-label="open drawer"
|
||||
onClick={handleDrawerToggle}
|
||||
edge="start"
|
||||
sx={{display: {md: "none"}, m: 0}}
|
||||
>
|
||||
<MenuIcon/>
|
||||
</IconButton>
|
||||
</Stack>
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="center"
|
||||
sx={{
|
||||
flex: 1,
|
||||
position: "relative",
|
||||
...theme.mixins.toolbar,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
position: "relative",
|
||||
my: 1,
|
||||
width: 56,
|
||||
height: 56,
|
||||
"@media (min-width:600px)": {maxWidth: 64, maxHeight: 64},
|
||||
"@media (min-width:0px)": {
|
||||
"@media (orientation: landscape)": {
|
||||
width: 48,
|
||||
height: 48,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
</Box>
|
||||
</Stack>
|
||||
<Stack direction="row" justifyContent="flex-end" sx={{flex: 1}}>
|
||||
<ProfileMenu/>
|
||||
</Stack>
|
||||
</Toolbar>
|
||||
</Container>
|
||||
</AppBar>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Header;
|
||||
45
src/layouts/dashboardLayouts/index.jsx
Normal file
45
src/layouts/dashboardLayouts/index.jsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import {useState} from "react";
|
||||
import FullPageLayout from "../FullPageLayout";
|
||||
import Header from "./header";
|
||||
import Sidebar from "./sidebar";
|
||||
import {Toolbar} from "@mui/material";
|
||||
import BreadCrumbs from "./breadcrumbs";
|
||||
|
||||
const drawerWidth = 240;
|
||||
|
||||
const DashboardLayouts = (props) => {
|
||||
const {window} = props;
|
||||
const [mobileOpen, setMobileOpen] = useState(false);
|
||||
const container =
|
||||
window !== undefined ? () => window().document.body : undefined;
|
||||
|
||||
const handleDrawerToggle = () => {
|
||||
setMobileOpen(!mobileOpen);
|
||||
};
|
||||
return (
|
||||
<FullPageLayout direction="row">
|
||||
<Header
|
||||
handleDrawerToggle={handleDrawerToggle}
|
||||
drawerWidth={drawerWidth}
|
||||
/>
|
||||
<Sidebar
|
||||
container={container}
|
||||
mobileOpen={mobileOpen}
|
||||
handleDrawerToggle={handleDrawerToggle}
|
||||
drawerWidth={drawerWidth}
|
||||
/>
|
||||
<FullPageLayout
|
||||
component="main"
|
||||
sx={{flexGrow: 1, width: {sm: `calc(100% - ${drawerWidth}px)`}}}
|
||||
>
|
||||
<Toolbar/>
|
||||
<FullPageLayout sx={{mt: 3}}>
|
||||
<BreadCrumbs isVisible={true}/>
|
||||
{props.children}
|
||||
</FullPageLayout>
|
||||
</FullPageLayout>
|
||||
</FullPageLayout>
|
||||
);
|
||||
};
|
||||
|
||||
export default DashboardLayouts;
|
||||
27
src/layouts/dashboardLayouts/sidebar/SidebarDrawer.jsx
Normal file
27
src/layouts/dashboardLayouts/sidebar/SidebarDrawer.jsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import {Divider, Stack, Toolbar, Typography} from "@mui/material";
|
||||
import {useTranslations} from "next-intl";
|
||||
import SidebarList from "./SidebarList";
|
||||
import useUser from "@/lib/app/hooks/useUser";
|
||||
|
||||
const SidebarDrawer = ({handleDrawerToggle}) => {
|
||||
const {user} = useUser()
|
||||
const t = useTranslations();
|
||||
return (
|
||||
<>
|
||||
<Toolbar>
|
||||
<Stack>
|
||||
<Typography variant="h6" sx={{color: "primary.main"}}>
|
||||
{t("app_short_name")}
|
||||
</Typography>
|
||||
<Typography variant="caption">
|
||||
{user.name} | {user.position}
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Toolbar>
|
||||
<Divider/>
|
||||
<SidebarList handleDrawerToggle={handleDrawerToggle}/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SidebarDrawer;
|
||||
72
src/layouts/dashboardLayouts/sidebar/SidebarList.jsx
Normal file
72
src/layouts/dashboardLayouts/sidebar/SidebarList.jsx
Normal file
@@ -0,0 +1,72 @@
|
||||
import {Divider, List} from "@mui/material";
|
||||
import {Fragment, useEffect, useReducer} from "react";
|
||||
import SidebarListItem from "./SidebarListItem";
|
||||
import sidebarMenu from "@/core/data/sidebarMenu";
|
||||
import {useRouter} from "next/router";
|
||||
import useUser from "@/lib/app/hooks/useUser";
|
||||
|
||||
function reducer(state, action) {
|
||||
switch (action.type) {
|
||||
case "COLLAPSE_MENU":
|
||||
return state.map((itemsArr) =>
|
||||
itemsArr.map((item) =>
|
||||
action.key == item.key
|
||||
? {...item, showSubItem: !item.showSubItem}
|
||||
: item
|
||||
)
|
||||
);
|
||||
case "SELECTED":
|
||||
return state.map((itemsArr) =>
|
||||
itemsArr.map((item) => {
|
||||
console.log(item)
|
||||
if (item.type === "page") {
|
||||
if (action.route === item.route)
|
||||
return {...item, selected: true}
|
||||
else
|
||||
return {...item, selected: false}
|
||||
}
|
||||
return {...item}
|
||||
// item.subItem.map((subitem) => {
|
||||
// if (action.route === subitem.route)
|
||||
// return {...item, showSubItem: true, subItem: {...subitem, selected: true}}
|
||||
// else
|
||||
// return {...item, showSubItem: false, subItem: {...subitem, selected: false}}
|
||||
// })
|
||||
}
|
||||
)
|
||||
);
|
||||
default:
|
||||
throw new Error();
|
||||
}
|
||||
}
|
||||
|
||||
export default function SidebarList({handleDrawerToggle}) {
|
||||
const [itemMenu, dispatch] = useReducer(reducer, sidebarMenu);
|
||||
const {user} = useUser();
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
dispatch({type: "SELECTED", route: router.pathname});
|
||||
}, [router.pathname]);
|
||||
|
||||
return (
|
||||
<List>
|
||||
{itemMenu.map((itemArr, index) => (
|
||||
<Fragment key={index}>
|
||||
{itemArr.map((item) =>
|
||||
<Fragment key={item.key}>
|
||||
{(user.permissions.includes(item.permission) || item.permission === "all") &&
|
||||
<SidebarListItem
|
||||
item={item}
|
||||
dispatch={dispatch}
|
||||
handleDrawerToggle={handleDrawerToggle}
|
||||
/>}
|
||||
</Fragment>
|
||||
)}
|
||||
<Divider/>
|
||||
</Fragment>
|
||||
))}
|
||||
</List>
|
||||
|
||||
);
|
||||
}
|
||||
84
src/layouts/dashboardLayouts/sidebar/SidebarListItem.jsx
Normal file
84
src/layouts/dashboardLayouts/sidebar/SidebarListItem.jsx
Normal file
@@ -0,0 +1,84 @@
|
||||
import {NextLinkComposed} from "@/core/components/LinkRouting";
|
||||
import ExpandLess from "@mui/icons-material/ExpandLess";
|
||||
import ExpandMore from "@mui/icons-material/ExpandMore";
|
||||
import {Badge, IconButton, ListItem, ListItemButton, ListItemIcon, ListItemText, Typography,} from "@mui/material";
|
||||
import {useTranslations} from "next-intl";
|
||||
import {Fragment} from "react";
|
||||
import SidebarListSubItem from "./SidebarListSubItem";
|
||||
import useNotification from "@/lib/app/hooks/useNotification";
|
||||
|
||||
const SidebarListItem = ({item, dispatch, handleDrawerToggle}) => {
|
||||
const t = useTranslations();
|
||||
const {notification_count} = useNotification()
|
||||
|
||||
return (
|
||||
<>
|
||||
<ListItem disablePadding secondaryAction={
|
||||
<IconButton>
|
||||
<Badge
|
||||
badgeContent={notification_count ? notification_count[item.name] : 0}
|
||||
color="error"
|
||||
variant="standard"
|
||||
anchorOrigin={{
|
||||
vertical: "top",
|
||||
horizontal: "right",
|
||||
}}
|
||||
/>
|
||||
</IconButton>
|
||||
}>
|
||||
<ListItemButton
|
||||
selected={item.selected}
|
||||
{...(item.type == "page" && {
|
||||
component: NextLinkComposed,
|
||||
to: {
|
||||
pathname: item.route,
|
||||
},
|
||||
})}
|
||||
onClick={() => {
|
||||
if (item.type == "menu") {
|
||||
dispatch({type: "COLLAPSE_MENU", key: item.key});
|
||||
}
|
||||
handleDrawerToggle();
|
||||
}}
|
||||
sx={{
|
||||
minHeight: 48,
|
||||
}}
|
||||
|
||||
>
|
||||
<ListItemIcon
|
||||
sx={{
|
||||
minWidth: 0,
|
||||
justifyContent: "center",
|
||||
color: "primary.main",
|
||||
pr: 2,
|
||||
}}
|
||||
>
|
||||
{item.icon}
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={t(item.key)}
|
||||
secondary={
|
||||
item.secondary !== undefined ? (
|
||||
<Typography variant="caption" color="textSecondary">
|
||||
{t(item.secondary)}
|
||||
</Typography>
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
|
||||
|
||||
{item.type == "menu" &&
|
||||
(item.showSubItem ? <ExpandLess/> : <ExpandMore/>)}
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
{item.subItem && (
|
||||
<SidebarListSubItem
|
||||
item={item}
|
||||
handleDrawerToggle={handleDrawerToggle}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SidebarListItem;
|
||||
71
src/layouts/dashboardLayouts/sidebar/SidebarListSubItem.jsx
Normal file
71
src/layouts/dashboardLayouts/sidebar/SidebarListSubItem.jsx
Normal file
@@ -0,0 +1,71 @@
|
||||
import {NextLinkComposed} from "@/core/components/LinkRouting";
|
||||
import {
|
||||
Badge,
|
||||
Collapse,
|
||||
IconButton,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemButton,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import {useTranslations} from "next-intl";
|
||||
import useNotification from "@/lib/app/hooks/useNotification";
|
||||
|
||||
const SidebarListSubItem = ({item, handleDrawerToggle}) => {
|
||||
const t = useTranslations();
|
||||
const {notification_count} = useNotification()
|
||||
|
||||
return (
|
||||
<Collapse in={item.showSubItem} timeout="auto" mountOnEnter={true} unmountOnExit={true}>
|
||||
<List component="div" disablePadding sx={{bgcolor: "#f6f6f6", pr: 1}}>
|
||||
{item.subItem.map((subitem, index) => (
|
||||
<ListItem key={subitem.key} disablePadding secondaryAction={
|
||||
<IconButton>
|
||||
<Badge
|
||||
badgeContent={notification_count ? notification_count[subitem.name] : 0}
|
||||
color="error"
|
||||
variant="standard"
|
||||
anchorOrigin={{
|
||||
vertical: "top",
|
||||
horizontal: "right",
|
||||
}}
|
||||
/>
|
||||
</IconButton>
|
||||
}>
|
||||
<ListItemButton
|
||||
selected={subitem.selected}
|
||||
component={NextLinkComposed}
|
||||
to={{
|
||||
pathname: subitem.route,
|
||||
}}
|
||||
sx={{
|
||||
minHeight: 48,
|
||||
}}
|
||||
>
|
||||
<ListItemIcon
|
||||
sx={{
|
||||
minWidth: 0,
|
||||
justifyContent: "center",
|
||||
pr: 2,
|
||||
}}
|
||||
>
|
||||
{subitem.icon}
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t(subitem.key)} secondary={
|
||||
subitem.secondary !== undefined ? (
|
||||
<Typography variant="caption" color="textSecondary">
|
||||
{t(subitem.secondary)}
|
||||
</Typography>
|
||||
) : null
|
||||
}/>
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</Collapse>
|
||||
);
|
||||
};
|
||||
|
||||
export default SidebarListSubItem;
|
||||
46
src/layouts/dashboardLayouts/sidebar/index.jsx
Normal file
46
src/layouts/dashboardLayouts/sidebar/index.jsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import {Box, Drawer} from "@mui/material";
|
||||
import SidebarDrawer from "./SidebarDrawer";
|
||||
|
||||
const Sidebar = (props) => {
|
||||
return (
|
||||
<Box
|
||||
component="nav"
|
||||
sx={{width: {md: props.drawerWidth}, flexShrink: {sm: 0}}}
|
||||
aria-label="mailbox folders"
|
||||
>
|
||||
<Drawer
|
||||
container={props.container}
|
||||
variant="temporary"
|
||||
open={props.mobileOpen}
|
||||
onClose={props.handleDrawerToggle}
|
||||
ModalProps={{
|
||||
keepMounted: true,
|
||||
}}
|
||||
sx={{
|
||||
display: {xs: "block", md: "none"},
|
||||
"& .MuiDrawer-paper": {
|
||||
boxSizing: "border-box",
|
||||
width: props.drawerWidth,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<SidebarDrawer handleDrawerToggle={props.handleDrawerToggle}/>
|
||||
</Drawer>
|
||||
<Drawer
|
||||
variant="permanent"
|
||||
sx={{
|
||||
display: {xs: "none", md: "block"},
|
||||
"& .MuiDrawer-paper": {
|
||||
boxSizing: "border-box",
|
||||
width: props.drawerWidth,
|
||||
},
|
||||
}}
|
||||
open
|
||||
>
|
||||
<SidebarDrawer handleDrawerToggle={props.handleDrawerToggle}/>
|
||||
</Drawer>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default Sidebar;
|
||||
Reference in New Issue
Block a user