merge and resolve conflict

This commit is contained in:
2024-07-11 10:47:01 +03:30
42 changed files with 1243 additions and 338 deletions

View File

@@ -0,0 +1,2 @@
NEXT_PUBLIC_VERSION="0.2.0"
NEXT_PUBLIC_API_URL="https://rms.witel.ir"

View File

@@ -1,3 +1,3 @@
{
"extends": "next/core-web-vitals"
"extends": ["next/core-web-vitals","prettier"]
}

View File

@@ -3,6 +3,9 @@ const isProd = process.env.NODE_ENV === 'production'
/** @type {import('next').NextConfig} */
const nextConfig = {
assetPrefix: isProd ? '/v3' : undefined,
images: {
path: isProd ? '/v3/_next/image' : undefined,
}
};
export default nextConfig;

View File

@@ -7,7 +7,8 @@
"build": "next build",
"start": "next start",
"lint": "next lint",
"format": "prettier --write \"src/**/*.{js,jsx,ts,tsx,html,css,scss}\""
"format": "prettier --write \"src/**/*.{js,jsx,ts,tsx,html,css,scss}\"",
"build:publish": "npm i && npm run build && pm2 restart ecosystem.config.js"
},
"dependencies": {
"@emotion/cache": "^11.11.0",
@@ -17,6 +18,7 @@
"@mui/material": "^5.15.6",
"@mui/material-nextjs": "^5.15.6",
"@mui/x-date-pickers": "^6.19.5",
"axios": "^1.7.2",
"date-fns": "^3.3.1",
"date-fns-jalali": "^2.13.0-0",
"dayjs": "^1.11.10",
@@ -36,6 +38,7 @@
"devDependencies": {
"eslint": "^8",
"eslint-config-next": "14.1.0",
"eslint-config-prettier": "^9.1.0",
"prettier": "^3.3.2"
}
}

View File

