CFE-1 create project from the tamplate project and config project

This commit is contained in:
AmirHossein Mahmoodi
2023-09-11 11:42:24 +03:30
parent a95709a583
commit 45c9fd74ef
151 changed files with 5459 additions and 0 deletions

56
src/layouts/AppLayout.jsx Normal file
View 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;

View 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;

View 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
View 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;

View 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;

View 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>
);
}

View 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;

View 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>
);
}

View 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;

View 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>
</>
);
}

View 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}/>
</>
);
}

View 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;

View 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;

View 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;

View 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>
);
}

View 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;

View 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;

View 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;