Merge branch 'release/v2.4.1'
This commit is contained in:
5
.env.example
Normal file
5
.env.example
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
NEXT_PUBLIC_VERSION = "2.4.1"
|
||||||
|
|
||||||
|
NEXT_PUBLIC_API_URL = "http://192.168.1.20:8888/server"
|
||||||
|
NEXT_PUBLIC_SERVER_SOCKET_URL = "ws://192.168.1.20:3023"
|
||||||
|
NEXT_PUBLIC_POWERED_BY_URL = "https://witel.ir"
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -31,7 +31,7 @@ yarn-error.log*
|
|||||||
.pnpm-debug.log*
|
.pnpm-debug.log*
|
||||||
|
|
||||||
# env files (can opt-in for committing if needed)
|
# env files (can opt-in for committing if needed)
|
||||||
.env*
|
.env
|
||||||
|
|
||||||
# vercel
|
# vercel
|
||||||
.vercel
|
.vercel
|
||||||
@@ -39,3 +39,4 @@ yarn-error.log*
|
|||||||
# typescript
|
# typescript
|
||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
next-env.d.ts
|
next-env.d.ts
|
||||||
|
/.idea
|
||||||
|
|||||||
12
.prettierrc
Normal file
12
.prettierrc
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"printWidth": 120,
|
||||||
|
"tabWidth": 4,
|
||||||
|
"useTabs": false,
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": false,
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"bracketSpacing": true,
|
||||||
|
"jsxBracketSameLine": false,
|
||||||
|
"arrowParens": "always",
|
||||||
|
"endOfLine": "crlf"
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
version: '3.8'
|
version: "3.8"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
nextjs:
|
nextjs:
|
||||||
@@ -13,5 +13,5 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "3000:3000"
|
- "3000:3000"
|
||||||
env_file:
|
env_file:
|
||||||
- .env.local
|
- .env
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint",
|
"lint": "next lint",
|
||||||
"format": "prettier --write \"src/**/*.{js,jsx,ts,tsx,html,css,scss}\""
|
"format": "npx prettier . --write"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emotion/cache": "^11.14.0",
|
"@emotion/cache": "^11.14.0",
|
||||||
@@ -19,6 +19,7 @@
|
|||||||
"@mui/material-nextjs": "^7.0.2",
|
"@mui/material-nextjs": "^7.0.2",
|
||||||
"@mui/x-date-pickers": "^8.2.0",
|
"@mui/x-date-pickers": "^8.2.0",
|
||||||
"@mui/x-tree-view": "^8.2.0",
|
"@mui/x-tree-view": "^8.2.0",
|
||||||
|
"@wavesurfer/react": "^1.0.11",
|
||||||
"axios": "^1.9.0",
|
"axios": "^1.9.0",
|
||||||
"date-fns-jalali": "4.1.0-0",
|
"date-fns-jalali": "4.1.0-0",
|
||||||
"jalali-moment": "^3.3.11",
|
"jalali-moment": "^3.3.11",
|
||||||
@@ -34,6 +35,7 @@
|
|||||||
"stylis": "^4.3.6",
|
"stylis": "^4.3.6",
|
||||||
"stylis-plugin-rtl": "^2.1.1",
|
"stylis-plugin-rtl": "^2.1.1",
|
||||||
"swr": "^2.3.3",
|
"swr": "^2.3.3",
|
||||||
|
"wavesurfer.js": "^7.10.1",
|
||||||
"yup": "^1.6.1"
|
"yup": "^1.6.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
5000
pnpm-lock.yaml
generated
5000
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
BIN
public/audio/file_example_WAV_1MG.wav
Normal file
BIN
public/audio/file_example_WAV_1MG.wav
Normal file
Binary file not shown.
@@ -0,0 +1,16 @@
|
|||||||
|
import WithPermission from "@/core/middlewares/withPermission";
|
||||||
|
import OperatorDialoguePage from "@/components/dashboard/monitoring/operator/OperatorDialogue";
|
||||||
|
|
||||||
|
export const metadata = {
|
||||||
|
title: "پیام های سیستمی",
|
||||||
|
};
|
||||||
|
|
||||||
|
const Page = () => {
|
||||||
|
return (
|
||||||
|
<WithPermission permission_name={["all"]}>
|
||||||
|
<OperatorDialoguePage />
|
||||||
|
</WithPermission>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Page;
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import WithPermission from "@/core/middlewares/withPermission";
|
||||||
|
import OperatorDialoguePage from "@/components/dashboard/monitoring/supervisor/OperatorDialogue";
|
||||||
|
|
||||||
|
export const metadata = {
|
||||||
|
title: "ارزیابی پیام های سیستمی",
|
||||||
|
};
|
||||||
|
|
||||||
|
const Page = () => {
|
||||||
|
return (
|
||||||
|
<WithPermission permission_name={["all"]}>
|
||||||
|
<OperatorDialoguePage />
|
||||||
|
</WithPermission>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Page;
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import WithPermission from "@/core/middlewares/withPermission";
|
||||||
|
import PeopleMessagePage from "@/components/dashboard/monitoring/operator/PeopleMessage";
|
||||||
|
|
||||||
|
export const metadata = {
|
||||||
|
title: "پیام های سیستمی",
|
||||||
|
};
|
||||||
|
|
||||||
|
const Page = () => {
|
||||||
|
return (
|
||||||
|
<WithPermission permission_name={["all"]}>
|
||||||
|
<PeopleMessagePage />
|
||||||
|
</WithPermission>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Page;
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import WithPermission from "@/core/middlewares/withPermission";
|
||||||
|
import PeopleMessagePage from "@/components/dashboard/monitoring/supervisor/PeopleMessage";
|
||||||
|
|
||||||
|
export const metadata = {
|
||||||
|
title: "پیام های سیستمی",
|
||||||
|
};
|
||||||
|
|
||||||
|
const Page = () => {
|
||||||
|
return (
|
||||||
|
<WithPermission permission_name={["all"]}>
|
||||||
|
<PeopleMessagePage />
|
||||||
|
</WithPermission>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Page;
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import WithPermission from "@/core/middlewares/withPermission";
|
||||||
|
import SystemMessagesPage from "@/components/dashboard/monitoring/operator/SystemMessage";
|
||||||
|
|
||||||
|
export const metadata = {
|
||||||
|
title: "پیام های سیستمی",
|
||||||
|
};
|
||||||
|
|
||||||
|
const Page = () => {
|
||||||
|
return (
|
||||||
|
<WithPermission permission_name={["all"]}>
|
||||||
|
<SystemMessagesPage />
|
||||||
|
</WithPermission>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Page;
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import WithPermission from "@/core/middlewares/withPermission";
|
||||||
|
import SystemMessagesPage from "@/components/dashboard/monitoring/supervisor/SystemMessages";
|
||||||
|
|
||||||
|
export const metadata = {
|
||||||
|
title: "پیام های سیستمی",
|
||||||
|
};
|
||||||
|
|
||||||
|
const Page = () => {
|
||||||
|
return (
|
||||||
|
<WithPermission permission_name={["all"]}>
|
||||||
|
<SystemMessagesPage />
|
||||||
|
</WithPermission>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Page;
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import ProvinceCumulativeCallsReportPage from "@/components/dashboard/provinceReports/ProvinceCumulativeCallsReport";
|
||||||
|
import WithPermission from "@/core/middlewares/withPermission";
|
||||||
|
|
||||||
|
export const metadata = {
|
||||||
|
title: "گزارشات",
|
||||||
|
};
|
||||||
|
|
||||||
|
const Page = () => {
|
||||||
|
return (
|
||||||
|
<WithPermission permission_name={["all"]}>
|
||||||
|
<ProvinceCumulativeCallsReportPage />
|
||||||
|
</WithPermission>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Page;
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import ProvinceDetailedLogsReportPage from "@/components/dashboard/provinceReports/ProvinceDetailedLogsReport";
|
||||||
|
import WithPermission from "@/core/middlewares/withPermission";
|
||||||
|
|
||||||
|
export const metadata = {
|
||||||
|
title: "ریز گزارشات",
|
||||||
|
};
|
||||||
|
|
||||||
|
const Page = () => {
|
||||||
|
return (
|
||||||
|
<WithPermission permission_name={["all"]}>
|
||||||
|
<ProvinceDetailedLogsReportPage />
|
||||||
|
</WithPermission>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Page;
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import ProvincePeopleMessagesReportPage from "@/components/dashboard/provinceReports/ProvincePeopleMessagesReport";
|
||||||
|
import WithPermission from "@/core/middlewares/withPermission";
|
||||||
|
|
||||||
|
export const metadata = {
|
||||||
|
title: "گزارش پیامها",
|
||||||
|
};
|
||||||
|
|
||||||
|
const Page = () => {
|
||||||
|
return (
|
||||||
|
<WithPermission permission_name={["all"]}>
|
||||||
|
<ProvincePeopleMessagesReportPage />
|
||||||
|
</WithPermission>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Page;
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import ProvinceOperatorPerformanceReportPage from "@/components/dashboard/provinceReports/ProvinceOperatorPerformanceReport";
|
||||||
|
import WithPermission from "@/core/middlewares/withPermission";
|
||||||
|
|
||||||
|
export const metadata = {
|
||||||
|
title: "عملکرد کارشناسان",
|
||||||
|
};
|
||||||
|
|
||||||
|
const Page = () => {
|
||||||
|
return (
|
||||||
|
<WithPermission permission_name={["all"]}>
|
||||||
|
<ProvinceOperatorPerformanceReportPage />
|
||||||
|
</WithPermission>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Page;
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import WithPermission from "@/core/middlewares/withPermission";
|
||||||
|
import CountryCallsReportPage from "@/components/dashboard/reports/CountryCallsReport";
|
||||||
|
|
||||||
|
export const metadata = {
|
||||||
|
title: "گزارشات",
|
||||||
|
};
|
||||||
|
|
||||||
|
const Page = () => {
|
||||||
|
return (
|
||||||
|
<WithPermission permission_name={["all"]}>
|
||||||
|
<CountryCallsReportPage />
|
||||||
|
</WithPermission>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Page;
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import WithPermission from "@/core/middlewares/withPermission";
|
||||||
|
import CountryKeyPressReportPage from "@/components/dashboard/reports/CountryKeyPressReport";
|
||||||
|
|
||||||
|
export const metadata = {
|
||||||
|
title: "گزارشات",
|
||||||
|
};
|
||||||
|
|
||||||
|
const Page = () => {
|
||||||
|
return (
|
||||||
|
<WithPermission permission_name={["all"]}>
|
||||||
|
<CountryKeyPressReportPage />
|
||||||
|
</WithPermission>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Page;
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import WithPermission from "@/core/middlewares/withPermission";
|
||||||
|
import CountryMessageRatingReportPage from "@/components/dashboard/reports/CountryMessageRatingReport";
|
||||||
|
|
||||||
|
export const metadata = {
|
||||||
|
title: "گزارشات",
|
||||||
|
};
|
||||||
|
|
||||||
|
const Page = () => {
|
||||||
|
return (
|
||||||
|
<WithPermission permission_name={["all"]}>
|
||||||
|
<CountryMessageRatingReportPage />
|
||||||
|
</WithPermission>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Page;
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import WithPermission from "@/core/middlewares/withPermission";
|
||||||
|
import CountryVoiceRatingReportPage from "@/components/dashboard/reports/CountryVoiceRatingReport";
|
||||||
|
|
||||||
|
export const metadata = {
|
||||||
|
title: "گزارشات",
|
||||||
|
};
|
||||||
|
|
||||||
|
const Page = () => {
|
||||||
|
return (
|
||||||
|
<WithPermission permission_name={["all"]}>
|
||||||
|
<CountryVoiceRatingReportPage />
|
||||||
|
</WithPermission>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Page;
|
||||||
16
src/app/(withAuth)/(dashboardLayout)/dashboard/roles/page.js
Normal file
16
src/app/(withAuth)/(dashboardLayout)/dashboard/roles/page.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import RolesPage from "@/components/dashboard/Roles";
|
||||||
|
import WithPermission from "@/core/middlewares/withPermission";
|
||||||
|
|
||||||
|
export const metadata = {
|
||||||
|
title: "نقش ها",
|
||||||
|
};
|
||||||
|
|
||||||
|
const Page = () => {
|
||||||
|
return (
|
||||||
|
<WithPermission permission_name={["manage_roles"]}>
|
||||||
|
<RolesPage />
|
||||||
|
</WithPermission>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Page;
|
||||||
@@ -9,9 +9,7 @@ import { SWRConfig } from "swr";
|
|||||||
|
|
||||||
const Layout = ({ children }) => {
|
const Layout = ({ children }) => {
|
||||||
const { data: permissions } = usePermissions();
|
const { data: permissions } = usePermissions();
|
||||||
const isCallWidgetEnabled = permissions?.some(
|
const isCallWidgetEnabled = permissions?.some((permission) => permission === "manage_calls");
|
||||||
(permission) => permission === "manage_calls",
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SocketProvider>
|
<SocketProvider>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import WithoutAuthMiddleware from "@/core/middlewares/withoutAuth";
|
import WithoutAuthMiddleware from "@/core/middlewares/withoutAuth";
|
||||||
import { Suspense } from "react";
|
import { Suspense } from "react";
|
||||||
|
|
||||||
|
|||||||
10
src/app/(withoutAuth)/teleport/page.js
Normal file
10
src/app/(withoutAuth)/teleport/page.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import TeleportPage from "@/components/Teleport";
|
||||||
|
|
||||||
|
export const metadata = {
|
||||||
|
title: "درحال انتقال به استان مورد نظر",
|
||||||
|
};
|
||||||
|
|
||||||
|
const Page = () => {
|
||||||
|
return <TeleportPage />;
|
||||||
|
};
|
||||||
|
export default Page;
|
||||||
@@ -18,10 +18,7 @@ export default function RootLayout({ children }) {
|
|||||||
<link rel="icon" href={favicon.src} type="image/png" sizes="any" />
|
<link rel="icon" href={favicon.src} type="image/png" sizes="any" />
|
||||||
</head>
|
</head>
|
||||||
<body style={{ height: "100vh", width: "100vw" }}>
|
<body style={{ height: "100vh", width: "100vw" }}>
|
||||||
<AppRouterCacheProvider
|
<AppRouterCacheProvider CacheProvider={Rtl} options={{ enableCssLayer: true }}>
|
||||||
CacheProvider={Rtl}
|
|
||||||
options={{ enableCssLayer: true }}
|
|
||||||
>
|
|
||||||
<AuthProvider>
|
<AuthProvider>
|
||||||
<TableSettingProvider>{children}</TableSettingProvider>
|
<TableSettingProvider>{children}</TableSettingProvider>
|
||||||
</AuthProvider>
|
</AuthProvider>
|
||||||
|
|||||||
@@ -4,12 +4,7 @@ import SvgNotFound from "@/core/components/svgs/SvgNotFound";
|
|||||||
|
|
||||||
export default function NotFound() {
|
export default function NotFound() {
|
||||||
return (
|
return (
|
||||||
<Stack
|
<Stack sx={{ height: "100%" }} justifyContent={"center"} alignItems={"center"} spacing={2}>
|
||||||
sx={{ height: "100%" }}
|
|
||||||
justifyContent={"center"}
|
|
||||||
alignItems={"center"}
|
|
||||||
spacing={2}
|
|
||||||
>
|
|
||||||
<Box>
|
<Box>
|
||||||
<SvgNotFound width={200} height={200} />
|
<SvgNotFound width={200} height={200} />
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -4,14 +4,10 @@
|
|||||||
font-weight: 900;
|
font-weight: 900;
|
||||||
src: url("../fonts/eot/IRANSansWeb(FaNum)_Black.eot");
|
src: url("../fonts/eot/IRANSansWeb(FaNum)_Black.eot");
|
||||||
src:
|
src:
|
||||||
url("../fonts/eot/IRANSansWeb(FaNum)_Black.eot?#iefix")
|
url("../fonts/eot/IRANSansWeb(FaNum)_Black.eot?#iefix") format("embedded-opentype"),
|
||||||
format("embedded-opentype"),
|
/* IE6-8 */ url("../fonts/woff2/IRANSansWeb(FaNum)_Black.woff2") format("woff2"),
|
||||||
/* IE6-8 */ url("../fonts/woff2/IRANSansWeb(FaNum)_Black.woff2")
|
/* FF39+,Chrome36+, Opera24+*/ url("../fonts/woff/IRANSansWeb(FaNum)_Black.woff") format("woff"),
|
||||||
format("woff2"),
|
/* FF3.6+, IE9, Chrome6+, Saf5.1+*/ url("../fonts/ttf/IRANSansWeb(FaNum)_Black.ttf") format("truetype");
|
||||||
/* FF39+,Chrome36+, Opera24+*/
|
|
||||||
url("../fonts/woff/IRANSansWeb(FaNum)_Black.woff") format("woff"),
|
|
||||||
/* FF3.6+, IE9, Chrome6+, Saf5.1+*/
|
|
||||||
url("../fonts/ttf/IRANSansWeb(FaNum)_Black.ttf") format("truetype");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
@@ -20,14 +16,10 @@
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
src: url("../fonts/eot/IRANSansWeb(FaNum)_Bold.eot");
|
src: url("../fonts/eot/IRANSansWeb(FaNum)_Bold.eot");
|
||||||
src:
|
src:
|
||||||
url("../fonts/eot/IRANSansWeb(FaNum)_Bold.eot?#iefix")
|
url("../fonts/eot/IRANSansWeb(FaNum)_Bold.eot?#iefix") format("embedded-opentype"),
|
||||||
format("embedded-opentype"),
|
/* IE6-8 */ url("../fonts/woff2/IRANSansWeb(FaNum)_Bold.woff2") format("woff2"),
|
||||||
/* IE6-8 */ url("../fonts/woff2/IRANSansWeb(FaNum)_Bold.woff2")
|
/* FF39+,Chrome36+, Opera24+*/ url("../fonts/woff/IRANSansWeb(FaNum)_Bold.woff") format("woff"),
|
||||||
format("woff2"),
|
/* FF3.6+, IE9, Chrome6+, Saf5.1+*/ url("../fonts/ttf/IRANSansWeb(FaNum)_Bold.ttf") format("truetype");
|
||||||
/* FF39+,Chrome36+, Opera24+*/
|
|
||||||
url("../fonts/woff/IRANSansWeb(FaNum)_Bold.woff") format("woff"),
|
|
||||||
/* FF3.6+, IE9, Chrome6+, Saf5.1+*/
|
|
||||||
url("../fonts/ttf/IRANSansWeb(FaNum)_Bold.ttf") format("truetype");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
@@ -36,14 +28,10 @@
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
src: url("../fonts/eot/IRANSansWeb(FaNum)_Medium.eot");
|
src: url("../fonts/eot/IRANSansWeb(FaNum)_Medium.eot");
|
||||||
src:
|
src:
|
||||||
url("../fonts/eot/IRANSansWeb(FaNum)_Medium.eot?#iefix")
|
url("../fonts/eot/IRANSansWeb(FaNum)_Medium.eot?#iefix") format("embedded-opentype"),
|
||||||
format("embedded-opentype"),
|
/* IE6-8 */ url("../fonts/woff2/IRANSansWeb(FaNum)_Medium.woff2") format("woff2"),
|
||||||
/* IE6-8 */ url("../fonts/woff2/IRANSansWeb(FaNum)_Medium.woff2")
|
/* FF39+,Chrome36+, Opera24+*/ url("../fonts/woff/IRANSansWeb(FaNum)_Medium.woff") format("woff"),
|
||||||
format("woff2"),
|
/* FF3.6+, IE9, Chrome6+, Saf5.1+*/ url("../fonts/ttf/IRANSansWeb(FaNum)_Medium.ttf") format("truetype");
|
||||||
/* FF39+,Chrome36+, Opera24+*/
|
|
||||||
url("../fonts/woff/IRANSansWeb(FaNum)_Medium.woff") format("woff"),
|
|
||||||
/* FF3.6+, IE9, Chrome6+, Saf5.1+*/
|
|
||||||
url("../fonts/ttf/IRANSansWeb(FaNum)_Medium.ttf") format("truetype");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
@@ -52,14 +40,10 @@
|
|||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
src: url("../fonts/eot/IRANSansWeb(FaNum)_Light.eot");
|
src: url("../fonts/eot/IRANSansWeb(FaNum)_Light.eot");
|
||||||
src:
|
src:
|
||||||
url("../fonts/eot/IRANSansWeb(FaNum)_Light.eot?#iefix")
|
url("../fonts/eot/IRANSansWeb(FaNum)_Light.eot?#iefix") format("embedded-opentype"),
|
||||||
format("embedded-opentype"),
|
/* IE6-8 */ url("../fonts/woff2/IRANSansWeb(FaNum)_Light.woff2") format("woff2"),
|
||||||
/* IE6-8 */ url("../fonts/woff2/IRANSansWeb(FaNum)_Light.woff2")
|
/* FF39+,Chrome36+, Opera24+*/ url("../fonts/woff/IRANSansWeb(FaNum)_Light.woff") format("woff"),
|
||||||
format("woff2"),
|
/* FF3.6+, IE9, Chrome6+, Saf5.1+*/ url("../fonts/ttf/IRANSansWeb(FaNum)_Light.ttf") format("truetype");
|
||||||
/* FF39+,Chrome36+, Opera24+*/
|
|
||||||
url("../fonts/woff/IRANSansWeb(FaNum)_Light.woff") format("woff"),
|
|
||||||
/* FF3.6+, IE9, Chrome6+, Saf5.1+*/
|
|
||||||
url("../fonts/ttf/IRANSansWeb(FaNum)_Light.ttf") format("truetype");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
@@ -68,14 +52,10 @@
|
|||||||
font-weight: 200;
|
font-weight: 200;
|
||||||
src: url("../fonts/eot/IRANSansWeb(FaNum)_UltraLight.eot");
|
src: url("../fonts/eot/IRANSansWeb(FaNum)_UltraLight.eot");
|
||||||
src:
|
src:
|
||||||
url("../fonts/eot/IRANSansWeb(FaNum)_UltraLight.eot?#iefix")
|
url("../fonts/eot/IRANSansWeb(FaNum)_UltraLight.eot?#iefix") format("embedded-opentype"),
|
||||||
format("embedded-opentype"),
|
/* IE6-8 */ url("../fonts/woff2/IRANSansWeb(FaNum)_UltraLight.woff2") format("woff2"),
|
||||||
/* IE6-8 */ url("../fonts/woff2/IRANSansWeb(FaNum)_UltraLight.woff2")
|
/* FF39+,Chrome36+, Opera24+*/ url("../fonts/woff/IRANSansWeb(FaNum)_UltraLight.woff") format("woff"),
|
||||||
format("woff2"),
|
/* FF3.6+, IE9, Chrome6+, Saf5.1+*/ url("../fonts/ttf/IRANSansWeb(FaNum)_UltraLight.ttf") format("truetype");
|
||||||
/* FF39+,Chrome36+, Opera24+*/
|
|
||||||
url("../fonts/woff/IRANSansWeb(FaNum)_UltraLight.woff") format("woff"),
|
|
||||||
/* FF3.6+, IE9, Chrome6+, Saf5.1+*/
|
|
||||||
url("../fonts/ttf/IRANSansWeb(FaNum)_UltraLight.ttf") format("truetype");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
@@ -84,13 +64,10 @@
|
|||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
src: url("../fonts/eot/IRANSansWeb(FaNum).eot");
|
src: url("../fonts/eot/IRANSansWeb(FaNum).eot");
|
||||||
src:
|
src:
|
||||||
url("../fonts/eot/IRANSansWeb(FaNum).eot?#iefix")
|
url("../fonts/eot/IRANSansWeb(FaNum).eot?#iefix") format("embedded-opentype"),
|
||||||
format("embedded-opentype"),
|
|
||||||
/* IE6-8 */ url("../fonts/woff2/IRANSansWeb(FaNum).woff2") format("woff2"),
|
/* IE6-8 */ url("../fonts/woff2/IRANSansWeb(FaNum).woff2") format("woff2"),
|
||||||
/* FF39+,Chrome36+, Opera24+*/ url("../fonts/woff/IRANSansWeb(FaNum).woff")
|
/* FF39+,Chrome36+, Opera24+*/ url("../fonts/woff/IRANSansWeb(FaNum).woff") format("woff"),
|
||||||
format("woff"),
|
/* FF3.6+, IE9, Chrome6+, Saf5.1+*/ url("../fonts/ttf/IRANSansWeb(FaNum).ttf") format("truetype");
|
||||||
/* FF3.6+, IE9, Chrome6+, Saf5.1+*/
|
|
||||||
url("../fonts/ttf/IRANSansWeb(FaNum).ttf") format("truetype");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
@@ -101,10 +78,8 @@
|
|||||||
src:
|
src:
|
||||||
url("../fonts/eot/IRANSansWeb_Black.eot?#iefix") format("embedded-opentype"),
|
url("../fonts/eot/IRANSansWeb_Black.eot?#iefix") format("embedded-opentype"),
|
||||||
/* IE6-8 */ url("../fonts/woff2/IRANSansWeb_Black.woff2") format("woff2"),
|
/* IE6-8 */ url("../fonts/woff2/IRANSansWeb_Black.woff2") format("woff2"),
|
||||||
/* FF39+,Chrome36+, Opera24+*/ url("../fonts/woff/IRANSansWeb_Black.woff")
|
/* FF39+,Chrome36+, Opera24+*/ url("../fonts/woff/IRANSansWeb_Black.woff") format("woff"),
|
||||||
format("woff"),
|
/* FF3.6+, IE9, Chrome6+, Saf5.1+*/ url("../fonts/ttf/IRANSansWeb_Black.ttf") format("truetype");
|
||||||
/* FF3.6+, IE9, Chrome6+, Saf5.1+*/
|
|
||||||
url("../fonts/ttf/IRANSansWeb_Black.ttf") format("truetype");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
@@ -115,10 +90,8 @@
|
|||||||
src:
|
src:
|
||||||
url("../fonts/eot/IRANSansWeb_Bold.eot?#iefix") format("embedded-opentype"),
|
url("../fonts/eot/IRANSansWeb_Bold.eot?#iefix") format("embedded-opentype"),
|
||||||
/* IE6-8 */ url("../fonts/woff2/IRANSansWeb_Bold.woff2") format("woff2"),
|
/* IE6-8 */ url("../fonts/woff2/IRANSansWeb_Bold.woff2") format("woff2"),
|
||||||
/* FF39+,Chrome36+, Opera24+*/ url("../fonts/woff/IRANSansWeb_Bold.woff")
|
/* FF39+,Chrome36+, Opera24+*/ url("../fonts/woff/IRANSansWeb_Bold.woff") format("woff"),
|
||||||
format("woff"),
|
/* FF3.6+, IE9, Chrome6+, Saf5.1+*/ url("../fonts/ttf/IRANSansWeb_Bold.ttf") format("truetype");
|
||||||
/* FF3.6+, IE9, Chrome6+, Saf5.1+*/ url("../fonts/ttf/IRANSansWeb_Bold.ttf")
|
|
||||||
format("truetype");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
@@ -127,13 +100,10 @@
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
src: url("../fonts/eot/IRANSansWeb_Medium.eot");
|
src: url("../fonts/eot/IRANSansWeb_Medium.eot");
|
||||||
src:
|
src:
|
||||||
url("../fonts/eot/IRANSansWeb_Medium.eot?#iefix")
|
url("../fonts/eot/IRANSansWeb_Medium.eot?#iefix") format("embedded-opentype"),
|
||||||
format("embedded-opentype"),
|
|
||||||
/* IE6-8 */ url("../fonts/woff2/IRANSansWeb_Medium.woff2") format("woff2"),
|
/* IE6-8 */ url("../fonts/woff2/IRANSansWeb_Medium.woff2") format("woff2"),
|
||||||
/* FF39+,Chrome36+, Opera24+*/ url("../fonts/woff/IRANSansWeb_Medium.woff")
|
/* FF39+,Chrome36+, Opera24+*/ url("../fonts/woff/IRANSansWeb_Medium.woff") format("woff"),
|
||||||
format("woff"),
|
/* FF3.6+, IE9, Chrome6+, Saf5.1+*/ url("../fonts/ttf/IRANSansWeb_Medium.ttf") format("truetype");
|
||||||
/* FF3.6+, IE9, Chrome6+, Saf5.1+*/
|
|
||||||
url("../fonts/ttf/IRANSansWeb_Medium.ttf") format("truetype");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
@@ -144,10 +114,8 @@
|
|||||||
src:
|
src:
|
||||||
url("../fonts/eot/IRANSansWeb_Light.eot?#iefix") format("embedded-opentype"),
|
url("../fonts/eot/IRANSansWeb_Light.eot?#iefix") format("embedded-opentype"),
|
||||||
/* IE6-8 */ url("../fonts/woff2/IRANSansWeb_Light.woff2") format("woff2"),
|
/* IE6-8 */ url("../fonts/woff2/IRANSansWeb_Light.woff2") format("woff2"),
|
||||||
/* FF39+,Chrome36+, Opera24+*/ url("../fonts/woff/IRANSansWeb_Light.woff")
|
/* FF39+,Chrome36+, Opera24+*/ url("../fonts/woff/IRANSansWeb_Light.woff") format("woff"),
|
||||||
format("woff"),
|
/* FF3.6+, IE9, Chrome6+, Saf5.1+*/ url("../fonts/ttf/IRANSansWeb_Light.ttf") format("truetype");
|
||||||
/* FF3.6+, IE9, Chrome6+, Saf5.1+*/
|
|
||||||
url("../fonts/ttf/IRANSansWeb_Light.ttf") format("truetype");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
@@ -156,14 +124,10 @@
|
|||||||
font-weight: 200;
|
font-weight: 200;
|
||||||
src: url("../fonts/eot/IRANSansWeb_UltraLight.eot");
|
src: url("../fonts/eot/IRANSansWeb_UltraLight.eot");
|
||||||
src:
|
src:
|
||||||
url("../fonts/eot/IRANSansWeb_UltraLight.eot?#iefix")
|
url("../fonts/eot/IRANSansWeb_UltraLight.eot?#iefix") format("embedded-opentype"),
|
||||||
format("embedded-opentype"),
|
/* IE6-8 */ url("../fonts/woff2/IRANSansWeb_UltraLight.woff2") format("woff2"),
|
||||||
/* IE6-8 */ url("../fonts/woff2/IRANSansWeb_UltraLight.woff2")
|
/* FF39+,Chrome36+, Opera24+*/ url("../fonts/woff/IRANSansWeb_UltraLight.woff") format("woff"),
|
||||||
format("woff2"),
|
/* FF3.6+, IE9, Chrome6+, Saf5.1+*/ url("../fonts/ttf/IRANSansWeb_UltraLight.ttf") format("truetype");
|
||||||
/* FF39+,Chrome36+, Opera24+*/
|
|
||||||
url("../fonts/woff/IRANSansWeb_UltraLight.woff") format("woff"),
|
|
||||||
/* FF3.6+, IE9, Chrome6+, Saf5.1+*/
|
|
||||||
url("../fonts/ttf/IRANSansWeb_UltraLight.ttf") format("truetype");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
@@ -174,10 +138,8 @@
|
|||||||
src:
|
src:
|
||||||
url("../fonts/eot/IRANSansWeb.eot?#iefix") format("embedded-opentype"),
|
url("../fonts/eot/IRANSansWeb.eot?#iefix") format("embedded-opentype"),
|
||||||
/* IE6-8 */ url("../fonts/woff2/IRANSansWeb.woff2") format("woff2"),
|
/* IE6-8 */ url("../fonts/woff2/IRANSansWeb.woff2") format("woff2"),
|
||||||
/* FF39+,Chrome36+, Opera24+*/ url("../fonts/woff/IRANSansWeb.woff")
|
/* FF39+,Chrome36+, Opera24+*/ url("../fonts/woff/IRANSansWeb.woff") format("woff"),
|
||||||
format("woff"),
|
/* FF3.6+, IE9, Chrome6+, Saf5.1+*/ url("../fonts/ttf/IRANSansWeb.ttf") format("truetype");
|
||||||
/* FF3.6+, IE9, Chrome6+, Saf5.1+*/ url("../fonts/ttf/IRANSansWeb.ttf")
|
|
||||||
format("truetype");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
@@ -188,8 +150,7 @@
|
|||||||
src:
|
src:
|
||||||
url("../fonts/eot/Parastoo.eot?#iefix") format("embedded-opentype"),
|
url("../fonts/eot/Parastoo.eot?#iefix") format("embedded-opentype"),
|
||||||
/* IE6-8 */ url("../fonts/woff/Parastoo.woff") format("woff"),
|
/* IE6-8 */ url("../fonts/woff/Parastoo.woff") format("woff"),
|
||||||
/* FF3.6+, IE9, Chrome6+, Saf5.1+*/ url("../fonts/ttf/Parastoo.ttf")
|
/* FF3.6+, IE9, Chrome6+, Saf5.1+*/ url("../fonts/ttf/Parastoo.ttf") format("truetype");
|
||||||
format("truetype");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
@@ -200,8 +161,7 @@
|
|||||||
src:
|
src:
|
||||||
url("../fonts/eot/Parastoo-Bold.eot?#iefix") format("embedded-opentype"),
|
url("../fonts/eot/Parastoo-Bold.eot?#iefix") format("embedded-opentype"),
|
||||||
/* IE6-8 */ url("../fonts/woff/Parastoo-Bold.woff") format("woff"),
|
/* IE6-8 */ url("../fonts/woff/Parastoo-Bold.woff") format("woff"),
|
||||||
/* FF3.6+, IE9, Chrome6+, Saf5.1+*/ url("../fonts/ttf/Parastoo-Bold.ttf")
|
/* FF3.6+, IE9, Chrome6+, Saf5.1+*/ url("../fonts/ttf/Parastoo-Bold.ttf") format("truetype");
|
||||||
format("truetype");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
|
|||||||
@@ -5,21 +5,8 @@ import { GET_USER_LOGIN_ROUTE } from "@/core/utils/routes";
|
|||||||
import { useAuth } from "@/lib/contexts/auth";
|
import { useAuth } from "@/lib/contexts/auth";
|
||||||
import useRequest from "@/lib/hooks/useRequest";
|
import useRequest from "@/lib/hooks/useRequest";
|
||||||
import { yupResolver } from "@hookform/resolvers/yup";
|
import { yupResolver } from "@hookform/resolvers/yup";
|
||||||
import {
|
import { Lock, Person, PhoneCallback, Visibility, VisibilityOff } from "@mui/icons-material";
|
||||||
Lock,
|
import { Button, Container, IconButton, InputAdornment, Stack, Typography } from "@mui/material";
|
||||||
Person,
|
|
||||||
PhoneCallback,
|
|
||||||
Visibility,
|
|
||||||
VisibilityOff,
|
|
||||||
} from "@mui/icons-material";
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
Container,
|
|
||||||
IconButton,
|
|
||||||
InputAdornment,
|
|
||||||
Stack,
|
|
||||||
Typography,
|
|
||||||
} from "@mui/material";
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
import { object, string } from "yup";
|
import { object, string } from "yup";
|
||||||
@@ -27,17 +14,15 @@ import { object, string } from "yup";
|
|||||||
const LoginForm = () => {
|
const LoginForm = () => {
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
const requestServer = useRequest();
|
const requestServer = useRequest();
|
||||||
const { getUser } = useAuth();
|
const { setToken } = useAuth();
|
||||||
const defaultValues = {
|
const defaultValues = {
|
||||||
username: "",
|
username: "",
|
||||||
password: "",
|
password: "",
|
||||||
telephone_id: "",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const validationSchema = object({
|
const validationSchema = object({
|
||||||
username: string().required("لطفا نام کاربری را وارد کنید!"),
|
username: string().required("لطفا نام کاربری را وارد کنید!"),
|
||||||
password: string().required("لطفا رمز عبور را وارد کنید!"),
|
password: string().required("لطفا رمز عبور را وارد کنید!"),
|
||||||
telephone_id: string().required("لطفا شماره تماس داخلی را وارد کنید!"),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -57,37 +42,19 @@ const LoginForm = () => {
|
|||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("username", data.username);
|
formData.append("username", data.username);
|
||||||
formData.append("password", data.password);
|
formData.append("password", data.password);
|
||||||
formData.append("telephone_id", data.telephone_id);
|
const response = await requestServer(GET_USER_LOGIN_ROUTE, "post", {
|
||||||
await requestServer(GET_USER_LOGIN_ROUTE, "post", {
|
|
||||||
data: formData,
|
data: formData,
|
||||||
});
|
});
|
||||||
getUser();
|
setToken(response.data.data.token);
|
||||||
} catch (error) {}
|
} catch (error) { }
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container maxWidth="xs" sx={{ flex: 1 }}>
|
<Container maxWidth="xs" sx={{ flex: 1 }}>
|
||||||
<StyledForm
|
<StyledForm onSubmit={handleSubmit(onSubmit)} style={{ width: "100%", height: "100%" }}>
|
||||||
onSubmit={handleSubmit(onSubmit)}
|
<Stack sx={{ width: "100%", height: "100%" }} justifyContent="center" alignItems="center" spacing={6}>
|
||||||
style={{ width: "100%", height: "100%" }}
|
<Stack sx={{ width: "100%", mb: 2 }} justifyContent="center" alignItems="center">
|
||||||
>
|
<Typography variant="h4" color="primary" fontWeight={600} textAlign="center">
|
||||||
<Stack
|
|
||||||
sx={{ width: "100%", height: "100%" }}
|
|
||||||
justifyContent="center"
|
|
||||||
alignItems="center"
|
|
||||||
spacing={6}
|
|
||||||
>
|
|
||||||
<Stack
|
|
||||||
sx={{ width: "100%", mb: 2 }}
|
|
||||||
justifyContent="center"
|
|
||||||
alignItems="center"
|
|
||||||
>
|
|
||||||
<Typography
|
|
||||||
variant="h4"
|
|
||||||
color="primary"
|
|
||||||
fontWeight={600}
|
|
||||||
textAlign="center"
|
|
||||||
>
|
|
||||||
ورود به سامانه CRM
|
ورود به سامانه CRM
|
||||||
</Typography>
|
</Typography>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -132,11 +99,7 @@ const LoginForm = () => {
|
|||||||
startAdornment: (
|
startAdornment: (
|
||||||
<InputAdornment position="start">
|
<InputAdornment position="start">
|
||||||
<IconButton onClick={handleClickShowPassword}>
|
<IconButton onClick={handleClickShowPassword}>
|
||||||
{showPassword ? (
|
{showPassword ? <VisibilityOff /> : <Visibility />}
|
||||||
<VisibilityOff />
|
|
||||||
) : (
|
|
||||||
<Visibility />
|
|
||||||
)}
|
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</InputAdornment>
|
</InputAdornment>
|
||||||
),
|
),
|
||||||
@@ -155,32 +118,6 @@ const LoginForm = () => {
|
|||||||
}}
|
}}
|
||||||
name={"password"}
|
name={"password"}
|
||||||
/>
|
/>
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
render={({ field, fieldState: { error } }) => {
|
|
||||||
return (
|
|
||||||
<LtrTextField
|
|
||||||
{...field}
|
|
||||||
label="شماره تماس داخلی"
|
|
||||||
variant="outlined"
|
|
||||||
type="tel"
|
|
||||||
slotProps={{
|
|
||||||
input: {
|
|
||||||
endAdornment: (
|
|
||||||
<InputAdornment position="end">
|
|
||||||
<PhoneCallback />
|
|
||||||
</InputAdornment>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
fullWidth
|
|
||||||
error={!!error}
|
|
||||||
helperText={!!error && error.message}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
name={"telephone_id"}
|
|
||||||
/>
|
|
||||||
</Stack>
|
</Stack>
|
||||||
<Stack>
|
<Stack>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
51
src/components/Teleport/index.jsx
Normal file
51
src/components/Teleport/index.jsx
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
'use client'
|
||||||
|
import LoadingHardPage from "@/core/components/LoadingHardPage";
|
||||||
|
import { TELEPORT_USER_ROUTE } from "@/core/utils/routes";
|
||||||
|
import { useAuth } from "@/lib/contexts/auth";
|
||||||
|
import useRequest from "@/lib/hooks/useRequest";
|
||||||
|
import { Stack, Typography } from "@mui/material";
|
||||||
|
import { useSearchParams } from "next/navigation";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
const TelePortPage = () => {
|
||||||
|
const { setToken } = useAuth()
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
const token = searchParams.get("token");
|
||||||
|
const user_id = searchParams.get("user_id");
|
||||||
|
|
||||||
|
const [error, setError] = useState(false)
|
||||||
|
const requestServer = useRequest({ notificationShow: false });
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const login = async () => {
|
||||||
|
try {
|
||||||
|
const response = await requestServer(TELEPORT_USER_ROUTE, "post", {
|
||||||
|
data: {
|
||||||
|
token: decodeURIComponent(token),
|
||||||
|
user_id: decodeURIComponent(user_id)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setToken(response.data.data.token);
|
||||||
|
} catch (error) { setError(true) }
|
||||||
|
}
|
||||||
|
|
||||||
|
login()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<LoadingHardPage
|
||||||
|
authState={error}
|
||||||
|
label={
|
||||||
|
error ? (
|
||||||
|
<Stack justifyContent={"center"} alignItems={"center"} spacing={2}>
|
||||||
|
<Typography variant={"body1"}>مشکلی در انتقال پیش آمده است!</Typography>
|
||||||
|
</Stack>
|
||||||
|
) : (
|
||||||
|
<Typography variant={"body1"}>درحال انتقال به مرکز مورد نظر...</Typography>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
loading={true}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default TelePortPage
|
||||||
102
src/components/dashboard/Roles/DataTable.jsx
Normal file
102
src/components/dashboard/Roles/DataTable.jsx
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
"use client";
|
||||||
|
import DataTableWithAuth from "@/core/components/DataTableWithAuth";
|
||||||
|
import { ROLE_SERVICE } from "@/core/utils/routes";
|
||||||
|
import { Box, Typography } from "@mui/material";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
import Toolbar from "./Toolbar";
|
||||||
|
import moment from "jalali-moment";
|
||||||
|
import RowActions from "./RowAcctions";
|
||||||
|
|
||||||
|
const DataTable = () => {
|
||||||
|
const columns = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
accessorKey: "id",
|
||||||
|
header: "کد یکتا",
|
||||||
|
id: "id",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "name_fa",
|
||||||
|
header: "نام",
|
||||||
|
id: "name_fa",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "name",
|
||||||
|
header: "نام انگلیسی",
|
||||||
|
id: "name",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "created_at",
|
||||||
|
header: "تاریخ ساخت",
|
||||||
|
id: "created_at",
|
||||||
|
enableColumnFilter: false,
|
||||||
|
datatype: "date",
|
||||||
|
filterMode: "equals",
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
Cell: ({ renderedCellValue }) => (
|
||||||
|
<Typography variant="body2">
|
||||||
|
{renderedCellValue ? moment(renderedCellValue).locale("fa").format("HH:mm | YYYY/MM/DD") : "-"}
|
||||||
|
</Typography>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "updated_at",
|
||||||
|
header: "تاریخ آخرین بروزرسانی",
|
||||||
|
id: "updated_at",
|
||||||
|
enableColumnFilter: false,
|
||||||
|
datatype: "date",
|
||||||
|
filterMode: "equals",
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
Cell: ({ renderedCellValue }) => (
|
||||||
|
<Typography variant="body2">
|
||||||
|
{renderedCellValue ? moment(renderedCellValue).locale("fa").format("HH:mm | YYYY/MM/DD") : "-"}
|
||||||
|
</Typography>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Box sx={{ p: 1, border: 1, borderColor: "divider", borderRadius: 1 }}>
|
||||||
|
<DataTableWithAuth
|
||||||
|
need_filter={true}
|
||||||
|
columns={columns}
|
||||||
|
sorting={[{ id: "id", desc: true }]}
|
||||||
|
table_url={ROLE_SERVICE}
|
||||||
|
page_name={"rolesPage"}
|
||||||
|
table_name={"rolesList"}
|
||||||
|
TableToolbar={Toolbar}
|
||||||
|
enableRowActions
|
||||||
|
positionActionsColumn={"last"}
|
||||||
|
RowActions={RowActions}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default DataTable;
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { ROLE_SERVICE } from "@/core/utils/routes";
|
||||||
|
import useRequest from "@/lib/hooks/useRequest";
|
||||||
|
import { Button, DialogActions, DialogContent, Stack, Typography } from "@mui/material";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
const DeleteContent = ({ rowId, mutate, setOpenDeleteDialog }) => {
|
||||||
|
const [submitting, setSubmitting] = useState(false);
|
||||||
|
const requestServer = useRequest({ notificationSuccess: true });
|
||||||
|
const handleClick = async () => {
|
||||||
|
setSubmitting(true);
|
||||||
|
try {
|
||||||
|
await requestServer(`${ROLE_SERVICE}/${rowId}`, "delete");
|
||||||
|
mutate();
|
||||||
|
setOpenDeleteDialog(false);
|
||||||
|
} catch (error) {
|
||||||
|
} finally {
|
||||||
|
setSubmitting(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DialogContent>
|
||||||
|
<Stack alignItems="center" spacing={2}>
|
||||||
|
<Typography mt={2}>آیا از حذف کاربر اطمینان دارید؟</Typography>
|
||||||
|
</Stack>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions sx={{ justifyContent: "center", paddingBottom: 2, width: "100%" }}>
|
||||||
|
<Button onClick={() => setOpenDeleteDialog(false)} variant="outlined" color="secondary">
|
||||||
|
خیر !
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleClick} variant="contained" color="error" disabled={submitting}>
|
||||||
|
{submitting ? "درحال حذف..." : "بله اطمینان دارم"}
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DeleteContent;
|
||||||
32
src/components/dashboard/Roles/RowAcctions/Delete/index.jsx
Normal file
32
src/components/dashboard/Roles/RowAcctions/Delete/index.jsx
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import DeleteIcon from "@mui/icons-material/Delete";
|
||||||
|
import { Dialog, DialogTitle, IconButton, Tooltip } from "@mui/material";
|
||||||
|
import { useState } from "react";
|
||||||
|
import DeleteContent from "./DeleteContent";
|
||||||
|
const Delete = ({ rowId, mutate }) => {
|
||||||
|
const [openDeleteDialog, setOpenDeleteDialog] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Tooltip title="حذف">
|
||||||
|
<IconButton color="error" onClick={() => setOpenDeleteDialog(true)}>
|
||||||
|
<DeleteIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
<Dialog
|
||||||
|
open={openDeleteDialog}
|
||||||
|
onClose={() => setOpenDeleteDialog(false)}
|
||||||
|
PaperProps={{
|
||||||
|
sx: {
|
||||||
|
boxShadow: "rgba(60, 64, 67, 0.3) 0px 1px 16px 0px, rgba(60, 64, 67, 0.15) 0px 1px 10px 0px",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
dir="rtl"
|
||||||
|
maxWidth={"md"}
|
||||||
|
>
|
||||||
|
<DialogTitle>حذف</DialogTitle>
|
||||||
|
<DeleteContent rowId={rowId} mutate={mutate} setOpenDeleteDialog={setOpenDeleteDialog} />
|
||||||
|
</Dialog>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default Delete;
|
||||||
202
src/components/dashboard/Roles/RowAcctions/Edit/Form/index.jsx
Normal file
202
src/components/dashboard/Roles/RowAcctions/Edit/Form/index.jsx
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
import LtrTextField from "@/core/components/LtrTextField";
|
||||||
|
import StyledForm from "@/core/components/StyledForm";
|
||||||
|
import { ROLE_SERVICE } from "@/core/utils/routes";
|
||||||
|
import useRequest from "@/lib/hooks/useRequest";
|
||||||
|
import { yupResolver } from "@hookform/resolvers/yup";
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Checkbox,
|
||||||
|
Chip,
|
||||||
|
DialogActions,
|
||||||
|
DialogContent,
|
||||||
|
Divider,
|
||||||
|
FormControlLabel,
|
||||||
|
FormHelperText,
|
||||||
|
Grid,
|
||||||
|
Skeleton,
|
||||||
|
Stack,
|
||||||
|
} from "@mui/material";
|
||||||
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import { array, object, string } from "yup";
|
||||||
|
import usePermissionsList from "@/lib/hooks/usePermissionsList";
|
||||||
|
|
||||||
|
const validationSchema = object().shape({
|
||||||
|
name: string().required("نام فارسی نقش را وارد کنید"),
|
||||||
|
name_fa: string().required("نام انگلیسی نقش را وارد کنید"),
|
||||||
|
permissions: array().min(1, "حداقل یک دسترسی را انتخاب نمایید"),
|
||||||
|
});
|
||||||
|
|
||||||
|
const UpdateRoleForm = ({ values, setOpen, mutate }) => {
|
||||||
|
const requestServer = useRequest({ notificationSuccess: true });
|
||||||
|
const { permissions, loadingPermissions } = usePermissionsList();
|
||||||
|
|
||||||
|
const defaultValues = {
|
||||||
|
name: values.name,
|
||||||
|
name_fa: values.name_fa,
|
||||||
|
permissions: values.permissions?.map((p) => p.id) || [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
handleSubmit,
|
||||||
|
formState: { isSubmitting, errors },
|
||||||
|
} = useForm({ defaultValues, resolver: yupResolver(validationSchema) });
|
||||||
|
|
||||||
|
const onSubmit = async (data) => {
|
||||||
|
try {
|
||||||
|
await requestServer(`${ROLE_SERVICE}/${values.id}`, "post", {
|
||||||
|
data: {
|
||||||
|
permissions: data.permissions,
|
||||||
|
name: data.name,
|
||||||
|
name_fa: data.name_fa,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
setOpen(false);
|
||||||
|
mutate();
|
||||||
|
} catch (error) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DialogContent dividers>
|
||||||
|
<Divider sx={{ mb: 3 }}>
|
||||||
|
<Chip color="primary" variant="outlined" label="کاربر جدید" />
|
||||||
|
</Divider>
|
||||||
|
<StyledForm
|
||||||
|
id="UpdateRoleForm"
|
||||||
|
onSubmit={handleSubmit(onSubmit)}
|
||||||
|
style={{ width: "100%", height: "100%" }}
|
||||||
|
>
|
||||||
|
<Stack spacing={2}>
|
||||||
|
<Grid container spacing={2}>
|
||||||
|
<Grid size={{ xs: 12, md: 6 }}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<LtrTextField
|
||||||
|
{...field}
|
||||||
|
label="نام انگلیسی نقش"
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
error={!!error}
|
||||||
|
helperText={!!error && error.message}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
name={"name"}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid size={{ xs: 12, md: 6 }}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<LtrTextField
|
||||||
|
{...field}
|
||||||
|
label="نام فارسی نقش"
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
error={!!error}
|
||||||
|
helperText={!!error && error.message}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
name={"name_fa"}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Box sx={{ width: "100%" }}>
|
||||||
|
<Divider>
|
||||||
|
<Chip label="سطوح دسترسی" size="medium" />
|
||||||
|
</Divider>
|
||||||
|
</Box>
|
||||||
|
<Grid container spacing={1} sx={{ width: "100%" }}>
|
||||||
|
{!loadingPermissions ? (
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="permissions"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<>
|
||||||
|
{permissions.map((permission) => (
|
||||||
|
<Grid key={permission.id} size={{ xs: 12, md: 4 }}>
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
value={permission.id}
|
||||||
|
checked={field.value.includes(permission.id)}
|
||||||
|
onChange={(e) => {
|
||||||
|
const value = parseInt(e.target.value, 10);
|
||||||
|
if (e.target.checked) {
|
||||||
|
field.onChange([...field.value, value]);
|
||||||
|
} else {
|
||||||
|
field.onChange(
|
||||||
|
field.value.filter((id) => id !== value)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={permission.name_fa}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
))}
|
||||||
|
<FormHelperText sx={{ color: "error.main" }}>
|
||||||
|
{!!error && error.message}
|
||||||
|
</FormHelperText>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Grid container spacing={2} width="100%">
|
||||||
|
<Grid size={{ xs: 12, md: 4 }}>
|
||||||
|
<Skeleton animation="wave" />
|
||||||
|
</Grid>
|
||||||
|
<Grid size={{ xs: 12, md: 4 }}>
|
||||||
|
<Skeleton animation="wave" />
|
||||||
|
</Grid>
|
||||||
|
<Grid size={{ xs: 12, md: 4 }}>
|
||||||
|
<Skeleton animation="wave" />
|
||||||
|
</Grid>
|
||||||
|
<Grid size={{ xs: 12, md: 4 }}>
|
||||||
|
<Skeleton animation="wave" />
|
||||||
|
</Grid>
|
||||||
|
<Grid size={{ xs: 12, md: 4 }}>
|
||||||
|
<Skeleton animation="wave" />
|
||||||
|
</Grid>
|
||||||
|
<Grid size={{ xs: 12, md: 4 }}>
|
||||||
|
<Skeleton animation="wave" />
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Stack>
|
||||||
|
</StyledForm>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button
|
||||||
|
disabled={isSubmitting}
|
||||||
|
variant="outlined"
|
||||||
|
color="secondary"
|
||||||
|
size="large"
|
||||||
|
onClick={() => setOpen(false)}
|
||||||
|
>
|
||||||
|
بستن
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
disabled={isSubmitting}
|
||||||
|
form="UpdateRoleForm"
|
||||||
|
size="large"
|
||||||
|
autoFocus
|
||||||
|
variant="contained"
|
||||||
|
>
|
||||||
|
{isSubmitting ? "در حال ویرایش کاربر..." : "ویرایش کاربر"}
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default UpdateRoleForm;
|
||||||
27
src/components/dashboard/Roles/RowAcctions/Edit/index.jsx
Normal file
27
src/components/dashboard/Roles/RowAcctions/Edit/index.jsx
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
"use client";
|
||||||
|
import { Edit } from "@mui/icons-material";
|
||||||
|
import { Dialog, IconButton, Tooltip } from "@mui/material";
|
||||||
|
import { useState } from "react";
|
||||||
|
import UpdateUserForm from "./Form";
|
||||||
|
|
||||||
|
const UpdateUser = ({ values, mutate }) => {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
const handleOpen = () => {
|
||||||
|
setOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Tooltip title="ویرایش">
|
||||||
|
<IconButton onClick={handleOpen}>
|
||||||
|
<Edit />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
<Dialog maxWidth="sm" fullWidth={true} open={open}>
|
||||||
|
<UpdateUserForm open={open} setOpen={setOpen} mutate={mutate} values={values} />
|
||||||
|
</Dialog>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default UpdateUser;
|
||||||
12
src/components/dashboard/Roles/RowAcctions/index.jsx
Normal file
12
src/components/dashboard/Roles/RowAcctions/index.jsx
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { Box } from "@mui/material";
|
||||||
|
import Delete from "./Delete";
|
||||||
|
import UpdateUser from "./Edit";
|
||||||
|
const RowActions = ({ row, mutate }) => {
|
||||||
|
return (
|
||||||
|
<Box sx={{ display: "flex", gap: 1, alignItems: "center" }}>
|
||||||
|
<Delete rowId={row.original.id} mutate={mutate} />
|
||||||
|
<UpdateUser values={row.original} mutate={mutate} />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default RowActions;
|
||||||
202
src/components/dashboard/Roles/Toolbar/Create/Form/index.jsx
Normal file
202
src/components/dashboard/Roles/Toolbar/Create/Form/index.jsx
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
import LtrTextField from "@/core/components/LtrTextField";
|
||||||
|
import StyledForm from "@/core/components/StyledForm";
|
||||||
|
import { yupResolver } from "@hookform/resolvers/yup";
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Checkbox,
|
||||||
|
Chip,
|
||||||
|
DialogActions,
|
||||||
|
DialogContent,
|
||||||
|
Divider,
|
||||||
|
FormControlLabel,
|
||||||
|
FormHelperText,
|
||||||
|
Grid,
|
||||||
|
Skeleton,
|
||||||
|
Stack,
|
||||||
|
} from "@mui/material";
|
||||||
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import { array, object, string } from "yup";
|
||||||
|
import useRequest from "@/lib/hooks/useRequest";
|
||||||
|
import { ROLE_SERVICE } from "@/core/utils/routes";
|
||||||
|
import usePermissionsList from "@/lib/hooks/usePermissionsList";
|
||||||
|
|
||||||
|
const defaultValues = {
|
||||||
|
name: "",
|
||||||
|
name_fa: "",
|
||||||
|
permissions: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const validationSchema = object().shape({
|
||||||
|
name: string().required("نام فارسی نقش را وارد کنید"),
|
||||||
|
name_fa: string().required("نام انگلیسی نقش را وارد کنید"),
|
||||||
|
permissions: array().min(1, "حداقل یک دسترسی را انتخاب نمایید"),
|
||||||
|
});
|
||||||
|
|
||||||
|
const CreateRoleForm = ({ setOpen, mutate }) => {
|
||||||
|
const requestServer = useRequest({ notificationSuccess: true });
|
||||||
|
const { permissions, loadingPermissions } = usePermissionsList();
|
||||||
|
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
handleSubmit,
|
||||||
|
formState: { isSubmitting, errors },
|
||||||
|
} = useForm({ defaultValues, resolver: yupResolver(validationSchema) });
|
||||||
|
|
||||||
|
const onSubmit = async (data) => {
|
||||||
|
try {
|
||||||
|
await requestServer(ROLE_SERVICE, "post", {
|
||||||
|
data: {
|
||||||
|
permissions: data.permissions,
|
||||||
|
name: data.name,
|
||||||
|
name_fa: data.name_fa,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
setOpen(false);
|
||||||
|
mutate();
|
||||||
|
} catch (error) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DialogContent dividers>
|
||||||
|
<Divider sx={{ mb: 3 }}>
|
||||||
|
<Chip color="primary" variant="outlined" label="نقش جدید" />
|
||||||
|
</Divider>
|
||||||
|
<StyledForm
|
||||||
|
id="createRoleForm"
|
||||||
|
onSubmit={handleSubmit(onSubmit)}
|
||||||
|
style={{ width: "100%", height: "100%" }}
|
||||||
|
>
|
||||||
|
<Stack spacing={2}>
|
||||||
|
<Grid container spacing={2}>
|
||||||
|
<Grid size={{ xs: 12, md: 6 }}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<LtrTextField
|
||||||
|
{...field}
|
||||||
|
label="نام انگلیسی نقش"
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
error={!!error}
|
||||||
|
helperText={!!error && error.message}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
name={"name"}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid size={{ xs: 12, md: 6 }}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<LtrTextField
|
||||||
|
{...field}
|
||||||
|
label="نام فارسی نقش"
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
error={!!error}
|
||||||
|
helperText={!!error && error.message}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
name={"name_fa"}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Box sx={{ width: "100%" }}>
|
||||||
|
<Divider>
|
||||||
|
<Chip label="سطوح دسترسی" size="medium" />
|
||||||
|
</Divider>
|
||||||
|
</Box>
|
||||||
|
<Grid container spacing={1} sx={{ width: "100%" }}>
|
||||||
|
{!loadingPermissions ? (
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="permissions"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<>
|
||||||
|
{permissions.map((permission) => (
|
||||||
|
<Grid key={permission.id} size={{ xs: 12, md: 4 }}>
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
value={permission.id}
|
||||||
|
checked={field.value.includes(permission.id)}
|
||||||
|
onChange={(e) => {
|
||||||
|
const value = parseInt(e.target.value, 10);
|
||||||
|
if (e.target.checked) {
|
||||||
|
field.onChange([...field.value, value]);
|
||||||
|
} else {
|
||||||
|
field.onChange(
|
||||||
|
field.value.filter((id) => id !== value)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={permission.name_fa}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
))}
|
||||||
|
<FormHelperText sx={{ color: "error.main" }}>
|
||||||
|
{!!error && error.message}
|
||||||
|
</FormHelperText>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Grid container spacing={2} width="100%">
|
||||||
|
<Grid size={{ xs: 12, md: 4 }}>
|
||||||
|
<Skeleton animation="wave" />
|
||||||
|
</Grid>
|
||||||
|
<Grid size={{ xs: 12, md: 4 }}>
|
||||||
|
<Skeleton animation="wave" />
|
||||||
|
</Grid>
|
||||||
|
<Grid size={{ xs: 12, md: 4 }}>
|
||||||
|
<Skeleton animation="wave" />
|
||||||
|
</Grid>
|
||||||
|
<Grid size={{ xs: 12, md: 4 }}>
|
||||||
|
<Skeleton animation="wave" />
|
||||||
|
</Grid>
|
||||||
|
<Grid size={{ xs: 12, md: 4 }}>
|
||||||
|
<Skeleton animation="wave" />
|
||||||
|
</Grid>
|
||||||
|
<Grid size={{ xs: 12, md: 4 }}>
|
||||||
|
<Skeleton animation="wave" />
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Stack>
|
||||||
|
</StyledForm>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button
|
||||||
|
disabled={isSubmitting}
|
||||||
|
variant="outlined"
|
||||||
|
color="secondary"
|
||||||
|
size="large"
|
||||||
|
onClick={() => setOpen(false)}
|
||||||
|
>
|
||||||
|
بستن
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
disabled={isSubmitting}
|
||||||
|
form="createRoleForm"
|
||||||
|
size="large"
|
||||||
|
autoFocus
|
||||||
|
variant="contained"
|
||||||
|
>
|
||||||
|
{isSubmitting ? "در حال ثبت نقش جدید..." : "ثبت نقش جدید"}
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CreateRoleForm;
|
||||||
39
src/components/dashboard/Roles/Toolbar/Create/index.jsx
Normal file
39
src/components/dashboard/Roles/Toolbar/Create/index.jsx
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
"use client";
|
||||||
|
import { AddCircle } from "@mui/icons-material";
|
||||||
|
import { Button, Dialog, IconButton, useMediaQuery, useTheme } from "@mui/material";
|
||||||
|
import CreateRoleForm from "./Form";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
const CreateRole = ({ mutate }) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
const handleOpen = () => {
|
||||||
|
setOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{isMobile ? (
|
||||||
|
<IconButton aria-label="نقش جدید" color="primary" onClick={handleOpen}>
|
||||||
|
<AddCircle sx={{ fontSize: "25px" }} />
|
||||||
|
</IconButton>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
size={"small"}
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
startIcon={<AddCircle />}
|
||||||
|
onClick={handleOpen}
|
||||||
|
>
|
||||||
|
نقش جدید
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Dialog maxWidth="sm" fullWidth={true} open={open}>
|
||||||
|
<CreateRoleForm open={open} setOpen={setOpen} mutate={mutate} />
|
||||||
|
</Dialog>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default CreateRole;
|
||||||
10
src/components/dashboard/Roles/Toolbar/index.jsx
Normal file
10
src/components/dashboard/Roles/Toolbar/index.jsx
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import CreateRole from "./Create";
|
||||||
|
|
||||||
|
const Toolbar = ({ mutate }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<CreateRole mutate={mutate} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default Toolbar;
|
||||||
14
src/components/dashboard/Roles/index.jsx
Normal file
14
src/components/dashboard/Roles/index.jsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import PageTitle from "@/core/components/PageTitle";
|
||||||
|
import { Stack } from "@mui/material";
|
||||||
|
import DataTable from "./DataTable";
|
||||||
|
|
||||||
|
const RolesPage = () => {
|
||||||
|
return (
|
||||||
|
<Stack spacing={1}>
|
||||||
|
<PageTitle title={"نقش ها"} />
|
||||||
|
<DataTable />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RolesPage;
|
||||||
@@ -1,13 +1,12 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import DataTableWithAuth from "@/core/components/DataTableWithAuth";
|
import DataTableWithAuth from "@/core/components/DataTableWithAuth";
|
||||||
import { GET_USER_LIST } from "@/core/utils/routes";
|
import { USER_SERVICE } from "@/core/utils/routes";
|
||||||
import { Box, Stack } from "@mui/material";
|
import { Box } from "@mui/material";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import RowActions from "./RowActions";
|
import RowActions from "./RowActions";
|
||||||
import Toolbar from "./Toolbar";
|
import Toolbar from "./Toolbar";
|
||||||
import useProvinces from "@/lib/hooks/useProvince";
|
import useProvinces from "@/lib/hooks/useProvince";
|
||||||
import CustomSelectByDependency from "@/core/components/DataTable/filter/fieldsType/CustomSelectByDependency";
|
import CustomSelectByDependency from "@/core/components/DataTable/filter/fieldsType/CustomSelectByDependency";
|
||||||
import { Power, PowerOff } from "@mui/icons-material";
|
|
||||||
import useRoles from "@/lib/hooks/useRoles";
|
import useRoles from "@/lib/hooks/useRoles";
|
||||||
import OnlineCell from "./RowActions/Online";
|
import OnlineCell from "./RowActions/Online";
|
||||||
import TelephoneId from "./RowActions/TelephoneId";
|
import TelephoneId from "./RowActions/TelephoneId";
|
||||||
@@ -40,6 +39,7 @@ const DataTable = () => {
|
|||||||
size: 100,
|
size: 100,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
accessorKey: "telephone_id",
|
||||||
header: "شماره تماس داخلی",
|
header: "شماره تماس داخلی",
|
||||||
id: "telephone_id",
|
id: "telephone_id",
|
||||||
enableColumnFilter: true,
|
enableColumnFilter: true,
|
||||||
@@ -49,7 +49,6 @@ const DataTable = () => {
|
|||||||
columnFilterModeOptions: ["equals", "contains"],
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
grow: false,
|
grow: false,
|
||||||
size: 100,
|
size: 100,
|
||||||
Cell: ({ row }) => <TelephoneId user_id={row.original.id} />,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "is_online",
|
accessorKey: "is_online",
|
||||||
@@ -73,8 +72,7 @@ const DataTable = () => {
|
|||||||
grow: false,
|
grow: false,
|
||||||
size: 130,
|
size: 130,
|
||||||
ColumnSelectComponent: (props) => {
|
ColumnSelectComponent: (props) => {
|
||||||
const { provinces, errorProvinces, loadingProvinces } =
|
const { provinces, errorProvinces, loadingProvinces } = useProvinces();
|
||||||
useProvinces();
|
|
||||||
const getColumnSelectOptions = useMemo(() => {
|
const getColumnSelectOptions = useMemo(() => {
|
||||||
if (loadingProvinces) {
|
if (loadingProvinces) {
|
||||||
return [{ value: "loading", label: "در حال بارگذاری..." }];
|
return [{ value: "loading", label: "در حال بارگذاری..." }];
|
||||||
@@ -93,9 +91,7 @@ const DataTable = () => {
|
|||||||
return (
|
return (
|
||||||
<CustomSelectByDependency
|
<CustomSelectByDependency
|
||||||
{...props}
|
{...props}
|
||||||
value={
|
value={loadingProvinces ? "loading" : props.filterParameters.value}
|
||||||
loadingProvinces ? "loading" : props.filterParameters.value
|
|
||||||
}
|
|
||||||
columnSelectOption={getColumnSelectOptions}
|
columnSelectOption={getColumnSelectOptions}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -187,12 +183,10 @@ const DataTable = () => {
|
|||||||
enableColumnFilter: false,
|
enableColumnFilter: false,
|
||||||
grow: false,
|
grow: false,
|
||||||
size: 100,
|
size: 100,
|
||||||
Cell: ({ renderedCellValue }) => (
|
Cell: ({ renderedCellValue }) => <>{renderedCellValue == "male" ? "آقا" : "خانم"}</>,
|
||||||
<>{renderedCellValue == "male" ? "آقا" : "خانم"}</>
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[],
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -202,7 +196,7 @@ const DataTable = () => {
|
|||||||
need_filter={true}
|
need_filter={true}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
sorting={[{ id: "id", desc: true }]}
|
sorting={[{ id: "id", desc: true }]}
|
||||||
table_url={GET_USER_LIST}
|
table_url={USER_SERVICE}
|
||||||
page_name={"usersPage"}
|
page_name={"usersPage"}
|
||||||
table_name={"usersList"}
|
table_name={"usersList"}
|
||||||
TableToolbar={Toolbar}
|
TableToolbar={Toolbar}
|
||||||
|
|||||||
@@ -1,12 +1,6 @@
|
|||||||
import { DELETE_USER } from "@/core/utils/routes";
|
import { USER_SERVICE } from "@/core/utils/routes";
|
||||||
import useRequest from "@/lib/hooks/useRequest";
|
import useRequest from "@/lib/hooks/useRequest";
|
||||||
import {
|
import { Button, DialogActions, DialogContent, Stack, Typography } from "@mui/material";
|
||||||
Button,
|
|
||||||
DialogActions,
|
|
||||||
DialogContent,
|
|
||||||
Stack,
|
|
||||||
Typography,
|
|
||||||
} from "@mui/material";
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
const DeleteContent = ({ rowId, mutate, setOpenDeleteDialog }) => {
|
const DeleteContent = ({ rowId, mutate, setOpenDeleteDialog }) => {
|
||||||
@@ -15,7 +9,7 @@ const DeleteContent = ({ rowId, mutate, setOpenDeleteDialog }) => {
|
|||||||
const handleClick = async () => {
|
const handleClick = async () => {
|
||||||
setSubmitting(true);
|
setSubmitting(true);
|
||||||
try {
|
try {
|
||||||
await requestServer(`${DELETE_USER}/${rowId}`, "delete");
|
await requestServer(`${USER_SERVICE}/${rowId}`, "delete");
|
||||||
mutate();
|
mutate();
|
||||||
setOpenDeleteDialog(false);
|
setOpenDeleteDialog(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -30,22 +24,11 @@ const DeleteContent = ({ rowId, mutate, setOpenDeleteDialog }) => {
|
|||||||
<Typography mt={2}>آیا از حذف کاربر اطمینان دارید؟</Typography>
|
<Typography mt={2}>آیا از حذف کاربر اطمینان دارید؟</Typography>
|
||||||
</Stack>
|
</Stack>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions
|
<DialogActions sx={{ justifyContent: "center", paddingBottom: 2, width: "100%" }}>
|
||||||
sx={{ justifyContent: "center", paddingBottom: 2, width: "100%" }}
|
<Button onClick={() => setOpenDeleteDialog(false)} variant="outlined" color="secondary">
|
||||||
>
|
|
||||||
<Button
|
|
||||||
onClick={() => setOpenDeleteDialog(false)}
|
|
||||||
variant="outlined"
|
|
||||||
color="secondary"
|
|
||||||
>
|
|
||||||
خیر !
|
خیر !
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button onClick={handleClick} variant="contained" color="error" disabled={submitting}>
|
||||||
onClick={handleClick}
|
|
||||||
variant="contained"
|
|
||||||
color="error"
|
|
||||||
disabled={submitting}
|
|
||||||
>
|
|
||||||
{submitting ? "درحال حذف..." : "بله اطمینان دارم"}
|
{submitting ? "درحال حذف..." : "بله اطمینان دارم"}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
|
|||||||
@@ -17,19 +17,14 @@ const Delete = ({ rowId, mutate }) => {
|
|||||||
onClose={() => setOpenDeleteDialog(false)}
|
onClose={() => setOpenDeleteDialog(false)}
|
||||||
PaperProps={{
|
PaperProps={{
|
||||||
sx: {
|
sx: {
|
||||||
boxShadow:
|
boxShadow: "rgba(60, 64, 67, 0.3) 0px 1px 16px 0px, rgba(60, 64, 67, 0.15) 0px 1px 10px 0px",
|
||||||
"rgba(60, 64, 67, 0.3) 0px 1px 16px 0px, rgba(60, 64, 67, 0.15) 0px 1px 10px 0px",
|
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
dir="rtl"
|
dir="rtl"
|
||||||
maxWidth={"md"}
|
maxWidth={"md"}
|
||||||
>
|
>
|
||||||
<DialogTitle>حذف</DialogTitle>
|
<DialogTitle>حذف</DialogTitle>
|
||||||
<DeleteContent
|
<DeleteContent rowId={rowId} mutate={mutate} setOpenDeleteDialog={setOpenDeleteDialog} />
|
||||||
rowId={rowId}
|
|
||||||
mutate={mutate}
|
|
||||||
setOpenDeleteDialog={setOpenDeleteDialog}
|
|
||||||
/>
|
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import LtrTextField from "@/core/components/LtrTextField";
|
import LtrTextField from "@/core/components/LtrTextField";
|
||||||
import StyledForm from "@/core/components/StyledForm";
|
import StyledForm from "@/core/components/StyledForm";
|
||||||
import validateNationalCode from "@/core/utils/nationalCodeValidation";
|
import validateNationalCode from "@/core/utils/nationalCodeValidation";
|
||||||
import { UPDATE_USER } from "@/core/utils/routes";
|
import { USER_SERVICE } from "@/core/utils/routes";
|
||||||
import useProvinces from "@/lib/hooks/useProvince";
|
import useProvinces from "@/lib/hooks/useProvince";
|
||||||
import useRequest from "@/lib/hooks/useRequest";
|
import useRequest from "@/lib/hooks/useRequest";
|
||||||
import useRoles from "@/lib/hooks/useRoles";
|
import useRoles from "@/lib/hooks/useRoles";
|
||||||
@@ -27,25 +27,20 @@ import { Controller, useForm } from "react-hook-form";
|
|||||||
import { object, string } from "yup";
|
import { object, string } from "yup";
|
||||||
|
|
||||||
const validationSchema = object().shape({
|
const validationSchema = object().shape({
|
||||||
full_name: string().required("نام و نام خانوادگی خود را وارد کنید"),
|
full_name: string().required("نام و نام خانوادگی را وارد کنید"),
|
||||||
username: string().required("نام کاربری خود را وارد کنید"),
|
username: string().required("نام کاربری را وارد کنید"),
|
||||||
phone_number: string()
|
phone_number: string()
|
||||||
.matches(/^09\d{9}$/, "شماره همراه باید با 09 شروع شود و 11 رقم باشد.")
|
.matches(/^09\d{9}$/, "شماره همراه باید با 09 شروع شود و 11 رقم باشد.")
|
||||||
.required("شماره همراه خود را وارد کنید"),
|
.required("شماره همراه را وارد کنید"),
|
||||||
gender: string().required("جنسیت خود را وارد کنید"),
|
gender: string().required("جنسیت را وارد کنید"),
|
||||||
national_id: string()
|
national_id: string()
|
||||||
.test(
|
.test("max", "کد ملی باید شامل 10 رقم باشد", (value) => value.toString().length === 10)
|
||||||
"max",
|
.test("validation", "کد ملی صحیح نمی باشد", (value) => validateNationalCode(value))
|
||||||
"کد ملی باید شامل 10 رقم باشد",
|
.required("کد ملی را وارد کنید"),
|
||||||
(value) => value.toString().length === 10,
|
position: string().required("سمت را وارد کنید"),
|
||||||
)
|
province_id: string().required("استان را وارد کنید"),
|
||||||
.test("validation", "کد ملی صحیح نمی باشد", (value) =>
|
role_id: string().required("نقش را وارد کنید"),
|
||||||
validateNationalCode(value),
|
telephone_id: string().required("شماره تماس داخلی را وارد کنید"),
|
||||||
)
|
|
||||||
.required("کد ملی خود را وارد کنید"),
|
|
||||||
position: string().required("سمت خود را وارد کنید"),
|
|
||||||
province_id: string().required("استان خود را وارد کنید"),
|
|
||||||
role_id: string().required("نقش خود را وارد کنید"),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const UpdateUserForm = ({ values, setOpen, mutate }) => {
|
const UpdateUserForm = ({ values, setOpen, mutate }) => {
|
||||||
@@ -62,6 +57,7 @@ const UpdateUserForm = ({ values, setOpen, mutate }) => {
|
|||||||
phone_number: values.phone_number,
|
phone_number: values.phone_number,
|
||||||
province_id: values.province_id,
|
province_id: values.province_id,
|
||||||
role_id: values.roles[0].id,
|
role_id: values.roles[0].id,
|
||||||
|
telephone_id: values.telephone_id,
|
||||||
};
|
};
|
||||||
|
|
||||||
const provinceSelectOptions = useMemo(() => {
|
const provinceSelectOptions = useMemo(() => {
|
||||||
@@ -108,7 +104,7 @@ const UpdateUserForm = ({ values, setOpen, mutate }) => {
|
|||||||
formData.append(key, data[key]);
|
formData.append(key, data[key]);
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await requestServer(`${UPDATE_USER}/${values.id}`, "post", {
|
await requestServer(`${USER_SERVICE}/${values.id}`, "post", {
|
||||||
data: formData,
|
data: formData,
|
||||||
});
|
});
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
@@ -218,12 +214,12 @@ const UpdateUserForm = ({ values, setOpen, mutate }) => {
|
|||||||
render={({ field, fieldState: { error } }) => {
|
render={({ field, fieldState: { error } }) => {
|
||||||
return (
|
return (
|
||||||
<FormControl fullWidth error={!!error}>
|
<FormControl fullWidth error={!!error}>
|
||||||
<InputLabel id={`label-phone-number`} shrink>
|
<InputLabel id={`label-gender`} shrink>
|
||||||
جنسیت
|
جنسیت
|
||||||
</InputLabel>
|
</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
labelId={`label-phone-number`}
|
labelId={`label-gender`}
|
||||||
id={"phone-number"}
|
id={"gender"}
|
||||||
name={`gender`}
|
name={`gender`}
|
||||||
value={field.value}
|
value={field.value}
|
||||||
input={<OutlinedInput label={"جنسیت"} />}
|
input={<OutlinedInput label={"جنسیت"} />}
|
||||||
@@ -337,6 +333,25 @@ const UpdateUserForm = ({ values, setOpen, mutate }) => {
|
|||||||
name={"role_id"}
|
name={"role_id"}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
<Grid size={{ xs: 12, md: 6 }}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<LtrTextField
|
||||||
|
{...field}
|
||||||
|
label="شماره تماس داخلی"
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
type="tel"
|
||||||
|
error={!!error}
|
||||||
|
helperText={!!error && error.message}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
name={"telephone_id"}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Stack>
|
</Stack>
|
||||||
</StyledForm>
|
</StyledForm>
|
||||||
|
|||||||
@@ -19,12 +19,7 @@ const UpdateUser = ({ values, mutate }) => {
|
|||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Dialog maxWidth="sm" fullWidth={true} open={open}>
|
<Dialog maxWidth="sm" fullWidth={true} open={open}>
|
||||||
<UpdateUserForm
|
<UpdateUserForm open={open} setOpen={setOpen} mutate={mutate} values={values} />
|
||||||
open={open}
|
|
||||||
setOpen={setOpen}
|
|
||||||
mutate={mutate}
|
|
||||||
values={values}
|
|
||||||
/>
|
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,11 +6,7 @@ const OnlineCell = ({ user_id }) => {
|
|||||||
const { clientsOnline } = useSocket();
|
const { clientsOnline } = useSocket();
|
||||||
return (
|
return (
|
||||||
<Stack alignItems={"center"}>
|
<Stack alignItems={"center"}>
|
||||||
{clientsOnline.some((co) => co.user_id == user_id) ? (
|
{clientsOnline.some((co) => co.user_id == user_id) ? <Power color="success" /> : <PowerOff color="error" />}
|
||||||
<Power color="success" />
|
|
||||||
) : (
|
|
||||||
<PowerOff color="error" />
|
|
||||||
)}
|
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,9 +3,7 @@ import { Stack } from "@mui/material";
|
|||||||
|
|
||||||
const TelephoneId = ({ user_id }) => {
|
const TelephoneId = ({ user_id }) => {
|
||||||
const { clientsOnline } = useSocket();
|
const { clientsOnline } = useSocket();
|
||||||
const telephone_id = clientsOnline.find(
|
const telephone_id = clientsOnline.find((co) => co.user_id == user_id)?.telephone_id;
|
||||||
(co) => co.user_id == user_id,
|
|
||||||
)?.telephone_id;
|
|
||||||
return <Stack alignItems={"center"}>{telephone_id}</Stack>;
|
return <Stack alignItems={"center"}>{telephone_id}</Stack>;
|
||||||
};
|
};
|
||||||
export default TelephoneId;
|
export default TelephoneId;
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import { object, string } from "yup";
|
|||||||
import { useMemo, useState } from "react";
|
import { useMemo, useState } from "react";
|
||||||
import useRoles from "@/lib/hooks/useRoles";
|
import useRoles from "@/lib/hooks/useRoles";
|
||||||
import useRequest from "@/lib/hooks/useRequest";
|
import useRequest from "@/lib/hooks/useRequest";
|
||||||
import { CREATE_USER } from "@/core/utils/routes";
|
import { USER_SERVICE } from "@/core/utils/routes";
|
||||||
import { Visibility, VisibilityOff } from "@mui/icons-material";
|
import { Visibility, VisibilityOff } from "@mui/icons-material";
|
||||||
|
|
||||||
const defaultValues = {
|
const defaultValues = {
|
||||||
@@ -39,34 +39,27 @@ const defaultValues = {
|
|||||||
province_id: "",
|
province_id: "",
|
||||||
password: "",
|
password: "",
|
||||||
role_id: "",
|
role_id: "",
|
||||||
|
telephone_id: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
const validationSchema = object().shape({
|
const validationSchema = object().shape({
|
||||||
full_name: string().required("نام و نام خانوادگی خود را وارد کنید"),
|
full_name: string().required("نام و نام خانوادگی را وارد کنید"),
|
||||||
username: string().required("نام کاربری خود را وارد کنید"),
|
username: string().required("نام کاربری را وارد کنید"),
|
||||||
phone_number: string()
|
phone_number: string()
|
||||||
.matches(/^09\d{9}$/, "شماره همراه باید با 09 شروع شود و 11 رقم باشد.")
|
.matches(/^09\d{9}$/, "شماره همراه باید با 09 شروع شود و 11 رقم باشد.")
|
||||||
.required("شماره همراه خود را وارد کنید"),
|
.required("شماره همراه را وارد کنید"),
|
||||||
gender: string().required("جنسیت خود را وارد کنید"),
|
gender: string().required("جنسیت را وارد کنید"),
|
||||||
national_id: string()
|
national_id: string()
|
||||||
.test(
|
.test("max", "کد ملی باید شامل 10 رقم باشد", (value) => value.toString().length === 10)
|
||||||
"max",
|
.test("validation", "کد ملی صحیح نمی باشد", (value) => validateNationalCode(value))
|
||||||
"کد ملی باید شامل 10 رقم باشد",
|
.required("کد ملی را وارد کنید"),
|
||||||
(value) => value.toString().length === 10,
|
|
||||||
)
|
|
||||||
.test("validation", "کد ملی صحیح نمی باشد", (value) =>
|
|
||||||
validateNationalCode(value),
|
|
||||||
)
|
|
||||||
.required("کد ملی خود را وارد کنید"),
|
|
||||||
password: string()
|
password: string()
|
||||||
.required("رمز عبور خود را وارد کنید")
|
.required("رمز عبور را وارد کنید")
|
||||||
.matches(
|
.matches(/^(?=.*[a-zA-Z])(?=.*\d).{8,}$/, "رمز عبور باید حداقل 8 کاراکتر و متشکل از عدد، حرف یا سمبل باشد"),
|
||||||
/^(?=.*[a-zA-Z])(?=.*\d).{8,}$/,
|
position: string().required("سمت را وارد کنید"),
|
||||||
"رمز عبور باید حداقل 8 کاراکتر و متشکل از عدد، حرف یا سمبل باشد",
|
province_id: string().required("استان را وارد کنید"),
|
||||||
),
|
role_id: string().required("نقش را وارد کنید"),
|
||||||
position: string().required("سمت خود را وارد کنید"),
|
telephone_id: string().required("شماره تماس داخلی را وارد کنید"),
|
||||||
province_id: string().required("استان خود را وارد کنید"),
|
|
||||||
role_id: string().required("نقش خود را وارد کنید"),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const CreateUserForm = ({ setOpen, mutate }) => {
|
const CreateUserForm = ({ setOpen, mutate }) => {
|
||||||
@@ -121,7 +114,7 @@ const CreateUserForm = ({ setOpen, mutate }) => {
|
|||||||
formData.append(key, data[key]);
|
formData.append(key, data[key]);
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await requestServer(CREATE_USER, "post", {
|
await requestServer(USER_SERVICE, "post", {
|
||||||
data: formData,
|
data: formData,
|
||||||
});
|
});
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
@@ -175,11 +168,7 @@ const CreateUserForm = ({ setOpen, mutate }) => {
|
|||||||
startAdornment: (
|
startAdornment: (
|
||||||
<InputAdornment position="start">
|
<InputAdornment position="start">
|
||||||
<IconButton onClick={handleClickShowPassword}>
|
<IconButton onClick={handleClickShowPassword}>
|
||||||
{showPassword ? (
|
{showPassword ? <VisibilityOff /> : <Visibility />}
|
||||||
<VisibilityOff />
|
|
||||||
) : (
|
|
||||||
<Visibility />
|
|
||||||
)}
|
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</InputAdornment>
|
</InputAdornment>
|
||||||
),
|
),
|
||||||
@@ -265,12 +254,12 @@ const CreateUserForm = ({ setOpen, mutate }) => {
|
|||||||
render={({ field, fieldState: { error } }) => {
|
render={({ field, fieldState: { error } }) => {
|
||||||
return (
|
return (
|
||||||
<FormControl fullWidth error={!!error}>
|
<FormControl fullWidth error={!!error}>
|
||||||
<InputLabel id={`label-phone-number`} shrink>
|
<InputLabel id={`label-gender`} shrink>
|
||||||
جنسیت
|
جنسیت
|
||||||
</InputLabel>
|
</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
labelId={`label-phone-number`}
|
labelId={`label-gender`}
|
||||||
id={"phone-number"}
|
id={"gender"}
|
||||||
name={`gender`}
|
name={`gender`}
|
||||||
value={field.value}
|
value={field.value}
|
||||||
input={<OutlinedInput label={"جنسیت"} />}
|
input={<OutlinedInput label={"جنسیت"} />}
|
||||||
@@ -384,6 +373,25 @@ const CreateUserForm = ({ setOpen, mutate }) => {
|
|||||||
name={"role_id"}
|
name={"role_id"}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
<Grid size={{ xs: 12, md: 6 }}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<LtrTextField
|
||||||
|
{...field}
|
||||||
|
label="شماره تماس داخلی"
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
type="tel"
|
||||||
|
error={!!error}
|
||||||
|
helperText={!!error && error.message}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
name={"telephone_id"}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Stack>
|
</Stack>
|
||||||
</StyledForm>
|
</StyledForm>
|
||||||
|
|||||||
@@ -1,12 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { AddCircle } from "@mui/icons-material";
|
import { AddCircle } from "@mui/icons-material";
|
||||||
import {
|
import { Button, Dialog, IconButton, useMediaQuery, useTheme } from "@mui/material";
|
||||||
Button,
|
|
||||||
Dialog,
|
|
||||||
IconButton,
|
|
||||||
useMediaQuery,
|
|
||||||
useTheme,
|
|
||||||
} from "@mui/material";
|
|
||||||
import CreateUserForm from "./Form";
|
import CreateUserForm from "./Form";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
@@ -22,11 +16,7 @@ const CreateUser = ({ mutate }) => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{isMobile ? (
|
{isMobile ? (
|
||||||
<IconButton
|
<IconButton aria-label="کاربر جدید" color="primary" onClick={handleOpen}>
|
||||||
aria-label="کاربر جدید"
|
|
||||||
color="primary"
|
|
||||||
onClick={handleOpen}
|
|
||||||
>
|
|
||||||
<AddCircle sx={{ fontSize: "25px" }} />
|
<AddCircle sx={{ fontSize: "25px" }} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -3,10 +3,6 @@ import { Stack } from "@mui/material";
|
|||||||
|
|
||||||
const OnlineUsersReport = () => {
|
const OnlineUsersReport = () => {
|
||||||
const { clientsOnline } = useSocket();
|
const { clientsOnline } = useSocket();
|
||||||
return (
|
return <Stack>clientsOnline: {clientsOnline.length}</Stack>;
|
||||||
<Stack>
|
};
|
||||||
clientsOnline: {clientsOnline.length}
|
|
||||||
</Stack>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
export default OnlineUsersReport;
|
export default OnlineUsersReport;
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
"use client"
|
"use client";
|
||||||
import { Stack } from "@mui/material";
|
import { Stack } from "@mui/material";
|
||||||
import OnlineUsersReport from "./OnlineUsersReport";
|
import OnlineUsersReport from "./OnlineUsersReport";
|
||||||
|
|
||||||
const DashboardPage = () => {
|
const DashboardPage = () => {
|
||||||
return (
|
return (
|
||||||
<Stack>
|
<Stack>
|
||||||
<OnlineUsersReport />
|
{/* <OnlineUsersReport /> */}
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
export default DashboardPage;
|
export default DashboardPage;
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import DataTableWithAuth from "@/core/components/DataTableWithAuth";
|
||||||
|
import { GET_OPERATOR_DIALOGUE_OPERATOR } from "@/core/utils/routes";
|
||||||
|
import { Box } from "@mui/material";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
import moment from "jalali-moment";
|
||||||
|
import Player from "./Player";
|
||||||
|
import CheckIcon from "@mui/icons-material/Check";
|
||||||
|
import CloseIcon from "@mui/icons-material/Close";
|
||||||
|
|
||||||
|
const DataTable = () => {
|
||||||
|
const columns = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
header: "تاریخ",
|
||||||
|
id: "date",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
Cell: ({ row }) =>
|
||||||
|
row.original.calldate ? moment(row.original.calldate).locale("fa").format("yyyy/MM/DD") : <>-</>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "ساعت",
|
||||||
|
id: "time",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
Cell: ({ row }) =>
|
||||||
|
row.original.calldate ? moment(row.original.calldate).locale("fa").format("HH:mm") : <>-</>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "src",
|
||||||
|
header: "شماره تماس گیرنده",
|
||||||
|
id: "src",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "dst",
|
||||||
|
header: "کارشناس",
|
||||||
|
id: "dst",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "پخش صدا",
|
||||||
|
id: "is_listen",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
Cell: ({ row }) => {
|
||||||
|
const isListen = row.original.is_listen;
|
||||||
|
const Icon = isListen ? CheckIcon : CloseIcon;
|
||||||
|
const color = isListen ? "success.main" : "error.main";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ display: "flex", justifyContent: "center" }}>
|
||||||
|
<Icon sx={{ color }} />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "disposition",
|
||||||
|
header: "وضعیت",
|
||||||
|
id: "disposition",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "duration",
|
||||||
|
header: "طول تماس ( ثانیه )",
|
||||||
|
id: "duration",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "پخش صدا",
|
||||||
|
id: "player",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
Cell: ({ row }) => <Player audioUrl={row.original.recordingfile} messageId={row.original.id} />,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ p: 1, border: 1, borderColor: "divider", borderRadius: 1 }}>
|
||||||
|
<DataTableWithAuth
|
||||||
|
need_filter={true}
|
||||||
|
columns={columns}
|
||||||
|
table_url={GET_OPERATOR_DIALOGUE_OPERATOR}
|
||||||
|
page_name={"operatorDialoguePage"}
|
||||||
|
table_name={"operatorDialogueList"}
|
||||||
|
positionActionsColumn={"last"}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default DataTable;
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import { IconButton, Box } from "@mui/material";
|
||||||
|
import PlayArrowIcon from "@mui/icons-material/PlayArrow";
|
||||||
|
import PauseIcon from "@mui/icons-material/Pause";
|
||||||
|
import useWaveSurferPlayer from "@/lib/hooks/useWaveSurferPlayer";
|
||||||
|
|
||||||
|
const Player = ({ audioUrl }) => {
|
||||||
|
const { containerRef, isPlaying, playPause } = useWaveSurferPlayer(audioUrl);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ display: "flex", alignItems: "center", gap: 1, justifyContent: "center" }}>
|
||||||
|
<div ref={containerRef} style={{ width: 180 }} />
|
||||||
|
<IconButton aria-label="play-pause" onClick={playPause}>
|
||||||
|
{isPlaying ? <PauseIcon sx={{ color: "#d32f2f" }} /> : <PlayArrowIcon sx={{ color: "#1b4332" }} />}
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Player;
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Button, FormControl, FormHelperText, Grid, InputLabel, MenuItem, OutlinedInput, Select } from "@mui/material";
|
||||||
|
import LtrTextField from "@/core/components/LtrTextField";
|
||||||
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import { yupResolver } from "@hookform/resolvers/yup";
|
||||||
|
import { object, string, number } from "yup";
|
||||||
|
import { GET_SYSTEM_MESSAGES_SUPERVISOR } from "@/core/utils/routes";
|
||||||
|
import StyledForm from "@/core/components/StyledForm";
|
||||||
|
import useRequest from "@/lib/hooks/useRequest";
|
||||||
|
import usePermissionsList from "@/lib/hooks/usePermissionsList";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
|
||||||
|
const RateMessage = ({ date, messageId, initialValues, mutate }) => {
|
||||||
|
const requestServer = useRequest({ notificationSuccess: true });
|
||||||
|
|
||||||
|
const defaultValues = {
|
||||||
|
supervisor_id: initialValues?.supervisor_id ?? "",
|
||||||
|
is_good: initialValues?.is_good ?? "",
|
||||||
|
description: initialValues?.description ?? "",
|
||||||
|
};
|
||||||
|
|
||||||
|
const validationSchema = object().shape({
|
||||||
|
supervisor_id: string().required("کارشناس بررسی کننده را مشخص کنید"),
|
||||||
|
is_good: string().required("نحوه پاسخگویی را مشخص کنید"),
|
||||||
|
description: string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
handleSubmit,
|
||||||
|
reset,
|
||||||
|
formState: { isSubmitting, errors, isDirty, isValid },
|
||||||
|
} = useForm({ defaultValues, resolver: yupResolver(validationSchema) });
|
||||||
|
|
||||||
|
const onSubmit = async (data) => {
|
||||||
|
const fixData = {
|
||||||
|
date: date,
|
||||||
|
cdr_unique_id: messageId,
|
||||||
|
supervisor_id: data.supervisor_id,
|
||||||
|
is_good: data.is_good,
|
||||||
|
...(data.description !== "" && { description: data.description }),
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
await requestServer(`${GET_SYSTEM_MESSAGES_SUPERVISOR}`, "post", {
|
||||||
|
data: fixData,
|
||||||
|
});
|
||||||
|
mutate();
|
||||||
|
reset({
|
||||||
|
supervisor_id: initialValues?.supervisor_id ?? "",
|
||||||
|
is_good: initialValues?.is_good ?? "",
|
||||||
|
description: initialValues?.description ?? "",
|
||||||
|
});
|
||||||
|
} catch (error) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
reset({
|
||||||
|
supervisor_id: initialValues?.supervisor_id ?? "",
|
||||||
|
is_good: initialValues?.is_good ?? "",
|
||||||
|
description: initialValues?.description ?? "",
|
||||||
|
});
|
||||||
|
}, [initialValues.supervisor_id, initialValues.is_good, initialValues.description]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledForm id="monitoringForm" onSubmit={handleSubmit(onSubmit)} style={{ width: "100%", height: "100%" }}>
|
||||||
|
<Grid container spacing={2} sx={{ flexWrap: "nowrap", alignItems: "start" }}>
|
||||||
|
<Grid item sx={{ xs: 3, sm: 3 }}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<LtrTextField
|
||||||
|
{...field}
|
||||||
|
variant="outlined"
|
||||||
|
label="کارشناس بررسی کننده"
|
||||||
|
size="small"
|
||||||
|
sx={{
|
||||||
|
width: "180px",
|
||||||
|
}}
|
||||||
|
InputProps={{ sx: { fontSize: "12px" } }}
|
||||||
|
InputLabelProps={{ sx: { fontSize: "12px" } }}
|
||||||
|
error={!!error}
|
||||||
|
helperText={!!error && error.message}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
name="supervisor_id"
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item sx={{ xs: 3, sm: 3 }}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<LtrTextField
|
||||||
|
{...field}
|
||||||
|
variant="outlined"
|
||||||
|
label="نحوه پاسخگویی"
|
||||||
|
size="small"
|
||||||
|
sx={{
|
||||||
|
width: "180px",
|
||||||
|
}}
|
||||||
|
InputProps={{ sx: { fontSize: "12px" } }}
|
||||||
|
InputLabelProps={{ sx: { fontSize: "12px" } }}
|
||||||
|
error={!!error}
|
||||||
|
helperText={!!error && error.message}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
name="is_good"
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item sx={{ xs: 3, sm: 3 }}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<LtrTextField
|
||||||
|
{...field}
|
||||||
|
variant="outlined"
|
||||||
|
label="توضیحات"
|
||||||
|
size="small"
|
||||||
|
sx={{
|
||||||
|
width: "180px",
|
||||||
|
}}
|
||||||
|
InputProps={{ sx: { fontSize: "12px" } }}
|
||||||
|
InputLabelProps={{ sx: { fontSize: "12px" } }}
|
||||||
|
error={!!error}
|
||||||
|
helperText={!!error && error.message}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
name="description"
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item sx={{ xs: 3, sm: 3 }}>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
disabled={isSubmitting || !isDirty || !isValid}
|
||||||
|
size="medium"
|
||||||
|
autoFocus
|
||||||
|
variant="contained"
|
||||||
|
>
|
||||||
|
{isSubmitting ? "درحال ثبت..." : "ثبت"}
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</StyledForm>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RateMessage;
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import PageTitle from "@/core/components/PageTitle";
|
||||||
|
import { Stack } from "@mui/material";
|
||||||
|
import DataTable from "./DataTable";
|
||||||
|
|
||||||
|
const OperatorDialoguePage = () => {
|
||||||
|
return (
|
||||||
|
<Stack spacing={1}>
|
||||||
|
<PageTitle title={"صدای کارشناسان"} />
|
||||||
|
<DataTable />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default OperatorDialoguePage;
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import DataTableWithAuth from "@/core/components/DataTableWithAuth";
|
||||||
|
import { GET_OPERATOR_PEOPLE_MESSAGE_OPERATOR } from "@/core/utils/routes";
|
||||||
|
import { Box } from "@mui/material";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
import moment from "jalali-moment";
|
||||||
|
import Player from "./Player";
|
||||||
|
import CloseIcon from '@mui/icons-material/Close';
|
||||||
|
import CheckIcon from '@mui/icons-material/Check';
|
||||||
|
import RateMessage from "./RateMessage";
|
||||||
|
|
||||||
|
const DataTable = () => {
|
||||||
|
const columns = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
accessorKey: "id",
|
||||||
|
header: "کد یکتا",
|
||||||
|
id: "id",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "تاریخ",
|
||||||
|
id: "date",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
Cell: ({ row }) =>
|
||||||
|
row.original.date ? moment(row.original.date).locale("fa").format("yyyy/MM/DD") : <>-</>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "ساعت",
|
||||||
|
id: "time",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
Cell: ({ row }) =>
|
||||||
|
row.original.date ? moment(row.original.date).locale("fa").format("HH:mm") : <>-</>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "caller_phone_number",
|
||||||
|
header: "شماره تماس",
|
||||||
|
id: "caller_phone_number",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "پخش صدا",
|
||||||
|
id: "player",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
Cell: ({ row }) => <Player audioUrl={row.original.voice} messageId={row.original.id} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "پخش صدا",
|
||||||
|
id: "is_listen",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
Cell: ({ row }) => {
|
||||||
|
const isListen = row.original.is_listen;
|
||||||
|
const Icon = isListen ? CheckIcon : CloseIcon;
|
||||||
|
const color = isListen ? "success.main" : "error.main";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ display: "flex", justifyContent: "center" }}>
|
||||||
|
<Icon sx={{ color }} />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "listen_at",
|
||||||
|
header: "زمان شنیده شدن",
|
||||||
|
id: "listen_at",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "review_duration",
|
||||||
|
header: "زمان بررسی (دقیقه)",
|
||||||
|
id: "review_duration",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "ارزیابی",
|
||||||
|
id: "rate",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
Cell: ({ cell, row, table }) => {
|
||||||
|
const mutate = table.options.meta?.mutate;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RateMessage
|
||||||
|
mutate={mutate}
|
||||||
|
messageId={row.original.id}
|
||||||
|
initialValues={{
|
||||||
|
savaneh_id: row.original.savaneh_id,
|
||||||
|
action_id: row.original.action_id,
|
||||||
|
description: row.original.description,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ p: 1, border: 1, borderColor: "divider", borderRadius: 1 }}>
|
||||||
|
<DataTableWithAuth
|
||||||
|
need_filter={true}
|
||||||
|
columns={columns}
|
||||||
|
table_url={GET_OPERATOR_PEOPLE_MESSAGE_OPERATOR}
|
||||||
|
page_name={"operatorDialoguePage"}
|
||||||
|
table_name={"operatorDialogueList"}
|
||||||
|
positionActionsColumn={"last"}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default DataTable;
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import { IconButton, Box, Typography } from "@mui/material";
|
||||||
|
import PlayArrowIcon from "@mui/icons-material/PlayArrow";
|
||||||
|
import PauseIcon from "@mui/icons-material/Pause";
|
||||||
|
import useWaveSurferPlayer from "@/lib/hooks/useWaveSurferPlayer";
|
||||||
|
|
||||||
|
function formatTime(seconds) {
|
||||||
|
if (!seconds || isNaN(seconds)) return "0:00";
|
||||||
|
const minutes = Math.floor(seconds / 60);
|
||||||
|
const secs = Math.floor(seconds % 60);
|
||||||
|
return `${minutes}:${secs.toString().padStart(2, "0")}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Player = ({ audioUrl }) => {
|
||||||
|
const { containerRef, isPlaying, playPause, currentTime, duration } =
|
||||||
|
useWaveSurferPlayer(audioUrl);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: 1,
|
||||||
|
justifyContent: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div ref={containerRef} style={{ width: 180 }} />
|
||||||
|
|
||||||
|
<IconButton aria-label="play-pause" onClick={playPause}>
|
||||||
|
{isPlaying ? (
|
||||||
|
<PauseIcon sx={{ color: "#d32f2f" }} />
|
||||||
|
) : (
|
||||||
|
<PlayArrowIcon sx={{ color: "#1b4332" }} />
|
||||||
|
)}
|
||||||
|
</IconButton>
|
||||||
|
<Typography variant="body2" sx={{ minWidth: 60, textAlign: "right" }}>
|
||||||
|
{formatTime(currentTime)} / {formatTime(duration)}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Player;
|
||||||
@@ -0,0 +1,159 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Button, FormControl, Grid, InputLabel, MenuItem, Select } from "@mui/material";
|
||||||
|
import LtrTextField from "@/core/components/LtrTextField";
|
||||||
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import { yupResolver } from "@hookform/resolvers/yup";
|
||||||
|
import { object, string } from "yup";
|
||||||
|
import { GET_OPERATOR_PEOPLE_MESSAGE_OPERATOR } from "@/core/utils/routes";
|
||||||
|
import StyledForm from "@/core/components/StyledForm";
|
||||||
|
import useRequest from "@/lib/hooks/useRequest";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
|
||||||
|
const RateMessage = ({ messageId, initialValues, mutate }) => {
|
||||||
|
const requestServer = useRequest({ notificationSuccess: true });
|
||||||
|
|
||||||
|
const defaultValues = {
|
||||||
|
savaneh_id: initialValues?.savaneh_id ?? "",
|
||||||
|
action_id: initialValues?.action_id ?? "",
|
||||||
|
description: initialValues?.description ?? "",
|
||||||
|
};
|
||||||
|
|
||||||
|
const validationSchema = object().shape({
|
||||||
|
savaneh_id: string().required("وضعیت کیفیت پیام را مشخص کنید"),
|
||||||
|
action_id: string().optional(),
|
||||||
|
description: string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
handleSubmit,
|
||||||
|
reset,
|
||||||
|
formState: { isSubmitting, errors, isDirty, isValid },
|
||||||
|
} = useForm({ defaultValues, resolver: yupResolver(validationSchema) });
|
||||||
|
|
||||||
|
const onSubmit = async (data) => {
|
||||||
|
const fixData = {
|
||||||
|
savaneh_id: parseInt(data.savaneh_id),
|
||||||
|
...(data.action_id !== "" && { action_id: data.action_id }),
|
||||||
|
...(data.description !== "" && { description: data.description }),
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
await requestServer(`${GET_OPERATOR_PEOPLE_MESSAGE_OPERATOR}/${messageId}`, "post", {
|
||||||
|
data: fixData,
|
||||||
|
});
|
||||||
|
mutate();
|
||||||
|
reset({
|
||||||
|
savaneh_id: initialValues?.savaneh_id ?? "",
|
||||||
|
action_id: initialValues?.action_id ?? "",
|
||||||
|
description: initialValues?.description ?? "",
|
||||||
|
});
|
||||||
|
} catch (error) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
reset({
|
||||||
|
savaneh_id: initialValues?.savaneh_id ?? "",
|
||||||
|
action_id: initialValues?.action_id ?? "",
|
||||||
|
description: initialValues?.description ?? "",
|
||||||
|
});
|
||||||
|
}, [initialValues.savaneh_id, initialValues.action_id, initialValues.description]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledForm id="monitoringForm" onSubmit={handleSubmit(onSubmit)} style={{ width: "100%", height: "100%" }}>
|
||||||
|
<Grid container spacing={2} sx={{ flexWrap: "nowrap", alignItems: "start" }}>
|
||||||
|
<Grid item sx={{ xs: 3, sm: 3 }}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<FormControl fullWidth error={!!error} variant="outlined">
|
||||||
|
<InputLabel id="label-message-quality" size="small" sx={{ fontSize: "12px" }}>
|
||||||
|
پیگیری
|
||||||
|
</InputLabel>
|
||||||
|
<Select
|
||||||
|
variant="outlined"
|
||||||
|
labelId="label-message-quality"
|
||||||
|
id="savaneh_id"
|
||||||
|
name="savaneh_id"
|
||||||
|
size="small"
|
||||||
|
sx={{ width: "100px", fontSize: "12px" }}
|
||||||
|
value={field.value}
|
||||||
|
label="پیگیری"
|
||||||
|
onChange={(e) => field.onChange(e.target.value)}
|
||||||
|
>
|
||||||
|
<MenuItem value={1}>عدم نیاز به ثبت (مشکل راه)</MenuItem>
|
||||||
|
<MenuItem value={2}>عدم نیاز به ثبت (مشکل حمل و نقلی)</MenuItem>
|
||||||
|
<MenuItem value={3}>عدم نیاز به ثبت (مشکل عوارضی)</MenuItem>
|
||||||
|
<MenuItem value={4}>ثبت سوانح (مشکل راه)</MenuItem>
|
||||||
|
<MenuItem value={5}>ثبت سوانح (مشکل حمل و نقلی)</MenuItem>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
name="savaneh_id"
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item sx={{ xs: 3, sm: 3 }}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<LtrTextField
|
||||||
|
{...field}
|
||||||
|
variant="outlined"
|
||||||
|
label="کد واقعه"
|
||||||
|
size="small"
|
||||||
|
sx={{
|
||||||
|
width: "180px",
|
||||||
|
}}
|
||||||
|
InputProps={{ sx: { fontSize: "12px" } }}
|
||||||
|
InputLabelProps={{ sx: { fontSize: "12px" } }}
|
||||||
|
error={!!error}
|
||||||
|
helperText={!!error && error.message}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
name="action_id"
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item sx={{ xs: 3, sm: 3 }}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<LtrTextField
|
||||||
|
{...field}
|
||||||
|
variant="outlined"
|
||||||
|
label="توضیحات"
|
||||||
|
size="small"
|
||||||
|
sx={{
|
||||||
|
width: "180px",
|
||||||
|
}}
|
||||||
|
InputProps={{ sx: { fontSize: "12px" } }}
|
||||||
|
InputLabelProps={{ sx: { fontSize: "12px" } }}
|
||||||
|
error={!!error}
|
||||||
|
helperText={!!error && error.message}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
name="description"
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item sx={{ xs: 3, sm: 3 }}>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
disabled={isSubmitting || !isDirty || !isValid}
|
||||||
|
size="medium"
|
||||||
|
autoFocus
|
||||||
|
variant="contained"
|
||||||
|
>
|
||||||
|
{isSubmitting ? "درحال ثبت..." : "ثبت"}
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</StyledForm>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RateMessage;
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import PageTitle from "@/core/components/PageTitle";
|
||||||
|
import { Stack } from "@mui/material";
|
||||||
|
import DataTable from "./DataTable";
|
||||||
|
|
||||||
|
const PeopleMessagePage = () => {
|
||||||
|
return (
|
||||||
|
<Stack spacing={1}>
|
||||||
|
<PageTitle title={"پیام های مردمی"} />
|
||||||
|
<DataTable />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PeopleMessagePage;
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import DataTableWithAuth from "@/core/components/DataTableWithAuth";
|
||||||
|
import { GET_SYSTEM_MESSAGES_OPERATOR } from "@/core/utils/routes";
|
||||||
|
import { Box } from "@mui/material";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
import moment from "jalali-moment";
|
||||||
|
import Player from "./Player";
|
||||||
|
const DataTable = () => {
|
||||||
|
const columns = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
accessorKey: "id",
|
||||||
|
header: "کد یکتا",
|
||||||
|
id: "id",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "type_name",
|
||||||
|
header: "نوع",
|
||||||
|
id: "type_name",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "تاریخ",
|
||||||
|
id: "date",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
Cell: ({ row }) =>
|
||||||
|
row.original.date ? moment(row.original.date).locale("fa").format("yyyy/MM/DD") : <>-</>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "ساعت",
|
||||||
|
id: "time",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
Cell: ({ row }) =>
|
||||||
|
row.original.date ? moment(row.original.date).locale("fa").format("HH:mm") : <>-</>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "duration",
|
||||||
|
header: "طول تماس ( ثانیه )",
|
||||||
|
id: "duration",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "پخش صدا",
|
||||||
|
id: "player",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
Cell: ({ row }) => <Player audioUrl={row.original.voice} messageId={row.original.id} />,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ p: 1, border: 1, borderColor: "divider", borderRadius: 1 }}>
|
||||||
|
<DataTableWithAuth
|
||||||
|
need_filter={true}
|
||||||
|
columns={columns}
|
||||||
|
table_url={GET_SYSTEM_MESSAGES_OPERATOR}
|
||||||
|
page_name={"systemMessagesPage"}
|
||||||
|
table_name={"systemMessagesList"}
|
||||||
|
positionActionsColumn={"last"}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default DataTable;
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import { IconButton, Box, Typography } from "@mui/material";
|
||||||
|
import PlayArrowIcon from "@mui/icons-material/PlayArrow";
|
||||||
|
import PauseIcon from "@mui/icons-material/Pause";
|
||||||
|
import useWaveSurferPlayer from "@/lib/hooks/useWaveSurferPlayer";
|
||||||
|
|
||||||
|
function formatTime(seconds) {
|
||||||
|
if (!seconds || isNaN(seconds)) return "0:00";
|
||||||
|
const minutes = Math.floor(seconds / 60);
|
||||||
|
const secs = Math.floor(seconds % 60);
|
||||||
|
return `${minutes}:${secs.toString().padStart(2, "0")}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Player = ({ audioUrl }) => {
|
||||||
|
const { containerRef, isPlaying, playPause, currentTime, duration } =
|
||||||
|
useWaveSurferPlayer(audioUrl);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: 1,
|
||||||
|
justifyContent: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div ref={containerRef} style={{ width: 180 }} />
|
||||||
|
|
||||||
|
<IconButton aria-label="play-pause" onClick={playPause}>
|
||||||
|
{isPlaying ? (
|
||||||
|
<PauseIcon sx={{ color: "#d32f2f" }} />
|
||||||
|
) : (
|
||||||
|
<PlayArrowIcon sx={{ color: "#1b4332" }} />
|
||||||
|
)}
|
||||||
|
</IconButton>
|
||||||
|
<Typography variant="body2" sx={{ minWidth: 60, textAlign: "right" }}>
|
||||||
|
{formatTime(currentTime)} / {formatTime(duration)}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Player;
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import PageTitle from "@/core/components/PageTitle";
|
||||||
|
import { Stack } from "@mui/material";
|
||||||
|
import DataTable from "./DataTable";
|
||||||
|
|
||||||
|
const SystemMessagesPage = () => {
|
||||||
|
return (
|
||||||
|
<Stack spacing={1}>
|
||||||
|
<PageTitle title={"پیام های سیستمی"} />
|
||||||
|
<DataTable />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SystemMessagesPage;
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import DataTableWithAuth from "@/core/components/DataTableWithAuth";
|
||||||
|
import { GET_OPERATOR_DIALOGUE_SUPERVISOR } from "@/core/utils/routes";
|
||||||
|
import { Box } from "@mui/material";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
import moment from "jalali-moment";
|
||||||
|
import Player from "./Player";
|
||||||
|
import RateMessage from "./RateMessage";
|
||||||
|
import CheckIcon from "@mui/icons-material/Check";
|
||||||
|
import CloseIcon from "@mui/icons-material/Close";
|
||||||
|
|
||||||
|
const DataTable = () => {
|
||||||
|
const columns = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
header: "تاریخ",
|
||||||
|
id: "date",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
Cell: ({ row }) =>
|
||||||
|
row.original.calldate ? moment(row.original.calldate).locale("fa").format("yyyy/MM/DD") : <>-</>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "ساعت",
|
||||||
|
id: "time",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
Cell: ({ row }) =>
|
||||||
|
row.original.calldate ? moment(row.original.calldate).locale("fa").format("HH:mm") : <>-</>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "src",
|
||||||
|
header: "شماره تماس گیرنده",
|
||||||
|
id: "src",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "dst",
|
||||||
|
header: "کارشناس",
|
||||||
|
id: "dst",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "پخش صدا",
|
||||||
|
id: "is_listen",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
Cell: ({ row }) => {
|
||||||
|
const isListen = row.original.is_listen;
|
||||||
|
const Icon = isListen ? CheckIcon : CloseIcon;
|
||||||
|
const color = isListen ? "success.main" : "error.main";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ display: "flex", justifyContent: "center" }}>
|
||||||
|
<Icon sx={{ color }} />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "disposition",
|
||||||
|
header: "وضعیت",
|
||||||
|
id: "disposition",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "duration",
|
||||||
|
header: "طول تماس ( ثانیه )",
|
||||||
|
id: "duration",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "پخش صدا",
|
||||||
|
id: "player",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
Cell: ({ row }) => <Player audioUrl={row.original.recordingfile} messageId={row.original.id} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "ارزیابی",
|
||||||
|
id: "rate",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
Cell: ({ cell, row, table }) => {
|
||||||
|
const mutate = table.options.meta?.mutate;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RateMessage
|
||||||
|
mutate={mutate}
|
||||||
|
date={row.original.calldate}
|
||||||
|
messageId={row.original.uniqueid}
|
||||||
|
initialValues={{
|
||||||
|
supervisor_id: row.original.supervisor_id,
|
||||||
|
is_good: row.original.is_good,
|
||||||
|
description: row.original.description,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ p: 1, border: 1, borderColor: "divider", borderRadius: 1 }}>
|
||||||
|
<DataTableWithAuth
|
||||||
|
need_filter={true}
|
||||||
|
columns={columns}
|
||||||
|
table_url={GET_OPERATOR_DIALOGUE_SUPERVISOR}
|
||||||
|
page_name={"operatorDialoguePage"}
|
||||||
|
table_name={"operatorDialogueList"}
|
||||||
|
positionActionsColumn={"last"}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default DataTable;
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import { IconButton, Box } from "@mui/material";
|
||||||
|
import PlayArrowIcon from "@mui/icons-material/PlayArrow";
|
||||||
|
import PauseIcon from "@mui/icons-material/Pause";
|
||||||
|
import useWaveSurferPlayer from "@/lib/hooks/useWaveSurferPlayer";
|
||||||
|
|
||||||
|
const Player = ({ audioUrl }) => {
|
||||||
|
const { containerRef, isPlaying, playPause } = useWaveSurferPlayer(audioUrl);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ display: "flex", alignItems: "center", gap: 1, justifyContent: "center" }}>
|
||||||
|
<div ref={containerRef} style={{ width: 180 }} />
|
||||||
|
<IconButton aria-label="play-pause" onClick={playPause}>
|
||||||
|
{isPlaying ? <PauseIcon sx={{ color: "#d32f2f" }} /> : <PlayArrowIcon sx={{ color: "#1b4332" }} />}
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Player;
|
||||||
@@ -0,0 +1,158 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Button, FormControl, Grid, InputLabel, MenuItem, Select } from "@mui/material";
|
||||||
|
import LtrTextField from "@/core/components/LtrTextField";
|
||||||
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import { yupResolver } from "@hookform/resolvers/yup";
|
||||||
|
import { object, string } from "yup";
|
||||||
|
import { GET_OPERATOR_DIALOGUE_SUPERVISOR } from "@/core/utils/routes";
|
||||||
|
import StyledForm from "@/core/components/StyledForm";
|
||||||
|
import useRequest from "@/lib/hooks/useRequest";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
|
||||||
|
const RateMessage = ({ date, messageId, initialValues, mutate }) => {
|
||||||
|
const requestServer = useRequest({ notificationSuccess: true });
|
||||||
|
|
||||||
|
const defaultValues = {
|
||||||
|
supervisor_id: initialValues?.supervisor_id ?? "",
|
||||||
|
is_good: initialValues?.is_good ?? "",
|
||||||
|
description: initialValues?.description ?? "",
|
||||||
|
};
|
||||||
|
|
||||||
|
const validationSchema = object().shape({
|
||||||
|
supervisor_id: string().required("کارشناس بررسی کننده را مشخص کنید"),
|
||||||
|
is_good: string().required("نحوه پاسخگویی را مشخص کنید"),
|
||||||
|
description: string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
handleSubmit,
|
||||||
|
reset,
|
||||||
|
formState: { isSubmitting, errors, isDirty, isValid },
|
||||||
|
} = useForm({ defaultValues, resolver: yupResolver(validationSchema) });
|
||||||
|
|
||||||
|
const onSubmit = async (data) => {
|
||||||
|
const fixData = {
|
||||||
|
date: date,
|
||||||
|
cdr_unique_id: messageId,
|
||||||
|
supervisor_id: data.supervisor_id,
|
||||||
|
is_good: parseInt(data.is_good),
|
||||||
|
...(data.description !== "" && { description: data.description }),
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
await requestServer(`${GET_OPERATOR_DIALOGUE_SUPERVISOR}/rate`, "post", {
|
||||||
|
data: fixData,
|
||||||
|
});
|
||||||
|
mutate();
|
||||||
|
reset({
|
||||||
|
supervisor_id: initialValues?.supervisor_id ?? "",
|
||||||
|
is_good: initialValues?.is_good ?? "",
|
||||||
|
description: initialValues?.description ?? "",
|
||||||
|
});
|
||||||
|
} catch (error) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
reset({
|
||||||
|
supervisor_id: initialValues?.supervisor_id ?? "",
|
||||||
|
is_good: initialValues?.is_good ?? "",
|
||||||
|
description: initialValues?.description ?? "",
|
||||||
|
});
|
||||||
|
}, [initialValues.supervisor_id, initialValues.is_good, initialValues.description]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledForm id="monitoringForm" onSubmit={handleSubmit(onSubmit)} style={{ width: "100%", height: "100%" }}>
|
||||||
|
<Grid container spacing={2} sx={{ flexWrap: "nowrap", alignItems: "start" }}>
|
||||||
|
<Grid item sx={{ xs: 3, sm: 3 }}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<LtrTextField
|
||||||
|
{...field}
|
||||||
|
variant="outlined"
|
||||||
|
label="کارشناس بررسی کننده"
|
||||||
|
size="small"
|
||||||
|
sx={{
|
||||||
|
width: "180px",
|
||||||
|
}}
|
||||||
|
InputProps={{ sx: { fontSize: "12px" } }}
|
||||||
|
InputLabelProps={{ sx: { fontSize: "12px" } }}
|
||||||
|
error={!!error}
|
||||||
|
helperText={!!error && error.message}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
name="supervisor_id"
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item sx={{ xs: 3, sm: 3 }}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<FormControl fullWidth error={!!error} variant="outlined">
|
||||||
|
<InputLabel id="label-message-quality" size="small" sx={{ fontSize: "12px" }}>
|
||||||
|
نحوه پاسخگویی
|
||||||
|
</InputLabel>
|
||||||
|
<Select
|
||||||
|
variant="outlined"
|
||||||
|
labelId="label-message-quality"
|
||||||
|
id="is_good"
|
||||||
|
name="is_good"
|
||||||
|
size="small"
|
||||||
|
sx={{ width: "100px", fontSize: "12px" }}
|
||||||
|
value={field.value}
|
||||||
|
label="نحوه پاسخگویی"
|
||||||
|
onChange={(e) => field.onChange(e.target.value)}
|
||||||
|
>
|
||||||
|
<MenuItem value={0}>نامناسب</MenuItem>
|
||||||
|
<MenuItem value={1}>مناسب</MenuItem>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
name="is_good"
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item sx={{ xs: 3, sm: 3 }}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<LtrTextField
|
||||||
|
{...field}
|
||||||
|
variant="outlined"
|
||||||
|
label="توضیحات"
|
||||||
|
size="small"
|
||||||
|
sx={{
|
||||||
|
width: "180px",
|
||||||
|
}}
|
||||||
|
InputProps={{ sx: { fontSize: "12px" } }}
|
||||||
|
InputLabelProps={{ sx: { fontSize: "12px" } }}
|
||||||
|
error={!!error}
|
||||||
|
helperText={!!error && error.message}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
name="description"
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item sx={{ xs: 3, sm: 3 }}>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
disabled={isSubmitting || !isDirty || !isValid}
|
||||||
|
size="medium"
|
||||||
|
autoFocus
|
||||||
|
variant="contained"
|
||||||
|
>
|
||||||
|
{isSubmitting ? "درحال ثبت..." : "ثبت"}
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</StyledForm>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RateMessage;
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import PageTitle from "@/core/components/PageTitle";
|
||||||
|
import { Stack } from "@mui/material";
|
||||||
|
import DataTable from "./DataTable";
|
||||||
|
|
||||||
|
const OperatorDialoguePage = () => {
|
||||||
|
return (
|
||||||
|
<Stack spacing={1}>
|
||||||
|
<PageTitle title={"ارزیابی صدای کارشناسان"} />
|
||||||
|
<DataTable />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default OperatorDialoguePage;
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import DataTableWithAuth from "@/core/components/DataTableWithAuth";
|
||||||
|
import { GET_OPERATOR_PEOPLE_MESSAGE_SUPERVISOR } from "@/core/utils/routes";
|
||||||
|
import { Box } from "@mui/material";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
import moment from "jalali-moment";
|
||||||
|
import Player from "./Player";
|
||||||
|
import CloseIcon from '@mui/icons-material/Close';
|
||||||
|
import CheckIcon from '@mui/icons-material/Check';
|
||||||
|
import RateMessage from "./RateMessage";
|
||||||
|
|
||||||
|
const DataTable = () => {
|
||||||
|
const columns = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
accessorKey: "id",
|
||||||
|
header: "کد یکتا",
|
||||||
|
id: "id",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "تاریخ",
|
||||||
|
id: "date",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
Cell: ({ row }) =>
|
||||||
|
row.original.date ? moment(row.original.date).locale("fa").format("yyyy/MM/DD") : <>-</>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "ساعت",
|
||||||
|
id: "time",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
Cell: ({ row }) =>
|
||||||
|
row.original.date ? moment(row.original.date).locale("fa").format("HH:mm") : <>-</>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "caller_phone_number",
|
||||||
|
header: "شماره تماس",
|
||||||
|
id: "caller_phone_number",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "پخش صدا",
|
||||||
|
id: "player",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
Cell: ({ row }) => <Player audioUrl={row.original.voice} messageId={row.original.id} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "پخش صدا",
|
||||||
|
id: "is_listen",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
Cell: ({ row }) => {
|
||||||
|
const isListen = row.original.is_listen;
|
||||||
|
const Icon = isListen ? CheckIcon : CloseIcon;
|
||||||
|
const color = isListen ? "success.main" : "error.main";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ display: "flex", justifyContent: "center" }}>
|
||||||
|
<Icon sx={{ color }} />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "listen_at",
|
||||||
|
header: "زمان شنیده شدن",
|
||||||
|
id: "listen_at",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "review_duration",
|
||||||
|
header: "زمان بررسی (دقیقه)",
|
||||||
|
id: "review_duration",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "ارزیابی",
|
||||||
|
id: "rate",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
Cell: ({ cell, row, table }) => {
|
||||||
|
const mutate = table.options.meta?.mutate;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RateMessage
|
||||||
|
mutate={mutate}
|
||||||
|
messageId={row.original.id}
|
||||||
|
initialValues={{
|
||||||
|
savaneh_id: row.original.savaneh_id,
|
||||||
|
action_id: row.original.action_id,
|
||||||
|
description: row.original.description,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ p: 1, border: 1, borderColor: "divider", borderRadius: 1 }}>
|
||||||
|
<DataTableWithAuth
|
||||||
|
need_filter={true}
|
||||||
|
columns={columns}
|
||||||
|
table_url={GET_OPERATOR_PEOPLE_MESSAGE_SUPERVISOR}
|
||||||
|
page_name={"operatorDialoguePage"}
|
||||||
|
table_name={"operatorDialogueList"}
|
||||||
|
positionActionsColumn={"last"}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default DataTable;
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import { IconButton, Box, Typography } from "@mui/material";
|
||||||
|
import PlayArrowIcon from "@mui/icons-material/PlayArrow";
|
||||||
|
import PauseIcon from "@mui/icons-material/Pause";
|
||||||
|
import useWaveSurferPlayer from "@/lib/hooks/useWaveSurferPlayer";
|
||||||
|
|
||||||
|
function formatTime(seconds) {
|
||||||
|
if (!seconds || isNaN(seconds)) return "0:00";
|
||||||
|
const minutes = Math.floor(seconds / 60);
|
||||||
|
const secs = Math.floor(seconds % 60);
|
||||||
|
return `${minutes}:${secs.toString().padStart(2, "0")}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Player = ({ audioUrl }) => {
|
||||||
|
const { containerRef, isPlaying, playPause, currentTime, duration } =
|
||||||
|
useWaveSurferPlayer(audioUrl);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: 1,
|
||||||
|
justifyContent: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div ref={containerRef} style={{ width: 180 }} />
|
||||||
|
|
||||||
|
<IconButton aria-label="play-pause" onClick={playPause}>
|
||||||
|
{isPlaying ? (
|
||||||
|
<PauseIcon sx={{ color: "#d32f2f" }} />
|
||||||
|
) : (
|
||||||
|
<PlayArrowIcon sx={{ color: "#1b4332" }} />
|
||||||
|
)}
|
||||||
|
</IconButton>
|
||||||
|
<Typography variant="body2" sx={{ minWidth: 60, textAlign: "right" }}>
|
||||||
|
{formatTime(currentTime)} / {formatTime(duration)}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Player;
|
||||||
@@ -0,0 +1,160 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Button, FormControl, FormHelperText, Grid, InputLabel, MenuItem, OutlinedInput, Select } from "@mui/material";
|
||||||
|
import LtrTextField from "@/core/components/LtrTextField";
|
||||||
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import { yupResolver } from "@hookform/resolvers/yup";
|
||||||
|
import { object, string, number } from "yup";
|
||||||
|
import { GET_SYSTEM_MESSAGES_SUPERVISOR } from "@/core/utils/routes";
|
||||||
|
import StyledForm from "@/core/components/StyledForm";
|
||||||
|
import useRequest from "@/lib/hooks/useRequest";
|
||||||
|
import usePermissionsList from "@/lib/hooks/usePermissionsList";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
|
||||||
|
const RateMessage = ({ messageId, initialValues, mutate }) => {
|
||||||
|
const requestServer = useRequest({ notificationSuccess: true });
|
||||||
|
|
||||||
|
const defaultValues = {
|
||||||
|
savaneh_id: initialValues?.savaneh_id ?? "",
|
||||||
|
action_id: initialValues?.action_id ?? "",
|
||||||
|
description: initialValues?.description ?? "",
|
||||||
|
};
|
||||||
|
|
||||||
|
const validationSchema = object().shape({
|
||||||
|
savaneh_id: string().required("وضعیت کیفیت پیام را مشخص کنید"),
|
||||||
|
action_id: string().optional(),
|
||||||
|
description: string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
handleSubmit,
|
||||||
|
reset,
|
||||||
|
formState: { isSubmitting, errors, isDirty, isValid },
|
||||||
|
} = useForm({ defaultValues, resolver: yupResolver(validationSchema) });
|
||||||
|
|
||||||
|
const onSubmit = async (data) => {
|
||||||
|
const fixData = {
|
||||||
|
savaneh_id: parseInt(data.savaneh_id),
|
||||||
|
...(data.action_id !== "" && { action_id: data.action_id }),
|
||||||
|
...(data.description !== "" && { description: data.description }),
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
await requestServer(`${GET_SYSTEM_MESSAGES_SUPERVISOR}/${messageId}`, "post", {
|
||||||
|
data: fixData,
|
||||||
|
});
|
||||||
|
mutate();
|
||||||
|
reset({
|
||||||
|
savaneh_id: initialValues?.savaneh_id ?? "",
|
||||||
|
action_id: initialValues?.action_id ?? "",
|
||||||
|
description: initialValues?.description ?? "",
|
||||||
|
});
|
||||||
|
} catch (error) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
reset({
|
||||||
|
savaneh_id: initialValues?.savaneh_id ?? "",
|
||||||
|
action_id: initialValues?.action_id ?? "",
|
||||||
|
description: initialValues?.description ?? "",
|
||||||
|
});
|
||||||
|
}, [initialValues.savaneh_id, initialValues.action_id, initialValues.description]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledForm id="monitoringForm" onSubmit={handleSubmit(onSubmit)} style={{ width: "100%", height: "100%" }}>
|
||||||
|
<Grid container spacing={2} sx={{ flexWrap: "nowrap", alignItems: "start" }}>
|
||||||
|
<Grid item sx={{ xs: 3, sm: 3 }}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<FormControl fullWidth error={!!error} variant="outlined">
|
||||||
|
<InputLabel id="label-message-quality" size="small" sx={{ fontSize: "12px" }}>
|
||||||
|
پیگیری
|
||||||
|
</InputLabel>
|
||||||
|
<Select
|
||||||
|
variant="outlined"
|
||||||
|
labelId="label-message-quality"
|
||||||
|
id="savaneh_id"
|
||||||
|
name="savaneh_id"
|
||||||
|
size="small"
|
||||||
|
sx={{ width: "100px", fontSize: "12px" }}
|
||||||
|
value={field.value}
|
||||||
|
label="پیگیری"
|
||||||
|
onChange={(e) => field.onChange(e.target.value)}
|
||||||
|
>
|
||||||
|
<MenuItem value={1}>عدم نیاز به ثبت (مشکل راه)</MenuItem>
|
||||||
|
<MenuItem value={2}>عدم نیاز به ثبت (مشکل حمل و نقلی)</MenuItem>
|
||||||
|
<MenuItem value={3}>عدم نیاز به ثبت (مشکل عوارضی)</MenuItem>
|
||||||
|
<MenuItem value={4}>ثبت سوانح (مشکل راه)</MenuItem>
|
||||||
|
<MenuItem value={5}>ثبت سوانح (مشکل حمل و نقلی)</MenuItem>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
name="savaneh_id"
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item sx={{ xs: 3, sm: 3 }}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<LtrTextField
|
||||||
|
{...field}
|
||||||
|
variant="outlined"
|
||||||
|
label="کد واقعه"
|
||||||
|
size="small"
|
||||||
|
sx={{
|
||||||
|
width: "180px",
|
||||||
|
}}
|
||||||
|
InputProps={{ sx: { fontSize: "12px" } }}
|
||||||
|
InputLabelProps={{ sx: { fontSize: "12px" } }}
|
||||||
|
error={!!error}
|
||||||
|
helperText={!!error && error.message}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
name="action_id"
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item sx={{ xs: 3, sm: 3 }}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<LtrTextField
|
||||||
|
{...field}
|
||||||
|
variant="outlined"
|
||||||
|
label="توضیحات"
|
||||||
|
size="small"
|
||||||
|
sx={{
|
||||||
|
width: "180px",
|
||||||
|
}}
|
||||||
|
InputProps={{ sx: { fontSize: "12px" } }}
|
||||||
|
InputLabelProps={{ sx: { fontSize: "12px" } }}
|
||||||
|
error={!!error}
|
||||||
|
helperText={!!error && error.message}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
name="description"
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item sx={{ xs: 3, sm: 3 }}>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
disabled={isSubmitting || !isDirty || !isValid}
|
||||||
|
size="medium"
|
||||||
|
autoFocus
|
||||||
|
variant="contained"
|
||||||
|
>
|
||||||
|
{isSubmitting ? "درحال ثبت..." : "ثبت"}
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</StyledForm>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RateMessage;
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import PageTitle from "@/core/components/PageTitle";
|
||||||
|
import { Stack } from "@mui/material";
|
||||||
|
import DataTable from "./DataTable";
|
||||||
|
|
||||||
|
const PeopleMessagePage = () => {
|
||||||
|
return (
|
||||||
|
<Stack spacing={1}>
|
||||||
|
<PageTitle title={"ارزیابی پیام های مردمی"} />
|
||||||
|
<DataTable />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PeopleMessagePage;
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import DataTableWithAuth from "@/core/components/DataTableWithAuth";
|
||||||
|
import { GET_SYSTEM_MESSAGES_SUPERVISOR } from "@/core/utils/routes";
|
||||||
|
import { Box } from "@mui/material";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
import moment from "jalali-moment";
|
||||||
|
import Player from "./Player";
|
||||||
|
import RateMessage from "./RateMessage";
|
||||||
|
|
||||||
|
const DataTable = () => {
|
||||||
|
const columns = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
accessorKey: "id",
|
||||||
|
header: "کد یکتا",
|
||||||
|
id: "id",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "type_name",
|
||||||
|
header: "نوع",
|
||||||
|
id: "type_name",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "تاریخ",
|
||||||
|
id: "date",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
Cell: ({ row }) =>
|
||||||
|
row.original.date ? moment(row.original.date).locale("fa").format("yyyy/MM/DD") : <>-</>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "ساعت",
|
||||||
|
id: "time",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
Cell: ({ row }) =>
|
||||||
|
row.original.date ? moment(row.original.date).locale("fa").format("HH:mm") : <>-</>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "duration",
|
||||||
|
header: "طول تماس ( ثانیه )",
|
||||||
|
id: "duration",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "پخش صدا",
|
||||||
|
id: "player",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
Cell: ({ row }) => <Player audioUrl={row.original.voice} messageId={row.original.id} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "ارزیابی",
|
||||||
|
id: "rate",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
Cell: ({ cell, row, table }) => {
|
||||||
|
const mutate = table.options.meta?.mutate;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RateMessage
|
||||||
|
mutate={mutate}
|
||||||
|
messageId={row.original.id}
|
||||||
|
initialValues={{
|
||||||
|
message_quality: row.original.message_quality,
|
||||||
|
format_quality: row.original.format_quality,
|
||||||
|
description: row.original.description,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ p: 1, border: 1, borderColor: "divider", borderRadius: 1 }}>
|
||||||
|
<DataTableWithAuth
|
||||||
|
need_filter={true}
|
||||||
|
columns={columns}
|
||||||
|
table_url={GET_SYSTEM_MESSAGES_SUPERVISOR}
|
||||||
|
page_name={"systemMessagesPage"}
|
||||||
|
table_name={"systemMessagesList"}
|
||||||
|
positionActionsColumn={"last"}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default DataTable;
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import { IconButton, Box, Typography } from "@mui/material";
|
||||||
|
import PlayArrowIcon from "@mui/icons-material/PlayArrow";
|
||||||
|
import PauseIcon from "@mui/icons-material/Pause";
|
||||||
|
import useWaveSurferPlayer from "@/lib/hooks/useWaveSurferPlayer";
|
||||||
|
|
||||||
|
function formatTime(seconds) {
|
||||||
|
if (!seconds || isNaN(seconds)) return "0:00";
|
||||||
|
const minutes = Math.floor(seconds / 60);
|
||||||
|
const secs = Math.floor(seconds % 60);
|
||||||
|
return `${minutes}:${secs.toString().padStart(2, "0")}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Player = ({ audioUrl }) => {
|
||||||
|
const { containerRef, isPlaying, playPause, currentTime, duration } =
|
||||||
|
useWaveSurferPlayer(audioUrl);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: 1,
|
||||||
|
justifyContent: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div ref={containerRef} style={{ width: 180 }} />
|
||||||
|
|
||||||
|
<IconButton aria-label="play-pause" onClick={playPause}>
|
||||||
|
{isPlaying ? (
|
||||||
|
<PauseIcon sx={{ color: "#d32f2f" }} />
|
||||||
|
) : (
|
||||||
|
<PlayArrowIcon sx={{ color: "#1b4332" }} />
|
||||||
|
)}
|
||||||
|
</IconButton>
|
||||||
|
<Typography variant="body2" sx={{ minWidth: 60, textAlign: "right" }}>
|
||||||
|
{formatTime(currentTime)} / {formatTime(duration)}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Player;
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Button, FormControl, FormHelperText, Grid, InputLabel, MenuItem, OutlinedInput, Select } from "@mui/material";
|
||||||
|
import LtrTextField from "@/core/components/LtrTextField";
|
||||||
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import { yupResolver } from "@hookform/resolvers/yup";
|
||||||
|
import { object, string, number } from "yup";
|
||||||
|
import { GET_SYSTEM_MESSAGES_SUPERVISOR } from "@/core/utils/routes";
|
||||||
|
import StyledForm from "@/core/components/StyledForm";
|
||||||
|
import useRequest from "@/lib/hooks/useRequest";
|
||||||
|
import usePermissionsList from "@/lib/hooks/usePermissionsList";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
|
||||||
|
const RateMessage = ({ messageId, initialValues, mutate }) => {
|
||||||
|
const requestServer = useRequest({ notificationSuccess: true });
|
||||||
|
|
||||||
|
const defaultValues = {
|
||||||
|
message_quality: initialValues?.message_quality ?? "",
|
||||||
|
format_quality: initialValues?.format_quality ?? "",
|
||||||
|
description: initialValues?.description ?? "",
|
||||||
|
};
|
||||||
|
|
||||||
|
const validationSchema = object().shape({
|
||||||
|
message_quality: string().required("وضعیت کیفیت پیام را مشخص کنید"),
|
||||||
|
format_quality: string().required("وضعیت فرمت پیام را مشخص کنید"),
|
||||||
|
description: string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
handleSubmit,
|
||||||
|
reset,
|
||||||
|
formState: { isSubmitting, errors, isDirty, isValid },
|
||||||
|
} = useForm({ defaultValues, resolver: yupResolver(validationSchema) });
|
||||||
|
|
||||||
|
const onSubmit = async (data) => {
|
||||||
|
const fixData = {
|
||||||
|
message_quality: parseInt(data.message_quality),
|
||||||
|
format_quality: parseInt(data.format_quality),
|
||||||
|
...(data.description !== "" && { description: data.description }),
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
await requestServer(`${GET_SYSTEM_MESSAGES_SUPERVISOR}/${messageId}`, "post", {
|
||||||
|
data: fixData,
|
||||||
|
});
|
||||||
|
mutate();
|
||||||
|
reset({
|
||||||
|
message_quality: initialValues?.message_quality ?? "",
|
||||||
|
format_quality: initialValues?.format_quality ?? "",
|
||||||
|
description: initialValues?.description ?? "",
|
||||||
|
});
|
||||||
|
} catch (error) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
reset({
|
||||||
|
message_quality: initialValues?.message_quality ?? "",
|
||||||
|
format_quality: initialValues?.format_quality ?? "",
|
||||||
|
description: initialValues?.description ?? "",
|
||||||
|
});
|
||||||
|
}, [initialValues.message_quality, initialValues.format_quality, initialValues.description]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledForm id="monitoringForm" onSubmit={handleSubmit(onSubmit)} style={{ width: "100%", height: "100%" }}>
|
||||||
|
<Grid container spacing={2} sx={{ flexWrap: "nowrap", alignItems: "start" }}>
|
||||||
|
<Grid item sx={{ xs: 3, sm: 3 }}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<FormControl fullWidth error={!!error} variant="outlined">
|
||||||
|
<InputLabel id="label-message-quality" size="small" sx={{ fontSize: "12px" }}>
|
||||||
|
کیفیت پیام
|
||||||
|
</InputLabel>
|
||||||
|
<Select
|
||||||
|
variant="outlined"
|
||||||
|
labelId="label-message-quality"
|
||||||
|
id="message_quality"
|
||||||
|
name="message_quality"
|
||||||
|
size="small"
|
||||||
|
sx={{ width: "100px", fontSize: "12px" }}
|
||||||
|
value={field.value}
|
||||||
|
label="کیفیت پیام"
|
||||||
|
onChange={(e) => field.onChange(e.target.value)}
|
||||||
|
>
|
||||||
|
<MenuItem value={0}>نامناسب</MenuItem>
|
||||||
|
<MenuItem value={1}>مناسب</MenuItem>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
name="message_quality"
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item sx={{ xs: 3, sm: 3 }}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<FormControl fullWidth error={!!error} variant="outlined">
|
||||||
|
<InputLabel id="label-format-quality" size="small" sx={{ fontSize: "12px" }}>
|
||||||
|
فرمت پیام
|
||||||
|
</InputLabel>
|
||||||
|
<Select
|
||||||
|
variant="outlined"
|
||||||
|
labelId="label-format-quality"
|
||||||
|
id="format_quality"
|
||||||
|
name="format_quality"
|
||||||
|
size="small"
|
||||||
|
sx={{ width: "100px", fontSize: "12px" }}
|
||||||
|
value={field.value}
|
||||||
|
label="فرمت پیام"
|
||||||
|
onChange={(e) => field.onChange(e.target.value)}
|
||||||
|
>
|
||||||
|
<MenuItem value={0}>نامناسب</MenuItem>
|
||||||
|
<MenuItem value={1}>مناسب</MenuItem>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
name="format_quality"
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item sx={{ xs: 3, sm: 3 }}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
render={({ field, fieldState: { error } }) => {
|
||||||
|
return (
|
||||||
|
<LtrTextField
|
||||||
|
{...field}
|
||||||
|
variant="outlined"
|
||||||
|
label="توضیحات"
|
||||||
|
size="small"
|
||||||
|
sx={{
|
||||||
|
width: "180px",
|
||||||
|
}}
|
||||||
|
InputProps={{ sx: { fontSize: "12px" } }}
|
||||||
|
InputLabelProps={{ sx: { fontSize: "12px" } }}
|
||||||
|
error={!!error}
|
||||||
|
helperText={!!error && error.message}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
name="description"
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item sx={{ xs: 3, sm: 3 }}>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
disabled={isSubmitting || !isDirty || !isValid}
|
||||||
|
size="medium"
|
||||||
|
autoFocus
|
||||||
|
variant="contained"
|
||||||
|
>
|
||||||
|
{isSubmitting ? "درحال ثبت..." : "ثبت"}
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</StyledForm>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RateMessage;
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import PageTitle from "@/core/components/PageTitle";
|
||||||
|
import { Stack } from "@mui/material";
|
||||||
|
import DataTable from "./DataTable";
|
||||||
|
|
||||||
|
const SystemMessagesPage = () => {
|
||||||
|
return (
|
||||||
|
<Stack spacing={1}>
|
||||||
|
<PageTitle title={"ارزیابی پیام های سیستمی"} />
|
||||||
|
<DataTable />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SystemMessagesPage;
|
||||||
@@ -0,0 +1,200 @@
|
|||||||
|
import DataTableWithAuth from "@/core/components/DataTableWithAuth";
|
||||||
|
import { GET_PROVINCE_CUMULATIVE_CALLS_DAILY_REPORT } from "@/core/utils/routes";
|
||||||
|
import { Box } from "@mui/material";
|
||||||
|
import moment from "jalali-moment";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
|
||||||
|
export default function DataTable() {
|
||||||
|
const columns = useMemo(() => [
|
||||||
|
{
|
||||||
|
header: "تاریخ",
|
||||||
|
id: "date",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
Cell: ({ row }) => (row.original.d ? moment(row.original.d).locale("fa").format("yyyy/MM/DD") : <>-</>),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "total",
|
||||||
|
header: "تمامی تماس ها",
|
||||||
|
id: "total",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "تماس با تهران",
|
||||||
|
id: "call_to_tehran",
|
||||||
|
enableColumnFilter: false,
|
||||||
|
enableSorting: false,
|
||||||
|
grow: false,
|
||||||
|
size: 50,
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
accessorKey: "teh4",
|
||||||
|
header: "کلید 4",
|
||||||
|
id: "teh4",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "integer",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "teh5",
|
||||||
|
header: "کلید 5",
|
||||||
|
id: "teh5",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "تماس با اپراتور استانی",
|
||||||
|
id: "call_to_ostan",
|
||||||
|
enableColumnFilter: false,
|
||||||
|
enableSorting: false,
|
||||||
|
grow: false,
|
||||||
|
size: 50,
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
accessorKey: "ok",
|
||||||
|
header: "موفق",
|
||||||
|
id: "ok",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "fail",
|
||||||
|
header: "جواب داده نشده",
|
||||||
|
id: "fail",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "nok",
|
||||||
|
header: "نا موفق",
|
||||||
|
id: "nok",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "os4",
|
||||||
|
header: "کلید 4",
|
||||||
|
id: "os4",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "os5",
|
||||||
|
header: "کلید 5",
|
||||||
|
id: "os5",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "dnd",
|
||||||
|
header: "dnd",
|
||||||
|
id: "dnd",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "trans",
|
||||||
|
header: "انتقال تماس",
|
||||||
|
id: "trans",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "b10",
|
||||||
|
header: "تماس بالای 10 ثانیه",
|
||||||
|
id: "b10",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "avg",
|
||||||
|
header: "میانگین تماس ها",
|
||||||
|
id: "avg",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
Cell: ({ row }) => <>{row.original.avg ? row.original.avg : <>-</>}</>,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Box sx={{ p: 1, border: 1, borderColor: "divider", borderRadius: 1 }}>
|
||||||
|
<DataTableWithAuth
|
||||||
|
need_filter={true}
|
||||||
|
columns={columns}
|
||||||
|
table_url={GET_PROVINCE_CUMULATIVE_CALLS_DAILY_REPORT}
|
||||||
|
page_name={"provinceCumulativeCallsReportPage"}
|
||||||
|
table_name={"provinceCumulativeCallsReportList"}
|
||||||
|
positionActionsColumn={"last"}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import PageTitle from "@/core/components/PageTitle";
|
||||||
|
import { Stack } from "@mui/material";
|
||||||
|
import DataTable from "./DataTable";
|
||||||
|
|
||||||
|
export default function ProvinceCumulativeCallsReportPage() {
|
||||||
|
return (
|
||||||
|
<Stack spacing={1}>
|
||||||
|
<PageTitle title={"گزارشات تجمعی"} />
|
||||||
|
<DataTable />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
import DataTableWithAuth from "@/core/components/DataTableWithAuth";
|
||||||
|
import { GET_PROVINCE_DETAILED_LOGS_REPORT } from "@/core/utils/routes";
|
||||||
|
import { Box } from "@mui/material";
|
||||||
|
import moment from "jalali-moment";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
|
||||||
|
export default function DataTable() {
|
||||||
|
const columns = useMemo(() => [
|
||||||
|
{
|
||||||
|
header: "تاریخ",
|
||||||
|
id: "date",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
Cell: ({ row }) => {
|
||||||
|
const dateTime = row.original.calldate;
|
||||||
|
if (!dateTime) return <>-</>;
|
||||||
|
const date = dateTime.split(" ")[0];
|
||||||
|
const persianDate = moment(date).locale("fa").format("YYYY-MM-DD") || "";
|
||||||
|
return <>{persianDate}</>;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "ساعت",
|
||||||
|
id: "time",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
Cell: ({ row }) => {
|
||||||
|
const dateTime = row.original.calldate;
|
||||||
|
if (!dateTime) return <>-</>;
|
||||||
|
const time = dateTime.split(" ")[1] || "";
|
||||||
|
return <>{time}</>;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "src",
|
||||||
|
header: "شماره تماس گیرنده",
|
||||||
|
id: "src",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "dst",
|
||||||
|
header: "مقصد تماس",
|
||||||
|
id: "dst",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "disposition",
|
||||||
|
header: "وضعیت پاسخگویی",
|
||||||
|
id: "disposition",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "duration",
|
||||||
|
header: "مدت زمان تماس",
|
||||||
|
id: "duration",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Box sx={{ p: 1, border: 1, borderColor: "divider", borderRadius: 1 }}>
|
||||||
|
<DataTableWithAuth
|
||||||
|
need_filter={true}
|
||||||
|
columns={columns}
|
||||||
|
table_url={GET_PROVINCE_DETAILED_LOGS_REPORT}
|
||||||
|
page_name={"provinceDetailedLogsReportPage"}
|
||||||
|
table_name={"provinceDetailedLogsReportList"}
|
||||||
|
positionActionsColumn={"last"}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import PageTitle from "@/core/components/PageTitle";
|
||||||
|
import { Stack } from "@mui/material";
|
||||||
|
import DataTable from "./DataTable";
|
||||||
|
|
||||||
|
export default function ProvinceDetailedLogsReportPage() {
|
||||||
|
return (
|
||||||
|
<Stack spacing={1}>
|
||||||
|
<PageTitle title={"ریز گزارش ها"} />
|
||||||
|
<DataTable />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
import DataTableWithAuth from "@/core/components/DataTableWithAuth";
|
||||||
|
import { GET_PROVINCE_OPERATORS_PERFORMANCE_TOTAL_REPORT } from "@/core/utils/routes";
|
||||||
|
import { Box } from "@mui/material";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
|
||||||
|
export default function DataTableCumulative() {
|
||||||
|
const columns = useMemo(() => [
|
||||||
|
{
|
||||||
|
accessorKey: "dst",
|
||||||
|
header: "کد یکتای کارشناس",
|
||||||
|
id: "dst",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "total",
|
||||||
|
header: "تعداد کل تماس ها",
|
||||||
|
id: "total",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "ok",
|
||||||
|
header: "تماس های موفق",
|
||||||
|
id: "ok",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "fail",
|
||||||
|
header: "تماس های جواب داده نشده",
|
||||||
|
id: "fail",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "nok",
|
||||||
|
header: "تماس های ناموفق",
|
||||||
|
id: "nok",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "b10",
|
||||||
|
header: "تماس های بالای 10 ثانیه",
|
||||||
|
id: "b10",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "dnd",
|
||||||
|
header: "dnd",
|
||||||
|
id: "dnd",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Box sx={{ p: 1, border: 1, borderColor: "divider", borderRadius: 1 }}>
|
||||||
|
<DataTableWithAuth
|
||||||
|
need_filter={true}
|
||||||
|
columns={columns}
|
||||||
|
table_url={GET_PROVINCE_OPERATORS_PERFORMANCE_TOTAL_REPORT}
|
||||||
|
page_name={"provinceOperatorPerformanceCumulativeReportPage"}
|
||||||
|
table_name={"provinceOperatorPerformanceCumulativeReportList"}
|
||||||
|
positionActionsColumn={"last"}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
import DataTableWithAuth from "@/core/components/DataTableWithAuth";
|
||||||
|
import { GET_PROVINCE_OPERATORS_PERFORMANCE_DAILY_REPORT } from "@/core/utils/routes";
|
||||||
|
import { Box } from "@mui/material";
|
||||||
|
import moment from "jalali-moment";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
|
||||||
|
export default function DataTableDaily() {
|
||||||
|
const columns = useMemo(() => [
|
||||||
|
{
|
||||||
|
header: "تاریخ",
|
||||||
|
id: "date",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
Cell: ({ row }) => (row.original.d ? moment(row.original.d).locale("fa").format("yyyy/MM/DD") : <>-</>),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "dst",
|
||||||
|
header: "کد یکتای کارشناس",
|
||||||
|
id: "dst",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "total",
|
||||||
|
header: "تعداد کل تماس ها",
|
||||||
|
id: "total",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "ok",
|
||||||
|
header: "تماس های موفق",
|
||||||
|
id: "ok",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "fail",
|
||||||
|
header: "تماس های جواب داده نشده",
|
||||||
|
id: "fail",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "nok",
|
||||||
|
header: "تماس های ناموفق",
|
||||||
|
id: "nok",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "b10",
|
||||||
|
header: "تماس های بالای 10 ثانیه",
|
||||||
|
id: "b10",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "dnd",
|
||||||
|
header: "dnd",
|
||||||
|
id: "dnd",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Box sx={{ p: 1, border: 1, borderColor: "divider", borderRadius: 1 }}>
|
||||||
|
<DataTableWithAuth
|
||||||
|
need_filter={true}
|
||||||
|
columns={columns}
|
||||||
|
table_url={GET_PROVINCE_OPERATORS_PERFORMANCE_DAILY_REPORT}
|
||||||
|
page_name={"provinceOperatorPerformanceDailyReportPage"}
|
||||||
|
table_name={"provinceOperatorPerformanceDailyReportList"}
|
||||||
|
positionActionsColumn={"last"}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import PageTitle from "@/core/components/PageTitle";
|
||||||
|
import { Stack } from "@mui/material";
|
||||||
|
import DataTableCumulative from "./DataTableCumulative";
|
||||||
|
import DataTableDaily from "./DataTableDaily";
|
||||||
|
|
||||||
|
export default function ProvinceOperatorPerformanceReportPage() {
|
||||||
|
return (
|
||||||
|
<Stack spacing={1}>
|
||||||
|
<PageTitle title={"گزارش تجمعی عملکرد کارشناسان"} />
|
||||||
|
<DataTableCumulative />
|
||||||
|
<PageTitle title={"گزارش روزانه عملکرد کارشناسان"} />
|
||||||
|
<DataTableDaily />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import DataTableWithAuth from "@/core/components/DataTableWithAuth";
|
||||||
|
import { GET_PROVINCE_PEOPLE_MESSAGES_REPORT } from "@/core/utils/routes";
|
||||||
|
import { Box } from "@mui/material";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
|
||||||
|
export default function DataTable() {
|
||||||
|
const columns = useMemo(() => [
|
||||||
|
{
|
||||||
|
header: "تعداد",
|
||||||
|
id: "count",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
Cell: ({ row }) => <>{row.original.nl + row.original.l}</>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "l",
|
||||||
|
header: "تماس های شنیده شده",
|
||||||
|
id: "l",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "nl",
|
||||||
|
header: "تماس های شنیده نشده",
|
||||||
|
id: "nl",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Box sx={{ p: 1, border: 1, borderColor: "divider", borderRadius: 1 }}>
|
||||||
|
<DataTableWithAuth
|
||||||
|
need_filter={true}
|
||||||
|
columns={columns}
|
||||||
|
table_url={GET_PROVINCE_PEOPLE_MESSAGES_REPORT}
|
||||||
|
page_name={"provincePeopleMessagesReportPage"}
|
||||||
|
table_name={"provincePeopleMessagesReportList"}
|
||||||
|
positionActionsColumn={"last"}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import PageTitle from "@/core/components/PageTitle";
|
||||||
|
import { Stack } from "@mui/material";
|
||||||
|
import DataTable from "./DataTable";
|
||||||
|
|
||||||
|
export default function ProvincePeopleMessagesReportPage() {
|
||||||
|
return (
|
||||||
|
<Stack spacing={1}>
|
||||||
|
<PageTitle title={"گزارش وضعیت پیام های مردمی"} />
|
||||||
|
<DataTable />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,205 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import DataTableWithAuth from "@/core/components/DataTableWithAuth";
|
||||||
|
import { GET_COUNTRY_CALLS_REPORT } from "@/core/utils/routes";
|
||||||
|
import { Box } from "@mui/material";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
|
||||||
|
const DataTable = () => {
|
||||||
|
const columns = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
accessorKey: "province_name",
|
||||||
|
header: "استان",
|
||||||
|
id: "province_id",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "total",
|
||||||
|
header: "تمامی تماس ها",
|
||||||
|
id: "total",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "تماس با تهران",
|
||||||
|
id: "call_to_tehran",
|
||||||
|
enableColumnFilter: false,
|
||||||
|
enableSorting: false,
|
||||||
|
grow: false,
|
||||||
|
size: 50,
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
accessorKey: "route_to_teh4",
|
||||||
|
header: "کلید 4",
|
||||||
|
id: "route_to_teh4",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "integer",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "route_to_teh5",
|
||||||
|
header: "کلید 5",
|
||||||
|
id: "route_to_teh5",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "تماس با اپراتور استانی",
|
||||||
|
id: "call_to_ostan",
|
||||||
|
enableColumnFilter: false,
|
||||||
|
enableSorting: false,
|
||||||
|
grow: false,
|
||||||
|
size: 50,
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
accessorKey: "answered",
|
||||||
|
header: "موفق",
|
||||||
|
id: "answered",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "missed",
|
||||||
|
header: "جواب داده نشده",
|
||||||
|
id: "missed",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "fails",
|
||||||
|
header: "نا موفق",
|
||||||
|
id: "fails",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "route_to_os4",
|
||||||
|
header: "کلید 4",
|
||||||
|
id: "route_to_os4",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "route_to_os5",
|
||||||
|
header: "کلید 5",
|
||||||
|
id: "route_to_os5",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "dnd",
|
||||||
|
header: "dnd",
|
||||||
|
id: "dnd",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "transferred",
|
||||||
|
header: "انتقال تماس",
|
||||||
|
id: "transferred",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "answered_over10",
|
||||||
|
header: "تماس بالای 10 ثانیه",
|
||||||
|
id: "answered_over10",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "average_calls_time",
|
||||||
|
header: "میانگین تماس ها",
|
||||||
|
id: "average_calls_time",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Box sx={{ p: 1, border: 1, borderColor: "divider", borderRadius: 1 }}>
|
||||||
|
<DataTableWithAuth
|
||||||
|
need_filter={true}
|
||||||
|
columns={columns}
|
||||||
|
table_url={GET_COUNTRY_CALLS_REPORT}
|
||||||
|
page_name={"countryCallsReportPage"}
|
||||||
|
table_name={"countryCallsReportList"}
|
||||||
|
positionActionsColumn={"last"}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default DataTable;
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import PageTitle from "@/core/components/PageTitle";
|
||||||
|
import { Stack } from "@mui/material";
|
||||||
|
import DataTable from "./DataTable";
|
||||||
|
|
||||||
|
const CountryCallsReportPage = () => {
|
||||||
|
return (
|
||||||
|
<Stack spacing={1}>
|
||||||
|
<PageTitle title={"تجمعی تمام مراکز"} />
|
||||||
|
<DataTable />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CountryCallsReportPage;
|
||||||
@@ -0,0 +1,236 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import DataTableWithAuth from "@/core/components/DataTableWithAuth";
|
||||||
|
import { GET_COUNTRY_KEYPRESS_REPORT } from "@/core/utils/routes";
|
||||||
|
import { Box } from "@mui/material";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
|
||||||
|
const DataTable = () => {
|
||||||
|
const columns = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
accessorKey: "province_name",
|
||||||
|
header: "استان",
|
||||||
|
id: "province_id",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "total",
|
||||||
|
header: "مجموع تماس ها",
|
||||||
|
id: "total",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "تماس با اعداد",
|
||||||
|
id: "call_with_nums",
|
||||||
|
enableColumnFilter: false,
|
||||||
|
enableSorting: false,
|
||||||
|
grow: false,
|
||||||
|
size: 50,
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
accessorKey: "key1",
|
||||||
|
header: "کلید 1",
|
||||||
|
id: "key1",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "integer",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "key2",
|
||||||
|
header: "کلید 2",
|
||||||
|
id: "key2",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "integer",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "key3",
|
||||||
|
header: "کلید 3",
|
||||||
|
id: "key3",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "integer",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "key4",
|
||||||
|
header: "کلید 4",
|
||||||
|
id: "key4",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "integer",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "key5",
|
||||||
|
header: "کلید 5",
|
||||||
|
id: "key5",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "integer",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "key6",
|
||||||
|
header: "کلید 6",
|
||||||
|
id: "key6",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "key9",
|
||||||
|
header: "منوی مدیریت",
|
||||||
|
id: "key9",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "key4_1",
|
||||||
|
header: "تماس با استان",
|
||||||
|
id: "key4_1",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "key4_2",
|
||||||
|
header: "تماس با تهران",
|
||||||
|
id: "key4_2",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "key5_1",
|
||||||
|
header: "تماس راننده با استان",
|
||||||
|
id: "key5_1",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "key5_2",
|
||||||
|
header: "تماس راننده با تهران",
|
||||||
|
id: "key5_2",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "محدودیت",
|
||||||
|
id: "limits",
|
||||||
|
enableColumnFilter: false,
|
||||||
|
enableSorting: false,
|
||||||
|
grow: false,
|
||||||
|
size: 50,
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
accessorKey: "key2_1",
|
||||||
|
header: "انسداد فصلی",
|
||||||
|
id: "key2_1",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "integer",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "key2_2",
|
||||||
|
header: "تردد وسایل نقلیه سنگین و موتورسیکلت",
|
||||||
|
id: "key2_2",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "integer",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "key2_3",
|
||||||
|
header: "سایر",
|
||||||
|
id: "key2_3",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "integer",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Box sx={{ p: 1, border: 1, borderColor: "divider", borderRadius: 1 }}>
|
||||||
|
<DataTableWithAuth
|
||||||
|
need_filter={true}
|
||||||
|
columns={columns}
|
||||||
|
table_url={GET_COUNTRY_KEYPRESS_REPORT}
|
||||||
|
page_name={"countryKeypressReportPage"}
|
||||||
|
table_name={"countryKeypressReportList"}
|
||||||
|
positionActionsColumn={"last"}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default DataTable;
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import PageTitle from "@/core/components/PageTitle";
|
||||||
|
import { Stack } from "@mui/material";
|
||||||
|
import DataTable from "./DataTable";
|
||||||
|
|
||||||
|
const CountryKeyPressReportPage = () => {
|
||||||
|
return (
|
||||||
|
<Stack spacing={1}>
|
||||||
|
<PageTitle title={"گزارش تماس ها"} />
|
||||||
|
<DataTable />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CountryKeyPressReportPage;
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import DataTableWithAuth from "@/core/components/DataTableWithAuth";
|
||||||
|
import { GET_COUNTRY_MESSAGE_RATING_REPORT } from "@/core/utils/routes";
|
||||||
|
import { Box } from "@mui/material";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
|
||||||
|
const DataTable = () => {
|
||||||
|
const columns = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
accessorKey: "province_name",
|
||||||
|
header: "استان",
|
||||||
|
id: "province_id",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "total",
|
||||||
|
header: "پیام ها",
|
||||||
|
id: "total",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "listens",
|
||||||
|
header: "شنیده شده",
|
||||||
|
id: "listens",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "not_listens",
|
||||||
|
header: "شنیده نشده",
|
||||||
|
id: "not_listens",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "review_average",
|
||||||
|
header: "میانگین زمان بررسی",
|
||||||
|
id: "review_average",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "action1",
|
||||||
|
header: "میانگین زمان بررسی (دقیقه)",
|
||||||
|
id: "action1",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "action2",
|
||||||
|
header: "عدم نیاز به ثبت (مشکل راه)",
|
||||||
|
id: "action2",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "action3",
|
||||||
|
header: "عدم نیاز به ثبت (مشکل حمل و نقلی)",
|
||||||
|
id: "action3",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "action4",
|
||||||
|
header: "عدم نیاز به ثبت (مشکل عوارضی)",
|
||||||
|
id: "action4",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "action5",
|
||||||
|
header: "ثبت سوانح (مشکل راه)",
|
||||||
|
id: "action5",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Box sx={{ p: 1, border: 1, borderColor: "divider", borderRadius: 1 }}>
|
||||||
|
<DataTableWithAuth
|
||||||
|
need_filter={true}
|
||||||
|
columns={columns}
|
||||||
|
table_url={GET_COUNTRY_MESSAGE_RATING_REPORT}
|
||||||
|
page_name={"countryMessageRatingReportPage"}
|
||||||
|
table_name={"countryMessageRatingReportList"}
|
||||||
|
positionActionsColumn={"last"}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default DataTable;
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import PageTitle from "@/core/components/PageTitle";
|
||||||
|
import { Stack } from "@mui/material";
|
||||||
|
import DataTable from "./DataTable";
|
||||||
|
|
||||||
|
const CountryMessageRatingReportPage = () => {
|
||||||
|
return (
|
||||||
|
<Stack spacing={1}>
|
||||||
|
<PageTitle title={"تجمعی پیام های مردمی مراکز"} />
|
||||||
|
<DataTable />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CountryMessageRatingReportPage;
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import DataTableWithAuth from "@/core/components/DataTableWithAuth";
|
||||||
|
import { GET_COUNTRY_VOICE_RATING_REPORT } from "@/core/utils/routes";
|
||||||
|
import { Box } from "@mui/material";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
|
||||||
|
const DataTable = () => {
|
||||||
|
const columns = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
accessorKey: "province_name",
|
||||||
|
header: "استان",
|
||||||
|
id: "province_id",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "کیفیت",
|
||||||
|
id: "quality",
|
||||||
|
enableColumnFilter: false,
|
||||||
|
enableSorting: false,
|
||||||
|
grow: false,
|
||||||
|
size: 50,
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
accessorKey: "message_quality_ok",
|
||||||
|
header: "مناسب",
|
||||||
|
id: "message_quality_ok",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "integer",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "message_quality_nok",
|
||||||
|
header: "نامناسب",
|
||||||
|
id: "message_quality_nok",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: "فرمت",
|
||||||
|
id: "format",
|
||||||
|
enableColumnFilter: false,
|
||||||
|
enableSorting: false,
|
||||||
|
grow: false,
|
||||||
|
size: 50,
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
accessorKey: "message_format_ok",
|
||||||
|
header: "مناسب",
|
||||||
|
id: "message_format_ok",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "integer",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "message_format_nok",
|
||||||
|
header: "نامناسب",
|
||||||
|
id: "message_format_nok",
|
||||||
|
enableColumnFilter: true,
|
||||||
|
datatype: "text",
|
||||||
|
filterMode: "equals",
|
||||||
|
sortDescFirst: false,
|
||||||
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
|
grow: false,
|
||||||
|
size: 100,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Box sx={{ p: 1, border: 1, borderColor: "divider", borderRadius: 1 }}>
|
||||||
|
<DataTableWithAuth
|
||||||
|
need_filter={true}
|
||||||
|
columns={columns}
|
||||||
|
table_url={GET_COUNTRY_VOICE_RATING_REPORT}
|
||||||
|
page_name={"countryVoiceRatingReportPage"}
|
||||||
|
table_name={"countryVoiceRatingReportList"}
|
||||||
|
positionActionsColumn={"last"}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default DataTable;
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import PageTitle from "@/core/components/PageTitle";
|
||||||
|
import { Stack } from "@mui/material";
|
||||||
|
import DataTable from "./DataTable";
|
||||||
|
|
||||||
|
const CountryVoiceRatingReportPage = () => {
|
||||||
|
return (
|
||||||
|
<Stack spacing={1}>
|
||||||
|
<PageTitle title={"گزارش کیفی استان ها"} />
|
||||||
|
<DataTable />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CountryVoiceRatingReportPage;
|
||||||
@@ -1,13 +1,5 @@
|
|||||||
import { ExpandLess, ExpandMore, Link } from "@mui/icons-material";
|
import { ExpandLess, ExpandMore, Link } from "@mui/icons-material";
|
||||||
import {
|
import { Button, Divider, ListItemIcon, ListItemText, Menu, MenuItem, Stack } from "@mui/material";
|
||||||
Button,
|
|
||||||
Divider,
|
|
||||||
ListItemIcon,
|
|
||||||
ListItemText,
|
|
||||||
Menu,
|
|
||||||
MenuItem,
|
|
||||||
Stack,
|
|
||||||
} from "@mui/material";
|
|
||||||
import NextLink from "next/link";
|
import NextLink from "next/link";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
@@ -36,21 +28,14 @@ const HeaderMenu = ({ menu }) => {
|
|||||||
<Menu anchorEl={anchorEl} open={open} onClose={handleClose}>
|
<Menu anchorEl={anchorEl} open={open} onClose={handleClose}>
|
||||||
{menu.subMenu.flatMap((subMenu, index) => [
|
{menu.subMenu.flatMap((subMenu, index) => [
|
||||||
...subMenu.map((sub) => (
|
...subMenu.map((sub) => (
|
||||||
<MenuItem
|
<MenuItem component={NextLink} href={sub.href} key={sub.title} onClick={handleClose}>
|
||||||
component={NextLink}
|
|
||||||
href={sub.href}
|
|
||||||
key={sub.title}
|
|
||||||
onClick={handleClose}
|
|
||||||
>
|
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<Link fontSize="small" />
|
<Link fontSize="small" />
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
<ListItemText>{sub.title}</ListItemText>
|
<ListItemText>{sub.title}</ListItemText>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)),
|
)),
|
||||||
index < menu.subMenu.length - 1 && (
|
index < menu.subMenu.length - 1 && <Divider key={`divider-${index}`} />,
|
||||||
<Divider key={`divider-${index}`} />
|
|
||||||
),
|
|
||||||
])}
|
])}
|
||||||
</Menu>
|
</Menu>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -1,13 +1,5 @@
|
|||||||
import { ExpandLess, ExpandMore } from "@mui/icons-material";
|
import { ExpandLess, ExpandMore } from "@mui/icons-material";
|
||||||
import {
|
import { Collapse, List, ListItem, ListItemButton, ListItemIcon, ListItemText, Stack } from "@mui/material";
|
||||||
Collapse,
|
|
||||||
List,
|
|
||||||
ListItem,
|
|
||||||
ListItemButton,
|
|
||||||
ListItemIcon,
|
|
||||||
ListItemText,
|
|
||||||
Stack,
|
|
||||||
} from "@mui/material";
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import SidebarBadge from "./SidebarBadge";
|
import SidebarBadge from "./SidebarBadge";
|
||||||
import SidebarSubitems from "./SidebarSubitems";
|
import SidebarSubitems from "./SidebarSubitems";
|
||||||
@@ -51,21 +43,10 @@ const SidebarListItems = ({ menuItem, dispatch }) => {
|
|||||||
))}
|
))}
|
||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
{menuItem.hasSubitems ? (
|
{menuItem.hasSubitems ? menuItem.showSubitems ? <ExpandLess /> : <ExpandMore /> : null}
|
||||||
menuItem.showSubitems ? (
|
|
||||||
<ExpandLess />
|
|
||||||
) : (
|
|
||||||
<ExpandMore />
|
|
||||||
)
|
|
||||||
) : null}
|
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<Collapse
|
<Collapse in={menuItem.showSubitems} timeout="auto" mountOnEnter={true} unmountOnExit={true}>
|
||||||
in={menuItem.showSubitems}
|
|
||||||
timeout="auto"
|
|
||||||
mountOnEnter={true}
|
|
||||||
unmountOnExit={true}
|
|
||||||
>
|
|
||||||
{menuItem.hasSubitems ? (
|
{menuItem.hasSubitems ? (
|
||||||
<List
|
<List
|
||||||
disablePadding
|
disablePadding
|
||||||
@@ -75,13 +56,7 @@ const SidebarListItems = ({ menuItem, dispatch }) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{menuItem.Subitems.map((subitem, index) => {
|
{menuItem.Subitems.map((subitem, index) => {
|
||||||
return (
|
return <SidebarSubitems key={index} dispatch={dispatch} subitem={subitem} />;
|
||||||
<SidebarSubitems
|
|
||||||
key={index}
|
|
||||||
dispatch={dispatch}
|
|
||||||
subitem={subitem}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
})}
|
||||||
</List>
|
</List>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
import Profile from "@/core/components/Profile";
|
||||||
|
import { filterMenuItems } from "@/core/utils/filterMenuItems";
|
||||||
|
import { pageMenu } from "@/core/utils/pageMenu";
|
||||||
import { usePermissions } from "@/lib/hooks/usePermissions";
|
import { usePermissions } from "@/lib/hooks/usePermissions";
|
||||||
import { List } from "@mui/material";
|
import { List } from "@mui/material";
|
||||||
import { usePathname } from "next/navigation";
|
import { usePathname } from "next/navigation";
|
||||||
import { useEffect, useReducer, useState } from "react";
|
import { useEffect, useReducer, useState } from "react";
|
||||||
import { filterMenuItems } from "@/core/utils/filterMenuItems";
|
|
||||||
import { pageMenu } from "@/core/utils/pageMenu";
|
|
||||||
import SidebarListItems from "./SidebarListItems";
|
import SidebarListItems from "./SidebarListItems";
|
||||||
import Profile from "@/core/components/Profile";
|
|
||||||
|
|
||||||
function selectPage(item, route) {
|
function selectPage(item, route) {
|
||||||
if (item.type === "page") {
|
if (item.type === "page") {
|
||||||
@@ -16,15 +16,11 @@ function selectPage(item, route) {
|
|||||||
showSubitems: item.route === route,
|
showSubitems: item.route === route,
|
||||||
};
|
};
|
||||||
} else if (item.Subitems && Array.isArray(item.Subitems)) {
|
} else if (item.Subitems && Array.isArray(item.Subitems)) {
|
||||||
const updatedSubitems = item.Subitems.map((subitem) =>
|
const updatedSubitems = item.Subitems.map((subitem) => selectPage(subitem, route));
|
||||||
selectPage(subitem, route),
|
|
||||||
);
|
|
||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
Subitems: updatedSubitems,
|
Subitems: updatedSubitems,
|
||||||
showSubitems: updatedSubitems.some(
|
showSubitems: updatedSubitems.some((subitem) => subitem.showSubitems || subitem.route === route),
|
||||||
(subitem) => subitem.showSubitems || subitem.route === route,
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return item;
|
return item;
|
||||||
@@ -32,9 +28,7 @@ function selectPage(item, route) {
|
|||||||
|
|
||||||
function toggleSubitems(items, actionId) {
|
function toggleSubitems(items, actionId) {
|
||||||
return items.map((item) =>
|
return items.map((item) =>
|
||||||
actionId === item.id
|
actionId === item.id ? { ...item, showSubitems: !item.showSubitems } : { ...item, showSubitems: false }
|
||||||
? { ...item, showSubitems: !item.showSubitems }
|
|
||||||
: { ...item, showSubitems: false },
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,9 +46,7 @@ function reducer(state, action) {
|
|||||||
|
|
||||||
case "COLLAPSE_SUB_ITEMS":
|
case "COLLAPSE_SUB_ITEMS":
|
||||||
return state.map((item) =>
|
return state.map((item) =>
|
||||||
item.hasSubitems
|
item.hasSubitems ? { ...item, Subitems: toggleSubitems(item.Subitems, action.id) } : item
|
||||||
? { ...item, Subitems: toggleSubitems(item.Subitems, action.id) }
|
|
||||||
: item,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
case "COLLAPSE_SUB_SECOND_ITEMS":
|
case "COLLAPSE_SUB_SECOND_ITEMS":
|
||||||
@@ -68,10 +60,10 @@ function reducer(state, action) {
|
|||||||
...subitem,
|
...subitem,
|
||||||
Subitems: toggleSubitems(subitem.Subitems, action.id),
|
Subitems: toggleSubitems(subitem.Subitems, action.id),
|
||||||
}
|
}
|
||||||
: subitem,
|
: subitem
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
: item,
|
: item
|
||||||
);
|
);
|
||||||
|
|
||||||
case "SELECTED":
|
case "SELECTED":
|
||||||
@@ -112,13 +104,7 @@ const SidebarMenu = () => {
|
|||||||
{userPermissions && (
|
{userPermissions && (
|
||||||
<List sx={{ overflow: "auto", flex: 1 }} disablePadding dense>
|
<List sx={{ overflow: "auto", flex: 1 }} disablePadding dense>
|
||||||
{menuItems.map((menuItem) => {
|
{menuItems.map((menuItem) => {
|
||||||
return (
|
return <SidebarListItems dispatch={dispatch} key={menuItem.id} menuItem={menuItem} />;
|
||||||
<SidebarListItems
|
|
||||||
dispatch={dispatch}
|
|
||||||
key={menuItem.id}
|
|
||||||
menuItem={menuItem}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
})}
|
||||||
</List>
|
</List>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,14 +1,5 @@
|
|||||||
import { ExpandLess, ExpandMore } from "@mui/icons-material";
|
import { ExpandLess, ExpandMore } from "@mui/icons-material";
|
||||||
import {
|
import { Box, Collapse, List, ListItem, ListItemButton, ListItemIcon, ListItemText, Stack } from "@mui/material";
|
||||||
Box,
|
|
||||||
Collapse,
|
|
||||||
List,
|
|
||||||
ListItem,
|
|
||||||
ListItemButton,
|
|
||||||
ListItemIcon,
|
|
||||||
ListItemText,
|
|
||||||
Stack,
|
|
||||||
} from "@mui/material";
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import SidebarBadge from "./SidebarBadge";
|
import SidebarBadge from "./SidebarBadge";
|
||||||
|
|
||||||
@@ -64,12 +55,7 @@ const SidebarSubitems = ({ subitem, dispatch }) => {
|
|||||||
)}
|
)}
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<Collapse
|
<Collapse in={subitem.showSubitems} timeout="auto" mountOnEnter={true} unmountOnExit={true}>
|
||||||
in={subitem.showSubitems}
|
|
||||||
timeout="auto"
|
|
||||||
mountOnEnter={true}
|
|
||||||
unmountOnExit={true}
|
|
||||||
>
|
|
||||||
{subitem.hasSubitems ? (
|
{subitem.hasSubitems ? (
|
||||||
<List
|
<List
|
||||||
disablePadding
|
disablePadding
|
||||||
@@ -79,13 +65,7 @@ const SidebarSubitems = ({ subitem, dispatch }) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{subitem.Subitems.map((subSubitem, index) => {
|
{subitem.Subitems.map((subSubitem, index) => {
|
||||||
return (
|
return <SidebarSubitems key={index} dispatch={dispatch} subitem={subSubitem} />;
|
||||||
<SidebarSubitems
|
|
||||||
key={index}
|
|
||||||
dispatch={dispatch}
|
|
||||||
subitem={subSubitem}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
})}
|
||||||
</List>
|
</List>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -1,28 +1,20 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import ChevronRightIcon from "@mui/icons-material/ChevronRight";
|
import ChevronRightIcon from "@mui/icons-material/ChevronRight";
|
||||||
import MenuIcon from "@mui/icons-material/Menu";
|
import MenuIcon from "@mui/icons-material/Menu";
|
||||||
import {
|
import { Box, Drawer, IconButton, Stack, styled, Toolbar, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||||
Box,
|
|
||||||
Drawer,
|
|
||||||
IconButton,
|
|
||||||
Stack,
|
|
||||||
styled,
|
|
||||||
Toolbar,
|
|
||||||
Typography,
|
|
||||||
useMediaQuery,
|
|
||||||
useTheme,
|
|
||||||
} from "@mui/material";
|
|
||||||
import MuiAppBar from "@mui/material/AppBar";
|
import MuiAppBar from "@mui/material/AppBar";
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import HeaderMenu from "./HeaderMenu";
|
import HeaderMenu from "./HeaderMenu";
|
||||||
import moment from "jalali-moment";
|
import moment from "jalali-moment";
|
||||||
import { headerMenu } from "@/core/utils/headerMenu";
|
import { headerMenu } from "@/core/utils/headerMenu";
|
||||||
import SidebarMenu from "./Sidebar/SidebarMenu";
|
import SidebarMenu from "./Sidebar/SidebarMenu";
|
||||||
|
import Teleporting from "@/core/components/Teleporting";
|
||||||
|
import { usePermissions } from "@/lib/hooks/usePermissions";
|
||||||
|
import WithWidgetMiddleware from "@/core/middlewares/withWidget";
|
||||||
|
|
||||||
const drawerWidth = 300;
|
const drawerWidth = 270;
|
||||||
|
|
||||||
const Main = styled(Stack, { shouldForwardProp: (prop) => prop !== "open" })(
|
const Main = styled(Stack, { shouldForwardProp: (prop) => prop !== "open" })(({ theme, open }) => ({
|
||||||
({ theme, open }) => ({
|
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
height: "100%",
|
height: "100%",
|
||||||
transition: theme.transitions.create("margin", {
|
transition: theme.transitions.create("margin", {
|
||||||
@@ -37,8 +29,7 @@ const Main = styled(Stack, { shouldForwardProp: (prop) => prop !== "open" })(
|
|||||||
}),
|
}),
|
||||||
marginLeft: 0,
|
marginLeft: 0,
|
||||||
}),
|
}),
|
||||||
}),
|
}));
|
||||||
);
|
|
||||||
|
|
||||||
const AppBar = styled(MuiAppBar, {
|
const AppBar = styled(MuiAppBar, {
|
||||||
shouldForwardProp: (prop) => prop !== "open",
|
shouldForwardProp: (prop) => prop !== "open",
|
||||||
@@ -62,11 +53,13 @@ const DrawerHeader = styled("div")(({ theme }) => ({
|
|||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
padding: theme.spacing(0, 1),
|
padding: theme.spacing(0, 1),
|
||||||
...theme.mixins.toolbar,
|
...theme.mixins.toolbar,
|
||||||
minHeight: "40px !important",
|
minHeight: "43px !important",
|
||||||
justifyContent: "flex-end",
|
justifyContent: "flex-end",
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const HeaderWithSidebar = ({ children }) => {
|
const HeaderWithSidebar = ({ children }) => {
|
||||||
|
const { data: permissions } = usePermissions();
|
||||||
|
const isTeleportToProvince = permissions?.some((permission) => permission === "teleport_to_province");
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down("md")); // Detect mobile/tablet screen
|
const isMobile = useMediaQuery(theme.breakpoints.down("md")); // Detect mobile/tablet screen
|
||||||
const [open, setOpen] = useState(!isMobile); // Initial state based on screen size
|
const [open, setOpen] = useState(!isMobile); // Initial state based on screen size
|
||||||
@@ -92,11 +85,7 @@ const HeaderWithSidebar = ({ children }) => {
|
|||||||
width: "100%",
|
width: "100%",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<AppBar
|
<AppBar sx={{ position: "fixed", top: "unset" }} elevation={0} open={open}>
|
||||||
sx={{ position: "fixed", top: "unset" }}
|
|
||||||
elevation={0}
|
|
||||||
open={open}
|
|
||||||
>
|
|
||||||
<Toolbar variant="dense" sx={{ minHeight: 40 }}>
|
<Toolbar variant="dense" sx={{ minHeight: 40 }}>
|
||||||
<IconButton
|
<IconButton
|
||||||
color="inherit"
|
color="inherit"
|
||||||
@@ -119,10 +108,17 @@ const HeaderWithSidebar = ({ children }) => {
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<Stack sx={{ flex: 1 }} direction={'row'} justifyContent={'space-between'}>
|
||||||
|
<Box sx={{ flex: 1 }}>
|
||||||
{headerMenu.map((menu) => (
|
{headerMenu.map((menu) => (
|
||||||
<HeaderMenu key={menu.title} menu={menu} />
|
<HeaderMenu key={menu.title} menu={menu} />
|
||||||
))}
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
|
<WithWidgetMiddleware enable={isTeleportToProvince}>
|
||||||
|
<Teleporting />
|
||||||
|
</WithWidgetMiddleware>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
</AppBar>
|
</AppBar>
|
||||||
<Drawer
|
<Drawer
|
||||||
@@ -158,18 +154,11 @@ const HeaderWithSidebar = ({ children }) => {
|
|||||||
</IconButton>
|
</IconButton>
|
||||||
</DrawerHeader>
|
</DrawerHeader>
|
||||||
<SidebarMenu />
|
<SidebarMenu />
|
||||||
<Typography
|
<Typography textAlign={"center"} variant="caption" sx={{ py: 0.2, fontFamily: "sans-serif" }}>
|
||||||
textAlign={"center"}
|
|
||||||
variant="caption"
|
|
||||||
sx={{ py: 0.2, fontFamily: "sans-serif" }}
|
|
||||||
>
|
|
||||||
v{process.env.NEXT_PUBLIC_VERSION}
|
v{process.env.NEXT_PUBLIC_VERSION}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
<Main
|
<Main open={open} sx={{ height: "100%", width: "100%", overflow: "hidden" }}>
|
||||||
open={open}
|
|
||||||
sx={{ height: "100%", width: "100%", overflow: "hidden" }}
|
|
||||||
>
|
|
||||||
<DrawerHeader />
|
<DrawerHeader />
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
|
|||||||
@@ -15,11 +15,7 @@ const ActionHeader = ({ tab }) => {
|
|||||||
<Typography variant="subtitle1" sx={{ marginRight: 1, color: "#fff" }}>
|
<Typography variant="subtitle1" sx={{ marginRight: 1, color: "#fff" }}>
|
||||||
.... عملیات های مربوط به تماس:
|
.... عملیات های مربوط به تماس:
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography
|
<Typography data-testid="phone_number" variant="subtitle1" sx={{ color: "#fff" }}>
|
||||||
data-testid="phone_number"
|
|
||||||
variant="subtitle1"
|
|
||||||
sx={{ color: "#fff" }}
|
|
||||||
>
|
|
||||||
{tab.phone_number} ....
|
{tab.phone_number} ....
|
||||||
</Typography>
|
</Typography>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -9,9 +9,7 @@ const CallActionCategoriesButton = ({ category, control }) => {
|
|||||||
<Button
|
<Button
|
||||||
sx={{ py: 1.5 }}
|
sx={{ py: 1.5 }}
|
||||||
fullWidth
|
fullWidth
|
||||||
variant={
|
variant={field.value === category.category_id ? "contained" : "outlined"}
|
||||||
field.value === category.category_id ? "contained" : "outlined"
|
|
||||||
}
|
|
||||||
color={"primary"}
|
color={"primary"}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
field.onChange(category.category_id);
|
field.onChange(category.category_id);
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const CallActionSubcategoriesButton = ({ sub_category, control, onSubmit }) => {
|
|||||||
onSubmit();
|
onSubmit();
|
||||||
}, 0);
|
}, 0);
|
||||||
},
|
},
|
||||||
[sub_category.subcategory_id],
|
[sub_category.subcategory_id]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -11,11 +11,7 @@ const CallActionsCategories = ({ control }) => {
|
|||||||
</Divider>
|
</Divider>
|
||||||
<Stack sx={{ m: 1, p: 1 }} direction={"row"} spacing={3}>
|
<Stack sx={{ m: 1, p: 1 }} direction={"row"} spacing={3}>
|
||||||
{categoryLists.map((category) => (
|
{categoryLists.map((category) => (
|
||||||
<CallActionCategoriesButton
|
<CallActionCategoriesButton key={category.category_id} category={category} control={control} />
|
||||||
key={category.category_id}
|
|
||||||
category={category}
|
|
||||||
control={control}
|
|
||||||
/>
|
|
||||||
))}
|
))}
|
||||||
</Stack>
|
</Stack>
|
||||||
</>
|
</>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user