@@ -1,6 +1,8 @@
@import "fontiran";
@import "react-toastify/dist/ReactToastify.css";
.filter-toast {
box-shadow: rgba(50, 50, 93, 0.25) 0px 13px 27px -5px, rgba(0, 0, 0, 0.3) 0px 8px 16px -8px;
box-shadow: rgba(50, 50, 93, 0.25) 0px 13px 27px -5px,
rgba(0, 0, 0, 0.3) 0px 8px 16px -8px;
background-color: #d0dfe8;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
public/images/rmsLogo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -0,0 +1,10 @@
import InquiryPrivacyFencingPage from "@/components/dashboard/inquiryPrivacyFencing";
const Page = () => {
return (
<InquiryPrivacyFencingPage />
);
};
export default Page;

View File

@@ -1,5 +1,9 @@
const Page = () => {
return <div>dashboard</div>;
return (
<>
</>
);
};
export default Page;

View File

@@ -1,17 +0,0 @@
import { Box, Stack } from "@mui/material";
import HeaderWithLogo from "@/components/layouts/dashboard/headerWithLogo";
import HeaderWithSidebar from "@/components/layouts/dashboard/headerWithSidebar";
import "react-toastify/dist/ReactToastify.css";
const Template = ({ children }) => {
return (
<Stack>
<HeaderWithLogo />
<Box>
<HeaderWithSidebar>{children}</HeaderWithSidebar>
</Box>
</Stack>
);
};
export default Template;

View File

@@ -0,0 +1,12 @@
import WithAuthMiddleware from "@/core/middlewares/withAuth";
import { AuthProvider } from "@/lib/contexts/auth";
const Layout = ({ children }) => {
return (
<AuthProvider>
<WithAuthMiddleware>{children}</WithAuthMiddleware>
</AuthProvider>
);
};
export default Layout;

3
src/app/default.js Normal file
View File

@@ -0,0 +1,3 @@
export default function Default() {
return null;
}

57
src/app/lfwd/page.js Normal file
View File

@@ -0,0 +1,57 @@
"use client";
import { GET_LOGIN_ROUTE } from "@/core/utils/routes";
import useRequest from "@/lib/hooks/useRequest";
import { Button, Stack, Typography, Zoom } from "@mui/material";
import Link from "next/link";
import { useEffect, useState } from "react";
const Page = ({ searchParams }) => {
const { username } = searchParams;
const [login, setLogin] = useState(0);
const request = useRequest();
useEffect(() => {
const login = async () => {
try {
await request(`${GET_LOGIN_ROUTE}?username=${username}`);
setLogin(1);
} catch (error) {
setLogin(2);
}
};
login();
}, []);
const retry = async () => {
setLogin(0);
try {
await request(`${GET_LOGIN_ROUTE}?username=${username}`);
setLogin(1);
} catch (error) {
setLogin(2);
}
};
return (
<Stack alignItems={"center"} justifyContent={"center"} sx={{ height: "100%" }} spacing={2}>
<Typography variant="h5">
{login === 0
? "درحال دریافت مجوز برای ارتباط با سرور..."
: login === 1
? "ارتباط با سرور برقرار شد."
: "ارتباط با سرور برقرار نشد. مشکلی وجود دارد."}
</Typography>
<Zoom in={login === 1}>
<Button component={Link} href="/">
ورود به سامانه
</Button>
</Zoom>
<Zoom in={login === 2}>
<Button onClick={retry}>تلاش مجدد</Button>
</Zoom>
</Stack>
);
};
export default Page;

View File

@@ -1,8 +1,8 @@
"use client";
import HomeComponent from "@/components/home";
import { redirect } from "next/navigation";
function Page() {
return <HomeComponent />;
redirect("/dashboard", "replace");
return null;
}
export default Page;

View File

@@ -2,7 +2,6 @@ import cacheProviderRtl from "@/core/utils/cacheRtl";
import { CssBaseline, ThemeProvider } from "@mui/material";
import theme from "@/core/utils/theme";
import { AppRouterCacheProvider } from "@mui/material-nextjs/v14-appRouter";
import "react-toastify/dist/ReactToastify.css";
import "^/global.scss";
import { ToastContainer } from "react-toastify";
@@ -12,7 +11,8 @@ const Template = ({ children }) => {
<ThemeProvider theme={theme}>
<CssBaseline />
{children}
<ToastContainer rtl containerId="datatable" closeButton={false} />
<ToastContainer rtl containerId="filtering" closeButton={false} />
<ToastContainer rtl containerId="request_data" />
</ThemeProvider>
</AppRouterCacheProvider>
);

View File

@@ -0,0 +1,4 @@
const InquiryPrivacyFencingPage = () => {
return <></>
}
export default InquiryPrivacyFencingPage

View File

@@ -0,0 +1,27 @@
"use client";
import { Stack, Typography } from "@mui/material";
import Image from "next/image";
const HeaderBottom = () => {
return (
<Stack
sx={{
px: 2,
py: 1,
background: (theme) => theme.palette.primary2.main,
}}
spacing={4}
alignItems={"center"}
direction={"row"}
>
<Image alt="" priority src={"/images/rmsLogo.png"} width={65} height={71} />
<Stack alignItems={"center"} spacing={1}>
<Typography variant="h5" sx={{ color: (theme) => theme.palette.primary2.contrastText }}>
سامانه جامع راهداری
</Typography>
<Typography sx={{ color: (theme) => theme.palette.primary2.contrastText }}>rms.rmto.ir</Typography>
</Stack>
</Stack>
);
};
export default HeaderBottom;

View File

@@ -0,0 +1,11 @@
import { Stack } from "@mui/material";
import Image from "next/image";
const HeaderTop = () => {
return (
<Stack sx={{ p: 2 }}>
<Image alt="" priority src={"/images/headerLogo.png"} width={190} height={44} />
</Stack>
);
};
export default HeaderTop;

View File

@@ -1,10 +1,12 @@
import { Box, Stack } from "@mui/material";
import HeaderTop from "./HaederTop";
import HeaderBottom from "./HaederBottom";
const HeaderWithLogo = () => {
return (
<Stack>
<Box>header1</Box>
<Box>header2</Box>
<HeaderTop />
<HeaderBottom />
</Stack>
);
};

View File

@@ -0,0 +1,4 @@
const HeaderMenu = () => {
return <>منو</>;
};
export default HeaderMenu;

View File

@@ -1,12 +1,13 @@
"use client";
import { useState } from "react";
import { Box, Drawer, IconButton, Paper, styled, Toolbar, Typography } from "@mui/material";
import MuiAppBar from "@mui/material/AppBar";
import MenuIcon from "@mui/icons-material/Menu";
import ChevronRightIcon from "@mui/icons-material/ChevronRight";
import SidebarMenu from "@/core/components/SidebarMenu";
import ChevronRightIcon from "@mui/icons-material/ChevronRight";
import MenuIcon from "@mui/icons-material/Menu";
import { Box, Drawer, IconButton, Paper, styled, Toolbar } from "@mui/material";
import MuiAppBar from "@mui/material/AppBar";
import { useState } from "react";
import HeaderMenu from "./HeaderMenu";
const drawerWidth = 260;
const drawerWidth = 300;
const Main = styled(Paper, { shouldForwardProp: (prop) => prop !== "open" })(({ theme, open }) => ({
flexGrow: 1,
@@ -44,25 +45,24 @@ const AppBar = styled(MuiAppBar, {
const DrawerHeader = styled("div")(({ theme }) => ({
display: "flex",
alignItems: "center",
padding: theme.spacing(0, 1), // necessary for content to be below app bar
padding: theme.spacing(0, 1),
...theme.mixins.toolbar,
justifyContent: "flex-end",
}));
const HeaderWithSidebar = ({ children }) => {
const [open, setOpen] = useState(false);
const [open, setOpen] = useState(true);
const handleDrawerOpen = () => {
setOpen(true);
};
const handleDrawerClose = () => {
setOpen(false);
};
return (
<Box sx={{ display: "flex" }}>
<AppBar sx={{ position: "fixed", top: "unset" }} elevation={0} open={open}>
<Toolbar>
<Toolbar variant="dense">
<IconButton
color="inherit"
aria-label="open drawer"
@@ -72,9 +72,9 @@ const HeaderWithSidebar = ({ children }) => {
>
<MenuIcon />
</IconButton>
<Typography variant="h6" noWrap component="div">
menu
</Typography>
<Box sx={{ flexGrow: 1, display: { xs: "none", md: "flex" } }}>
<HeaderMenu />
</Box>
</Toolbar>
</AppBar>
<Drawer

View File

@@ -1,19 +1,18 @@
import { Collapse, ListItem, ListItemButton, ListItemIcon, ListItemText } from "@mui/material";
import { ExpandLess, ExpandMore } from "@mui/icons-material";
import WifiIcon from "@mui/icons-material/Wifi";
import SidebarSubitems from "@/core/components/SidebarSubitems";
import { ExpandLess, ExpandMore } from "@mui/icons-material";
import { Collapse, List, ListItem, ListItemButton, ListItemIcon, ListItemText } from "@mui/material";
import Link from "next/link";
const SidebarListItems = ({ menuItem, dispatch }) => {
return (
<>
<ListItem disablePadding sx={{ p: 0 }}>
<ListItem disablePadding divider>
<ListItemButton
sx={{
"&.Mui-selected": {
backgroundColor: "rgba(0, 0, 0, 0.2)",
},
}}
sx={{ py: 1, px: 0.5 }}
disableGutters
selected={menuItem.selected}
component={menuItem.type === "page" ? Link : null}
href={menuItem.type === "page" ? menuItem.route : null}
onClick={() => {
dispatch({ type: "COLLAPSE_MENU", id: menuItem.id });
}}
@@ -25,21 +24,28 @@ const SidebarListItems = ({ menuItem, dispatch }) => {
color: "primary.main",
width: 40,
height: 24,
pr: 2,
}}
>
{menuItem.icon}
</ListItemIcon>
<ListItemText primary={menuItem.id} />
<ListItemText primary={menuItem.label} />
{menuItem.hasSubitems ? menuItem.showSubitems ? <ExpandLess /> : <ExpandMore /> : null}
</ListItemButton>
</ListItem>
<Collapse in={menuItem.showSubitems} timeout="auto" mountOnEnter={true} unmountOnExit={true}>
{menuItem.hasSubitems
? menuItem.Subitems.map((subitem, index) => {
return <SidebarSubitems dispatch={dispatch} key={index} subitem={subitem} />;
})
: null}
{menuItem.hasSubitems ? (
<List
disablePadding
dense
sx={{
background: "#00000008",
}}
>
{menuItem.Subitems.map((subitem, index) => {
return <SidebarSubitems key={index} dispatch={dispatch} subitem={subitem} />;
})}
</List>
) : null}
</Collapse>
</>
);

View File

@@ -1,112 +1,36 @@
"use client";
import { List } from "@mui/material";
import SidebarListItems from "@/core/components/SidebarListItems";
import { useEffect, useReducer, useState } from "react";
import { usePermissions } from "@/lib/hooks/usePermissions";
import { List } from "@mui/material";
import { usePathname } from "next/navigation";
import SecurityIcon from "@mui/icons-material/Security";
import SpaceDashboardIcon from "@mui/icons-material/SpaceDashboard";
import AssessmentIcon from "@mui/icons-material/Assessment";
import AirlineSeatReclineNormalIcon from "@mui/icons-material/AirlineSeatReclineNormal";
import RepartitionIcon from "@mui/icons-material/Repartition";
import { useEffect, useReducer, useState } from "react";
import { filterMenuItems } from "../utils/filterMenuItems";
import { pageMenu } from "../utils/pageMenu";
const data = [
{
id: "hello",
type: "menu",
route: "/hello",
icon: <SecurityIcon sx={{ width: "inherit", height: "inherit" }} />,
selected: false,
Subitems: [
{
id: "amin",
route: "/s",
icon: <SpaceDashboardIcon sx={{ width: "inherit", height: "inherit" }} />,
firstName: "amin",
lastName: "ali",
Subitems: [
{
id: "shahrokh",
shahrokh: "shahrokh",
type: "page",
selected: false,
route: "/dashbssoard",
icon: <AssessmentIcon sx={{ width: "inherit", height: "inherit" }} />,
},
{
id: "shasdasdsahrokh",
shahrokh: "shahroasdasdkh",
type: "page",
selected: false,
route: "/dashboardsd",
icon: <AirlineSeatReclineNormalIcon sx={{ width: "inherit", height: "inherit" }} />,
},
],
hasSubitems: true,
showSubitems: false,
type: "menu",
selected: false,
},
{
id: "amir",
route: "/dashboarsad",
icon: <RepartitionIcon sx={{ width: "inherit", height: "inherit" }} />,
firstName: "amir",
lastName: "akbar",
hasSubitems: false,
showSubitems: false,
selected: false,
type: "page",
},
],
hasSubitems: true,
showSubitems: false,
},
{
id: "hi",
type: "menu",
icon: <SecurityIcon sx={{ width: "inherit", height: "inherit" }} />,
route: "/hi",
selected: false,
Subitems: [
{
id: "ali",
route: "/dashboardss",
firstName: "ali",
lastName: "ali",
icon: <SecurityIcon sx={{ width: "inherit", height: "inherit" }} />,
hasSubitems: false,
showSubitems: false,
type: "page",
selected: false,
},
{
id: "akbar",
route: "/akbar",
firstName: "akbar",
icon: <SecurityIcon sx={{ width: "inherit", height: "inherit" }} />,
lastName: "akbar",
hasSubitems: false,
showSubitems: false,
type: "page",
selected: false,
},
],
hasSubitems: true,
showSubitems: false,
},
{
id: "hoo",
type: "page",
route: "/dashboard",
icon: <SecurityIcon sx={{ width: "inherit", height: "inherit" }} />,
selected: false,
hasSubitems: false,
showSubitems: false,
},
];
function selectPage(item, route) {
if (item.type === "page") {
return {
...item,
selected: item.route === route,
showSubitems: item.route === route,
};
} else if (item.Subitems && Array.isArray(item.Subitems)) {
const updatedSubitems = item.Subitems.map(subitem => selectPage(subitem, route));
return {
...item,
Subitems: updatedSubitems,
showSubitems: updatedSubitems.some(subitem => subitem.showSubitems || subitem.route === route),
};
}
return item;
}
function reducer(state, action) {
switch (action.type) {
case "UPDATE_MENU":
const _permissions = action.permissions || []
const filteredPageMenu = filterMenuItems(state, ['all', ..._permissions])
return filteredPageMenu
case "COLLAPSE_MENU":
return state.map((item) =>
action.id == item.id
@@ -156,66 +80,7 @@ function reducer(state, action) {
: item;
});
case "SELECTED":
return state.map((item) => {
return item.type === "page"
? {
...item,
selected: action.route === item.route,
showSubitems: item.route === action.route,
}
: item.Subitems && Array.isArray(item.Subitems)
? {
...item,
Subitems: item.Subitems.map((subitem) => {
return subitem.type === "page"
? {
...subitem,
selected: action.route === subitem.route,
showSubitems: subitem.route === action.route,
}
: subitem.Subitems && Array.isArray(subitem.Subitems)
? {
...subitem,
Subitems: subitem.Subitems.map((secondSubItem) => ({
...secondSubItem,
selected: secondSubItem.route === action.route,
})),
showSubitems: subitem.Subitems.some(
(secondSubItem) => secondSubItem.route === action.route
),
}
: subitem;
}),
showSubitems: item.Subitems.some((subitem) => subitem.showSubitems),
}
: item;
});
// case "SELECTED":
// return state.map((item) => {
// return item.type === "page" ? {
// ...item,
// selected: action.route === item.route
// } : item.Subitems && Array.isArray(item.Subitems) ? {
// ...item,
// Subitems: item.Subitems.map((subitem) => {
// const updatedSubitem = subitem.type === "page" ? {
// ...subitem,
// selected: action.route === subitem.route
// } : subitem.Subitems && Array.isArray(subitem.Subitems) ? {
// ...subitem,
// Subitems: subitem.Subitems.map((secondSubItem) => ({
// ...secondSubItem,
// selected: secondSubItem.route === action.route
// })),
// showSubitems: subitem.Subitems.some((secondSubItem) => secondSubItem.route === action.route),
// } : subitem;
//
// console.log("Updated subitem:", updatedSubitem); // Log the updated subitem
// return updatedSubitem;
// }),
// showSubitems: item.Subitems.some((subitem) => subitem.showSubitems),
// } : item
// });
return state.map(item => selectPage(item, action.route));
default:
throw new Error();
@@ -223,13 +88,21 @@ function reducer(state, action) {
}
const SidebarMenu = () => {
const [menuItems, dispatch] = useReducer(reducer, data);
const { data: userPermissions } = usePermissions()
const [menuItems, dispatch] = useReducer(reducer, pageMenu);
const pathname = usePathname();
const [selectedKey, setSelectedKey] = useState(null);
useEffect(() => {
dispatch({ type: "SELECTED", route: pathname });
setSelectedKey(pathname);
}, [pathname]);
}, [userPermissions, pathname]);
useEffect(() => {
if (!userPermissions) return
dispatch({ type: 'UPDATE_MENU', permissions: userPermissions });
}, [userPermissions])
useEffect(() => {
selectedKey &&
@@ -240,11 +113,13 @@ const SidebarMenu = () => {
}, [selectedKey]);
return (
<List disablePadding>
userPermissions && (
<List disablePadding dense>
{menuItems.map((menuItem) => {
return <SidebarListItems dispatch={dispatch} key={menuItem.id} menuItem={menuItem} />;
})}
</List>
)
);
};
export default SidebarMenu;

View File

@@ -1,18 +1,17 @@
import { Collapse, List, ListItem, ListItemButton, ListItemIcon, ListItemText } from "@mui/material";
import { ExpandLess, ExpandMore } from "@mui/icons-material";
import { Collapse, List, ListItem, ListItemButton, ListItemIcon, ListItemText } from "@mui/material";
import Link from "next/link";
const SidebarSubitems = ({ subitem, dispatch }) => {
return (
<>
<List disablePadding>
<ListItem disablePadding>
<ListItem disablePadding divider>
<ListItemButton
sx={{
"&.Mui-selected": {
backgroundColor: "rgba(0, 0, 0, 0.1)",
},
}}
disableGutters
sx={{ py: 1, px: 0.5, pl: 1 }}
selected={subitem.selected}
component={subitem.type === "page" ? Link : null}
href={subitem.type === "page" ? subitem.route : null}
onClick={() => {
dispatch({ type: "COLLAPSE_SUB_ITEMS", id: subitem.id });
}}
@@ -21,62 +20,31 @@ const SidebarSubitems = ({ subitem, dispatch }) => {
sx={{
minWidth: 0,
justifyContent: "center",
color: "primary.main",
width: 40,
height: 24,
pr: 2,
}}
>
{subitem.icon}
</ListItemIcon>
<ListItemText primary={subitem.firstName} />
<ListItemText primary={subitem.label} />
{subitem.hasSubitems ? subitem.showSubitems ? <ExpandLess /> : <ExpandMore /> : null}
</ListItemButton>
</ListItem>
</List>
{subitem.hasSubitems
? subitem.Subitems.map((subSubitem, index) => {
return (
<Collapse
key={index}
in={subitem.showSubitems}
timeout="auto"
mountOnEnter={true}
unmountOnExit={true}
>
<List disablePadding>
<ListItem disablePadding>
<ListItemButton
onClick={() => {
dispatch({ type: "COLLAPSE_SUB_SECOND_ITEMS", id: subSubitem.id });
}}
selected={subSubitem.selected}
<Collapse in={subitem.showSubitems} timeout="auto" mountOnEnter={true} unmountOnExit={true}>
{subitem.hasSubitems ? (
<List
disablePadding
dense
sx={{
"&.Mui-selected": {
backgroundColor: "rgba(0, 0, 0, 0.05)",
},
background: "#00000008",
}}
>
<ListItemIcon
sx={{
minWidth: 0,
justifyContent: "center",
color: "primary.main",
width: 40,
height: 24,
pr: 2,
}}
>
{subSubitem.icon}
</ListItemIcon>
<ListItemText primary={subSubitem.shahrokh} />
</ListItemButton>
</ListItem>
{subitem.Subitems.map((subSubitem, index) => {
return <SidebarSubitems key={index} dispatch={dispatch} subitem={subSubitem} />;
})}
</List>
) : null}
</Collapse>
);
})
: null}
</>
);
};

View File

@@ -0,0 +1,187 @@
import { toast } from "react-toastify";
import { Box, Typography } from "@mui/material";
import { Dangerous } from "@mui/icons-material";
export const errorServerToast = (toastContainer) =>
toast.error(
() => (
<Box
sx={{
display: "flex",
flexDirection: "column",
alignItems: "start",
justifyContent: "start",
}}
>
<Box sx={{ display: "flex", alignItems: "center" }}>
<Dangerous color="error" sx={{ mr: 1.6 }} />
<Box sx={{ display: "flex", flexDirection: "column" }}>
<Typography variant="caption">{"The request failed due to an internal error."}</Typography>
</Box>
</Box>
</Box>
),
{
icon: false,
containerId: toastContainer,
autoClose: 5000,
hideProgressBar: true,
pauseOnHover: true,
closeOnClick: false,
draggable: true,
}
);
export const errorUnauthorizedToast = (toastContainer) =>
toast.error(
() => (
<Box
sx={{
display: "flex",
flexDirection: "column",
alignItems: "start",
justifyContent: "start",
}}
>
<Box sx={{ display: "flex", alignItems: "center" }}>
<Dangerous color="error" sx={{ mr: 1.6 }} />
<Box sx={{ display: "flex", flexDirection: "column" }}>
<Typography variant="caption">{"The user is not authorized to make the request."}</Typography>
</Box>
</Box>
</Box>
),
{
icon: false,
containerId: toastContainer,
autoClose: 3000,
hideProgressBar: true,
pauseOnHover: true,
closeOnClick: false,
draggable: true,
}
);
export const errorLogicToast = (message, toastContainer) =>
toast.error(
() => (
<Box
sx={{
display: "flex",
flexDirection: "column",
alignItems: "start",
justifyContent: "start",
}}
>
<Box sx={{ display: "flex", alignItems: "center" }}>
<Dangerous color="error" sx={{ mr: 1.6 }} />
<Box sx={{ display: "flex", flexDirection: "column" }}>
<Typography variant="caption">
{message ||
"The request was well-formed but was unable to be followed due to semantic errors."}
</Typography>
</Box>
</Box>
</Box>
),
{
icon: false,
containerId: toastContainer,
autoClose: false,
closeOnClick: false,
draggable: false,
}
);
export const errorValidationToast = (message, toastContainer) =>
toast.error(
() => (
<Box
sx={{
display: "flex",
flexDirection: "column",
alignItems: "start",
justifyContent: "start",
}}
>
<Box sx={{ display: "flex", alignItems: "center" }}>
<Dangerous color="error" sx={{ mr: 1.6 }} />
<Box sx={{ display: "flex", flexDirection: "column" }}>
<Typography variant="caption">
{message ||
"The request was well-formed but was unable to be followed due to semantic errors."}
</Typography>
</Box>
</Box>
</Box>
),
{
icon: false,
containerId: toastContainer,
autoClose: false,
closeOnClick: false,
draggable: false,
}
);
export const errorTooManyToast = (toastContainer) =>
toast.error(
() => (
<Box
sx={{
display: "flex",
flexDirection: "column",
alignItems: "start",
justifyContent: "start",
}}
>
<Box sx={{ display: "flex", alignItems: "center" }}>
<Dangerous color="error" sx={{ mr: 1.6 }} />
<Box sx={{ display: "flex", flexDirection: "column" }}>
<Typography variant="caption">
{"Too many requests have been sent within a given time span."}
</Typography>
</Box>
</Box>
</Box>
),
{
icon: false,
containerId: toastContainer,
autoClose: false,
closeOnClick: false,
draggable: false,
}
);
export const errorClientToast = (toastContainer) =>
toast.error(
() => (
<Box
sx={{
display: "flex",
flexDirection: "column",
alignItems: "start",
justifyContent: "start",
}}
>
<Box sx={{ display: "flex", alignItems: "center" }}>
<Dangerous color="error" sx={{ mr: 1.6 }} />
<Box sx={{ display: "flex", flexDirection: "column" }}>
<Typography variant="caption">
{
"The API request is invalid or improperly formed. Consequently, the API server could not understand the request."
}
</Typography>
</Box>
</Box>
</Box>
),
{
icon: false,
containerId: toastContainer,
autoClose: false,
closeOnClick: false,
draggable: false,
}
);

View File

@@ -0,0 +1,33 @@
import { toast } from "react-toastify";
import { Box, Typography } from "@mui/material";
import { Beenhere } from "@mui/icons-material";
export const successToast = (toastContainer) =>
toast.success(
() => (
<Box
sx={{
display: "flex",
flexDirection: "column",
alignItems: "start",
justifyContent: "start",
}}
>
<Box sx={{ display: "flex", alignItems: "center" }}>
<Beenhere color="success" sx={{ mr: 1.6 }} />
<Box sx={{ display: "flex", flexDirection: "column" }}>
<Typography variant="caption">{"Your operation was successful"}</Typography>
</Box>
</Box>
</Box>
),
{
icon: false,
containerId: toastContainer,
autoClose: 3000,
hideProgressBar: true,
pauseOnHover: true,
closeOnClick: false,
draggable: true,
}
);

View File

@@ -0,0 +1,22 @@
"use client";
import { useAuth } from "@/lib/contexts/auth";
import { usePathname, useRouter } from "next/navigation";
import { useEffect } from "react";
function WithAuthMiddleware({ children }) {
const router = useRouter();
const pathName = usePathname();
const { isAuth, initAuthState } = useAuth();
useEffect(() => {
if (!initAuthState) return;
if (!isAuth) {
router.replace(`${process.env.NEXT_PUBLIC_API_URL}/login?_back=${encodeURIComponent(pathName)}`);
}
}, [isAuth, initAuthState]);
if (!initAuthState || !isAuth) return null;
return <>{children}</>;
}
export default WithAuthMiddleware;

View File

@@ -0,0 +1,60 @@
"use client";
import { toast } from "react-toastify";
import {
errorClientToast,
errorLogicToast,
errorServerToast,
errorTooManyToast,
errorUnauthorizedToast,
errorValidationToast,
} from "@/core/components/Toasts/error";
const isServerError = (status) => status >= 500 && status <= 599;
const isClientError = (status) => status >= 400 && status <= 499;
const errorServer = (response, notification, toastContainer) => {
if (notification) errorServerToast(toastContainer);
};
const errorClient = (response, notification, toastContainer) => {
switch (response.status) {
case 401:
if (notification) errorUnauthorizedToast(toastContainer);
break;
case 422:
if ("type" in response.data) {
if (Array.isArray(response.data.message)) {
response.data.message.map((item) => {
if (notification) errorLogicToast(item, toastContainer);
});
} else {
if (notification) errorLogicToast(response.data.message, toastContainer);
}
break;
}
if (notification) {
const errorsMap = Object.keys(response.data.errors);
const errorsArray = response.data.errors;
errorsMap.map((item, index) => {
errorValidationToast(errorsArray[item][0], toastContainer);
});
}
break;
case 429:
if (notification) errorTooManyToast(toastContainer);
break;
default:
if (notification) errorClientToast(toastContainer);
break;
}
};
export const errorResponse = (response, notification, toastContainer) => {
if (notification) toast.dismiss({ container: toastContainer });
if (isServerError(response.status)) {
errorServer(response, notification, toastContainer);
} else if (isClientError(response.status)) {
errorClient(response, notification, toastContainer);
}
};

View File

@@ -0,0 +1,17 @@
export function filterMenuItems(items, _permissions = []) {
return items.reduce((acc, item) => {
if (item.permissions) {
if (item.permissions.some(permission => _permissions.includes(permission))) {
acc.push(item);
}
} else if (item.hasSubitems) {
const filteredSubItems = filterMenuItems(item.Subitems, _permissions);
if (filteredSubItems.length > 0) {
acc.push({ ...item, Subitems: filteredSubItems });
}
} else {
acc.push(item);
}
return acc;
}, []);
}

409
src/core/utils/pageMenu.js Normal file
View File

@@ -0,0 +1,409 @@
import { Security } from "@mui/icons-material";
const prefix = process.env.NODE_ENV === 'production' ? '/v3' : ''
export const pageMenu = [
{
id: "dashboard",
label: "پیشخوان",
type: "page",
route: prefix + "/dashboard",
icon: <Security sx={{ width: "inherit", height: "inherit" }} />,
permissions: ["all"]
},
{
id: "userManagement",
label: "مدیریت کاربران",
type: "page",
route: process.env.NEXT_PUBLIC_API_URL + "/v2/user_management",
icon: <Security sx={{ width: "inherit", height: "inherit" }} />,
permissions: ['full-user-management', 'limited-user-management']
},
{
id: "projectsManagment",
label: "پروژه های راهداری",
type: "menu",
icon: <Security sx={{ width: "inherit", height: "inherit" }} />,
hasSubitems: true,
Subitems: [
{
id: "projectsManagmentFinance",
label: "ثبت قرارداد و پروژه",
type: "page",
route: process.env.NEXT_PUBLIC_API_URL + "/v2/rahdari_projects/finance",
icon: <Security sx={{ width: "inherit", height: "inherit" }} />,
permissions: ['show-contract', 'show-contract-province'],
},
{
id: "projectsManagmentList",
label: "لیست پروژه ها",
type: "page",
route: process.env.NEXT_PUBLIC_API_URL + "/v2/rahdari_projects/projects_list",
icon: <Security sx={{ width: "inherit", height: "inherit" }} />,
permissions: ["all"]
},
{
id: "projectsManagmentProposal",
label: "پروژه های پیشنهادی",
type: "page",
route: process.env.NEXT_PUBLIC_API_URL + "/v2/rahdari_projects/proposal",
icon: <Security sx={{ width: "inherit", height: "inherit" }} />,
permissions: ['show-proposal', 'show-proposal-province']
},
{
id: "projectsManagmentLawmaker",
label: "درخواست نمایندگان",
type: "page",
route: process.env.NEXT_PUBLIC_API_URL + "/v2/rahdari_projects/lawmakers",
icon: <Security sx={{ width: "inherit", height: "inherit" }} />,
permissions: ['show-lawmaker', 'show-lawmaker-province']
},
{
id: "projectsManagmentMap",
label: "پراکندگی بر روی نقشه",
type: "page",
route: process.env.NEXT_PUBLIC_API_URL + "/v2/map?type=rahdari_projects",
icon: <Security sx={{ width: "inherit", height: "inherit" }} />,
permissions: ["all"]
},
],
},
{
id: "roadItemManagment",
label: "فعالیت های روزانه",
type: "menu",
icon: <Security sx={{ width: "inherit", height: "inherit" }} />,
hasSubitems: true,
Subitems: [
{
id: "roadItemManagmentSupervisor",
label: "ارزیابی",
type: "menu",
icon: <Security sx={{ width: "inherit", height: "inherit" }} />,
hasSubitems: true,
Subitems: [
{
id: "roadItemManagmentSupervisorCartable",
label: "کارتابل",
type: "page",
route: process.env.NEXT_PUBLIC_API_URL + "/v2/road_items/supervisor/cartable",
permissions: ['show-road-item-supervise-cartable', 'show-road-item-supervise-cartable-province']
},
],
},
{
id: "roadItemManagmentOparation",
label: "عملیات",
type: "menu",
icon: <Security sx={{ width: "inherit", height: "inherit" }} />,
hasSubitems: true,
Subitems: [
{
id: "roadItemManagmentOparationCreate",
label: "ثبت فعالیت",
type: "page",
route: process.env.NEXT_PUBLIC_API_URL + "/v2/road_items/operator/create",
permissions: ['create-road-item']
},
{
id: "roadItemManagmentOparationCartable",
label: "کارتابل",
type: "page",
route: process.env.NEXT_PUBLIC_API_URL + "/v2/road_items/operator/cartable",
permissions: ['create-road-item']
},
],
},
{
id: "roadItemManagmentMap",
label: "پراکندگی بر روی نقشه",
type: "page",
route: process.env.NEXT_PUBLIC_API_URL + "/v2/map?type=road_items",
icon: <Security sx={{ width: "inherit", height: "inherit" }} />,
permissions: ['show-road-item-supervise-cartable', 'show-road-item-supervise-cartable-province', 'create-road-item']
},
{
id: "roadItemManagmentReport",
label: "گزارش ها",
type: "page",
route: process.env.NEXT_PUBLIC_API_URL + "/v2/road_items/report",
icon: <Security sx={{ width: "inherit", height: "inherit" }} />,
permissions: ['show-road-item-supervise-cartable', 'show-road-item-supervise-cartable-province', 'create-road-item']
},
],
},
{
id: "roadPatrolManagment",
label: "گشت راهداری و ترابری",
type: "menu",
icon: <Security sx={{ width: "inherit", height: "inherit" }} />,
hasSubitems: true,
Subitems: [
{
id: "roadPatrolManagmentSupervisor",
label: "ارزیابی",
type: "menu",
icon: <Security sx={{ width: "inherit", height: "inherit" }} />,
hasSubitems: true,
Subitems: [
{
id: "roadPatrolManagmentSupervisorCartable",
label: "کارتابل",
type: "page",
route: process.env.NEXT_PUBLIC_API_URL + "/road_patrols/supervisor/cartable",
permissions: ['show-road-patrol-supervise-cartable', 'show-road-patrol-supervise-cartable-province']
},
],
},
{
id: "roadPatrolManagmentOparation",
label: "عملیات",
type: "menu",
icon: <Security sx={{ width: "inherit", height: "inherit" }} />,
hasSubitems: true,
Subitems: [
{
id: "roadPatrolManagmentOparationCreate",
label: "ثبت اقدام",
type: "page",
route: process.env.NEXT_PUBLIC_API_URL + "/v2/road_patrols/operator/create",
permissions: ['add-road-patrol']
},
{
id: "roadPatrolManagmentOparationCartable",
label: "کارتابل",
type: "page",
route: process.env.NEXT_PUBLIC_API_URL + "/v2/road_patrols/operator/cartable",
permissions: ['add-road-patrol']
},
],
},
{
id: "roadPatrolManagmentMap",
label: "پراکندگی بر روی نقشه",
type: "page",
route: process.env.NEXT_PUBLIC_API_URL + "/v2/map?type=road_patrols",
icon: <Security sx={{ width: "inherit", height: "inherit" }} />,
permissions: ['add-road-patrol', 'show-road-patrol-supervise-cartable', 'show-road-patrol-supervise-cartable-province']
},
{
id: "roadPatrolManagmentReport",
label: "گزارش ها",
type: "page",
route: process.env.NEXT_PUBLIC_API_URL + "/v2/road_patrols/report",
icon: <Security sx={{ width: "inherit", height: "inherit" }} />,
permissions: ['add-road-patrol', 'show-road-patrol-supervise-cartable', 'show-road-patrol-supervise-cartable-province']
},
],
},
{
id: "inquiryPrivacyManagment",
label: "استعلام حریم راه",
type: "menu",
icon: <Security sx={{ width: "inherit", height: "inherit" }} />,
hasSubitems: true,
Subitems: [
{
id: "inquiryPrivacyManagmentFencing",
label: "احداث و بهره برداری دیوارکشی و مستحدثات",
type: "page",
route: prefix + "/dashboard/inquiry_privacy/fencing",
permissions: ['all']
},
{
id: "inquiryPrivacyManagmentRoad",
label: "احداث راه دسترسی اختصاصی",
type: "page",
route: prefix + "/dashboard/inquiry_privacy/road",
permissions: ['all']
},
{
id: "inquiryPrivacyManagmentInfrastructure",
label: "احداث و بهره برداری تاسیسات زیربنایی",
type: "page",
route: prefix + "/dashboard/inquiry_privacy/infrastructure",
permissions: ['all']
},
]
},
{
id: "safetyAndPrivacyManagment",
label: "نگهداری حریم راه",
type: "menu",
icon: <Security sx={{ width: "inherit", height: "inherit" }} />,
hasSubitems: true,
Subitems: [
{
id: "safetyAndPrivacyManagmentOparation",
label: "عملیات",
type: "menu",
icon: <Security sx={{ width: "inherit", height: "inherit" }} />,
hasSubitems: true,
Subitems: [
{
id: "safetyAndPrivacyManagmentOparationCreate",
label: "ثبت فعالیت",
type: "page",
route: process.env.NEXT_PUBLIC_API_URL + "/safety_and_privacy/operator/first_step",
permissions: ['add-safety-and-privacy']
},
{
id: "safetyAndPrivacyManagmentOparationCartable",
label: "کارتابل",
type: "page",
route: process.env.NEXT_PUBLIC_API_URL + "/v2/safety_and_privacy/operator/cartable",
permissions: ['show-safety-and-privacy-operator-cartable', 'show-safety-and-privacy-operator-cartable-province', 'show-safety-and-privacy-operator-cartable-edarate-shahri']
},
],
},
{
id: "safetyAndPrivacyManagmentMap",
label: "پراکندگی بر روی نقشه",
type: "page",
route: process.env.NEXT_PUBLIC_API_URL + "/v2/map?type=safety_and_privacy",
icon: <Security sx={{ width: "inherit", height: "inherit" }} />,
permissions: ['show-safety-and-privacy-operator-cartable', 'show-safety-and-privacy-operator-cartable-province',
'show-safety-and-privacy-operator-cartable-edarate-shahri', 'add-safety-and-privacy']
},
{
id: "safetyAndPrivacyManagmentReport",
label: "گزارش ها",
type: "page",
route: process.env.NEXT_PUBLIC_API_URL + "/v2/safety_and_privacy/report",
icon: <Security sx={{ width: "inherit", height: "inherit" }} />,
permissions: ['show-safety-and-privacy-operator-cartable', 'show-safety-and-privacy-operator-cartable-province',
'show-safety-and-privacy-operator-cartable-edarate-shahri', 'add-safety-and-privacy']
},
],
},
{
id: "roadObservationsManagment",
label: "واکنش سریع",
type: "menu",
icon: <Security sx={{ width: "inherit", height: "inherit" }} />,
hasSubitems: true,
Subitems: [
{
id: "roadObservationsManagmentSupervisor",
label: "ارزیابی",
type: "menu",
icon: <Security sx={{ width: "inherit", height: "inherit" }} />,
hasSubitems: true,
Subitems: [
{
id: "roadObservationsManagmentSupervisorCartable",
label: "کارتابل",
type: "page",
route: process.env.NEXT_PUBLIC_API_URL + "/v2/road_observations/supervisor/cartable",
permissions: ['supervise-fast-react', 'supervise-fast-react-province']
},
],
},
{
id: "roadObservationsManagmentList",
label: "رسیدگی به شکایات",
type: "page",
route: process.env.NEXT_PUBLIC_API_URL + "/v2/road_observations",
icon: <Security sx={{ width: "inherit", height: "inherit" }} />,
permissions: ['show-fast-react', 'show-fast-react-province', 'show-fast-react-edarate-shahri']
},
{
id: "roadObservationsManagmentOparation",
label: "عملیات",
type: "menu",
icon: <Security sx={{ width: "inherit", height: "inherit" }} />,
hasSubitems: true,
Subitems: [
{
id: "roadObservationsManagmentOparationCartable",
label: "کارتابل",
type: "page",
route: process.env.NEXT_PUBLIC_API_URL + "/v2/road_observations/operator/cartable",
permissions: ['show-fast-react', 'show-fast-react-province', 'show-fast-react-edarate-shahri']
},
],
},
{
id: "roadObservationsManagmentMap",
label: "پراکندگی بر روی نقشه",
type: "page",
route: process.env.NEXT_PUBLIC_API_URL + "/v2/map?type=road_observations",
icon: <Security sx={{ width: "inherit", height: "inherit" }} />,
permissions: ["all"]
},
{
id: "roadObservationsManagmentReport",
label: "گزارش ها",
type: "page",
route: process.env.NEXT_PUBLIC_API_URL + "/v2/road_observations/report/index",
icon: <Security sx={{ width: "inherit", height: "inherit" }} />,
permissions: ["all"]
},
],
},
{
id: "winterCampManagment",
label: "قرارگاه زمستانی",
type: "menu",
icon: <Security sx={{ width: "inherit", height: "inherit" }} />,
hasSubitems: true,
Subitems: [
{
id: "winterCampManagmentBlocking",
label: "محور های انسدادی",
type: "page",
route: process.env.NEXT_PUBLIC_API_URL + "/v2/winter_camp",
icon: <Security sx={{ width: "inherit", height: "inherit" }} />,
permissions: ['show-camp', 'show-camp-province']
},
{
id: "winterCampManagmentFirstRing",
label: "محور های حلقه اول",
type: "page",
route: process.env.NEXT_PUBLIC_API_URL + "/v2/map?type=camp",
icon: <Security sx={{ width: "inherit", height: "inherit" }} />,
permissions: ['show-camp', 'show-camp-province']
},
{
id: "winterCampManagmentReport",
label: "گزارش ها",
type: "page",
route: process.env.NEXT_PUBLIC_API_URL + "/v2/map?type=campNew",
icon: <Security sx={{ width: "inherit", height: "inherit" }} />,
permissions: ['show-camp', 'show-camp-province']
},
],
},
{
id: "receiptManagment",
label: "خسارات وارده بر ابنیه فنی و تاسیسات راه",
type: "menu",
icon: <Security sx={{ width: "inherit", height: "inherit" }} />,
hasSubitems: true,
Subitems: [
{
id: "receiptManagmentOparation",
label: "عملیات",
type: "menu",
icon: <Security sx={{ width: "inherit", height: "inherit" }} />,
hasSubitems: true,
Subitems: [
{
id: "receiptManagmentOparationCartable",
label: "کارتابل",
type: "page",
route: process.env.NEXT_PUBLIC_API_URL + "/v2/receipt",
permissions: ["all"]
},
],
},
{
id: "receiptManagmentReport",
label: "گزارش ها",
type: "page",
route: process.env.NEXT_PUBLIC_API_URL + "/v2/receipt/report",
icon: <Security sx={{ width: "inherit", height: "inherit" }} />,
permissions: ["all"]
},
],
},
];

6
src/core/utils/routes.js Normal file
View File

@@ -0,0 +1,6 @@
const api = process.env.NEXT_PUBLIC_API_URL;
export const GET_USER_ROUTE = api + "/webapi/user/get-permission";
export const GET_LOGIN_ROUTE = api + "/login_dev";
export const GET_PERMISSIONS_ROUTE = api + "/webapi/user/get-permission";

View File

@@ -0,0 +1,10 @@
"use client";
import { toast } from "react-toastify";
import { successToast } from "@/core/components/Toasts/success";
export const successRequest = (notification, toastContainer) => {
if (notification) {
toast.dismiss({ container: toastContainer });
successToast(toastContainer);
}
};

View File

@@ -7,6 +7,16 @@ const theme = createTheme({
fontFamily: `IRANSansFaNum, sans-serif`,
fontSize: 12,
},
palette: {
primary: {
main: "#2070af",
contrastText: "#fff",
},
primary2: {
main: "#015688",
contrastText: "#fff",
},
},
});
export default theme;

View File

@@ -0,0 +1,11 @@
import { createContext, useState } from "react";
export const DataTableContext = createContext();
const DataTableProvider = ({ children }) => {
const [filterData, setFilterData] = useState({});
return <DataTableContext.Provider value={{ filterData, setFilterData }}>{children}</DataTableContext.Provider>;
};
export default DataTableProvider;

82
src/lib/contexts/auth.js Normal file
View File

@@ -0,0 +1,82 @@
"use client";
import { createContext, useCallback, useContext, useEffect, useReducer } from "react";
import useRequest from "../hooks/useRequest";
import { GET_USER_ROUTE } from "@/core/utils/routes";
const initAuth = {
initAuthState: false,
isAuth: false,
user: {},
};
const authReducer = (state, action) => {
switch (action.type) {
case "CLEAR_USER":
return { ...state, user: {} };
case "CHANGE_USER":
return { ...state, user: action.user };
case "CHANGE_AUTH_STATE":
return { ...state, isAuth: action.isAuth };
case "CHANGE_INIT_AUTH":
return { ...state, initAuthState: action.initAuthState };
default:
return state;
}
};
const AuthContext = createContext();
export const AuthProvider = ({ children }) => {
const [state, dispatch] = useReducer(authReducer, initAuth);
const request = useRequest();
const clearUser = useCallback(() => {
dispatch({ type: "CLEAR_USER" });
}, []);
const changeUser = useCallback((user) => {
dispatch({ type: "CHANGE_USER", user });
}, []);
const changeAuthState = useCallback((isAuth) => {
dispatch({ type: "CHANGE_AUTH_STATE", isAuth });
}, []);
const changeInitAuth = useCallback((initAuthState) => {
dispatch({ type: "CHANGE_INIT_AUTH", initAuthState });
}, []);
const getUser = useCallback(async () => {
try {
const { data } = await request(GET_USER_ROUTE, "get");
changeUser(data);
changeAuthState(true);
changeInitAuth(true);
} catch (error) {
if (error.response && error.response.status === 401) {
clearUser();
changeAuthState(false);
changeInitAuth(true);
}
}
}, [clearUser, changeUser, changeAuthState, changeInitAuth]);
useEffect(() => {
getUser();
}, []);
return (
<AuthContext.Provider
value={{
user: state.user,
isAuth: state.isAuth,
initAuthState: state.initAuthState,
getUser,
}}
>
{children}
</AuthContext.Provider>
);
};
export const useAuth = () => useContext(AuthContext);

View File

@@ -0,0 +1,9 @@
import { useContext } from "react";
import { DataTableContext } from "@/lib/contexts/DataTable";
const useTableSetting = () => {
const { filterData, setFilterData } = useContext(DataTableContext);
return { filterData, setFilterData };
};
export default useTableSetting;

View File

@@ -0,0 +1,18 @@
import { GET_PERMISSIONS_ROUTE } from "@/core/utils/routes";
import useSWR from "swr";
import useRequest from "./useRequest";
export const usePermissions = () => {
const request = useRequest()
const fetcher = async (url) => {
try {
const response = await request(url)
return response.data.data
} catch (error) {
throw new Error();
}
};
return useSWR(GET_PERMISSIONS_ROUTE, fetcher, { keepPreviousData: true });
}

View File

@@ -0,0 +1,46 @@
import { errorResponse } from "@/core/utils/errorResponse";
import { successRequest } from "@/core/utils/successRequest";
import axios from "axios";
const defaultOptions = {
data: {},
requestOptions: {
headers: {},
},
notification: {
show: true,
success: false,
failed: true,
},
};
const useRequest = (initOptions) => {
const _options = Object.assign({}, defaultOptions, initOptions);
return async (url = "", method = "get", options) => {
const mergedOptions = Object.assign({}, _options, options);
try {
const response = await axios({
url,
method,
data: method === "get" ? null : mergedOptions.data,
withCredentials: true,
...mergedOptions.requestOptions,
});
successRequest(mergedOptions.notification.show && mergedOptions.notification.success, "request_data");
return response;
} catch (error) {
if (error.response) {
errorResponse(
error.response,
mergedOptions.notification.show && mergedOptions.notification.failed,
"request_data"
);
}
throw error;
}
};
};
export default useRequest;

View File

@@ -0,0 +1,9 @@
import { useContext } from "react";
import { TableSettingContext } from "../contexts/tableSetting";
const useTableSetting = () => {
const { settingStore, hideAction, sortAction, filterAction, refactorAction } = useContext(TableSettingContext);
return { settingStore, hideAction, sortAction, filterAction, refactorAction };
};
export default useTableSetting;