add prettier for formatting to project
This commit is contained in:
47
.idea/codeStyles/Project.xml
generated
Normal file
47
.idea/codeStyles/Project.xml
generated
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
|
<code_scheme name="Project" version="173">
|
||||||
|
<option name="LINE_SEPARATOR" value=" " />
|
||||||
|
<HTMLCodeStyleSettings>
|
||||||
|
<option name="HTML_SPACE_INSIDE_EMPTY_TAG" value="true" />
|
||||||
|
</HTMLCodeStyleSettings>
|
||||||
|
<JSCodeStyleSettings version="0">
|
||||||
|
<option name="FORCE_SEMICOLON_STYLE" value="true" />
|
||||||
|
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
|
||||||
|
<option name="FORCE_QUOTE_STYlE" value="true" />
|
||||||
|
<option name="ENFORCE_TRAILING_COMMA" value="WhenMultiline" />
|
||||||
|
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
|
||||||
|
<option name="SPACES_WITHIN_IMPORTS" value="true" />
|
||||||
|
</JSCodeStyleSettings>
|
||||||
|
<TypeScriptCodeStyleSettings version="0">
|
||||||
|
<option name="FORCE_SEMICOLON_STYLE" value="true" />
|
||||||
|
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
|
||||||
|
<option name="FORCE_QUOTE_STYlE" value="true" />
|
||||||
|
<option name="ENFORCE_TRAILING_COMMA" value="WhenMultiline" />
|
||||||
|
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
|
||||||
|
<option name="SPACES_WITHIN_IMPORTS" value="true" />
|
||||||
|
</TypeScriptCodeStyleSettings>
|
||||||
|
<VueCodeStyleSettings>
|
||||||
|
<option name="INTERPOLATION_NEW_LINE_AFTER_START_DELIMITER" value="false" />
|
||||||
|
<option name="INTERPOLATION_NEW_LINE_BEFORE_END_DELIMITER" value="false" />
|
||||||
|
</VueCodeStyleSettings>
|
||||||
|
<codeStyleSettings language="HTML">
|
||||||
|
<option name="SOFT_MARGINS" value="120" />
|
||||||
|
<indentOptions>
|
||||||
|
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="JavaScript">
|
||||||
|
<option name="SOFT_MARGINS" value="120" />
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="TypeScript">
|
||||||
|
<option name="SOFT_MARGINS" value="120" />
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="Vue">
|
||||||
|
<option name="SOFT_MARGINS" value="120" />
|
||||||
|
<indentOptions>
|
||||||
|
<option name="INDENT_SIZE" value="4" />
|
||||||
|
<option name="TAB_SIZE" value="4" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
</code_scheme>
|
||||||
|
</component>
|
||||||
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
|
<state>
|
||||||
|
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||||
|
</state>
|
||||||
|
</component>
|
||||||
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,17 +1,17 @@
|
|||||||
version: '3.8'
|
version: "3.8"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
nextjs:
|
nextjs:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
container_name: nextjs-app
|
container_name: nextjs-app
|
||||||
environment:
|
environment:
|
||||||
- NEXT_PUBLIC_VERSION=${NEXT_PUBLIC_VERSION}
|
- NEXT_PUBLIC_VERSION=${NEXT_PUBLIC_VERSION}
|
||||||
- NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL}
|
- NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL}
|
||||||
- NEXT_PUBLIC_SERVER_SOCKET_URL=${NEXT_PUBLIC_SERVER_SOCKET_URL}
|
- NEXT_PUBLIC_SERVER_SOCKET_URL=${NEXT_PUBLIC_SERVER_SOCKET_URL}
|
||||||
ports:
|
ports:
|
||||||
- "3000:3000"
|
- "3000:3000"
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ const __filename = fileURLToPath(import.meta.url);
|
|||||||
const __dirname = dirname(__filename);
|
const __dirname = dirname(__filename);
|
||||||
|
|
||||||
const compat = new FlatCompat({
|
const compat = new FlatCompat({
|
||||||
baseDirectory: __dirname,
|
baseDirectory: __dirname,
|
||||||
});
|
});
|
||||||
|
|
||||||
const eslintConfig = [...compat.extends("next/core-web-vitals")];
|
const eslintConfig = [...compat.extends("next/core-web-vitals")];
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./src/*"]
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
90
package.json
90
package.json
@@ -1,46 +1,46 @@
|
|||||||
{
|
{
|
||||||
"name": "app",
|
"name": "app",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev --turbopack",
|
"dev": "next dev --turbopack",
|
||||||
"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",
|
||||||
"@emotion/react": "^11.14.0",
|
"@emotion/react": "^11.14.0",
|
||||||
"@emotion/styled": "^11.14.0",
|
"@emotion/styled": "^11.14.0",
|
||||||
"@hookform/resolvers": "^5.0.1",
|
"@hookform/resolvers": "^5.0.1",
|
||||||
"@mui/icons-material": "^7.0.2",
|
"@mui/icons-material": "^7.0.2",
|
||||||
"@mui/material": "^7.0.2",
|
"@mui/material": "^7.0.2",
|
||||||
"@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",
|
||||||
"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",
|
||||||
"lz-string": "^1.5.0",
|
"lz-string": "^1.5.0",
|
||||||
"material-react-table": "^3.2.1",
|
"material-react-table": "^3.2.1",
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
"next": "15.3.1",
|
"next": "15.3.1",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"react-hook-form": "^7.56.1",
|
"react-hook-form": "^7.56.1",
|
||||||
"react-toastify": "^11.0.5",
|
"react-toastify": "^11.0.5",
|
||||||
"socket.io-client": "^4.8.1",
|
"socket.io-client": "^4.8.1",
|
||||||
"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",
|
||||||
"yup": "^1.6.1"
|
"yup": "^1.6.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "^3",
|
"@eslint/eslintrc": "^3",
|
||||||
"eslint": "^9",
|
"eslint": "^9",
|
||||||
"eslint-config-next": "15.3.1",
|
"eslint-config-next": "15.3.1",
|
||||||
"prettier": "3.5.3",
|
"prettier": "3.5.3",
|
||||||
"sass": "^1.87.0"
|
"sass": "^1.87.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
12232
pnpm-lock.yaml
generated
12232
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -2,6 +2,6 @@
|
|||||||
import PageLoading from "@/core/components/PageLoading";
|
import PageLoading from "@/core/components/PageLoading";
|
||||||
|
|
||||||
const Loading = () => {
|
const Loading = () => {
|
||||||
return <PageLoading />;
|
return <PageLoading />;
|
||||||
};
|
};
|
||||||
export default Loading;
|
export default Loading;
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import DashboardPage from "@/components/dashboard/dashboard";
|
import DashboardPage from "@/components/dashboard/dashboard";
|
||||||
|
|
||||||
export const metadata = {
|
export const metadata = {
|
||||||
title: "پیشخوان",
|
title: "پیشخوان",
|
||||||
};
|
};
|
||||||
|
|
||||||
const Page = () => {
|
const Page = () => {
|
||||||
return <DashboardPage />;
|
return <DashboardPage />;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Page;
|
export default Page;
|
||||||
|
|||||||
@@ -2,15 +2,15 @@ import UsersPage from "@/components/dashboard/Users";
|
|||||||
import WithPermission from "@/core/middlewares/withPermission";
|
import WithPermission from "@/core/middlewares/withPermission";
|
||||||
|
|
||||||
export const metadata = {
|
export const metadata = {
|
||||||
title: "کاربران",
|
title: "کاربران",
|
||||||
};
|
};
|
||||||
|
|
||||||
const Page = () => {
|
const Page = () => {
|
||||||
return (
|
return (
|
||||||
<WithPermission permission_name={["manage_users"]}>
|
<WithPermission permission_name={["manage_users"]}>
|
||||||
<UsersPage />
|
<UsersPage />
|
||||||
</WithPermission>
|
</WithPermission>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Page;
|
export default Page;
|
||||||
|
|||||||
@@ -8,22 +8,20 @@ import { Stack } from "@mui/material";
|
|||||||
import { SWRConfig } from "swr";
|
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>
|
||||||
<SWRConfig value={{ provider: () => new Map() }}>
|
<SWRConfig value={{ provider: () => new Map() }}>
|
||||||
<Stack sx={{ width: "100%", height: "100%" }}>
|
<Stack sx={{ width: "100%", height: "100%" }}>
|
||||||
<HeaderWithSidebar>{children}</HeaderWithSidebar>
|
<HeaderWithSidebar>{children}</HeaderWithSidebar>
|
||||||
</Stack>
|
</Stack>
|
||||||
</SWRConfig>
|
</SWRConfig>
|
||||||
<WithWidgetMiddleware enable={isCallWidgetEnabled}>
|
<WithWidgetMiddleware enable={isCallWidgetEnabled}>
|
||||||
<CallWidget />
|
<CallWidget />
|
||||||
</WithWidgetMiddleware>
|
</WithWidgetMiddleware>
|
||||||
</SocketProvider>
|
</SocketProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default Layout;
|
export default Layout;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import WithAuthMiddleware from "@/core/middlewares/withAuth";
|
import WithAuthMiddleware from "@/core/middlewares/withAuth";
|
||||||
|
|
||||||
const Layout = ({ children }) => {
|
const Layout = ({ children }) => {
|
||||||
return <WithAuthMiddleware>{children}</WithAuthMiddleware>;
|
return <WithAuthMiddleware>{children}</WithAuthMiddleware>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Layout;
|
export default Layout;
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ import WithoutAuthMiddleware from "@/core/middlewares/withoutAuth";
|
|||||||
import { Suspense } from "react";
|
import { Suspense } from "react";
|
||||||
|
|
||||||
const Layout = ({ children }) => {
|
const Layout = ({ children }) => {
|
||||||
return (
|
return (
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<WithoutAuthMiddleware>{children}</WithoutAuthMiddleware>
|
<WithoutAuthMiddleware>{children}</WithoutAuthMiddleware>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Layout;
|
export default Layout;
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import LoginPage from "@/components/Login";
|
import LoginPage from "@/components/Login";
|
||||||
|
|
||||||
export const metadata = {
|
export const metadata = {
|
||||||
title: "ورود",
|
title: "ورود",
|
||||||
};
|
};
|
||||||
|
|
||||||
const Page = () => {
|
const Page = () => {
|
||||||
return <LoginPage />;
|
return <LoginPage />;
|
||||||
};
|
};
|
||||||
export default Page;
|
export default Page;
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
export default function Default() {
|
export default function Default() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,28 +5,25 @@ import { TableSettingProvider } from "@/lib/contexts/tableSetting";
|
|||||||
import { AppRouterCacheProvider } from "@mui/material-nextjs/v15-appRouter";
|
import { AppRouterCacheProvider } from "@mui/material-nextjs/v15-appRouter";
|
||||||
|
|
||||||
export const metadata = {
|
export const metadata = {
|
||||||
title: {
|
title: {
|
||||||
template: "%s | سامانه CRM",
|
template: "%s | سامانه CRM",
|
||||||
default: "سامانه CRM",
|
default: "سامانه CRM",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function RootLayout({ children }) {
|
export default function RootLayout({ children }) {
|
||||||
return (
|
return (
|
||||||
<html lang="fa" dir={"rtl"}>
|
<html lang="fa" dir={"rtl"}>
|
||||||
<head>
|
<head>
|
||||||
<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}
|
<AuthProvider>
|
||||||
options={{ enableCssLayer: true }}
|
<TableSettingProvider>{children}</TableSettingProvider>
|
||||||
>
|
</AuthProvider>
|
||||||
<AuthProvider>
|
</AppRouterCacheProvider>
|
||||||
<TableSettingProvider>{children}</TableSettingProvider>
|
</body>
|
||||||
</AuthProvider>
|
</html>
|
||||||
</AppRouterCacheProvider>
|
);
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,19 +3,14 @@ import { Box, Stack, Typography } from "@mui/material";
|
|||||||
import SvgNotFound from "@/core/components/svgs/SvgNotFound";
|
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%" }}
|
<Box>
|
||||||
justifyContent={"center"}
|
<SvgNotFound width={200} height={200} />
|
||||||
alignItems={"center"}
|
</Box>
|
||||||
spacing={2}
|
<Typography variant={"body1"} sx={{ color: "primary.main" }}>
|
||||||
>
|
صفحه موردنظر یافت نشد ...
|
||||||
<Box>
|
</Typography>
|
||||||
<SvgNotFound width={200} height={200} />
|
</Stack>
|
||||||
</Box>
|
);
|
||||||
<Typography variant={"body1"} sx={{ color: "primary.main" }}>
|
|
||||||
صفحه موردنظر یافت نشد ...
|
|
||||||
</Typography>
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
redirect("/dashboard", "replace");
|
redirect("/dashboard", "replace");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Page;
|
export default Page;
|
||||||
|
|||||||
@@ -7,34 +7,34 @@ import { CssBaseline, GlobalStyles, ThemeProvider } from "@mui/material";
|
|||||||
import { ToastContainer } from "react-toastify";
|
import { ToastContainer } from "react-toastify";
|
||||||
|
|
||||||
const Template = ({ children }) => {
|
const Template = ({ children }) => {
|
||||||
return (
|
return (
|
||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={theme}>
|
||||||
<GlobalStyles
|
<GlobalStyles
|
||||||
styles={{
|
styles={{
|
||||||
"*": {
|
"*": {
|
||||||
scrollbarWidth: "thin",
|
scrollbarWidth: "thin",
|
||||||
scrollbarColor: `${theme.palette.primary.dark} transparent`,
|
scrollbarColor: `${theme.palette.primary.dark} transparent`,
|
||||||
},
|
},
|
||||||
"*&::-webkit-scrollbar": {
|
"*&::-webkit-scrollbar": {
|
||||||
width: "4px",
|
width: "4px",
|
||||||
},
|
},
|
||||||
"*&::-webkit-scrollbar-track": {
|
"*&::-webkit-scrollbar-track": {
|
||||||
boxShadow: "inset 0 0 5px #fff",
|
boxShadow: "inset 0 0 5px #fff",
|
||||||
borderRadius: "4px",
|
borderRadius: "4px",
|
||||||
},
|
},
|
||||||
"*&::-webkit-scrollbar-thumb": {
|
"*&::-webkit-scrollbar-thumb": {
|
||||||
background: `${theme.palette.primary.dark}`,
|
background: `${theme.palette.primary.dark}`,
|
||||||
borderRadius: "4px",
|
borderRadius: "4px",
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<CssBaseline />
|
<CssBaseline />
|
||||||
{children}
|
{children}
|
||||||
<ToastContainer rtl containerId="filtering" closeButton={false} />
|
<ToastContainer rtl containerId="filtering" closeButton={false} />
|
||||||
<ToastContainer rtl containerId="request_data" />
|
<ToastContainer rtl containerId="request_data" />
|
||||||
<ToastContainer rtl containerId="socket_connection" position="top-left" />
|
<ToastContainer rtl containerId="socket_connection" position="top-left" />
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Template;
|
export default Template;
|
||||||
|
|||||||
@@ -1,212 +1,172 @@
|
|||||||
@font-face {
|
@font-face {
|
||||||
font-family: IRANSansFaNum;
|
font-family: IRANSansFaNum;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
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 {
|
||||||
font-family: IRANSansFaNum;
|
font-family: IRANSansFaNum;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
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 {
|
||||||
font-family: IRANSansFaNum;
|
font-family: IRANSansFaNum;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
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 {
|
||||||
font-family: IRANSansFaNum;
|
font-family: IRANSansFaNum;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
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 {
|
||||||
font-family: IRANSansFaNum;
|
font-family: IRANSansFaNum;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
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 {
|
||||||
font-family: IRANSansFaNum;
|
font-family: IRANSansFaNum;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
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") format("woff"),
|
||||||
/* FF39+,Chrome36+, Opera24+*/ url("../fonts/woff/IRANSansWeb(FaNum).woff")
|
/* FF3.6+, IE9, Chrome6+, Saf5.1+*/ url("../fonts/ttf/IRANSansWeb(FaNum).ttf") format("truetype");
|
||||||
format("woff"),
|
|
||||||
/* FF3.6+, IE9, Chrome6+, Saf5.1+*/
|
|
||||||
url("../fonts/ttf/IRANSansWeb(FaNum).ttf") format("truetype");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: IRANSans;
|
font-family: IRANSans;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 900;
|
font-weight: 900;
|
||||||
src: url("../fonts/eot/IRANSansWeb_Black.eot");
|
src: url("../fonts/eot/IRANSansWeb_Black.eot");
|
||||||
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 {
|
||||||
font-family: IRANSans;
|
font-family: IRANSans;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
src: url("../fonts/eot/IRANSansWeb_Bold.eot");
|
src: url("../fonts/eot/IRANSansWeb_Bold.eot");
|
||||||
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 {
|
||||||
font-family: IRANSans;
|
font-family: IRANSans;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
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") format("woff"),
|
||||||
/* FF39+,Chrome36+, Opera24+*/ url("../fonts/woff/IRANSansWeb_Medium.woff")
|
/* FF3.6+, IE9, Chrome6+, Saf5.1+*/ url("../fonts/ttf/IRANSansWeb_Medium.ttf") format("truetype");
|
||||||
format("woff"),
|
|
||||||
/* FF3.6+, IE9, Chrome6+, Saf5.1+*/
|
|
||||||
url("../fonts/ttf/IRANSansWeb_Medium.ttf") format("truetype");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: IRANSans;
|
font-family: IRANSans;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
src: url("../fonts/eot/IRANSansWeb_Light.eot");
|
src: url("../fonts/eot/IRANSansWeb_Light.eot");
|
||||||
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 {
|
||||||
font-family: IRANSans;
|
font-family: IRANSans;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
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 {
|
||||||
font-family: IRANSans;
|
font-family: IRANSans;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
src: url("../fonts/eot/IRANSansWeb.eot");
|
src: url("../fonts/eot/IRANSansWeb.eot");
|
||||||
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 {
|
||||||
font-family: Parastoo;
|
font-family: Parastoo;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
src: url("../fonts/eot/Parastoo.eot");
|
src: url("../fonts/eot/Parastoo.eot");
|
||||||
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 {
|
||||||
font-family: Parastoo;
|
font-family: Parastoo;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
src: url("../fonts/eot/Parastoo-Bold.eot");
|
src: url("../fonts/eot/Parastoo-Bold.eot");
|
||||||
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 {
|
||||||
font-family: Bnazanin;
|
font-family: Bnazanin;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
src: url("../fonts/ttf/BNazanin.ttf") format("truetype");
|
src: url("../fonts/ttf/BNazanin.ttf") format("truetype");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.filter-toast {
|
.filter-toast {
|
||||||
box-shadow:
|
box-shadow:
|
||||||
rgba(50, 50, 93, 0.25) 0px 13px 27px -5px,
|
rgba(50, 50, 93, 0.25) 0px 13px 27px -5px,
|
||||||
rgba(0, 0, 0, 0.3) 0px 8px 16px -8px;
|
rgba(0, 0, 0, 0.3) 0px 8px 16px -8px;
|
||||||
background-color: #d0dfe8;
|
background-color: #d0dfe8;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,169 +5,135 @@ 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";
|
||||||
|
|
||||||
const LoginForm = () => {
|
const LoginForm = () => {
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
const requestServer = useRequest();
|
const requestServer = useRequest();
|
||||||
const { getUser } = useAuth();
|
const { getUser } = useAuth();
|
||||||
const defaultValues = {
|
const defaultValues = {
|
||||||
username: "",
|
username: "",
|
||||||
password: "",
|
password: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
const validationSchema = object({
|
const validationSchema = object({
|
||||||
username: string().required("لطفا نام کاربری را وارد کنید!"),
|
username: string().required("لطفا نام کاربری را وارد کنید!"),
|
||||||
password: string().required("لطفا رمز عبور را وارد کنید!"),
|
password: string().required("لطفا رمز عبور را وارد کنید!"),
|
||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
control,
|
control,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
formState: { isSubmitting },
|
formState: { isSubmitting },
|
||||||
} = useForm({
|
} = useForm({
|
||||||
defaultValues,
|
defaultValues,
|
||||||
resolver: yupResolver(validationSchema),
|
resolver: yupResolver(validationSchema),
|
||||||
mode: "all",
|
mode: "all",
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleClickShowPassword = () => setShowPassword((show) => !show);
|
const handleClickShowPassword = () => setShowPassword((show) => !show);
|
||||||
|
|
||||||
const onSubmit = async (data) => {
|
const onSubmit = async (data) => {
|
||||||
try {
|
try {
|
||||||
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);
|
||||||
await requestServer(GET_USER_LOGIN_ROUTE, "post", {
|
await requestServer(GET_USER_LOGIN_ROUTE, "post", {
|
||||||
data: formData,
|
data: formData,
|
||||||
});
|
});
|
||||||
getUser();
|
getUser();
|
||||||
} 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
|
ورود به سامانه CRM
|
||||||
sx={{ width: "100%", height: "100%" }}
|
</Typography>
|
||||||
justifyContent="center"
|
</Stack>
|
||||||
alignItems="center"
|
<Stack sx={{ width: "100%", p: 2 }} spacing={4}>
|
||||||
spacing={6}
|
<Stack spacing={2}>
|
||||||
>
|
<Controller
|
||||||
<Stack
|
control={control}
|
||||||
sx={{ width: "100%", mb: 2 }}
|
render={({ field, fieldState: { error } }) => {
|
||||||
justifyContent="center"
|
return (
|
||||||
alignItems="center"
|
<LtrTextField
|
||||||
>
|
{...field}
|
||||||
<Typography
|
label="نام کاربری"
|
||||||
variant="h4"
|
variant="outlined"
|
||||||
color="primary"
|
slotProps={{
|
||||||
fontWeight={600}
|
input: {
|
||||||
textAlign="center"
|
endAdornment: (
|
||||||
>
|
<InputAdornment position="end">
|
||||||
ورود به سامانه CRM
|
<Person />
|
||||||
</Typography>
|
</InputAdornment>
|
||||||
</Stack>
|
),
|
||||||
<Stack sx={{ width: "100%", p: 2 }} spacing={4}>
|
},
|
||||||
<Stack spacing={2}>
|
}}
|
||||||
<Controller
|
fullWidth
|
||||||
control={control}
|
error={!!error}
|
||||||
render={({ field, fieldState: { error } }) => {
|
helperText={!!error && error.message}
|
||||||
return (
|
/>
|
||||||
<LtrTextField
|
);
|
||||||
{...field}
|
}}
|
||||||
label="نام کاربری"
|
name={"username"}
|
||||||
variant="outlined"
|
/>
|
||||||
slotProps={{
|
<Controller
|
||||||
input: {
|
control={control}
|
||||||
endAdornment: (
|
render={({ field, fieldState: { error } }) => {
|
||||||
<InputAdornment position="end">
|
return (
|
||||||
<Person />
|
<LtrTextField
|
||||||
</InputAdornment>
|
{...field}
|
||||||
),
|
label="رمزعبور"
|
||||||
},
|
variant="outlined"
|
||||||
}}
|
type={showPassword ? "text" : "password"}
|
||||||
fullWidth
|
slotProps={{
|
||||||
error={!!error}
|
input: {
|
||||||
helperText={!!error && error.message}
|
startAdornment: (
|
||||||
/>
|
<InputAdornment position="start">
|
||||||
);
|
<IconButton onClick={handleClickShowPassword}>
|
||||||
}}
|
{showPassword ? <VisibilityOff /> : <Visibility />}
|
||||||
name={"username"}
|
</IconButton>
|
||||||
/>
|
</InputAdornment>
|
||||||
<Controller
|
),
|
||||||
control={control}
|
endAdornment: (
|
||||||
render={({ field, fieldState: { error } }) => {
|
<InputAdornment position="end">
|
||||||
return (
|
<Lock />
|
||||||
<LtrTextField
|
</InputAdornment>
|
||||||
{...field}
|
),
|
||||||
label="رمزعبور"
|
},
|
||||||
variant="outlined"
|
}}
|
||||||
type={showPassword ? "text" : "password"}
|
fullWidth
|
||||||
slotProps={{
|
error={!!error}
|
||||||
input: {
|
helperText={!!error && error.message}
|
||||||
startAdornment: (
|
/>
|
||||||
<InputAdornment position="start">
|
);
|
||||||
<IconButton onClick={handleClickShowPassword}>
|
}}
|
||||||
{showPassword ? (
|
name={"password"}
|
||||||
<VisibilityOff />
|
/>
|
||||||
) : (
|
</Stack>
|
||||||
<Visibility />
|
<Stack>
|
||||||
)}
|
<Button
|
||||||
</IconButton>
|
variant="contained"
|
||||||
</InputAdornment>
|
color="primary"
|
||||||
),
|
size="large"
|
||||||
endAdornment: (
|
type={"submit"}
|
||||||
<InputAdornment position="end">
|
disabled={isSubmitting}
|
||||||
<Lock />
|
>
|
||||||
</InputAdornment>
|
{isSubmitting ? "درحال ورود..." : "ورود"}
|
||||||
),
|
</Button>
|
||||||
},
|
</Stack>
|
||||||
}}
|
</Stack>
|
||||||
fullWidth
|
</Stack>
|
||||||
error={!!error}
|
</StyledForm>
|
||||||
helperText={!!error && error.message}
|
</Container>
|
||||||
/>
|
);
|
||||||
);
|
|
||||||
}}
|
|
||||||
name={"password"}
|
|
||||||
/>
|
|
||||||
</Stack>
|
|
||||||
<Stack>
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
color="primary"
|
|
||||||
size="large"
|
|
||||||
type={"submit"}
|
|
||||||
disabled={isSubmitting}
|
|
||||||
>
|
|
||||||
{isSubmitting ? "درحال ورود..." : "ورود"}
|
|
||||||
</Button>
|
|
||||||
</Stack>
|
|
||||||
</Stack>
|
|
||||||
</Stack>
|
|
||||||
</StyledForm>
|
|
||||||
</Container>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
export default LoginForm;
|
export default LoginForm;
|
||||||
|
|||||||
@@ -4,22 +4,22 @@ import Link from "next/link";
|
|||||||
import { useSearchParams } from "next/navigation";
|
import { useSearchParams } from "next/navigation";
|
||||||
|
|
||||||
const LoginLinkRouting = () => {
|
const LoginLinkRouting = () => {
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
|
|
||||||
const redirect = searchParams.get("redirect");
|
const redirect = searchParams.get("redirect");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack direction="row" alignItems="center" justifyContent="center">
|
<Stack direction="row" alignItems="center" justifyContent="center">
|
||||||
<Button
|
<Button
|
||||||
component={Link}
|
component={Link}
|
||||||
data-testid="link_routing"
|
data-testid="link_routing"
|
||||||
sx={{ margin: 2 }}
|
sx={{ margin: 2 }}
|
||||||
color="secondary"
|
color="secondary"
|
||||||
href={redirect ? decodeURIComponent(redirect) : "/"}
|
href={redirect ? decodeURIComponent(redirect) : "/"}
|
||||||
>
|
>
|
||||||
{`بازگشت به ${redirect ? "صفحه قبلی" : "صفحه اصلی"}`}
|
{`بازگشت به ${redirect ? "صفحه قبلی" : "صفحه اصلی"}`}
|
||||||
</Button>
|
</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default LoginLinkRouting;
|
export default LoginLinkRouting;
|
||||||
|
|||||||
@@ -5,15 +5,15 @@ import LoginLinkRouting from "./LoginLinkRouting";
|
|||||||
import { Suspense } from "react";
|
import { Suspense } from "react";
|
||||||
|
|
||||||
const LoginPage = () => {
|
const LoginPage = () => {
|
||||||
return (
|
return (
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<Fade in={true}>
|
<Fade in={true}>
|
||||||
<Stack sx={{ width: "100%", height: "100vh" }}>
|
<Stack sx={{ width: "100%", height: "100vh" }}>
|
||||||
<LoginForm />
|
<LoginForm />
|
||||||
<LoginLinkRouting />
|
<LoginLinkRouting />
|
||||||
</Stack>
|
</Stack>
|
||||||
</Fade>
|
</Fade>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default LoginPage;
|
export default LoginPage;
|
||||||
|
|||||||
@@ -13,205 +13,200 @@ import OnlineCell from "./RowActions/Online";
|
|||||||
import TelephoneId from "./RowActions/TelephoneId";
|
import TelephoneId from "./RowActions/TelephoneId";
|
||||||
|
|
||||||
const DataTable = () => {
|
const DataTable = () => {
|
||||||
const columns = useMemo(
|
const columns = useMemo(
|
||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
accessorKey: "id",
|
accessorKey: "id",
|
||||||
header: "کد یکتا",
|
header: "کد یکتا",
|
||||||
id: "id",
|
id: "id",
|
||||||
enableColumnFilter: true,
|
enableColumnFilter: true,
|
||||||
datatype: "text",
|
datatype: "text",
|
||||||
filterMode: "equals",
|
filterMode: "equals",
|
||||||
sortDescFirst: false,
|
sortDescFirst: false,
|
||||||
columnFilterModeOptions: ["equals", "contains"],
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
grow: false,
|
grow: false,
|
||||||
size: 100,
|
size: 100,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "full_name",
|
accessorKey: "full_name",
|
||||||
header: "نام و نام خانوادگی",
|
header: "نام و نام خانوادگی",
|
||||||
id: "full_name",
|
id: "full_name",
|
||||||
enableColumnFilter: true,
|
enableColumnFilter: true,
|
||||||
datatype: "text",
|
datatype: "text",
|
||||||
filterMode: "equals",
|
filterMode: "equals",
|
||||||
sortDescFirst: false,
|
sortDescFirst: false,
|
||||||
columnFilterModeOptions: ["equals", "contains"],
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
grow: false,
|
grow: false,
|
||||||
size: 100,
|
size: 100,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: "شماره تماس داخلی",
|
header: "شماره تماس داخلی",
|
||||||
id: "telephone_id",
|
id: "telephone_id",
|
||||||
enableColumnFilter: true,
|
enableColumnFilter: true,
|
||||||
datatype: "text",
|
datatype: "text",
|
||||||
filterMode: "equals",
|
filterMode: "equals",
|
||||||
sortDescFirst: false,
|
sortDescFirst: false,
|
||||||
columnFilterModeOptions: ["equals", "contains"],
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
grow: false,
|
grow: false,
|
||||||
size: 100,
|
size: 100,
|
||||||
Cell: ({ row }) => <TelephoneId user_id={row.original.id} />,
|
Cell: ({ row }) => <TelephoneId user_id={row.original.id} />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "is_online",
|
accessorKey: "is_online",
|
||||||
header: "وضعیت برخط",
|
header: "وضعیت برخط",
|
||||||
id: "is_online",
|
id: "is_online",
|
||||||
enableColumnFilter: true,
|
enableColumnFilter: true,
|
||||||
datatype: "text",
|
datatype: "text",
|
||||||
filterMode: "equals",
|
filterMode: "equals",
|
||||||
sortDescFirst: false,
|
sortDescFirst: false,
|
||||||
columnFilterModeOptions: ["equals", "contains"],
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
grow: false,
|
grow: false,
|
||||||
size: 100,
|
size: 100,
|
||||||
Cell: ({ row }) => <OnlineCell user_id={row.original.id} />,
|
Cell: ({ row }) => <OnlineCell user_id={row.original.id} />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: "استان",
|
header: "استان",
|
||||||
id: "province_id",
|
id: "province_id",
|
||||||
enableColumnFilter: true,
|
enableColumnFilter: true,
|
||||||
datatype: "numeric",
|
datatype: "numeric",
|
||||||
filterMode: "equals",
|
filterMode: "equals",
|
||||||
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: "در حال بارگذاری..." }];
|
}
|
||||||
}
|
if (errorProvinces) {
|
||||||
if (errorProvinces) {
|
return [{ value: "error", label: "خطا در بارگذاری" }];
|
||||||
return [{ value: "error", label: "خطا در بارگذاری" }];
|
}
|
||||||
}
|
return [
|
||||||
return [
|
{ value: "", label: "کل کشور" },
|
||||||
{ value: "", label: "کل کشور" },
|
...provinces.map((province) => ({
|
||||||
...provinces.map((province) => ({
|
value: province.id,
|
||||||
value: province.id,
|
label: province.name,
|
||||||
label: province.name,
|
})),
|
||||||
})),
|
];
|
||||||
];
|
}, [provinces, errorProvinces, loadingProvinces]);
|
||||||
}, [provinces, errorProvinces, loadingProvinces]);
|
return (
|
||||||
return (
|
<CustomSelectByDependency
|
||||||
<CustomSelectByDependency
|
{...props}
|
||||||
{...props}
|
value={loadingProvinces ? "loading" : props.filterParameters.value}
|
||||||
value={
|
columnSelectOption={getColumnSelectOptions}
|
||||||
loadingProvinces ? "loading" : props.filterParameters.value
|
/>
|
||||||
}
|
);
|
||||||
columnSelectOption={getColumnSelectOptions}
|
},
|
||||||
/>
|
Cell: ({ renderedCellValue, row }) => <>{row.original.province_fa}</>,
|
||||||
);
|
},
|
||||||
},
|
{
|
||||||
Cell: ({ renderedCellValue, row }) => <>{row.original.province_fa}</>,
|
accessorKey: "username",
|
||||||
},
|
header: "نام کاربری",
|
||||||
{
|
id: "username",
|
||||||
accessorKey: "username",
|
enableColumnFilter: true,
|
||||||
header: "نام کاربری",
|
datatype: "text",
|
||||||
id: "username",
|
filterMode: "equals",
|
||||||
enableColumnFilter: true,
|
sortDescFirst: false,
|
||||||
datatype: "text",
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
filterMode: "equals",
|
grow: false,
|
||||||
sortDescFirst: false,
|
size: 100,
|
||||||
columnFilterModeOptions: ["equals", "contains"],
|
},
|
||||||
grow: false,
|
{
|
||||||
size: 100,
|
accessorKey: "position",
|
||||||
},
|
header: "سمت",
|
||||||
{
|
id: "position",
|
||||||
accessorKey: "position",
|
enableColumnFilter: false,
|
||||||
header: "سمت",
|
grow: false,
|
||||||
id: "position",
|
size: 100,
|
||||||
enableColumnFilter: false,
|
},
|
||||||
grow: false,
|
{
|
||||||
size: 100,
|
accessorKey: "phone_number",
|
||||||
},
|
header: "شماره همراه",
|
||||||
{
|
id: "phone_number",
|
||||||
accessorKey: "phone_number",
|
enableColumnFilter: false,
|
||||||
header: "شماره همراه",
|
grow: false,
|
||||||
id: "phone_number",
|
size: 100,
|
||||||
enableColumnFilter: false,
|
},
|
||||||
grow: false,
|
{
|
||||||
size: 100,
|
header: "نقش",
|
||||||
},
|
id: "role__id",
|
||||||
{
|
enableColumnFilter: false,
|
||||||
header: "نقش",
|
grow: false,
|
||||||
id: "role__id",
|
size: 100,
|
||||||
enableColumnFilter: false,
|
enableColumnFilter: true,
|
||||||
grow: false,
|
datatype: "numeric",
|
||||||
size: 100,
|
filterMode: "equals",
|
||||||
enableColumnFilter: true,
|
grow: false,
|
||||||
datatype: "numeric",
|
size: 130,
|
||||||
filterMode: "equals",
|
ColumnSelectComponent: (props) => {
|
||||||
grow: false,
|
const { roles, errorRoles, loadingRoles } = useRoles();
|
||||||
size: 130,
|
const getColumnSelectOptions = useMemo(() => {
|
||||||
ColumnSelectComponent: (props) => {
|
if (loadingRoles) {
|
||||||
const { roles, errorRoles, loadingRoles } = useRoles();
|
return [{ value: "loading", label: "در حال بارگذاری..." }];
|
||||||
const getColumnSelectOptions = useMemo(() => {
|
}
|
||||||
if (loadingRoles) {
|
if (errorRoles) {
|
||||||
return [{ value: "loading", label: "در حال بارگذاری..." }];
|
return [{ value: "error", label: "خطا در بارگذاری" }];
|
||||||
}
|
}
|
||||||
if (errorRoles) {
|
return [
|
||||||
return [{ value: "error", label: "خطا در بارگذاری" }];
|
{ value: "", label: "همه نقش ها" },
|
||||||
}
|
...roles.map((role) => ({
|
||||||
return [
|
value: role.id,
|
||||||
{ value: "", label: "همه نقش ها" },
|
label: role.name_fa,
|
||||||
...roles.map((role) => ({
|
})),
|
||||||
value: role.id,
|
];
|
||||||
label: role.name_fa,
|
}, [roles, errorRoles, loadingRoles]);
|
||||||
})),
|
return (
|
||||||
];
|
<CustomSelectByDependency
|
||||||
}, [roles, errorRoles, loadingRoles]);
|
{...props}
|
||||||
return (
|
value={loadingRoles ? "loading" : props.filterParameters.value}
|
||||||
<CustomSelectByDependency
|
columnSelectOption={getColumnSelectOptions}
|
||||||
{...props}
|
/>
|
||||||
value={loadingRoles ? "loading" : props.filterParameters.value}
|
);
|
||||||
columnSelectOption={getColumnSelectOptions}
|
},
|
||||||
/>
|
Cell: ({ row }) => <>{row.original.roles[0].name_fa}</>,
|
||||||
);
|
},
|
||||||
},
|
{
|
||||||
Cell: ({ row }) => <>{row.original.roles[0].name_fa}</>,
|
accessorKey: "national_id",
|
||||||
},
|
header: "کد ملی",
|
||||||
{
|
id: "national_id",
|
||||||
accessorKey: "national_id",
|
enableColumnFilter: true,
|
||||||
header: "کد ملی",
|
datatype: "text",
|
||||||
id: "national_id",
|
filterMode: "equals",
|
||||||
enableColumnFilter: true,
|
sortDescFirst: false,
|
||||||
datatype: "text",
|
columnFilterModeOptions: ["equals", "contains"],
|
||||||
filterMode: "equals",
|
grow: false,
|
||||||
sortDescFirst: false,
|
size: 100,
|
||||||
columnFilterModeOptions: ["equals", "contains"],
|
},
|
||||||
grow: false,
|
{
|
||||||
size: 100,
|
accessorKey: "gender",
|
||||||
},
|
header: "جنسیت",
|
||||||
{
|
id: "gender",
|
||||||
accessorKey: "gender",
|
enableColumnFilter: false,
|
||||||
header: "جنسیت",
|
grow: false,
|
||||||
id: "gender",
|
size: 100,
|
||||||
enableColumnFilter: false,
|
Cell: ({ renderedCellValue }) => <>{renderedCellValue == "male" ? "آقا" : "خانم"}</>,
|
||||||
grow: false,
|
},
|
||||||
size: 100,
|
],
|
||||||
Cell: ({ renderedCellValue }) => (
|
[]
|
||||||
<>{renderedCellValue == "male" ? "آقا" : "خانم"}</>
|
);
|
||||||
),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Box sx={{ p: 1, border: 1, borderColor: "divider", borderRadius: 1 }}>
|
<Box sx={{ p: 1, border: 1, borderColor: "divider", borderRadius: 1 }}>
|
||||||
<DataTableWithAuth
|
<DataTableWithAuth
|
||||||
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={GET_USER_LIST}
|
||||||
page_name={"usersPage"}
|
page_name={"usersPage"}
|
||||||
table_name={"usersList"}
|
table_name={"usersList"}
|
||||||
TableToolbar={Toolbar}
|
TableToolbar={Toolbar}
|
||||||
enableRowActions
|
enableRowActions
|
||||||
positionActionsColumn={"last"}
|
positionActionsColumn={"last"}
|
||||||
RowActions={RowActions}
|
RowActions={RowActions}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default DataTable;
|
export default DataTable;
|
||||||
|
|||||||
@@ -1,56 +1,39 @@
|
|||||||
import { DELETE_USER } from "@/core/utils/routes";
|
import { DELETE_USER } 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 }) => {
|
||||||
const [submitting, setSubmitting] = useState(false);
|
const [submitting, setSubmitting] = useState(false);
|
||||||
const requestServer = useRequest({ notificationSuccess: true });
|
const requestServer = useRequest({ notificationSuccess: true });
|
||||||
const handleClick = async () => {
|
const handleClick = async () => {
|
||||||
setSubmitting(true);
|
setSubmitting(true);
|
||||||
try {
|
try {
|
||||||
await requestServer(`${DELETE_USER}/${rowId}`, "delete");
|
await requestServer(`${DELETE_USER}/${rowId}`, "delete");
|
||||||
mutate();
|
mutate();
|
||||||
setOpenDeleteDialog(false);
|
setOpenDeleteDialog(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
} finally {
|
} finally {
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<Stack alignItems="center" spacing={2}>
|
<Stack alignItems="center" spacing={2}>
|
||||||
<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
|
</Button>
|
||||||
onClick={() => setOpenDeleteDialog(false)}
|
<Button onClick={handleClick} variant="contained" color="error" disabled={submitting}>
|
||||||
variant="outlined"
|
{submitting ? "درحال حذف..." : "بله اطمینان دارم"}
|
||||||
color="secondary"
|
</Button>
|
||||||
>
|
</DialogActions>
|
||||||
خیر !
|
</>
|
||||||
</Button>
|
);
|
||||||
<Button
|
|
||||||
onClick={handleClick}
|
|
||||||
variant="contained"
|
|
||||||
color="error"
|
|
||||||
disabled={submitting}
|
|
||||||
>
|
|
||||||
{submitting ? "درحال حذف..." : "بله اطمینان دارم"}
|
|
||||||
</Button>
|
|
||||||
</DialogActions>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default DeleteContent;
|
export default DeleteContent;
|
||||||
|
|||||||
@@ -3,35 +3,30 @@ import { Dialog, DialogTitle, IconButton, Tooltip } from "@mui/material";
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import DeleteContent from "./DeleteContent";
|
import DeleteContent from "./DeleteContent";
|
||||||
const Delete = ({ rowId, mutate }) => {
|
const Delete = ({ rowId, mutate }) => {
|
||||||
const [openDeleteDialog, setOpenDeleteDialog] = useState(false);
|
const [openDeleteDialog, setOpenDeleteDialog] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Tooltip title="حذف">
|
<Tooltip title="حذف">
|
||||||
<IconButton color="error" onClick={() => setOpenDeleteDialog(true)}>
|
<IconButton color="error" onClick={() => setOpenDeleteDialog(true)}>
|
||||||
<DeleteIcon />
|
<DeleteIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Dialog
|
<Dialog
|
||||||
open={openDeleteDialog}
|
open={openDeleteDialog}
|
||||||
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 rowId={rowId} mutate={mutate} setOpenDeleteDialog={setOpenDeleteDialog} />
|
||||||
<DeleteContent
|
</Dialog>
|
||||||
rowId={rowId}
|
</>
|
||||||
mutate={mutate}
|
);
|
||||||
setOpenDeleteDialog={setOpenDeleteDialog}
|
|
||||||
/>
|
|
||||||
</Dialog>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
export default Delete;
|
export default Delete;
|
||||||
|
|||||||
@@ -7,363 +7,357 @@ import useRequest from "@/lib/hooks/useRequest";
|
|||||||
import useRoles from "@/lib/hooks/useRoles";
|
import useRoles from "@/lib/hooks/useRoles";
|
||||||
import { yupResolver } from "@hookform/resolvers/yup";
|
import { yupResolver } from "@hookform/resolvers/yup";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Chip,
|
Chip,
|
||||||
DialogActions,
|
DialogActions,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
Divider,
|
Divider,
|
||||||
FormControl,
|
FormControl,
|
||||||
FormHelperText,
|
FormHelperText,
|
||||||
Grid,
|
Grid,
|
||||||
InputLabel,
|
InputLabel,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
OutlinedInput,
|
OutlinedInput,
|
||||||
Select,
|
Select,
|
||||||
Stack,
|
Stack,
|
||||||
TextField,
|
TextField,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { useMemo } from "react";
|
import { useMemo } 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";
|
||||||
|
|
||||||
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),
|
|
||||||
)
|
|
||||||
.required("کد ملی خود را وارد کنید"),
|
|
||||||
position: string().required("سمت خود را وارد کنید"),
|
|
||||||
province_id: string().required("استان خود را وارد کنید"),
|
|
||||||
role_id: string().required("نقش خود را وارد کنید"),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const UpdateUserForm = ({ values, setOpen, mutate }) => {
|
const UpdateUserForm = ({ values, setOpen, mutate }) => {
|
||||||
const requestServer = useRequest({ notificationSuccess: true });
|
const requestServer = useRequest({ notificationSuccess: true });
|
||||||
const { provinces, errorProvinces, loadingProvinces } = useProvinces();
|
const { provinces, errorProvinces, loadingProvinces } = useProvinces();
|
||||||
const { roles, errorRoles, loadingRoles } = useRoles();
|
const { roles, errorRoles, loadingRoles } = useRoles();
|
||||||
|
|
||||||
const defaultValues = {
|
const defaultValues = {
|
||||||
full_name: values.full_name,
|
full_name: values.full_name,
|
||||||
username: values.username,
|
username: values.username,
|
||||||
position: values.position,
|
position: values.position,
|
||||||
gender: values.gender,
|
gender: values.gender,
|
||||||
national_id: values.national_id,
|
national_id: values.national_id,
|
||||||
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,
|
||||||
};
|
};
|
||||||
|
|
||||||
const provinceSelectOptions = useMemo(() => {
|
const provinceSelectOptions = useMemo(() => {
|
||||||
if (loadingProvinces) {
|
if (loadingProvinces) {
|
||||||
return [{ value: "loading", label: "در حال بارگذاری..." }];
|
return [{ value: "loading", label: "در حال بارگذاری..." }];
|
||||||
}
|
}
|
||||||
if (errorProvinces) {
|
if (errorProvinces) {
|
||||||
return [{ value: "error", label: "خطا در بارگذاری" }];
|
return [{ value: "error", label: "خطا در بارگذاری" }];
|
||||||
}
|
}
|
||||||
return [
|
return [
|
||||||
{ value: "", label: "انتخاب استان" },
|
{ value: "", label: "انتخاب استان" },
|
||||||
...provinces.map((province) => ({
|
...provinces.map((province) => ({
|
||||||
value: province.id,
|
value: province.id,
|
||||||
label: province.name,
|
label: province.name,
|
||||||
})),
|
})),
|
||||||
];
|
];
|
||||||
}, [provinces, errorProvinces, loadingProvinces]);
|
}, [provinces, errorProvinces, loadingProvinces]);
|
||||||
|
|
||||||
const roleSelectOptions = useMemo(() => {
|
const roleSelectOptions = useMemo(() => {
|
||||||
if (loadingRoles) {
|
if (loadingRoles) {
|
||||||
return [{ value: "loading", label: "در حال بارگذاری..." }];
|
return [{ value: "loading", label: "در حال بارگذاری..." }];
|
||||||
}
|
}
|
||||||
if (errorRoles) {
|
if (errorRoles) {
|
||||||
return [{ value: "error", label: "خطا در بارگذاری" }];
|
return [{ value: "error", label: "خطا در بارگذاری" }];
|
||||||
}
|
}
|
||||||
return [
|
return [
|
||||||
{ value: "", label: "انتخاب نقش" },
|
{ value: "", label: "انتخاب نقش" },
|
||||||
...roles.map((role) => ({
|
...roles.map((role) => ({
|
||||||
value: role.id,
|
value: role.id,
|
||||||
label: role.name_fa,
|
label: role.name_fa,
|
||||||
})),
|
})),
|
||||||
];
|
];
|
||||||
}, [roles, errorRoles, loadingRoles]);
|
}, [roles, errorRoles, loadingRoles]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
control,
|
control,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
formState: { isSubmitting, errors },
|
formState: { isSubmitting, errors },
|
||||||
} = useForm({ defaultValues, resolver: yupResolver(validationSchema) });
|
} = useForm({ defaultValues, resolver: yupResolver(validationSchema) });
|
||||||
|
|
||||||
const onSubmit = async (data) => {
|
const onSubmit = async (data) => {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
Object.keys(data).forEach((key) => {
|
Object.keys(data).forEach((key) => {
|
||||||
formData.append(key, data[key]);
|
formData.append(key, data[key]);
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await requestServer(`${UPDATE_USER}/${values.id}`, "post", {
|
await requestServer(`${UPDATE_USER}/${values.id}`, "post", {
|
||||||
data: formData,
|
data: formData,
|
||||||
});
|
});
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
mutate();
|
mutate();
|
||||||
} catch (error) {}
|
} catch (error) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<DialogContent dividers>
|
<DialogContent dividers>
|
||||||
<Divider sx={{ mb: 3 }}>
|
<Divider sx={{ mb: 3 }}>
|
||||||
<Chip color="primary" variant="outlined" label="کاربر جدید" />
|
<Chip color="primary" variant="outlined" label="کاربر جدید" />
|
||||||
</Divider>
|
</Divider>
|
||||||
<StyledForm
|
<StyledForm
|
||||||
id="UpdateUserForm"
|
id="UpdateUserForm"
|
||||||
onSubmit={handleSubmit(onSubmit)}
|
onSubmit={handleSubmit(onSubmit)}
|
||||||
style={{ width: "100%", height: "100%" }}
|
style={{ width: "100%", height: "100%" }}
|
||||||
>
|
>
|
||||||
<Stack spacing={2}>
|
<Stack spacing={2}>
|
||||||
<Grid container spacing={2}>
|
<Grid container spacing={2}>
|
||||||
<Grid size={{ xs: 12, md: 6 }}>
|
<Grid size={{ xs: 12, md: 6 }}>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field, fieldState: { error } }) => {
|
render={({ field, fieldState: { error } }) => {
|
||||||
return (
|
return (
|
||||||
<LtrTextField
|
<LtrTextField
|
||||||
{...field}
|
{...field}
|
||||||
label="نام کاربری"
|
label="نام کاربری"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
fullWidth
|
fullWidth
|
||||||
error={!!error}
|
error={!!error}
|
||||||
helperText={!!error && error.message}
|
helperText={!!error && error.message}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
name={"username"}
|
name={"username"}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid size={{ xs: 12, md: 6 }}>
|
<Grid size={{ xs: 12, md: 6 }}>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field, fieldState: { error } }) => {
|
render={({ field, fieldState: { error } }) => {
|
||||||
return (
|
return (
|
||||||
<TextField
|
<TextField
|
||||||
{...field}
|
{...field}
|
||||||
label="نام و نام خانوادگی"
|
label="نام و نام خانوادگی"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
fullWidth
|
fullWidth
|
||||||
error={!!error}
|
error={!!error}
|
||||||
helperText={!!error && error.message}
|
helperText={!!error && error.message}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
name={"full_name"}
|
name={"full_name"}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid size={{ xs: 12, md: 6 }}>
|
<Grid size={{ xs: 12, md: 6 }}>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field, fieldState: { error } }) => {
|
render={({ field, fieldState: { error } }) => {
|
||||||
return (
|
return (
|
||||||
<TextField
|
<TextField
|
||||||
{...field}
|
{...field}
|
||||||
label="سمت"
|
label="سمت"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
fullWidth
|
fullWidth
|
||||||
error={!!error}
|
error={!!error}
|
||||||
helperText={!!error && error.message}
|
helperText={!!error && error.message}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
name={"position"}
|
name={"position"}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid size={{ xs: 12, md: 6 }}>
|
<Grid size={{ xs: 12, md: 6 }}>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field, fieldState: { error } }) => {
|
render={({ field, fieldState: { error } }) => {
|
||||||
return (
|
return (
|
||||||
<LtrTextField
|
<LtrTextField
|
||||||
{...field}
|
{...field}
|
||||||
label="شماره همراه"
|
label="شماره همراه"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
fullWidth
|
fullWidth
|
||||||
type="tel"
|
type="tel"
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const inputValue = event.target.value;
|
const inputValue = event.target.value;
|
||||||
if (isNaN(Number(inputValue))) {
|
if (isNaN(Number(inputValue))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (inputValue.length > 11) {
|
if (inputValue.length > 11) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
field.onChange(inputValue);
|
field.onChange(inputValue);
|
||||||
}}
|
}}
|
||||||
error={!!error}
|
error={!!error}
|
||||||
helperText={!!error && error.message}
|
helperText={!!error && error.message}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
name={"phone_number"}
|
name={"phone_number"}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid size={{ xs: 12, md: 6 }}>
|
<Grid size={{ xs: 12, md: 6 }}>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
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-phone-number`} shrink>
|
||||||
جنسیت
|
جنسیت
|
||||||
</InputLabel>
|
</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
labelId={`label-phone-number`}
|
labelId={`label-phone-number`}
|
||||||
id={"phone-number"}
|
id={"phone-number"}
|
||||||
name={`gender`}
|
name={`gender`}
|
||||||
value={field.value}
|
value={field.value}
|
||||||
input={<OutlinedInput label={"جنسیت"} />}
|
input={<OutlinedInput label={"جنسیت"} />}
|
||||||
onChange={(e) => field.onChange(e.target.value)}
|
onChange={(e) => field.onChange(e.target.value)}
|
||||||
displayEmpty
|
displayEmpty
|
||||||
>
|
>
|
||||||
<MenuItem value={"male"}>مرد</MenuItem>
|
<MenuItem value={"male"}>مرد</MenuItem>
|
||||||
<MenuItem value={"female"}>زن</MenuItem>
|
<MenuItem value={"female"}>زن</MenuItem>
|
||||||
</Select>
|
</Select>
|
||||||
<FormHelperText error={!!error} sx={{ mt: 1 }}>
|
<FormHelperText error={!!error} sx={{ mt: 1 }}>
|
||||||
{!!error && error.message}
|
{!!error && error.message}
|
||||||
</FormHelperText>
|
</FormHelperText>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
name={"gender"}
|
name={"gender"}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid size={{ xs: 12, md: 6 }}>
|
<Grid size={{ xs: 12, md: 6 }}>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field, fieldState: { error } }) => {
|
render={({ field, fieldState: { error } }) => {
|
||||||
return (
|
return (
|
||||||
<LtrTextField
|
<LtrTextField
|
||||||
{...field}
|
{...field}
|
||||||
label="شماره ملی"
|
label="شماره ملی"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
fullWidth
|
fullWidth
|
||||||
type="tel"
|
type="tel"
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const inputValue = event.target.value;
|
const inputValue = event.target.value;
|
||||||
if (isNaN(Number(inputValue))) {
|
if (isNaN(Number(inputValue))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (inputValue.length > 10) {
|
if (inputValue.length > 10) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
field.onChange(inputValue);
|
field.onChange(inputValue);
|
||||||
}}
|
}}
|
||||||
error={!!error}
|
error={!!error}
|
||||||
helperText={!!error && error.message}
|
helperText={!!error && error.message}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
name={"national_id"}
|
name={"national_id"}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid size={{ xs: 12, md: 6 }}>
|
<Grid size={{ xs: 12, md: 6 }}>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field, fieldState: { error } }) => {
|
render={({ field, fieldState: { error } }) => {
|
||||||
return (
|
return (
|
||||||
<FormControl fullWidth error={!!error}>
|
<FormControl fullWidth error={!!error}>
|
||||||
<InputLabel id={`label-province`} shrink>
|
<InputLabel id={`label-province`} shrink>
|
||||||
استان
|
استان
|
||||||
</InputLabel>
|
</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
labelId={`label-province`}
|
labelId={`label-province`}
|
||||||
id={"province"}
|
id={"province"}
|
||||||
name={`province_id`}
|
name={`province_id`}
|
||||||
value={loadingProvinces ? "loading" : field.value}
|
value={loadingProvinces ? "loading" : field.value}
|
||||||
input={<OutlinedInput label={"استان"} />}
|
input={<OutlinedInput label={"استان"} />}
|
||||||
onChange={(e) => field.onChange(e.target.value)}
|
onChange={(e) => field.onChange(e.target.value)}
|
||||||
displayEmpty
|
displayEmpty
|
||||||
>
|
>
|
||||||
{provinceSelectOptions.map((option) => (
|
{provinceSelectOptions.map((option) => (
|
||||||
<MenuItem key={option.value} value={option.value}>
|
<MenuItem key={option.value} value={option.value}>
|
||||||
{option.label}
|
{option.label}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
<FormHelperText error={!!error} sx={{ mt: 1 }}>
|
<FormHelperText error={!!error} sx={{ mt: 1 }}>
|
||||||
{!!error && error.message}
|
{!!error && error.message}
|
||||||
</FormHelperText>
|
</FormHelperText>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
name={"province_id"}
|
name={"province_id"}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid size={{ xs: 12, md: 6 }}>
|
<Grid size={{ xs: 12, md: 6 }}>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field, fieldState: { error } }) => {
|
render={({ field, fieldState: { error } }) => {
|
||||||
return (
|
return (
|
||||||
<FormControl fullWidth error={!!error}>
|
<FormControl fullWidth error={!!error}>
|
||||||
<InputLabel id={`label-role`} shrink>
|
<InputLabel id={`label-role`} shrink>
|
||||||
نقش
|
نقش
|
||||||
</InputLabel>
|
</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
labelId={`label-role`}
|
labelId={`label-role`}
|
||||||
id={"role"}
|
id={"role"}
|
||||||
name={`role_id`}
|
name={`role_id`}
|
||||||
value={loadingRoles ? "loading" : field.value}
|
value={loadingRoles ? "loading" : field.value}
|
||||||
input={<OutlinedInput label={"نقش"} />}
|
input={<OutlinedInput label={"نقش"} />}
|
||||||
onChange={(e) => field.onChange(e.target.value)}
|
onChange={(e) => field.onChange(e.target.value)}
|
||||||
displayEmpty
|
displayEmpty
|
||||||
>
|
>
|
||||||
{roleSelectOptions.map((option) => (
|
{roleSelectOptions.map((option) => (
|
||||||
<MenuItem key={option.value} value={option.value}>
|
<MenuItem key={option.value} value={option.value}>
|
||||||
{option.label}
|
{option.label}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
<FormHelperText error={!!error} sx={{ mt: 1 }}>
|
<FormHelperText error={!!error} sx={{ mt: 1 }}>
|
||||||
{!!error && error.message}
|
{!!error && error.message}
|
||||||
</FormHelperText>
|
</FormHelperText>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
name={"role_id"}
|
name={"role_id"}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Stack>
|
</Stack>
|
||||||
</StyledForm>
|
</StyledForm>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button
|
<Button
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
size="large"
|
size="large"
|
||||||
onClick={() => setOpen(false)}
|
onClick={() => setOpen(false)}
|
||||||
>
|
>
|
||||||
بستن
|
بستن
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
form="UpdateUserForm"
|
form="UpdateUserForm"
|
||||||
size="large"
|
size="large"
|
||||||
autoFocus
|
autoFocus
|
||||||
variant="contained"
|
variant="contained"
|
||||||
>
|
>
|
||||||
{isSubmitting ? "در حال ویرایش کاربر..." : "ویرایش کاربر"}
|
{isSubmitting ? "در حال ویرایش کاربر..." : "ویرایش کاربر"}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default UpdateUserForm;
|
export default UpdateUserForm;
|
||||||
|
|||||||
@@ -5,28 +5,23 @@ import { useState } from "react";
|
|||||||
import UpdateUserForm from "./Form";
|
import UpdateUserForm from "./Form";
|
||||||
|
|
||||||
const UpdateUser = ({ values, mutate }) => {
|
const UpdateUser = ({ values, mutate }) => {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
const handleOpen = () => {
|
const handleOpen = () => {
|
||||||
setOpen(true);
|
setOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Tooltip title="ویرایش">
|
<Tooltip title="ویرایش">
|
||||||
<IconButton onClick={handleOpen}>
|
<IconButton onClick={handleOpen}>
|
||||||
<Edit />
|
<Edit />
|
||||||
</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}
|
</Dialog>
|
||||||
setOpen={setOpen}
|
</>
|
||||||
mutate={mutate}
|
);
|
||||||
values={values}
|
|
||||||
/>
|
|
||||||
</Dialog>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
export default UpdateUser;
|
export default UpdateUser;
|
||||||
|
|||||||
@@ -3,15 +3,11 @@ import { Power, PowerOff } from "@mui/icons-material";
|
|||||||
import { Stack } from "@mui/material";
|
import { Stack } from "@mui/material";
|
||||||
|
|
||||||
const OnlineCell = ({ user_id }) => {
|
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" />
|
</Stack>
|
||||||
) : (
|
);
|
||||||
<PowerOff color="error" />
|
|
||||||
)}
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
export default OnlineCell;
|
export default OnlineCell;
|
||||||
|
|||||||
@@ -2,10 +2,8 @@ import { useSocket } from "@/lib/contexts/socket";
|
|||||||
import { Stack } from "@mui/material";
|
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,
|
return <Stack alignItems={"center"}>{telephone_id}</Stack>;
|
||||||
)?.telephone_id;
|
|
||||||
return <Stack alignItems={"center"}>{telephone_id}</Stack>;
|
|
||||||
};
|
};
|
||||||
export default TelephoneId;
|
export default TelephoneId;
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ import { Box } from "@mui/material";
|
|||||||
import Delete from "./Delete";
|
import Delete from "./Delete";
|
||||||
import UpdateUser from "./Edit";
|
import UpdateUser from "./Edit";
|
||||||
const RowActions = ({ row, mutate }) => {
|
const RowActions = ({ row, mutate }) => {
|
||||||
return (
|
return (
|
||||||
<Box sx={{ display: "flex", gap: 1, alignItems: "center" }}>
|
<Box sx={{ display: "flex", gap: 1, alignItems: "center" }}>
|
||||||
<Delete rowId={row.original.id} mutate={mutate} />
|
<Delete rowId={row.original.id} mutate={mutate} />
|
||||||
<UpdateUser values={row.original} mutate={mutate} />
|
<UpdateUser values={row.original} mutate={mutate} />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default RowActions;
|
export default RowActions;
|
||||||
|
|||||||
@@ -4,22 +4,22 @@ import validateNationalCode from "@/core/utils/nationalCodeValidation";
|
|||||||
import useProvinces from "@/lib/hooks/useProvince";
|
import useProvinces from "@/lib/hooks/useProvince";
|
||||||
import { yupResolver } from "@hookform/resolvers/yup";
|
import { yupResolver } from "@hookform/resolvers/yup";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Chip,
|
Chip,
|
||||||
DialogActions,
|
DialogActions,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
Divider,
|
Divider,
|
||||||
FormControl,
|
FormControl,
|
||||||
FormHelperText,
|
FormHelperText,
|
||||||
Grid,
|
Grid,
|
||||||
IconButton,
|
IconButton,
|
||||||
InputAdornment,
|
InputAdornment,
|
||||||
InputLabel,
|
InputLabel,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
OutlinedInput,
|
OutlinedInput,
|
||||||
Select,
|
Select,
|
||||||
Stack,
|
Stack,
|
||||||
TextField,
|
TextField,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
import { object, string } from "yup";
|
import { object, string } from "yup";
|
||||||
@@ -30,387 +30,375 @@ import { CREATE_USER } from "@/core/utils/routes";
|
|||||||
import { Visibility, VisibilityOff } from "@mui/icons-material";
|
import { Visibility, VisibilityOff } from "@mui/icons-material";
|
||||||
|
|
||||||
const defaultValues = {
|
const defaultValues = {
|
||||||
full_name: "",
|
full_name: "",
|
||||||
username: "",
|
username: "",
|
||||||
position: "",
|
position: "",
|
||||||
gender: "male",
|
gender: "male",
|
||||||
national_id: "",
|
national_id: "",
|
||||||
phone_number: "",
|
phone_number: "",
|
||||||
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,
|
password: string()
|
||||||
)
|
.required("رمز عبور خود را وارد کنید")
|
||||||
.test("validation", "کد ملی صحیح نمی باشد", (value) =>
|
.matches(/^(?=.*[a-zA-Z])(?=.*\d).{8,}$/, "رمز عبور باید حداقل 8 کاراکتر و متشکل از عدد، حرف یا سمبل باشد"),
|
||||||
validateNationalCode(value),
|
position: string().required("سمت خود را وارد کنید"),
|
||||||
)
|
province_id: string().required("استان خود را وارد کنید"),
|
||||||
.required("کد ملی خود را وارد کنید"),
|
role_id: string().required("نقش خود را وارد کنید"),
|
||||||
password: string()
|
|
||||||
.required("رمز عبور خود را وارد کنید")
|
|
||||||
.matches(
|
|
||||||
/^(?=.*[a-zA-Z])(?=.*\d).{8,}$/,
|
|
||||||
"رمز عبور باید حداقل 8 کاراکتر و متشکل از عدد، حرف یا سمبل باشد",
|
|
||||||
),
|
|
||||||
position: string().required("سمت خود را وارد کنید"),
|
|
||||||
province_id: string().required("استان خود را وارد کنید"),
|
|
||||||
role_id: string().required("نقش خود را وارد کنید"),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const CreateUserForm = ({ setOpen, mutate }) => {
|
const CreateUserForm = ({ setOpen, mutate }) => {
|
||||||
const requestServer = useRequest({ notificationSuccess: true });
|
const requestServer = useRequest({ notificationSuccess: true });
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
const { provinces, errorProvinces, loadingProvinces } = useProvinces();
|
const { provinces, errorProvinces, loadingProvinces } = useProvinces();
|
||||||
const { roles, errorRoles, loadingRoles } = useRoles();
|
const { roles, errorRoles, loadingRoles } = useRoles();
|
||||||
|
|
||||||
const handleClickShowPassword = () => setShowPassword((show) => !show);
|
const handleClickShowPassword = () => setShowPassword((show) => !show);
|
||||||
|
|
||||||
const provinceSelectOptions = useMemo(() => {
|
const provinceSelectOptions = useMemo(() => {
|
||||||
if (loadingProvinces) {
|
if (loadingProvinces) {
|
||||||
return [{ value: "loading", label: "در حال بارگذاری..." }];
|
return [{ value: "loading", label: "در حال بارگذاری..." }];
|
||||||
}
|
}
|
||||||
if (errorProvinces) {
|
if (errorProvinces) {
|
||||||
return [{ value: "error", label: "خطا در بارگذاری" }];
|
return [{ value: "error", label: "خطا در بارگذاری" }];
|
||||||
}
|
}
|
||||||
return [
|
return [
|
||||||
{ value: "", label: "انتخاب استان" },
|
{ value: "", label: "انتخاب استان" },
|
||||||
...provinces.map((province) => ({
|
...provinces.map((province) => ({
|
||||||
value: province.id,
|
value: province.id,
|
||||||
label: province.name,
|
label: province.name,
|
||||||
})),
|
})),
|
||||||
];
|
];
|
||||||
}, [provinces, errorProvinces, loadingProvinces]);
|
}, [provinces, errorProvinces, loadingProvinces]);
|
||||||
|
|
||||||
const roleSelectOptions = useMemo(() => {
|
const roleSelectOptions = useMemo(() => {
|
||||||
if (loadingRoles) {
|
if (loadingRoles) {
|
||||||
return [{ value: "loading", label: "در حال بارگذاری..." }];
|
return [{ value: "loading", label: "در حال بارگذاری..." }];
|
||||||
}
|
}
|
||||||
if (errorRoles) {
|
if (errorRoles) {
|
||||||
return [{ value: "error", label: "خطا در بارگذاری" }];
|
return [{ value: "error", label: "خطا در بارگذاری" }];
|
||||||
}
|
}
|
||||||
return [
|
return [
|
||||||
{ value: "", label: "انتخاب نقش" },
|
{ value: "", label: "انتخاب نقش" },
|
||||||
...roles.map((role) => ({
|
...roles.map((role) => ({
|
||||||
value: role.id,
|
value: role.id,
|
||||||
label: role.name_fa,
|
label: role.name_fa,
|
||||||
})),
|
})),
|
||||||
];
|
];
|
||||||
}, [roles, errorRoles, loadingRoles]);
|
}, [roles, errorRoles, loadingRoles]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
control,
|
control,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
formState: { isSubmitting, errors },
|
formState: { isSubmitting, errors },
|
||||||
} = useForm({ defaultValues, resolver: yupResolver(validationSchema) });
|
} = useForm({ defaultValues, resolver: yupResolver(validationSchema) });
|
||||||
|
|
||||||
const onSubmit = async (data) => {
|
const onSubmit = async (data) => {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
Object.keys(data).forEach((key) => {
|
Object.keys(data).forEach((key) => {
|
||||||
formData.append(key, data[key]);
|
formData.append(key, data[key]);
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await requestServer(CREATE_USER, "post", {
|
await requestServer(CREATE_USER, "post", {
|
||||||
data: formData,
|
data: formData,
|
||||||
});
|
});
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
mutate();
|
mutate();
|
||||||
} catch (error) {}
|
} catch (error) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<DialogContent dividers>
|
<DialogContent dividers>
|
||||||
<Divider sx={{ mb: 3 }}>
|
<Divider sx={{ mb: 3 }}>
|
||||||
<Chip color="primary" variant="outlined" label="کاربر جدید" />
|
<Chip color="primary" variant="outlined" label="کاربر جدید" />
|
||||||
</Divider>
|
</Divider>
|
||||||
<StyledForm
|
<StyledForm
|
||||||
id="createUserForm"
|
id="createUserForm"
|
||||||
onSubmit={handleSubmit(onSubmit)}
|
onSubmit={handleSubmit(onSubmit)}
|
||||||
style={{ width: "100%", height: "100%" }}
|
style={{ width: "100%", height: "100%" }}
|
||||||
>
|
>
|
||||||
<Stack spacing={2}>
|
<Stack spacing={2}>
|
||||||
<Grid container spacing={2}>
|
<Grid container spacing={2}>
|
||||||
<Grid size={{ xs: 12, md: 6 }}>
|
<Grid size={{ xs: 12, md: 6 }}>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field, fieldState: { error } }) => {
|
render={({ field, fieldState: { error } }) => {
|
||||||
return (
|
return (
|
||||||
<LtrTextField
|
<LtrTextField
|
||||||
{...field}
|
{...field}
|
||||||
label="نام کاربری"
|
label="نام کاربری"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
fullWidth
|
fullWidth
|
||||||
error={!!error}
|
error={!!error}
|
||||||
helperText={!!error && error.message}
|
helperText={!!error && error.message}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
name={"username"}
|
name={"username"}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid size={{ xs: 12, md: 6 }}>
|
<Grid size={{ xs: 12, md: 6 }}>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field, fieldState: { error } }) => {
|
render={({ field, fieldState: { error } }) => {
|
||||||
return (
|
return (
|
||||||
<LtrTextField
|
<LtrTextField
|
||||||
{...field}
|
{...field}
|
||||||
label="رمز عبور"
|
label="رمز عبور"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
type={showPassword ? "text" : "password"}
|
type={showPassword ? "text" : "password"}
|
||||||
slotProps={{
|
slotProps={{
|
||||||
input: {
|
input: {
|
||||||
startAdornment: (
|
startAdornment: (
|
||||||
<InputAdornment position="start">
|
<InputAdornment position="start">
|
||||||
<IconButton onClick={handleClickShowPassword}>
|
<IconButton onClick={handleClickShowPassword}>
|
||||||
{showPassword ? (
|
{showPassword ? <VisibilityOff /> : <Visibility />}
|
||||||
<VisibilityOff />
|
</IconButton>
|
||||||
) : (
|
</InputAdornment>
|
||||||
<Visibility />
|
),
|
||||||
)}
|
},
|
||||||
</IconButton>
|
}}
|
||||||
</InputAdornment>
|
fullWidth
|
||||||
),
|
error={!!error}
|
||||||
},
|
helperText={!!error && error.message}
|
||||||
}}
|
/>
|
||||||
fullWidth
|
);
|
||||||
error={!!error}
|
}}
|
||||||
helperText={!!error && error.message}
|
name={"password"}
|
||||||
/>
|
/>
|
||||||
);
|
</Grid>
|
||||||
}}
|
<Grid size={{ xs: 12, md: 6 }}>
|
||||||
name={"password"}
|
<Controller
|
||||||
/>
|
control={control}
|
||||||
</Grid>
|
render={({ field, fieldState: { error } }) => {
|
||||||
<Grid size={{ xs: 12, md: 6 }}>
|
return (
|
||||||
<Controller
|
<TextField
|
||||||
control={control}
|
{...field}
|
||||||
render={({ field, fieldState: { error } }) => {
|
label="نام و نام خانوادگی"
|
||||||
return (
|
variant="outlined"
|
||||||
<TextField
|
fullWidth
|
||||||
{...field}
|
error={!!error}
|
||||||
label="نام و نام خانوادگی"
|
helperText={!!error && error.message}
|
||||||
variant="outlined"
|
/>
|
||||||
fullWidth
|
);
|
||||||
error={!!error}
|
}}
|
||||||
helperText={!!error && error.message}
|
name={"full_name"}
|
||||||
/>
|
/>
|
||||||
);
|
</Grid>
|
||||||
}}
|
<Grid size={{ xs: 12, md: 6 }}>
|
||||||
name={"full_name"}
|
<Controller
|
||||||
/>
|
control={control}
|
||||||
</Grid>
|
render={({ field, fieldState: { error } }) => {
|
||||||
<Grid size={{ xs: 12, md: 6 }}>
|
return (
|
||||||
<Controller
|
<TextField
|
||||||
control={control}
|
{...field}
|
||||||
render={({ field, fieldState: { error } }) => {
|
label="سمت"
|
||||||
return (
|
variant="outlined"
|
||||||
<TextField
|
fullWidth
|
||||||
{...field}
|
error={!!error}
|
||||||
label="سمت"
|
helperText={!!error && error.message}
|
||||||
variant="outlined"
|
/>
|
||||||
fullWidth
|
);
|
||||||
error={!!error}
|
}}
|
||||||
helperText={!!error && error.message}
|
name={"position"}
|
||||||
/>
|
/>
|
||||||
);
|
</Grid>
|
||||||
}}
|
<Grid size={{ xs: 12, md: 6 }}>
|
||||||
name={"position"}
|
<Controller
|
||||||
/>
|
control={control}
|
||||||
</Grid>
|
render={({ field, fieldState: { error } }) => {
|
||||||
<Grid size={{ xs: 12, md: 6 }}>
|
return (
|
||||||
<Controller
|
<LtrTextField
|
||||||
control={control}
|
{...field}
|
||||||
render={({ field, fieldState: { error } }) => {
|
label="شماره همراه"
|
||||||
return (
|
variant="outlined"
|
||||||
<LtrTextField
|
fullWidth
|
||||||
{...field}
|
type="tel"
|
||||||
label="شماره همراه"
|
onChange={(e) => {
|
||||||
variant="outlined"
|
const inputValue = event.target.value;
|
||||||
fullWidth
|
if (isNaN(Number(inputValue))) {
|
||||||
type="tel"
|
return;
|
||||||
onChange={(e) => {
|
}
|
||||||
const inputValue = event.target.value;
|
if (inputValue.length > 11) {
|
||||||
if (isNaN(Number(inputValue))) {
|
return;
|
||||||
return;
|
}
|
||||||
}
|
field.onChange(inputValue);
|
||||||
if (inputValue.length > 11) {
|
}}
|
||||||
return;
|
error={!!error}
|
||||||
}
|
helperText={!!error && error.message}
|
||||||
field.onChange(inputValue);
|
/>
|
||||||
}}
|
);
|
||||||
error={!!error}
|
}}
|
||||||
helperText={!!error && error.message}
|
name={"phone_number"}
|
||||||
/>
|
/>
|
||||||
);
|
</Grid>
|
||||||
}}
|
<Grid size={{ xs: 12, md: 6 }}>
|
||||||
name={"phone_number"}
|
<Controller
|
||||||
/>
|
control={control}
|
||||||
</Grid>
|
render={({ field, fieldState: { error } }) => {
|
||||||
<Grid size={{ xs: 12, md: 6 }}>
|
return (
|
||||||
<Controller
|
<FormControl fullWidth error={!!error}>
|
||||||
control={control}
|
<InputLabel id={`label-phone-number`} shrink>
|
||||||
render={({ field, fieldState: { error } }) => {
|
جنسیت
|
||||||
return (
|
</InputLabel>
|
||||||
<FormControl fullWidth error={!!error}>
|
<Select
|
||||||
<InputLabel id={`label-phone-number`} shrink>
|
labelId={`label-phone-number`}
|
||||||
جنسیت
|
id={"phone-number"}
|
||||||
</InputLabel>
|
name={`gender`}
|
||||||
<Select
|
value={field.value}
|
||||||
labelId={`label-phone-number`}
|
input={<OutlinedInput label={"جنسیت"} />}
|
||||||
id={"phone-number"}
|
onChange={(e) => field.onChange(e.target.value)}
|
||||||
name={`gender`}
|
displayEmpty
|
||||||
value={field.value}
|
>
|
||||||
input={<OutlinedInput label={"جنسیت"} />}
|
<MenuItem value={"male"}>مرد</MenuItem>
|
||||||
onChange={(e) => field.onChange(e.target.value)}
|
<MenuItem value={"female"}>زن</MenuItem>
|
||||||
displayEmpty
|
</Select>
|
||||||
>
|
<FormHelperText error={!!error} sx={{ mt: 1 }}>
|
||||||
<MenuItem value={"male"}>مرد</MenuItem>
|
{!!error && error.message}
|
||||||
<MenuItem value={"female"}>زن</MenuItem>
|
</FormHelperText>
|
||||||
</Select>
|
</FormControl>
|
||||||
<FormHelperText error={!!error} sx={{ mt: 1 }}>
|
);
|
||||||
{!!error && error.message}
|
}}
|
||||||
</FormHelperText>
|
name={"gender"}
|
||||||
</FormControl>
|
/>
|
||||||
);
|
</Grid>
|
||||||
}}
|
<Grid size={{ xs: 12, md: 6 }}>
|
||||||
name={"gender"}
|
<Controller
|
||||||
/>
|
control={control}
|
||||||
</Grid>
|
render={({ field, fieldState: { error } }) => {
|
||||||
<Grid size={{ xs: 12, md: 6 }}>
|
return (
|
||||||
<Controller
|
<LtrTextField
|
||||||
control={control}
|
{...field}
|
||||||
render={({ field, fieldState: { error } }) => {
|
label="شماره ملی"
|
||||||
return (
|
variant="outlined"
|
||||||
<LtrTextField
|
fullWidth
|
||||||
{...field}
|
type="tel"
|
||||||
label="شماره ملی"
|
onChange={(e) => {
|
||||||
variant="outlined"
|
const inputValue = event.target.value;
|
||||||
fullWidth
|
if (isNaN(Number(inputValue))) {
|
||||||
type="tel"
|
return;
|
||||||
onChange={(e) => {
|
}
|
||||||
const inputValue = event.target.value;
|
if (inputValue.length > 10) {
|
||||||
if (isNaN(Number(inputValue))) {
|
return;
|
||||||
return;
|
}
|
||||||
}
|
field.onChange(inputValue);
|
||||||
if (inputValue.length > 10) {
|
}}
|
||||||
return;
|
error={!!error}
|
||||||
}
|
helperText={!!error && error.message}
|
||||||
field.onChange(inputValue);
|
/>
|
||||||
}}
|
);
|
||||||
error={!!error}
|
}}
|
||||||
helperText={!!error && error.message}
|
name={"national_id"}
|
||||||
/>
|
/>
|
||||||
);
|
</Grid>
|
||||||
}}
|
<Grid size={{ xs: 12, md: 6 }}>
|
||||||
name={"national_id"}
|
<Controller
|
||||||
/>
|
control={control}
|
||||||
</Grid>
|
render={({ field, fieldState: { error } }) => {
|
||||||
<Grid size={{ xs: 12, md: 6 }}>
|
return (
|
||||||
<Controller
|
<FormControl fullWidth error={!!error}>
|
||||||
control={control}
|
<InputLabel id={`label-province`} shrink>
|
||||||
render={({ field, fieldState: { error } }) => {
|
استان
|
||||||
return (
|
</InputLabel>
|
||||||
<FormControl fullWidth error={!!error}>
|
<Select
|
||||||
<InputLabel id={`label-province`} shrink>
|
labelId={`label-province`}
|
||||||
استان
|
id={"province"}
|
||||||
</InputLabel>
|
name={`province_id`}
|
||||||
<Select
|
value={field.value}
|
||||||
labelId={`label-province`}
|
input={<OutlinedInput label={"استان"} />}
|
||||||
id={"province"}
|
onChange={(e) => field.onChange(e.target.value)}
|
||||||
name={`province_id`}
|
displayEmpty
|
||||||
value={field.value}
|
>
|
||||||
input={<OutlinedInput label={"استان"} />}
|
{provinceSelectOptions.map((option) => (
|
||||||
onChange={(e) => field.onChange(e.target.value)}
|
<MenuItem key={option.value} value={option.value}>
|
||||||
displayEmpty
|
{option.label}
|
||||||
>
|
</MenuItem>
|
||||||
{provinceSelectOptions.map((option) => (
|
))}
|
||||||
<MenuItem key={option.value} value={option.value}>
|
</Select>
|
||||||
{option.label}
|
<FormHelperText error={!!error} sx={{ mt: 1 }}>
|
||||||
</MenuItem>
|
{!!error && error.message}
|
||||||
))}
|
</FormHelperText>
|
||||||
</Select>
|
</FormControl>
|
||||||
<FormHelperText error={!!error} sx={{ mt: 1 }}>
|
);
|
||||||
{!!error && error.message}
|
}}
|
||||||
</FormHelperText>
|
name={"province_id"}
|
||||||
</FormControl>
|
/>
|
||||||
);
|
</Grid>
|
||||||
}}
|
<Grid size={{ xs: 12, md: 6 }}>
|
||||||
name={"province_id"}
|
<Controller
|
||||||
/>
|
control={control}
|
||||||
</Grid>
|
render={({ field, fieldState: { error } }) => {
|
||||||
<Grid size={{ xs: 12, md: 6 }}>
|
return (
|
||||||
<Controller
|
<FormControl fullWidth error={!!error}>
|
||||||
control={control}
|
<InputLabel id={`label-role`} shrink>
|
||||||
render={({ field, fieldState: { error } }) => {
|
نقش
|
||||||
return (
|
</InputLabel>
|
||||||
<FormControl fullWidth error={!!error}>
|
<Select
|
||||||
<InputLabel id={`label-role`} shrink>
|
labelId={`label-role`}
|
||||||
نقش
|
id={"role"}
|
||||||
</InputLabel>
|
name={`role_id`}
|
||||||
<Select
|
value={field.value}
|
||||||
labelId={`label-role`}
|
input={<OutlinedInput label={"نقش"} />}
|
||||||
id={"role"}
|
onChange={(e) => field.onChange(e.target.value)}
|
||||||
name={`role_id`}
|
displayEmpty
|
||||||
value={field.value}
|
>
|
||||||
input={<OutlinedInput label={"نقش"} />}
|
{roleSelectOptions.map((option) => (
|
||||||
onChange={(e) => field.onChange(e.target.value)}
|
<MenuItem key={option.value} value={option.value}>
|
||||||
displayEmpty
|
{option.label}
|
||||||
>
|
</MenuItem>
|
||||||
{roleSelectOptions.map((option) => (
|
))}
|
||||||
<MenuItem key={option.value} value={option.value}>
|
</Select>
|
||||||
{option.label}
|
<FormHelperText error={!!error} sx={{ mt: 1 }}>
|
||||||
</MenuItem>
|
{!!error && error.message}
|
||||||
))}
|
</FormHelperText>
|
||||||
</Select>
|
</FormControl>
|
||||||
<FormHelperText error={!!error} sx={{ mt: 1 }}>
|
);
|
||||||
{!!error && error.message}
|
}}
|
||||||
</FormHelperText>
|
name={"role_id"}
|
||||||
</FormControl>
|
/>
|
||||||
);
|
</Grid>
|
||||||
}}
|
</Grid>
|
||||||
name={"role_id"}
|
</Stack>
|
||||||
/>
|
</StyledForm>
|
||||||
</Grid>
|
</DialogContent>
|
||||||
</Grid>
|
<DialogActions>
|
||||||
</Stack>
|
<Button
|
||||||
</StyledForm>
|
disabled={isSubmitting}
|
||||||
</DialogContent>
|
variant="outlined"
|
||||||
<DialogActions>
|
color="secondary"
|
||||||
<Button
|
size="large"
|
||||||
disabled={isSubmitting}
|
onClick={() => setOpen(false)}
|
||||||
variant="outlined"
|
>
|
||||||
color="secondary"
|
بستن
|
||||||
size="large"
|
</Button>
|
||||||
onClick={() => setOpen(false)}
|
<Button
|
||||||
>
|
type="submit"
|
||||||
بستن
|
disabled={isSubmitting}
|
||||||
</Button>
|
form="createUserForm"
|
||||||
<Button
|
size="large"
|
||||||
type="submit"
|
autoFocus
|
||||||
disabled={isSubmitting}
|
variant="contained"
|
||||||
form="createUserForm"
|
>
|
||||||
size="large"
|
{isSubmitting ? "در حال ثبت کاربر جدید..." : "ثبت کاربر جدید"}
|
||||||
autoFocus
|
</Button>
|
||||||
variant="contained"
|
</DialogActions>
|
||||||
>
|
</>
|
||||||
{isSubmitting ? "در حال ثبت کاربر جدید..." : "ثبت کاربر جدید"}
|
);
|
||||||
</Button>
|
|
||||||
</DialogActions>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default CreateUserForm;
|
export default CreateUserForm;
|
||||||
|
|||||||
@@ -1,49 +1,39 @@
|
|||||||
"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";
|
||||||
|
|
||||||
const CreateUser = ({ mutate }) => {
|
const CreateUser = ({ mutate }) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
const handleOpen = () => {
|
const handleOpen = () => {
|
||||||
setOpen(true);
|
setOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{isMobile ? (
|
{isMobile ? (
|
||||||
<IconButton
|
<IconButton aria-label="کاربر جدید" color="primary" onClick={handleOpen}>
|
||||||
aria-label="کاربر جدید"
|
<AddCircle sx={{ fontSize: "25px" }} />
|
||||||
color="primary"
|
</IconButton>
|
||||||
onClick={handleOpen}
|
) : (
|
||||||
>
|
<Button
|
||||||
<AddCircle sx={{ fontSize: "25px" }} />
|
size={"small"}
|
||||||
</IconButton>
|
variant="contained"
|
||||||
) : (
|
color="primary"
|
||||||
<Button
|
startIcon={<AddCircle />}
|
||||||
size={"small"}
|
onClick={handleOpen}
|
||||||
variant="contained"
|
>
|
||||||
color="primary"
|
کاربر جدید
|
||||||
startIcon={<AddCircle />}
|
</Button>
|
||||||
onClick={handleOpen}
|
)}
|
||||||
>
|
<Dialog maxWidth="sm" fullWidth={true} open={open}>
|
||||||
کاربر جدید
|
<CreateUserForm open={open} setOpen={setOpen} mutate={mutate} />
|
||||||
</Button>
|
</Dialog>
|
||||||
)}
|
</>
|
||||||
<Dialog maxWidth="sm" fullWidth={true} open={open}>
|
);
|
||||||
<CreateUserForm open={open} setOpen={setOpen} mutate={mutate} />
|
|
||||||
</Dialog>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
export default CreateUser;
|
export default CreateUser;
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import CreateUser from "./Create";
|
import CreateUser from "./Create";
|
||||||
|
|
||||||
const Toolbar = ({ mutate }) => {
|
const Toolbar = ({ mutate }) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<CreateUser mutate={mutate} />
|
<CreateUser mutate={mutate} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default Toolbar;
|
export default Toolbar;
|
||||||
|
|||||||
@@ -3,12 +3,12 @@ import { Stack } from "@mui/material";
|
|||||||
import DataTable from "./DataTable";
|
import DataTable from "./DataTable";
|
||||||
|
|
||||||
const UsersPage = () => {
|
const UsersPage = () => {
|
||||||
return (
|
return (
|
||||||
<Stack spacing={1}>
|
<Stack spacing={1}>
|
||||||
<PageTitle title={"کاربران"} />
|
<PageTitle title={"کاربران"} />
|
||||||
<DataTable />
|
<DataTable />
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default UsersPage;
|
export default UsersPage;
|
||||||
|
|||||||
@@ -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}
|
export default OnlineUsersReport;
|
||||||
</Stack>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
export default OnlineUsersReport;
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
"use client"
|
"use client";
|
||||||
import { Stack } from "@mui/material";
|
import { Stack } from "@mui/material";
|
||||||
import OnlineUsersReport from "./OnlineUsersReport";
|
import OnlineUsersReport from "./OnlineUsersReport";
|
||||||
|
|
||||||
@@ -7,6 +7,6 @@ const DashboardPage = () => {
|
|||||||
<Stack>
|
<Stack>
|
||||||
<OnlineUsersReport />
|
<OnlineUsersReport />
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
export default DashboardPage;
|
export default DashboardPage;
|
||||||
|
|||||||
@@ -1,60 +1,45 @@
|
|||||||
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";
|
||||||
|
|
||||||
const HeaderMenu = ({ menu }) => {
|
const HeaderMenu = ({ menu }) => {
|
||||||
const [anchorEl, setAnchorEl] = useState(null);
|
const [anchorEl, setAnchorEl] = useState(null);
|
||||||
const open = Boolean(anchorEl);
|
const open = Boolean(anchorEl);
|
||||||
|
|
||||||
const handleClick = (event) => {
|
const handleClick = (event) => {
|
||||||
setAnchorEl(event.currentTarget);
|
setAnchorEl(event.currentTarget);
|
||||||
};
|
};
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
setAnchorEl(null);
|
setAnchorEl(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack sx={{ mr: 0.5 }}>
|
<Stack sx={{ mr: 0.5 }}>
|
||||||
<Button
|
<Button
|
||||||
color="inherit"
|
color="inherit"
|
||||||
aria-haspopup="true"
|
aria-haspopup="true"
|
||||||
aria-expanded={open ? "true" : undefined}
|
aria-expanded={open ? "true" : undefined}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
endIcon={open ? <ExpandLess /> : <ExpandMore />}
|
endIcon={open ? <ExpandLess /> : <ExpandMore />}
|
||||||
>
|
|
||||||
{menu.title}
|
|
||||||
</Button>
|
|
||||||
<Menu anchorEl={anchorEl} open={open} onClose={handleClose}>
|
|
||||||
{menu.subMenu.flatMap((subMenu, index) => [
|
|
||||||
...subMenu.map((sub) => (
|
|
||||||
<MenuItem
|
|
||||||
component={NextLink}
|
|
||||||
href={sub.href}
|
|
||||||
key={sub.title}
|
|
||||||
onClick={handleClose}
|
|
||||||
>
|
>
|
||||||
<ListItemIcon>
|
{menu.title}
|
||||||
<Link fontSize="small" />
|
</Button>
|
||||||
</ListItemIcon>
|
<Menu anchorEl={anchorEl} open={open} onClose={handleClose}>
|
||||||
<ListItemText>{sub.title}</ListItemText>
|
{menu.subMenu.flatMap((subMenu, index) => [
|
||||||
</MenuItem>
|
...subMenu.map((sub) => (
|
||||||
)),
|
<MenuItem component={NextLink} href={sub.href} key={sub.title} onClick={handleClose}>
|
||||||
index < menu.subMenu.length - 1 && (
|
<ListItemIcon>
|
||||||
<Divider key={`divider-${index}`} />
|
<Link fontSize="small" />
|
||||||
),
|
</ListItemIcon>
|
||||||
])}
|
<ListItemText>{sub.title}</ListItemText>
|
||||||
</Menu>
|
</MenuItem>
|
||||||
</Stack>
|
)),
|
||||||
);
|
index < menu.subMenu.length - 1 && <Divider key={`divider-${index}`} />,
|
||||||
|
])}
|
||||||
|
</Menu>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default HeaderMenu;
|
export default HeaderMenu;
|
||||||
|
|||||||
@@ -4,16 +4,16 @@ import { Chip } from "@mui/material";
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
const SidebarBadge = ({ badge, chipProps = {} }) => {
|
const SidebarBadge = ({ badge, chipProps = {} }) => {
|
||||||
const { data } = useSidebarBadge();
|
const { data } = useSidebarBadge();
|
||||||
const [value, setValue] = useState();
|
const [value, setValue] = useState();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setValue(getValueByPath(data, badge));
|
setValue(getValueByPath(data, badge));
|
||||||
}, [data, badge]);
|
}, [data, badge]);
|
||||||
|
|
||||||
if (!data) return null;
|
if (!data) return null;
|
||||||
if (!value) return null;
|
if (!value) return null;
|
||||||
|
|
||||||
return <Chip label={value.toLocaleString()} size="small" {...chipProps} />;
|
return <Chip label={value.toLocaleString()} size="small" {...chipProps} />;
|
||||||
};
|
};
|
||||||
export default SidebarBadge;
|
export default SidebarBadge;
|
||||||
|
|||||||
@@ -1,92 +1,67 @@
|
|||||||
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";
|
||||||
|
|
||||||
const SidebarListItems = ({ menuItem, dispatch }) => {
|
const SidebarListItems = ({ menuItem, dispatch }) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ListItem disablePadding divider>
|
<ListItem disablePadding divider>
|
||||||
<ListItemButton
|
<ListItemButton
|
||||||
sx={{ p: 0.5 }}
|
sx={{ p: 0.5 }}
|
||||||
disableGutters
|
disableGutters
|
||||||
selected={menuItem.selected}
|
selected={menuItem.selected}
|
||||||
component={menuItem.type === "page" ? Link : null}
|
component={menuItem.type === "page" ? Link : null}
|
||||||
href={menuItem.type === "page" ? menuItem.route : null}
|
href={menuItem.type === "page" ? menuItem.route : null}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (menuItem.type !== "page") {
|
if (menuItem.type !== "page") {
|
||||||
dispatch({ type: "COLLAPSE_MENU", id: menuItem.id });
|
dispatch({ type: "COLLAPSE_MENU", id: menuItem.id });
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ListItemIcon
|
<ListItemIcon
|
||||||
sx={{
|
sx={{
|
||||||
minWidth: 0,
|
minWidth: 0,
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
color: "primary.main",
|
color: "primary.main",
|
||||||
width: 40,
|
width: 40,
|
||||||
height: 24,
|
height: 24,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{menuItem.icon}
|
{menuItem.icon}
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
<ListItemText primary={menuItem.label} />
|
<ListItemText primary={menuItem.label} />
|
||||||
{menuItem.badges && (
|
{menuItem.badges && (
|
||||||
<Stack direction={"row"} sx={{ px: 0.5 }} spacing={0.5}>
|
<Stack direction={"row"} sx={{ px: 0.5 }} spacing={0.5}>
|
||||||
{menuItem.badges.map((badge, i) => (
|
{menuItem.badges.map((badge, i) => (
|
||||||
<SidebarBadge
|
<SidebarBadge
|
||||||
chipProps={{ color: badge.color || "primary" }}
|
chipProps={{ color: badge.color || "primary" }}
|
||||||
key={badge.key}
|
key={badge.key}
|
||||||
badge={badge.key}
|
badge={badge.key}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
{menuItem.hasSubitems ? (
|
{menuItem.hasSubitems ? menuItem.showSubitems ? <ExpandLess /> : <ExpandMore /> : null}
|
||||||
menuItem.showSubitems ? (
|
</ListItemButton>
|
||||||
<ExpandLess />
|
</ListItem>
|
||||||
) : (
|
<Collapse in={menuItem.showSubitems} timeout="auto" mountOnEnter={true} unmountOnExit={true}>
|
||||||
<ExpandMore />
|
{menuItem.hasSubitems ? (
|
||||||
)
|
<List
|
||||||
) : null}
|
disablePadding
|
||||||
</ListItemButton>
|
dense
|
||||||
</ListItem>
|
sx={{
|
||||||
<Collapse
|
background: "#00000008",
|
||||||
in={menuItem.showSubitems}
|
}}
|
||||||
timeout="auto"
|
>
|
||||||
mountOnEnter={true}
|
{menuItem.Subitems.map((subitem, index) => {
|
||||||
unmountOnExit={true}
|
return <SidebarSubitems key={index} dispatch={dispatch} subitem={subitem} />;
|
||||||
>
|
})}
|
||||||
{menuItem.hasSubitems ? (
|
</List>
|
||||||
<List
|
) : null}
|
||||||
disablePadding
|
</Collapse>
|
||||||
dense
|
</>
|
||||||
sx={{
|
);
|
||||||
background: "#00000008",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{menuItem.Subitems.map((subitem, index) => {
|
|
||||||
return (
|
|
||||||
<SidebarSubitems
|
|
||||||
key={index}
|
|
||||||
dispatch={dispatch}
|
|
||||||
subitem={subitem}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</List>
|
|
||||||
) : null}
|
|
||||||
</Collapse>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
export default SidebarListItems;
|
export default SidebarListItems;
|
||||||
|
|||||||
@@ -9,120 +9,106 @@ import SidebarListItems from "./SidebarListItems";
|
|||||||
import Profile from "@/core/components/Profile";
|
import Profile from "@/core/components/Profile";
|
||||||
|
|
||||||
function selectPage(item, route) {
|
function selectPage(item, route) {
|
||||||
if (item.type === "page") {
|
if (item.type === "page") {
|
||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
selected: item.route === route,
|
selected: item.route === 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 {
|
||||||
);
|
...item,
|
||||||
return {
|
Subitems: updatedSubitems,
|
||||||
...item,
|
showSubitems: updatedSubitems.some((subitem) => subitem.showSubitems || subitem.route === route),
|
||||||
Subitems: updatedSubitems,
|
};
|
||||||
showSubitems: updatedSubitems.some(
|
}
|
||||||
(subitem) => subitem.showSubitems || subitem.route === route,
|
return item;
|
||||||
),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return item;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 },
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function reducer(state, action) {
|
function reducer(state, action) {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case "UPDATE_MENU":
|
case "UPDATE_MENU":
|
||||||
const _permissions = action.permissions || [];
|
const _permissions = action.permissions || [];
|
||||||
return filterMenuItems(state, ["all", ..._permissions]);
|
return filterMenuItems(state, ["all", ..._permissions]);
|
||||||
|
|
||||||
case "COLLAPSE_MENU":
|
case "COLLAPSE_MENU":
|
||||||
return state.map((item) => ({
|
return state.map((item) => ({
|
||||||
...item,
|
...item,
|
||||||
showSubitems: item.id === action.id ? !item.showSubitems : false,
|
showSubitems: item.id === action.id ? !item.showSubitems : false,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
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":
|
||||||
return state.map((item) =>
|
return state.map((item) =>
|
||||||
item.hasSubitems
|
item.hasSubitems
|
||||||
? {
|
? {
|
||||||
...item,
|
...item,
|
||||||
Subitems: item.Subitems.map((subitem) =>
|
Subitems: item.Subitems.map((subitem) =>
|
||||||
subitem.hasSubitems
|
subitem.hasSubitems
|
||||||
? {
|
? {
|
||||||
...subitem,
|
...subitem,
|
||||||
Subitems: toggleSubitems(subitem.Subitems, action.id),
|
Subitems: toggleSubitems(subitem.Subitems, action.id),
|
||||||
}
|
}
|
||||||
: subitem,
|
: subitem
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
: item,
|
: item
|
||||||
);
|
);
|
||||||
|
|
||||||
case "SELECTED":
|
case "SELECTED":
|
||||||
return state.map((item) => selectPage(item, action.route));
|
return state.map((item) => selectPage(item, action.route));
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new Error();
|
throw new Error();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const SidebarMenu = () => {
|
const SidebarMenu = () => {
|
||||||
const { data: userPermissions } = usePermissions();
|
const { data: userPermissions } = usePermissions();
|
||||||
const [menuItems, dispatch] = useReducer(reducer, pageMenu);
|
const [menuItems, dispatch] = useReducer(reducer, pageMenu);
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
const [selectedKey, setSelectedKey] = useState(null);
|
const [selectedKey, setSelectedKey] = useState(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch({ type: "SELECTED", route: pathname });
|
dispatch({ type: "SELECTED", route: pathname });
|
||||||
setSelectedKey(pathname);
|
setSelectedKey(pathname);
|
||||||
}, [userPermissions, pathname]);
|
}, [userPermissions, pathname]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!userPermissions) return;
|
if (!userPermissions) return;
|
||||||
dispatch({ type: "UPDATE_MENU", permissions: userPermissions });
|
dispatch({ type: "UPDATE_MENU", permissions: userPermissions });
|
||||||
}, [userPermissions]);
|
}, [userPermissions]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
selectedKey &&
|
selectedKey &&
|
||||||
document.querySelector(`[data-route="${selectedKey}"]`)?.scrollIntoView({
|
document.querySelector(`[data-route="${selectedKey}"]`)?.scrollIntoView({
|
||||||
behavior: "smooth",
|
behavior: "smooth",
|
||||||
block: "center",
|
block: "center",
|
||||||
});
|
});
|
||||||
}, [selectedKey]);
|
}, [selectedKey]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Profile />
|
<Profile />
|
||||||
{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}
|
</List>
|
||||||
key={menuItem.id}
|
)}
|
||||||
menuItem={menuItem}
|
</>
|
||||||
/>
|
);
|
||||||
);
|
|
||||||
})}
|
|
||||||
</List>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
export default SidebarMenu;
|
export default SidebarMenu;
|
||||||
|
|||||||
@@ -1,96 +1,76 @@
|
|||||||
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";
|
||||||
|
|
||||||
const SidebarSubitems = ({ subitem, dispatch }) => {
|
const SidebarSubitems = ({ subitem, dispatch }) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ListItem disablePadding divider>
|
<ListItem disablePadding divider>
|
||||||
<ListItemButton
|
<ListItemButton
|
||||||
disableGutters
|
disableGutters
|
||||||
sx={{ p: 0.5, pl: 1 }}
|
sx={{ p: 0.5, pl: 1 }}
|
||||||
selected={subitem.selected}
|
selected={subitem.selected}
|
||||||
component={subitem.type === "page" ? Link : null}
|
component={subitem.type === "page" ? Link : null}
|
||||||
href={subitem.type === "page" ? subitem.route : null}
|
href={subitem.type === "page" ? subitem.route : null}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (subitem.type !== "page") {
|
if (subitem.type !== "page") {
|
||||||
dispatch({ type: "COLLAPSE_SUB_ITEMS", id: subitem.id });
|
dispatch({ type: "COLLAPSE_SUB_ITEMS", id: subitem.id });
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ListItemIcon
|
<ListItemIcon
|
||||||
sx={{
|
sx={{
|
||||||
minWidth: 0,
|
minWidth: 0,
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
width: 40,
|
width: 40,
|
||||||
height: 24,
|
height: 24,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{subitem.icon}
|
{subitem.icon}
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
<ListItemText primary={subitem.label} />
|
<ListItemText primary={subitem.label} />
|
||||||
{subitem.badges && (
|
{subitem.badges && (
|
||||||
<Stack direction={"row"} sx={{ px: 0.5 }} spacing={0.5}>
|
<Stack direction={"row"} sx={{ px: 0.5 }} spacing={0.5}>
|
||||||
{subitem.badges.map((badge, i) => (
|
{subitem.badges.map((badge, i) => (
|
||||||
<SidebarBadge
|
<SidebarBadge
|
||||||
chipProps={{
|
chipProps={{
|
||||||
color: badge.color || "default",
|
color: badge.color || "default",
|
||||||
variant: "outlined",
|
variant: "outlined",
|
||||||
}}
|
}}
|
||||||
key={badge.key}
|
key={badge.key}
|
||||||
badge={badge.key}
|
badge={badge.key}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
{subitem.hasSubitems ? (
|
{subitem.hasSubitems ? (
|
||||||
subitem.showSubitems ? (
|
subitem.showSubitems ? (
|
||||||
<ExpandLess />
|
<ExpandLess />
|
||||||
) : (
|
) : (
|
||||||
<ExpandMore />
|
<ExpandMore />
|
||||||
)
|
)
|
||||||
) : (
|
) : (
|
||||||
<Box sx={{ width: 29 }} />
|
<Box sx={{ width: 29 }} />
|
||||||
)}
|
)}
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<Collapse
|
<Collapse in={subitem.showSubitems} timeout="auto" mountOnEnter={true} unmountOnExit={true}>
|
||||||
in={subitem.showSubitems}
|
{subitem.hasSubitems ? (
|
||||||
timeout="auto"
|
<List
|
||||||
mountOnEnter={true}
|
disablePadding
|
||||||
unmountOnExit={true}
|
dense
|
||||||
>
|
sx={{
|
||||||
{subitem.hasSubitems ? (
|
background: "#00000008",
|
||||||
<List
|
}}
|
||||||
disablePadding
|
>
|
||||||
dense
|
{subitem.Subitems.map((subSubitem, index) => {
|
||||||
sx={{
|
return <SidebarSubitems key={index} dispatch={dispatch} subitem={subSubitem} />;
|
||||||
background: "#00000008",
|
})}
|
||||||
}}
|
</List>
|
||||||
>
|
) : null}
|
||||||
{subitem.Subitems.map((subSubitem, index) => {
|
</Collapse>
|
||||||
return (
|
</>
|
||||||
<SidebarSubitems
|
);
|
||||||
key={index}
|
|
||||||
dispatch={dispatch}
|
|
||||||
subitem={subSubitem}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</List>
|
|
||||||
) : null}
|
|
||||||
</Collapse>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
export default SidebarSubitems;
|
export default SidebarSubitems;
|
||||||
|
|||||||
@@ -1,17 +1,7 @@
|
|||||||
"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";
|
||||||
@@ -21,170 +11,157 @@ import SidebarMenu from "./Sidebar/SidebarMenu";
|
|||||||
|
|
||||||
const drawerWidth = 300;
|
const drawerWidth = 300;
|
||||||
|
|
||||||
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", {
|
||||||
easing: theme.transitions.easing.sharp,
|
easing: theme.transitions.easing.sharp,
|
||||||
duration: theme.transitions.duration.leavingScreen,
|
duration: theme.transitions.duration.leavingScreen,
|
||||||
}),
|
}),
|
||||||
marginLeft: `-${drawerWidth}px`,
|
marginLeft: `-${drawerWidth}px`,
|
||||||
...(open && {
|
...(open && {
|
||||||
transition: theme.transitions.create("margin", {
|
transition: theme.transitions.create("margin", {
|
||||||
easing: theme.transitions.easing.easeOut,
|
easing: theme.transitions.easing.easeOut,
|
||||||
duration: theme.transitions.duration.enteringScreen,
|
duration: theme.transitions.duration.enteringScreen,
|
||||||
}),
|
}),
|
||||||
marginLeft: 0,
|
marginLeft: 0,
|
||||||
}),
|
}),
|
||||||
}),
|
}));
|
||||||
);
|
|
||||||
|
|
||||||
const AppBar = styled(MuiAppBar, {
|
const AppBar = styled(MuiAppBar, {
|
||||||
shouldForwardProp: (prop) => prop !== "open",
|
shouldForwardProp: (prop) => prop !== "open",
|
||||||
})(({ theme, open }) => ({
|
})(({ theme, open }) => ({
|
||||||
transition: theme.transitions.create(["margin", "width"], {
|
|
||||||
easing: theme.transitions.easing.sharp,
|
|
||||||
duration: theme.transitions.duration.leavingScreen,
|
|
||||||
}),
|
|
||||||
...(open && {
|
|
||||||
width: `calc(100% - ${drawerWidth}px)`,
|
|
||||||
marginLeft: `${drawerWidth}px`,
|
|
||||||
transition: theme.transitions.create(["margin", "width"], {
|
transition: theme.transitions.create(["margin", "width"], {
|
||||||
easing: theme.transitions.easing.easeOut,
|
easing: theme.transitions.easing.sharp,
|
||||||
duration: theme.transitions.duration.enteringScreen,
|
duration: theme.transitions.duration.leavingScreen,
|
||||||
|
}),
|
||||||
|
...(open && {
|
||||||
|
width: `calc(100% - ${drawerWidth}px)`,
|
||||||
|
marginLeft: `${drawerWidth}px`,
|
||||||
|
transition: theme.transitions.create(["margin", "width"], {
|
||||||
|
easing: theme.transitions.easing.easeOut,
|
||||||
|
duration: theme.transitions.duration.enteringScreen,
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
}),
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const DrawerHeader = styled("div")(({ theme }) => ({
|
const DrawerHeader = styled("div")(({ theme }) => ({
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
padding: theme.spacing(0, 1),
|
padding: theme.spacing(0, 1),
|
||||||
...theme.mixins.toolbar,
|
...theme.mixins.toolbar,
|
||||||
minHeight: "40px !important",
|
minHeight: "40px !important",
|
||||||
justifyContent: "flex-end",
|
justifyContent: "flex-end",
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const HeaderWithSidebar = ({ children }) => {
|
const HeaderWithSidebar = ({ children }) => {
|
||||||
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
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setOpen(!isMobile); // Toggle state when screen size changes
|
setOpen(!isMobile); // Toggle state when screen size changes
|
||||||
}, [isMobile]);
|
}, [isMobile]);
|
||||||
|
|
||||||
const now = moment().locale("fa").format("YYYY/MM/DD");
|
const now = moment().locale("fa").format("YYYY/MM/DD");
|
||||||
|
|
||||||
const handleDrawerOpen = () => {
|
const handleDrawerOpen = () => {
|
||||||
setOpen(true);
|
setOpen(true);
|
||||||
};
|
};
|
||||||
const handleDrawerClose = () => {
|
const handleDrawerClose = () => {
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
height: `100%`,
|
|
||||||
width: "100%",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<AppBar
|
|
||||||
sx={{ position: "fixed", top: "unset" }}
|
|
||||||
elevation={0}
|
|
||||||
open={open}
|
|
||||||
>
|
|
||||||
<Toolbar variant="dense" sx={{ minHeight: 40 }}>
|
|
||||||
<IconButton
|
|
||||||
color="inherit"
|
|
||||||
aria-label="open drawer"
|
|
||||||
onClick={handleDrawerOpen}
|
|
||||||
edge="start"
|
|
||||||
sx={{ mr: 2, ...(open && { display: "none" }) }}
|
|
||||||
>
|
|
||||||
<MenuIcon />
|
|
||||||
</IconButton>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
flexGrow: 1,
|
|
||||||
display: "flex",
|
|
||||||
overflowX: "auto",
|
|
||||||
whiteSpace: "nowrap",
|
|
||||||
scrollbarWidth: "none",
|
|
||||||
"&::-webkit-scrollbar": {
|
|
||||||
display: "none",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{headerMenu.map((menu) => (
|
|
||||||
<HeaderMenu key={menu.title} menu={menu} />
|
|
||||||
))}
|
|
||||||
</Box>
|
|
||||||
</Toolbar>
|
|
||||||
</AppBar>
|
|
||||||
<Drawer
|
|
||||||
sx={{
|
|
||||||
height: "100%",
|
|
||||||
width: drawerWidth,
|
|
||||||
flexShrink: 0,
|
|
||||||
"& .MuiDrawer-paper": {
|
|
||||||
overflow: "hidden",
|
|
||||||
width: drawerWidth,
|
|
||||||
boxSizing: "border-box",
|
|
||||||
borderTop: 1,
|
|
||||||
borderTopColor: "divider",
|
|
||||||
position: "inherit",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
variant="persistent"
|
|
||||||
anchor="left"
|
|
||||||
open={open}
|
|
||||||
>
|
|
||||||
<DrawerHeader
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography variant="subtitle2" color="#757575" sx={{ px: 1 }}>
|
|
||||||
تاریخ امروز: {now}
|
|
||||||
</Typography>
|
|
||||||
<IconButton onClick={handleDrawerClose}>
|
|
||||||
<ChevronRightIcon />
|
|
||||||
</IconButton>
|
|
||||||
</DrawerHeader>
|
|
||||||
<SidebarMenu />
|
|
||||||
<Typography
|
|
||||||
textAlign={"center"}
|
|
||||||
variant="caption"
|
|
||||||
sx={{ py: 0.2, fontFamily: "sans-serif" }}
|
|
||||||
>
|
|
||||||
v{process.env.NEXT_PUBLIC_VERSION}
|
|
||||||
</Typography>
|
|
||||||
</Drawer>
|
|
||||||
<Main
|
|
||||||
open={open}
|
|
||||||
sx={{ height: "100%", width: "100%", overflow: "hidden" }}
|
|
||||||
>
|
|
||||||
<DrawerHeader />
|
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
height: "100%",
|
display: "flex",
|
||||||
width: "100%",
|
height: `100%`,
|
||||||
overflowY: "auto",
|
width: "100%",
|
||||||
p: 1,
|
}}
|
||||||
overflowX: "hidden",
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{children}
|
<AppBar sx={{ position: "fixed", top: "unset" }} elevation={0} open={open}>
|
||||||
|
<Toolbar variant="dense" sx={{ minHeight: 40 }}>
|
||||||
|
<IconButton
|
||||||
|
color="inherit"
|
||||||
|
aria-label="open drawer"
|
||||||
|
onClick={handleDrawerOpen}
|
||||||
|
edge="start"
|
||||||
|
sx={{ mr: 2, ...(open && { display: "none" }) }}
|
||||||
|
>
|
||||||
|
<MenuIcon />
|
||||||
|
</IconButton>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
flexGrow: 1,
|
||||||
|
display: "flex",
|
||||||
|
overflowX: "auto",
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
scrollbarWidth: "none",
|
||||||
|
"&::-webkit-scrollbar": {
|
||||||
|
display: "none",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{headerMenu.map((menu) => (
|
||||||
|
<HeaderMenu key={menu.title} menu={menu} />
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
</Toolbar>
|
||||||
|
</AppBar>
|
||||||
|
<Drawer
|
||||||
|
sx={{
|
||||||
|
height: "100%",
|
||||||
|
width: drawerWidth,
|
||||||
|
flexShrink: 0,
|
||||||
|
"& .MuiDrawer-paper": {
|
||||||
|
overflow: "hidden",
|
||||||
|
width: drawerWidth,
|
||||||
|
boxSizing: "border-box",
|
||||||
|
borderTop: 1,
|
||||||
|
borderTopColor: "divider",
|
||||||
|
position: "inherit",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
variant="persistent"
|
||||||
|
anchor="left"
|
||||||
|
open={open}
|
||||||
|
>
|
||||||
|
<DrawerHeader
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography variant="subtitle2" color="#757575" sx={{ px: 1 }}>
|
||||||
|
تاریخ امروز: {now}
|
||||||
|
</Typography>
|
||||||
|
<IconButton onClick={handleDrawerClose}>
|
||||||
|
<ChevronRightIcon />
|
||||||
|
</IconButton>
|
||||||
|
</DrawerHeader>
|
||||||
|
<SidebarMenu />
|
||||||
|
<Typography textAlign={"center"} variant="caption" sx={{ py: 0.2, fontFamily: "sans-serif" }}>
|
||||||
|
v{process.env.NEXT_PUBLIC_VERSION}
|
||||||
|
</Typography>
|
||||||
|
</Drawer>
|
||||||
|
<Main open={open} sx={{ height: "100%", width: "100%", overflow: "hidden" }}>
|
||||||
|
<DrawerHeader />
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
height: "100%",
|
||||||
|
width: "100%",
|
||||||
|
overflowY: "auto",
|
||||||
|
p: 1,
|
||||||
|
overflowX: "hidden",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Box>
|
||||||
|
</Main>
|
||||||
</Box>
|
</Box>
|
||||||
</Main>
|
);
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default HeaderWithSidebar;
|
export default HeaderWithSidebar;
|
||||||
|
|||||||
@@ -1,29 +1,25 @@
|
|||||||
import { Stack, Typography } from "@mui/material";
|
import { Stack, Typography } from "@mui/material";
|
||||||
|
|
||||||
const ActionHeader = ({ tab }) => {
|
const ActionHeader = ({ tab }) => {
|
||||||
return (
|
return (
|
||||||
<Stack
|
<Stack
|
||||||
sx={{
|
sx={{
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
py: 2,
|
py: 2,
|
||||||
backgroundColor: "primary.main",
|
backgroundColor: "primary.main",
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<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"
|
{tab.phone_number} ....
|
||||||
variant="subtitle1"
|
</Typography>
|
||||||
sx={{ color: "#fff" }}
|
</Stack>
|
||||||
>
|
);
|
||||||
{tab.phone_number} ....
|
|
||||||
</Typography>
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ActionHeader;
|
export default ActionHeader;
|
||||||
|
|||||||
@@ -2,26 +2,24 @@ import { Button, Grid } from "@mui/material";
|
|||||||
import { Controller } from "react-hook-form";
|
import { Controller } from "react-hook-form";
|
||||||
|
|
||||||
const CallActionCategoriesButton = ({ category, control }) => {
|
const CallActionCategoriesButton = ({ category, control }) => {
|
||||||
return (
|
return (
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field, fieldState: { error } }) => (
|
render={({ field, fieldState: { error } }) => (
|
||||||
<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"}
|
||||||
}
|
onClick={() => {
|
||||||
color={"primary"}
|
field.onChange(category.category_id);
|
||||||
onClick={() => {
|
}}
|
||||||
field.onChange(category.category_id);
|
>
|
||||||
}}
|
{category.category_name}
|
||||||
>
|
</Button>
|
||||||
{category.category_name}
|
)}
|
||||||
</Button>
|
name={"category_id"}
|
||||||
)}
|
/>
|
||||||
name={"category_id"}
|
);
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
export default CallActionCategoriesButton;
|
export default CallActionCategoriesButton;
|
||||||
|
|||||||
@@ -3,42 +3,42 @@ import { useEffect, useRef } from "react";
|
|||||||
import { Controller } from "react-hook-form";
|
import { Controller } from "react-hook-form";
|
||||||
|
|
||||||
const CallActionDescription = ({ control }) => {
|
const CallActionDescription = ({ control }) => {
|
||||||
const inputRef = useRef(null);
|
const inputRef = useRef(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const timer = setTimeout(() => {
|
const timer = setTimeout(() => {
|
||||||
if (inputRef.current) {
|
if (inputRef.current) {
|
||||||
inputRef.current.focus();
|
inputRef.current.focus();
|
||||||
}
|
}
|
||||||
}, 100);
|
}, 100);
|
||||||
|
|
||||||
return () => clearTimeout(timer);
|
return () => clearTimeout(timer);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Divider sx={{ margin: 2 }}>
|
<Divider sx={{ margin: 2 }}>
|
||||||
<Chip label={"توضیحات تماس (اختیاری)"} variant="outlined" />
|
<Chip label={"توضیحات تماس (اختیاری)"} variant="outlined" />
|
||||||
</Divider>
|
</Divider>
|
||||||
<Box sx={{ m: 2 }}>
|
<Box sx={{ m: 2 }}>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field, fieldState: { error } }) => (
|
render={({ field, fieldState: { error } }) => (
|
||||||
<TextField
|
<TextField
|
||||||
{...field}
|
{...field}
|
||||||
inputRef={inputRef}
|
inputRef={inputRef}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
placeholder={"لطفا توضیحات خود را وارد نمایید"}
|
placeholder={"لطفا توضیحات خود را وارد نمایید"}
|
||||||
type={"text"}
|
type={"text"}
|
||||||
fullWidth
|
fullWidth
|
||||||
multiline
|
multiline
|
||||||
rows={8}
|
rows={8}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
name={"description"}
|
name={"description"}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default CallActionDescription;
|
export default CallActionDescription;
|
||||||
|
|||||||
@@ -3,36 +3,36 @@ import { useCallback } from "react";
|
|||||||
import { Controller, useWatch } from "react-hook-form";
|
import { Controller, useWatch } from "react-hook-form";
|
||||||
|
|
||||||
const CallActionSubcategoriesButton = ({ sub_category, control, onSubmit }) => {
|
const CallActionSubcategoriesButton = ({ sub_category, control, onSubmit }) => {
|
||||||
const category_id = useWatch({ control, name: "category_id" });
|
const category_id = useWatch({ control, name: "category_id" });
|
||||||
|
|
||||||
const handleClick = useCallback(
|
const handleClick = useCallback(
|
||||||
(onChange) => {
|
(onChange) => {
|
||||||
onChange(sub_category.subcategory_id);
|
onChange(sub_category.subcategory_id);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
onSubmit();
|
onSubmit();
|
||||||
}, 0);
|
}, 0);
|
||||||
},
|
},
|
||||||
[sub_category.subcategory_id],
|
[sub_category.subcategory_id]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field, fieldState: { error } }) => {
|
render={({ field, fieldState: { error } }) => {
|
||||||
return sub_category.category_id === category_id ? (
|
return sub_category.category_id === category_id ? (
|
||||||
<Button
|
<Button
|
||||||
sx={{ py: 1.5 }}
|
sx={{ py: 1.5 }}
|
||||||
fullWidth
|
fullWidth
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
color={"primary"}
|
color={"primary"}
|
||||||
onClick={() => handleClick(field.onChange)}
|
onClick={() => handleClick(field.onChange)}
|
||||||
>
|
>
|
||||||
{sub_category.subcategory_name}
|
{sub_category.subcategory_name}
|
||||||
</Button>
|
</Button>
|
||||||
) : null;
|
) : null;
|
||||||
}}
|
}}
|
||||||
name={"subcategory_id"}
|
name={"subcategory_id"}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default CallActionSubcategoriesButton;
|
export default CallActionSubcategoriesButton;
|
||||||
|
|||||||
@@ -3,22 +3,18 @@ import CallActionCategoriesButton from "./CallActionCategoriesButton";
|
|||||||
import { Chip, Divider, Grid, Stack } from "@mui/material";
|
import { Chip, Divider, Grid, Stack } from "@mui/material";
|
||||||
|
|
||||||
const CallActionsCategories = ({ control }) => {
|
const CallActionsCategories = ({ control }) => {
|
||||||
const { categoryLists } = useCategory();
|
const { categoryLists } = useCategory();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Divider sx={{ margin: 2 }}>
|
<Divider sx={{ margin: 2 }}>
|
||||||
<Chip label={"موضوع ها"} variant="outlined" />
|
<Chip label={"موضوع ها"} variant="outlined" />
|
||||||
</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}
|
</Stack>
|
||||||
control={control}
|
</>
|
||||||
/>
|
);
|
||||||
))}
|
|
||||||
</Stack>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
export default CallActionsCategories;
|
export default CallActionsCategories;
|
||||||
|
|||||||
@@ -3,25 +3,25 @@ import CallActionSubcategoriesButton from "./CallActionSubcategoriesButton";
|
|||||||
import { Chip, Divider, Grid, Stack } from "@mui/material";
|
import { Chip, Divider, Grid, Stack } from "@mui/material";
|
||||||
|
|
||||||
const CallActionsSubcategories = ({ control, onSubmit }) => {
|
const CallActionsSubcategories = ({ control, onSubmit }) => {
|
||||||
const { subCategoryLists } = useCategory();
|
const { subCategoryLists } = useCategory();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Divider sx={{ margin: 2 }}>
|
<Divider sx={{ margin: 2 }}>
|
||||||
<Chip label={"زیر موضوع ها"} variant="outlined" />
|
<Chip label={"زیر موضوع ها"} variant="outlined" />
|
||||||
</Divider>
|
</Divider>
|
||||||
<Stack sx={{ m: 1, p: 1 }} direction={"row"} spacing={3}>
|
<Stack sx={{ m: 1, p: 1 }} direction={"row"} spacing={3}>
|
||||||
{subCategoryLists.map((sub_category, index) => {
|
{subCategoryLists.map((sub_category, index) => {
|
||||||
return (
|
return (
|
||||||
<CallActionSubcategoriesButton
|
<CallActionSubcategoriesButton
|
||||||
key={index}
|
key={index}
|
||||||
sub_category={sub_category}
|
sub_category={sub_category}
|
||||||
control={control}
|
control={control}
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</Stack>
|
</Stack>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default CallActionsSubcategories;
|
export default CallActionsSubcategories;
|
||||||
|
|||||||
@@ -12,76 +12,70 @@ import CallActionsCategories from "./CallActionsCategories";
|
|||||||
import CallActionsSubcategories from "./CallActionsSubcategories";
|
import CallActionsSubcategories from "./CallActionsSubcategories";
|
||||||
|
|
||||||
const defaultValues = {
|
const defaultValues = {
|
||||||
description: "",
|
description: "",
|
||||||
category_id: "",
|
category_id: "",
|
||||||
subcategory_id: "",
|
subcategory_id: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
const validationSchema = object({
|
const validationSchema = object({
|
||||||
category_id: string().required("لطفا نام کاربری را وارد کنید!"),
|
category_id: string().required("لطفا نام کاربری را وارد کنید!"),
|
||||||
subcategory_id: string().required("لطفا رمز عبور را وارد کنید!"),
|
subcategory_id: string().required("لطفا رمز عبور را وارد کنید!"),
|
||||||
});
|
});
|
||||||
|
|
||||||
function CallActions({ tab }) {
|
function CallActions({ tab }) {
|
||||||
const requestServer = useRequest();
|
const requestServer = useRequest();
|
||||||
const { closeCall } = useCall();
|
const { closeCall } = useCall();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
control,
|
control,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
formState: { isSubmitting },
|
formState: { isSubmitting },
|
||||||
} = useForm({
|
} = useForm({
|
||||||
defaultValues,
|
defaultValues,
|
||||||
resolver: yupResolver(validationSchema),
|
resolver: yupResolver(validationSchema),
|
||||||
mode: "all",
|
mode: "all",
|
||||||
});
|
});
|
||||||
|
|
||||||
const onSubmit = handleSubmit(async (data) => {
|
const onSubmit = handleSubmit(async (data) => {
|
||||||
try {
|
try {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
data.description != "" &&
|
data.description != "" && formData.append("description", data.description);
|
||||||
formData.append("description", data.description);
|
formData.append("category_id", data.category_id);
|
||||||
formData.append("category_id", data.category_id);
|
formData.append("subcategory_id", data.subcategory_id);
|
||||||
formData.append("subcategory_id", data.subcategory_id);
|
await requestServer(`${CALL_ACTION}/${tab.id}`, "post", {
|
||||||
await requestServer(`${CALL_ACTION}/${tab.id}`, "post", {
|
data: formData,
|
||||||
data: formData,
|
});
|
||||||
});
|
closeCall(tab.id);
|
||||||
closeCall(tab.id);
|
} catch (error) {}
|
||||||
} catch (error) {}
|
});
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack
|
<Stack
|
||||||
sx={{
|
|
||||||
height: "100%",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ActionHeader tab={tab} />
|
|
||||||
<Stack sx={{ height: "100%", overflowY: "auto", position: "relative" }}>
|
|
||||||
<CallActionDescription control={control} />
|
|
||||||
<CallActionsCategories control={control} />
|
|
||||||
<CallActionsSubcategories control={control} onSubmit={onSubmit} />
|
|
||||||
{isSubmitting && (
|
|
||||||
<Box
|
|
||||||
sx={{
|
sx={{
|
||||||
position: "absolute",
|
height: "100%",
|
||||||
width: "100%",
|
|
||||||
height: "100%",
|
|
||||||
opacity: 0.9,
|
|
||||||
zIndex: 9999,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<LoadingHardPage
|
<ActionHeader tab={tab} />
|
||||||
width={80}
|
<Stack sx={{ height: "100%", overflowY: "auto", position: "relative" }}>
|
||||||
height={80}
|
<CallActionDescription control={control} />
|
||||||
loading={true}
|
<CallActionsCategories control={control} />
|
||||||
sx={{ position: "absolute" }}
|
<CallActionsSubcategories control={control} onSubmit={onSubmit} />
|
||||||
/>
|
{isSubmitting && (
|
||||||
</Box>
|
<Box
|
||||||
)}
|
sx={{
|
||||||
</Stack>
|
position: "absolute",
|
||||||
</Stack>
|
width: "100%",
|
||||||
);
|
height: "100%",
|
||||||
|
opacity: 0.9,
|
||||||
|
zIndex: 9999,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<LoadingHardPage width={80} height={80} loading={true} sx={{ position: "absolute" }} />
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CallActions;
|
export default CallActions;
|
||||||
|
|||||||
@@ -1,39 +1,39 @@
|
|||||||
import { Typography } from "@mui/material";
|
import { Typography } from "@mui/material";
|
||||||
|
|
||||||
const ErrorOrEmpty = ({ isErrorOrEmpty }) => {
|
const ErrorOrEmpty = ({ isErrorOrEmpty }) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{isErrorOrEmpty === "error" ? (
|
{isErrorOrEmpty === "error" ? (
|
||||||
<Typography
|
<Typography
|
||||||
variant="subtitle1"
|
variant="subtitle1"
|
||||||
sx={{
|
sx={{
|
||||||
color: "#837e7e",
|
color: "#837e7e",
|
||||||
fontWeight: "500",
|
fontWeight: "500",
|
||||||
letterSpacing: 1,
|
letterSpacing: 1,
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
mt: "50%",
|
mt: "50%",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
خطا در دریافت تاریخچه تماس دریافتی
|
خطا در دریافت تاریخچه تماس دریافتی
|
||||||
</Typography>
|
</Typography>
|
||||||
) : isErrorOrEmpty === "empty" ? (
|
) : isErrorOrEmpty === "empty" ? (
|
||||||
<Typography
|
<Typography
|
||||||
variant="subtitle1"
|
variant="subtitle1"
|
||||||
sx={{
|
sx={{
|
||||||
color: "#837e7e",
|
color: "#837e7e",
|
||||||
fontWeight: "500",
|
fontWeight: "500",
|
||||||
letterSpacing: 1,
|
letterSpacing: 1,
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
mt: "50%",
|
mt: "50%",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
تاریخچه ای برای تماس دریافتی یافت نشد
|
تاریخچه ای برای تماس دریافتی یافت نشد
|
||||||
</Typography>
|
</Typography>
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ErrorOrEmpty;
|
export default ErrorOrEmpty;
|
||||||
|
|||||||
@@ -1,25 +1,25 @@
|
|||||||
import { Stack, Typography } from "@mui/material";
|
import { Stack, Typography } from "@mui/material";
|
||||||
|
|
||||||
const HistoryHeader = ({ tab }) => {
|
const HistoryHeader = ({ tab }) => {
|
||||||
return (
|
return (
|
||||||
<Stack
|
<Stack
|
||||||
sx={{
|
sx={{
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
py: 2,
|
py: 2,
|
||||||
backgroundColor: "primary.main",
|
backgroundColor: "primary.main",
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography variant="subtitle1" sx={{ marginRight: 1, color: "#fff" }}>
|
<Typography variant="subtitle1" sx={{ marginRight: 1, color: "#fff" }}>
|
||||||
.... تاریخچه تماس های:
|
.... تاریخچه تماس های:
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="subtitle1" sx={{ color: "#fff" }}>
|
<Typography variant="subtitle1" sx={{ color: "#fff" }}>
|
||||||
{tab.phone_number} ....
|
{tab.phone_number} ....
|
||||||
</Typography>
|
</Typography>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default HistoryHeader;
|
export default HistoryHeader;
|
||||||
|
|||||||
@@ -3,28 +3,26 @@ import { Avatar, Box, Stack, Typography } from "@mui/material";
|
|||||||
import moment from "jalali-moment";
|
import moment from "jalali-moment";
|
||||||
|
|
||||||
const PreviousOperatorData = ({ historyItem }) => {
|
const PreviousOperatorData = ({ historyItem }) => {
|
||||||
return (
|
return (
|
||||||
<Stack direction={"row"} spacing={2}>
|
<Stack direction={"row"} spacing={2}>
|
||||||
<Box>
|
<Box>
|
||||||
<Avatar sx={{ backgroundColor: "primary.light" }}>
|
<Avatar sx={{ backgroundColor: "primary.light" }}>
|
||||||
<HeadsetMicIcon />
|
<HeadsetMicIcon />
|
||||||
</Avatar>
|
</Avatar>
|
||||||
</Box>
|
</Box>
|
||||||
<Stack>
|
<Stack>
|
||||||
<Stack direction={"row"} sx={{ alignItems: "end" }}>
|
<Stack direction={"row"} sx={{ alignItems: "end" }}>
|
||||||
<Typography sx={{ mr: 1 }}>
|
<Typography sx={{ mr: 1 }}>{historyItem.operator_full_name}</Typography>
|
||||||
{historyItem.operator_full_name}
|
<Typography variant="caption" sx={{ color: "secondary.main" }}>
|
||||||
</Typography>
|
(کارشناس)
|
||||||
<Typography variant="caption" sx={{ color: "secondary.main" }}>
|
</Typography>
|
||||||
(کارشناس)
|
</Stack>
|
||||||
</Typography>
|
<Typography variant="caption" sx={{ color: "secondary.main" }}>
|
||||||
|
{moment(historyItem.created_at).locale("fa").fromNow()}
|
||||||
|
</Typography>
|
||||||
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Typography variant="caption" sx={{ color: "secondary.main" }}>
|
);
|
||||||
{moment(historyItem.created_at).locale("fa").fromNow()}
|
|
||||||
</Typography>
|
|
||||||
</Stack>
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default PreviousOperatorData;
|
export default PreviousOperatorData;
|
||||||
|
|||||||
@@ -1,60 +1,46 @@
|
|||||||
import { Chip, Divider, Stack } from "@mui/material";
|
import { Chip, Divider, Stack } from "@mui/material";
|
||||||
|
|
||||||
const Topics = ({ historyItem }) => {
|
const Topics = ({ historyItem }) => {
|
||||||
return (
|
return (
|
||||||
<Stack spacing={1}>
|
<Stack spacing={1}>
|
||||||
<Stack
|
<Stack
|
||||||
sx={{
|
sx={{
|
||||||
width: "100%",
|
width: "100%",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
}}
|
}}
|
||||||
spacing={1}
|
spacing={1}
|
||||||
direction={"row"}
|
direction={"row"}
|
||||||
>
|
>
|
||||||
<Chip label={"موضوع"} size="small" variant="outlined" color="primary" />
|
<Chip label={"موضوع"} size="small" variant="outlined" color="primary" />
|
||||||
<Divider sx={{ flex: 1 }} />
|
<Divider sx={{ flex: 1 }} />
|
||||||
{historyItem.category_name && (
|
{historyItem.category_name && <Chip label={historyItem.category_name} />}
|
||||||
<Chip label={historyItem.category_name} />
|
</Stack>
|
||||||
)}
|
<Stack
|
||||||
</Stack>
|
sx={{
|
||||||
<Stack
|
width: "100%",
|
||||||
sx={{
|
alignItems: "center",
|
||||||
width: "100%",
|
}}
|
||||||
alignItems: "center",
|
spacing={1}
|
||||||
}}
|
direction={"row"}
|
||||||
spacing={1}
|
>
|
||||||
direction={"row"}
|
<Chip label={"زیر موضوع"} size="small" variant="outlined" color="primary" />
|
||||||
>
|
<Divider sx={{ flex: 1 }} />
|
||||||
<Chip
|
{historyItem.subcategory_name && <Chip label={historyItem.subcategory_name} />}
|
||||||
label={"زیر موضوع"}
|
</Stack>
|
||||||
size="small"
|
<Stack
|
||||||
variant="outlined"
|
sx={{
|
||||||
color="primary"
|
width: "100%",
|
||||||
/>
|
alignItems: "center",
|
||||||
<Divider sx={{ flex: 1 }} />
|
}}
|
||||||
{historyItem.subcategory_name && (
|
spacing={1}
|
||||||
<Chip label={historyItem.subcategory_name} />
|
direction={"row"}
|
||||||
)}
|
>
|
||||||
</Stack>
|
<Chip label={"توضیحات تماس"} size="small" variant="outlined" color="primary" />
|
||||||
<Stack
|
<Divider sx={{ flex: 1 }} />
|
||||||
sx={{
|
{historyItem.description && <Chip label={historyItem.description} />}
|
||||||
width: "100%",
|
</Stack>
|
||||||
alignItems: "center",
|
</Stack>
|
||||||
}}
|
);
|
||||||
spacing={1}
|
|
||||||
direction={"row"}
|
|
||||||
>
|
|
||||||
<Chip
|
|
||||||
label={"توضیحات تماس"}
|
|
||||||
size="small"
|
|
||||||
variant="outlined"
|
|
||||||
color="primary"
|
|
||||||
/>
|
|
||||||
<Divider sx={{ flex: 1 }} />
|
|
||||||
{historyItem.description && <Chip label={historyItem.description} />}
|
|
||||||
</Stack>
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Topics;
|
export default Topics;
|
||||||
|
|||||||
@@ -3,12 +3,12 @@ import PreviousOperatorData from "./PreviousOperatorData";
|
|||||||
import Topics from "./Topics.";
|
import Topics from "./Topics.";
|
||||||
|
|
||||||
const ListItemOfCalls = ({ historyItem }) => {
|
const ListItemOfCalls = ({ historyItem }) => {
|
||||||
return (
|
return (
|
||||||
<Stack sx={{ p: 2 }} spacing={2}>
|
<Stack sx={{ p: 2 }} spacing={2}>
|
||||||
<PreviousOperatorData historyItem={historyItem} />
|
<PreviousOperatorData historyItem={historyItem} />
|
||||||
<Topics historyItem={historyItem} />
|
<Topics historyItem={historyItem} />
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ListItemOfCalls;
|
export default ListItemOfCalls;
|
||||||
|
|||||||
@@ -1,115 +1,108 @@
|
|||||||
import {
|
import { Box, List, ListItem, ListItemAvatar, ListItemText, Skeleton } from "@mui/material";
|
||||||
Box,
|
|
||||||
List,
|
|
||||||
ListItem,
|
|
||||||
ListItemAvatar,
|
|
||||||
ListItemText,
|
|
||||||
Skeleton,
|
|
||||||
} from "@mui/material";
|
|
||||||
|
|
||||||
const LoadingHistory = () => {
|
const LoadingHistory = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<List
|
<List
|
||||||
data-testid="loading-list"
|
data-testid="loading-list"
|
||||||
sx={{
|
|
||||||
width: "100%",
|
|
||||||
bgcolor: "rgba(222,234,215,0.11)",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
px: 2,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ListItemAvatar data-testid="loading-listItemAvatar">
|
|
||||||
<Skeleton
|
|
||||||
data-testid="operator-avatar-skeleton"
|
|
||||||
animation="wave"
|
|
||||||
variant="circular"
|
|
||||||
width={60}
|
|
||||||
height={60}
|
|
||||||
/>
|
|
||||||
</ListItemAvatar>
|
|
||||||
<ListItemText
|
|
||||||
data-testid="loading-listItemText"
|
|
||||||
primary={
|
|
||||||
<Box sx={{ display: "flex", alignItems: "end" }}>
|
|
||||||
<Skeleton
|
|
||||||
data-testid="operator-name-skeleton"
|
|
||||||
animation="wave"
|
|
||||||
height={15}
|
|
||||||
width="50%"
|
|
||||||
style={{ marginRight: 10, marginBottom: 5 }}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
}
|
|
||||||
secondary={
|
|
||||||
<Skeleton
|
|
||||||
data-testid="operator-answer-date-skeleton"
|
|
||||||
animation="wave"
|
|
||||||
height={10}
|
|
||||||
width="30%"
|
|
||||||
style={{ marginRight: 10 }}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
<Box timeout="auto" sx={{ py: 2 }}>
|
|
||||||
<List component="div" disablePadding>
|
|
||||||
<ListItem data-testid="loading-listItem-cat" sx={{ px: 4 }}>
|
|
||||||
<Box
|
|
||||||
sx={{
|
sx={{
|
||||||
width: "100%",
|
width: "100%",
|
||||||
display: "flex",
|
bgcolor: "rgba(222,234,215,0.11)",
|
||||||
justifyContent: "space-between",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Skeleton
|
<Box
|
||||||
data-testid="operator-category-header-skeleton"
|
sx={{
|
||||||
animation="wave"
|
display: "flex",
|
||||||
height={15}
|
alignItems: "center",
|
||||||
width="15%"
|
px: 2,
|
||||||
/>
|
}}
|
||||||
<Skeleton
|
>
|
||||||
data-testid="operator-category-value-skeleton"
|
<ListItemAvatar data-testid="loading-listItemAvatar">
|
||||||
animation="wave"
|
<Skeleton
|
||||||
height={15}
|
data-testid="operator-avatar-skeleton"
|
||||||
width="30%"
|
animation="wave"
|
||||||
/>
|
variant="circular"
|
||||||
</Box>
|
width={60}
|
||||||
</ListItem>
|
height={60}
|
||||||
</List>
|
/>
|
||||||
<List component="div" disablePadding>
|
</ListItemAvatar>
|
||||||
<ListItem data-testid="loading-listItem-subCat" sx={{ px: 4 }}>
|
<ListItemText
|
||||||
<Box
|
data-testid="loading-listItemText"
|
||||||
sx={{
|
primary={
|
||||||
width: "100%",
|
<Box sx={{ display: "flex", alignItems: "end" }}>
|
||||||
display: "flex",
|
<Skeleton
|
||||||
justifyContent: "space-between",
|
data-testid="operator-name-skeleton"
|
||||||
}}
|
animation="wave"
|
||||||
>
|
height={15}
|
||||||
<Skeleton
|
width="50%"
|
||||||
data-testid="operator-subCategory-header-skeleton"
|
style={{ marginRight: 10, marginBottom: 5 }}
|
||||||
animation="wave"
|
/>
|
||||||
height={15}
|
</Box>
|
||||||
width="25%"
|
}
|
||||||
/>
|
secondary={
|
||||||
<Skeleton
|
<Skeleton
|
||||||
data-testid="operator-subCategory-value-skeleton"
|
data-testid="operator-answer-date-skeleton"
|
||||||
animation="wave"
|
animation="wave"
|
||||||
height={15}
|
height={10}
|
||||||
width="50%"
|
width="30%"
|
||||||
/>
|
style={{ marginRight: 10 }}
|
||||||
</Box>
|
/>
|
||||||
</ListItem>
|
}
|
||||||
</List>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</List>
|
<Box timeout="auto" sx={{ py: 2 }}>
|
||||||
</>
|
<List component="div" disablePadding>
|
||||||
);
|
<ListItem data-testid="loading-listItem-cat" sx={{ px: 4 }}>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: "100%",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Skeleton
|
||||||
|
data-testid="operator-category-header-skeleton"
|
||||||
|
animation="wave"
|
||||||
|
height={15}
|
||||||
|
width="15%"
|
||||||
|
/>
|
||||||
|
<Skeleton
|
||||||
|
data-testid="operator-category-value-skeleton"
|
||||||
|
animation="wave"
|
||||||
|
height={15}
|
||||||
|
width="30%"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</ListItem>
|
||||||
|
</List>
|
||||||
|
<List component="div" disablePadding>
|
||||||
|
<ListItem data-testid="loading-listItem-subCat" sx={{ px: 4 }}>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: "100%",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Skeleton
|
||||||
|
data-testid="operator-subCategory-header-skeleton"
|
||||||
|
animation="wave"
|
||||||
|
height={15}
|
||||||
|
width="25%"
|
||||||
|
/>
|
||||||
|
<Skeleton
|
||||||
|
data-testid="operator-subCategory-value-skeleton"
|
||||||
|
animation="wave"
|
||||||
|
height={15}
|
||||||
|
width="50%"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</ListItem>
|
||||||
|
</List>
|
||||||
|
</Box>
|
||||||
|
</List>
|
||||||
|
</>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default LoadingHistory;
|
export default LoadingHistory;
|
||||||
|
|||||||
@@ -9,53 +9,52 @@ import { Headphones } from "@mui/icons-material";
|
|||||||
|
|
||||||
const max_size = 10;
|
const max_size = 10;
|
||||||
const CallHistory = ({ tab }) => {
|
const CallHistory = ({ tab }) => {
|
||||||
const {
|
const { firstItemOfHistory, historyList, isLoadingHistoryList, errorHistoryList } = useCallerHistory(
|
||||||
firstItemOfHistory,
|
tab.id,
|
||||||
historyList,
|
tab.phone_number,
|
||||||
isLoadingHistoryList,
|
max_size
|
||||||
errorHistoryList,
|
);
|
||||||
} = useCallerHistory(tab.id, tab.phone_number, max_size);
|
const [showRestOfHistory, setShowRestOfHistory] = useState(false);
|
||||||
const [showRestOfHistory, setShowRestOfHistory] = useState(false);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack
|
<Stack
|
||||||
sx={{
|
sx={{
|
||||||
height: "100%",
|
height: "100%",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<HistoryHeader tab={tab} />
|
<HistoryHeader tab={tab} />
|
||||||
<Stack sx={{ height: "100%", overflowY: "auto" }}>
|
<Stack sx={{ height: "100%", overflowY: "auto" }}>
|
||||||
{isLoadingHistoryList ? (
|
{isLoadingHistoryList ? (
|
||||||
<LoadingHistory />
|
<LoadingHistory />
|
||||||
) : errorHistoryList ? (
|
) : errorHistoryList ? (
|
||||||
<ErrorOrEmpty isErrorOrEmpty="error" />
|
<ErrorOrEmpty isErrorOrEmpty="error" />
|
||||||
) : !firstItemOfHistory ? (
|
) : !firstItemOfHistory ? (
|
||||||
<ErrorOrEmpty isErrorOrEmpty="empty" />
|
<ErrorOrEmpty isErrorOrEmpty="empty" />
|
||||||
) : (
|
) : (
|
||||||
<Box>
|
<Box>
|
||||||
<ListItemOfCalls key={"first"} historyItem={firstItemOfHistory} />
|
<ListItemOfCalls key={"first"} historyItem={firstItemOfHistory} />
|
||||||
{historyList && (
|
{historyList && (
|
||||||
<Divider>
|
<Divider>
|
||||||
<Chip
|
<Chip
|
||||||
icon={<Headphones />}
|
icon={<Headphones />}
|
||||||
onClick={() => setShowRestOfHistory(true)}
|
onClick={() => setShowRestOfHistory(true)}
|
||||||
label={"نمایش موارد بیشتر"}
|
label={"نمایش موارد بیشتر"}
|
||||||
disabled={showRestOfHistory}
|
disabled={showRestOfHistory}
|
||||||
/>
|
/>
|
||||||
</Divider>
|
</Divider>
|
||||||
)}
|
)}
|
||||||
{showRestOfHistory &&
|
{showRestOfHistory &&
|
||||||
historyList.map((historyItem, index) => (
|
historyList.map((historyItem, index) => (
|
||||||
<>
|
<>
|
||||||
<ListItemOfCalls key={index} historyItem={historyItem} />
|
<ListItemOfCalls key={index} historyItem={historyItem} />
|
||||||
<Divider />
|
<Divider />
|
||||||
</>
|
</>
|
||||||
))}
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default CallHistory;
|
export default CallHistory;
|
||||||
|
|||||||
@@ -3,27 +3,27 @@ import CallActions from "./CallActions";
|
|||||||
import CallHistory from "./CallHistory";
|
import CallHistory from "./CallHistory";
|
||||||
|
|
||||||
const CallTabPanel = ({ tab }) => {
|
const CallTabPanel = ({ tab }) => {
|
||||||
return (
|
return (
|
||||||
<Stack
|
<Stack
|
||||||
role="tabpanel"
|
role="tabpanel"
|
||||||
id={`call-tabpanel-${tab.id}`}
|
id={`call-tabpanel-${tab.id}`}
|
||||||
aria-labelledby={`call-tab-${tab.id}`}
|
aria-labelledby={`call-tab-${tab.id}`}
|
||||||
direction={"row"}
|
direction={"row"}
|
||||||
sx={{
|
sx={{
|
||||||
height: "100%",
|
height: "100%",
|
||||||
display: !tab.active && "none",
|
display: !tab.active && "none",
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box sx={{ flex: 2 }}>
|
<Box sx={{ flex: 2 }}>
|
||||||
<CallActions tab={tab} />
|
<CallActions tab={tab} />
|
||||||
</Box>
|
</Box>
|
||||||
<Divider variant="fullWidth" orientation="vertical" />
|
<Divider variant="fullWidth" orientation="vertical" />
|
||||||
<Box sx={{ flex: 1 }}>
|
<Box sx={{ flex: 1 }}>
|
||||||
<CallHistory tab={tab} />
|
<CallHistory tab={tab} />
|
||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default CallTabPanel;
|
export default CallTabPanel;
|
||||||
|
|||||||
@@ -3,19 +3,14 @@ import { Stack, Typography } from "@mui/material";
|
|||||||
import CallTabTime from "./CallTabTime";
|
import CallTabTime from "./CallTabTime";
|
||||||
|
|
||||||
const CallTabDetails = ({ tab }) => {
|
const CallTabDetails = ({ tab }) => {
|
||||||
return (
|
return (
|
||||||
<Stack
|
<Stack direction="row" justifyContent="center" alignItems="center" spacing={3}>
|
||||||
direction="row"
|
<Call />
|
||||||
justifyContent="center"
|
<Stack alignItems={"start"}>
|
||||||
alignItems="center"
|
<Typography>{tab.phone_number}</Typography>
|
||||||
spacing={3}
|
<CallTabTime realTimeDate={tab.date} />
|
||||||
>
|
</Stack>
|
||||||
<Call />
|
</Stack>
|
||||||
<Stack alignItems={"start"}>
|
);
|
||||||
<Typography>{tab.phone_number}</Typography>
|
|
||||||
<CallTabTime realTimeDate={tab.date} />
|
|
||||||
</Stack>
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
export default CallTabDetails;
|
export default CallTabDetails;
|
||||||
|
|||||||
@@ -4,32 +4,31 @@ import { IconButton, Stack, Zoom } from "@mui/material";
|
|||||||
import CallTabDetails from "./CallTabDetails";
|
import CallTabDetails from "./CallTabDetails";
|
||||||
|
|
||||||
const CallTabLabel = ({ tab, index }) => {
|
const CallTabLabel = ({ tab, index }) => {
|
||||||
const { closeCall, activeCall } = useCall();
|
const { closeCall, activeCall } = useCall();
|
||||||
const handleCloseClick = (id) => {
|
const handleCloseClick = (id) => {
|
||||||
closeCall(id);
|
closeCall(id);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack
|
<Stack
|
||||||
sx={{
|
sx={{
|
||||||
width: "100%",
|
width: "100%",
|
||||||
px: 1,
|
px: 1,
|
||||||
borderRight:
|
borderRight: activeCall === index ? 0 : activeCall - 1 === index ? 0 : 1,
|
||||||
activeCall === index ? 0 : activeCall - 1 === index ? 0 : 1,
|
borderRightColor: "divider",
|
||||||
borderRightColor: "divider",
|
}}
|
||||||
}}
|
direction={"row"}
|
||||||
direction={"row"}
|
alignItems={"center"}
|
||||||
alignItems={"center"}
|
justifyContent={"space-between"}
|
||||||
justifyContent={"space-between"}
|
>
|
||||||
>
|
<CallTabDetails tab={tab} />
|
||||||
<CallTabDetails tab={tab} />
|
<Zoom in={tab.active}>
|
||||||
<Zoom in={tab.active}>
|
<IconButton size="small" onClick={() => handleCloseClick(tab.id)}>
|
||||||
<IconButton size="small" onClick={() => handleCloseClick(tab.id)}>
|
<Close fontSize="small" />
|
||||||
<Close fontSize="small" />
|
</IconButton>
|
||||||
</IconButton>
|
</Zoom>
|
||||||
</Zoom>
|
</Stack>
|
||||||
</Stack>
|
);
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default CallTabLabel;
|
export default CallTabLabel;
|
||||||
|
|||||||
@@ -3,26 +3,26 @@ import moment from "jalali-moment";
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
const CallTabTime = ({ realTimeDate }) => {
|
const CallTabTime = ({ realTimeDate }) => {
|
||||||
const [date, setDate] = useState("...");
|
const [date, setDate] = useState("...");
|
||||||
const changeTabTime = (tabTime) => {
|
const changeTabTime = (tabTime) => {
|
||||||
moment.relativeTimeThreshold("ss", 1);
|
moment.relativeTimeThreshold("ss", 1);
|
||||||
return moment(tabTime).locale("fa").fromNow();
|
return moment(tabTime).locale("fa").fromNow();
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const timer = setInterval(() => {
|
|
||||||
setDate(changeTabTime(realTimeDate));
|
|
||||||
}, 1000);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
clearInterval(timer);
|
|
||||||
};
|
};
|
||||||
}, [realTimeDate]);
|
|
||||||
|
|
||||||
return (
|
useEffect(() => {
|
||||||
<Typography variant="caption" color="gray">
|
const timer = setInterval(() => {
|
||||||
{date}
|
setDate(changeTabTime(realTimeDate));
|
||||||
</Typography>
|
}, 1000);
|
||||||
);
|
|
||||||
|
return () => {
|
||||||
|
clearInterval(timer);
|
||||||
|
};
|
||||||
|
}, [realTimeDate]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Typography variant="caption" color="gray">
|
||||||
|
{date}
|
||||||
|
</Typography>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
export default CallTabTime;
|
export default CallTabTime;
|
||||||
|
|||||||
@@ -3,38 +3,38 @@ import { Box, Tab, Tabs } from "@mui/material";
|
|||||||
import CallTabLabel from "./CallTabLabel";
|
import CallTabLabel from "./CallTabLabel";
|
||||||
|
|
||||||
const CallTabsList = () => {
|
const CallTabsList = () => {
|
||||||
const { calls, changeActiveTabCall, activeCall } = useCall();
|
const { calls, changeActiveTabCall, activeCall } = useCall();
|
||||||
const handleChange = (event, newValue) => {
|
const handleChange = (event, newValue) => {
|
||||||
changeActiveTabCall(newValue);
|
changeActiveTabCall(newValue);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ borderBottom: 1, borderColor: "divider" }}>
|
<Box sx={{ borderBottom: 1, borderColor: "divider" }}>
|
||||||
<Tabs
|
<Tabs
|
||||||
value={activeCall}
|
value={activeCall}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
variant="scrollable"
|
variant="scrollable"
|
||||||
scrollButtons="auto"
|
scrollButtons="auto"
|
||||||
sx={{ background: "#e0e0e0" }}
|
sx={{ background: "#e0e0e0" }}
|
||||||
>
|
>
|
||||||
{calls.map((call, index) => (
|
{calls.map((call, index) => (
|
||||||
<Tab
|
<Tab
|
||||||
component={"div"}
|
component={"div"}
|
||||||
sx={{
|
sx={{
|
||||||
width: 350,
|
width: 350,
|
||||||
p: 0,
|
p: 0,
|
||||||
py: 0.5,
|
py: 0.5,
|
||||||
background: call.active ? "#fff" : null,
|
background: call.active ? "#fff" : null,
|
||||||
borderTopRightRadius: call.active ? "16px" : 0,
|
borderTopRightRadius: call.active ? "16px" : 0,
|
||||||
borderTopLeftRadius: call.active ? "16px" : 0,
|
borderTopLeftRadius: call.active ? "16px" : 0,
|
||||||
}}
|
}}
|
||||||
label={<CallTabLabel index={index} tab={call} />}
|
label={<CallTabLabel index={index} tab={call} />}
|
||||||
key={call.id}
|
key={call.id}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default CallTabsList;
|
export default CallTabsList;
|
||||||
|
|||||||
@@ -3,15 +3,15 @@ import CallTabPanel from "./CallTabPanel";
|
|||||||
import CallTabsList from "./CallTabsList";
|
import CallTabsList from "./CallTabsList";
|
||||||
|
|
||||||
const CallTabs = () => {
|
const CallTabs = () => {
|
||||||
const { calls } = useCall();
|
const { calls } = useCall();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<CallTabsList />
|
<CallTabsList />
|
||||||
{calls.map((call) => (
|
{calls.map((call) => (
|
||||||
<CallTabPanel key={call.id} tab={call} />
|
<CallTabPanel key={call.id} tab={call} />
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default CallTabs;
|
export default CallTabs;
|
||||||
|
|||||||
@@ -7,42 +7,38 @@ import { useEffect } from "react";
|
|||||||
import CallTabs from "./CallTabs";
|
import CallTabs from "./CallTabs";
|
||||||
|
|
||||||
const CallWidgetDialog = () => {
|
const CallWidgetDialog = () => {
|
||||||
const { openCallDialog, setOpenCallDialog, calls, newCall } = useCall();
|
const { openCallDialog, setOpenCallDialog, calls, newCall } = useCall();
|
||||||
const { socket } = useSocket();
|
const { socket } = useSocket();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const call = (_data, act) => {
|
const call = (_data, act) => {
|
||||||
const data = JSON.parse(_data);
|
const data = JSON.parse(_data);
|
||||||
newCall({
|
newCall({
|
||||||
id: data.call_id,
|
id: data.call_id,
|
||||||
phone_number: data.phone_number,
|
phone_number: data.phone_number,
|
||||||
});
|
});
|
||||||
act();
|
act();
|
||||||
};
|
};
|
||||||
socket.on("call", call);
|
socket.on("call", call);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
socket.off("call", call);
|
socket.off("call", call);
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (calls.length === 0) {
|
if (calls.length === 0) {
|
||||||
setOpenCallDialog(false);
|
setOpenCallDialog(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setOpenCallDialog(true);
|
setOpenCallDialog(true);
|
||||||
}, [calls.length]);
|
}, [calls.length]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog fullScreen open={openCallDialog} slots={{ transition: DialogTransition }}>
|
||||||
fullScreen
|
<CallTabs />
|
||||||
open={openCallDialog}
|
</Dialog>
|
||||||
slots={{ transition: DialogTransition }}
|
);
|
||||||
>
|
|
||||||
<CallTabs />
|
|
||||||
</Dialog>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default CallWidgetDialog;
|
export default CallWidgetDialog;
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ import { CategoriesProvider } from "@/lib/contexts/category";
|
|||||||
import CallWidgetDialog from "./CallWidgetDialog";
|
import CallWidgetDialog from "./CallWidgetDialog";
|
||||||
|
|
||||||
const CallWidget = () => {
|
const CallWidget = () => {
|
||||||
return (
|
return (
|
||||||
<CategoriesProvider>
|
<CategoriesProvider>
|
||||||
<CallProvider>
|
<CallProvider>
|
||||||
<CallWidgetDialog />
|
<CallWidgetDialog />
|
||||||
</CallProvider>
|
</CallProvider>
|
||||||
</CategoriesProvider>
|
</CategoriesProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default CallWidget;
|
export default CallWidget;
|
||||||
|
|||||||
@@ -5,56 +5,49 @@ import { LocalizationProvider, MobileDatePicker } from "@mui/x-date-pickers";
|
|||||||
import { IconButton, InputAdornment } from "@mui/material";
|
import { IconButton, InputAdornment } from "@mui/material";
|
||||||
import ClearIcon from "@mui/icons-material/Clear";
|
import ClearIcon from "@mui/icons-material/Clear";
|
||||||
|
|
||||||
const CustomDatePicker = ({
|
const CustomDatePicker = ({ dateValue, setDateValue, placeholder = "انتخاب تاریخ", size = "small" }) => {
|
||||||
dateValue,
|
const handleDateChange = (newValue) => {
|
||||||
setDateValue,
|
setDateValue(newValue);
|
||||||
placeholder = "انتخاب تاریخ",
|
};
|
||||||
size = "small",
|
|
||||||
}) => {
|
|
||||||
const handleDateChange = (newValue) => {
|
|
||||||
setDateValue(newValue);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LocalizationProvider
|
<LocalizationProvider
|
||||||
dateAdapter={AdapterDateFnsJalali}
|
dateAdapter={AdapterDateFnsJalali}
|
||||||
localeText={
|
localeText={faIR.components.MuiLocalizationProvider.defaultProps.localeText}
|
||||||
faIR.components.MuiLocalizationProvider.defaultProps.localeText
|
>
|
||||||
}
|
<MobileDatePicker
|
||||||
>
|
value={dateValue || null}
|
||||||
<MobileDatePicker
|
onChange={handleDateChange}
|
||||||
value={dateValue || null}
|
slotProps={{
|
||||||
onChange={handleDateChange}
|
textField: {
|
||||||
slotProps={{
|
size: size,
|
||||||
textField: {
|
placeholder: placeholder,
|
||||||
size: size,
|
InputProps: {
|
||||||
placeholder: placeholder,
|
endAdornment: (
|
||||||
InputProps: {
|
<InputAdornment position="end">
|
||||||
endAdornment: (
|
<IconButton
|
||||||
<InputAdornment position="end">
|
size={size}
|
||||||
<IconButton
|
onClick={(event) => {
|
||||||
size={size}
|
event.stopPropagation();
|
||||||
onClick={(event) => {
|
setDateValue(null);
|
||||||
event.stopPropagation();
|
}}
|
||||||
setDateValue(null);
|
sx={{
|
||||||
}}
|
color: "#bfbfbf",
|
||||||
sx={{
|
"&:hover": {
|
||||||
color: "#bfbfbf",
|
backgroundColor: "rgba(189, 189, 189, 0.1)",
|
||||||
"&:hover": {
|
color: "#363434",
|
||||||
backgroundColor: "rgba(189, 189, 189, 0.1)",
|
},
|
||||||
color: "#363434",
|
}}
|
||||||
},
|
>
|
||||||
}}
|
<ClearIcon />
|
||||||
>
|
</IconButton>
|
||||||
<ClearIcon />
|
</InputAdornment>
|
||||||
</IconButton>
|
),
|
||||||
</InputAdornment>
|
},
|
||||||
),
|
},
|
||||||
},
|
}}
|
||||||
},
|
></MobileDatePicker>
|
||||||
}}
|
</LocalizationProvider>
|
||||||
></MobileDatePicker>
|
);
|
||||||
</LocalizationProvider>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
export default CustomDatePicker;
|
export default CustomDatePicker;
|
||||||
|
|||||||
@@ -11,189 +11,169 @@ import { FA_DATATABLE_LOCALIZATION } from "./localization/fa/datatable";
|
|||||||
import DataTable_Paper from "./table/Paper";
|
import DataTable_Paper from "./table/Paper";
|
||||||
|
|
||||||
const rowSelectionReducer = (state, action) => {
|
const rowSelectionReducer = (state, action) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case "TOGGLE_ROW":
|
case "TOGGLE_ROW":
|
||||||
return { [action.payload]: true };
|
return { [action.payload]: true };
|
||||||
case "RESET":
|
case "RESET":
|
||||||
return {};
|
return {};
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const DataTable_Main = (props) => {
|
const DataTable_Main = (props) => {
|
||||||
const request = useRequest();
|
const request = useRequest();
|
||||||
const [rowSelection, dispatchRowSelection] = useReducer(
|
const [rowSelection, dispatchRowSelection] = useReducer(rowSelectionReducer, {});
|
||||||
rowSelectionReducer,
|
const { filterData, sortData, setSortData, hideData } = useDataTable();
|
||||||
{},
|
const {
|
||||||
);
|
need_filter,
|
||||||
const { filterData, sortData, setSortData, hideData } = useDataTable();
|
table_url,
|
||||||
const {
|
user_id,
|
||||||
need_filter,
|
page_name,
|
||||||
table_url,
|
table_name,
|
||||||
user_id,
|
columns,
|
||||||
page_name,
|
initialStateProps,
|
||||||
table_name,
|
TableToolbar,
|
||||||
columns,
|
table_title,
|
||||||
initialStateProps,
|
RowActions,
|
||||||
TableToolbar,
|
specific_data,
|
||||||
table_title,
|
specialFilter,
|
||||||
RowActions,
|
} = props;
|
||||||
specific_data,
|
const flatColumns = flattenArrayOfObjects(columns, "columns");
|
||||||
specialFilter,
|
const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 10 });
|
||||||
} = props;
|
const [totalRowCount, setTotalRowCount] = useState(0);
|
||||||
const flatColumns = flattenArrayOfObjects(columns, "columns");
|
const flattenHideData = flattenObjectOfObjects(hideData);
|
||||||
const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 10 });
|
const onSortingChange = (event) => {
|
||||||
const [totalRowCount, setTotalRowCount] = useState(0);
|
setSortData(event);
|
||||||
const flattenHideData = flattenObjectOfObjects(hideData);
|
};
|
||||||
const onSortingChange = (event) => {
|
|
||||||
setSortData(event);
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchUrl = useCallback(() => {
|
const fetchUrl = useCallback(() => {
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
params.set("start", `${pagination.pageIndex * pagination.pageSize}`);
|
params.set("start", `${pagination.pageIndex * pagination.pageSize}`);
|
||||||
params.set("size", pagination.pageSize);
|
params.set("size", pagination.pageSize);
|
||||||
if (specialFilter) {
|
if (specialFilter) {
|
||||||
const filteredSpecialFilterData = Object.values(specialFilter).filter(
|
const filteredSpecialFilterData = Object.values(specialFilter).filter(
|
||||||
(filter) => !isArrayEmpty(filter.value),
|
(filter) => !isArrayEmpty(filter.value)
|
||||||
);
|
);
|
||||||
params.set("filters", JSON.stringify(filteredSpecialFilterData || []));
|
params.set("filters", JSON.stringify(filteredSpecialFilterData || []));
|
||||||
} else {
|
} else {
|
||||||
const filteredFilterData = DataTableFilterDataStructure(
|
const filteredFilterData = DataTableFilterDataStructure(filterData, isArrayEmpty);
|
||||||
filterData,
|
params.set("filters", JSON.stringify(filteredFilterData.length === 0 ? [] : filteredFilterData));
|
||||||
isArrayEmpty,
|
}
|
||||||
);
|
params.set(
|
||||||
params.set(
|
"sorting",
|
||||||
"filters",
|
JSON.stringify(
|
||||||
JSON.stringify(
|
Object.values(sortData).map(({ id, ...rest }) => ({
|
||||||
filteredFilterData.length === 0 ? [] : filteredFilterData,
|
...rest,
|
||||||
|
id: id.replace(/__/g, "."),
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return `${table_url}?${params}`;
|
||||||
|
}, [table_url, filterData, pagination, sortData, specialFilter]);
|
||||||
|
|
||||||
|
const fetcher = async (url) => {
|
||||||
|
try {
|
||||||
|
const response = await request(url);
|
||||||
|
setTotalRowCount(response.data?.meta?.totalRowCount);
|
||||||
|
if (specific_data) {
|
||||||
|
return specific_data(response.data);
|
||||||
|
} else {
|
||||||
|
return response.data?.data;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const { data, isValidating, mutate } = useSWR(props.data ? "" : fetchUrl(), fetcher, {
|
||||||
|
revalidateIfStale: true,
|
||||||
|
revalidateOnFocus: false,
|
||||||
|
revalidateOnReconnect: true,
|
||||||
|
keepPreviousData: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isValidating) return;
|
||||||
|
dispatchRowSelection({ type: "RESET" });
|
||||||
|
}, [isValidating]);
|
||||||
|
|
||||||
|
const table = useMaterialReactTable({
|
||||||
|
localization: FA_DATATABLE_LOCALIZATION,
|
||||||
|
columns,
|
||||||
|
data: data ?? [],
|
||||||
|
muiTableBodyRowProps: ({ row }) => ({
|
||||||
|
onClick: () => dispatchRowSelection({ type: "TOGGLE_ROW", payload: row.id }),
|
||||||
|
selected: !!rowSelection[row.id],
|
||||||
|
}),
|
||||||
|
initialState: {
|
||||||
|
density: "compact",
|
||||||
|
columnVisibility: flattenHideData,
|
||||||
|
sorting: sortData,
|
||||||
|
...initialStateProps,
|
||||||
|
},
|
||||||
|
state: {
|
||||||
|
showProgressBars: isValidating || props.loading,
|
||||||
|
columnVisibility: flattenHideData,
|
||||||
|
sorting: sortData,
|
||||||
|
pagination,
|
||||||
|
rowSelection,
|
||||||
|
},
|
||||||
|
muiTableHeadProps: {
|
||||||
|
sx: {
|
||||||
|
borderTop: "1px solid #e1e1e1",
|
||||||
|
borderBottom: "2px solid #e1e1e1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
muiTableHeadCellProps: {
|
||||||
|
sx: {
|
||||||
|
color: "primary.main",
|
||||||
|
borderLeft: "1px solid #e1e1e1",
|
||||||
|
"&:first-of-type": {
|
||||||
|
borderLeft: "unset",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
muiTableBodyCellProps: {
|
||||||
|
sx: {
|
||||||
|
borderLeft: "1px solid #e1e1e1",
|
||||||
|
"&:first-of-type": {
|
||||||
|
borderLeft: "unset",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
manualFiltering: true,
|
||||||
|
manualPagination: true,
|
||||||
|
manualSorting: true,
|
||||||
|
onPaginationChange: setPagination,
|
||||||
|
onSortingChange: onSortingChange,
|
||||||
|
rowCount: totalRowCount,
|
||||||
|
renderTopToolbarCustomActions: ({ table }) => (
|
||||||
|
<>
|
||||||
|
{TableToolbar && (
|
||||||
|
<TableToolbar table={table} mutate={mutate} filterData={specialFilter || filterData} />
|
||||||
|
)}
|
||||||
|
</>
|
||||||
),
|
),
|
||||||
);
|
renderRowActions: ({ row }) => <RowActions row={row} mutate={mutate} />,
|
||||||
}
|
...props,
|
||||||
params.set(
|
});
|
||||||
"sorting",
|
|
||||||
JSON.stringify(
|
|
||||||
Object.values(sortData).map(({ id, ...rest }) => ({
|
|
||||||
...rest,
|
|
||||||
id: id.replace(/__/g, "."),
|
|
||||||
})),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return `${table_url}?${params}`;
|
|
||||||
}, [table_url, filterData, pagination, sortData, specialFilter]);
|
|
||||||
|
|
||||||
const fetcher = async (url) => {
|
return (
|
||||||
try {
|
<DataTable_Paper
|
||||||
const response = await request(url);
|
|
||||||
setTotalRowCount(response.data?.meta?.totalRowCount);
|
|
||||||
if (specific_data) {
|
|
||||||
return specific_data(response.data);
|
|
||||||
} else {
|
|
||||||
return response.data?.data;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const { data, isValidating, mutate } = useSWR(
|
|
||||||
props.data ? "" : fetchUrl(),
|
|
||||||
fetcher,
|
|
||||||
{
|
|
||||||
revalidateIfStale: true,
|
|
||||||
revalidateOnFocus: false,
|
|
||||||
revalidateOnReconnect: true,
|
|
||||||
keepPreviousData: true,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isValidating) return;
|
|
||||||
dispatchRowSelection({ type: "RESET" });
|
|
||||||
}, [isValidating]);
|
|
||||||
|
|
||||||
const table = useMaterialReactTable({
|
|
||||||
localization: FA_DATATABLE_LOCALIZATION,
|
|
||||||
columns,
|
|
||||||
data: data ?? [],
|
|
||||||
muiTableBodyRowProps: ({ row }) => ({
|
|
||||||
onClick: () =>
|
|
||||||
dispatchRowSelection({ type: "TOGGLE_ROW", payload: row.id }),
|
|
||||||
selected: !!rowSelection[row.id],
|
|
||||||
}),
|
|
||||||
initialState: {
|
|
||||||
density: "compact",
|
|
||||||
columnVisibility: flattenHideData,
|
|
||||||
sorting: sortData,
|
|
||||||
...initialStateProps,
|
|
||||||
},
|
|
||||||
state: {
|
|
||||||
showProgressBars: isValidating || props.loading,
|
|
||||||
columnVisibility: flattenHideData,
|
|
||||||
sorting: sortData,
|
|
||||||
pagination,
|
|
||||||
rowSelection,
|
|
||||||
},
|
|
||||||
muiTableHeadProps: {
|
|
||||||
sx: {
|
|
||||||
borderTop: "1px solid #e1e1e1",
|
|
||||||
borderBottom: "2px solid #e1e1e1",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
muiTableHeadCellProps: {
|
|
||||||
sx: {
|
|
||||||
color: "primary.main",
|
|
||||||
borderLeft: "1px solid #e1e1e1",
|
|
||||||
"&:first-of-type": {
|
|
||||||
borderLeft: "unset",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
muiTableBodyCellProps: {
|
|
||||||
sx: {
|
|
||||||
borderLeft: "1px solid #e1e1e1",
|
|
||||||
"&:first-of-type": {
|
|
||||||
borderLeft: "unset",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
manualFiltering: true,
|
|
||||||
manualPagination: true,
|
|
||||||
manualSorting: true,
|
|
||||||
onPaginationChange: setPagination,
|
|
||||||
onSortingChange: onSortingChange,
|
|
||||||
rowCount: totalRowCount,
|
|
||||||
renderTopToolbarCustomActions: ({ table }) => (
|
|
||||||
<>
|
|
||||||
{TableToolbar && (
|
|
||||||
<TableToolbar
|
|
||||||
table={table}
|
table={table}
|
||||||
|
table_title={table_title}
|
||||||
|
need_filter={need_filter}
|
||||||
mutate={mutate}
|
mutate={mutate}
|
||||||
filterData={specialFilter || filterData}
|
table_url={table_url}
|
||||||
/>
|
columns={flatColumns}
|
||||||
)}
|
user_id={user_id}
|
||||||
</>
|
page_name={page_name}
|
||||||
),
|
table_name={table_name}
|
||||||
renderRowActions: ({ row }) => <RowActions row={row} mutate={mutate} />,
|
special_data={props.data}
|
||||||
...props,
|
special_filter={specialFilter}
|
||||||
});
|
setFilterData={props.setFilterData}
|
||||||
|
/>
|
||||||
return (
|
);
|
||||||
<DataTable_Paper
|
|
||||||
table={table}
|
|
||||||
table_title={table_title}
|
|
||||||
need_filter={need_filter}
|
|
||||||
mutate={mutate}
|
|
||||||
table_url={table_url}
|
|
||||||
columns={flatColumns}
|
|
||||||
user_id={user_id}
|
|
||||||
page_name={page_name}
|
|
||||||
table_name={table_name}
|
|
||||||
special_data={props.data}
|
|
||||||
special_filter={specialFilter}
|
|
||||||
setFilterData={props.setFilterData}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
export default DataTable_Main;
|
export default DataTable_Main;
|
||||||
|
|||||||
@@ -2,200 +2,190 @@ import { parseFromValuesOrFunc } from "@/core/utils/utils";
|
|||||||
import { TableBody, Typography } from "@mui/material";
|
import { TableBody, Typography } from "@mui/material";
|
||||||
import { useMRT_RowVirtualizer, useMRT_Rows } from "material-react-table";
|
import { useMRT_RowVirtualizer, useMRT_Rows } from "material-react-table";
|
||||||
import { memo, useMemo } from "react";
|
import { memo, useMemo } from "react";
|
||||||
import DataTable_TableBodyRow, {
|
import DataTable_TableBodyRow, { Memo_DataTable_TableBodyRow } from "./TableBodyRow";
|
||||||
Memo_DataTable_TableBodyRow,
|
|
||||||
} from "./TableBodyRow";
|
|
||||||
|
|
||||||
const DataTable_TableBody = ({ columnVirtualizer, table, ...rest }) => {
|
const DataTable_TableBody = ({ columnVirtualizer, table, ...rest }) => {
|
||||||
const {
|
const {
|
||||||
getBottomRows,
|
getBottomRows,
|
||||||
getIsSomeRowsPinned,
|
getIsSomeRowsPinned,
|
||||||
getRowModel,
|
getRowModel,
|
||||||
getState,
|
getState,
|
||||||
getTopRows,
|
getTopRows,
|
||||||
options: {
|
options: {
|
||||||
enableStickyFooter,
|
enableStickyFooter,
|
||||||
enableStickyHeader,
|
enableStickyHeader,
|
||||||
layoutMode,
|
layoutMode,
|
||||||
localization,
|
localization,
|
||||||
memoMode,
|
memoMode,
|
||||||
muiTableBodyProps,
|
muiTableBodyProps,
|
||||||
renderDetailPanel,
|
renderDetailPanel,
|
||||||
renderEmptyRowsFallback,
|
renderEmptyRowsFallback,
|
||||||
rowPinningDisplayMode,
|
rowPinningDisplayMode,
|
||||||
},
|
},
|
||||||
refs: { tableFooterRef, tableHeadRef, tablePaperRef },
|
refs: { tableFooterRef, tableHeadRef, tablePaperRef },
|
||||||
} = table;
|
} = table;
|
||||||
const { columnFilters, globalFilter, isFullScreen, rowPinning } = getState();
|
const { columnFilters, globalFilter, isFullScreen, rowPinning } = getState();
|
||||||
|
|
||||||
const tableBodyProps = {
|
const tableBodyProps = {
|
||||||
...parseFromValuesOrFunc(muiTableBodyProps, { table }),
|
...parseFromValuesOrFunc(muiTableBodyProps, { table }),
|
||||||
...rest,
|
...rest,
|
||||||
};
|
};
|
||||||
|
|
||||||
const tableHeadHeight =
|
const tableHeadHeight = ((enableStickyHeader || isFullScreen) && tableHeadRef.current?.clientHeight) || 0;
|
||||||
((enableStickyHeader || isFullScreen) &&
|
const tableFooterHeight = (enableStickyFooter && tableFooterRef.current?.clientHeight) || 0;
|
||||||
tableHeadRef.current?.clientHeight) ||
|
|
||||||
0;
|
|
||||||
const tableFooterHeight =
|
|
||||||
(enableStickyFooter && tableFooterRef.current?.clientHeight) || 0;
|
|
||||||
|
|
||||||
const pinnedRowIds = useMemo(() => {
|
const pinnedRowIds = useMemo(() => {
|
||||||
if (!rowPinning.bottom?.length && !rowPinning.top?.length) return [];
|
if (!rowPinning.bottom?.length && !rowPinning.top?.length) return [];
|
||||||
return getRowModel()
|
return getRowModel()
|
||||||
.rows.filter((row) => row.getIsPinned())
|
.rows.filter((row) => row.getIsPinned())
|
||||||
.map((r) => r.id);
|
.map((r) => r.id);
|
||||||
}, [rowPinning, getRowModel().rows]);
|
}, [rowPinning, getRowModel().rows]);
|
||||||
|
|
||||||
const rows = useMRT_Rows(table);
|
const rows = useMRT_Rows(table);
|
||||||
|
|
||||||
const rowVirtualizer = useMRT_RowVirtualizer(table, rows);
|
const rowVirtualizer = useMRT_RowVirtualizer(table, rows);
|
||||||
|
|
||||||
const { virtualRows } = rowVirtualizer ?? {};
|
const { virtualRows } = rowVirtualizer ?? {};
|
||||||
|
|
||||||
const commonRowProps = {
|
const commonRowProps = {
|
||||||
columnVirtualizer,
|
columnVirtualizer,
|
||||||
numRows: rows.length,
|
numRows: rows.length,
|
||||||
table,
|
table,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{!rowPinningDisplayMode?.includes("sticky") &&
|
{!rowPinningDisplayMode?.includes("sticky") && getIsSomeRowsPinned("top") && (
|
||||||
getIsSomeRowsPinned("top") && (
|
<TableBody
|
||||||
<TableBody
|
{...tableBodyProps}
|
||||||
{...tableBodyProps}
|
sx={(theme) => ({
|
||||||
sx={(theme) => ({
|
display: layoutMode?.startsWith("grid") ? "grid" : undefined,
|
||||||
display: layoutMode?.startsWith("grid") ? "grid" : undefined,
|
position: "sticky",
|
||||||
position: "sticky",
|
top: tableHeadHeight - 1,
|
||||||
top: tableHeadHeight - 1,
|
zIndex: 1,
|
||||||
zIndex: 1,
|
...parseFromValuesOrFunc(tableBodyProps?.sx, theme),
|
||||||
...parseFromValuesOrFunc(tableBodyProps?.sx, theme),
|
})}
|
||||||
})}
|
>
|
||||||
>
|
{getTopRows().map((row, staticRowIndex) => {
|
||||||
{getTopRows().map((row, staticRowIndex) => {
|
const props = {
|
||||||
const props = {
|
...commonRowProps,
|
||||||
...commonRowProps,
|
row,
|
||||||
row,
|
staticRowIndex,
|
||||||
staticRowIndex,
|
};
|
||||||
};
|
return memoMode === "rows" ? (
|
||||||
return memoMode === "rows" ? (
|
<Memo_DataTable_TableBodyRow key={row.id} {...props} />
|
||||||
<Memo_DataTable_TableBodyRow key={row.id} {...props} />
|
) : (
|
||||||
) : (
|
<DataTable_TableBodyRow key={row.id} {...props} />
|
||||||
<DataTable_TableBodyRow key={row.id} {...props} />
|
);
|
||||||
);
|
})}
|
||||||
})}
|
</TableBody>
|
||||||
</TableBody>
|
)}
|
||||||
)}
|
<TableBody
|
||||||
<TableBody
|
{...tableBodyProps}
|
||||||
{...tableBodyProps}
|
sx={(theme) => ({
|
||||||
sx={(theme) => ({
|
display: layoutMode?.startsWith("grid") ? "grid" : undefined,
|
||||||
display: layoutMode?.startsWith("grid") ? "grid" : undefined,
|
height: rowVirtualizer ? `${rowVirtualizer.getTotalSize()}px` : undefined,
|
||||||
height: rowVirtualizer
|
minHeight: !rows.length ? "100px" : undefined,
|
||||||
? `${rowVirtualizer.getTotalSize()}px`
|
position: "relative",
|
||||||
: undefined,
|
...parseFromValuesOrFunc(tableBodyProps?.sx, theme),
|
||||||
minHeight: !rows.length ? "100px" : undefined,
|
})}
|
||||||
position: "relative",
|
|
||||||
...parseFromValuesOrFunc(tableBodyProps?.sx, theme),
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{tableBodyProps?.children ??
|
|
||||||
(!rows.length ? (
|
|
||||||
<tr
|
|
||||||
style={{
|
|
||||||
display: layoutMode?.startsWith("grid") ? "grid" : undefined,
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<td
|
{tableBodyProps?.children ??
|
||||||
colSpan={table.getVisibleLeafColumns().length}
|
(!rows.length ? (
|
||||||
style={{
|
<tr
|
||||||
display: layoutMode?.startsWith("grid") ? "grid" : undefined,
|
style={{
|
||||||
}}
|
display: layoutMode?.startsWith("grid") ? "grid" : undefined,
|
||||||
>
|
}}
|
||||||
{renderEmptyRowsFallback?.({ table }) ?? (
|
>
|
||||||
<Typography
|
<td
|
||||||
sx={{
|
colSpan={table.getVisibleLeafColumns().length}
|
||||||
color: "text.secondary",
|
style={{
|
||||||
fontStyle: "italic",
|
display: layoutMode?.startsWith("grid") ? "grid" : undefined,
|
||||||
maxWidth: `min(100vw, ${tablePaperRef.current?.clientWidth ?? 360}px)`,
|
}}
|
||||||
py: "2rem",
|
>
|
||||||
textAlign: "center",
|
{renderEmptyRowsFallback?.({ table }) ?? (
|
||||||
width: "100%",
|
<Typography
|
||||||
}}
|
sx={{
|
||||||
>
|
color: "text.secondary",
|
||||||
{globalFilter || columnFilters.length
|
fontStyle: "italic",
|
||||||
? localization.noResultsFound
|
maxWidth: `min(100vw, ${tablePaperRef.current?.clientWidth ?? 360}px)`,
|
||||||
: localization.noRecordsToDisplay}
|
py: "2rem",
|
||||||
</Typography>
|
textAlign: "center",
|
||||||
)}
|
width: "100%",
|
||||||
</td>
|
}}
|
||||||
</tr>
|
>
|
||||||
) : (
|
{globalFilter || columnFilters.length
|
||||||
<>
|
? localization.noResultsFound
|
||||||
{(virtualRows ?? rows).map((rowOrVirtualRow, staticRowIndex) => {
|
: localization.noRecordsToDisplay}
|
||||||
let row = rowOrVirtualRow;
|
</Typography>
|
||||||
if (rowVirtualizer) {
|
)}
|
||||||
if (renderDetailPanel) {
|
</td>
|
||||||
if (rowOrVirtualRow.index % 2 === 1) {
|
</tr>
|
||||||
return null;
|
) : (
|
||||||
} else {
|
<>
|
||||||
staticRowIndex = rowOrVirtualRow.index / 2;
|
{(virtualRows ?? rows).map((rowOrVirtualRow, staticRowIndex) => {
|
||||||
}
|
let row = rowOrVirtualRow;
|
||||||
} else {
|
if (rowVirtualizer) {
|
||||||
staticRowIndex = rowOrVirtualRow.index;
|
if (renderDetailPanel) {
|
||||||
}
|
if (rowOrVirtualRow.index % 2 === 1) {
|
||||||
row = rows[staticRowIndex];
|
return null;
|
||||||
}
|
} else {
|
||||||
const props = {
|
staticRowIndex = rowOrVirtualRow.index / 2;
|
||||||
...commonRowProps,
|
}
|
||||||
pinnedRowIds,
|
} else {
|
||||||
row,
|
staticRowIndex = rowOrVirtualRow.index;
|
||||||
rowVirtualizer,
|
}
|
||||||
staticRowIndex,
|
row = rows[staticRowIndex];
|
||||||
virtualRow: rowVirtualizer ? rowOrVirtualRow : undefined,
|
}
|
||||||
};
|
const props = {
|
||||||
const key = `${row.id}-${row.index}`;
|
...commonRowProps,
|
||||||
return memoMode === "rows" ? (
|
pinnedRowIds,
|
||||||
<Memo_DataTable_TableBodyRow key={key} {...props} />
|
row,
|
||||||
) : (
|
rowVirtualizer,
|
||||||
<DataTable_TableBodyRow key={key} {...props} />
|
staticRowIndex,
|
||||||
);
|
virtualRow: rowVirtualizer ? rowOrVirtualRow : undefined,
|
||||||
})}
|
};
|
||||||
</>
|
const key = `${row.id}-${row.index}`;
|
||||||
))}
|
return memoMode === "rows" ? (
|
||||||
</TableBody>
|
<Memo_DataTable_TableBodyRow key={key} {...props} />
|
||||||
{!rowPinningDisplayMode?.includes("sticky") &&
|
) : (
|
||||||
getIsSomeRowsPinned("bottom") && (
|
<DataTable_TableBodyRow key={key} {...props} />
|
||||||
<TableBody
|
);
|
||||||
{...tableBodyProps}
|
})}
|
||||||
sx={(theme) => ({
|
</>
|
||||||
bottom: tableFooterHeight - 1,
|
))}
|
||||||
display: layoutMode?.startsWith("grid") ? "grid" : undefined,
|
</TableBody>
|
||||||
position: "sticky",
|
{!rowPinningDisplayMode?.includes("sticky") && getIsSomeRowsPinned("bottom") && (
|
||||||
zIndex: 1,
|
<TableBody
|
||||||
...parseFromValuesOrFunc(tableBodyProps?.sx, theme),
|
{...tableBodyProps}
|
||||||
})}
|
sx={(theme) => ({
|
||||||
>
|
bottom: tableFooterHeight - 1,
|
||||||
{getBottomRows().map((row, staticRowIndex) => {
|
display: layoutMode?.startsWith("grid") ? "grid" : undefined,
|
||||||
const props = {
|
position: "sticky",
|
||||||
...commonRowProps,
|
zIndex: 1,
|
||||||
row,
|
...parseFromValuesOrFunc(tableBodyProps?.sx, theme),
|
||||||
staticRowIndex,
|
})}
|
||||||
};
|
>
|
||||||
return memoMode === "rows" ? (
|
{getBottomRows().map((row, staticRowIndex) => {
|
||||||
<Memo_DataTable_TableBodyRow key={row.id} {...props} />
|
const props = {
|
||||||
) : (
|
...commonRowProps,
|
||||||
<DataTable_TableBodyRow key={row.id} {...props} />
|
row,
|
||||||
);
|
staticRowIndex,
|
||||||
})}
|
};
|
||||||
</TableBody>
|
return memoMode === "rows" ? (
|
||||||
)}
|
<Memo_DataTable_TableBodyRow key={row.id} {...props} />
|
||||||
</>
|
) : (
|
||||||
);
|
<DataTable_TableBodyRow key={row.id} {...props} />
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</TableBody>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
export default DataTable_TableBody;
|
export default DataTable_TableBody;
|
||||||
|
|
||||||
export const Memo_DataTable_TableBody = memo(
|
export const Memo_DataTable_TableBody = memo(
|
||||||
DataTable_TableBody,
|
DataTable_TableBody,
|
||||||
(prev, next) => prev.table.options.data === next.table.options.data,
|
(prev, next) => prev.table.options.data === next.table.options.data
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
import {
|
import { getCommonMRTCellStyles, parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||||
getCommonMRTCellStyles,
|
|
||||||
parseFromValuesOrFunc,
|
|
||||||
} from "@/core/utils/utils";
|
|
||||||
import { useTheme } from "@emotion/react";
|
import { useTheme } from "@emotion/react";
|
||||||
import { Skeleton, TableCell } from "@mui/material";
|
import { Skeleton, TableCell } from "@mui/material";
|
||||||
import { isCellEditable, openEditingCell } from "material-react-table";
|
import { isCellEditable, openEditingCell } from "material-react-table";
|
||||||
@@ -9,300 +6,250 @@ import { memo, useEffect, useMemo, useState } from "react";
|
|||||||
import DataTable_CopyButton from "../buttons/CopyButton";
|
import DataTable_CopyButton from "../buttons/CopyButton";
|
||||||
import DataTable_TableBodyCellValue from "./TableBodyCellValue";
|
import DataTable_TableBodyCellValue from "./TableBodyCellValue";
|
||||||
|
|
||||||
const DataTable_TableBodyCell = ({
|
const DataTable_TableBodyCell = ({ cell, numRows, rowRef, staticColumnIndex, staticRowIndex, table, ...rest }) => {
|
||||||
cell,
|
const theme = useTheme();
|
||||||
numRows,
|
const {
|
||||||
rowRef,
|
getState,
|
||||||
staticColumnIndex,
|
options: {
|
||||||
staticRowIndex,
|
columnResizeDirection,
|
||||||
table,
|
columnResizeMode,
|
||||||
...rest
|
createDisplayMode,
|
||||||
}) => {
|
editDisplayMode,
|
||||||
const theme = useTheme();
|
enableCellActions,
|
||||||
const {
|
enableClickToCopy,
|
||||||
getState,
|
enableColumnOrdering,
|
||||||
options: {
|
enableColumnPinning,
|
||||||
columnResizeDirection,
|
enableGrouping,
|
||||||
columnResizeMode,
|
layoutMode,
|
||||||
createDisplayMode,
|
mrtTheme: { draggingBorderColor, baseBackgroundColor },
|
||||||
editDisplayMode,
|
muiSkeletonProps,
|
||||||
enableCellActions,
|
muiTableBodyCellProps,
|
||||||
enableClickToCopy,
|
|
||||||
enableColumnOrdering,
|
|
||||||
enableColumnPinning,
|
|
||||||
enableGrouping,
|
|
||||||
layoutMode,
|
|
||||||
mrtTheme: { draggingBorderColor, baseBackgroundColor },
|
|
||||||
muiSkeletonProps,
|
|
||||||
muiTableBodyCellProps,
|
|
||||||
},
|
|
||||||
setHoveredColumn,
|
|
||||||
} = table;
|
|
||||||
const {
|
|
||||||
actionCell,
|
|
||||||
columnSizingInfo,
|
|
||||||
creatingRow,
|
|
||||||
density,
|
|
||||||
draggingColumn,
|
|
||||||
draggingRow,
|
|
||||||
editingCell,
|
|
||||||
editingRow,
|
|
||||||
hoveredColumn,
|
|
||||||
hoveredRow,
|
|
||||||
isLoading,
|
|
||||||
showSkeletons,
|
|
||||||
} = getState();
|
|
||||||
const { column, row } = cell;
|
|
||||||
const { columnDef } = column;
|
|
||||||
const { columnDefType } = columnDef;
|
|
||||||
|
|
||||||
const args = { cell, column, row, table };
|
|
||||||
const tableCellProps = {
|
|
||||||
...parseFromValuesOrFunc(muiTableBodyCellProps, args),
|
|
||||||
...parseFromValuesOrFunc(columnDef.muiTableBodyCellProps, args),
|
|
||||||
...rest,
|
|
||||||
};
|
|
||||||
|
|
||||||
const skeletonProps = parseFromValuesOrFunc(muiSkeletonProps, {
|
|
||||||
cell,
|
|
||||||
column,
|
|
||||||
row,
|
|
||||||
table,
|
|
||||||
});
|
|
||||||
|
|
||||||
const [skeletonWidth, setSkeletonWidth] = useState(100);
|
|
||||||
useEffect(() => {
|
|
||||||
if ((!isLoading && !showSkeletons) || skeletonWidth !== 100) return;
|
|
||||||
const size = column.getSize();
|
|
||||||
setSkeletonWidth(
|
|
||||||
columnDefType === "display"
|
|
||||||
? size / 2
|
|
||||||
: Math.round(Math.random() * (size - size / 3) + size / 3),
|
|
||||||
);
|
|
||||||
}, [isLoading, showSkeletons]);
|
|
||||||
|
|
||||||
const draggingBorders = useMemo(() => {
|
|
||||||
const isDraggingColumn = draggingColumn?.id === column.id;
|
|
||||||
const isHoveredColumn = hoveredColumn?.id === column.id;
|
|
||||||
const isDraggingRow = draggingRow?.id === row.id;
|
|
||||||
const isHoveredRow = hoveredRow?.id === row.id;
|
|
||||||
const isFirstColumn = column.getIsFirstColumn();
|
|
||||||
const isLastColumn = column.getIsLastColumn();
|
|
||||||
const isLastRow = numRows && staticRowIndex === numRows - 1;
|
|
||||||
const isResizingColumn = columnSizingInfo.isResizingColumn === column.id;
|
|
||||||
const showResizeBorder =
|
|
||||||
isResizingColumn && columnResizeMode === "onChange";
|
|
||||||
|
|
||||||
const borderStyle = showResizeBorder
|
|
||||||
? `2px solid ${draggingBorderColor} !important`
|
|
||||||
: isDraggingColumn || isDraggingRow
|
|
||||||
? `1px dashed ${theme.palette.grey[500]} !important`
|
|
||||||
: isHoveredColumn || isHoveredRow || isResizingColumn
|
|
||||||
? `2px dashed ${draggingBorderColor} !important`
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
if (showResizeBorder) {
|
|
||||||
return columnResizeDirection === "ltr"
|
|
||||||
? { borderRight: borderStyle }
|
|
||||||
: { borderLeft: borderStyle };
|
|
||||||
}
|
|
||||||
|
|
||||||
return borderStyle
|
|
||||||
? {
|
|
||||||
borderBottom:
|
|
||||||
isDraggingRow || isHoveredRow || (isLastRow && !isResizingColumn)
|
|
||||||
? borderStyle
|
|
||||||
: undefined,
|
|
||||||
borderLeft:
|
|
||||||
isDraggingColumn ||
|
|
||||||
isHoveredColumn ||
|
|
||||||
((isDraggingRow || isHoveredRow) && isFirstColumn)
|
|
||||||
? borderStyle
|
|
||||||
: undefined,
|
|
||||||
borderRight:
|
|
||||||
isDraggingColumn ||
|
|
||||||
isHoveredColumn ||
|
|
||||||
((isDraggingRow || isHoveredRow) && isLastColumn)
|
|
||||||
? borderStyle
|
|
||||||
: undefined,
|
|
||||||
borderTop: isDraggingRow || isHoveredRow ? borderStyle : undefined,
|
|
||||||
}
|
|
||||||
: undefined;
|
|
||||||
}, [
|
|
||||||
columnSizingInfo.isResizingColumn,
|
|
||||||
draggingColumn,
|
|
||||||
draggingRow,
|
|
||||||
hoveredColumn,
|
|
||||||
hoveredRow,
|
|
||||||
staticRowIndex,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const isColumnPinned =
|
|
||||||
enableColumnPinning &&
|
|
||||||
columnDef.columnDefType !== "group" &&
|
|
||||||
column.getIsPinned();
|
|
||||||
|
|
||||||
const isEditable = isCellEditable({ cell, table });
|
|
||||||
|
|
||||||
const isEditing =
|
|
||||||
isEditable &&
|
|
||||||
!["custom", "modal"].includes(editDisplayMode) &&
|
|
||||||
(editDisplayMode === "table" ||
|
|
||||||
editingRow?.id === row.id ||
|
|
||||||
editingCell?.id === cell.id) &&
|
|
||||||
!row.getIsGrouped();
|
|
||||||
|
|
||||||
const isCreating =
|
|
||||||
isEditable && createDisplayMode === "row" && creatingRow?.id === row.id;
|
|
||||||
|
|
||||||
const showClickToCopyButton =
|
|
||||||
(parseFromValuesOrFunc(enableClickToCopy, cell) === true ||
|
|
||||||
parseFromValuesOrFunc(columnDef.enableClickToCopy, cell) === true) &&
|
|
||||||
!["context-menu", false].includes(
|
|
||||||
// @ts-ignore
|
|
||||||
parseFromValuesOrFunc(columnDef.enableClickToCopy, cell),
|
|
||||||
);
|
|
||||||
|
|
||||||
const isRightClickable = parseFromValuesOrFunc(enableCellActions, cell);
|
|
||||||
|
|
||||||
const cellValueProps = {
|
|
||||||
cell,
|
|
||||||
table,
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDoubleClick = (event) => {
|
|
||||||
tableCellProps?.onDoubleClick?.(event);
|
|
||||||
openEditingCell({ cell, table });
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDragEnter = (e) => {
|
|
||||||
tableCellProps?.onDragEnter?.(e);
|
|
||||||
if (enableGrouping && hoveredColumn?.id === "drop-zone") {
|
|
||||||
setHoveredColumn(null);
|
|
||||||
}
|
|
||||||
if (enableColumnOrdering && draggingColumn) {
|
|
||||||
setHoveredColumn(
|
|
||||||
columnDef.enableColumnOrdering !== false ? column : null,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDragOver = (e) => {
|
|
||||||
if (columnDef.enableColumnOrdering !== false) {
|
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleContextMenu = (e) => {
|
|
||||||
tableCellProps?.onContextMenu?.(e);
|
|
||||||
if (isRightClickable) {
|
|
||||||
e.preventDefault();
|
|
||||||
table.setActionCell(cell);
|
|
||||||
table.refs.actionCellRef.current = e.currentTarget;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TableCell
|
|
||||||
data-index={staticColumnIndex}
|
|
||||||
data-pinned={!!isColumnPinned || undefined}
|
|
||||||
{...tableCellProps}
|
|
||||||
onContextMenu={handleContextMenu}
|
|
||||||
onDoubleClick={handleDoubleClick}
|
|
||||||
onDragEnter={handleDragEnter}
|
|
||||||
onDragOver={handleDragOver}
|
|
||||||
sx={(theme) => ({
|
|
||||||
"&:hover": {
|
|
||||||
outline:
|
|
||||||
actionCell?.id === cell.id ||
|
|
||||||
(editDisplayMode === "cell" && isEditable) ||
|
|
||||||
(editDisplayMode === "table" && (isCreating || isEditing))
|
|
||||||
? `1px solid ${theme.palette.grey[500]}`
|
|
||||||
: undefined,
|
|
||||||
textOverflow: "clip",
|
|
||||||
},
|
},
|
||||||
alignItems: layoutMode?.startsWith("grid") ? "center" : undefined,
|
setHoveredColumn,
|
||||||
cursor: isRightClickable
|
} = table;
|
||||||
? "context-menu"
|
const {
|
||||||
: isEditable && editDisplayMode === "cell"
|
actionCell,
|
||||||
? "pointer"
|
columnSizingInfo,
|
||||||
: "inherit",
|
creatingRow,
|
||||||
outline:
|
density,
|
||||||
actionCell?.id === cell.id
|
draggingColumn,
|
||||||
? `1px solid ${theme.palette.grey[500]}`
|
draggingRow,
|
||||||
: undefined,
|
editingCell,
|
||||||
outlineOffset: "-1px",
|
editingRow,
|
||||||
overflow: "hidden",
|
hoveredColumn,
|
||||||
p:
|
hoveredRow,
|
||||||
density === "compact"
|
isLoading,
|
||||||
? columnDefType === "display"
|
showSkeletons,
|
||||||
? "0 0.5rem"
|
} = getState();
|
||||||
: "0.5rem"
|
const { column, row } = cell;
|
||||||
: density === "comfortable"
|
const { columnDef } = column;
|
||||||
? columnDefType === "display"
|
const { columnDefType } = columnDef;
|
||||||
? "0.5rem 0.75rem"
|
|
||||||
: "1rem"
|
|
||||||
: columnDefType === "display"
|
|
||||||
? "1rem 1.25rem"
|
|
||||||
: "1.5rem",
|
|
||||||
|
|
||||||
textOverflow: columnDefType !== "display" ? "ellipsis" : undefined,
|
const args = { cell, column, row, table };
|
||||||
whiteSpace:
|
const tableCellProps = {
|
||||||
row.getIsPinned() || density === "compact" ? "nowrap" : "normal",
|
...parseFromValuesOrFunc(muiTableBodyCellProps, args),
|
||||||
...getCommonMRTCellStyles({
|
...parseFromValuesOrFunc(columnDef.muiTableBodyCellProps, args),
|
||||||
column,
|
...rest,
|
||||||
table,
|
};
|
||||||
tableCellProps,
|
|
||||||
theme,
|
const skeletonProps = parseFromValuesOrFunc(muiSkeletonProps, {
|
||||||
}),
|
cell,
|
||||||
...draggingBorders,
|
column,
|
||||||
backgroundColor: baseBackgroundColor,
|
row,
|
||||||
})}
|
table,
|
||||||
>
|
});
|
||||||
{tableCellProps.children ?? (
|
|
||||||
<>
|
const [skeletonWidth, setSkeletonWidth] = useState(100);
|
||||||
{cell.getIsPlaceholder() ? (
|
useEffect(() => {
|
||||||
(columnDef.PlaceholderCell?.({ cell, column, row, table }) ?? null)
|
if ((!isLoading && !showSkeletons) || skeletonWidth !== 100) return;
|
||||||
) : showSkeletons !== false && (isLoading || showSkeletons) ? (
|
const size = column.getSize();
|
||||||
<Skeleton
|
setSkeletonWidth(
|
||||||
animation="wave"
|
columnDefType === "display" ? size / 2 : Math.round(Math.random() * (size - size / 3) + size / 3)
|
||||||
height={20}
|
);
|
||||||
width={skeletonWidth}
|
}, [isLoading, showSkeletons]);
|
||||||
{...skeletonProps}
|
|
||||||
/>
|
const draggingBorders = useMemo(() => {
|
||||||
) : columnDefType === "display" &&
|
const isDraggingColumn = draggingColumn?.id === column.id;
|
||||||
(["mrt-row-expand", "mrt-row-numbers", "mrt-row-select"].includes(
|
const isHoveredColumn = hoveredColumn?.id === column.id;
|
||||||
column.id,
|
const isDraggingRow = draggingRow?.id === row.id;
|
||||||
) ||
|
const isHoveredRow = hoveredRow?.id === row.id;
|
||||||
!row.getIsGrouped()) ? (
|
const isFirstColumn = column.getIsFirstColumn();
|
||||||
columnDef.Cell?.({
|
const isLastColumn = column.getIsLastColumn();
|
||||||
cell,
|
const isLastRow = numRows && staticRowIndex === numRows - 1;
|
||||||
column,
|
const isResizingColumn = columnSizingInfo.isResizingColumn === column.id;
|
||||||
renderedCellValue: cell.renderValue(),
|
const showResizeBorder = isResizingColumn && columnResizeMode === "onChange";
|
||||||
row,
|
|
||||||
rowRef,
|
const borderStyle = showResizeBorder
|
||||||
staticColumnIndex,
|
? `2px solid ${draggingBorderColor} !important`
|
||||||
staticRowIndex,
|
: isDraggingColumn || isDraggingRow
|
||||||
table,
|
? `1px dashed ${theme.palette.grey[500]} !important`
|
||||||
})
|
: isHoveredColumn || isHoveredRow || isResizingColumn
|
||||||
) : showClickToCopyButton && columnDef.enableClickToCopy !== false ? (
|
? `2px dashed ${draggingBorderColor} !important`
|
||||||
<DataTable_CopyButton cell={cell} table={table}>
|
: undefined;
|
||||||
<DataTable_TableBodyCellValue {...cellValueProps} />
|
|
||||||
</DataTable_CopyButton>
|
if (showResizeBorder) {
|
||||||
) : (
|
return columnResizeDirection === "ltr" ? { borderRight: borderStyle } : { borderLeft: borderStyle };
|
||||||
<DataTable_TableBodyCellValue {...cellValueProps} />
|
}
|
||||||
)}
|
|
||||||
{cell.getIsGrouped() && !columnDef.GroupedCell && (
|
return borderStyle
|
||||||
<> ({row.subRows?.length})</>
|
? {
|
||||||
)}
|
borderBottom:
|
||||||
</>
|
isDraggingRow || isHoveredRow || (isLastRow && !isResizingColumn) ? borderStyle : undefined,
|
||||||
)}
|
borderLeft:
|
||||||
</TableCell>
|
isDraggingColumn || isHoveredColumn || ((isDraggingRow || isHoveredRow) && isFirstColumn)
|
||||||
);
|
? borderStyle
|
||||||
|
: undefined,
|
||||||
|
borderRight:
|
||||||
|
isDraggingColumn || isHoveredColumn || ((isDraggingRow || isHoveredRow) && isLastColumn)
|
||||||
|
? borderStyle
|
||||||
|
: undefined,
|
||||||
|
borderTop: isDraggingRow || isHoveredRow ? borderStyle : undefined,
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
}, [columnSizingInfo.isResizingColumn, draggingColumn, draggingRow, hoveredColumn, hoveredRow, staticRowIndex]);
|
||||||
|
|
||||||
|
const isColumnPinned = enableColumnPinning && columnDef.columnDefType !== "group" && column.getIsPinned();
|
||||||
|
|
||||||
|
const isEditable = isCellEditable({ cell, table });
|
||||||
|
|
||||||
|
const isEditing =
|
||||||
|
isEditable &&
|
||||||
|
!["custom", "modal"].includes(editDisplayMode) &&
|
||||||
|
(editDisplayMode === "table" || editingRow?.id === row.id || editingCell?.id === cell.id) &&
|
||||||
|
!row.getIsGrouped();
|
||||||
|
|
||||||
|
const isCreating = isEditable && createDisplayMode === "row" && creatingRow?.id === row.id;
|
||||||
|
|
||||||
|
const showClickToCopyButton =
|
||||||
|
(parseFromValuesOrFunc(enableClickToCopy, cell) === true ||
|
||||||
|
parseFromValuesOrFunc(columnDef.enableClickToCopy, cell) === true) &&
|
||||||
|
!["context-menu", false].includes(
|
||||||
|
// @ts-ignore
|
||||||
|
parseFromValuesOrFunc(columnDef.enableClickToCopy, cell)
|
||||||
|
);
|
||||||
|
|
||||||
|
const isRightClickable = parseFromValuesOrFunc(enableCellActions, cell);
|
||||||
|
|
||||||
|
const cellValueProps = {
|
||||||
|
cell,
|
||||||
|
table,
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDoubleClick = (event) => {
|
||||||
|
tableCellProps?.onDoubleClick?.(event);
|
||||||
|
openEditingCell({ cell, table });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDragEnter = (e) => {
|
||||||
|
tableCellProps?.onDragEnter?.(e);
|
||||||
|
if (enableGrouping && hoveredColumn?.id === "drop-zone") {
|
||||||
|
setHoveredColumn(null);
|
||||||
|
}
|
||||||
|
if (enableColumnOrdering && draggingColumn) {
|
||||||
|
setHoveredColumn(columnDef.enableColumnOrdering !== false ? column : null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDragOver = (e) => {
|
||||||
|
if (columnDef.enableColumnOrdering !== false) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleContextMenu = (e) => {
|
||||||
|
tableCellProps?.onContextMenu?.(e);
|
||||||
|
if (isRightClickable) {
|
||||||
|
e.preventDefault();
|
||||||
|
table.setActionCell(cell);
|
||||||
|
table.refs.actionCellRef.current = e.currentTarget;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableCell
|
||||||
|
data-index={staticColumnIndex}
|
||||||
|
data-pinned={!!isColumnPinned || undefined}
|
||||||
|
{...tableCellProps}
|
||||||
|
onContextMenu={handleContextMenu}
|
||||||
|
onDoubleClick={handleDoubleClick}
|
||||||
|
onDragEnter={handleDragEnter}
|
||||||
|
onDragOver={handleDragOver}
|
||||||
|
sx={(theme) => ({
|
||||||
|
"&:hover": {
|
||||||
|
outline:
|
||||||
|
actionCell?.id === cell.id ||
|
||||||
|
(editDisplayMode === "cell" && isEditable) ||
|
||||||
|
(editDisplayMode === "table" && (isCreating || isEditing))
|
||||||
|
? `1px solid ${theme.palette.grey[500]}`
|
||||||
|
: undefined,
|
||||||
|
textOverflow: "clip",
|
||||||
|
},
|
||||||
|
alignItems: layoutMode?.startsWith("grid") ? "center" : undefined,
|
||||||
|
cursor: isRightClickable
|
||||||
|
? "context-menu"
|
||||||
|
: isEditable && editDisplayMode === "cell"
|
||||||
|
? "pointer"
|
||||||
|
: "inherit",
|
||||||
|
outline: actionCell?.id === cell.id ? `1px solid ${theme.palette.grey[500]}` : undefined,
|
||||||
|
outlineOffset: "-1px",
|
||||||
|
overflow: "hidden",
|
||||||
|
p:
|
||||||
|
density === "compact"
|
||||||
|
? columnDefType === "display"
|
||||||
|
? "0 0.5rem"
|
||||||
|
: "0.5rem"
|
||||||
|
: density === "comfortable"
|
||||||
|
? columnDefType === "display"
|
||||||
|
? "0.5rem 0.75rem"
|
||||||
|
: "1rem"
|
||||||
|
: columnDefType === "display"
|
||||||
|
? "1rem 1.25rem"
|
||||||
|
: "1.5rem",
|
||||||
|
|
||||||
|
textOverflow: columnDefType !== "display" ? "ellipsis" : undefined,
|
||||||
|
whiteSpace: row.getIsPinned() || density === "compact" ? "nowrap" : "normal",
|
||||||
|
...getCommonMRTCellStyles({
|
||||||
|
column,
|
||||||
|
table,
|
||||||
|
tableCellProps,
|
||||||
|
theme,
|
||||||
|
}),
|
||||||
|
...draggingBorders,
|
||||||
|
backgroundColor: baseBackgroundColor,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{tableCellProps.children ?? (
|
||||||
|
<>
|
||||||
|
{cell.getIsPlaceholder() ? (
|
||||||
|
(columnDef.PlaceholderCell?.({ cell, column, row, table }) ?? null)
|
||||||
|
) : showSkeletons !== false && (isLoading || showSkeletons) ? (
|
||||||
|
<Skeleton animation="wave" height={20} width={skeletonWidth} {...skeletonProps} />
|
||||||
|
) : columnDefType === "display" &&
|
||||||
|
(["mrt-row-expand", "mrt-row-numbers", "mrt-row-select"].includes(column.id) ||
|
||||||
|
!row.getIsGrouped()) ? (
|
||||||
|
columnDef.Cell?.({
|
||||||
|
cell,
|
||||||
|
column,
|
||||||
|
renderedCellValue: cell.renderValue(),
|
||||||
|
row,
|
||||||
|
rowRef,
|
||||||
|
staticColumnIndex,
|
||||||
|
staticRowIndex,
|
||||||
|
table,
|
||||||
|
})
|
||||||
|
) : showClickToCopyButton && columnDef.enableClickToCopy !== false ? (
|
||||||
|
<DataTable_CopyButton cell={cell} table={table}>
|
||||||
|
<DataTable_TableBodyCellValue {...cellValueProps} />
|
||||||
|
</DataTable_CopyButton>
|
||||||
|
) : (
|
||||||
|
<DataTable_TableBodyCellValue {...cellValueProps} />
|
||||||
|
)}
|
||||||
|
{cell.getIsGrouped() && !columnDef.GroupedCell && <> ({row.subRows?.length})</>}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</TableCell>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
export default DataTable_TableBodyCell;
|
export default DataTable_TableBodyCell;
|
||||||
|
|
||||||
export const Memo_DataTable_TableBodyCell = memo(
|
export const Memo_DataTable_TableBodyCell = memo(DataTable_TableBodyCell, (prev, next) => next.cell === prev.cell);
|
||||||
DataTable_TableBodyCell,
|
|
||||||
(prev, next) => next.cell === prev.cell,
|
|
||||||
);
|
|
||||||
|
|||||||
@@ -2,111 +2,102 @@ import { Box } from "@mui/material";
|
|||||||
|
|
||||||
const allowedTypes = ["string", "number"];
|
const allowedTypes = ["string", "number"];
|
||||||
|
|
||||||
const DataTable_TableBodyCellValue = ({
|
const DataTable_TableBodyCellValue = ({ cell, rowRef, staticColumnIndex, staticRowIndex, table }) => {
|
||||||
cell,
|
const {
|
||||||
rowRef,
|
getState,
|
||||||
staticColumnIndex,
|
options: {
|
||||||
staticRowIndex,
|
enableFilterMatchHighlighting,
|
||||||
table,
|
mrtTheme: { matchHighlightColor },
|
||||||
}) => {
|
},
|
||||||
const {
|
} = table;
|
||||||
getState,
|
const { column, row } = cell;
|
||||||
options: {
|
const { columnDef } = column;
|
||||||
enableFilterMatchHighlighting,
|
const { globalFilter, globalFilterFn } = getState();
|
||||||
mrtTheme: { matchHighlightColor },
|
const filterValue = column.getFilterValue();
|
||||||
},
|
|
||||||
} = table;
|
|
||||||
const { column, row } = cell;
|
|
||||||
const { columnDef } = column;
|
|
||||||
const { globalFilter, globalFilterFn } = getState();
|
|
||||||
const filterValue = column.getFilterValue();
|
|
||||||
|
|
||||||
let renderedCellValue =
|
let renderedCellValue =
|
||||||
cell.getIsAggregated() && columnDef.AggregatedCell
|
cell.getIsAggregated() && columnDef.AggregatedCell
|
||||||
? columnDef.AggregatedCell({
|
? columnDef.AggregatedCell({
|
||||||
cell,
|
cell,
|
||||||
column,
|
column,
|
||||||
row,
|
row,
|
||||||
table,
|
table,
|
||||||
})
|
})
|
||||||
: row.getIsGrouped() && !cell.getIsGrouped()
|
: row.getIsGrouped() && !cell.getIsGrouped()
|
||||||
? null
|
? null
|
||||||
: cell.getIsGrouped() && columnDef.GroupedCell
|
: cell.getIsGrouped() && columnDef.GroupedCell
|
||||||
? columnDef.GroupedCell({
|
? columnDef.GroupedCell({
|
||||||
cell,
|
cell,
|
||||||
column,
|
column,
|
||||||
row,
|
row,
|
||||||
table,
|
table,
|
||||||
})
|
})
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const isGroupedValue = renderedCellValue !== undefined;
|
const isGroupedValue = renderedCellValue !== undefined;
|
||||||
|
|
||||||
if (!isGroupedValue) {
|
if (!isGroupedValue) {
|
||||||
renderedCellValue = cell.renderValue();
|
renderedCellValue = cell.renderValue();
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
enableFilterMatchHighlighting &&
|
|
||||||
columnDef.enableFilterMatchHighlighting !== false &&
|
|
||||||
String(renderedCellValue) &&
|
|
||||||
allowedTypes.includes(typeof renderedCellValue) &&
|
|
||||||
((filterValue &&
|
|
||||||
allowedTypes.includes(typeof filterValue) &&
|
|
||||||
["autocomplete", "text"].includes(columnDef.filterVariant)) ||
|
|
||||||
(globalFilter &&
|
|
||||||
allowedTypes.includes(typeof globalFilter) &&
|
|
||||||
column.getCanGlobalFilter()))
|
|
||||||
) {
|
|
||||||
const chunks = highlightWords?.({
|
|
||||||
matchExactly:
|
|
||||||
(filterValue ? columnDef._filterFn : globalFilterFn) !== "fuzzy",
|
|
||||||
query: (filterValue ?? globalFilter ?? "").toString(),
|
|
||||||
text: renderedCellValue?.toString(),
|
|
||||||
});
|
|
||||||
if (chunks?.length > 1 || chunks?.[0]?.match) {
|
|
||||||
renderedCellValue = (
|
|
||||||
<span aria-label={renderedCellValue} role="note">
|
|
||||||
{chunks?.map(({ key, match, text }) => (
|
|
||||||
<Box
|
|
||||||
aria-hidden="true"
|
|
||||||
component="span"
|
|
||||||
key={key}
|
|
||||||
sx={
|
|
||||||
match
|
|
||||||
? {
|
|
||||||
backgroundColor: matchHighlightColor,
|
|
||||||
borderRadius: "2px",
|
|
||||||
color: (theme) =>
|
|
||||||
theme.palette.mode === "dark"
|
|
||||||
? theme.palette.common.white
|
|
||||||
: theme.palette.common.black,
|
|
||||||
padding: "2px 1px",
|
|
||||||
}
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{text}
|
|
||||||
</Box>
|
|
||||||
)) ?? renderedCellValue}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (columnDef.Cell && !isGroupedValue) {
|
if (
|
||||||
renderedCellValue = columnDef.Cell({
|
enableFilterMatchHighlighting &&
|
||||||
cell,
|
columnDef.enableFilterMatchHighlighting !== false &&
|
||||||
column,
|
String(renderedCellValue) &&
|
||||||
renderedCellValue,
|
allowedTypes.includes(typeof renderedCellValue) &&
|
||||||
row,
|
((filterValue &&
|
||||||
rowRef,
|
allowedTypes.includes(typeof filterValue) &&
|
||||||
staticColumnIndex,
|
["autocomplete", "text"].includes(columnDef.filterVariant)) ||
|
||||||
staticRowIndex,
|
(globalFilter && allowedTypes.includes(typeof globalFilter) && column.getCanGlobalFilter()))
|
||||||
table,
|
) {
|
||||||
});
|
const chunks = highlightWords?.({
|
||||||
}
|
matchExactly: (filterValue ? columnDef._filterFn : globalFilterFn) !== "fuzzy",
|
||||||
|
query: (filterValue ?? globalFilter ?? "").toString(),
|
||||||
|
text: renderedCellValue?.toString(),
|
||||||
|
});
|
||||||
|
if (chunks?.length > 1 || chunks?.[0]?.match) {
|
||||||
|
renderedCellValue = (
|
||||||
|
<span aria-label={renderedCellValue} role="note">
|
||||||
|
{chunks?.map(({ key, match, text }) => (
|
||||||
|
<Box
|
||||||
|
aria-hidden="true"
|
||||||
|
component="span"
|
||||||
|
key={key}
|
||||||
|
sx={
|
||||||
|
match
|
||||||
|
? {
|
||||||
|
backgroundColor: matchHighlightColor,
|
||||||
|
borderRadius: "2px",
|
||||||
|
color: (theme) =>
|
||||||
|
theme.palette.mode === "dark"
|
||||||
|
? theme.palette.common.white
|
||||||
|
: theme.palette.common.black,
|
||||||
|
padding: "2px 1px",
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{text}
|
||||||
|
</Box>
|
||||||
|
)) ?? renderedCellValue}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return renderedCellValue;
|
if (columnDef.Cell && !isGroupedValue) {
|
||||||
|
renderedCellValue = columnDef.Cell({
|
||||||
|
cell,
|
||||||
|
column,
|
||||||
|
renderedCellValue,
|
||||||
|
row,
|
||||||
|
rowRef,
|
||||||
|
staticColumnIndex,
|
||||||
|
staticRowIndex,
|
||||||
|
table,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return renderedCellValue;
|
||||||
};
|
};
|
||||||
export default DataTable_TableBodyCellValue;
|
export default DataTable_TableBodyCellValue;
|
||||||
|
|||||||
@@ -1,258 +1,216 @@
|
|||||||
import {
|
import { commonCellBeforeAfterStyles, getCommonPinnedCellStyles, parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||||
commonCellBeforeAfterStyles,
|
|
||||||
getCommonPinnedCellStyles,
|
|
||||||
parseFromValuesOrFunc,
|
|
||||||
} from "@/core/utils/utils";
|
|
||||||
import { useTheme } from "@emotion/react";
|
import { useTheme } from "@emotion/react";
|
||||||
import { TableRow, alpha, darken, lighten } from "@mui/material";
|
import { TableRow, alpha, darken, lighten } from "@mui/material";
|
||||||
import { getIsRowSelected } from "material-react-table";
|
import { getIsRowSelected } from "material-react-table";
|
||||||
import { memo, useMemo, useRef } from "react";
|
import { memo, useMemo, useRef } from "react";
|
||||||
import DataTable_TableBodyCell, {
|
import DataTable_TableBodyCell, { Memo_DataTable_TableBodyCell } from "./TableBodyCell";
|
||||||
Memo_DataTable_TableBodyCell,
|
|
||||||
} from "./TableBodyCell";
|
|
||||||
import DataTable_TableDetailPanel from "./TableDetailPanel";
|
import DataTable_TableDetailPanel from "./TableDetailPanel";
|
||||||
|
|
||||||
const DataTable_TableBodyRow = ({
|
const DataTable_TableBodyRow = ({
|
||||||
columnVirtualizer,
|
columnVirtualizer,
|
||||||
numRows,
|
numRows,
|
||||||
pinnedRowIds,
|
pinnedRowIds,
|
||||||
row,
|
row,
|
||||||
rowVirtualizer,
|
rowVirtualizer,
|
||||||
staticRowIndex,
|
staticRowIndex,
|
||||||
table,
|
table,
|
||||||
virtualRow,
|
virtualRow,
|
||||||
...rest
|
...rest
|
||||||
}) => {
|
}) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
getState,
|
getState,
|
||||||
options: {
|
options: {
|
||||||
enableRowOrdering,
|
enableRowOrdering,
|
||||||
enableRowPinning,
|
enableRowPinning,
|
||||||
enableStickyFooter,
|
enableStickyFooter,
|
||||||
enableStickyHeader,
|
enableStickyHeader,
|
||||||
layoutMode,
|
layoutMode,
|
||||||
memoMode,
|
memoMode,
|
||||||
mrtTheme: {
|
mrtTheme: { baseBackgroundColor, pinnedRowBackgroundColor, selectedRowBackgroundColor },
|
||||||
baseBackgroundColor,
|
muiTableBodyRowProps,
|
||||||
pinnedRowBackgroundColor,
|
renderDetailPanel,
|
||||||
selectedRowBackgroundColor,
|
rowPinningDisplayMode,
|
||||||
},
|
},
|
||||||
muiTableBodyRowProps,
|
refs: { tableFooterRef, tableHeadRef },
|
||||||
renderDetailPanel,
|
setHoveredRow,
|
||||||
rowPinningDisplayMode,
|
} = table;
|
||||||
},
|
const { density, draggingColumn, draggingRow, editingCell, editingRow, hoveredRow, isFullScreen, rowPinning } =
|
||||||
refs: { tableFooterRef, tableHeadRef },
|
getState();
|
||||||
setHoveredRow,
|
|
||||||
} = table;
|
|
||||||
const {
|
|
||||||
density,
|
|
||||||
draggingColumn,
|
|
||||||
draggingRow,
|
|
||||||
editingCell,
|
|
||||||
editingRow,
|
|
||||||
hoveredRow,
|
|
||||||
isFullScreen,
|
|
||||||
rowPinning,
|
|
||||||
} = getState();
|
|
||||||
|
|
||||||
const visibleCells = row.getVisibleCells();
|
const visibleCells = row.getVisibleCells();
|
||||||
|
|
||||||
const { virtualColumns, virtualPaddingLeft, virtualPaddingRight } =
|
const { virtualColumns, virtualPaddingLeft, virtualPaddingRight } = columnVirtualizer ?? {};
|
||||||
columnVirtualizer ?? {};
|
|
||||||
|
|
||||||
const isRowSelected = getIsRowSelected({ row, table });
|
const isRowSelected = getIsRowSelected({ row, table });
|
||||||
const isRowPinned = enableRowPinning && row.getIsPinned();
|
const isRowPinned = enableRowPinning && row.getIsPinned();
|
||||||
const isDraggingRow = draggingRow?.id === row.id;
|
const isDraggingRow = draggingRow?.id === row.id;
|
||||||
const isHoveredRow = hoveredRow?.id === row.id;
|
const isHoveredRow = hoveredRow?.id === row.id;
|
||||||
|
|
||||||
const tableRowProps = {
|
const tableRowProps = {
|
||||||
...parseFromValuesOrFunc(muiTableBodyRowProps, {
|
...parseFromValuesOrFunc(muiTableBodyRowProps, {
|
||||||
row,
|
row,
|
||||||
staticRowIndex,
|
staticRowIndex,
|
||||||
table,
|
table,
|
||||||
}),
|
}),
|
||||||
...rest,
|
...rest,
|
||||||
};
|
};
|
||||||
|
|
||||||
const [bottomPinnedIndex, topPinnedIndex] = useMemo(() => {
|
const [bottomPinnedIndex, topPinnedIndex] = useMemo(() => {
|
||||||
if (
|
if (!enableRowPinning || !rowPinningDisplayMode?.includes("sticky") || !pinnedRowIds || !row.getIsPinned())
|
||||||
!enableRowPinning ||
|
return [];
|
||||||
!rowPinningDisplayMode?.includes("sticky") ||
|
return [[...pinnedRowIds].reverse().indexOf(row.id), pinnedRowIds.indexOf(row.id)];
|
||||||
!pinnedRowIds ||
|
}, [pinnedRowIds, rowPinning]);
|
||||||
!row.getIsPinned()
|
|
||||||
)
|
|
||||||
return [];
|
|
||||||
return [
|
|
||||||
[...pinnedRowIds].reverse().indexOf(row.id),
|
|
||||||
pinnedRowIds.indexOf(row.id),
|
|
||||||
];
|
|
||||||
}, [pinnedRowIds, rowPinning]);
|
|
||||||
|
|
||||||
const tableHeadHeight =
|
const tableHeadHeight = ((enableStickyHeader || isFullScreen) && tableHeadRef.current?.clientHeight) || 0;
|
||||||
((enableStickyHeader || isFullScreen) &&
|
const tableFooterHeight = (enableStickyFooter && tableFooterRef.current?.clientHeight) || 0;
|
||||||
tableHeadRef.current?.clientHeight) ||
|
|
||||||
0;
|
|
||||||
const tableFooterHeight =
|
|
||||||
(enableStickyFooter && tableFooterRef.current?.clientHeight) || 0;
|
|
||||||
|
|
||||||
const sx = parseFromValuesOrFunc(tableRowProps?.sx, theme);
|
const sx = parseFromValuesOrFunc(tableRowProps?.sx, theme);
|
||||||
|
|
||||||
const defaultRowHeight =
|
const defaultRowHeight = density === "compact" ? 37 : density === "comfortable" ? 53 : 69;
|
||||||
density === "compact" ? 37 : density === "comfortable" ? 53 : 69;
|
|
||||||
|
|
||||||
const customRowHeight =
|
const customRowHeight =
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
parseInt(tableRowProps?.style?.height ?? sx?.height, 10) || undefined;
|
parseInt(tableRowProps?.style?.height ?? sx?.height, 10) || undefined;
|
||||||
|
|
||||||
const rowHeight = customRowHeight || defaultRowHeight;
|
const rowHeight = customRowHeight || defaultRowHeight;
|
||||||
|
|
||||||
const handleDragEnter = (_e) => {
|
const handleDragEnter = (_e) => {
|
||||||
if (enableRowOrdering && draggingRow) {
|
if (enableRowOrdering && draggingRow) {
|
||||||
setHoveredRow(row);
|
setHoveredRow(row);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDragOver = (e) => {
|
const handleDragOver = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
};
|
};
|
||||||
|
|
||||||
const rowRef = useRef(null);
|
const rowRef = useRef(null);
|
||||||
|
|
||||||
const cellHighlightColor = isRowSelected
|
const cellHighlightColor = isRowSelected
|
||||||
? selectedRowBackgroundColor
|
? selectedRowBackgroundColor
|
||||||
: isRowPinned
|
: isRowPinned
|
||||||
? pinnedRowBackgroundColor
|
? pinnedRowBackgroundColor
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const cellHighlightColorHover =
|
const cellHighlightColorHover =
|
||||||
tableRowProps?.hover !== false
|
tableRowProps?.hover !== false
|
||||||
? isRowSelected
|
? isRowSelected
|
||||||
? cellHighlightColor
|
? cellHighlightColor
|
||||||
: theme.palette.mode === "dark"
|
: theme.palette.mode === "dark"
|
||||||
? `${lighten(baseBackgroundColor, 0.2)}`
|
? `${lighten(baseBackgroundColor, 0.2)}`
|
||||||
: `${darken(baseBackgroundColor, 0.2)}`
|
: `${darken(baseBackgroundColor, 0.2)}`
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TableRow
|
<TableRow
|
||||||
data-index={renderDetailPanel ? staticRowIndex * 2 : staticRowIndex}
|
data-index={renderDetailPanel ? staticRowIndex * 2 : staticRowIndex}
|
||||||
data-pinned={!!isRowPinned || undefined}
|
data-pinned={!!isRowPinned || undefined}
|
||||||
data-selected={isRowSelected || undefined}
|
data-selected={isRowSelected || undefined}
|
||||||
onDragEnter={handleDragEnter}
|
onDragEnter={handleDragEnter}
|
||||||
onDragOver={handleDragOver}
|
onDragOver={handleDragOver}
|
||||||
ref={(node) => {
|
ref={(node) => {
|
||||||
if (node) {
|
if (node) {
|
||||||
rowRef.current = node;
|
rowRef.current = node;
|
||||||
rowVirtualizer?.measureElement(node);
|
rowVirtualizer?.measureElement(node);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
selected={isRowSelected}
|
selected={isRowSelected}
|
||||||
{...tableRowProps}
|
{...tableRowProps}
|
||||||
style={{
|
style={{
|
||||||
transform: virtualRow
|
transform: virtualRow ? `translateY(${virtualRow.start}px)` : undefined,
|
||||||
? `translateY(${virtualRow.start}px)`
|
...tableRowProps?.style,
|
||||||
: undefined,
|
}}
|
||||||
...tableRowProps?.style,
|
sx={(theme) => ({
|
||||||
}}
|
"&:hover td:after": cellHighlightColorHover
|
||||||
sx={(theme) => ({
|
? {
|
||||||
"&:hover td:after": cellHighlightColorHover
|
backgroundColor: alpha(cellHighlightColorHover, 0.3),
|
||||||
? {
|
...commonCellBeforeAfterStyles,
|
||||||
backgroundColor: alpha(cellHighlightColorHover, 0.3),
|
}
|
||||||
...commonCellBeforeAfterStyles,
|
: undefined,
|
||||||
}
|
bottom:
|
||||||
: undefined,
|
!virtualRow && bottomPinnedIndex !== undefined && isRowPinned
|
||||||
bottom:
|
? `${bottomPinnedIndex * rowHeight + (enableStickyFooter ? tableFooterHeight - 1 : 0)}px`
|
||||||
!virtualRow && bottomPinnedIndex !== undefined && isRowPinned
|
: undefined,
|
||||||
? `${bottomPinnedIndex * rowHeight + (enableStickyFooter ? tableFooterHeight - 1 : 0)}px`
|
boxSizing: "border-box",
|
||||||
: undefined,
|
display: layoutMode?.startsWith("grid") ? "flex" : undefined,
|
||||||
boxSizing: "border-box",
|
opacity: isRowPinned ? 0.97 : isDraggingRow || isHoveredRow ? 0.5 : 1,
|
||||||
display: layoutMode?.startsWith("grid") ? "flex" : undefined,
|
position: virtualRow
|
||||||
opacity: isRowPinned ? 0.97 : isDraggingRow || isHoveredRow ? 0.5 : 1,
|
? "absolute"
|
||||||
position: virtualRow
|
: rowPinningDisplayMode?.includes("sticky") && isRowPinned
|
||||||
? "absolute"
|
? "sticky"
|
||||||
: rowPinningDisplayMode?.includes("sticky") && isRowPinned
|
: "relative",
|
||||||
? "sticky"
|
td: {
|
||||||
: "relative",
|
...getCommonPinnedCellStyles({ table, theme }),
|
||||||
td: {
|
},
|
||||||
...getCommonPinnedCellStyles({ table, theme }),
|
"td:after": cellHighlightColor
|
||||||
},
|
? {
|
||||||
"td:after": cellHighlightColor
|
backgroundColor: cellHighlightColor,
|
||||||
? {
|
...commonCellBeforeAfterStyles,
|
||||||
backgroundColor: cellHighlightColor,
|
}
|
||||||
...commonCellBeforeAfterStyles,
|
: undefined,
|
||||||
}
|
top: virtualRow
|
||||||
: undefined,
|
? 0
|
||||||
top: virtualRow
|
: topPinnedIndex !== undefined && isRowPinned
|
||||||
? 0
|
? `${
|
||||||
: topPinnedIndex !== undefined && isRowPinned
|
topPinnedIndex * rowHeight +
|
||||||
? `${
|
(enableStickyHeader || isFullScreen ? tableHeadHeight - 1 : 0)
|
||||||
topPinnedIndex * rowHeight +
|
}px`
|
||||||
(enableStickyHeader || isFullScreen ? tableHeadHeight - 1 : 0)
|
: undefined,
|
||||||
}px`
|
transition: virtualRow ? "none" : "all 150ms ease-in-out",
|
||||||
: undefined,
|
width: "100%",
|
||||||
transition: virtualRow ? "none" : "all 150ms ease-in-out",
|
zIndex: rowPinningDisplayMode?.includes("sticky") && isRowPinned ? 2 : 0,
|
||||||
width: "100%",
|
...sx,
|
||||||
zIndex:
|
})}
|
||||||
rowPinningDisplayMode?.includes("sticky") && isRowPinned ? 2 : 0,
|
>
|
||||||
...sx,
|
{virtualPaddingLeft ? <td style={{ display: "flex", width: virtualPaddingLeft }} /> : null}
|
||||||
})}
|
{(virtualColumns ?? visibleCells).map((cellOrVirtualCell, staticColumnIndex) => {
|
||||||
>
|
let cell = cellOrVirtualCell;
|
||||||
{virtualPaddingLeft ? (
|
if (columnVirtualizer) {
|
||||||
<td style={{ display: "flex", width: virtualPaddingLeft }} />
|
staticColumnIndex = cellOrVirtualCell.index;
|
||||||
) : null}
|
cell = visibleCells[staticColumnIndex];
|
||||||
{(virtualColumns ?? visibleCells).map(
|
}
|
||||||
(cellOrVirtualCell, staticColumnIndex) => {
|
const props = {
|
||||||
let cell = cellOrVirtualCell;
|
cell,
|
||||||
if (columnVirtualizer) {
|
numRows,
|
||||||
staticColumnIndex = cellOrVirtualCell.index;
|
rowRef,
|
||||||
cell = visibleCells[staticColumnIndex];
|
staticColumnIndex,
|
||||||
}
|
staticRowIndex,
|
||||||
const props = {
|
table,
|
||||||
cell,
|
};
|
||||||
numRows,
|
return cell ? (
|
||||||
rowRef,
|
memoMode === "cells" &&
|
||||||
staticColumnIndex,
|
cell.column.columnDef.columnDefType === "data" &&
|
||||||
staticRowIndex,
|
!draggingColumn &&
|
||||||
table,
|
!draggingRow &&
|
||||||
};
|
editingCell?.id !== cell.id &&
|
||||||
return cell ? (
|
editingRow?.id !== row.id ? (
|
||||||
memoMode === "cells" &&
|
<Memo_DataTable_TableBodyCell key={cell.id} {...props} />
|
||||||
cell.column.columnDef.columnDefType === "data" &&
|
) : (
|
||||||
!draggingColumn &&
|
<DataTable_TableBodyCell key={cell.id} {...props} />
|
||||||
!draggingRow &&
|
)
|
||||||
editingCell?.id !== cell.id &&
|
) : null;
|
||||||
editingRow?.id !== row.id ? (
|
})}
|
||||||
<Memo_DataTable_TableBodyCell key={cell.id} {...props} />
|
{virtualPaddingRight ? <td style={{ display: "flex", width: virtualPaddingRight }} /> : null}
|
||||||
) : (
|
</TableRow>
|
||||||
<DataTable_TableBodyCell key={cell.id} {...props} />
|
{renderDetailPanel && !row.getIsGrouped() && (
|
||||||
)
|
<DataTable_TableDetailPanel
|
||||||
) : null;
|
parentRowRef={rowRef}
|
||||||
},
|
row={row}
|
||||||
)}
|
rowVirtualizer={rowVirtualizer}
|
||||||
{virtualPaddingRight ? (
|
staticRowIndex={staticRowIndex}
|
||||||
<td style={{ display: "flex", width: virtualPaddingRight }} />
|
table={table}
|
||||||
) : null}
|
virtualRow={virtualRow}
|
||||||
</TableRow>
|
/>
|
||||||
{renderDetailPanel && !row.getIsGrouped() && (
|
)}
|
||||||
<DataTable_TableDetailPanel
|
</>
|
||||||
parentRowRef={rowRef}
|
);
|
||||||
row={row}
|
|
||||||
rowVirtualizer={rowVirtualizer}
|
|
||||||
staticRowIndex={staticRowIndex}
|
|
||||||
table={table}
|
|
||||||
virtualRow={virtualRow}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
export default DataTable_TableBodyRow;
|
export default DataTable_TableBodyRow;
|
||||||
|
|
||||||
export const Memo_DataTable_TableBodyRow = memo(
|
export const Memo_DataTable_TableBodyRow = memo(
|
||||||
DataTable_TableBodyRow,
|
DataTable_TableBodyRow,
|
||||||
(prev, next) =>
|
(prev, next) => prev.row === next.row && prev.staticRowIndex === next.staticRowIndex
|
||||||
prev.row === next.row && prev.staticRowIndex === next.staticRowIndex,
|
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,90 +2,86 @@ import { parseFromValuesOrFunc } from "@/core/utils/utils";
|
|||||||
import { Collapse, TableCell, TableRow } from "@mui/material";
|
import { Collapse, TableCell, TableRow } from "@mui/material";
|
||||||
|
|
||||||
const DataTable_TableDetailPanel = ({
|
const DataTable_TableDetailPanel = ({
|
||||||
parentRowRef,
|
parentRowRef,
|
||||||
row,
|
|
||||||
rowVirtualizer,
|
|
||||||
staticRowIndex,
|
|
||||||
table,
|
|
||||||
virtualRow,
|
|
||||||
...rest
|
|
||||||
}) => {
|
|
||||||
const {
|
|
||||||
getState,
|
|
||||||
getVisibleLeafColumns,
|
|
||||||
options: {
|
|
||||||
layoutMode,
|
|
||||||
mrtTheme: { baseBackgroundColor },
|
|
||||||
muiDetailPanelProps,
|
|
||||||
muiTableBodyRowProps,
|
|
||||||
renderDetailPanel,
|
|
||||||
},
|
|
||||||
} = table;
|
|
||||||
const { isLoading } = getState();
|
|
||||||
|
|
||||||
const tableRowProps = parseFromValuesOrFunc(muiTableBodyRowProps, {
|
|
||||||
isDetailPanel: true,
|
|
||||||
row,
|
row,
|
||||||
|
rowVirtualizer,
|
||||||
staticRowIndex,
|
staticRowIndex,
|
||||||
table,
|
table,
|
||||||
});
|
virtualRow,
|
||||||
|
...rest
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
getState,
|
||||||
|
getVisibleLeafColumns,
|
||||||
|
options: {
|
||||||
|
layoutMode,
|
||||||
|
mrtTheme: { baseBackgroundColor },
|
||||||
|
muiDetailPanelProps,
|
||||||
|
muiTableBodyRowProps,
|
||||||
|
renderDetailPanel,
|
||||||
|
},
|
||||||
|
} = table;
|
||||||
|
const { isLoading } = getState();
|
||||||
|
|
||||||
const tableCellProps = {
|
const tableRowProps = parseFromValuesOrFunc(muiTableBodyRowProps, {
|
||||||
...parseFromValuesOrFunc(muiDetailPanelProps, {
|
isDetailPanel: true,
|
||||||
row,
|
row,
|
||||||
table,
|
staticRowIndex,
|
||||||
}),
|
table,
|
||||||
...rest,
|
});
|
||||||
};
|
|
||||||
|
|
||||||
const DetailPanel = !isLoading && renderDetailPanel?.({ row, table });
|
const tableCellProps = {
|
||||||
|
...parseFromValuesOrFunc(muiDetailPanelProps, {
|
||||||
|
row,
|
||||||
|
table,
|
||||||
|
}),
|
||||||
|
...rest,
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
const DetailPanel = !isLoading && renderDetailPanel?.({ row, table });
|
||||||
<TableRow
|
|
||||||
className="Mui-TableBodyCell-DetailPanel"
|
return (
|
||||||
data-index={renderDetailPanel ? staticRowIndex * 2 + 1 : staticRowIndex}
|
<TableRow
|
||||||
ref={(node) => {
|
className="Mui-TableBodyCell-DetailPanel"
|
||||||
if (node) {
|
data-index={renderDetailPanel ? staticRowIndex * 2 + 1 : staticRowIndex}
|
||||||
rowVirtualizer?.measureElement?.(node);
|
ref={(node) => {
|
||||||
}
|
if (node) {
|
||||||
}}
|
rowVirtualizer?.measureElement?.(node);
|
||||||
{...tableRowProps}
|
}
|
||||||
sx={(theme) => ({
|
}}
|
||||||
display: layoutMode?.startsWith("grid") ? "flex" : undefined,
|
{...tableRowProps}
|
||||||
position: virtualRow ? "absolute" : undefined,
|
sx={(theme) => ({
|
||||||
top: virtualRow
|
display: layoutMode?.startsWith("grid") ? "flex" : undefined,
|
||||||
? `${parentRowRef.current?.getBoundingClientRect()?.height}px`
|
position: virtualRow ? "absolute" : undefined,
|
||||||
: undefined,
|
top: virtualRow ? `${parentRowRef.current?.getBoundingClientRect()?.height}px` : undefined,
|
||||||
transform: virtualRow
|
transform: virtualRow ? `translateY(${virtualRow?.start}px)` : undefined,
|
||||||
? `translateY(${virtualRow?.start}px)`
|
width: "100%",
|
||||||
: undefined,
|
...parseFromValuesOrFunc(tableRowProps?.sx, theme),
|
||||||
width: "100%",
|
})}
|
||||||
...parseFromValuesOrFunc(tableRowProps?.sx, theme),
|
>
|
||||||
})}
|
<TableCell
|
||||||
>
|
className="Mui-TableBodyCell-DetailPanel"
|
||||||
<TableCell
|
colSpan={getVisibleLeafColumns().length}
|
||||||
className="Mui-TableBodyCell-DetailPanel"
|
{...tableCellProps}
|
||||||
colSpan={getVisibleLeafColumns().length}
|
sx={(theme) => ({
|
||||||
{...tableCellProps}
|
backgroundColor: virtualRow ? baseBackgroundColor : undefined,
|
||||||
sx={(theme) => ({
|
borderBottom: !row.getIsExpanded() ? "none" : undefined,
|
||||||
backgroundColor: virtualRow ? baseBackgroundColor : undefined,
|
display: layoutMode?.startsWith("grid") ? "flex" : undefined,
|
||||||
borderBottom: !row.getIsExpanded() ? "none" : undefined,
|
py: !!DetailPanel && row.getIsExpanded() ? "1rem" : 0,
|
||||||
display: layoutMode?.startsWith("grid") ? "flex" : undefined,
|
transition: !virtualRow ? "all 150ms ease-in-out" : undefined,
|
||||||
py: !!DetailPanel && row.getIsExpanded() ? "1rem" : 0,
|
width: `100%`,
|
||||||
transition: !virtualRow ? "all 150ms ease-in-out" : undefined,
|
...parseFromValuesOrFunc(tableCellProps?.sx, theme),
|
||||||
width: `100%`,
|
})}
|
||||||
...parseFromValuesOrFunc(tableCellProps?.sx, theme),
|
>
|
||||||
})}
|
{virtualRow ? (
|
||||||
>
|
row.getIsExpanded() && DetailPanel
|
||||||
{virtualRow ? (
|
) : (
|
||||||
row.getIsExpanded() && DetailPanel
|
<Collapse in={row.getIsExpanded()} mountOnEnter unmountOnExit>
|
||||||
) : (
|
{DetailPanel}
|
||||||
<Collapse in={row.getIsExpanded()} mountOnEnter unmountOnExit>
|
</Collapse>
|
||||||
{DetailPanel}
|
)}
|
||||||
</Collapse>
|
</TableCell>
|
||||||
)}
|
</TableRow>
|
||||||
</TableCell>
|
);
|
||||||
</TableRow>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
export default DataTable_TableDetailPanel;
|
export default DataTable_TableDetailPanel;
|
||||||
|
|||||||
@@ -1,74 +1,68 @@
|
|||||||
import {
|
import { getCommonTooltipProps, parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||||
getCommonTooltipProps,
|
|
||||||
parseFromValuesOrFunc,
|
|
||||||
} from "@/core/utils/utils";
|
|
||||||
import { Button, Tooltip } from "@mui/material";
|
import { Button, Tooltip } from "@mui/material";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
const DataTable_CopyButton = ({ cell, table, ...rest }) => {
|
const DataTable_CopyButton = ({ cell, table, ...rest }) => {
|
||||||
const {
|
const {
|
||||||
options: { localization, muiCopyButtonProps },
|
options: { localization, muiCopyButtonProps },
|
||||||
} = table;
|
} = table;
|
||||||
const { column, row } = cell;
|
const { column, row } = cell;
|
||||||
const { columnDef } = column;
|
const { columnDef } = column;
|
||||||
|
|
||||||
const [copied, setCopied] = useState(false);
|
const [copied, setCopied] = useState(false);
|
||||||
|
|
||||||
const handleCopy = (event, text) => {
|
const handleCopy = (event, text) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
navigator.clipboard.writeText(text);
|
navigator.clipboard.writeText(text);
|
||||||
setCopied(true);
|
setCopied(true);
|
||||||
setTimeout(() => setCopied(false), 4000);
|
setTimeout(() => setCopied(false), 4000);
|
||||||
};
|
};
|
||||||
|
|
||||||
const buttonProps = {
|
const buttonProps = {
|
||||||
...parseFromValuesOrFunc(muiCopyButtonProps, {
|
...parseFromValuesOrFunc(muiCopyButtonProps, {
|
||||||
cell,
|
cell,
|
||||||
column,
|
column,
|
||||||
row,
|
row,
|
||||||
table,
|
table,
|
||||||
}),
|
}),
|
||||||
...parseFromValuesOrFunc(columnDef.muiCopyButtonProps, {
|
...parseFromValuesOrFunc(columnDef.muiCopyButtonProps, {
|
||||||
cell,
|
cell,
|
||||||
column,
|
column,
|
||||||
row,
|
row,
|
||||||
table,
|
table,
|
||||||
}),
|
}),
|
||||||
...rest,
|
...rest,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
{...getCommonTooltipProps("top")}
|
{...getCommonTooltipProps("top")}
|
||||||
title={
|
title={buttonProps?.title ?? (copied ? localization.copiedToClipboard : localization.clickToCopy)}
|
||||||
buttonProps?.title ??
|
>
|
||||||
(copied ? localization.copiedToClipboard : localization.clickToCopy)
|
<Button
|
||||||
}
|
onClick={(e) => handleCopy(e, cell.getValue())}
|
||||||
>
|
size="small"
|
||||||
<Button
|
type="button"
|
||||||
onClick={(e) => handleCopy(e, cell.getValue())}
|
variant="text"
|
||||||
size="small"
|
{...buttonProps}
|
||||||
type="button"
|
sx={(theme) => ({
|
||||||
variant="text"
|
backgroundColor: "transparent",
|
||||||
{...buttonProps}
|
border: "none",
|
||||||
sx={(theme) => ({
|
color: "inherit",
|
||||||
backgroundColor: "transparent",
|
cursor: "copy",
|
||||||
border: "none",
|
fontFamily: "inherit",
|
||||||
color: "inherit",
|
fontSize: "inherit",
|
||||||
cursor: "copy",
|
letterSpacing: "inherit",
|
||||||
fontFamily: "inherit",
|
m: "-0.25rem",
|
||||||
fontSize: "inherit",
|
minWidth: "unset",
|
||||||
letterSpacing: "inherit",
|
py: 0,
|
||||||
m: "-0.25rem",
|
textAlign: "inherit",
|
||||||
minWidth: "unset",
|
textTransform: "inherit",
|
||||||
py: 0,
|
...parseFromValuesOrFunc(buttonProps?.sx, theme),
|
||||||
textAlign: "inherit",
|
})}
|
||||||
textTransform: "inherit",
|
title={undefined}
|
||||||
...parseFromValuesOrFunc(buttonProps?.sx, theme),
|
/>
|
||||||
})}
|
</Tooltip>
|
||||||
title={undefined}
|
);
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
export default DataTable_CopyButton;
|
export default DataTable_CopyButton;
|
||||||
|
|||||||
@@ -1,52 +1,46 @@
|
|||||||
import {
|
import { getCommonTooltipProps, parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||||
getCommonTooltipProps,
|
|
||||||
parseFromValuesOrFunc,
|
|
||||||
} from "@/core/utils/utils";
|
|
||||||
import { IconButton, Tooltip } from "@mui/material";
|
import { IconButton, Tooltip } from "@mui/material";
|
||||||
|
|
||||||
const DataTable_GrabHandleButton = ({ location, table, ...rest }) => {
|
const DataTable_GrabHandleButton = ({ location, table, ...rest }) => {
|
||||||
const {
|
const {
|
||||||
options: {
|
options: {
|
||||||
icons: { DragHandleIcon },
|
icons: { DragHandleIcon },
|
||||||
localization,
|
localization,
|
||||||
},
|
},
|
||||||
} = table;
|
} = table;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip
|
<Tooltip {...getCommonTooltipProps("top")} title={rest?.title ?? localization.move}>
|
||||||
{...getCommonTooltipProps("top")}
|
<IconButton
|
||||||
title={rest?.title ?? localization.move}
|
aria-label={rest.title ?? localization.move}
|
||||||
>
|
disableRipple
|
||||||
<IconButton
|
draggable="true"
|
||||||
aria-label={rest.title ?? localization.move}
|
size="small"
|
||||||
disableRipple
|
{...rest}
|
||||||
draggable="true"
|
onClick={(e) => {
|
||||||
size="small"
|
e.stopPropagation();
|
||||||
{...rest}
|
rest?.onClick?.(e);
|
||||||
onClick={(e) => {
|
}}
|
||||||
e.stopPropagation();
|
sx={(theme) => ({
|
||||||
rest?.onClick?.(e);
|
"&:active": {
|
||||||
}}
|
cursor: "grabbing",
|
||||||
sx={(theme) => ({
|
},
|
||||||
"&:active": {
|
"&:hover": {
|
||||||
cursor: "grabbing",
|
backgroundColor: "transparent",
|
||||||
},
|
opacity: 1,
|
||||||
"&:hover": {
|
},
|
||||||
backgroundColor: "transparent",
|
cursor: "grab",
|
||||||
opacity: 1,
|
m: "0 -0.1rem",
|
||||||
},
|
opacity: location === "row" ? 1 : 0.5,
|
||||||
cursor: "grab",
|
p: "2px",
|
||||||
m: "0 -0.1rem",
|
transition: "all 150ms ease-in-out",
|
||||||
opacity: location === "row" ? 1 : 0.5,
|
...parseFromValuesOrFunc(rest?.sx, theme),
|
||||||
p: "2px",
|
})}
|
||||||
transition: "all 150ms ease-in-out",
|
title={undefined}
|
||||||
...parseFromValuesOrFunc(rest?.sx, theme),
|
>
|
||||||
})}
|
<DragHandleIcon />
|
||||||
title={undefined}
|
</IconButton>
|
||||||
>
|
</Tooltip>
|
||||||
<DragHandleIcon />
|
);
|
||||||
</IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
export default DataTable_GrabHandleButton;
|
export default DataTable_GrabHandleButton;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
const DataTable_ToggleFiltersButton = ({ table, ...rest }) => {
|
const DataTable_ToggleFiltersButton = ({ table, ...rest }) => {
|
||||||
return <></>;
|
return <></>;
|
||||||
};
|
};
|
||||||
export default DataTable_ToggleFiltersButton;
|
export default DataTable_ToggleFiltersButton;
|
||||||
|
|||||||
@@ -12,122 +12,121 @@ import ScrollBox from "../../ScrollBox";
|
|||||||
import FilterBodyController from "./FilterBodyController";
|
import FilterBodyController from "./FilterBodyController";
|
||||||
|
|
||||||
function FilterBody({ columns, drawerState, setDrawerState }) {
|
function FilterBody({ columns, drawerState, setDrawerState }) {
|
||||||
const { filterData, setFilterData } = useDataTable();
|
const { filterData, setFilterData } = useDataTable();
|
||||||
const [validationSchema, setValidationSchema] = useState(Yup.object({}));
|
const [validationSchema, setValidationSchema] = useState(Yup.object({}));
|
||||||
|
|
||||||
console.log(filterData);
|
console.log(filterData);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const schema = Yup.object(
|
const schema = Yup.object(
|
||||||
Object.keys(filterData).reduce((acc, key) => {
|
Object.keys(filterData).reduce((acc, key) => {
|
||||||
const initialValue = filterData[key];
|
const initialValue = filterData[key];
|
||||||
if (initialValue.filterMode === "between") {
|
if (initialValue.filterMode === "between") {
|
||||||
acc[key] = Yup.object().shape({
|
acc[key] = Yup.object().shape({
|
||||||
value: Yup.array()
|
value: Yup.array()
|
||||||
.of(Yup.string().nullable())
|
.of(Yup.string().nullable())
|
||||||
.test({
|
.test({
|
||||||
test(value, ctx) {
|
test(value, ctx) {
|
||||||
const [start, end] = value || ["", ""];
|
const [start, end] = value || ["", ""];
|
||||||
if (
|
if (
|
||||||
initialValue.datatype === "numeric" &&
|
initialValue.datatype === "numeric" &&
|
||||||
parseInt(end, 10) <= parseInt(start, 10)
|
parseInt(end, 10) <= parseInt(start, 10)
|
||||||
) {
|
) {
|
||||||
return ctx.createError({
|
return ctx.createError({
|
||||||
message: `مقدار وارده باید بیشتر از (${start}) باشد`,
|
message: `مقدار وارده باید بیشتر از (${start}) باشد`,
|
||||||
|
});
|
||||||
|
} else if ((start && !end) || (!start && end)) {
|
||||||
|
return ctx.createError({
|
||||||
|
message: "این بخش را تکمیل نمایید",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
} else if ((start && !end) || (!start && end)) {
|
}
|
||||||
return ctx.createError({
|
return acc;
|
||||||
message: "این بخش را تکمیل نمایید",
|
}, {})
|
||||||
});
|
);
|
||||||
}
|
|
||||||
return true;
|
setValidationSchema(schema);
|
||||||
},
|
}, [filterData]);
|
||||||
}),
|
|
||||||
});
|
const {
|
||||||
}
|
handleSubmit,
|
||||||
return acc;
|
control,
|
||||||
}, {}),
|
reset,
|
||||||
|
formState: { errors, isDirty, isValid },
|
||||||
|
} = useForm({
|
||||||
|
defaultValues: filterData,
|
||||||
|
resolver: yupResolver(validationSchema),
|
||||||
|
mode: "all",
|
||||||
|
});
|
||||||
|
|
||||||
|
const onSubmit = (data) => {
|
||||||
|
setDrawerState(false);
|
||||||
|
setFilterData(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<FilterHeader setDrawerState={setDrawerState} />
|
||||||
|
{Object.keys(filterData).length > 0 && (
|
||||||
|
<ScrollBox>
|
||||||
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
|
<Box sx={{ px: 2, py: 3 }}>
|
||||||
|
{columns.map((column) =>
|
||||||
|
column.enableColumnFilter ? (
|
||||||
|
column.dependencyId ? (
|
||||||
|
<FilterBodyControllerWithDependency
|
||||||
|
key={column.id}
|
||||||
|
column={column}
|
||||||
|
control={control}
|
||||||
|
errors={errors}
|
||||||
|
reset={reset}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<FilterBodyController
|
||||||
|
key={column.id}
|
||||||
|
column={column}
|
||||||
|
control={control}
|
||||||
|
errors={errors}
|
||||||
|
reset={reset}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
) : null
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
pb: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
variant="contained"
|
||||||
|
size="large"
|
||||||
|
disabled={!isDirty || !isValid}
|
||||||
|
sx={{
|
||||||
|
px: 8,
|
||||||
|
boxShadow: "rgba(0, 0, 0, 0.23) 0px 6px 6px, rgba(0, 0, 0, 0.19) 10px 0px 20px",
|
||||||
|
backgroundColor: "primary2",
|
||||||
|
":hover": {
|
||||||
|
backgroundColor: "primary2",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
اعمال فیلتر
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</form>
|
||||||
|
</ScrollBox>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
setValidationSchema(schema);
|
|
||||||
}, [filterData]);
|
|
||||||
|
|
||||||
const {
|
|
||||||
handleSubmit,
|
|
||||||
control,
|
|
||||||
reset,
|
|
||||||
formState: { errors, isDirty, isValid },
|
|
||||||
} = useForm({
|
|
||||||
defaultValues: filterData,
|
|
||||||
resolver: yupResolver(validationSchema),
|
|
||||||
mode: "all",
|
|
||||||
});
|
|
||||||
|
|
||||||
const onSubmit = (data) => {
|
|
||||||
setDrawerState(false);
|
|
||||||
setFilterData(data);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<FilterHeader setDrawerState={setDrawerState} />
|
|
||||||
{Object.keys(filterData).length > 0 && (
|
|
||||||
<ScrollBox>
|
|
||||||
<form onSubmit={handleSubmit(onSubmit)}>
|
|
||||||
<Box sx={{ px: 2, py: 3 }}>
|
|
||||||
{columns.map((column) =>
|
|
||||||
column.enableColumnFilter ? (
|
|
||||||
column.dependencyId ? (
|
|
||||||
<FilterBodyControllerWithDependency
|
|
||||||
key={column.id}
|
|
||||||
column={column}
|
|
||||||
control={control}
|
|
||||||
errors={errors}
|
|
||||||
reset={reset}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<FilterBodyController
|
|
||||||
key={column.id}
|
|
||||||
column={column}
|
|
||||||
control={control}
|
|
||||||
errors={errors}
|
|
||||||
reset={reset}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
) : null,
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "center",
|
|
||||||
alignItems: "center",
|
|
||||||
pb: 2,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
type="submit"
|
|
||||||
variant="contained"
|
|
||||||
size="large"
|
|
||||||
disabled={!isDirty || !isValid}
|
|
||||||
sx={{
|
|
||||||
px: 8,
|
|
||||||
boxShadow:
|
|
||||||
"rgba(0, 0, 0, 0.23) 0px 6px 6px, rgba(0, 0, 0, 0.19) 10px 0px 20px",
|
|
||||||
backgroundColor: "primary2",
|
|
||||||
":hover": {
|
|
||||||
backgroundColor: "primary2",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
اعمال فیلتر
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
</form>
|
|
||||||
</ScrollBox>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default FilterBody;
|
export default FilterBody;
|
||||||
|
|||||||
@@ -2,24 +2,24 @@ import { Controller, useWatch } from "react-hook-form";
|
|||||||
import FilterBodyField from "./FilterBodyField";
|
import FilterBodyField from "./FilterBodyField";
|
||||||
|
|
||||||
const FilterBodyController = ({ column, control, reset, errors }) => {
|
const FilterBodyController = ({ column, control, reset, errors }) => {
|
||||||
return (
|
return (
|
||||||
<Controller
|
<Controller
|
||||||
name={`${column.id}`}
|
name={`${column.id}`}
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field: { value, name, onBlur, onChange } }) => {
|
render={({ field: { value, name, onBlur, onChange } }) => {
|
||||||
return (
|
return (
|
||||||
<FilterBodyField
|
<FilterBodyField
|
||||||
column={column}
|
column={column}
|
||||||
filterParameters={value}
|
filterParameters={value}
|
||||||
handleChange={onChange}
|
handleChange={onChange}
|
||||||
handleBlur={onBlur}
|
handleBlur={onBlur}
|
||||||
dependencyFieldValue={null}
|
dependencyFieldValue={null}
|
||||||
resetForm={() => reset()}
|
resetForm={() => reset()}
|
||||||
errors={errors}
|
errors={errors}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default FilterBodyController;
|
export default FilterBodyController;
|
||||||
|
|||||||
@@ -1,32 +1,27 @@
|
|||||||
import { Controller, useWatch } from "react-hook-form";
|
import { Controller, useWatch } from "react-hook-form";
|
||||||
import FilterBodyField from "./FilterBodyField";
|
import FilterBodyField from "./FilterBodyField";
|
||||||
|
|
||||||
const FilterBodyControllerWithDependency = ({
|
const FilterBodyControllerWithDependency = ({ column, control, reset, errors }) => {
|
||||||
column,
|
const dependencyField = useWatch({ control, name: column.dependencyId });
|
||||||
control,
|
|
||||||
reset,
|
|
||||||
errors,
|
|
||||||
}) => {
|
|
||||||
const dependencyField = useWatch({ control, name: column.dependencyId });
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Controller
|
<Controller
|
||||||
name={`${column.id}`}
|
name={`${column.id}`}
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field: { value, name, onBlur, onChange } }) => {
|
render={({ field: { value, name, onBlur, onChange } }) => {
|
||||||
return (
|
return (
|
||||||
<FilterBodyField
|
<FilterBodyField
|
||||||
column={column}
|
column={column}
|
||||||
filterParameters={value}
|
filterParameters={value}
|
||||||
handleChange={onChange}
|
handleChange={onChange}
|
||||||
handleBlur={onBlur}
|
handleBlur={onBlur}
|
||||||
dependencyFieldValue={dependencyField}
|
dependencyFieldValue={dependencyField}
|
||||||
resetForm={() => reset()}
|
resetForm={() => reset()}
|
||||||
errors={errors}
|
errors={errors}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default FilterBodyControllerWithDependency;
|
export default FilterBodyControllerWithDependency;
|
||||||
|
|||||||
@@ -8,98 +8,97 @@ import CustomTextFieldRange from "@/core/components/DataTable/filter/fieldsType/
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
const columnFilterModeOptionFa = {
|
const columnFilterModeOptionFa = {
|
||||||
equals: "برابر",
|
equals: "برابر",
|
||||||
notEquals: "نابرابر",
|
notEquals: "نابرابر",
|
||||||
contains: "شامل",
|
contains: "شامل",
|
||||||
lessThan: "کوچکتر",
|
lessThan: "کوچکتر",
|
||||||
greaterThan: "بزرگتر",
|
greaterThan: "بزرگتر",
|
||||||
fuzzy: "فازی",
|
fuzzy: "فازی",
|
||||||
between: "مابین",
|
between: "مابین",
|
||||||
};
|
};
|
||||||
|
|
||||||
function FilterBodyField({
|
function FilterBodyField({
|
||||||
column,
|
column,
|
||||||
filterParameters,
|
filterParameters,
|
||||||
handleChange,
|
handleChange,
|
||||||
handleBlur,
|
handleBlur,
|
||||||
dependencyFieldValue,
|
dependencyFieldValue,
|
||||||
setFieldValue,
|
setFieldValue,
|
||||||
resetForm,
|
resetForm,
|
||||||
errors,
|
errors,
|
||||||
}) {
|
}) {
|
||||||
const [anchorEl, setAnchorEl] = useState(null);
|
const [anchorEl, setAnchorEl] = useState(null);
|
||||||
const defaultFilterTranslation =
|
const defaultFilterTranslation =
|
||||||
columnFilterModeOptionFa[filterParameters.filterMode] ||
|
columnFilterModeOptionFa[filterParameters.filterMode] || filterParameters.filterMode;
|
||||||
filterParameters.filterMode;
|
|
||||||
|
|
||||||
const handleOpenFilterBox = (event) => {
|
const handleOpenFilterBox = (event) => {
|
||||||
setAnchorEl(event.currentTarget);
|
setAnchorEl(event.currentTarget);
|
||||||
};
|
|
||||||
|
|
||||||
const renderField = () => {
|
|
||||||
const commonProps = {
|
|
||||||
column,
|
|
||||||
filterParameters,
|
|
||||||
defaultFilterTranslation,
|
|
||||||
handleOpenFilterBox,
|
|
||||||
dependencyFieldValue,
|
|
||||||
setFieldValue,
|
|
||||||
handleChange,
|
|
||||||
handleBlur,
|
|
||||||
errors,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
switch (filterParameters.datatype) {
|
const renderField = () => {
|
||||||
case "numeric":
|
const commonProps = {
|
||||||
if (filterParameters.filterMode === "between") {
|
column,
|
||||||
return <CustomTextFieldRange {...commonProps} />;
|
filterParameters,
|
||||||
}
|
defaultFilterTranslation,
|
||||||
if (filterParameters.filterMode === "equals") {
|
handleOpenFilterBox,
|
||||||
return column.ColumnSelectComponent ? (
|
dependencyFieldValue,
|
||||||
<column.ColumnSelectComponent {...commonProps} />
|
setFieldValue,
|
||||||
) : (
|
handleChange,
|
||||||
<CustomSelect {...commonProps} />
|
handleBlur,
|
||||||
);
|
errors,
|
||||||
}
|
};
|
||||||
break;
|
|
||||||
case "date":
|
|
||||||
if (filterParameters.filterMode === "equals") {
|
|
||||||
return <CustomDatePicker {...commonProps} />;
|
|
||||||
}
|
|
||||||
if (filterParameters.filterMode === "between") {
|
|
||||||
return <CustomDatePickerRange {...commonProps} />;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "array":
|
switch (filterParameters.datatype) {
|
||||||
if (filterParameters.filterMode === "equals") {
|
case "numeric":
|
||||||
return <CustomSelectMultiple {...commonProps} />;
|
if (filterParameters.filterMode === "between") {
|
||||||
|
return <CustomTextFieldRange {...commonProps} />;
|
||||||
|
}
|
||||||
|
if (filterParameters.filterMode === "equals") {
|
||||||
|
return column.ColumnSelectComponent ? (
|
||||||
|
<column.ColumnSelectComponent {...commonProps} />
|
||||||
|
) : (
|
||||||
|
<CustomSelect {...commonProps} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "date":
|
||||||
|
if (filterParameters.filterMode === "equals") {
|
||||||
|
return <CustomDatePicker {...commonProps} />;
|
||||||
|
}
|
||||||
|
if (filterParameters.filterMode === "between") {
|
||||||
|
return <CustomDatePickerRange {...commonProps} />;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "array":
|
||||||
|
if (filterParameters.filterMode === "equals") {
|
||||||
|
return <CustomSelectMultiple {...commonProps} />;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return <CustomTextField {...commonProps} />;
|
||||||
}
|
}
|
||||||
break;
|
};
|
||||||
|
|
||||||
default:
|
return (
|
||||||
return <CustomTextField {...commonProps} />;
|
<>
|
||||||
}
|
{renderField()}
|
||||||
};
|
{Array.isArray(column.columnFilterModeOptions) && (
|
||||||
|
<FilterOptionList
|
||||||
return (
|
column={column}
|
||||||
<>
|
anchorEl={anchorEl}
|
||||||
{renderField()}
|
setAnchorEl={setAnchorEl}
|
||||||
{Array.isArray(column.columnFilterModeOptions) && (
|
filterParameters={filterParameters}
|
||||||
<FilterOptionList
|
filterType={filterParameters.filterMode}
|
||||||
column={column}
|
handleChange={handleChange}
|
||||||
anchorEl={anchorEl}
|
resetForm={resetForm}
|
||||||
setAnchorEl={setAnchorEl}
|
filterOption={column.columnFilterModeOptions}
|
||||||
filterParameters={filterParameters}
|
columnFilterModeOptionFa={columnFilterModeOptionFa}
|
||||||
filterType={filterParameters.filterMode}
|
/>
|
||||||
handleChange={handleChange}
|
)}
|
||||||
resetForm={resetForm}
|
</>
|
||||||
filterOption={column.columnFilterModeOptions}
|
);
|
||||||
columnFilterModeOptionFa={columnFilterModeOptionFa}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default FilterBodyField;
|
export default FilterBodyField;
|
||||||
|
|||||||
@@ -4,36 +4,34 @@ import { IconButton, Tooltip, Badge } from "@mui/material";
|
|||||||
import useDataTable from "@/lib/hooks/useDataTable";
|
import useDataTable from "@/lib/hooks/useDataTable";
|
||||||
|
|
||||||
function FilterButton({ drawerState, setDrawerState }) {
|
function FilterButton({ drawerState, setDrawerState }) {
|
||||||
const { filterData } = useDataTable();
|
const { filterData } = useDataTable();
|
||||||
const isValueEmpty = (value) => {
|
const isValueEmpty = (value) => {
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
return value.length === 0 || value.every((v) => v === "");
|
return value.length === 0 || value.every((v) => v === "");
|
||||||
}
|
}
|
||||||
return value === "" || value === null || value === undefined;
|
return value === "" || value === null || value === undefined;
|
||||||
};
|
};
|
||||||
const filteredFilterData = Object.values(filterData).filter(
|
const filteredFilterData = Object.values(filterData).filter((filter) => !isValueEmpty(filter.value));
|
||||||
(filter) => !isValueEmpty(filter.value),
|
return (
|
||||||
);
|
<Tooltip title="فیلتر">
|
||||||
return (
|
<IconButton
|
||||||
<Tooltip title="فیلتر">
|
onClick={() => {
|
||||||
<IconButton
|
setDrawerState(!drawerState);
|
||||||
onClick={() => {
|
}}
|
||||||
setDrawerState(!drawerState);
|
aria-label="filter table"
|
||||||
}}
|
>
|
||||||
aria-label="filter table"
|
<Badge
|
||||||
>
|
badgeContent={filteredFilterData.length}
|
||||||
<Badge
|
color="primary"
|
||||||
badgeContent={filteredFilterData.length}
|
anchorOrigin={{
|
||||||
color="primary"
|
vertical: "top",
|
||||||
anchorOrigin={{
|
horizontal: "left",
|
||||||
vertical: "top",
|
}}
|
||||||
horizontal: "left",
|
>
|
||||||
}}
|
<FilterAltIcon />
|
||||||
>
|
</Badge>
|
||||||
<FilterAltIcon />
|
</IconButton>
|
||||||
</Badge>
|
</Tooltip>
|
||||||
</IconButton>
|
);
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
export default FilterButton;
|
export default FilterButton;
|
||||||
|
|||||||
@@ -4,35 +4,35 @@ import { Box, Drawer } from "@mui/material";
|
|||||||
import FilterDrawer from "../../FilterDrawer";
|
import FilterDrawer from "../../FilterDrawer";
|
||||||
|
|
||||||
const FilterCustom = ({ filterData, setFilterData }) => {
|
const FilterCustom = ({ filterData, setFilterData }) => {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
const closeDrawer = useCallback(() => setOpen(false), []);
|
const closeDrawer = useCallback(() => setOpen(false), []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<FilterButton drawerState={open} setDrawerState={setOpen} />
|
<FilterButton drawerState={open} setDrawerState={setOpen} />
|
||||||
<Drawer
|
<Drawer
|
||||||
open={open}
|
open={open}
|
||||||
onClose={() => setOpen(false)}
|
onClose={() => setOpen(false)}
|
||||||
sx={{
|
sx={{
|
||||||
overflowY: "hidden",
|
overflowY: "hidden",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
height: "100%",
|
height: "100%",
|
||||||
zIndex: "1300",
|
zIndex: "1300",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box sx={{ width: { xs: 300, sm: 450 } }}>
|
<Box sx={{ width: { xs: 300, sm: 450 } }}>
|
||||||
{open && (
|
{open && (
|
||||||
<FilterDrawer
|
<FilterDrawer
|
||||||
defaultValues={filterData}
|
defaultValues={filterData}
|
||||||
setFilterData={setFilterData}
|
setFilterData={setFilterData}
|
||||||
closeDrawer={closeDrawer}
|
closeDrawer={closeDrawer}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default FilterCustom;
|
export default FilterCustom;
|
||||||
|
|||||||
@@ -5,31 +5,30 @@ import FilterAltIcon from "@mui/icons-material/FilterAlt";
|
|||||||
import { Box, IconButton, Typography } from "@mui/material";
|
import { Box, IconButton, Typography } from "@mui/material";
|
||||||
|
|
||||||
function FilterHeader({ setDrawerState }) {
|
function FilterHeader({ setDrawerState }) {
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
px: 2,
|
px: 2,
|
||||||
py: 1,
|
py: 1,
|
||||||
backgroundColor: (theme) => theme.palette.primary.main,
|
backgroundColor: (theme) => theme.palette.primary.main,
|
||||||
boxShadow:
|
boxShadow: "rgba(0, 0, 0, 0.19) 0px 10px 20px, rgba(0, 0, 0, 0.23) 0px 6px 6px",
|
||||||
"rgba(0, 0, 0, 0.19) 0px 10px 20px, rgba(0, 0, 0, 0.23) 0px 6px 6px",
|
maxWidth: "450px",
|
||||||
maxWidth: "450px",
|
}}
|
||||||
}}
|
>
|
||||||
>
|
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||||
<Box sx={{ display: "flex", alignItems: "center" }}>
|
<FilterAltIcon sx={{ color: "#fff", mr: 1 }} />
|
||||||
<FilterAltIcon sx={{ color: "#fff", mr: 1 }} />
|
<Typography variant="h6" sx={{ color: "#fff" }}>
|
||||||
<Typography variant="h6" sx={{ color: "#fff" }}>
|
فیلتر
|
||||||
فیلتر
|
</Typography>
|
||||||
</Typography>
|
</Box>
|
||||||
</Box>
|
<IconButton sx={{ color: "#fff" }} onClick={() => setDrawerState(false)}>
|
||||||
<IconButton sx={{ color: "#fff" }} onClick={() => setDrawerState(false)}>
|
<CancelIcon sx={{ fontSize: "1em" }} />
|
||||||
<CancelIcon sx={{ fontSize: "1em" }} />
|
</IconButton>
|
||||||
</IconButton>
|
</Box>
|
||||||
</Box>
|
);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default FilterHeader;
|
export default FilterHeader;
|
||||||
|
|||||||
@@ -4,55 +4,49 @@ import { ListItem, Menu } from "@mui/material";
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
function FilterOptionList({
|
function FilterOptionList({
|
||||||
filterType,
|
filterType,
|
||||||
filterOption,
|
filterOption,
|
||||||
filterParameters,
|
filterParameters,
|
||||||
anchorEl,
|
anchorEl,
|
||||||
columnFilterModeOptionFa,
|
columnFilterModeOptionFa,
|
||||||
setAnchorEl,
|
setAnchorEl,
|
||||||
handleChange,
|
handleChange,
|
||||||
}) {
|
}) {
|
||||||
const [selectedFilter, setSelectedFilter] = useState(filterType);
|
const [selectedFilter, setSelectedFilter] = useState(filterType);
|
||||||
|
|
||||||
const handleChangeItem = (event, index) => {
|
const handleChangeItem = (event, index) => {
|
||||||
handleChange({
|
handleChange({
|
||||||
...filterParameters,
|
...filterParameters,
|
||||||
value: filterOption[index] === "between" ? ["", ""] : "",
|
value: filterOption[index] === "between" ? ["", ""] : "",
|
||||||
filterMode: filterOption[index],
|
filterMode: filterOption[index],
|
||||||
});
|
});
|
||||||
setSelectedFilter(filterOption[index]);
|
setSelectedFilter(filterOption[index]);
|
||||||
setAnchorEl(null);
|
setAnchorEl(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCloseFilterBox = () => {
|
const handleCloseFilterBox = () => {
|
||||||
setAnchorEl(null);
|
setAnchorEl(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu
|
<Menu id="simple-menu" anchorEl={anchorEl} keepMounted open={Boolean(anchorEl)} onClose={handleCloseFilterBox}>
|
||||||
id="simple-menu"
|
{filterOption.map((option, index) => (
|
||||||
anchorEl={anchorEl}
|
<ListItem
|
||||||
keepMounted
|
key={index}
|
||||||
open={Boolean(anchorEl)}
|
onClick={(event) => handleChangeItem(event, index)}
|
||||||
onClose={handleCloseFilterBox}
|
selected={option === selectedFilter}
|
||||||
>
|
sx={{
|
||||||
{filterOption.map((option, index) => (
|
cursor: "pointer",
|
||||||
<ListItem
|
width: "100px",
|
||||||
key={index}
|
display: "flex",
|
||||||
onClick={(event) => handleChangeItem(event, index)}
|
justifyContent: "center",
|
||||||
selected={option === selectedFilter}
|
}}
|
||||||
sx={{
|
>
|
||||||
cursor: "pointer",
|
{columnFilterModeOptionFa[option]}
|
||||||
width: "100px",
|
</ListItem>
|
||||||
display: "flex",
|
))}
|
||||||
justifyContent: "center",
|
</Menu>
|
||||||
}}
|
);
|
||||||
>
|
|
||||||
{columnFilterModeOptionFa[option]}
|
|
||||||
</ListItem>
|
|
||||||
))}
|
|
||||||
</Menu>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default FilterOptionList;
|
export default FilterOptionList;
|
||||||
|
|||||||
@@ -2,30 +2,22 @@ import React from "react";
|
|||||||
import MuiDatePicker from "@/core/components/DataTable/filter/fieldsType/CustomDate/MuiDatePicker";
|
import MuiDatePicker from "@/core/components/DataTable/filter/fieldsType/CustomDate/MuiDatePicker";
|
||||||
import { Typography } from "@mui/material";
|
import { Typography } from "@mui/material";
|
||||||
|
|
||||||
function CustomDatePicker({
|
function CustomDatePicker({ column, filterParameters, defaultFilterTranslation, handleChange }) {
|
||||||
column,
|
return (
|
||||||
filterParameters,
|
<MuiDatePicker
|
||||||
defaultFilterTranslation,
|
name={`${column.id}.value`}
|
||||||
handleChange,
|
value={filterParameters.value}
|
||||||
}) {
|
setFieldValue={(name, formattedDate) => {
|
||||||
return (
|
handleChange({ ...filterParameters, value: formattedDate });
|
||||||
<MuiDatePicker
|
}}
|
||||||
name={`${column.id}.value`}
|
placeholder={column.header}
|
||||||
value={filterParameters.value}
|
helperText={
|
||||||
setFieldValue={(name, formattedDate) => {
|
<Typography variant="button" sx={{ color: (theme) => theme.palette.primary.main }}>
|
||||||
handleChange({ ...filterParameters, value: formattedDate });
|
نوع فیلتر: {defaultFilterTranslation} (تاریخ)
|
||||||
}}
|
</Typography>
|
||||||
placeholder={column.header}
|
}
|
||||||
helperText={
|
/>
|
||||||
<Typography
|
);
|
||||||
variant="button"
|
|
||||||
sx={{ color: (theme) => theme.palette.primary.main }}
|
|
||||||
>
|
|
||||||
نوع فیلتر: {defaultFilterTranslation} (تاریخ)
|
|
||||||
</Typography>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CustomDatePicker;
|
export default CustomDatePicker;
|
||||||
|
|||||||
@@ -4,57 +4,44 @@ import React from "react";
|
|||||||
import { Box, Typography } from "@mui/material";
|
import { Box, Typography } from "@mui/material";
|
||||||
import MuiDatePicker from "@/core/components/DataTable/filter/fieldsType/CustomDate/MuiDatePicker";
|
import MuiDatePicker from "@/core/components/DataTable/filter/fieldsType/CustomDate/MuiDatePicker";
|
||||||
|
|
||||||
function CustomDatePickerRange({
|
function CustomDatePickerRange({ column, filterParameters, defaultFilterTranslation, handleChange, errors }) {
|
||||||
column,
|
return (
|
||||||
filterParameters,
|
<Box sx={{ display: "flex", gap: 1 }}>
|
||||||
defaultFilterTranslation,
|
<MuiDatePicker
|
||||||
handleChange,
|
label={column.header}
|
||||||
errors,
|
name={`${column.id}.value[0]`}
|
||||||
}) {
|
value={filterParameters.value[0]}
|
||||||
return (
|
setFieldValue={(name, formattedDate) => {
|
||||||
<Box sx={{ display: "flex", gap: 1 }}>
|
handleChange({
|
||||||
<MuiDatePicker
|
...filterParameters,
|
||||||
label={column.header}
|
value: [formattedDate, filterParameters.value[1]],
|
||||||
name={`${column.id}.value[0]`}
|
});
|
||||||
value={filterParameters.value[0]}
|
}}
|
||||||
setFieldValue={(name, formattedDate) => {
|
maxDate={filterParameters.value[1]}
|
||||||
handleChange({
|
placeholder={`از تاریخ`}
|
||||||
...filterParameters,
|
helperText={
|
||||||
value: [formattedDate, filterParameters.value[1]],
|
<Typography variant="button" sx={{ color: (theme) => theme.palette.primary.main }}>
|
||||||
});
|
نوع فیلتر: {defaultFilterTranslation} (تاریخ)
|
||||||
}}
|
</Typography>
|
||||||
maxDate={filterParameters.value[1]}
|
}
|
||||||
placeholder={`از تاریخ`}
|
/>
|
||||||
helperText={
|
<MuiDatePicker
|
||||||
<Typography
|
label={column.header}
|
||||||
variant="button"
|
name={`${column.id}.value[1]`}
|
||||||
sx={{ color: (theme) => theme.palette.primary.main }}
|
value={filterParameters.value[1]}
|
||||||
>
|
setFieldValue={(name, formattedDate) => {
|
||||||
نوع فیلتر: {defaultFilterTranslation} (تاریخ)
|
handleChange({
|
||||||
</Typography>
|
...filterParameters,
|
||||||
}
|
value: [filterParameters.value[0], formattedDate],
|
||||||
/>
|
});
|
||||||
<MuiDatePicker
|
}}
|
||||||
label={column.header}
|
minDate={filterParameters.value[0]}
|
||||||
name={`${column.id}.value[1]`}
|
placeholder={`تا تاریخ`}
|
||||||
value={filterParameters.value[1]}
|
helperText={errors?.[`${column.id}`]?.value ? errors?.[`${column.id}`]?.value.message : null}
|
||||||
setFieldValue={(name, formattedDate) => {
|
error={Boolean(errors?.[`${column.id}`]?.value)}
|
||||||
handleChange({
|
/>
|
||||||
...filterParameters,
|
</Box>
|
||||||
value: [filterParameters.value[0], formattedDate],
|
);
|
||||||
});
|
|
||||||
}}
|
|
||||||
minDate={filterParameters.value[0]}
|
|
||||||
placeholder={`تا تاریخ`}
|
|
||||||
helperText={
|
|
||||||
errors?.[`${column.id}`]?.value
|
|
||||||
? errors?.[`${column.id}`]?.value.message
|
|
||||||
: null
|
|
||||||
}
|
|
||||||
error={Boolean(errors?.[`${column.id}`]?.value)}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CustomDatePickerRange;
|
export default CustomDatePickerRange;
|
||||||
|
|||||||
@@ -6,81 +6,67 @@ import { Box, FormHelperText, IconButton, InputAdornment } from "@mui/material";
|
|||||||
import ClearIcon from "@mui/icons-material/Clear";
|
import ClearIcon from "@mui/icons-material/Clear";
|
||||||
import moment from "jalali-moment";
|
import moment from "jalali-moment";
|
||||||
|
|
||||||
function MuiDatePicker({
|
function MuiDatePicker({ label, value, setFieldValue, name, minDate, maxDate, helperText, placeholder, error }) {
|
||||||
label,
|
return (
|
||||||
value,
|
<Box sx={{ display: "flex", flexDirection: "column", my: 1 }}>
|
||||||
setFieldValue,
|
<LocalizationProvider
|
||||||
name,
|
dateAdapter={AdapterDateFnsJalali}
|
||||||
minDate,
|
localeText={faIR.components.MuiLocalizationProvider.defaultProps.localeText}
|
||||||
maxDate,
|
>
|
||||||
helperText,
|
<MobileDatePicker
|
||||||
placeholder,
|
value={value ? new Date(value) : null}
|
||||||
error,
|
sx={{ width: "100%" }}
|
||||||
}) {
|
id={name}
|
||||||
return (
|
label={label}
|
||||||
<Box sx={{ display: "flex", flexDirection: "column", my: 1 }}>
|
name={name}
|
||||||
<LocalizationProvider
|
closeOnSelect={true}
|
||||||
dateAdapter={AdapterDateFnsJalali}
|
aria-describedby="component-helper-text"
|
||||||
localeText={
|
onChange={(value) => {
|
||||||
faIR.components.MuiLocalizationProvider.defaultProps.localeText
|
const date = new Date(value);
|
||||||
}
|
const formattedDate = moment(date).locale("en").format("YYYY-MM-DD");
|
||||||
>
|
setFieldValue(name, formattedDate);
|
||||||
<MobileDatePicker
|
}}
|
||||||
value={value ? new Date(value) : null}
|
minDate={minDate ? new Date(minDate) : null}
|
||||||
sx={{ width: "100%" }}
|
maxDate={maxDate ? new Date(maxDate) : null}
|
||||||
id={name}
|
slotProps={{
|
||||||
label={label}
|
textField: {
|
||||||
name={name}
|
size: "small",
|
||||||
closeOnSelect={true}
|
error: error,
|
||||||
aria-describedby="component-helper-text"
|
placeholder: placeholder,
|
||||||
onChange={(value) => {
|
InputProps: {
|
||||||
const date = new Date(value);
|
endAdornment: (
|
||||||
const formattedDate = moment(date)
|
<InputAdornment position="end">
|
||||||
.locale("en")
|
<IconButton
|
||||||
.format("YYYY-MM-DD");
|
size="small"
|
||||||
setFieldValue(name, formattedDate);
|
onClick={(event) => {
|
||||||
}}
|
event.stopPropagation();
|
||||||
minDate={minDate ? new Date(minDate) : null}
|
setFieldValue(name, "");
|
||||||
maxDate={maxDate ? new Date(maxDate) : null}
|
}}
|
||||||
slotProps={{
|
sx={{
|
||||||
textField: {
|
color: "#bfbfbf",
|
||||||
size: "small",
|
"&:hover": {
|
||||||
error: error,
|
backgroundColor: "rgba(189, 189, 189, 0.1)",
|
||||||
placeholder: placeholder,
|
color: "#363434",
|
||||||
InputProps: {
|
},
|
||||||
endAdornment: (
|
}}
|
||||||
<InputAdornment position="end">
|
>
|
||||||
<IconButton
|
<ClearIcon />
|
||||||
size="small"
|
</IconButton>
|
||||||
onClick={(event) => {
|
</InputAdornment>
|
||||||
event.stopPropagation();
|
),
|
||||||
setFieldValue(name, "");
|
},
|
||||||
}}
|
InputLabelProps: {
|
||||||
sx={{
|
shrink: true,
|
||||||
color: "#bfbfbf",
|
},
|
||||||
"&:hover": {
|
|
||||||
backgroundColor: "rgba(189, 189, 189, 0.1)",
|
|
||||||
color: "#363434",
|
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
/>
|
||||||
<ClearIcon />
|
{/* <FormHelperText id="component-helper-text" variant="caption" sx={{ ml: 2, color: error ? "#d32f2f" : "unset", fontWeight: 'bold' }}>
|
||||||
</IconButton>
|
|
||||||
</InputAdornment>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
InputLabelProps: {
|
|
||||||
shrink: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{/* <FormHelperText id="component-helper-text" variant="caption" sx={{ ml: 2, color: error ? "#d32f2f" : "unset", fontWeight: 'bold' }}>
|
|
||||||
{helperText ? helperText : ""}
|
{helperText ? helperText : ""}
|
||||||
</FormHelperText> */}
|
</FormHelperText> */}
|
||||||
</LocalizationProvider>
|
</LocalizationProvider>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default MuiDatePicker;
|
export default MuiDatePicker;
|
||||||
|
|||||||
@@ -1,38 +1,30 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import {
|
import { FormControl, InputLabel, MenuItem, OutlinedInput, Select } from "@mui/material";
|
||||||
FormControl,
|
|
||||||
InputLabel,
|
|
||||||
MenuItem,
|
|
||||||
OutlinedInput,
|
|
||||||
Select,
|
|
||||||
} from "@mui/material";
|
|
||||||
function CustomSelect({ column, filterParameters, handleChange }) {
|
function CustomSelect({ column, filterParameters, handleChange }) {
|
||||||
return (
|
return (
|
||||||
<FormControl fullWidth sx={{ marginY: 1 }} size="small">
|
<FormControl fullWidth sx={{ marginY: 1 }} size="small">
|
||||||
<InputLabel id={`label${column.id}`} shrink>
|
<InputLabel id={`label${column.id}`} shrink>
|
||||||
{column.header}
|
{column.header}
|
||||||
</InputLabel>
|
</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
labelId={`label${column.id}`}
|
labelId={`label${column.id}`}
|
||||||
id={column.id}
|
id={column.id}
|
||||||
name={`${column.id}.value`}
|
name={`${column.id}.value`}
|
||||||
value={filterParameters.value}
|
value={filterParameters.value}
|
||||||
input={<OutlinedInput label={column.header} />}
|
input={<OutlinedInput label={column.header} />}
|
||||||
size="small"
|
size="small"
|
||||||
onChange={(e) =>
|
onChange={(e) => handleChange({ ...filterParameters, value: e.target.value })}
|
||||||
handleChange({ ...filterParameters, value: e.target.value })
|
displayEmpty
|
||||||
}
|
>
|
||||||
displayEmpty
|
{column.columnSelectOption().map((option) => (
|
||||||
>
|
<MenuItem key={option.value} value={option.value}>
|
||||||
{column.columnSelectOption().map((option) => (
|
{option.label}
|
||||||
<MenuItem key={option.value} value={option.value}>
|
</MenuItem>
|
||||||
{option.label}
|
))}
|
||||||
</MenuItem>
|
</Select>
|
||||||
))}
|
</FormControl>
|
||||||
</Select>
|
);
|
||||||
</FormControl>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CustomSelect;
|
export default CustomSelect;
|
||||||
|
|||||||
@@ -1,49 +1,39 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import {
|
import { FormControl, FormHelperText, InputLabel, MenuItem, OutlinedInput, Select, Typography } from "@mui/material";
|
||||||
FormControl,
|
|
||||||
FormHelperText,
|
|
||||||
InputLabel,
|
|
||||||
MenuItem,
|
|
||||||
OutlinedInput,
|
|
||||||
Select,
|
|
||||||
Typography,
|
|
||||||
} from "@mui/material";
|
|
||||||
import { useCallback, useEffect } from "react";
|
import { useCallback, useEffect } from "react";
|
||||||
|
|
||||||
function CustomSelectByDependency({
|
function CustomSelectByDependency({
|
||||||
column,
|
column,
|
||||||
filterParameters,
|
filterParameters,
|
||||||
value,
|
value,
|
||||||
defaultFilterTranslation,
|
defaultFilterTranslation,
|
||||||
handleChange,
|
handleChange,
|
||||||
columnSelectOption,
|
columnSelectOption,
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<FormControl fullWidth sx={{ my: 1 }} size="small">
|
<FormControl fullWidth sx={{ my: 1 }} size="small">
|
||||||
<InputLabel id={`label${column.id}`} shrink>
|
<InputLabel id={`label${column.id}`} shrink>
|
||||||
{column.header}
|
{column.header}
|
||||||
</InputLabel>
|
</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
labelId={`label${column.id}`}
|
labelId={`label${column.id}`}
|
||||||
id={column.id}
|
id={column.id}
|
||||||
name={`${column.id}.value`}
|
name={`${column.id}.value`}
|
||||||
value={value}
|
value={value}
|
||||||
size="small"
|
size="small"
|
||||||
input={<OutlinedInput label={column.header} />}
|
input={<OutlinedInput label={column.header} />}
|
||||||
onChange={(e) =>
|
onChange={(e) => handleChange({ ...filterParameters, value: e.target.value })}
|
||||||
handleChange({ ...filterParameters, value: e.target.value })
|
displayEmpty
|
||||||
}
|
>
|
||||||
displayEmpty
|
{columnSelectOption.map((option) => (
|
||||||
>
|
<MenuItem key={option.value} value={option.value}>
|
||||||
{columnSelectOption.map((option) => (
|
{option.label}
|
||||||
<MenuItem key={option.value} value={option.value}>
|
</MenuItem>
|
||||||
{option.label}
|
))}
|
||||||
</MenuItem>
|
</Select>
|
||||||
))}
|
</FormControl>
|
||||||
</Select>
|
);
|
||||||
</FormControl>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CustomSelectByDependency;
|
export default CustomSelectByDependency;
|
||||||
|
|||||||
@@ -1,62 +1,55 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Chip,
|
Chip,
|
||||||
FormControl,
|
FormControl,
|
||||||
FormHelperText,
|
FormHelperText,
|
||||||
InputLabel,
|
InputLabel,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
OutlinedInput,
|
OutlinedInput,
|
||||||
Select,
|
Select,
|
||||||
Typography,
|
Typography,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
|
|
||||||
function CustomSelectMultiple({
|
function CustomSelectMultiple({ column, filterParameters, defaultFilterTranslation, handleChange }) {
|
||||||
column,
|
const columnSelectOption = column.columnSelectOption;
|
||||||
filterParameters,
|
|
||||||
defaultFilterTranslation,
|
|
||||||
handleChange,
|
|
||||||
}) {
|
|
||||||
const columnSelectOption = column.columnSelectOption;
|
|
||||||
|
|
||||||
const getLabelForValue = (value) => {
|
const getLabelForValue = (value) => {
|
||||||
const option = columnSelectOption.find((opt) => opt.value === value);
|
const option = columnSelectOption.find((opt) => opt.value === value);
|
||||||
return option ? option.label : value;
|
return option ? option.label : value;
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormControl fullWidth sx={{ marginY: 1 }} size="small">
|
<FormControl fullWidth sx={{ marginY: 1 }} size="small">
|
||||||
<InputLabel htmlFor="uncontrolled-native" id={`label${column.id}`}>
|
<InputLabel htmlFor="uncontrolled-native" id={`label${column.id}`}>
|
||||||
{column.header}
|
{column.header}
|
||||||
</InputLabel>
|
</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
labelId={`label${column.id}`}
|
labelId={`label${column.id}`}
|
||||||
id={column.id}
|
id={column.id}
|
||||||
value={filterParameters.value}
|
value={filterParameters.value}
|
||||||
multiple
|
multiple
|
||||||
size="small"
|
size="small"
|
||||||
onChange={(e) =>
|
onChange={(e) => handleChange({ ...filterParameters, value: e.target.value })}
|
||||||
handleChange({ ...filterParameters, value: e.target.value })
|
renderValue={(selected) => (
|
||||||
}
|
<Box sx={{ display: "flex", flexWrap: "wrap", gap: 0.5 }}>
|
||||||
renderValue={(selected) => (
|
{selected.map((value) => (
|
||||||
<Box sx={{ display: "flex", flexWrap: "wrap", gap: 0.5 }}>
|
<Chip key={value} label={getLabelForValue(value)} />
|
||||||
{selected.map((value) => (
|
))}
|
||||||
<Chip key={value} label={getLabelForValue(value)} />
|
</Box>
|
||||||
))}
|
)}
|
||||||
</Box>
|
input={<OutlinedInput label={column.header} />}
|
||||||
)}
|
InputLabelProps={{ shrink: true }}
|
||||||
input={<OutlinedInput label={column.header} />}
|
>
|
||||||
InputLabelProps={{ shrink: true }}
|
{columnSelectOption.map((option) => (
|
||||||
>
|
<MenuItem key={option.value} value={option.value}>
|
||||||
{columnSelectOption.map((option) => (
|
{option.label}
|
||||||
<MenuItem key={option.value} value={option.value}>
|
</MenuItem>
|
||||||
{option.label}
|
))}
|
||||||
</MenuItem>
|
</Select>
|
||||||
))}
|
</FormControl>
|
||||||
</Select>
|
);
|
||||||
</FormControl>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CustomSelectMultiple;
|
export default CustomSelectMultiple;
|
||||||
|
|||||||
@@ -2,41 +2,35 @@ import { InputAdornment, TextField, Typography } from "@mui/material";
|
|||||||
import FilterListIcon from "@mui/icons-material/FilterList";
|
import FilterListIcon from "@mui/icons-material/FilterList";
|
||||||
|
|
||||||
function CustomTextField({
|
function CustomTextField({
|
||||||
column,
|
column,
|
||||||
defaultFilterTranslation,
|
defaultFilterTranslation,
|
||||||
filterParameters,
|
filterParameters,
|
||||||
handleOpenFilterBox,
|
handleOpenFilterBox,
|
||||||
handleBlur,
|
handleBlur,
|
||||||
handleChange,
|
handleChange,
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<TextField
|
<TextField
|
||||||
id={column.id}
|
id={column.id}
|
||||||
name={column.id}
|
name={column.id}
|
||||||
label={column.header}
|
label={column.header}
|
||||||
value={filterParameters.value}
|
value={filterParameters.value}
|
||||||
onChange={(e) =>
|
onChange={(e) => handleChange({ ...filterParameters, value: e.target.value })}
|
||||||
handleChange({ ...filterParameters, value: e.target.value })
|
onBlur={handleBlur}
|
||||||
}
|
fullWidth
|
||||||
onBlur={handleBlur}
|
variant="outlined"
|
||||||
fullWidth
|
size="small"
|
||||||
variant="outlined"
|
sx={{ my: 1 }}
|
||||||
size="small"
|
InputProps={{
|
||||||
sx={{ my: 1 }}
|
endAdornment: (
|
||||||
InputProps={{
|
<InputAdornment position="end" sx={{ cursor: "pointer" }} onClick={handleOpenFilterBox}>
|
||||||
endAdornment: (
|
<FilterListIcon />
|
||||||
<InputAdornment
|
</InputAdornment>
|
||||||
position="end"
|
),
|
||||||
sx={{ cursor: "pointer" }}
|
}}
|
||||||
onClick={handleOpenFilterBox}
|
InputLabelProps={{ shrink: true }}
|
||||||
>
|
/>
|
||||||
<FilterListIcon />
|
);
|
||||||
</InputAdornment>
|
|
||||||
),
|
|
||||||
}}
|
|
||||||
InputLabelProps={{ shrink: true }}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CustomTextField;
|
export default CustomTextField;
|
||||||
|
|||||||
@@ -2,84 +2,73 @@ import { Box, InputAdornment, TextField, Typography } from "@mui/material";
|
|||||||
import FilterListIcon from "@mui/icons-material/FilterList";
|
import FilterListIcon from "@mui/icons-material/FilterList";
|
||||||
|
|
||||||
function CustomTextFieldRange({
|
function CustomTextFieldRange({
|
||||||
column,
|
column,
|
||||||
defaultFilterTranslation,
|
defaultFilterTranslation,
|
||||||
handleOpenFilterBox,
|
handleOpenFilterBox,
|
||||||
handleChange,
|
handleChange,
|
||||||
filterParameters,
|
filterParameters,
|
||||||
handleBlur,
|
handleBlur,
|
||||||
errors,
|
errors,
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<Box sx={{ display: "flex" }}>
|
<Box sx={{ display: "flex" }}>
|
||||||
<TextField
|
<TextField
|
||||||
id={`${column.id}.value[0]`}
|
id={`${column.id}.value[0]`}
|
||||||
name={`${column.id}.value[0]`}
|
name={`${column.id}.value[0]`}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
handleChange({
|
handleChange({
|
||||||
...filterParameters,
|
...filterParameters,
|
||||||
value: [e.target.value, filterParameters.value[1]],
|
value: [e.target.value, filterParameters.value[1]],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
onBlur={handleBlur}
|
onBlur={handleBlur}
|
||||||
label={<Typography>از {column.header}</Typography>}
|
label={<Typography>از {column.header}</Typography>}
|
||||||
value={filterParameters.value[0]}
|
value={filterParameters.value[0]}
|
||||||
fullWidth
|
fullWidth
|
||||||
error={
|
error={touched?.[`${column.id}`]?.value && Boolean(errors?.[`${column.id}`]?.value)}
|
||||||
touched?.[`${column.id}`]?.value &&
|
helperText={
|
||||||
Boolean(errors?.[`${column.id}`]?.value)
|
<Typography
|
||||||
}
|
variant="caption"
|
||||||
helperText={
|
sx={{
|
||||||
<Typography
|
color: (theme) => theme.palette.primary.main,
|
||||||
variant="caption"
|
fontWeight: "bold",
|
||||||
sx={{
|
}}
|
||||||
color: (theme) => theme.palette.primary.main,
|
>
|
||||||
fontWeight: "bold",
|
نوع فیلتر: {defaultFilterTranslation}
|
||||||
}}
|
</Typography>
|
||||||
>
|
}
|
||||||
نوع فیلتر: {defaultFilterTranslation}
|
variant="outlined"
|
||||||
</Typography>
|
size="small"
|
||||||
}
|
sx={{ my: 1, marginRight: 1 }}
|
||||||
variant="outlined"
|
/>
|
||||||
size="small"
|
<TextField
|
||||||
sx={{ my: 1, marginRight: 1 }}
|
id={`${column.id}.value[1]`}
|
||||||
/>
|
name={`${column.id}.value[1]`}
|
||||||
<TextField
|
onChange={(e) =>
|
||||||
id={`${column.id}.value[1]`}
|
handleChange({
|
||||||
name={`${column.id}.value[1]`}
|
...filterParameters,
|
||||||
onChange={(e) =>
|
value: [filterParameters.value[0], e.target.value],
|
||||||
handleChange({
|
})
|
||||||
...filterParameters,
|
}
|
||||||
value: [filterParameters.value[0], e.target.value],
|
onBlur={handleBlur}
|
||||||
})
|
error={Boolean(errors?.[`${column.id}`]?.value)}
|
||||||
}
|
helperText={errors?.[`${column.id}`]?.value ? errors?.[`${column.id}`]?.value.message : null}
|
||||||
onBlur={handleBlur}
|
label={<Typography>تا {column.header}</Typography>}
|
||||||
error={Boolean(errors?.[`${column.id}`]?.value)}
|
value={filterParameters.value[1]}
|
||||||
helperText={
|
fullWidth
|
||||||
errors?.[`${column.id}`]?.value
|
variant="outlined"
|
||||||
? errors?.[`${column.id}`]?.value.message
|
size="small"
|
||||||
: null
|
sx={{ my: 1 }}
|
||||||
}
|
InputProps={{
|
||||||
label={<Typography>تا {column.header}</Typography>}
|
endAdornment: (
|
||||||
value={filterParameters.value[1]}
|
<InputAdornment position="end" sx={{ cursor: "pointer" }} onClick={handleOpenFilterBox}>
|
||||||
fullWidth
|
<FilterListIcon />
|
||||||
variant="outlined"
|
</InputAdornment>
|
||||||
size="small"
|
),
|
||||||
sx={{ my: 1 }}
|
}}
|
||||||
InputProps={{
|
/>
|
||||||
endAdornment: (
|
</Box>
|
||||||
<InputAdornment
|
);
|
||||||
position="end"
|
|
||||||
sx={{ cursor: "pointer" }}
|
|
||||||
onClick={handleOpenFilterBox}
|
|
||||||
>
|
|
||||||
<FilterListIcon />
|
|
||||||
</InputAdornment>
|
|
||||||
),
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CustomTextFieldRange;
|
export default CustomTextFieldRange;
|
||||||
|
|||||||
@@ -5,37 +5,37 @@ import FilterBody from "@/core/components/DataTable/filter/FilterBody";
|
|||||||
import { Box, Drawer } from "@mui/material";
|
import { Box, Drawer } from "@mui/material";
|
||||||
|
|
||||||
function FilterColumn({ columns, user_id, page_name, table_name }) {
|
function FilterColumn({ columns, user_id, page_name, table_name }) {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<FilterButton drawerState={open} setDrawerState={setOpen} />
|
<FilterButton drawerState={open} setDrawerState={setOpen} />
|
||||||
<Drawer
|
<Drawer
|
||||||
open={open}
|
open={open}
|
||||||
onClose={() => setOpen(false)}
|
onClose={() => setOpen(false)}
|
||||||
sx={{
|
sx={{
|
||||||
overflowY: "hidden",
|
overflowY: "hidden",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
height: "100%",
|
height: "100%",
|
||||||
zIndex: "1300",
|
zIndex: "1300",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box sx={{ width: { xs: 300, sm: 450 } }}>
|
<Box sx={{ width: { xs: 300, sm: 450 } }}>
|
||||||
{open && (
|
{open && (
|
||||||
<FilterBody
|
<FilterBody
|
||||||
columns={columns}
|
columns={columns}
|
||||||
drawerState={open}
|
drawerState={open}
|
||||||
setDrawerState={setOpen}
|
setDrawerState={setOpen}
|
||||||
user_id={user_id}
|
user_id={user_id}
|
||||||
page_name={page_name}
|
page_name={page_name}
|
||||||
table_name={table_name}
|
table_name={table_name}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default FilterColumn;
|
export default FilterColumn;
|
||||||
|
|||||||
@@ -3,54 +3,49 @@ import { TableHead } from "@mui/material";
|
|||||||
import DataTable_TableHeadRow from "./TableHeadRow";
|
import DataTable_TableHeadRow from "./TableHeadRow";
|
||||||
|
|
||||||
const DataTable_TableHead = ({ columnVirtualizer, table, ...rest }) => {
|
const DataTable_TableHead = ({ columnVirtualizer, table, ...rest }) => {
|
||||||
const {
|
const {
|
||||||
getState,
|
getState,
|
||||||
options: {
|
options: { enableStickyHeader, layoutMode, muiTableHeadProps, positionToolbarAlertBanner },
|
||||||
enableStickyHeader,
|
refs: { tableHeadRef },
|
||||||
layoutMode,
|
} = table;
|
||||||
muiTableHeadProps,
|
const { isFullScreen, showAlertBanner } = getState();
|
||||||
positionToolbarAlertBanner,
|
|
||||||
},
|
|
||||||
refs: { tableHeadRef },
|
|
||||||
} = table;
|
|
||||||
const { isFullScreen, showAlertBanner } = getState();
|
|
||||||
|
|
||||||
const tableHeadProps = {
|
const tableHeadProps = {
|
||||||
...parseFromValuesOrFunc(muiTableHeadProps, { table }),
|
...parseFromValuesOrFunc(muiTableHeadProps, { table }),
|
||||||
...rest,
|
...rest,
|
||||||
};
|
};
|
||||||
|
|
||||||
const stickyHeader = enableStickyHeader || isFullScreen;
|
const stickyHeader = enableStickyHeader || isFullScreen;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableHead
|
<TableHead
|
||||||
{...tableHeadProps}
|
{...tableHeadProps}
|
||||||
ref={(ref) => {
|
ref={(ref) => {
|
||||||
tableHeadRef.current = ref;
|
tableHeadRef.current = ref;
|
||||||
if (tableHeadProps?.ref) {
|
if (tableHeadProps?.ref) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
tableHeadProps.ref.current = ref;
|
tableHeadProps.ref.current = ref;
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
sx={(theme) => ({
|
sx={(theme) => ({
|
||||||
display: layoutMode?.startsWith("grid") ? "grid" : undefined,
|
display: layoutMode?.startsWith("grid") ? "grid" : undefined,
|
||||||
opacity: 0.97,
|
opacity: 0.97,
|
||||||
position: stickyHeader ? "sticky" : "relative",
|
position: stickyHeader ? "sticky" : "relative",
|
||||||
top: stickyHeader && layoutMode?.startsWith("grid") ? 0 : undefined,
|
top: stickyHeader && layoutMode?.startsWith("grid") ? 0 : undefined,
|
||||||
zIndex: stickyHeader ? 2 : undefined,
|
zIndex: stickyHeader ? 2 : undefined,
|
||||||
...parseFromValuesOrFunc(tableHeadProps?.sx, theme),
|
...parseFromValuesOrFunc(tableHeadProps?.sx, theme),
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{table.getHeaderGroups().map((headerGroup, index) => (
|
{table.getHeaderGroups().map((headerGroup, index) => (
|
||||||
<DataTable_TableHeadRow
|
<DataTable_TableHeadRow
|
||||||
columnVirtualizer={columnVirtualizer}
|
columnVirtualizer={columnVirtualizer}
|
||||||
headerGroup={headerGroup}
|
headerGroup={headerGroup}
|
||||||
key={headerGroup.id}
|
key={headerGroup.id}
|
||||||
table={table}
|
table={table}
|
||||||
index={index}
|
index={index}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</TableHead>
|
</TableHead>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default DataTable_TableHead;
|
export default DataTable_TableHead;
|
||||||
|
|||||||
@@ -1,290 +1,244 @@
|
|||||||
import {
|
import { getCommonMRTCellStyles, parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||||
getCommonMRTCellStyles,
|
|
||||||
parseFromValuesOrFunc,
|
|
||||||
} from "@/core/utils/utils";
|
|
||||||
import { useTheme } from "@emotion/react";
|
import { useTheme } from "@emotion/react";
|
||||||
import { Box, TableCell } from "@mui/material";
|
import { Box, TableCell } from "@mui/material";
|
||||||
import { MRT_TableHeadCellSortLabel } from "material-react-table";
|
import { MRT_TableHeadCellSortLabel } from "material-react-table";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
|
|
||||||
const DataTable_TableHeadCell = ({
|
const DataTable_TableHeadCell = ({ columnVirtualizer, header, staticColumnIndex, backgroundColor, table, ...rest }) => {
|
||||||
columnVirtualizer,
|
const theme = useTheme();
|
||||||
header,
|
const {
|
||||||
staticColumnIndex,
|
getState,
|
||||||
backgroundColor,
|
options: {
|
||||||
table,
|
columnResizeDirection,
|
||||||
...rest
|
columnResizeMode,
|
||||||
}) => {
|
enableColumnActions,
|
||||||
const theme = useTheme();
|
enableColumnDragging,
|
||||||
const {
|
enableColumnOrdering,
|
||||||
getState,
|
enableColumnPinning,
|
||||||
options: {
|
enableGrouping,
|
||||||
columnResizeDirection,
|
enableMultiSort,
|
||||||
columnResizeMode,
|
layoutMode,
|
||||||
enableColumnActions,
|
mrtTheme: { draggingBorderColor },
|
||||||
enableColumnDragging,
|
muiTableHeadCellProps,
|
||||||
enableColumnOrdering,
|
|
||||||
enableColumnPinning,
|
|
||||||
enableGrouping,
|
|
||||||
enableMultiSort,
|
|
||||||
layoutMode,
|
|
||||||
mrtTheme: { draggingBorderColor },
|
|
||||||
muiTableHeadCellProps,
|
|
||||||
},
|
|
||||||
refs: { tableHeadCellRefs },
|
|
||||||
setHoveredColumn,
|
|
||||||
} = table;
|
|
||||||
const {
|
|
||||||
columnSizingInfo,
|
|
||||||
density,
|
|
||||||
draggingColumn,
|
|
||||||
grouping,
|
|
||||||
hoveredColumn,
|
|
||||||
showColumnFilters,
|
|
||||||
} = getState();
|
|
||||||
const { column } = header;
|
|
||||||
const { columnDef } = column;
|
|
||||||
const { columnDefType } = columnDef;
|
|
||||||
|
|
||||||
const tableCellProps = {
|
|
||||||
...parseFromValuesOrFunc(muiTableHeadCellProps, { column, table }),
|
|
||||||
...parseFromValuesOrFunc(columnDef.muiTableHeadCellProps, {
|
|
||||||
column,
|
|
||||||
table,
|
|
||||||
}),
|
|
||||||
...rest,
|
|
||||||
};
|
|
||||||
|
|
||||||
const isColumnPinned =
|
|
||||||
enableColumnPinning &&
|
|
||||||
columnDef.columnDefType !== "group" &&
|
|
||||||
column.getIsPinned();
|
|
||||||
|
|
||||||
const showColumnActions =
|
|
||||||
(enableColumnActions || columnDef.enableColumnActions) &&
|
|
||||||
columnDef.enableColumnActions !== false;
|
|
||||||
|
|
||||||
const showDragHandle =
|
|
||||||
enableColumnDragging !== false &&
|
|
||||||
columnDef.enableColumnDragging !== false &&
|
|
||||||
(enableColumnDragging ||
|
|
||||||
(enableColumnOrdering && columnDef.enableColumnOrdering !== false) ||
|
|
||||||
(enableGrouping &&
|
|
||||||
columnDef.enableGrouping !== false &&
|
|
||||||
!grouping.includes(column.id)));
|
|
||||||
|
|
||||||
const headerPL = useMemo(() => {
|
|
||||||
let pl = 0;
|
|
||||||
if (column.getCanSort()) pl += 1;
|
|
||||||
if (showColumnActions) pl += 1.75;
|
|
||||||
if (showDragHandle) pl += 1.5;
|
|
||||||
return pl;
|
|
||||||
}, [showColumnActions, showDragHandle]);
|
|
||||||
|
|
||||||
const draggingBorders = useMemo(() => {
|
|
||||||
const showResizeBorder =
|
|
||||||
columnSizingInfo.isResizingColumn === column.id &&
|
|
||||||
columnResizeMode === "onChange" &&
|
|
||||||
!header.subHeaders.length;
|
|
||||||
|
|
||||||
const borderStyle = showResizeBorder
|
|
||||||
? `2px solid ${draggingBorderColor} !important`
|
|
||||||
: draggingColumn?.id === column.id
|
|
||||||
? `1px dashed ${theme.palette.grey[500]}`
|
|
||||||
: hoveredColumn?.id === column.id
|
|
||||||
? `2px dashed ${draggingBorderColor}`
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
if (showResizeBorder) {
|
|
||||||
return columnResizeDirection === "ltr"
|
|
||||||
? { borderRight: borderStyle }
|
|
||||||
: { borderLeft: borderStyle };
|
|
||||||
}
|
|
||||||
const draggingBorders = borderStyle
|
|
||||||
? {
|
|
||||||
borderLeft: borderStyle,
|
|
||||||
borderRight: borderStyle,
|
|
||||||
borderTop: borderStyle,
|
|
||||||
}
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
return draggingBorders;
|
|
||||||
}, [draggingColumn, hoveredColumn, columnSizingInfo.isResizingColumn]);
|
|
||||||
|
|
||||||
const handleDragEnter = (_e) => {
|
|
||||||
if (enableGrouping && hoveredColumn?.id === "drop-zone") {
|
|
||||||
setHoveredColumn(null);
|
|
||||||
}
|
|
||||||
if (enableColumnOrdering && draggingColumn && columnDefType !== "group") {
|
|
||||||
setHoveredColumn(
|
|
||||||
columnDef.enableColumnOrdering !== false ? column : null,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDragOver = (e) => {
|
|
||||||
if (columnDef.enableColumnOrdering !== false) {
|
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const HeaderElement =
|
|
||||||
parseFromValuesOrFunc(columnDef.Header, {
|
|
||||||
column,
|
|
||||||
header,
|
|
||||||
table,
|
|
||||||
}) ?? columnDef.header;
|
|
||||||
|
|
||||||
const columnRelativeDepth = header.depth - column.depth;
|
|
||||||
|
|
||||||
if (columnRelativeDepth > 1) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
let rowSpan = 1;
|
|
||||||
if (header.isPlaceholder) {
|
|
||||||
const leafs = header.getLeafHeaders();
|
|
||||||
rowSpan = leafs[leafs.length - 1].depth - header.depth;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TableCell
|
|
||||||
align={
|
|
||||||
columnDefType === "group"
|
|
||||||
? "center"
|
|
||||||
: theme.direction === "rtl"
|
|
||||||
? "right"
|
|
||||||
: "left"
|
|
||||||
}
|
|
||||||
colSpan={header.colSpan}
|
|
||||||
rowSpan={rowSpan}
|
|
||||||
data-index={staticColumnIndex}
|
|
||||||
data-pinned={!!isColumnPinned || undefined}
|
|
||||||
onDragEnter={handleDragEnter}
|
|
||||||
onDragOver={handleDragOver}
|
|
||||||
ref={(node) => {
|
|
||||||
if (node) {
|
|
||||||
tableHeadCellRefs.current[column.id] = node;
|
|
||||||
if (columnDefType !== "group") {
|
|
||||||
columnVirtualizer?.measureElement?.(node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
{...tableCellProps}
|
|
||||||
sx={(theme) => ({
|
|
||||||
"& :hover": {
|
|
||||||
".MuiButtonBase-root": {
|
|
||||||
opacity: 1,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
flexDirection: layoutMode?.startsWith("grid") ? "column" : undefined,
|
refs: { tableHeadCellRefs },
|
||||||
fontWeight: "bold",
|
setHoveredColumn,
|
||||||
overflow: "visible",
|
} = table;
|
||||||
p:
|
const { columnSizingInfo, density, draggingColumn, grouping, hoveredColumn, showColumnFilters } = getState();
|
||||||
density === "compact"
|
const { column } = header;
|
||||||
? "0.5rem"
|
const { columnDef } = column;
|
||||||
: density === "comfortable"
|
const { columnDefType } = columnDef;
|
||||||
? columnDefType === "display"
|
|
||||||
? "0.75rem"
|
const tableCellProps = {
|
||||||
: "1rem"
|
...parseFromValuesOrFunc(muiTableHeadCellProps, { column, table }),
|
||||||
: columnDefType === "display"
|
...parseFromValuesOrFunc(columnDef.muiTableHeadCellProps, {
|
||||||
? "1rem 1.25rem"
|
column,
|
||||||
: "1.5rem",
|
table,
|
||||||
pb:
|
|
||||||
columnDefType === "display"
|
|
||||||
? 0
|
|
||||||
: showColumnFilters || density === "compact"
|
|
||||||
? "0.4rem"
|
|
||||||
: "0.6rem",
|
|
||||||
pt:
|
|
||||||
columnDefType === "group" || density === "compact"
|
|
||||||
? "0.25rem"
|
|
||||||
: density === "comfortable"
|
|
||||||
? ".75rem"
|
|
||||||
: "1.25rem",
|
|
||||||
userSelect: enableMultiSort && column.getCanSort() ? "none" : undefined,
|
|
||||||
verticalAlign: "middle",
|
|
||||||
...getCommonMRTCellStyles({
|
|
||||||
column,
|
|
||||||
header,
|
|
||||||
table,
|
|
||||||
tableCellProps,
|
|
||||||
theme,
|
|
||||||
}),
|
}),
|
||||||
...draggingBorders,
|
...rest,
|
||||||
backgroundColor: backgroundColor,
|
};
|
||||||
})}
|
|
||||||
>
|
const isColumnPinned = enableColumnPinning && columnDef.columnDefType !== "group" && column.getIsPinned();
|
||||||
{tableCellProps.children ?? (
|
|
||||||
<Box
|
const showColumnActions =
|
||||||
className="Mui-TableHeadCell-Content"
|
(enableColumnActions || columnDef.enableColumnActions) && columnDef.enableColumnActions !== false;
|
||||||
sx={{
|
|
||||||
alignItems: "center",
|
const showDragHandle =
|
||||||
display: "flex",
|
enableColumnDragging !== false &&
|
||||||
flexDirection:
|
columnDef.enableColumnDragging !== false &&
|
||||||
tableCellProps?.align === "right" ? "row-reverse" : "row",
|
(enableColumnDragging ||
|
||||||
justifyContent:
|
(enableColumnOrdering && columnDef.enableColumnOrdering !== false) ||
|
||||||
columnDefType === "group" || tableCellProps?.align === "center"
|
(enableGrouping && columnDef.enableGrouping !== false && !grouping.includes(column.id)));
|
||||||
? "center"
|
|
||||||
: column.getCanResize()
|
const headerPL = useMemo(() => {
|
||||||
? "space-between"
|
let pl = 0;
|
||||||
: "flex-start",
|
if (column.getCanSort()) pl += 1;
|
||||||
position: "relative",
|
if (showColumnActions) pl += 1.75;
|
||||||
width: "100%",
|
if (showDragHandle) pl += 1.5;
|
||||||
}}
|
return pl;
|
||||||
>
|
}, [showColumnActions, showDragHandle]);
|
||||||
<Box
|
|
||||||
className="Mui-TableHeadCell-Content-Labels"
|
const draggingBorders = useMemo(() => {
|
||||||
onClick={column.getToggleSortingHandler()}
|
const showResizeBorder =
|
||||||
sx={{
|
columnSizingInfo.isResizingColumn === column.id &&
|
||||||
alignItems: "center",
|
columnResizeMode === "onChange" &&
|
||||||
cursor:
|
!header.subHeaders.length;
|
||||||
column.getCanSort() && columnDefType !== "group"
|
|
||||||
? "pointer"
|
const borderStyle = showResizeBorder
|
||||||
: undefined,
|
? `2px solid ${draggingBorderColor} !important`
|
||||||
display: "flex",
|
: draggingColumn?.id === column.id
|
||||||
flexDirection:
|
? `1px dashed ${theme.palette.grey[500]}`
|
||||||
tableCellProps?.align === "right" ? "row-reverse" : "row",
|
: hoveredColumn?.id === column.id
|
||||||
overflow: columnDefType === "data" ? "hidden" : undefined,
|
? `2px dashed ${draggingBorderColor}`
|
||||||
pl:
|
: undefined;
|
||||||
tableCellProps?.align === "center"
|
|
||||||
? `${headerPL}rem`
|
if (showResizeBorder) {
|
||||||
: undefined,
|
return columnResizeDirection === "ltr" ? { borderRight: borderStyle } : { borderLeft: borderStyle };
|
||||||
|
}
|
||||||
|
const draggingBorders = borderStyle
|
||||||
|
? {
|
||||||
|
borderLeft: borderStyle,
|
||||||
|
borderRight: borderStyle,
|
||||||
|
borderTop: borderStyle,
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
return draggingBorders;
|
||||||
|
}, [draggingColumn, hoveredColumn, columnSizingInfo.isResizingColumn]);
|
||||||
|
|
||||||
|
const handleDragEnter = (_e) => {
|
||||||
|
if (enableGrouping && hoveredColumn?.id === "drop-zone") {
|
||||||
|
setHoveredColumn(null);
|
||||||
|
}
|
||||||
|
if (enableColumnOrdering && draggingColumn && columnDefType !== "group") {
|
||||||
|
setHoveredColumn(columnDef.enableColumnOrdering !== false ? column : null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDragOver = (e) => {
|
||||||
|
if (columnDef.enableColumnOrdering !== false) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const HeaderElement =
|
||||||
|
parseFromValuesOrFunc(columnDef.Header, {
|
||||||
|
column,
|
||||||
|
header,
|
||||||
|
table,
|
||||||
|
}) ?? columnDef.header;
|
||||||
|
|
||||||
|
const columnRelativeDepth = header.depth - column.depth;
|
||||||
|
|
||||||
|
if (columnRelativeDepth > 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let rowSpan = 1;
|
||||||
|
if (header.isPlaceholder) {
|
||||||
|
const leafs = header.getLeafHeaders();
|
||||||
|
rowSpan = leafs[leafs.length - 1].depth - header.depth;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableCell
|
||||||
|
align={columnDefType === "group" ? "center" : theme.direction === "rtl" ? "right" : "left"}
|
||||||
|
colSpan={header.colSpan}
|
||||||
|
rowSpan={rowSpan}
|
||||||
|
data-index={staticColumnIndex}
|
||||||
|
data-pinned={!!isColumnPinned || undefined}
|
||||||
|
onDragEnter={handleDragEnter}
|
||||||
|
onDragOver={handleDragOver}
|
||||||
|
ref={(node) => {
|
||||||
|
if (node) {
|
||||||
|
tableHeadCellRefs.current[column.id] = node;
|
||||||
|
if (columnDefType !== "group") {
|
||||||
|
columnVirtualizer?.measureElement?.(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
>
|
{...tableCellProps}
|
||||||
<Box
|
sx={(theme) => ({
|
||||||
className="Mui-TableHeadCell-Content-Wrapper"
|
"& :hover": {
|
||||||
sx={{
|
".MuiButtonBase-root": {
|
||||||
"&:hover": {
|
opacity: 1,
|
||||||
textOverflow: "clip",
|
},
|
||||||
},
|
},
|
||||||
minWidth: `${Math.min(columnDef.header?.length ?? 0, 4)}ch`,
|
flexDirection: layoutMode?.startsWith("grid") ? "column" : undefined,
|
||||||
overflow: columnDefType === "data" ? "hidden" : undefined,
|
fontWeight: "bold",
|
||||||
textOverflow: "ellipsis",
|
overflow: "visible",
|
||||||
textAlign: "start",
|
p:
|
||||||
color: "#fff",
|
density === "compact"
|
||||||
fontWeight: "400",
|
? "0.5rem"
|
||||||
whiteSpace: "nowrap",
|
: density === "comfortable"
|
||||||
}}
|
? columnDefType === "display"
|
||||||
>
|
? "0.75rem"
|
||||||
{HeaderElement}
|
: "1rem"
|
||||||
</Box>
|
: columnDefType === "display"
|
||||||
{column.getCanSort() && columnDefType !== "group" && (
|
? "1rem 1.25rem"
|
||||||
<MRT_TableHeadCellSortLabel
|
: "1.5rem",
|
||||||
sx={{
|
pb: columnDefType === "display" ? 0 : showColumnFilters || density === "compact" ? "0.4rem" : "0.6rem",
|
||||||
width: 20,
|
pt:
|
||||||
"& .MuiTableSortLabel-icon": {
|
columnDefType === "group" || density === "compact"
|
||||||
color: `#fff !important`,
|
? "0.25rem"
|
||||||
},
|
: density === "comfortable"
|
||||||
}}
|
? ".75rem"
|
||||||
header={header}
|
: "1.25rem",
|
||||||
table={table}
|
userSelect: enableMultiSort && column.getCanSort() ? "none" : undefined,
|
||||||
/>
|
verticalAlign: "middle",
|
||||||
|
...getCommonMRTCellStyles({
|
||||||
|
column,
|
||||||
|
header,
|
||||||
|
table,
|
||||||
|
tableCellProps,
|
||||||
|
theme,
|
||||||
|
}),
|
||||||
|
...draggingBorders,
|
||||||
|
backgroundColor: backgroundColor,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{tableCellProps.children ?? (
|
||||||
|
<Box
|
||||||
|
className="Mui-TableHeadCell-Content"
|
||||||
|
sx={{
|
||||||
|
alignItems: "center",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: tableCellProps?.align === "right" ? "row-reverse" : "row",
|
||||||
|
justifyContent:
|
||||||
|
columnDefType === "group" || tableCellProps?.align === "center"
|
||||||
|
? "center"
|
||||||
|
: column.getCanResize()
|
||||||
|
? "space-between"
|
||||||
|
: "flex-start",
|
||||||
|
position: "relative",
|
||||||
|
width: "100%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
className="Mui-TableHeadCell-Content-Labels"
|
||||||
|
onClick={column.getToggleSortingHandler()}
|
||||||
|
sx={{
|
||||||
|
alignItems: "center",
|
||||||
|
cursor: column.getCanSort() && columnDefType !== "group" ? "pointer" : undefined,
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: tableCellProps?.align === "right" ? "row-reverse" : "row",
|
||||||
|
overflow: columnDefType === "data" ? "hidden" : undefined,
|
||||||
|
pl: tableCellProps?.align === "center" ? `${headerPL}rem` : undefined,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
className="Mui-TableHeadCell-Content-Wrapper"
|
||||||
|
sx={{
|
||||||
|
"&:hover": {
|
||||||
|
textOverflow: "clip",
|
||||||
|
},
|
||||||
|
minWidth: `${Math.min(columnDef.header?.length ?? 0, 4)}ch`,
|
||||||
|
overflow: columnDefType === "data" ? "hidden" : undefined,
|
||||||
|
textOverflow: "ellipsis",
|
||||||
|
textAlign: "start",
|
||||||
|
color: "#fff",
|
||||||
|
fontWeight: "400",
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{HeaderElement}
|
||||||
|
</Box>
|
||||||
|
{column.getCanSort() && columnDefType !== "group" && (
|
||||||
|
<MRT_TableHeadCellSortLabel
|
||||||
|
sx={{
|
||||||
|
width: 20,
|
||||||
|
"& .MuiTableSortLabel-icon": {
|
||||||
|
color: `#fff !important`,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
header={header}
|
||||||
|
table={table}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</TableCell>
|
||||||
</Box>
|
);
|
||||||
)}
|
|
||||||
</TableCell>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
export default DataTable_TableHeadCell;
|
export default DataTable_TableHeadCell;
|
||||||
|
|||||||
@@ -3,67 +3,58 @@ import { TableRow, useTheme } from "@mui/material";
|
|||||||
import DataTable_TableHeadCell from "./TableHeadCell";
|
import DataTable_TableHeadCell from "./TableHeadCell";
|
||||||
|
|
||||||
const DataTable_TableHeadRow = ({
|
const DataTable_TableHeadRow = ({
|
||||||
columnVirtualizer,
|
columnVirtualizer,
|
||||||
headerGroup,
|
headerGroup,
|
||||||
table,
|
table,
|
||||||
index, // Add index prop
|
index, // Add index prop
|
||||||
...rest
|
...rest
|
||||||
}) => {
|
}) => {
|
||||||
const { palette } = useTheme();
|
const { palette } = useTheme();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
options: { enableStickyHeader, layoutMode, muiTableHeadRowProps },
|
options: { enableStickyHeader, layoutMode, muiTableHeadRowProps },
|
||||||
} = table;
|
} = table;
|
||||||
const { virtualColumns, virtualPaddingLeft, virtualPaddingRight } =
|
const { virtualColumns, virtualPaddingLeft, virtualPaddingRight } = columnVirtualizer ?? {};
|
||||||
columnVirtualizer ?? {};
|
|
||||||
|
|
||||||
const backgroundColor =
|
const backgroundColor = index % 2 === 0 ? palette.primary.main : palette.secondary.main;
|
||||||
index % 2 === 0 ? palette.primary.main : palette.secondary.main;
|
|
||||||
|
|
||||||
const tableRowProps = {
|
const tableRowProps = {
|
||||||
...parseFromValuesOrFunc(muiTableHeadRowProps, {
|
...parseFromValuesOrFunc(muiTableHeadRowProps, {
|
||||||
headerGroup,
|
headerGroup,
|
||||||
table,
|
table,
|
||||||
}),
|
}),
|
||||||
...rest,
|
...rest,
|
||||||
sx: (theme) => ({
|
sx: (theme) => ({
|
||||||
// Access theme from the sx function
|
// Access theme from the sx function
|
||||||
...(parseFromValuesOrFunc(muiTableHeadRowProps?.sx, theme) || {}),
|
...(parseFromValuesOrFunc(muiTableHeadRowProps?.sx, theme) || {}),
|
||||||
display: layoutMode?.startsWith("grid") ? "flex" : undefined,
|
display: layoutMode?.startsWith("grid") ? "flex" : undefined,
|
||||||
position:
|
position: enableStickyHeader && layoutMode === "semantic" ? "sticky" : "relative",
|
||||||
enableStickyHeader && layoutMode === "semantic" ? "sticky" : "relative",
|
top: 0,
|
||||||
top: 0,
|
}),
|
||||||
}),
|
};
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableRow {...tableRowProps}>
|
<TableRow {...tableRowProps}>
|
||||||
{virtualPaddingLeft && (
|
{virtualPaddingLeft && <th style={{ display: "flex", width: virtualPaddingLeft }} />}
|
||||||
<th style={{ display: "flex", width: virtualPaddingLeft }} />
|
{(virtualColumns ?? headerGroup.headers).map((headerOrVirtualHeader, staticColumnIndex) => {
|
||||||
)}
|
const header = columnVirtualizer
|
||||||
{(virtualColumns ?? headerGroup.headers).map(
|
? headerGroup.headers[headerOrVirtualHeader.index]
|
||||||
(headerOrVirtualHeader, staticColumnIndex) => {
|
: headerOrVirtualHeader;
|
||||||
const header = columnVirtualizer
|
|
||||||
? headerGroup.headers[headerOrVirtualHeader.index]
|
|
||||||
: headerOrVirtualHeader;
|
|
||||||
|
|
||||||
return header ? (
|
return header ? (
|
||||||
<DataTable_TableHeadCell
|
<DataTable_TableHeadCell
|
||||||
columnVirtualizer={columnVirtualizer}
|
columnVirtualizer={columnVirtualizer}
|
||||||
header={header}
|
header={header}
|
||||||
backgroundColor={backgroundColor}
|
backgroundColor={backgroundColor}
|
||||||
key={header.id}
|
key={header.id}
|
||||||
staticColumnIndex={staticColumnIndex}
|
staticColumnIndex={staticColumnIndex}
|
||||||
table={table}
|
table={table}
|
||||||
/>
|
/>
|
||||||
) : null;
|
) : null;
|
||||||
},
|
})}
|
||||||
)}
|
{virtualPaddingRight && <th style={{ display: "flex", width: virtualPaddingRight }} />}
|
||||||
{virtualPaddingRight && (
|
</TableRow>
|
||||||
<th style={{ display: "flex", width: virtualPaddingRight }} />
|
);
|
||||||
)}
|
|
||||||
</TableRow>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default DataTable_TableHeadRow;
|
export default DataTable_TableHeadRow;
|
||||||
|
|||||||
@@ -8,36 +8,31 @@ import HideOrShowAll from "@/core/components/DataTable/hide/HideOrShowAll";
|
|||||||
import ScrollBox from "../../ScrollBox";
|
import ScrollBox from "../../ScrollBox";
|
||||||
|
|
||||||
function HideBody({ columns, drawerState, setDrawerState }) {
|
function HideBody({ columns, drawerState, setDrawerState }) {
|
||||||
const { hideData, setHideData } = useDataTable();
|
const { hideData, setHideData } = useDataTable();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Drawer
|
<Drawer
|
||||||
open={drawerState}
|
open={drawerState}
|
||||||
onClose={() => setDrawerState(false)}
|
onClose={() => setDrawerState(false)}
|
||||||
sx={{
|
sx={{
|
||||||
overflowY: "hidden",
|
overflowY: "hidden",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
height: "100%",
|
height: "100%",
|
||||||
zIndex: "1300",
|
zIndex: "1300",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<HideHeader setDrawerState={setDrawerState} />
|
<HideHeader setDrawerState={setDrawerState} />
|
||||||
<HideOrShowAll hideData={hideData} setHideData={setHideData} />
|
<HideOrShowAll hideData={hideData} setHideData={setHideData} />
|
||||||
<ScrollBox>
|
<ScrollBox>
|
||||||
<Box sx={{ px: 1, py: 2, display: "flex", flexDirection: "column" }}>
|
<Box sx={{ px: 1, py: 2, display: "flex", flexDirection: "column" }}>
|
||||||
{columns.map((column) => (
|
{columns.map((column) => (
|
||||||
<HideBodyField
|
<HideBodyField key={column.id} column={column} hideData={hideData} setHideData={setHideData} />
|
||||||
key={column.id}
|
))}
|
||||||
column={column}
|
</Box>
|
||||||
hideData={hideData}
|
</ScrollBox>
|
||||||
setHideData={setHideData}
|
</Drawer>
|
||||||
/>
|
);
|
||||||
))}
|
|
||||||
</Box>
|
|
||||||
</ScrollBox>
|
|
||||||
</Drawer>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default HideBody;
|
export default HideBody;
|
||||||
|
|||||||
@@ -3,78 +3,74 @@ import { SimpleTreeView } from "@mui/x-tree-view";
|
|||||||
import { TreeItem, treeItemClasses } from "@mui/x-tree-view/TreeItem";
|
import { TreeItem, treeItemClasses } from "@mui/x-tree-view/TreeItem";
|
||||||
|
|
||||||
const CustomTreeItem = styled(TreeItem)(({ theme }) => ({
|
const CustomTreeItem = styled(TreeItem)(({ theme }) => ({
|
||||||
[`& .${treeItemClasses.content}`]: {
|
[`& .${treeItemClasses.content}`]: {
|
||||||
padding: theme.spacing(0.5, 0.5),
|
padding: theme.spacing(0.5, 0.5),
|
||||||
margin: theme.spacing(0.2, 0),
|
margin: theme.spacing(0.2, 0),
|
||||||
gap: 0,
|
gap: 0,
|
||||||
},
|
},
|
||||||
[`& .${treeItemClasses.iconContainer}`]: {
|
[`& .${treeItemClasses.iconContainer}`]: {
|
||||||
"& .close": {
|
"& .close": {
|
||||||
opacity: 0.3,
|
opacity: 0.3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[`& .${treeItemClasses.groupTransition}`]: {
|
||||||
|
marginLeft: 16,
|
||||||
|
paddingLeft: 16,
|
||||||
|
borderLeft: `1px dashed ${alpha(theme.palette.text.primary, 0.4)}`,
|
||||||
},
|
},
|
||||||
},
|
|
||||||
[`& .${treeItemClasses.groupTransition}`]: {
|
|
||||||
marginLeft: 16,
|
|
||||||
paddingLeft: 16,
|
|
||||||
borderLeft: `1px dashed ${alpha(theme.palette.text.primary, 0.4)}`,
|
|
||||||
},
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
function HideBodyField({ column, hideData, setHideData }) {
|
function HideBodyField({ column, hideData, setHideData }) {
|
||||||
const handleCheckboxChange = () => {
|
const handleCheckboxChange = () => {
|
||||||
setHideData((prevData) => {
|
setHideData((prevData) => {
|
||||||
const updateHideData = (data, id) => {
|
const updateHideData = (data, id) => {
|
||||||
if (data.hasOwnProperty(id)) {
|
if (data.hasOwnProperty(id)) {
|
||||||
return { ...data, [id]: !data[id] };
|
return { ...data, [id]: !data[id] };
|
||||||
|
}
|
||||||
|
const updatedData = { ...data };
|
||||||
|
for (const key in data) {
|
||||||
|
if (data[key] && typeof data[key] === "object") {
|
||||||
|
updatedData[key] = updateHideData(data[key], id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return updatedData;
|
||||||
|
};
|
||||||
|
|
||||||
|
return updateHideData(prevData, column.id);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const labelContent = (() => {
|
||||||
|
if (typeof hideData[column.id] === "boolean") {
|
||||||
|
return (
|
||||||
|
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||||
|
<Checkbox checked={hideData[column.id]} onChange={handleCheckboxChange} name={column.id} />
|
||||||
|
<Typography variant="subtitle1">{column.header}</Typography>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<Box sx={{ display: "flex", alignItems: "center", p: 1 }}>
|
||||||
|
<Typography variant="subtitle1">{column.header}</Typography>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
const updatedData = { ...data };
|
})();
|
||||||
for (const key in data) {
|
|
||||||
if (data[key] && typeof data[key] === "object") {
|
|
||||||
updatedData[key] = updateHideData(data[key], id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return updatedData;
|
|
||||||
};
|
|
||||||
|
|
||||||
return updateHideData(prevData, column.id);
|
return (
|
||||||
});
|
<SimpleTreeView disableSelection>
|
||||||
};
|
<CustomTreeItem itemId={column.id} label={labelContent}>
|
||||||
|
{column.columns?.map((subColumn) => (
|
||||||
const labelContent = (() => {
|
<HideBodyField
|
||||||
if (typeof hideData[column.id] === "boolean") {
|
key={subColumn.id}
|
||||||
return (
|
column={subColumn}
|
||||||
<Box sx={{ display: "flex", alignItems: "center" }}>
|
hideData={hideData[column.id]}
|
||||||
<Checkbox
|
setHideData={setHideData}
|
||||||
checked={hideData[column.id]}
|
/>
|
||||||
onChange={handleCheckboxChange}
|
))}
|
||||||
name={column.id}
|
</CustomTreeItem>
|
||||||
/>
|
</SimpleTreeView>
|
||||||
<Typography variant="subtitle1">{column.header}</Typography>
|
);
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<Box sx={{ display: "flex", alignItems: "center", p: 1 }}>
|
|
||||||
<Typography variant="subtitle1">{column.header}</Typography>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SimpleTreeView disableSelection>
|
|
||||||
<CustomTreeItem itemId={column.id} label={labelContent}>
|
|
||||||
{column.columns?.map((subColumn) => (
|
|
||||||
<HideBodyField
|
|
||||||
key={subColumn.id}
|
|
||||||
column={subColumn}
|
|
||||||
hideData={hideData[column.id]}
|
|
||||||
setHideData={setHideData}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</CustomTreeItem>
|
|
||||||
</SimpleTreeView>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default HideBodyField;
|
export default HideBodyField;
|
||||||
|
|||||||
@@ -5,33 +5,31 @@ import useDataTable from "@/lib/hooks/useDataTable";
|
|||||||
import { flattenObjectOfObjects } from "@/core/utils/flattenObjectOfObjects";
|
import { flattenObjectOfObjects } from "@/core/utils/flattenObjectOfObjects";
|
||||||
|
|
||||||
function HideButton({ drawerState, setDrawerState }) {
|
function HideButton({ drawerState, setDrawerState }) {
|
||||||
const { hideData } = useDataTable();
|
const { hideData } = useDataTable();
|
||||||
const flattenHideData = flattenObjectOfObjects(hideData);
|
const flattenHideData = flattenObjectOfObjects(hideData);
|
||||||
const falseCount = Object.values(flattenHideData).filter(
|
const falseCount = Object.values(flattenHideData).filter((value) => value === false).length;
|
||||||
(value) => value === false,
|
|
||||||
).length;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip title="نمایش/مخفی کردن ستون ها">
|
<Tooltip title="نمایش/مخفی کردن ستون ها">
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setDrawerState(!drawerState);
|
setDrawerState(!drawerState);
|
||||||
}}
|
}}
|
||||||
aria-label="hide table column"
|
aria-label="hide table column"
|
||||||
>
|
>
|
||||||
<Badge
|
<Badge
|
||||||
badgeContent={falseCount}
|
badgeContent={falseCount}
|
||||||
color="primary"
|
color="primary"
|
||||||
anchorOrigin={{
|
anchorOrigin={{
|
||||||
vertical: "top",
|
vertical: "top",
|
||||||
horizontal: "left",
|
horizontal: "left",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ViewColumnIcon />
|
<ViewColumnIcon />
|
||||||
</Badge>
|
</Badge>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default HideButton;
|
export default HideButton;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user