diff --git a/src/app/(withAuth)/layout.js b/src/app/(withAuth)/layout.js
index 63d25a7..60ac863 100644
--- a/src/app/(withAuth)/layout.js
+++ b/src/app/(withAuth)/layout.js
@@ -1,6 +1,22 @@
+"use client";
import WithAuthMiddleware from "@/core/middlewares/withAuth";
+import LoadingHardPage from "@/core/components/LoadingHardPage";
+import { useAuth } from "@/lib/contexts/auth";
const Layout = ({ children }) => {
+ const { isAuth, initAuthState, errorState } = useAuth();
+
+ if (!initAuthState && !isAuth)
+ return (
+
+ );
+
return {children};
};
diff --git a/src/app/layout.js b/src/app/layout.js
index 237d222..66d8c25 100644
--- a/src/app/layout.js
+++ b/src/app/layout.js
@@ -1,5 +1,6 @@
import { AuthProvider } from "@/lib/contexts/auth";
import { TableSettingProvider } from "@/lib/contexts/tableSetting";
+import favicon from "@/assets/images/favicon.svg";
export const metadata = {
title: "سامانه جامع راهداری",
@@ -8,6 +9,9 @@ export const metadata = {
export default function RootLayout({ children }) {
return (
+
+
+
{children}
diff --git a/src/app/not-found.js b/src/app/not-found.js
new file mode 100644
index 0000000..5d38652
--- /dev/null
+++ b/src/app/not-found.js
@@ -0,0 +1,16 @@
+"use client";
+import { Box, Stack, Typography } from "@mui/material";
+import SvgNotFound from "@/core/components/svgs/SvgNotFound";
+
+export default function NotFound() {
+ return (
+
+
+
+
+
+ صفحه موردنظر یافت نشد ...
+
+
+ );
+}
diff --git a/src/assets/images/favicon.svg b/src/assets/images/favicon.svg
new file mode 100644
index 0000000..f5943ad
--- /dev/null
+++ b/src/assets/images/favicon.svg
@@ -0,0 +1,25 @@
+
+
+
diff --git a/src/components/dashboard/azmayesh/AzmayeshList.jsx b/src/components/dashboard/azmayesh/AzmayeshList.jsx
index c3dcb83..4bfd193 100644
--- a/src/components/dashboard/azmayesh/AzmayeshList.jsx
+++ b/src/components/dashboard/azmayesh/AzmayeshList.jsx
@@ -42,6 +42,7 @@ const AzmayeshList = () => {
id: "lat",
enableColumnFilter: false,
datatype: "text",
+ enableSorting: false,
filterFn: "equals",
Cell: ({ row }) => ,
},
diff --git a/src/components/dashboard/carDetails/CarDetailsContent.jsx b/src/components/dashboard/carDetails/CarDetailsContent.jsx
index bce967d..8c709cf 100644
--- a/src/components/dashboard/carDetails/CarDetailsContent.jsx
+++ b/src/components/dashboard/carDetails/CarDetailsContent.jsx
@@ -1,18 +1,13 @@
-import {
- Button, Collapse,
- DialogActions,
- DialogContent, Grid,
- Stack, TextField
-} from "@mui/material";
+import { Button, Collapse, DialogActions, DialogContent, Grid, Stack, TextField } from "@mui/material";
import MuiDatePicker from "@/core/components/MuiDatePicker";
-import {useFormik} from "formik";
+import { useFormik } from "formik";
import moment from "jalali-moment";
import * as Yup from "yup";
-import {useState} from "react";
+import { useState } from "react";
import ApplicantRequestDetail from "@/core/components/ApplicantRequestDetail";
import DetailMap from "@/core/components/ApplicantRequestDetail/DetailMap";
import FlyToPolyline from "@/core/components/ApplicantRequestDetail/FlyToPolyline";
-import {MapContainer} from "react-leaflet";
+import { MapContainer } from "react-leaflet";
import dynamic from "next/dynamic";
import MapLoading from "@/core/components/MapLayer/Loading";
const MapLayer = dynamic(() => import("@/core/components/MapLayer"), {
@@ -37,55 +32,51 @@ const fakeData = {
address: "کرج - فلان جا - جنب پاساژ - نزدیک اونجا - اونور خیابون - زیر رو گذر - روی زیر گذر",
file_mosavab: "file",
polyline: [
- [35.6892, 51.3890],
+ [35.6892, 51.389],
[35.7074, 51.3929],
],
};
-const CarDetailsContent = ({setOpenCarDialog}) => {
- const [openCarDetailContent, setOpenCarDetailContent] = useState(false)
+const CarDetailsContent = ({ setOpenCarDialog }) => {
+ const [openCarDetailContent, setOpenCarDetailContent] = useState(false);
const initialValues = {
start_date: "",
end_date: "",
vehicle_code: "",
- }
+ };
const validationSchema = Yup.object().shape({
start_date: Yup.string().required("تاریخ شروع الزامیست!!!"),
- end_date: Yup.string().test(
- "is-greater",
- "تاریخ اتمام نمیتواند کمتر از تاریخ شروع باشد",
- function (value) {
- const {start_date} = this.parent; // Access other field values
+ end_date: Yup.string()
+ .test("is-greater", "تاریخ اتمام نمیتواند کمتر از تاریخ شروع باشد", function (value) {
+ const { start_date } = this.parent; // Access other field values
if (!value || !start_date) return true; // Skip validation if either field is empty
- return moment(value, "YYYY/MM/DD").isSameOrAfter(
- moment(start_date, "YYYY/MM/DD")
- );
- }
- ).required("تاریخ اتمام الزامیست!!!"),
+ return moment(value, "YYYY/MM/DD").isSameOrAfter(moment(start_date, "YYYY/MM/DD"));
+ })
+ .required("تاریخ اتمام الزامیست!!!"),
vehicle_code: Yup.mixed()
.test("is-number", "کد خودرو باید عدد باشد!!!", (value) => !isNaN(value))
.test("positive", "کد خودرو باید مثبت باشد!!!", (value) => value >= 0)
.required("کد خودرو الزامیست!!!"),
- })
+ });
const handleSubmit = async (values, props) => {
props.setSubmitting(true);
- setOpenCarDetailContent(true)
- console.log(values)
- }
+ setOpenCarDetailContent(true);
+ console.log(values);
+ };
const formik = useFormik({
initialValues,
validationSchema,
onSubmit: handleSubmit,
});
- console.log(formik.errors)
+ console.log(formik.errors);
return (
<>
-
-
+
+
{
/>
-
-
+
{
- const [openCarDialog, setOpenCarDialog] = useState(false)
+ const [openCarDialog, setOpenCarDialog] = useState(false);
return (
@@ -22,20 +22,19 @@ const TestPage = () => {
>
diff --git a/src/components/layouts/dashboard/headerWithSidebar/HeaderMenu/index.jsx b/src/components/layouts/dashboard/headerWithSidebar/HeaderMenu/index.jsx
index 47decdf..92c9553 100644
--- a/src/components/layouts/dashboard/headerWithSidebar/HeaderMenu/index.jsx
+++ b/src/components/layouts/dashboard/headerWithSidebar/HeaderMenu/index.jsx
@@ -1,5 +1,5 @@
import { ExpandLess, ExpandMore, Link } from "@mui/icons-material";
-import { Button, Divider, ListItemIcon, ListItemText, Menu, MenuItem } from "@mui/material";
+import { Button, Divider, ListItemIcon, ListItemText, Menu, MenuItem, Stack } from "@mui/material";
import NextLink from "next/link";
import { useState } from "react";
@@ -15,7 +15,7 @@ const HeaderMenu = ({ menu }) => {
};
return (
- <>
+
{
index < menu.subMenu.length - 1 && ,
])}
- >
+
);
};
diff --git a/src/components/layouts/dashboard/headerWithSidebar/index.jsx b/src/components/layouts/dashboard/headerWithSidebar/index.jsx
index a0ffad2..ed1cca5 100644
--- a/src/components/layouts/dashboard/headerWithSidebar/index.jsx
+++ b/src/components/layouts/dashboard/headerWithSidebar/index.jsx
@@ -1,9 +1,9 @@
"use client";
import ChevronRightIcon from "@mui/icons-material/ChevronRight";
import MenuIcon from "@mui/icons-material/Menu";
-import { Box, Drawer, IconButton, Stack, styled, Toolbar, Typography } from "@mui/material";
+import { Box, Drawer, IconButton, Stack, styled, Toolbar, Typography, useMediaQuery, useTheme } from "@mui/material";
import MuiAppBar from "@mui/material/AppBar";
-import { useState } from "react";
+import { useState, useEffect } from "react";
import HeaderMenu from "./HeaderMenu";
import moment from "jalali-moment";
import { headerMenu } from "@/core/utils/headerMenu";
@@ -56,7 +56,14 @@ const DrawerHeader = styled("div")(({ theme }) => ({
}));
const HeaderWithSidebar = ({ children }) => {
- const [open, setOpen] = useState(true);
+ const theme = useTheme();
+ const isMobile = useMediaQuery(theme.breakpoints.down("md")); // Detect mobile/tablet screen
+ const [open, setOpen] = useState(!isMobile); // Initial state based on screen size
+
+ useEffect(() => {
+ setOpen(!isMobile); // Toggle state when screen size changes
+ }, [isMobile]);
+
const now = moment().locale("fa").format("YYYY/MM/DD");
const handleDrawerOpen = () => {
@@ -65,6 +72,7 @@ const HeaderWithSidebar = ({ children }) => {
const handleDrawerClose = () => {
setOpen(false);
};
+
return (
{
>
-
+
{headerMenu.map((menu) => (
))}
@@ -132,4 +151,5 @@ const HeaderWithSidebar = ({ children }) => {
);
};
+
export default HeaderWithSidebar;
diff --git a/src/core/components/LoadingHardPage.jsx b/src/core/components/LoadingHardPage.jsx
new file mode 100644
index 0000000..c126c5c
--- /dev/null
+++ b/src/core/components/LoadingHardPage.jsx
@@ -0,0 +1,65 @@
+import { Backdrop, Box, Stack, styled, Typography, useTheme } from "@mui/material";
+import SvgLoading from "@/core/components/svgs/SvgLoading";
+import SvgError from "@/core/components/svgs/SvgError";
+
+const LoadingImage = styled(Box)({
+ "@keyframes load": {
+ "0%": {
+ // opacity: 0,
+ transform: "scale(1)",
+ },
+ "50%": {
+ // opacity: 1,
+ transform: "scale(.5)",
+ },
+ "100%": {
+ // opacity: 0,
+ transform: "scale(1)",
+ },
+ },
+ animation: "load 2s infinite",
+});
+
+const LoadingHardPage = ({
+ children,
+ loading,
+ authState,
+ sx = {},
+ icon = null,
+ width = 200,
+ height = 200,
+ label = "",
+}) => {
+ const theme = useTheme();
+ return (
+ <>
+ theme.zIndex.drawer + 1, ...sx }} open={loading}>
+
+ {authState ? (
+
+ {icon ? (
+ {icon}
+ ) : (
+
+ )}
+
+ ) : (
+
+ {icon ? (
+ {icon}
+ ) : (
+
+ )}
+
+ )}
+
+ {label}
+
+
+
+ {children}
+ >
+ );
+};
+
+export default LoadingHardPage;
diff --git a/src/core/components/MuiDatePicker.jsx b/src/core/components/MuiDatePicker.jsx
index 31c25ba..1be8fdb 100644
--- a/src/core/components/MuiDatePicker.jsx
+++ b/src/core/components/MuiDatePicker.jsx
@@ -2,20 +2,13 @@ import { LocalizationProvider, MobileDateTimePicker } from "@mui/x-date-pickers"
import { AdapterDateFnsJalali } from "@mui/x-date-pickers/AdapterDateFnsJalali";
import moment from "jalali-moment";
import { faIR } from "@mui/x-date-pickers/locales";
-import {Box, IconButton, FormControl, FormHelperText, InputAdornment, Stack} from "@mui/material";
+import { Box, IconButton, FormControl, FormHelperText, InputAdornment, Stack } from "@mui/material";
import ClearIcon from "@mui/icons-material/Clear";
-export default function PickerWithButtonField({
- formik,
- name,
- value,
- error,
- touched,
- disabled,
- }) {
+export default function PickerWithButtonField({ formik, name, value, error, touched, disabled }) {
return (
-
+
{
- const { getUser } = useAuth();
+ const { getUser, logout } = useAuth();
+ const requestServer = useRequest();
const [openUpdate, setOpenUpdate] = useState(false);
const [openChangePass, setOpenChangePass] = useState(false);
@@ -18,6 +20,11 @@ const ProfileActions = () => {
getUser();
setOpenUpdate(true);
};
+ const handleLogout = () => {
+ requestServer(LOGOUT_USER_ROUTE, "post").then(() => {
+ logout();
+ });
+ };
return (
<>
@@ -29,7 +36,7 @@ const ProfileActions = () => {
-
+
diff --git a/src/core/components/svgs/SvgError.jsx b/src/core/components/svgs/SvgError.jsx
new file mode 100644
index 0000000..3c41d22
--- /dev/null
+++ b/src/core/components/svgs/SvgError.jsx
@@ -0,0 +1,94 @@
+import { useTheme } from "@mui/material";
+
+const SvgError = ({ width, height, color = null }) => {
+ const theme = useTheme();
+ const fillColor = color || theme.palette.primary.main;
+
+ return (
+
+ );
+};
+
+export default SvgError;
diff --git a/src/core/components/svgs/SvgLoading.jsx b/src/core/components/svgs/SvgLoading.jsx
new file mode 100644
index 0000000..ced3cd1
--- /dev/null
+++ b/src/core/components/svgs/SvgLoading.jsx
@@ -0,0 +1,292 @@
+import { useTheme } from "@mui/material";
+
+const SvgLoading = ({ width, height, color = null }) => {
+ const theme = useTheme();
+ const fillColor = color || theme.palette.primary.main;
+
+ return (
+
+ );
+};
+
+export default SvgLoading;
diff --git a/src/core/components/svgs/SvgNotFound.jsx b/src/core/components/svgs/SvgNotFound.jsx
new file mode 100644
index 0000000..5d16909
--- /dev/null
+++ b/src/core/components/svgs/SvgNotFound.jsx
@@ -0,0 +1,162 @@
+import { useTheme } from "@mui/material";
+
+const SvgNotFound = ({ width, height, color = null }) => {
+ const theme = useTheme();
+ const fillColor = color || theme.palette.primary.main;
+
+ return (
+
+ );
+};
+export default SvgNotFound;
diff --git a/src/core/utils/pageMenu.js b/src/core/utils/pageMenu.js
index 39e4120..8118ebf 100644
--- a/src/core/utils/pageMenu.js
+++ b/src/core/utils/pageMenu.js
@@ -15,11 +15,11 @@ import AdminPanelSettingsIcon from "@mui/icons-material/AdminPanelSettings";
import MapIcon from "@mui/icons-material/Map";
import SpeedIcon from "@mui/icons-material/Speed";
import EngineeringIcon from "@mui/icons-material/Engineering";
-import ReportIcon from "@mui/icons-material/Report";
+import AssessmentIcon from "@mui/icons-material/Assessment";
import AssignmentLateIcon from "@mui/icons-material/AssignmentLate";
import LineAxisIcon from "@mui/icons-material/LineAxis";
import ScienceIcon from "@mui/icons-material/Science";
-import DriveEtaIcon from '@mui/icons-material/DriveEta';
+import DriveEtaIcon from "@mui/icons-material/DriveEta";
import BiotechIcon from "@mui/icons-material/Biotech";
export const pageMenu = [
@@ -157,7 +157,7 @@ export const pageMenu = [
label: "گزارش ها",
type: "page",
route: process.env.NEXT_PUBLIC_API_URL + "/v2/road_items/report",
- icon: ,
+ icon: ,
permissions: [
"show-road-item-supervise-cartable",
"show-road-item-supervise-cartable-province",
@@ -235,7 +235,7 @@ export const pageMenu = [
label: "گزارش ها",
type: "page",
route: process.env.NEXT_PUBLIC_API_URL + "/v2/road_patrols/report",
- icon: ,
+ icon: ,
permissions: [
"add-road-patrol",
"show-road-patrol-supervise-cartable",
@@ -334,7 +334,7 @@ export const pageMenu = [
label: "گزارش ها",
type: "page",
route: process.env.NEXT_PUBLIC_API_URL + "/v2/safety_and_privacy/report",
- icon: ,
+ icon: ,
permissions: [
"show-safety-and-privacy-operator-cartable",
"show-safety-and-privacy-operator-cartable-province",
@@ -407,7 +407,7 @@ export const pageMenu = [
label: "گزارش ها",
type: "page",
route: process.env.NEXT_PUBLIC_API_URL + "/v2/road_observations/report/index",
- icon: ,
+ icon: ,
permissions: ["all"],
},
],
@@ -440,7 +440,7 @@ export const pageMenu = [
label: "گزارش ها",
type: "page",
route: process.env.NEXT_PUBLIC_API_URL + "/v2/map?type=campNew",
- icon: ,
+ icon: ,
permissions: ["show-camp", "show-camp-province"],
},
],
@@ -473,7 +473,7 @@ export const pageMenu = [
label: "گزارش ها",
type: "page",
route: process.env.NEXT_PUBLIC_API_URL + "/v2/receipt/report",
- icon: ,
+ icon: ,
permissions: ["all"],
},
],
diff --git a/src/core/utils/routes.js b/src/core/utils/routes.js
index 2fa0137..8dcba92 100644
--- a/src/core/utils/routes.js
+++ b/src/core/utils/routes.js
@@ -1,6 +1,7 @@
const api = process.env.NEXT_PUBLIC_API_URL;
export const GET_USER_ROUTE = api + "/api/v3/profile/info";
+export const LOGOUT_USER_ROUTE = api + "/api/v3/logout";
export const UPDATE_USER_ROUTE = api + "/api/v3/profile/edit";
export const CHANGE_USER_PASSWORD = api + "/api/v3/profile/change_password";
export const GET_LOGIN_ROUTE = api + "/login_dev";
diff --git a/src/lib/contexts/auth.js b/src/lib/contexts/auth.js
index edc83fb..818daaf 100644
--- a/src/lib/contexts/auth.js
+++ b/src/lib/contexts/auth.js
@@ -1,7 +1,7 @@
"use client";
import { GET_USER_ROUTE } from "@/core/utils/routes";
import axios from "axios";
-import { createContext, useCallback, useContext, useEffect, useReducer } from "react";
+import { createContext, useCallback, useContext, useEffect, useReducer, useState } from "react";
const initAuth = {
initAuthState: false,
@@ -27,6 +27,7 @@ const authReducer = (state, action) => {
const AuthContext = createContext();
export const AuthProvider = ({ children }) => {
+ const [errorState, setErrorState] = useState(null);
const [state, dispatch] = useReducer(authReducer, initAuth);
const clearUser = useCallback(() => {
@@ -57,12 +58,15 @@ export const AuthProvider = ({ children }) => {
changeUser(data.data);
changeAuthState(true);
changeInitAuth(true);
+ setErrorState(false);
} catch (error) {
if (error.response && error.response.status === 401) {
clearUser();
changeAuthState(false);
changeInitAuth(true);
+ return;
}
+ setErrorState(true);
}
}, [clearUser, changeUser, changeAuthState, changeInitAuth]);
@@ -78,6 +82,7 @@ export const AuthProvider = ({ children }) => {
initAuthState: state.initAuthState,
getUser,
logout,
+ errorState,
}}
>
{children}