Merge branch 'feature/remove_tel_number' into 'develop'
complete role page with show/store/update and delete actions and remove tel number from login page and add it to user page and add prettier to format the code as code formatter See merge request witel-front-end/crm-app!45
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -31,7 +31,7 @@ yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# env files (can opt-in for committing if needed)
|
||||
.env*
|
||||
.env
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
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
.idea/crm-app.iml
generated
Normal file
12
.idea/crm-app.iml
generated
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
7
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
7
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="JSAnnotator" enabled="false" level="ERROR" enabled_by_default="false" />
|
||||
</profile>
|
||||
</component>
|
||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/crm-app.iml" filepath="$PROJECT_DIR$/.idea/crm-app.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
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:
|
||||
nextjs:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
container_name: nextjs-app
|
||||
environment:
|
||||
- NEXT_PUBLIC_VERSION=${NEXT_PUBLIC_VERSION}
|
||||
- NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL}
|
||||
- NEXT_PUBLIC_SERVER_SOCKET_URL=${NEXT_PUBLIC_SERVER_SOCKET_URL}
|
||||
ports:
|
||||
- "3000:3000"
|
||||
env_file:
|
||||
- .env.local
|
||||
restart: unless-stopped
|
||||
nextjs:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
container_name: nextjs-app
|
||||
environment:
|
||||
- NEXT_PUBLIC_VERSION=${NEXT_PUBLIC_VERSION}
|
||||
- NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL}
|
||||
- NEXT_PUBLIC_SERVER_SOCKET_URL=${NEXT_PUBLIC_SERVER_SOCKET_URL}
|
||||
ports:
|
||||
- "3000:3000"
|
||||
env_file:
|
||||
- .env
|
||||
restart: unless-stopped
|
||||
|
||||
@@ -6,7 +6,7 @@ const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: __dirname,
|
||||
baseDirectory: __dirname,
|
||||
});
|
||||
|
||||
const eslintConfig = [...compat.extends("next/core-web-vitals")];
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
90
package.json
90
package.json
@@ -1,46 +1,46 @@
|
||||
{
|
||||
"name": "app",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev --turbopack",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"format": "prettier --write \"src/**/*.{js,jsx,ts,tsx,html,css,scss}\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/cache": "^11.14.0",
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.0",
|
||||
"@hookform/resolvers": "^5.0.1",
|
||||
"@mui/icons-material": "^7.0.2",
|
||||
"@mui/material": "^7.0.2",
|
||||
"@mui/material-nextjs": "^7.0.2",
|
||||
"@mui/x-date-pickers": "^8.2.0",
|
||||
"@mui/x-tree-view": "^8.2.0",
|
||||
"axios": "^1.9.0",
|
||||
"date-fns-jalali": "4.1.0-0",
|
||||
"jalali-moment": "^3.3.11",
|
||||
"lz-string": "^1.5.0",
|
||||
"material-react-table": "^3.2.1",
|
||||
"moment": "^2.30.1",
|
||||
"next": "15.3.1",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-hook-form": "^7.56.1",
|
||||
"react-toastify": "^11.0.5",
|
||||
"socket.io-client": "^4.8.1",
|
||||
"stylis": "^4.3.6",
|
||||
"stylis-plugin-rtl": "^2.1.1",
|
||||
"swr": "^2.3.3",
|
||||
"yup": "^1.6.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "15.3.1",
|
||||
"prettier": "3.5.3",
|
||||
"sass": "^1.87.0"
|
||||
}
|
||||
}
|
||||
"name": "app",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev --turbopack",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"format": "npx prettier . --write"
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/cache": "^11.14.0",
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.0",
|
||||
"@hookform/resolvers": "^5.0.1",
|
||||
"@mui/icons-material": "^7.0.2",
|
||||
"@mui/material": "^7.0.2",
|
||||
"@mui/material-nextjs": "^7.0.2",
|
||||
"@mui/x-date-pickers": "^8.2.0",
|
||||
"@mui/x-tree-view": "^8.2.0",
|
||||
"axios": "^1.9.0",
|
||||
"date-fns-jalali": "4.1.0-0",
|
||||
"jalali-moment": "^3.3.11",
|
||||
"lz-string": "^1.5.0",
|
||||
"material-react-table": "^3.2.1",
|
||||
"moment": "^2.30.1",
|
||||
"next": "15.3.1",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-hook-form": "^7.56.1",
|
||||
"react-toastify": "^11.0.5",
|
||||
"socket.io-client": "^4.8.1",
|
||||
"stylis": "^4.3.6",
|
||||
"stylis-plugin-rtl": "^2.1.1",
|
||||
"swr": "^2.3.3",
|
||||
"yup": "^1.6.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "15.3.1",
|
||||
"prettier": "3.5.3",
|
||||
"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";
|
||||
|
||||
const Loading = () => {
|
||||
return <PageLoading />;
|
||||
return <PageLoading />;
|
||||
};
|
||||
export default Loading;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import DashboardPage from "@/components/dashboard/dashboard";
|
||||
|
||||
export const metadata = {
|
||||
title: "پیشخوان",
|
||||
title: "پیشخوان",
|
||||
};
|
||||
|
||||
const Page = () => {
|
||||
return <DashboardPage />;
|
||||
return <DashboardPage />;
|
||||
};
|
||||
|
||||
export default Page;
|
||||
|
||||
16
src/app/(withAuth)/(dashboardLayout)/dashboard/roles/page.js
Normal file
16
src/app/(withAuth)/(dashboardLayout)/dashboard/roles/page.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import RolesPage from "@/components/dashboard/Roles";
|
||||
import WithPermission from "@/core/middlewares/withPermission";
|
||||
|
||||
export const metadata = {
|
||||
title: "نقش ها",
|
||||
};
|
||||
|
||||
const Page = () => {
|
||||
return (
|
||||
<WithPermission permission_name={["manage_roles"]}>
|
||||
<RolesPage />
|
||||
</WithPermission>
|
||||
);
|
||||
};
|
||||
|
||||
export default Page;
|
||||
@@ -2,15 +2,15 @@ import UsersPage from "@/components/dashboard/Users";
|
||||
import WithPermission from "@/core/middlewares/withPermission";
|
||||
|
||||
export const metadata = {
|
||||
title: "کاربران",
|
||||
title: "کاربران",
|
||||
};
|
||||
|
||||
const Page = () => {
|
||||
return (
|
||||
<WithPermission permission_name={["manage_users"]}>
|
||||
<UsersPage />
|
||||
</WithPermission>
|
||||
);
|
||||
return (
|
||||
<WithPermission permission_name={["manage_users"]}>
|
||||
<UsersPage />
|
||||
</WithPermission>
|
||||
);
|
||||
};
|
||||
|
||||
export default Page;
|
||||
|
||||
@@ -8,22 +8,20 @@ import { Stack } from "@mui/material";
|
||||
import { SWRConfig } from "swr";
|
||||
|
||||
const Layout = ({ children }) => {
|
||||
const { data: permissions } = usePermissions();
|
||||
const isCallWidgetEnabled = permissions?.some(
|
||||
(permission) => permission === "manage_calls",
|
||||
);
|
||||
const { data: permissions } = usePermissions();
|
||||
const isCallWidgetEnabled = permissions?.some((permission) => permission === "manage_calls");
|
||||
|
||||
return (
|
||||
<SocketProvider>
|
||||
<SWRConfig value={{ provider: () => new Map() }}>
|
||||
<Stack sx={{ width: "100%", height: "100%" }}>
|
||||
<HeaderWithSidebar>{children}</HeaderWithSidebar>
|
||||
</Stack>
|
||||
</SWRConfig>
|
||||
<WithWidgetMiddleware enable={isCallWidgetEnabled}>
|
||||
<CallWidget />
|
||||
</WithWidgetMiddleware>
|
||||
</SocketProvider>
|
||||
);
|
||||
return (
|
||||
<SocketProvider>
|
||||
<SWRConfig value={{ provider: () => new Map() }}>
|
||||
<Stack sx={{ width: "100%", height: "100%" }}>
|
||||
<HeaderWithSidebar>{children}</HeaderWithSidebar>
|
||||
</Stack>
|
||||
</SWRConfig>
|
||||
<WithWidgetMiddleware enable={isCallWidgetEnabled}>
|
||||
<CallWidget />
|
||||
</WithWidgetMiddleware>
|
||||
</SocketProvider>
|
||||
);
|
||||
};
|
||||
export default Layout;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import WithAuthMiddleware from "@/core/middlewares/withAuth";
|
||||
|
||||
const Layout = ({ children }) => {
|
||||
return <WithAuthMiddleware>{children}</WithAuthMiddleware>;
|
||||
return <WithAuthMiddleware>{children}</WithAuthMiddleware>;
|
||||
};
|
||||
|
||||
export default Layout;
|
||||
|
||||
@@ -3,11 +3,11 @@ import WithoutAuthMiddleware from "@/core/middlewares/withoutAuth";
|
||||
import { Suspense } from "react";
|
||||
|
||||
const Layout = ({ children }) => {
|
||||
return (
|
||||
<Suspense>
|
||||
<WithoutAuthMiddleware>{children}</WithoutAuthMiddleware>
|
||||
</Suspense>
|
||||
);
|
||||
return (
|
||||
<Suspense>
|
||||
<WithoutAuthMiddleware>{children}</WithoutAuthMiddleware>
|
||||
</Suspense>
|
||||
);
|
||||
};
|
||||
|
||||
export default Layout;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import LoginPage from "@/components/Login";
|
||||
|
||||
export const metadata = {
|
||||
title: "ورود",
|
||||
title: "ورود",
|
||||
};
|
||||
|
||||
const Page = () => {
|
||||
return <LoginPage />;
|
||||
return <LoginPage />;
|
||||
};
|
||||
export default Page;
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
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";
|
||||
|
||||
export const metadata = {
|
||||
title: {
|
||||
template: "%s | سامانه CRM",
|
||||
default: "سامانه CRM",
|
||||
},
|
||||
title: {
|
||||
template: "%s | سامانه CRM",
|
||||
default: "سامانه CRM",
|
||||
},
|
||||
};
|
||||
|
||||
export default function RootLayout({ children }) {
|
||||
return (
|
||||
<html lang="fa" dir={"rtl"}>
|
||||
<head>
|
||||
<link rel="icon" href={favicon.src} type="image/png" sizes="any" />
|
||||
</head>
|
||||
<body style={{ height: "100vh", width: "100vw" }}>
|
||||
<AppRouterCacheProvider
|
||||
CacheProvider={Rtl}
|
||||
options={{ enableCssLayer: true }}
|
||||
>
|
||||
<AuthProvider>
|
||||
<TableSettingProvider>{children}</TableSettingProvider>
|
||||
</AuthProvider>
|
||||
</AppRouterCacheProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
return (
|
||||
<html lang="fa" dir={"rtl"}>
|
||||
<head>
|
||||
<link rel="icon" href={favicon.src} type="image/png" sizes="any" />
|
||||
</head>
|
||||
<body style={{ height: "100vh", width: "100vw" }}>
|
||||
<AppRouterCacheProvider CacheProvider={Rtl} options={{ enableCssLayer: true }}>
|
||||
<AuthProvider>
|
||||
<TableSettingProvider>{children}</TableSettingProvider>
|
||||
</AuthProvider>
|
||||
</AppRouterCacheProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,19 +3,14 @@ import { Box, Stack, Typography } from "@mui/material";
|
||||
import SvgNotFound from "@/core/components/svgs/SvgNotFound";
|
||||
|
||||
export default function NotFound() {
|
||||
return (
|
||||
<Stack
|
||||
sx={{ height: "100%" }}
|
||||
justifyContent={"center"}
|
||||
alignItems={"center"}
|
||||
spacing={2}
|
||||
>
|
||||
<Box>
|
||||
<SvgNotFound width={200} height={200} />
|
||||
</Box>
|
||||
<Typography variant={"body1"} sx={{ color: "primary.main" }}>
|
||||
صفحه موردنظر یافت نشد ...
|
||||
</Typography>
|
||||
</Stack>
|
||||
);
|
||||
return (
|
||||
<Stack sx={{ height: "100%" }} justifyContent={"center"} alignItems={"center"} spacing={2}>
|
||||
<Box>
|
||||
<SvgNotFound width={200} height={200} />
|
||||
</Box>
|
||||
<Typography variant={"body1"} sx={{ color: "primary.main" }}>
|
||||
صفحه موردنظر یافت نشد ...
|
||||
</Typography>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
function Page() {
|
||||
redirect("/dashboard", "replace");
|
||||
return null;
|
||||
redirect("/dashboard", "replace");
|
||||
return null;
|
||||
}
|
||||
|
||||
export default Page;
|
||||
|
||||
@@ -7,34 +7,34 @@ import { CssBaseline, GlobalStyles, ThemeProvider } from "@mui/material";
|
||||
import { ToastContainer } from "react-toastify";
|
||||
|
||||
const Template = ({ children }) => {
|
||||
return (
|
||||
<ThemeProvider theme={theme}>
|
||||
<GlobalStyles
|
||||
styles={{
|
||||
"*": {
|
||||
scrollbarWidth: "thin",
|
||||
scrollbarColor: `${theme.palette.primary.dark} transparent`,
|
||||
},
|
||||
"*&::-webkit-scrollbar": {
|
||||
width: "4px",
|
||||
},
|
||||
"*&::-webkit-scrollbar-track": {
|
||||
boxShadow: "inset 0 0 5px #fff",
|
||||
borderRadius: "4px",
|
||||
},
|
||||
"*&::-webkit-scrollbar-thumb": {
|
||||
background: `${theme.palette.primary.dark}`,
|
||||
borderRadius: "4px",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<CssBaseline />
|
||||
{children}
|
||||
<ToastContainer rtl containerId="filtering" closeButton={false} />
|
||||
<ToastContainer rtl containerId="request_data" />
|
||||
<ToastContainer rtl containerId="socket_connection" position="top-left" />
|
||||
</ThemeProvider>
|
||||
);
|
||||
return (
|
||||
<ThemeProvider theme={theme}>
|
||||
<GlobalStyles
|
||||
styles={{
|
||||
"*": {
|
||||
scrollbarWidth: "thin",
|
||||
scrollbarColor: `${theme.palette.primary.dark} transparent`,
|
||||
},
|
||||
"*&::-webkit-scrollbar": {
|
||||
width: "4px",
|
||||
},
|
||||
"*&::-webkit-scrollbar-track": {
|
||||
boxShadow: "inset 0 0 5px #fff",
|
||||
borderRadius: "4px",
|
||||
},
|
||||
"*&::-webkit-scrollbar-thumb": {
|
||||
background: `${theme.palette.primary.dark}`,
|
||||
borderRadius: "4px",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<CssBaseline />
|
||||
{children}
|
||||
<ToastContainer rtl containerId="filtering" closeButton={false} />
|
||||
<ToastContainer rtl containerId="request_data" />
|
||||
<ToastContainer rtl containerId="socket_connection" position="top-left" />
|
||||
</ThemeProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default Template;
|
||||
|
||||
@@ -1,212 +1,172 @@
|
||||
@font-face {
|
||||
font-family: IRANSansFaNum;
|
||||
font-style: normal;
|
||||
font-weight: 900;
|
||||
src: url("../fonts/eot/IRANSansWeb(FaNum)_Black.eot");
|
||||
src:
|
||||
url("../fonts/eot/IRANSansWeb(FaNum)_Black.eot?#iefix")
|
||||
format("embedded-opentype"),
|
||||
/* IE6-8 */ url("../fonts/woff2/IRANSansWeb(FaNum)_Black.woff2")
|
||||
format("woff2"),
|
||||
/* 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-family: IRANSansFaNum;
|
||||
font-style: normal;
|
||||
font-weight: 900;
|
||||
src: url("../fonts/eot/IRANSansWeb(FaNum)_Black.eot");
|
||||
src:
|
||||
url("../fonts/eot/IRANSansWeb(FaNum)_Black.eot?#iefix") format("embedded-opentype"),
|
||||
/* IE6-8 */ url("../fonts/woff2/IRANSansWeb(FaNum)_Black.woff2") format("woff2"),
|
||||
/* 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-family: IRANSansFaNum;
|
||||
font-style: normal;
|
||||
font-weight: bold;
|
||||
src: url("../fonts/eot/IRANSansWeb(FaNum)_Bold.eot");
|
||||
src:
|
||||
url("../fonts/eot/IRANSansWeb(FaNum)_Bold.eot?#iefix")
|
||||
format("embedded-opentype"),
|
||||
/* IE6-8 */ url("../fonts/woff2/IRANSansWeb(FaNum)_Bold.woff2")
|
||||
format("woff2"),
|
||||
/* 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-family: IRANSansFaNum;
|
||||
font-style: normal;
|
||||
font-weight: bold;
|
||||
src: url("../fonts/eot/IRANSansWeb(FaNum)_Bold.eot");
|
||||
src:
|
||||
url("../fonts/eot/IRANSansWeb(FaNum)_Bold.eot?#iefix") format("embedded-opentype"),
|
||||
/* IE6-8 */ url("../fonts/woff2/IRANSansWeb(FaNum)_Bold.woff2") format("woff2"),
|
||||
/* 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-family: IRANSansFaNum;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
src: url("../fonts/eot/IRANSansWeb(FaNum)_Medium.eot");
|
||||
src:
|
||||
url("../fonts/eot/IRANSansWeb(FaNum)_Medium.eot?#iefix")
|
||||
format("embedded-opentype"),
|
||||
/* IE6-8 */ url("../fonts/woff2/IRANSansWeb(FaNum)_Medium.woff2")
|
||||
format("woff2"),
|
||||
/* 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-family: IRANSansFaNum;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
src: url("../fonts/eot/IRANSansWeb(FaNum)_Medium.eot");
|
||||
src:
|
||||
url("../fonts/eot/IRANSansWeb(FaNum)_Medium.eot?#iefix") format("embedded-opentype"),
|
||||
/* IE6-8 */ url("../fonts/woff2/IRANSansWeb(FaNum)_Medium.woff2") format("woff2"),
|
||||
/* 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-family: IRANSansFaNum;
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src: url("../fonts/eot/IRANSansWeb(FaNum)_Light.eot");
|
||||
src:
|
||||
url("../fonts/eot/IRANSansWeb(FaNum)_Light.eot?#iefix")
|
||||
format("embedded-opentype"),
|
||||
/* IE6-8 */ url("../fonts/woff2/IRANSansWeb(FaNum)_Light.woff2")
|
||||
format("woff2"),
|
||||
/* 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-family: IRANSansFaNum;
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src: url("../fonts/eot/IRANSansWeb(FaNum)_Light.eot");
|
||||
src:
|
||||
url("../fonts/eot/IRANSansWeb(FaNum)_Light.eot?#iefix") format("embedded-opentype"),
|
||||
/* IE6-8 */ url("../fonts/woff2/IRANSansWeb(FaNum)_Light.woff2") format("woff2"),
|
||||
/* 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-family: IRANSansFaNum;
|
||||
font-style: normal;
|
||||
font-weight: 200;
|
||||
src: url("../fonts/eot/IRANSansWeb(FaNum)_UltraLight.eot");
|
||||
src:
|
||||
url("../fonts/eot/IRANSansWeb(FaNum)_UltraLight.eot?#iefix")
|
||||
format("embedded-opentype"),
|
||||
/* IE6-8 */ url("../fonts/woff2/IRANSansWeb(FaNum)_UltraLight.woff2")
|
||||
format("woff2"),
|
||||
/* 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-family: IRANSansFaNum;
|
||||
font-style: normal;
|
||||
font-weight: 200;
|
||||
src: url("../fonts/eot/IRANSansWeb(FaNum)_UltraLight.eot");
|
||||
src:
|
||||
url("../fonts/eot/IRANSansWeb(FaNum)_UltraLight.eot?#iefix") format("embedded-opentype"),
|
||||
/* IE6-8 */ url("../fonts/woff2/IRANSansWeb(FaNum)_UltraLight.woff2") format("woff2"),
|
||||
/* 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-family: IRANSansFaNum;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
src: url("../fonts/eot/IRANSansWeb(FaNum).eot");
|
||||
src:
|
||||
url("../fonts/eot/IRANSansWeb(FaNum).eot?#iefix")
|
||||
format("embedded-opentype"),
|
||||
/* IE6-8 */ url("../fonts/woff2/IRANSansWeb(FaNum).woff2") format("woff2"),
|
||||
/* FF39+,Chrome36+, Opera24+*/ url("../fonts/woff/IRANSansWeb(FaNum).woff")
|
||||
format("woff"),
|
||||
/* FF3.6+, IE9, Chrome6+, Saf5.1+*/
|
||||
url("../fonts/ttf/IRANSansWeb(FaNum).ttf") format("truetype");
|
||||
font-family: IRANSansFaNum;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
src: url("../fonts/eot/IRANSansWeb(FaNum).eot");
|
||||
src:
|
||||
url("../fonts/eot/IRANSansWeb(FaNum).eot?#iefix") format("embedded-opentype"),
|
||||
/* IE6-8 */ url("../fonts/woff2/IRANSansWeb(FaNum).woff2") format("woff2"),
|
||||
/* FF39+,Chrome36+, Opera24+*/ url("../fonts/woff/IRANSansWeb(FaNum).woff") format("woff"),
|
||||
/* FF3.6+, IE9, Chrome6+, Saf5.1+*/ url("../fonts/ttf/IRANSansWeb(FaNum).ttf") format("truetype");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: IRANSans;
|
||||
font-style: normal;
|
||||
font-weight: 900;
|
||||
src: url("../fonts/eot/IRANSansWeb_Black.eot");
|
||||
src:
|
||||
url("../fonts/eot/IRANSansWeb_Black.eot?#iefix") format("embedded-opentype"),
|
||||
/* IE6-8 */ url("../fonts/woff2/IRANSansWeb_Black.woff2") format("woff2"),
|
||||
/* FF39+,Chrome36+, Opera24+*/ url("../fonts/woff/IRANSansWeb_Black.woff")
|
||||
format("woff"),
|
||||
/* FF3.6+, IE9, Chrome6+, Saf5.1+*/
|
||||
url("../fonts/ttf/IRANSansWeb_Black.ttf") format("truetype");
|
||||
font-family: IRANSans;
|
||||
font-style: normal;
|
||||
font-weight: 900;
|
||||
src: url("../fonts/eot/IRANSansWeb_Black.eot");
|
||||
src:
|
||||
url("../fonts/eot/IRANSansWeb_Black.eot?#iefix") format("embedded-opentype"),
|
||||
/* IE6-8 */ url("../fonts/woff2/IRANSansWeb_Black.woff2") format("woff2"),
|
||||
/* FF39+,Chrome36+, Opera24+*/ url("../fonts/woff/IRANSansWeb_Black.woff") format("woff"),
|
||||
/* FF3.6+, IE9, Chrome6+, Saf5.1+*/ url("../fonts/ttf/IRANSansWeb_Black.ttf") format("truetype");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: IRANSans;
|
||||
font-style: normal;
|
||||
font-weight: bold;
|
||||
src: url("../fonts/eot/IRANSansWeb_Bold.eot");
|
||||
src:
|
||||
url("../fonts/eot/IRANSansWeb_Bold.eot?#iefix") format("embedded-opentype"),
|
||||
/* IE6-8 */ url("../fonts/woff2/IRANSansWeb_Bold.woff2") format("woff2"),
|
||||
/* FF39+,Chrome36+, Opera24+*/ url("../fonts/woff/IRANSansWeb_Bold.woff")
|
||||
format("woff"),
|
||||
/* FF3.6+, IE9, Chrome6+, Saf5.1+*/ url("../fonts/ttf/IRANSansWeb_Bold.ttf")
|
||||
format("truetype");
|
||||
font-family: IRANSans;
|
||||
font-style: normal;
|
||||
font-weight: bold;
|
||||
src: url("../fonts/eot/IRANSansWeb_Bold.eot");
|
||||
src:
|
||||
url("../fonts/eot/IRANSansWeb_Bold.eot?#iefix") format("embedded-opentype"),
|
||||
/* IE6-8 */ url("../fonts/woff2/IRANSansWeb_Bold.woff2") format("woff2"),
|
||||
/* FF39+,Chrome36+, Opera24+*/ url("../fonts/woff/IRANSansWeb_Bold.woff") format("woff"),
|
||||
/* FF3.6+, IE9, Chrome6+, Saf5.1+*/ url("../fonts/ttf/IRANSansWeb_Bold.ttf") format("truetype");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: IRANSans;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
src: url("../fonts/eot/IRANSansWeb_Medium.eot");
|
||||
src:
|
||||
url("../fonts/eot/IRANSansWeb_Medium.eot?#iefix")
|
||||
format("embedded-opentype"),
|
||||
/* IE6-8 */ url("../fonts/woff2/IRANSansWeb_Medium.woff2") format("woff2"),
|
||||
/* FF39+,Chrome36+, Opera24+*/ url("../fonts/woff/IRANSansWeb_Medium.woff")
|
||||
format("woff"),
|
||||
/* FF3.6+, IE9, Chrome6+, Saf5.1+*/
|
||||
url("../fonts/ttf/IRANSansWeb_Medium.ttf") format("truetype");
|
||||
font-family: IRANSans;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
src: url("../fonts/eot/IRANSansWeb_Medium.eot");
|
||||
src:
|
||||
url("../fonts/eot/IRANSansWeb_Medium.eot?#iefix") format("embedded-opentype"),
|
||||
/* IE6-8 */ url("../fonts/woff2/IRANSansWeb_Medium.woff2") format("woff2"),
|
||||
/* FF39+,Chrome36+, Opera24+*/ url("../fonts/woff/IRANSansWeb_Medium.woff") format("woff"),
|
||||
/* FF3.6+, IE9, Chrome6+, Saf5.1+*/ url("../fonts/ttf/IRANSansWeb_Medium.ttf") format("truetype");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: IRANSans;
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src: url("../fonts/eot/IRANSansWeb_Light.eot");
|
||||
src:
|
||||
url("../fonts/eot/IRANSansWeb_Light.eot?#iefix") format("embedded-opentype"),
|
||||
/* IE6-8 */ url("../fonts/woff2/IRANSansWeb_Light.woff2") format("woff2"),
|
||||
/* FF39+,Chrome36+, Opera24+*/ url("../fonts/woff/IRANSansWeb_Light.woff")
|
||||
format("woff"),
|
||||
/* FF3.6+, IE9, Chrome6+, Saf5.1+*/
|
||||
url("../fonts/ttf/IRANSansWeb_Light.ttf") format("truetype");
|
||||
font-family: IRANSans;
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src: url("../fonts/eot/IRANSansWeb_Light.eot");
|
||||
src:
|
||||
url("../fonts/eot/IRANSansWeb_Light.eot?#iefix") format("embedded-opentype"),
|
||||
/* IE6-8 */ url("../fonts/woff2/IRANSansWeb_Light.woff2") format("woff2"),
|
||||
/* FF39+,Chrome36+, Opera24+*/ url("../fonts/woff/IRANSansWeb_Light.woff") format("woff"),
|
||||
/* FF3.6+, IE9, Chrome6+, Saf5.1+*/ url("../fonts/ttf/IRANSansWeb_Light.ttf") format("truetype");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: IRANSans;
|
||||
font-style: normal;
|
||||
font-weight: 200;
|
||||
src: url("../fonts/eot/IRANSansWeb_UltraLight.eot");
|
||||
src:
|
||||
url("../fonts/eot/IRANSansWeb_UltraLight.eot?#iefix")
|
||||
format("embedded-opentype"),
|
||||
/* IE6-8 */ url("../fonts/woff2/IRANSansWeb_UltraLight.woff2")
|
||||
format("woff2"),
|
||||
/* 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-family: IRANSans;
|
||||
font-style: normal;
|
||||
font-weight: 200;
|
||||
src: url("../fonts/eot/IRANSansWeb_UltraLight.eot");
|
||||
src:
|
||||
url("../fonts/eot/IRANSansWeb_UltraLight.eot?#iefix") format("embedded-opentype"),
|
||||
/* IE6-8 */ url("../fonts/woff2/IRANSansWeb_UltraLight.woff2") format("woff2"),
|
||||
/* 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-family: IRANSans;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
src: url("../fonts/eot/IRANSansWeb.eot");
|
||||
src:
|
||||
url("../fonts/eot/IRANSansWeb.eot?#iefix") format("embedded-opentype"),
|
||||
/* IE6-8 */ url("../fonts/woff2/IRANSansWeb.woff2") format("woff2"),
|
||||
/* FF39+,Chrome36+, Opera24+*/ url("../fonts/woff/IRANSansWeb.woff")
|
||||
format("woff"),
|
||||
/* FF3.6+, IE9, Chrome6+, Saf5.1+*/ url("../fonts/ttf/IRANSansWeb.ttf")
|
||||
format("truetype");
|
||||
font-family: IRANSans;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
src: url("../fonts/eot/IRANSansWeb.eot");
|
||||
src:
|
||||
url("../fonts/eot/IRANSansWeb.eot?#iefix") format("embedded-opentype"),
|
||||
/* IE6-8 */ url("../fonts/woff2/IRANSansWeb.woff2") format("woff2"),
|
||||
/* FF39+,Chrome36+, Opera24+*/ url("../fonts/woff/IRANSansWeb.woff") format("woff"),
|
||||
/* FF3.6+, IE9, Chrome6+, Saf5.1+*/ url("../fonts/ttf/IRANSansWeb.ttf") format("truetype");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Parastoo;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
src: url("../fonts/eot/Parastoo.eot");
|
||||
src:
|
||||
url("../fonts/eot/Parastoo.eot?#iefix") format("embedded-opentype"),
|
||||
/* IE6-8 */ url("../fonts/woff/Parastoo.woff") format("woff"),
|
||||
/* FF3.6+, IE9, Chrome6+, Saf5.1+*/ url("../fonts/ttf/Parastoo.ttf")
|
||||
format("truetype");
|
||||
font-family: Parastoo;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
src: url("../fonts/eot/Parastoo.eot");
|
||||
src:
|
||||
url("../fonts/eot/Parastoo.eot?#iefix") format("embedded-opentype"),
|
||||
/* IE6-8 */ url("../fonts/woff/Parastoo.woff") format("woff"),
|
||||
/* FF3.6+, IE9, Chrome6+, Saf5.1+*/ url("../fonts/ttf/Parastoo.ttf") format("truetype");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Parastoo;
|
||||
font-style: normal;
|
||||
font-weight: bold;
|
||||
src: url("../fonts/eot/Parastoo-Bold.eot");
|
||||
src:
|
||||
url("../fonts/eot/Parastoo-Bold.eot?#iefix") format("embedded-opentype"),
|
||||
/* IE6-8 */ url("../fonts/woff/Parastoo-Bold.woff") format("woff"),
|
||||
/* FF3.6+, IE9, Chrome6+, Saf5.1+*/ url("../fonts/ttf/Parastoo-Bold.ttf")
|
||||
format("truetype");
|
||||
font-family: Parastoo;
|
||||
font-style: normal;
|
||||
font-weight: bold;
|
||||
src: url("../fonts/eot/Parastoo-Bold.eot");
|
||||
src:
|
||||
url("../fonts/eot/Parastoo-Bold.eot?#iefix") format("embedded-opentype"),
|
||||
/* IE6-8 */ url("../fonts/woff/Parastoo-Bold.woff") format("woff"),
|
||||
/* FF3.6+, IE9, Chrome6+, Saf5.1+*/ url("../fonts/ttf/Parastoo-Bold.ttf") format("truetype");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Bnazanin;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
src: url("../fonts/ttf/BNazanin.ttf") format("truetype");
|
||||
font-family: Bnazanin;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
src: url("../fonts/ttf/BNazanin.ttf") format("truetype");
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.filter-toast {
|
||||
box-shadow:
|
||||
rgba(50, 50, 93, 0.25) 0px 13px 27px -5px,
|
||||
rgba(0, 0, 0, 0.3) 0px 8px 16px -8px;
|
||||
background-color: #d0dfe8;
|
||||
box-shadow:
|
||||
rgba(50, 50, 93, 0.25) 0px 13px 27px -5px,
|
||||
rgba(0, 0, 0, 0.3) 0px 8px 16px -8px;
|
||||
background-color: #d0dfe8;
|
||||
}
|
||||
|
||||
@@ -5,198 +5,135 @@ import { GET_USER_LOGIN_ROUTE } from "@/core/utils/routes";
|
||||
import { useAuth } from "@/lib/contexts/auth";
|
||||
import useRequest from "@/lib/hooks/useRequest";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import {
|
||||
Lock,
|
||||
Person,
|
||||
PhoneCallback,
|
||||
Visibility,
|
||||
VisibilityOff,
|
||||
} from "@mui/icons-material";
|
||||
import {
|
||||
Button,
|
||||
Container,
|
||||
IconButton,
|
||||
InputAdornment,
|
||||
Stack,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import { Lock, Person, PhoneCallback, Visibility, VisibilityOff } from "@mui/icons-material";
|
||||
import { Button, Container, IconButton, InputAdornment, Stack, Typography } from "@mui/material";
|
||||
import { useState } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { object, string } from "yup";
|
||||
|
||||
const LoginForm = () => {
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const requestServer = useRequest();
|
||||
const { getUser } = useAuth();
|
||||
const defaultValues = {
|
||||
username: "",
|
||||
password: "",
|
||||
telephone_id: "",
|
||||
};
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const requestServer = useRequest();
|
||||
const { getUser } = useAuth();
|
||||
const defaultValues = {
|
||||
username: "",
|
||||
password: "",
|
||||
};
|
||||
|
||||
const validationSchema = object({
|
||||
username: string().required("لطفا نام کاربری را وارد کنید!"),
|
||||
password: string().required("لطفا رمز عبور را وارد کنید!"),
|
||||
telephone_id: string().required("لطفا شماره تماس داخلی را وارد کنید!"),
|
||||
});
|
||||
const validationSchema = object({
|
||||
username: string().required("لطفا نام کاربری را وارد کنید!"),
|
||||
password: string().required("لطفا رمز عبور را وارد کنید!"),
|
||||
});
|
||||
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
formState: { isSubmitting },
|
||||
} = useForm({
|
||||
defaultValues,
|
||||
resolver: yupResolver(validationSchema),
|
||||
mode: "all",
|
||||
});
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
formState: { isSubmitting },
|
||||
} = useForm({
|
||||
defaultValues,
|
||||
resolver: yupResolver(validationSchema),
|
||||
mode: "all",
|
||||
});
|
||||
|
||||
const handleClickShowPassword = () => setShowPassword((show) => !show);
|
||||
const handleClickShowPassword = () => setShowPassword((show) => !show);
|
||||
|
||||
const onSubmit = async (data) => {
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append("username", data.username);
|
||||
formData.append("password", data.password);
|
||||
formData.append("telephone_id", data.telephone_id);
|
||||
await requestServer(GET_USER_LOGIN_ROUTE, "post", {
|
||||
data: formData,
|
||||
});
|
||||
getUser();
|
||||
} catch (error) {}
|
||||
};
|
||||
const onSubmit = async (data) => {
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append("username", data.username);
|
||||
formData.append("password", data.password);
|
||||
await requestServer(GET_USER_LOGIN_ROUTE, "post", {
|
||||
data: formData,
|
||||
});
|
||||
getUser();
|
||||
} catch (error) {}
|
||||
};
|
||||
|
||||
return (
|
||||
<Container maxWidth="xs" sx={{ flex: 1 }}>
|
||||
<StyledForm
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
style={{ width: "100%", height: "100%" }}
|
||||
>
|
||||
<Stack
|
||||
sx={{ width: "100%", height: "100%" }}
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
spacing={6}
|
||||
>
|
||||
<Stack
|
||||
sx={{ width: "100%", mb: 2 }}
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
>
|
||||
<Typography
|
||||
variant="h4"
|
||||
color="primary"
|
||||
fontWeight={600}
|
||||
textAlign="center"
|
||||
>
|
||||
ورود به سامانه CRM
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Stack sx={{ width: "100%", p: 2 }} spacing={4}>
|
||||
<Stack spacing={2}>
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<LtrTextField
|
||||
{...field}
|
||||
label="نام کاربری"
|
||||
variant="outlined"
|
||||
slotProps={{
|
||||
input: {
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<Person />
|
||||
</InputAdornment>
|
||||
),
|
||||
},
|
||||
}}
|
||||
fullWidth
|
||||
error={!!error}
|
||||
helperText={!!error && error.message}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
name={"username"}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<LtrTextField
|
||||
{...field}
|
||||
label="رمزعبور"
|
||||
variant="outlined"
|
||||
type={showPassword ? "text" : "password"}
|
||||
slotProps={{
|
||||
input: {
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<IconButton onClick={handleClickShowPassword}>
|
||||
{showPassword ? (
|
||||
<VisibilityOff />
|
||||
) : (
|
||||
<Visibility />
|
||||
)}
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
),
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<Lock />
|
||||
</InputAdornment>
|
||||
),
|
||||
},
|
||||
}}
|
||||
fullWidth
|
||||
error={!!error}
|
||||
helperText={!!error && error.message}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
name={"password"}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<LtrTextField
|
||||
{...field}
|
||||
label="شماره تماس داخلی"
|
||||
variant="outlined"
|
||||
type="tel"
|
||||
slotProps={{
|
||||
input: {
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<PhoneCallback />
|
||||
</InputAdornment>
|
||||
),
|
||||
},
|
||||
}}
|
||||
fullWidth
|
||||
error={!!error}
|
||||
helperText={!!error && error.message}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
name={"telephone_id"}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
size="large"
|
||||
type={"submit"}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
{isSubmitting ? "درحال ورود..." : "ورود"}
|
||||
</Button>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</StyledForm>
|
||||
</Container>
|
||||
);
|
||||
return (
|
||||
<Container maxWidth="xs" sx={{ flex: 1 }}>
|
||||
<StyledForm onSubmit={handleSubmit(onSubmit)} style={{ width: "100%", height: "100%" }}>
|
||||
<Stack sx={{ width: "100%", height: "100%" }} justifyContent="center" alignItems="center" spacing={6}>
|
||||
<Stack sx={{ width: "100%", mb: 2 }} justifyContent="center" alignItems="center">
|
||||
<Typography variant="h4" color="primary" fontWeight={600} textAlign="center">
|
||||
ورود به سامانه CRM
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Stack sx={{ width: "100%", p: 2 }} spacing={4}>
|
||||
<Stack spacing={2}>
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<LtrTextField
|
||||
{...field}
|
||||
label="نام کاربری"
|
||||
variant="outlined"
|
||||
slotProps={{
|
||||
input: {
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<Person />
|
||||
</InputAdornment>
|
||||
),
|
||||
},
|
||||
}}
|
||||
fullWidth
|
||||
error={!!error}
|
||||
helperText={!!error && error.message}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
name={"username"}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<LtrTextField
|
||||
{...field}
|
||||
label="رمزعبور"
|
||||
variant="outlined"
|
||||
type={showPassword ? "text" : "password"}
|
||||
slotProps={{
|
||||
input: {
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<IconButton onClick={handleClickShowPassword}>
|
||||
{showPassword ? <VisibilityOff /> : <Visibility />}
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
),
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<Lock />
|
||||
</InputAdornment>
|
||||
),
|
||||
},
|
||||
}}
|
||||
fullWidth
|
||||
error={!!error}
|
||||
helperText={!!error && error.message}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
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;
|
||||
|
||||
@@ -4,22 +4,22 @@ import Link from "next/link";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
|
||||
const LoginLinkRouting = () => {
|
||||
const searchParams = useSearchParams();
|
||||
const searchParams = useSearchParams();
|
||||
|
||||
const redirect = searchParams.get("redirect");
|
||||
const redirect = searchParams.get("redirect");
|
||||
|
||||
return (
|
||||
<Stack direction="row" alignItems="center" justifyContent="center">
|
||||
<Button
|
||||
component={Link}
|
||||
data-testid="link_routing"
|
||||
sx={{ margin: 2 }}
|
||||
color="secondary"
|
||||
href={redirect ? decodeURIComponent(redirect) : "/"}
|
||||
>
|
||||
{`بازگشت به ${redirect ? "صفحه قبلی" : "صفحه اصلی"}`}
|
||||
</Button>
|
||||
</Stack>
|
||||
);
|
||||
return (
|
||||
<Stack direction="row" alignItems="center" justifyContent="center">
|
||||
<Button
|
||||
component={Link}
|
||||
data-testid="link_routing"
|
||||
sx={{ margin: 2 }}
|
||||
color="secondary"
|
||||
href={redirect ? decodeURIComponent(redirect) : "/"}
|
||||
>
|
||||
{`بازگشت به ${redirect ? "صفحه قبلی" : "صفحه اصلی"}`}
|
||||
</Button>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
export default LoginLinkRouting;
|
||||
|
||||
@@ -5,15 +5,15 @@ import LoginLinkRouting from "./LoginLinkRouting";
|
||||
import { Suspense } from "react";
|
||||
|
||||
const LoginPage = () => {
|
||||
return (
|
||||
<Suspense>
|
||||
<Fade in={true}>
|
||||
<Stack sx={{ width: "100%", height: "100vh" }}>
|
||||
<LoginForm />
|
||||
<LoginLinkRouting />
|
||||
</Stack>
|
||||
</Fade>
|
||||
</Suspense>
|
||||
);
|
||||
return (
|
||||
<Suspense>
|
||||
<Fade in={true}>
|
||||
<Stack sx={{ width: "100%", height: "100vh" }}>
|
||||
<LoginForm />
|
||||
<LoginLinkRouting />
|
||||
</Stack>
|
||||
</Fade>
|
||||
</Suspense>
|
||||
);
|
||||
};
|
||||
export default LoginPage;
|
||||
|
||||
90
src/components/dashboard/Roles/DataTable.jsx
Normal file
90
src/components/dashboard/Roles/DataTable.jsx
Normal file
@@ -0,0 +1,90 @@
|
||||
"use client";
|
||||
import DataTableWithAuth from "@/core/components/DataTableWithAuth";
|
||||
import { GET_ROLE_LIST } from "@/core/utils/routes";
|
||||
import { Box, Typography } from "@mui/material";
|
||||
import { useMemo } from "react";
|
||||
import Toolbar from "./Toolbar";
|
||||
import moment from "jalali-moment";
|
||||
import RowActions from "./RowAcctions";
|
||||
|
||||
const DataTable = () => {
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
{
|
||||
accessorKey: "id",
|
||||
header: "کد یکتا",
|
||||
id: "id",
|
||||
enableColumnFilter: true,
|
||||
datatype: "text",
|
||||
filterMode: "equals",
|
||||
sortDescFirst: false,
|
||||
columnFilterModeOptions: ["equals", "contains"],
|
||||
grow: false,
|
||||
size: 100,
|
||||
},
|
||||
{
|
||||
accessorKey: "name_fa",
|
||||
header: "نام",
|
||||
id: "name_fa",
|
||||
enableColumnFilter: true,
|
||||
datatype: "text",
|
||||
filterMode: "equals",
|
||||
sortDescFirst: false,
|
||||
columnFilterModeOptions: ["equals", "contains"],
|
||||
grow: false,
|
||||
size: 100,
|
||||
},
|
||||
{
|
||||
accessorKey: "created_at",
|
||||
header: "تاریخ ساخت",
|
||||
id: "created_at",
|
||||
enableColumnFilter: false,
|
||||
datatype: "date",
|
||||
filterMode: "equals",
|
||||
grow: false,
|
||||
size: 100,
|
||||
Cell: ({ renderedCellValue }) => (
|
||||
<Typography variant="body2">
|
||||
{renderedCellValue ? moment(renderedCellValue).locale("fa").format("HH:mm | YYYY/MM/DD") : "-"}
|
||||
</Typography>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "updated_at",
|
||||
header: "تاریخ آخرین بروزرسانی",
|
||||
id: "updated_at",
|
||||
enableColumnFilter: false,
|
||||
datatype: "date",
|
||||
filterMode: "equals",
|
||||
grow: false,
|
||||
size: 100,
|
||||
Cell: ({ renderedCellValue }) => (
|
||||
<Typography variant="body2">
|
||||
{renderedCellValue ? moment(renderedCellValue).locale("fa").format("HH:mm | YYYY/MM/DD") : "-"}
|
||||
</Typography>
|
||||
),
|
||||
},
|
||||
],
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box sx={{ p: 1, border: 1, borderColor: "divider", borderRadius: 1 }}>
|
||||
<DataTableWithAuth
|
||||
need_filter={true}
|
||||
columns={columns}
|
||||
sorting={[{ id: "id", desc: true }]}
|
||||
table_url={GET_ROLE_LIST}
|
||||
page_name={"rolesPage"}
|
||||
table_name={"rolesList"}
|
||||
TableToolbar={Toolbar}
|
||||
enableRowActions
|
||||
positionActionsColumn={"last"}
|
||||
RowActions={RowActions}
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default DataTable;
|
||||
@@ -0,0 +1,39 @@
|
||||
import { DELETE_ROLE } from "@/core/utils/routes";
|
||||
import useRequest from "@/lib/hooks/useRequest";
|
||||
import { Button, DialogActions, DialogContent, Stack, Typography } from "@mui/material";
|
||||
import { useState } from "react";
|
||||
|
||||
const DeleteContent = ({ rowId, mutate, setOpenDeleteDialog }) => {
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const requestServer = useRequest({ notificationSuccess: true });
|
||||
const handleClick = async () => {
|
||||
setSubmitting(true);
|
||||
try {
|
||||
await requestServer(`${DELETE_ROLE}/${rowId}`, "delete");
|
||||
mutate();
|
||||
setOpenDeleteDialog(false);
|
||||
} catch (error) {
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<DialogContent>
|
||||
<Stack alignItems="center" spacing={2}>
|
||||
<Typography mt={2}>آیا از حذف کاربر اطمینان دارید؟</Typography>
|
||||
</Stack>
|
||||
</DialogContent>
|
||||
<DialogActions sx={{ justifyContent: "center", paddingBottom: 2, width: "100%" }}>
|
||||
<Button onClick={() => setOpenDeleteDialog(false)} variant="outlined" color="secondary">
|
||||
خیر !
|
||||
</Button>
|
||||
<Button onClick={handleClick} variant="contained" color="error" disabled={submitting}>
|
||||
{submitting ? "درحال حذف..." : "بله اطمینان دارم"}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeleteContent;
|
||||
32
src/components/dashboard/Roles/RowAcctions/Delete/index.jsx
Normal file
32
src/components/dashboard/Roles/RowAcctions/Delete/index.jsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import DeleteIcon from "@mui/icons-material/Delete";
|
||||
import { Dialog, DialogTitle, IconButton, Tooltip } from "@mui/material";
|
||||
import { useState } from "react";
|
||||
import DeleteContent from "./DeleteContent";
|
||||
const Delete = ({ rowId, mutate }) => {
|
||||
const [openDeleteDialog, setOpenDeleteDialog] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tooltip title="حذف">
|
||||
<IconButton color="error" onClick={() => setOpenDeleteDialog(true)}>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Dialog
|
||||
open={openDeleteDialog}
|
||||
onClose={() => setOpenDeleteDialog(false)}
|
||||
PaperProps={{
|
||||
sx: {
|
||||
boxShadow: "rgba(60, 64, 67, 0.3) 0px 1px 16px 0px, rgba(60, 64, 67, 0.15) 0px 1px 10px 0px",
|
||||
},
|
||||
}}
|
||||
dir="rtl"
|
||||
maxWidth={"md"}
|
||||
>
|
||||
<DialogTitle>حذف</DialogTitle>
|
||||
<DeleteContent rowId={rowId} mutate={mutate} setOpenDeleteDialog={setOpenDeleteDialog} />
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default Delete;
|
||||
186
src/components/dashboard/Roles/RowAcctions/Edit/Form/index.jsx
Normal file
186
src/components/dashboard/Roles/RowAcctions/Edit/Form/index.jsx
Normal file
@@ -0,0 +1,186 @@
|
||||
import LtrTextField from "@/core/components/LtrTextField";
|
||||
import StyledForm from "@/core/components/StyledForm";
|
||||
import { CREATE_ROLE } from "@/core/utils/routes";
|
||||
import useRequest from "@/lib/hooks/useRequest";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Checkbox,
|
||||
Chip,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
Divider,
|
||||
FormControlLabel,
|
||||
FormHelperText,
|
||||
Grid,
|
||||
Skeleton,
|
||||
Stack,
|
||||
} from "@mui/material";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { array, object, string } from "yup";
|
||||
import usePermissionsList from "@/lib/hooks/usePermissionsList";
|
||||
|
||||
const validationSchema = object().shape({
|
||||
name: string().required("نام فارسی نقش را وارد کنید"),
|
||||
name_fa: string().required("نام انگلیسی نقش را وارد کنید"),
|
||||
permissions: array().min(1, "حداقل یک دسترسی را انتخاب نمایید")
|
||||
});
|
||||
|
||||
const UpdateRoleForm = ({ values, setOpen, mutate }) => {
|
||||
const requestServer = useRequest({ notificationSuccess: true });
|
||||
const { permissions, loadingPermissions } = usePermissionsList();
|
||||
|
||||
const defaultValues = {
|
||||
name: values.name,
|
||||
name_fa: values.name_fa,
|
||||
permissions: values.permissions?.map(p => p.id) || []
|
||||
};
|
||||
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
formState: { isSubmitting, errors },
|
||||
} = useForm({ defaultValues, resolver: yupResolver(validationSchema) });
|
||||
|
||||
const onSubmit = async (data) => {
|
||||
try {
|
||||
await requestServer(`${CREATE_ROLE}/${values.id}`, "post", {
|
||||
data: {
|
||||
permissions: data.permissions,
|
||||
name: data.name,
|
||||
name_fa: data.name_fa,
|
||||
},
|
||||
});
|
||||
setOpen(false);
|
||||
mutate();
|
||||
} catch (error) {}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<DialogContent dividers>
|
||||
<Divider sx={{ mb: 3 }}>
|
||||
<Chip color="primary" variant="outlined" label="کاربر جدید" />
|
||||
</Divider>
|
||||
<StyledForm
|
||||
id="UpdateRoleForm"
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
style={{ width: "100%", height: "100%" }}
|
||||
>
|
||||
<Stack spacing={2}>
|
||||
<Grid container spacing={2}>
|
||||
<Grid size={{ xs: 12, md: 6 }}>
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<LtrTextField
|
||||
{...field}
|
||||
label="نام انگلیسی نقش"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
error={!!error}
|
||||
helperText={!!error && error.message}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
name={"name"}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, md: 6 }}>
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<LtrTextField
|
||||
{...field}
|
||||
label="نام فارسی نقش"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
error={!!error}
|
||||
helperText={!!error && error.message}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
name={"name_fa"}
|
||||
/>
|
||||
</Grid>
|
||||
<Box sx={{width: "100%"}}>
|
||||
<Divider>
|
||||
<Chip label="سطوح دسترسی" size="medium" />
|
||||
</Divider>
|
||||
</Box>
|
||||
<Grid container spacing={1} sx={{width: "100%"}}>
|
||||
{!loadingPermissions ? (
|
||||
<Controller
|
||||
control={control}
|
||||
name="permissions"
|
||||
render={({ field, fieldState: {error} }) => (
|
||||
<>
|
||||
{permissions.map((permission) => (
|
||||
<Grid key={permission.id} size={{ xs: 12, md: 4 }}>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
value={permission.id}
|
||||
checked={field.value.includes(permission.id)}
|
||||
onChange={(e) => {
|
||||
const value = parseInt(e.target.value, 10);
|
||||
if (e.target.checked) {
|
||||
field.onChange([...field.value, value]);
|
||||
} else {
|
||||
field.onChange(field.value.filter((id) => id !== value));
|
||||
}
|
||||
}}
|
||||
/>
|
||||
}
|
||||
label={permission.name_fa}
|
||||
/>
|
||||
</Grid>
|
||||
))}
|
||||
<FormHelperText sx={{color: "error.main"}}>{!!error && error.message}</FormHelperText>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<Grid container spacing={2} width="100%">
|
||||
<Grid size={{ xs: 12, md: 4 }}><Skeleton animation="wave" /></Grid>
|
||||
<Grid size={{ xs: 12, md: 4 }}><Skeleton animation="wave" /></Grid>
|
||||
<Grid size={{ xs: 12, md: 4 }}><Skeleton animation="wave" /></Grid>
|
||||
<Grid size={{ xs: 12, md: 4 }}><Skeleton animation="wave" /></Grid>
|
||||
<Grid size={{ xs: 12, md: 4 }}><Skeleton animation="wave" /></Grid>
|
||||
<Grid size={{ xs: 12, md: 4 }}><Skeleton animation="wave" /></Grid>
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Stack>
|
||||
</StyledForm>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
disabled={isSubmitting}
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
size="large"
|
||||
onClick={() => setOpen(false)}
|
||||
>
|
||||
بستن
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={isSubmitting}
|
||||
form="UpdateRoleForm"
|
||||
size="large"
|
||||
autoFocus
|
||||
variant="contained"
|
||||
>
|
||||
{isSubmitting ? "در حال ویرایش کاربر..." : "ویرایش کاربر"}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default UpdateRoleForm;
|
||||
27
src/components/dashboard/Roles/RowAcctions/Edit/index.jsx
Normal file
27
src/components/dashboard/Roles/RowAcctions/Edit/index.jsx
Normal file
@@ -0,0 +1,27 @@
|
||||
"use client";
|
||||
import { Edit } from "@mui/icons-material";
|
||||
import { Dialog, IconButton, Tooltip } from "@mui/material";
|
||||
import { useState } from "react";
|
||||
import UpdateUserForm from "./Form";
|
||||
|
||||
const UpdateUser = ({ values, mutate }) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const handleOpen = () => {
|
||||
setOpen(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tooltip title="ویرایش">
|
||||
<IconButton onClick={handleOpen}>
|
||||
<Edit />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Dialog maxWidth="sm" fullWidth={true} open={open}>
|
||||
<UpdateUserForm open={open} setOpen={setOpen} mutate={mutate} values={values} />
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default UpdateUser;
|
||||
12
src/components/dashboard/Roles/RowAcctions/index.jsx
Normal file
12
src/components/dashboard/Roles/RowAcctions/index.jsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Box } from "@mui/material";
|
||||
import Delete from "./Delete";
|
||||
import UpdateUser from "./Edit";
|
||||
const RowActions = ({ row, mutate }) => {
|
||||
return (
|
||||
<Box sx={{ display: "flex", gap: 1, alignItems: "center" }}>
|
||||
<Delete rowId={row.original.id} mutate={mutate} />
|
||||
<UpdateUser values={row.original} mutate={mutate} />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
export default RowActions;
|
||||
184
src/components/dashboard/Roles/Toolbar/Create/Form/index.jsx
Normal file
184
src/components/dashboard/Roles/Toolbar/Create/Form/index.jsx
Normal file
@@ -0,0 +1,184 @@
|
||||
import LtrTextField from "@/core/components/LtrTextField";
|
||||
import StyledForm from "@/core/components/StyledForm";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Checkbox,
|
||||
Chip,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
Divider,
|
||||
FormControlLabel, FormHelperText,
|
||||
Grid, Skeleton,
|
||||
Stack,
|
||||
} from "@mui/material";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { array, object, string } from "yup";
|
||||
import useRequest from "@/lib/hooks/useRequest";
|
||||
import { CREATE_ROLE } from "@/core/utils/routes";
|
||||
import usePermissionsList from "@/lib/hooks/usePermissionsList";
|
||||
|
||||
const defaultValues = {
|
||||
name: "",
|
||||
name_fa: "",
|
||||
permissions: []
|
||||
};
|
||||
|
||||
const validationSchema = object().shape({
|
||||
name: string().required("نام فارسی نقش را وارد کنید"),
|
||||
name_fa: string().required("نام انگلیسی نقش را وارد کنید"),
|
||||
permissions: array().min(1, "حداقل یک دسترسی را انتخاب نمایید")
|
||||
});
|
||||
|
||||
const CreateRoleForm = ({ setOpen, mutate }) => {
|
||||
const requestServer = useRequest({ notificationSuccess: true });
|
||||
const { permissions, loadingPermissions } = usePermissionsList();
|
||||
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
formState: { isSubmitting, errors },
|
||||
} = useForm({ defaultValues, resolver: yupResolver(validationSchema) });
|
||||
|
||||
const onSubmit = async (data) => {
|
||||
try {
|
||||
await requestServer(CREATE_ROLE, "post", {
|
||||
data: {
|
||||
permissions: data.permissions,
|
||||
name: data.name,
|
||||
name_fa: data.name_fa,
|
||||
},
|
||||
});
|
||||
setOpen(false);
|
||||
mutate();
|
||||
} catch (error) {}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<DialogContent dividers>
|
||||
<Divider sx={{ mb: 3 }}>
|
||||
<Chip color="primary" variant="outlined" label="نقش جدید" />
|
||||
</Divider>
|
||||
<StyledForm
|
||||
id="createRoleForm"
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
style={{ width: "100%", height: "100%" }}
|
||||
>
|
||||
<Stack spacing={2}>
|
||||
<Grid container spacing={2}>
|
||||
<Grid size={{ xs: 12, md: 6 }}>
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<LtrTextField
|
||||
{...field}
|
||||
label="نام انگلیسی نقش"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
error={!!error}
|
||||
helperText={!!error && error.message}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
name={"name"}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, md: 6 }}>
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<LtrTextField
|
||||
{...field}
|
||||
label="نام فارسی نقش"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
error={!!error}
|
||||
helperText={!!error && error.message}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
name={"name_fa"}
|
||||
/>
|
||||
</Grid>
|
||||
<Box sx={{width: "100%"}}>
|
||||
<Divider>
|
||||
<Chip label="سطوح دسترسی" size="medium" />
|
||||
</Divider>
|
||||
</Box>
|
||||
<Grid container spacing={1} sx={{width: "100%"}}>
|
||||
{!loadingPermissions ? (
|
||||
<Controller
|
||||
control={control}
|
||||
name="permissions"
|
||||
render={({ field, fieldState: {error} }) => (
|
||||
<>
|
||||
{permissions.map((permission) => (
|
||||
<Grid key={permission.id} size={{ xs: 12, md: 4 }}>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
value={permission.id}
|
||||
checked={field.value.includes(permission.id)}
|
||||
onChange={(e) => {
|
||||
const value = parseInt(e.target.value, 10);
|
||||
if (e.target.checked) {
|
||||
field.onChange([...field.value, value]);
|
||||
} else {
|
||||
field.onChange(field.value.filter((id) => id !== value));
|
||||
}
|
||||
}}
|
||||
/>
|
||||
}
|
||||
label={permission.name_fa}
|
||||
/>
|
||||
</Grid>
|
||||
))}
|
||||
<FormHelperText sx={{color: "error.main"}}>{!!error && error.message}</FormHelperText>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<Grid container spacing={2} width="100%">
|
||||
<Grid size={{ xs: 12, md: 4 }}><Skeleton animation="wave" /></Grid>
|
||||
<Grid size={{ xs: 12, md: 4 }}><Skeleton animation="wave" /></Grid>
|
||||
<Grid size={{ xs: 12, md: 4 }}><Skeleton animation="wave" /></Grid>
|
||||
<Grid size={{ xs: 12, md: 4 }}><Skeleton animation="wave" /></Grid>
|
||||
<Grid size={{ xs: 12, md: 4 }}><Skeleton animation="wave" /></Grid>
|
||||
<Grid size={{ xs: 12, md: 4 }}><Skeleton animation="wave" /></Grid>
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Stack>
|
||||
</StyledForm>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
disabled={isSubmitting}
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
size="large"
|
||||
onClick={() => setOpen(false)}
|
||||
>
|
||||
بستن
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={isSubmitting}
|
||||
form="createRoleForm"
|
||||
size="large"
|
||||
autoFocus
|
||||
variant="contained"
|
||||
>
|
||||
{isSubmitting ? "در حال ثبت نقش جدید..." : "ثبت نقش جدید"}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default CreateRoleForm;
|
||||
39
src/components/dashboard/Roles/Toolbar/Create/index.jsx
Normal file
39
src/components/dashboard/Roles/Toolbar/Create/index.jsx
Normal file
@@ -0,0 +1,39 @@
|
||||
"use client";
|
||||
import { AddCircle } from "@mui/icons-material";
|
||||
import { Button, Dialog, IconButton, useMediaQuery, useTheme } from "@mui/material";
|
||||
import CreateRoleForm from "./Form";
|
||||
import { useState } from "react";
|
||||
|
||||
const CreateRole = ({ mutate }) => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const handleOpen = () => {
|
||||
setOpen(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{isMobile ? (
|
||||
<IconButton aria-label="نقش جدید" color="primary" onClick={handleOpen}>
|
||||
<AddCircle sx={{ fontSize: "25px" }} />
|
||||
</IconButton>
|
||||
) : (
|
||||
<Button
|
||||
size={"small"}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
startIcon={<AddCircle />}
|
||||
onClick={handleOpen}
|
||||
>
|
||||
نقش جدید
|
||||
</Button>
|
||||
)}
|
||||
<Dialog maxWidth="sm" fullWidth={true} open={open}>
|
||||
<CreateRoleForm open={open} setOpen={setOpen} mutate={mutate} />
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default CreateRole;
|
||||
10
src/components/dashboard/Roles/Toolbar/index.jsx
Normal file
10
src/components/dashboard/Roles/Toolbar/index.jsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import CreateRole from "./Create";
|
||||
|
||||
const Toolbar = ({ mutate }) => {
|
||||
return (
|
||||
<>
|
||||
<CreateRole mutate={mutate} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default Toolbar;
|
||||
14
src/components/dashboard/Roles/index.jsx
Normal file
14
src/components/dashboard/Roles/index.jsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import PageTitle from "@/core/components/PageTitle";
|
||||
import { Stack } from "@mui/material";
|
||||
import DataTable from "./DataTable";
|
||||
|
||||
const RolesPage = () => {
|
||||
return (
|
||||
<Stack spacing={1}>
|
||||
<PageTitle title={"نقش ها"} />
|
||||
<DataTable />
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default RolesPage;
|
||||
@@ -13,205 +13,200 @@ import OnlineCell from "./RowActions/Online";
|
||||
import TelephoneId from "./RowActions/TelephoneId";
|
||||
|
||||
const DataTable = () => {
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
{
|
||||
accessorKey: "id",
|
||||
header: "کد یکتا",
|
||||
id: "id",
|
||||
enableColumnFilter: true,
|
||||
datatype: "text",
|
||||
filterMode: "equals",
|
||||
sortDescFirst: false,
|
||||
columnFilterModeOptions: ["equals", "contains"],
|
||||
grow: false,
|
||||
size: 100,
|
||||
},
|
||||
{
|
||||
accessorKey: "full_name",
|
||||
header: "نام و نام خانوادگی",
|
||||
id: "full_name",
|
||||
enableColumnFilter: true,
|
||||
datatype: "text",
|
||||
filterMode: "equals",
|
||||
sortDescFirst: false,
|
||||
columnFilterModeOptions: ["equals", "contains"],
|
||||
grow: false,
|
||||
size: 100,
|
||||
},
|
||||
{
|
||||
header: "شماره تماس داخلی",
|
||||
id: "telephone_id",
|
||||
enableColumnFilter: true,
|
||||
datatype: "text",
|
||||
filterMode: "equals",
|
||||
sortDescFirst: false,
|
||||
columnFilterModeOptions: ["equals", "contains"],
|
||||
grow: false,
|
||||
size: 100,
|
||||
Cell: ({ row }) => <TelephoneId user_id={row.original.id} />,
|
||||
},
|
||||
{
|
||||
accessorKey: "is_online",
|
||||
header: "وضعیت برخط",
|
||||
id: "is_online",
|
||||
enableColumnFilter: true,
|
||||
datatype: "text",
|
||||
filterMode: "equals",
|
||||
sortDescFirst: false,
|
||||
columnFilterModeOptions: ["equals", "contains"],
|
||||
grow: false,
|
||||
size: 100,
|
||||
Cell: ({ row }) => <OnlineCell user_id={row.original.id} />,
|
||||
},
|
||||
{
|
||||
header: "استان",
|
||||
id: "province_id",
|
||||
enableColumnFilter: true,
|
||||
datatype: "numeric",
|
||||
filterMode: "equals",
|
||||
grow: false,
|
||||
size: 130,
|
||||
ColumnSelectComponent: (props) => {
|
||||
const { provinces, errorProvinces, loadingProvinces } =
|
||||
useProvinces();
|
||||
const getColumnSelectOptions = useMemo(() => {
|
||||
if (loadingProvinces) {
|
||||
return [{ value: "loading", label: "در حال بارگذاری..." }];
|
||||
}
|
||||
if (errorProvinces) {
|
||||
return [{ value: "error", label: "خطا در بارگذاری" }];
|
||||
}
|
||||
return [
|
||||
{ value: "", label: "کل کشور" },
|
||||
...provinces.map((province) => ({
|
||||
value: province.id,
|
||||
label: province.name,
|
||||
})),
|
||||
];
|
||||
}, [provinces, errorProvinces, loadingProvinces]);
|
||||
return (
|
||||
<CustomSelectByDependency
|
||||
{...props}
|
||||
value={
|
||||
loadingProvinces ? "loading" : props.filterParameters.value
|
||||
}
|
||||
columnSelectOption={getColumnSelectOptions}
|
||||
/>
|
||||
);
|
||||
},
|
||||
Cell: ({ renderedCellValue, row }) => <>{row.original.province_fa}</>,
|
||||
},
|
||||
{
|
||||
accessorKey: "username",
|
||||
header: "نام کاربری",
|
||||
id: "username",
|
||||
enableColumnFilter: true,
|
||||
datatype: "text",
|
||||
filterMode: "equals",
|
||||
sortDescFirst: false,
|
||||
columnFilterModeOptions: ["equals", "contains"],
|
||||
grow: false,
|
||||
size: 100,
|
||||
},
|
||||
{
|
||||
accessorKey: "position",
|
||||
header: "سمت",
|
||||
id: "position",
|
||||
enableColumnFilter: false,
|
||||
grow: false,
|
||||
size: 100,
|
||||
},
|
||||
{
|
||||
accessorKey: "phone_number",
|
||||
header: "شماره همراه",
|
||||
id: "phone_number",
|
||||
enableColumnFilter: false,
|
||||
grow: false,
|
||||
size: 100,
|
||||
},
|
||||
{
|
||||
header: "نقش",
|
||||
id: "role__id",
|
||||
enableColumnFilter: false,
|
||||
grow: false,
|
||||
size: 100,
|
||||
enableColumnFilter: true,
|
||||
datatype: "numeric",
|
||||
filterMode: "equals",
|
||||
grow: false,
|
||||
size: 130,
|
||||
ColumnSelectComponent: (props) => {
|
||||
const { roles, errorRoles, loadingRoles } = useRoles();
|
||||
const getColumnSelectOptions = useMemo(() => {
|
||||
if (loadingRoles) {
|
||||
return [{ value: "loading", label: "در حال بارگذاری..." }];
|
||||
}
|
||||
if (errorRoles) {
|
||||
return [{ value: "error", label: "خطا در بارگذاری" }];
|
||||
}
|
||||
return [
|
||||
{ value: "", label: "همه نقش ها" },
|
||||
...roles.map((role) => ({
|
||||
value: role.id,
|
||||
label: role.name_fa,
|
||||
})),
|
||||
];
|
||||
}, [roles, errorRoles, loadingRoles]);
|
||||
return (
|
||||
<CustomSelectByDependency
|
||||
{...props}
|
||||
value={loadingRoles ? "loading" : props.filterParameters.value}
|
||||
columnSelectOption={getColumnSelectOptions}
|
||||
/>
|
||||
);
|
||||
},
|
||||
Cell: ({ row }) => <>{row.original.roles[0].name_fa}</>,
|
||||
},
|
||||
{
|
||||
accessorKey: "national_id",
|
||||
header: "کد ملی",
|
||||
id: "national_id",
|
||||
enableColumnFilter: true,
|
||||
datatype: "text",
|
||||
filterMode: "equals",
|
||||
sortDescFirst: false,
|
||||
columnFilterModeOptions: ["equals", "contains"],
|
||||
grow: false,
|
||||
size: 100,
|
||||
},
|
||||
{
|
||||
accessorKey: "gender",
|
||||
header: "جنسیت",
|
||||
id: "gender",
|
||||
enableColumnFilter: false,
|
||||
grow: false,
|
||||
size: 100,
|
||||
Cell: ({ renderedCellValue }) => (
|
||||
<>{renderedCellValue == "male" ? "آقا" : "خانم"}</>
|
||||
),
|
||||
},
|
||||
],
|
||||
[],
|
||||
);
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
{
|
||||
accessorKey: "id",
|
||||
header: "کد یکتا",
|
||||
id: "id",
|
||||
enableColumnFilter: true,
|
||||
datatype: "text",
|
||||
filterMode: "equals",
|
||||
sortDescFirst: false,
|
||||
columnFilterModeOptions: ["equals", "contains"],
|
||||
grow: false,
|
||||
size: 100,
|
||||
},
|
||||
{
|
||||
accessorKey: "full_name",
|
||||
header: "نام و نام خانوادگی",
|
||||
id: "full_name",
|
||||
enableColumnFilter: true,
|
||||
datatype: "text",
|
||||
filterMode: "equals",
|
||||
sortDescFirst: false,
|
||||
columnFilterModeOptions: ["equals", "contains"],
|
||||
grow: false,
|
||||
size: 100,
|
||||
},
|
||||
{
|
||||
header: "شماره تماس داخلی",
|
||||
id: "telephone_id",
|
||||
enableColumnFilter: true,
|
||||
datatype: "text",
|
||||
filterMode: "equals",
|
||||
sortDescFirst: false,
|
||||
columnFilterModeOptions: ["equals", "contains"],
|
||||
grow: false,
|
||||
size: 100,
|
||||
Cell: ({ row }) => <TelephoneId user_id={row.original.id} />,
|
||||
},
|
||||
{
|
||||
accessorKey: "is_online",
|
||||
header: "وضعیت برخط",
|
||||
id: "is_online",
|
||||
enableColumnFilter: true,
|
||||
datatype: "text",
|
||||
filterMode: "equals",
|
||||
sortDescFirst: false,
|
||||
columnFilterModeOptions: ["equals", "contains"],
|
||||
grow: false,
|
||||
size: 100,
|
||||
Cell: ({ row }) => <OnlineCell user_id={row.original.id} />,
|
||||
},
|
||||
{
|
||||
header: "استان",
|
||||
id: "province_id",
|
||||
enableColumnFilter: true,
|
||||
datatype: "numeric",
|
||||
filterMode: "equals",
|
||||
grow: false,
|
||||
size: 130,
|
||||
ColumnSelectComponent: (props) => {
|
||||
const { provinces, errorProvinces, loadingProvinces } = useProvinces();
|
||||
const getColumnSelectOptions = useMemo(() => {
|
||||
if (loadingProvinces) {
|
||||
return [{ value: "loading", label: "در حال بارگذاری..." }];
|
||||
}
|
||||
if (errorProvinces) {
|
||||
return [{ value: "error", label: "خطا در بارگذاری" }];
|
||||
}
|
||||
return [
|
||||
{ value: "", label: "کل کشور" },
|
||||
...provinces.map((province) => ({
|
||||
value: province.id,
|
||||
label: province.name,
|
||||
})),
|
||||
];
|
||||
}, [provinces, errorProvinces, loadingProvinces]);
|
||||
return (
|
||||
<CustomSelectByDependency
|
||||
{...props}
|
||||
value={loadingProvinces ? "loading" : props.filterParameters.value}
|
||||
columnSelectOption={getColumnSelectOptions}
|
||||
/>
|
||||
);
|
||||
},
|
||||
Cell: ({ renderedCellValue, row }) => <>{row.original.province_fa}</>,
|
||||
},
|
||||
{
|
||||
accessorKey: "username",
|
||||
header: "نام کاربری",
|
||||
id: "username",
|
||||
enableColumnFilter: true,
|
||||
datatype: "text",
|
||||
filterMode: "equals",
|
||||
sortDescFirst: false,
|
||||
columnFilterModeOptions: ["equals", "contains"],
|
||||
grow: false,
|
||||
size: 100,
|
||||
},
|
||||
{
|
||||
accessorKey: "position",
|
||||
header: "سمت",
|
||||
id: "position",
|
||||
enableColumnFilter: false,
|
||||
grow: false,
|
||||
size: 100,
|
||||
},
|
||||
{
|
||||
accessorKey: "phone_number",
|
||||
header: "شماره همراه",
|
||||
id: "phone_number",
|
||||
enableColumnFilter: false,
|
||||
grow: false,
|
||||
size: 100,
|
||||
},
|
||||
{
|
||||
header: "نقش",
|
||||
id: "role__id",
|
||||
enableColumnFilter: false,
|
||||
grow: false,
|
||||
size: 100,
|
||||
enableColumnFilter: true,
|
||||
datatype: "numeric",
|
||||
filterMode: "equals",
|
||||
grow: false,
|
||||
size: 130,
|
||||
ColumnSelectComponent: (props) => {
|
||||
const { roles, errorRoles, loadingRoles } = useRoles();
|
||||
const getColumnSelectOptions = useMemo(() => {
|
||||
if (loadingRoles) {
|
||||
return [{ value: "loading", label: "در حال بارگذاری..." }];
|
||||
}
|
||||
if (errorRoles) {
|
||||
return [{ value: "error", label: "خطا در بارگذاری" }];
|
||||
}
|
||||
return [
|
||||
{ value: "", label: "همه نقش ها" },
|
||||
...roles.map((role) => ({
|
||||
value: role.id,
|
||||
label: role.name_fa,
|
||||
})),
|
||||
];
|
||||
}, [roles, errorRoles, loadingRoles]);
|
||||
return (
|
||||
<CustomSelectByDependency
|
||||
{...props}
|
||||
value={loadingRoles ? "loading" : props.filterParameters.value}
|
||||
columnSelectOption={getColumnSelectOptions}
|
||||
/>
|
||||
);
|
||||
},
|
||||
Cell: ({ row }) => <>{row.original.roles[0].name_fa}</>,
|
||||
},
|
||||
{
|
||||
accessorKey: "national_id",
|
||||
header: "کد ملی",
|
||||
id: "national_id",
|
||||
enableColumnFilter: true,
|
||||
datatype: "text",
|
||||
filterMode: "equals",
|
||||
sortDescFirst: false,
|
||||
columnFilterModeOptions: ["equals", "contains"],
|
||||
grow: false,
|
||||
size: 100,
|
||||
},
|
||||
{
|
||||
accessorKey: "gender",
|
||||
header: "جنسیت",
|
||||
id: "gender",
|
||||
enableColumnFilter: false,
|
||||
grow: false,
|
||||
size: 100,
|
||||
Cell: ({ renderedCellValue }) => <>{renderedCellValue == "male" ? "آقا" : "خانم"}</>,
|
||||
},
|
||||
],
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box sx={{ p: 1, border: 1, borderColor: "divider", borderRadius: 1 }}>
|
||||
<DataTableWithAuth
|
||||
need_filter={true}
|
||||
columns={columns}
|
||||
sorting={[{ id: "id", desc: true }]}
|
||||
table_url={GET_USER_LIST}
|
||||
page_name={"usersPage"}
|
||||
table_name={"usersList"}
|
||||
TableToolbar={Toolbar}
|
||||
enableRowActions
|
||||
positionActionsColumn={"last"}
|
||||
RowActions={RowActions}
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<Box sx={{ p: 1, border: 1, borderColor: "divider", borderRadius: 1 }}>
|
||||
<DataTableWithAuth
|
||||
need_filter={true}
|
||||
columns={columns}
|
||||
sorting={[{ id: "id", desc: true }]}
|
||||
table_url={GET_USER_LIST}
|
||||
page_name={"usersPage"}
|
||||
table_name={"usersList"}
|
||||
TableToolbar={Toolbar}
|
||||
enableRowActions
|
||||
positionActionsColumn={"last"}
|
||||
RowActions={RowActions}
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default DataTable;
|
||||
|
||||
@@ -1,56 +1,39 @@
|
||||
import { DELETE_USER } from "@/core/utils/routes";
|
||||
import useRequest from "@/lib/hooks/useRequest";
|
||||
import {
|
||||
Button,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
Stack,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import { Button, DialogActions, DialogContent, Stack, Typography } from "@mui/material";
|
||||
import { useState } from "react";
|
||||
|
||||
const DeleteContent = ({ rowId, mutate, setOpenDeleteDialog }) => {
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const requestServer = useRequest({ notificationSuccess: true });
|
||||
const handleClick = async () => {
|
||||
setSubmitting(true);
|
||||
try {
|
||||
await requestServer(`${DELETE_USER}/${rowId}`, "delete");
|
||||
mutate();
|
||||
setOpenDeleteDialog(false);
|
||||
} catch (error) {
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<DialogContent>
|
||||
<Stack alignItems="center" spacing={2}>
|
||||
<Typography mt={2}>آیا از حذف کاربر اطمینان دارید؟</Typography>
|
||||
</Stack>
|
||||
</DialogContent>
|
||||
<DialogActions
|
||||
sx={{ justifyContent: "center", paddingBottom: 2, width: "100%" }}
|
||||
>
|
||||
<Button
|
||||
onClick={() => setOpenDeleteDialog(false)}
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
>
|
||||
خیر !
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleClick}
|
||||
variant="contained"
|
||||
color="error"
|
||||
disabled={submitting}
|
||||
>
|
||||
{submitting ? "درحال حذف..." : "بله اطمینان دارم"}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</>
|
||||
);
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const requestServer = useRequest({ notificationSuccess: true });
|
||||
const handleClick = async () => {
|
||||
setSubmitting(true);
|
||||
try {
|
||||
await requestServer(`${DELETE_USER}/${rowId}`, "delete");
|
||||
mutate();
|
||||
setOpenDeleteDialog(false);
|
||||
} catch (error) {
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<DialogContent>
|
||||
<Stack alignItems="center" spacing={2}>
|
||||
<Typography mt={2}>آیا از حذف کاربر اطمینان دارید؟</Typography>
|
||||
</Stack>
|
||||
</DialogContent>
|
||||
<DialogActions sx={{ justifyContent: "center", paddingBottom: 2, width: "100%" }}>
|
||||
<Button onClick={() => setOpenDeleteDialog(false)} variant="outlined" color="secondary">
|
||||
خیر !
|
||||
</Button>
|
||||
<Button onClick={handleClick} variant="contained" color="error" disabled={submitting}>
|
||||
{submitting ? "درحال حذف..." : "بله اطمینان دارم"}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeleteContent;
|
||||
|
||||
@@ -3,35 +3,30 @@ import { Dialog, DialogTitle, IconButton, Tooltip } from "@mui/material";
|
||||
import { useState } from "react";
|
||||
import DeleteContent from "./DeleteContent";
|
||||
const Delete = ({ rowId, mutate }) => {
|
||||
const [openDeleteDialog, setOpenDeleteDialog] = useState(false);
|
||||
const [openDeleteDialog, setOpenDeleteDialog] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tooltip title="حذف">
|
||||
<IconButton color="error" onClick={() => setOpenDeleteDialog(true)}>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Dialog
|
||||
open={openDeleteDialog}
|
||||
onClose={() => setOpenDeleteDialog(false)}
|
||||
PaperProps={{
|
||||
sx: {
|
||||
boxShadow:
|
||||
"rgba(60, 64, 67, 0.3) 0px 1px 16px 0px, rgba(60, 64, 67, 0.15) 0px 1px 10px 0px",
|
||||
},
|
||||
}}
|
||||
dir="rtl"
|
||||
maxWidth={"md"}
|
||||
>
|
||||
<DialogTitle>حذف</DialogTitle>
|
||||
<DeleteContent
|
||||
rowId={rowId}
|
||||
mutate={mutate}
|
||||
setOpenDeleteDialog={setOpenDeleteDialog}
|
||||
/>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<Tooltip title="حذف">
|
||||
<IconButton color="error" onClick={() => setOpenDeleteDialog(true)}>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Dialog
|
||||
open={openDeleteDialog}
|
||||
onClose={() => setOpenDeleteDialog(false)}
|
||||
PaperProps={{
|
||||
sx: {
|
||||
boxShadow: "rgba(60, 64, 67, 0.3) 0px 1px 16px 0px, rgba(60, 64, 67, 0.15) 0px 1px 10px 0px",
|
||||
},
|
||||
}}
|
||||
dir="rtl"
|
||||
maxWidth={"md"}
|
||||
>
|
||||
<DialogTitle>حذف</DialogTitle>
|
||||
<DeleteContent rowId={rowId} mutate={mutate} setOpenDeleteDialog={setOpenDeleteDialog} />
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default Delete;
|
||||
|
||||
@@ -7,363 +7,378 @@ import useRequest from "@/lib/hooks/useRequest";
|
||||
import useRoles from "@/lib/hooks/useRoles";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import {
|
||||
Button,
|
||||
Chip,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
Divider,
|
||||
FormControl,
|
||||
FormHelperText,
|
||||
Grid,
|
||||
InputLabel,
|
||||
MenuItem,
|
||||
OutlinedInput,
|
||||
Select,
|
||||
Stack,
|
||||
TextField,
|
||||
Button,
|
||||
Chip,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
Divider,
|
||||
FormControl,
|
||||
FormHelperText,
|
||||
Grid,
|
||||
InputLabel,
|
||||
MenuItem,
|
||||
OutlinedInput,
|
||||
Select,
|
||||
Stack,
|
||||
TextField,
|
||||
} from "@mui/material";
|
||||
import { useMemo } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { object, string } from "yup";
|
||||
|
||||
const validationSchema = object().shape({
|
||||
full_name: string().required("نام و نام خانوادگی خود را وارد کنید"),
|
||||
username: string().required("نام کاربری خود را وارد کنید"),
|
||||
phone_number: string()
|
||||
.matches(/^09\d{9}$/, "شماره همراه باید با 09 شروع شود و 11 رقم باشد.")
|
||||
.required("شماره همراه خود را وارد کنید"),
|
||||
gender: string().required("جنسیت خود را وارد کنید"),
|
||||
national_id: string()
|
||||
.test(
|
||||
"max",
|
||||
"کد ملی باید شامل 10 رقم باشد",
|
||||
(value) => value.toString().length === 10,
|
||||
)
|
||||
.test("validation", "کد ملی صحیح نمی باشد", (value) =>
|
||||
validateNationalCode(value),
|
||||
)
|
||||
.required("کد ملی خود را وارد کنید"),
|
||||
position: string().required("سمت خود را وارد کنید"),
|
||||
province_id: string().required("استان خود را وارد کنید"),
|
||||
role_id: string().required("نقش خود را وارد کنید"),
|
||||
full_name: string().required("نام و نام خانوادگی را وارد کنید"),
|
||||
username: string().required("نام کاربری را وارد کنید"),
|
||||
phone_number: string()
|
||||
.matches(/^09\d{9}$/, "شماره همراه باید با 09 شروع شود و 11 رقم باشد.")
|
||||
.required("شماره همراه را وارد کنید"),
|
||||
gender: string().required("جنسیت را وارد کنید"),
|
||||
national_id: string()
|
||||
.test("max", "کد ملی باید شامل 10 رقم باشد", (value) => value.toString().length === 10)
|
||||
.test("validation", "کد ملی صحیح نمی باشد", (value) => validateNationalCode(value))
|
||||
.required("کد ملی را وارد کنید"),
|
||||
position: string().required("سمت را وارد کنید"),
|
||||
province_id: string().required("استان را وارد کنید"),
|
||||
role_id: string().required("نقش را وارد کنید"),
|
||||
telephone_id: string().required("شماره تماس داخلی را وارد کنید")
|
||||
});
|
||||
|
||||
const UpdateUserForm = ({ values, setOpen, mutate }) => {
|
||||
const requestServer = useRequest({ notificationSuccess: true });
|
||||
const { provinces, errorProvinces, loadingProvinces } = useProvinces();
|
||||
const { roles, errorRoles, loadingRoles } = useRoles();
|
||||
const requestServer = useRequest({ notificationSuccess: true });
|
||||
const { provinces, errorProvinces, loadingProvinces } = useProvinces();
|
||||
const { roles, errorRoles, loadingRoles } = useRoles();
|
||||
|
||||
const defaultValues = {
|
||||
full_name: values.full_name,
|
||||
username: values.username,
|
||||
position: values.position,
|
||||
gender: values.gender,
|
||||
national_id: values.national_id,
|
||||
phone_number: values.phone_number,
|
||||
province_id: values.province_id,
|
||||
role_id: values.roles[0].id,
|
||||
};
|
||||
const defaultValues = {
|
||||
full_name: values.full_name,
|
||||
username: values.username,
|
||||
position: values.position,
|
||||
gender: values.gender,
|
||||
national_id: values.national_id,
|
||||
phone_number: values.phone_number,
|
||||
province_id: values.province_id,
|
||||
role_id: values.roles[0].id,
|
||||
telephone_id: values.telephone_id
|
||||
};
|
||||
|
||||
const provinceSelectOptions = useMemo(() => {
|
||||
if (loadingProvinces) {
|
||||
return [{ value: "loading", label: "در حال بارگذاری..." }];
|
||||
}
|
||||
if (errorProvinces) {
|
||||
return [{ value: "error", label: "خطا در بارگذاری" }];
|
||||
}
|
||||
return [
|
||||
{ value: "", label: "انتخاب استان" },
|
||||
...provinces.map((province) => ({
|
||||
value: province.id,
|
||||
label: province.name,
|
||||
})),
|
||||
];
|
||||
}, [provinces, errorProvinces, loadingProvinces]);
|
||||
const provinceSelectOptions = useMemo(() => {
|
||||
if (loadingProvinces) {
|
||||
return [{ value: "loading", label: "در حال بارگذاری..." }];
|
||||
}
|
||||
if (errorProvinces) {
|
||||
return [{ value: "error", label: "خطا در بارگذاری" }];
|
||||
}
|
||||
return [
|
||||
{ value: "", label: "انتخاب استان" },
|
||||
...provinces.map((province) => ({
|
||||
value: province.id,
|
||||
label: province.name,
|
||||
})),
|
||||
];
|
||||
}, [provinces, errorProvinces, loadingProvinces]);
|
||||
|
||||
const roleSelectOptions = useMemo(() => {
|
||||
if (loadingRoles) {
|
||||
return [{ value: "loading", label: "در حال بارگذاری..." }];
|
||||
}
|
||||
if (errorRoles) {
|
||||
return [{ value: "error", label: "خطا در بارگذاری" }];
|
||||
}
|
||||
return [
|
||||
{ value: "", label: "انتخاب نقش" },
|
||||
...roles.map((role) => ({
|
||||
value: role.id,
|
||||
label: role.name_fa,
|
||||
})),
|
||||
];
|
||||
}, [roles, errorRoles, loadingRoles]);
|
||||
const roleSelectOptions = useMemo(() => {
|
||||
if (loadingRoles) {
|
||||
return [{ value: "loading", label: "در حال بارگذاری..." }];
|
||||
}
|
||||
if (errorRoles) {
|
||||
return [{ value: "error", label: "خطا در بارگذاری" }];
|
||||
}
|
||||
return [
|
||||
{ value: "", label: "انتخاب نقش" },
|
||||
...roles.map((role) => ({
|
||||
value: role.id,
|
||||
label: role.name_fa,
|
||||
})),
|
||||
];
|
||||
}, [roles, errorRoles, loadingRoles]);
|
||||
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
formState: { isSubmitting, errors },
|
||||
} = useForm({ defaultValues, resolver: yupResolver(validationSchema) });
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
formState: { isSubmitting, errors },
|
||||
} = useForm({ defaultValues, resolver: yupResolver(validationSchema) });
|
||||
|
||||
const onSubmit = async (data) => {
|
||||
const formData = new FormData();
|
||||
Object.keys(data).forEach((key) => {
|
||||
formData.append(key, data[key]);
|
||||
});
|
||||
try {
|
||||
await requestServer(`${UPDATE_USER}/${values.id}`, "post", {
|
||||
data: formData,
|
||||
});
|
||||
setOpen(false);
|
||||
mutate();
|
||||
} catch (error) {}
|
||||
};
|
||||
const onSubmit = async (data) => {
|
||||
const formData = new FormData();
|
||||
Object.keys(data).forEach((key) => {
|
||||
formData.append(key, data[key]);
|
||||
});
|
||||
try {
|
||||
await requestServer(`${UPDATE_USER}/${values.id}`, "post", {
|
||||
data: formData,
|
||||
});
|
||||
setOpen(false);
|
||||
mutate();
|
||||
} catch (error) {}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<DialogContent dividers>
|
||||
<Divider sx={{ mb: 3 }}>
|
||||
<Chip color="primary" variant="outlined" label="کاربر جدید" />
|
||||
</Divider>
|
||||
<StyledForm
|
||||
id="UpdateUserForm"
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
style={{ width: "100%", height: "100%" }}
|
||||
>
|
||||
<Stack spacing={2}>
|
||||
<Grid container spacing={2}>
|
||||
<Grid size={{ xs: 12, md: 6 }}>
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<LtrTextField
|
||||
{...field}
|
||||
label="نام کاربری"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
error={!!error}
|
||||
helperText={!!error && error.message}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
name={"username"}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, md: 6 }}>
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<TextField
|
||||
{...field}
|
||||
label="نام و نام خانوادگی"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
error={!!error}
|
||||
helperText={!!error && error.message}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
name={"full_name"}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, md: 6 }}>
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<TextField
|
||||
{...field}
|
||||
label="سمت"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
error={!!error}
|
||||
helperText={!!error && error.message}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
name={"position"}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, md: 6 }}>
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<LtrTextField
|
||||
{...field}
|
||||
label="شماره همراه"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
type="tel"
|
||||
onChange={(e) => {
|
||||
const inputValue = event.target.value;
|
||||
if (isNaN(Number(inputValue))) {
|
||||
return;
|
||||
}
|
||||
if (inputValue.length > 11) {
|
||||
return;
|
||||
}
|
||||
field.onChange(inputValue);
|
||||
}}
|
||||
error={!!error}
|
||||
helperText={!!error && error.message}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
name={"phone_number"}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, md: 6 }}>
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<FormControl fullWidth error={!!error}>
|
||||
<InputLabel id={`label-phone-number`} shrink>
|
||||
جنسیت
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId={`label-phone-number`}
|
||||
id={"phone-number"}
|
||||
name={`gender`}
|
||||
value={field.value}
|
||||
input={<OutlinedInput label={"جنسیت"} />}
|
||||
onChange={(e) => field.onChange(e.target.value)}
|
||||
displayEmpty
|
||||
>
|
||||
<MenuItem value={"male"}>مرد</MenuItem>
|
||||
<MenuItem value={"female"}>زن</MenuItem>
|
||||
</Select>
|
||||
<FormHelperText error={!!error} sx={{ mt: 1 }}>
|
||||
{!!error && error.message}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
);
|
||||
}}
|
||||
name={"gender"}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, md: 6 }}>
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<LtrTextField
|
||||
{...field}
|
||||
label="شماره ملی"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
type="tel"
|
||||
onChange={(e) => {
|
||||
const inputValue = event.target.value;
|
||||
if (isNaN(Number(inputValue))) {
|
||||
return;
|
||||
}
|
||||
if (inputValue.length > 10) {
|
||||
return;
|
||||
}
|
||||
field.onChange(inputValue);
|
||||
}}
|
||||
error={!!error}
|
||||
helperText={!!error && error.message}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
name={"national_id"}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, md: 6 }}>
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<FormControl fullWidth error={!!error}>
|
||||
<InputLabel id={`label-province`} shrink>
|
||||
استان
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId={`label-province`}
|
||||
id={"province"}
|
||||
name={`province_id`}
|
||||
value={loadingProvinces ? "loading" : field.value}
|
||||
input={<OutlinedInput label={"استان"} />}
|
||||
onChange={(e) => field.onChange(e.target.value)}
|
||||
displayEmpty
|
||||
>
|
||||
{provinceSelectOptions.map((option) => (
|
||||
<MenuItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
<FormHelperText error={!!error} sx={{ mt: 1 }}>
|
||||
{!!error && error.message}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
);
|
||||
}}
|
||||
name={"province_id"}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, md: 6 }}>
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<FormControl fullWidth error={!!error}>
|
||||
<InputLabel id={`label-role`} shrink>
|
||||
نقش
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId={`label-role`}
|
||||
id={"role"}
|
||||
name={`role_id`}
|
||||
value={loadingRoles ? "loading" : field.value}
|
||||
input={<OutlinedInput label={"نقش"} />}
|
||||
onChange={(e) => field.onChange(e.target.value)}
|
||||
displayEmpty
|
||||
>
|
||||
{roleSelectOptions.map((option) => (
|
||||
<MenuItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
<FormHelperText error={!!error} sx={{ mt: 1 }}>
|
||||
{!!error && error.message}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
);
|
||||
}}
|
||||
name={"role_id"}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Stack>
|
||||
</StyledForm>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
disabled={isSubmitting}
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
size="large"
|
||||
onClick={() => setOpen(false)}
|
||||
>
|
||||
بستن
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={isSubmitting}
|
||||
form="UpdateUserForm"
|
||||
size="large"
|
||||
autoFocus
|
||||
variant="contained"
|
||||
>
|
||||
{isSubmitting ? "در حال ویرایش کاربر..." : "ویرایش کاربر"}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<DialogContent dividers>
|
||||
<Divider sx={{ mb: 3 }}>
|
||||
<Chip color="primary" variant="outlined" label="کاربر جدید" />
|
||||
</Divider>
|
||||
<StyledForm
|
||||
id="UpdateUserForm"
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
style={{ width: "100%", height: "100%" }}
|
||||
>
|
||||
<Stack spacing={2}>
|
||||
<Grid container spacing={2}>
|
||||
<Grid size={{ xs: 12, md: 6 }}>
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<LtrTextField
|
||||
{...field}
|
||||
label="نام کاربری"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
error={!!error}
|
||||
helperText={!!error && error.message}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
name={"username"}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, md: 6 }}>
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<TextField
|
||||
{...field}
|
||||
label="نام و نام خانوادگی"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
error={!!error}
|
||||
helperText={!!error && error.message}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
name={"full_name"}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, md: 6 }}>
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<TextField
|
||||
{...field}
|
||||
label="سمت"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
error={!!error}
|
||||
helperText={!!error && error.message}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
name={"position"}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, md: 6 }}>
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<LtrTextField
|
||||
{...field}
|
||||
label="شماره همراه"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
type="tel"
|
||||
onChange={(e) => {
|
||||
const inputValue = event.target.value;
|
||||
if (isNaN(Number(inputValue))) {
|
||||
return;
|
||||
}
|
||||
if (inputValue.length > 11) {
|
||||
return;
|
||||
}
|
||||
field.onChange(inputValue);
|
||||
}}
|
||||
error={!!error}
|
||||
helperText={!!error && error.message}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
name={"phone_number"}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, md: 6 }}>
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<FormControl fullWidth error={!!error}>
|
||||
<InputLabel id={`label-gender`} shrink>
|
||||
جنسیت
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId={`label-gender`}
|
||||
id={"gender"}
|
||||
name={`gender`}
|
||||
value={field.value}
|
||||
input={<OutlinedInput label={"جنسیت"} />}
|
||||
onChange={(e) => field.onChange(e.target.value)}
|
||||
displayEmpty
|
||||
>
|
||||
<MenuItem value={"male"}>مرد</MenuItem>
|
||||
<MenuItem value={"female"}>زن</MenuItem>
|
||||
</Select>
|
||||
<FormHelperText error={!!error} sx={{ mt: 1 }}>
|
||||
{!!error && error.message}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
);
|
||||
}}
|
||||
name={"gender"}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, md: 6 }}>
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<LtrTextField
|
||||
{...field}
|
||||
label="شماره ملی"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
type="tel"
|
||||
onChange={(e) => {
|
||||
const inputValue = event.target.value;
|
||||
if (isNaN(Number(inputValue))) {
|
||||
return;
|
||||
}
|
||||
if (inputValue.length > 10) {
|
||||
return;
|
||||
}
|
||||
field.onChange(inputValue);
|
||||
}}
|
||||
error={!!error}
|
||||
helperText={!!error && error.message}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
name={"national_id"}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, md: 6 }}>
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<FormControl fullWidth error={!!error}>
|
||||
<InputLabel id={`label-province`} shrink>
|
||||
استان
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId={`label-province`}
|
||||
id={"province"}
|
||||
name={`province_id`}
|
||||
value={loadingProvinces ? "loading" : field.value}
|
||||
input={<OutlinedInput label={"استان"} />}
|
||||
onChange={(e) => field.onChange(e.target.value)}
|
||||
displayEmpty
|
||||
>
|
||||
{provinceSelectOptions.map((option) => (
|
||||
<MenuItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
<FormHelperText error={!!error} sx={{ mt: 1 }}>
|
||||
{!!error && error.message}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
);
|
||||
}}
|
||||
name={"province_id"}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, md: 6 }}>
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<FormControl fullWidth error={!!error}>
|
||||
<InputLabel id={`label-role`} shrink>
|
||||
نقش
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId={`label-role`}
|
||||
id={"role"}
|
||||
name={`role_id`}
|
||||
value={loadingRoles ? "loading" : field.value}
|
||||
input={<OutlinedInput label={"نقش"} />}
|
||||
onChange={(e) => field.onChange(e.target.value)}
|
||||
displayEmpty
|
||||
>
|
||||
{roleSelectOptions.map((option) => (
|
||||
<MenuItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
<FormHelperText error={!!error} sx={{ mt: 1 }}>
|
||||
{!!error && error.message}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
);
|
||||
}}
|
||||
name={"role_id"}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, md: 6 }}>
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<LtrTextField
|
||||
{...field}
|
||||
label="شماره تماس داخلی"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
type="tel"
|
||||
error={!!error}
|
||||
helperText={!!error && error.message}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
name={"telephone_id"}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Stack>
|
||||
</StyledForm>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
disabled={isSubmitting}
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
size="large"
|
||||
onClick={() => setOpen(false)}
|
||||
>
|
||||
بستن
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={isSubmitting}
|
||||
form="UpdateUserForm"
|
||||
size="large"
|
||||
autoFocus
|
||||
variant="contained"
|
||||
>
|
||||
{isSubmitting ? "در حال ویرایش کاربر..." : "ویرایش کاربر"}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default UpdateUserForm;
|
||||
|
||||
@@ -5,28 +5,23 @@ import { useState } from "react";
|
||||
import UpdateUserForm from "./Form";
|
||||
|
||||
const UpdateUser = ({ values, mutate }) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const handleOpen = () => {
|
||||
setOpen(true);
|
||||
};
|
||||
const handleOpen = () => {
|
||||
setOpen(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tooltip title="ویرایش">
|
||||
<IconButton onClick={handleOpen}>
|
||||
<Edit />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Dialog maxWidth="sm" fullWidth={true} open={open}>
|
||||
<UpdateUserForm
|
||||
open={open}
|
||||
setOpen={setOpen}
|
||||
mutate={mutate}
|
||||
values={values}
|
||||
/>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<Tooltip title="ویرایش">
|
||||
<IconButton onClick={handleOpen}>
|
||||
<Edit />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Dialog maxWidth="sm" fullWidth={true} open={open}>
|
||||
<UpdateUserForm open={open} setOpen={setOpen} mutate={mutate} values={values} />
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default UpdateUser;
|
||||
|
||||
@@ -3,15 +3,11 @@ import { Power, PowerOff } from "@mui/icons-material";
|
||||
import { Stack } from "@mui/material";
|
||||
|
||||
const OnlineCell = ({ user_id }) => {
|
||||
const { clientsOnline } = useSocket();
|
||||
return (
|
||||
<Stack alignItems={"center"}>
|
||||
{clientsOnline.some((co) => co.user_id == user_id) ? (
|
||||
<Power color="success" />
|
||||
) : (
|
||||
<PowerOff color="error" />
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
const { clientsOnline } = useSocket();
|
||||
return (
|
||||
<Stack alignItems={"center"}>
|
||||
{clientsOnline.some((co) => co.user_id == user_id) ? <Power color="success" /> : <PowerOff color="error" />}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
export default OnlineCell;
|
||||
|
||||
@@ -2,10 +2,8 @@ import { useSocket } from "@/lib/contexts/socket";
|
||||
import { Stack } from "@mui/material";
|
||||
|
||||
const TelephoneId = ({ user_id }) => {
|
||||
const { clientsOnline } = useSocket();
|
||||
const telephone_id = clientsOnline.find(
|
||||
(co) => co.user_id == user_id,
|
||||
)?.telephone_id;
|
||||
return <Stack alignItems={"center"}>{telephone_id}</Stack>;
|
||||
const { clientsOnline } = useSocket();
|
||||
const telephone_id = clientsOnline.find((co) => co.user_id == user_id)?.telephone_id;
|
||||
return <Stack alignItems={"center"}>{telephone_id}</Stack>;
|
||||
};
|
||||
export default TelephoneId;
|
||||
|
||||
@@ -2,11 +2,11 @@ import { Box } from "@mui/material";
|
||||
import Delete from "./Delete";
|
||||
import UpdateUser from "./Edit";
|
||||
const RowActions = ({ row, mutate }) => {
|
||||
return (
|
||||
<Box sx={{ display: "flex", gap: 1, alignItems: "center" }}>
|
||||
<Delete rowId={row.original.id} mutate={mutate} />
|
||||
<UpdateUser values={row.original} mutate={mutate} />
|
||||
</Box>
|
||||
);
|
||||
return (
|
||||
<Box sx={{ display: "flex", gap: 1, alignItems: "center" }}>
|
||||
<Delete rowId={row.original.id} mutate={mutate} />
|
||||
<UpdateUser values={row.original} mutate={mutate} />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
export default RowActions;
|
||||
|
||||
@@ -4,22 +4,22 @@ import validateNationalCode from "@/core/utils/nationalCodeValidation";
|
||||
import useProvinces from "@/lib/hooks/useProvince";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import {
|
||||
Button,
|
||||
Chip,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
Divider,
|
||||
FormControl,
|
||||
FormHelperText,
|
||||
Grid,
|
||||
IconButton,
|
||||
InputAdornment,
|
||||
InputLabel,
|
||||
MenuItem,
|
||||
OutlinedInput,
|
||||
Select,
|
||||
Stack,
|
||||
TextField,
|
||||
Button,
|
||||
Chip,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
Divider,
|
||||
FormControl,
|
||||
FormHelperText,
|
||||
Grid,
|
||||
IconButton,
|
||||
InputAdornment,
|
||||
InputLabel,
|
||||
MenuItem,
|
||||
OutlinedInput,
|
||||
Select,
|
||||
Stack,
|
||||
TextField,
|
||||
} from "@mui/material";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { object, string } from "yup";
|
||||
@@ -30,387 +30,395 @@ import { CREATE_USER } from "@/core/utils/routes";
|
||||
import { Visibility, VisibilityOff } from "@mui/icons-material";
|
||||
|
||||
const defaultValues = {
|
||||
full_name: "",
|
||||
username: "",
|
||||
position: "",
|
||||
gender: "male",
|
||||
national_id: "",
|
||||
phone_number: "",
|
||||
province_id: "",
|
||||
password: "",
|
||||
role_id: "",
|
||||
full_name: "",
|
||||
username: "",
|
||||
position: "",
|
||||
gender: "male",
|
||||
national_id: "",
|
||||
phone_number: "",
|
||||
province_id: "",
|
||||
password: "",
|
||||
role_id: "",
|
||||
telephone_id: "",
|
||||
};
|
||||
|
||||
const validationSchema = object().shape({
|
||||
full_name: string().required("نام و نام خانوادگی خود را وارد کنید"),
|
||||
username: string().required("نام کاربری خود را وارد کنید"),
|
||||
phone_number: string()
|
||||
.matches(/^09\d{9}$/, "شماره همراه باید با 09 شروع شود و 11 رقم باشد.")
|
||||
.required("شماره همراه خود را وارد کنید"),
|
||||
gender: string().required("جنسیت خود را وارد کنید"),
|
||||
national_id: string()
|
||||
.test(
|
||||
"max",
|
||||
"کد ملی باید شامل 10 رقم باشد",
|
||||
(value) => value.toString().length === 10,
|
||||
)
|
||||
.test("validation", "کد ملی صحیح نمی باشد", (value) =>
|
||||
validateNationalCode(value),
|
||||
)
|
||||
.required("کد ملی خود را وارد کنید"),
|
||||
password: string()
|
||||
.required("رمز عبور خود را وارد کنید")
|
||||
.matches(
|
||||
/^(?=.*[a-zA-Z])(?=.*\d).{8,}$/,
|
||||
"رمز عبور باید حداقل 8 کاراکتر و متشکل از عدد، حرف یا سمبل باشد",
|
||||
),
|
||||
position: string().required("سمت خود را وارد کنید"),
|
||||
province_id: string().required("استان خود را وارد کنید"),
|
||||
role_id: string().required("نقش خود را وارد کنید"),
|
||||
full_name: string().required("نام و نام خانوادگی را وارد کنید"),
|
||||
username: string().required("نام کاربری را وارد کنید"),
|
||||
phone_number: string()
|
||||
.matches(/^09\d{9}$/, "شماره همراه باید با 09 شروع شود و 11 رقم باشد.")
|
||||
.required("شماره همراه را وارد کنید"),
|
||||
gender: string().required("جنسیت را وارد کنید"),
|
||||
national_id: string()
|
||||
.test("max", "کد ملی باید شامل 10 رقم باشد", (value) => value.toString().length === 10)
|
||||
.test("validation", "کد ملی صحیح نمی باشد", (value) => validateNationalCode(value))
|
||||
.required("کد ملی را وارد کنید"),
|
||||
password: string()
|
||||
.required("رمز عبور را وارد کنید")
|
||||
.matches(/^(?=.*[a-zA-Z])(?=.*\d).{8,}$/, "رمز عبور باید حداقل 8 کاراکتر و متشکل از عدد، حرف یا سمبل باشد"),
|
||||
position: string().required("سمت را وارد کنید"),
|
||||
province_id: string().required("استان را وارد کنید"),
|
||||
role_id: string().required("نقش را وارد کنید"),
|
||||
telephone_id: string().required("شماره تماس داخلی را وارد کنید"),
|
||||
});
|
||||
|
||||
const CreateUserForm = ({ setOpen, mutate }) => {
|
||||
const requestServer = useRequest({ notificationSuccess: true });
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const { provinces, errorProvinces, loadingProvinces } = useProvinces();
|
||||
const { roles, errorRoles, loadingRoles } = useRoles();
|
||||
const requestServer = useRequest({ notificationSuccess: true });
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const { provinces, errorProvinces, loadingProvinces } = useProvinces();
|
||||
const { roles, errorRoles, loadingRoles } = useRoles();
|
||||
|
||||
const handleClickShowPassword = () => setShowPassword((show) => !show);
|
||||
const handleClickShowPassword = () => setShowPassword((show) => !show);
|
||||
|
||||
const provinceSelectOptions = useMemo(() => {
|
||||
if (loadingProvinces) {
|
||||
return [{ value: "loading", label: "در حال بارگذاری..." }];
|
||||
}
|
||||
if (errorProvinces) {
|
||||
return [{ value: "error", label: "خطا در بارگذاری" }];
|
||||
}
|
||||
return [
|
||||
{ value: "", label: "انتخاب استان" },
|
||||
...provinces.map((province) => ({
|
||||
value: province.id,
|
||||
label: province.name,
|
||||
})),
|
||||
];
|
||||
}, [provinces, errorProvinces, loadingProvinces]);
|
||||
const provinceSelectOptions = useMemo(() => {
|
||||
if (loadingProvinces) {
|
||||
return [{ value: "loading", label: "در حال بارگذاری..." }];
|
||||
}
|
||||
if (errorProvinces) {
|
||||
return [{ value: "error", label: "خطا در بارگذاری" }];
|
||||
}
|
||||
return [
|
||||
{ value: "", label: "انتخاب استان" },
|
||||
...provinces.map((province) => ({
|
||||
value: province.id,
|
||||
label: province.name,
|
||||
})),
|
||||
];
|
||||
}, [provinces, errorProvinces, loadingProvinces]);
|
||||
|
||||
const roleSelectOptions = useMemo(() => {
|
||||
if (loadingRoles) {
|
||||
return [{ value: "loading", label: "در حال بارگذاری..." }];
|
||||
}
|
||||
if (errorRoles) {
|
||||
return [{ value: "error", label: "خطا در بارگذاری" }];
|
||||
}
|
||||
return [
|
||||
{ value: "", label: "انتخاب نقش" },
|
||||
...roles.map((role) => ({
|
||||
value: role.id,
|
||||
label: role.name_fa,
|
||||
})),
|
||||
];
|
||||
}, [roles, errorRoles, loadingRoles]);
|
||||
const roleSelectOptions = useMemo(() => {
|
||||
if (loadingRoles) {
|
||||
return [{ value: "loading", label: "در حال بارگذاری..." }];
|
||||
}
|
||||
if (errorRoles) {
|
||||
return [{ value: "error", label: "خطا در بارگذاری" }];
|
||||
}
|
||||
return [
|
||||
{ value: "", label: "انتخاب نقش" },
|
||||
...roles.map((role) => ({
|
||||
value: role.id,
|
||||
label: role.name_fa,
|
||||
})),
|
||||
];
|
||||
}, [roles, errorRoles, loadingRoles]);
|
||||
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
formState: { isSubmitting, errors },
|
||||
} = useForm({ defaultValues, resolver: yupResolver(validationSchema) });
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
formState: { isSubmitting, errors },
|
||||
} = useForm({ defaultValues, resolver: yupResolver(validationSchema) });
|
||||
|
||||
const onSubmit = async (data) => {
|
||||
const formData = new FormData();
|
||||
Object.keys(data).forEach((key) => {
|
||||
formData.append(key, data[key]);
|
||||
});
|
||||
try {
|
||||
await requestServer(CREATE_USER, "post", {
|
||||
data: formData,
|
||||
});
|
||||
setOpen(false);
|
||||
mutate();
|
||||
} catch (error) {}
|
||||
};
|
||||
const onSubmit = async (data) => {
|
||||
const formData = new FormData();
|
||||
Object.keys(data).forEach((key) => {
|
||||
formData.append(key, data[key]);
|
||||
});
|
||||
try {
|
||||
await requestServer(CREATE_USER, "post", {
|
||||
data: formData,
|
||||
});
|
||||
setOpen(false);
|
||||
mutate();
|
||||
} catch (error) {}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<DialogContent dividers>
|
||||
<Divider sx={{ mb: 3 }}>
|
||||
<Chip color="primary" variant="outlined" label="کاربر جدید" />
|
||||
</Divider>
|
||||
<StyledForm
|
||||
id="createUserForm"
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
style={{ width: "100%", height: "100%" }}
|
||||
>
|
||||
<Stack spacing={2}>
|
||||
<Grid container spacing={2}>
|
||||
<Grid size={{ xs: 12, md: 6 }}>
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<LtrTextField
|
||||
{...field}
|
||||
label="نام کاربری"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
error={!!error}
|
||||
helperText={!!error && error.message}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
name={"username"}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, md: 6 }}>
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<LtrTextField
|
||||
{...field}
|
||||
label="رمز عبور"
|
||||
variant="outlined"
|
||||
type={showPassword ? "text" : "password"}
|
||||
slotProps={{
|
||||
input: {
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<IconButton onClick={handleClickShowPassword}>
|
||||
{showPassword ? (
|
||||
<VisibilityOff />
|
||||
) : (
|
||||
<Visibility />
|
||||
)}
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
),
|
||||
},
|
||||
}}
|
||||
fullWidth
|
||||
error={!!error}
|
||||
helperText={!!error && error.message}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
name={"password"}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, md: 6 }}>
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<TextField
|
||||
{...field}
|
||||
label="نام و نام خانوادگی"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
error={!!error}
|
||||
helperText={!!error && error.message}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
name={"full_name"}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, md: 6 }}>
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<TextField
|
||||
{...field}
|
||||
label="سمت"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
error={!!error}
|
||||
helperText={!!error && error.message}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
name={"position"}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, md: 6 }}>
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<LtrTextField
|
||||
{...field}
|
||||
label="شماره همراه"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
type="tel"
|
||||
onChange={(e) => {
|
||||
const inputValue = event.target.value;
|
||||
if (isNaN(Number(inputValue))) {
|
||||
return;
|
||||
}
|
||||
if (inputValue.length > 11) {
|
||||
return;
|
||||
}
|
||||
field.onChange(inputValue);
|
||||
}}
|
||||
error={!!error}
|
||||
helperText={!!error && error.message}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
name={"phone_number"}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, md: 6 }}>
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<FormControl fullWidth error={!!error}>
|
||||
<InputLabel id={`label-phone-number`} shrink>
|
||||
جنسیت
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId={`label-phone-number`}
|
||||
id={"phone-number"}
|
||||
name={`gender`}
|
||||
value={field.value}
|
||||
input={<OutlinedInput label={"جنسیت"} />}
|
||||
onChange={(e) => field.onChange(e.target.value)}
|
||||
displayEmpty
|
||||
>
|
||||
<MenuItem value={"male"}>مرد</MenuItem>
|
||||
<MenuItem value={"female"}>زن</MenuItem>
|
||||
</Select>
|
||||
<FormHelperText error={!!error} sx={{ mt: 1 }}>
|
||||
{!!error && error.message}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
);
|
||||
}}
|
||||
name={"gender"}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, md: 6 }}>
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<LtrTextField
|
||||
{...field}
|
||||
label="شماره ملی"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
type="tel"
|
||||
onChange={(e) => {
|
||||
const inputValue = event.target.value;
|
||||
if (isNaN(Number(inputValue))) {
|
||||
return;
|
||||
}
|
||||
if (inputValue.length > 10) {
|
||||
return;
|
||||
}
|
||||
field.onChange(inputValue);
|
||||
}}
|
||||
error={!!error}
|
||||
helperText={!!error && error.message}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
name={"national_id"}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, md: 6 }}>
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<FormControl fullWidth error={!!error}>
|
||||
<InputLabel id={`label-province`} shrink>
|
||||
استان
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId={`label-province`}
|
||||
id={"province"}
|
||||
name={`province_id`}
|
||||
value={field.value}
|
||||
input={<OutlinedInput label={"استان"} />}
|
||||
onChange={(e) => field.onChange(e.target.value)}
|
||||
displayEmpty
|
||||
>
|
||||
{provinceSelectOptions.map((option) => (
|
||||
<MenuItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
<FormHelperText error={!!error} sx={{ mt: 1 }}>
|
||||
{!!error && error.message}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
);
|
||||
}}
|
||||
name={"province_id"}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, md: 6 }}>
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<FormControl fullWidth error={!!error}>
|
||||
<InputLabel id={`label-role`} shrink>
|
||||
نقش
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId={`label-role`}
|
||||
id={"role"}
|
||||
name={`role_id`}
|
||||
value={field.value}
|
||||
input={<OutlinedInput label={"نقش"} />}
|
||||
onChange={(e) => field.onChange(e.target.value)}
|
||||
displayEmpty
|
||||
>
|
||||
{roleSelectOptions.map((option) => (
|
||||
<MenuItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
<FormHelperText error={!!error} sx={{ mt: 1 }}>
|
||||
{!!error && error.message}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
);
|
||||
}}
|
||||
name={"role_id"}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Stack>
|
||||
</StyledForm>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
disabled={isSubmitting}
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
size="large"
|
||||
onClick={() => setOpen(false)}
|
||||
>
|
||||
بستن
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={isSubmitting}
|
||||
form="createUserForm"
|
||||
size="large"
|
||||
autoFocus
|
||||
variant="contained"
|
||||
>
|
||||
{isSubmitting ? "در حال ثبت کاربر جدید..." : "ثبت کاربر جدید"}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<DialogContent dividers>
|
||||
<Divider sx={{ mb: 3 }}>
|
||||
<Chip color="primary" variant="outlined" label="کاربر جدید" />
|
||||
</Divider>
|
||||
<StyledForm
|
||||
id="createUserForm"
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
style={{ width: "100%", height: "100%" }}
|
||||
>
|
||||
<Stack spacing={2}>
|
||||
<Grid container spacing={2}>
|
||||
<Grid size={{ xs: 12, md: 6 }}>
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<LtrTextField
|
||||
{...field}
|
||||
label="نام کاربری"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
error={!!error}
|
||||
helperText={!!error && error.message}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
name={"username"}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, md: 6 }}>
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<LtrTextField
|
||||
{...field}
|
||||
label="رمز عبور"
|
||||
variant="outlined"
|
||||
type={showPassword ? "text" : "password"}
|
||||
slotProps={{
|
||||
input: {
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<IconButton onClick={handleClickShowPassword}>
|
||||
{showPassword ? <VisibilityOff /> : <Visibility />}
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
),
|
||||
},
|
||||
}}
|
||||
fullWidth
|
||||
error={!!error}
|
||||
helperText={!!error && error.message}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
name={"password"}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, md: 6 }}>
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<TextField
|
||||
{...field}
|
||||
label="نام و نام خانوادگی"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
error={!!error}
|
||||
helperText={!!error && error.message}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
name={"full_name"}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, md: 6 }}>
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<TextField
|
||||
{...field}
|
||||
label="سمت"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
error={!!error}
|
||||
helperText={!!error && error.message}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
name={"position"}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, md: 6 }}>
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<LtrTextField
|
||||
{...field}
|
||||
label="شماره همراه"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
type="tel"
|
||||
onChange={(e) => {
|
||||
const inputValue = event.target.value;
|
||||
if (isNaN(Number(inputValue))) {
|
||||
return;
|
||||
}
|
||||
if (inputValue.length > 11) {
|
||||
return;
|
||||
}
|
||||
field.onChange(inputValue);
|
||||
}}
|
||||
error={!!error}
|
||||
helperText={!!error && error.message}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
name={"phone_number"}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, md: 6 }}>
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<FormControl fullWidth error={!!error}>
|
||||
<InputLabel id={`label-gender`} shrink>
|
||||
جنسیت
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId={`label-gender`}
|
||||
id={"gender"}
|
||||
name={`gender`}
|
||||
value={field.value}
|
||||
input={<OutlinedInput label={"جنسیت"} />}
|
||||
onChange={(e) => field.onChange(e.target.value)}
|
||||
displayEmpty
|
||||
>
|
||||
<MenuItem value={"male"}>مرد</MenuItem>
|
||||
<MenuItem value={"female"}>زن</MenuItem>
|
||||
</Select>
|
||||
<FormHelperText error={!!error} sx={{ mt: 1 }}>
|
||||
{!!error && error.message}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
);
|
||||
}}
|
||||
name={"gender"}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, md: 6 }}>
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<LtrTextField
|
||||
{...field}
|
||||
label="شماره ملی"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
type="tel"
|
||||
onChange={(e) => {
|
||||
const inputValue = event.target.value;
|
||||
if (isNaN(Number(inputValue))) {
|
||||
return;
|
||||
}
|
||||
if (inputValue.length > 10) {
|
||||
return;
|
||||
}
|
||||
field.onChange(inputValue);
|
||||
}}
|
||||
error={!!error}
|
||||
helperText={!!error && error.message}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
name={"national_id"}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, md: 6 }}>
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<FormControl fullWidth error={!!error}>
|
||||
<InputLabel id={`label-province`} shrink>
|
||||
استان
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId={`label-province`}
|
||||
id={"province"}
|
||||
name={`province_id`}
|
||||
value={field.value}
|
||||
input={<OutlinedInput label={"استان"} />}
|
||||
onChange={(e) => field.onChange(e.target.value)}
|
||||
displayEmpty
|
||||
>
|
||||
{provinceSelectOptions.map((option) => (
|
||||
<MenuItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
<FormHelperText error={!!error} sx={{ mt: 1 }}>
|
||||
{!!error && error.message}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
);
|
||||
}}
|
||||
name={"province_id"}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, md: 6 }}>
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<FormControl fullWidth error={!!error}>
|
||||
<InputLabel id={`label-role`} shrink>
|
||||
نقش
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId={`label-role`}
|
||||
id={"role"}
|
||||
name={`role_id`}
|
||||
value={field.value}
|
||||
input={<OutlinedInput label={"نقش"} />}
|
||||
onChange={(e) => field.onChange(e.target.value)}
|
||||
displayEmpty
|
||||
>
|
||||
{roleSelectOptions.map((option) => (
|
||||
<MenuItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
<FormHelperText error={!!error} sx={{ mt: 1 }}>
|
||||
{!!error && error.message}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
);
|
||||
}}
|
||||
name={"role_id"}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, md: 6 }}>
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<LtrTextField
|
||||
{...field}
|
||||
label="شماره تماس داخلی"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
type="tel"
|
||||
error={!!error}
|
||||
helperText={!!error && error.message}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
name={"telephone_id"}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Stack>
|
||||
</StyledForm>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
disabled={isSubmitting}
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
size="large"
|
||||
onClick={() => setOpen(false)}
|
||||
>
|
||||
بستن
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={isSubmitting}
|
||||
form="createUserForm"
|
||||
size="large"
|
||||
autoFocus
|
||||
variant="contained"
|
||||
>
|
||||
{isSubmitting ? "در حال ثبت کاربر جدید..." : "ثبت کاربر جدید"}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default CreateUserForm;
|
||||
|
||||
@@ -1,49 +1,39 @@
|
||||
"use client";
|
||||
import { AddCircle } from "@mui/icons-material";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
IconButton,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import { Button, Dialog, IconButton, useMediaQuery, useTheme } from "@mui/material";
|
||||
import CreateUserForm from "./Form";
|
||||
import { useState } from "react";
|
||||
|
||||
const CreateUser = ({ mutate }) => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
||||
const [open, setOpen] = useState(false);
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const handleOpen = () => {
|
||||
setOpen(true);
|
||||
};
|
||||
const handleOpen = () => {
|
||||
setOpen(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{isMobile ? (
|
||||
<IconButton
|
||||
aria-label="کاربر جدید"
|
||||
color="primary"
|
||||
onClick={handleOpen}
|
||||
>
|
||||
<AddCircle sx={{ fontSize: "25px" }} />
|
||||
</IconButton>
|
||||
) : (
|
||||
<Button
|
||||
size={"small"}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
startIcon={<AddCircle />}
|
||||
onClick={handleOpen}
|
||||
>
|
||||
کاربر جدید
|
||||
</Button>
|
||||
)}
|
||||
<Dialog maxWidth="sm" fullWidth={true} open={open}>
|
||||
<CreateUserForm open={open} setOpen={setOpen} mutate={mutate} />
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
{isMobile ? (
|
||||
<IconButton aria-label="کاربر جدید" color="primary" onClick={handleOpen}>
|
||||
<AddCircle sx={{ fontSize: "25px" }} />
|
||||
</IconButton>
|
||||
) : (
|
||||
<Button
|
||||
size={"small"}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
startIcon={<AddCircle />}
|
||||
onClick={handleOpen}
|
||||
>
|
||||
کاربر جدید
|
||||
</Button>
|
||||
)}
|
||||
<Dialog maxWidth="sm" fullWidth={true} open={open}>
|
||||
<CreateUserForm open={open} setOpen={setOpen} mutate={mutate} />
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default CreateUser;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import CreateUser from "./Create";
|
||||
|
||||
const Toolbar = ({ mutate }) => {
|
||||
return (
|
||||
<>
|
||||
<CreateUser mutate={mutate} />
|
||||
</>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<CreateUser mutate={mutate} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default Toolbar;
|
||||
|
||||
@@ -3,12 +3,12 @@ import { Stack } from "@mui/material";
|
||||
import DataTable from "./DataTable";
|
||||
|
||||
const UsersPage = () => {
|
||||
return (
|
||||
<Stack spacing={1}>
|
||||
<PageTitle title={"کاربران"} />
|
||||
<DataTable />
|
||||
</Stack>
|
||||
);
|
||||
return (
|
||||
<Stack spacing={1}>
|
||||
<PageTitle title={"کاربران"} />
|
||||
<DataTable />
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default UsersPage;
|
||||
|
||||
@@ -3,10 +3,6 @@ import { Stack } from "@mui/material";
|
||||
|
||||
const OnlineUsersReport = () => {
|
||||
const { clientsOnline } = useSocket();
|
||||
return (
|
||||
<Stack>
|
||||
clientsOnline: {clientsOnline.length}
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
export default OnlineUsersReport;
|
||||
return <Stack>clientsOnline: {clientsOnline.length}</Stack>;
|
||||
};
|
||||
export default OnlineUsersReport;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"use client"
|
||||
"use client";
|
||||
import { Stack } from "@mui/material";
|
||||
import OnlineUsersReport from "./OnlineUsersReport";
|
||||
|
||||
@@ -7,6 +7,6 @@ const DashboardPage = () => {
|
||||
<Stack>
|
||||
<OnlineUsersReport />
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
export default DashboardPage;
|
||||
);
|
||||
};
|
||||
export default DashboardPage;
|
||||
|
||||
@@ -1,60 +1,45 @@
|
||||
import { ExpandLess, ExpandMore, Link } from "@mui/icons-material";
|
||||
import {
|
||||
Button,
|
||||
Divider,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
Menu,
|
||||
MenuItem,
|
||||
Stack,
|
||||
} from "@mui/material";
|
||||
import { Button, Divider, ListItemIcon, ListItemText, Menu, MenuItem, Stack } from "@mui/material";
|
||||
import NextLink from "next/link";
|
||||
import { useState } from "react";
|
||||
|
||||
const HeaderMenu = ({ menu }) => {
|
||||
const [anchorEl, setAnchorEl] = useState(null);
|
||||
const open = Boolean(anchorEl);
|
||||
const [anchorEl, setAnchorEl] = useState(null);
|
||||
const open = Boolean(anchorEl);
|
||||
|
||||
const handleClick = (event) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
const handleClose = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
const handleClick = (event) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
const handleClose = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack sx={{ mr: 0.5 }}>
|
||||
<Button
|
||||
color="inherit"
|
||||
aria-haspopup="true"
|
||||
aria-expanded={open ? "true" : undefined}
|
||||
onClick={handleClick}
|
||||
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}
|
||||
return (
|
||||
<Stack sx={{ mr: 0.5 }}>
|
||||
<Button
|
||||
color="inherit"
|
||||
aria-haspopup="true"
|
||||
aria-expanded={open ? "true" : undefined}
|
||||
onClick={handleClick}
|
||||
endIcon={open ? <ExpandLess /> : <ExpandMore />}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<Link fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<ListItemText>{sub.title}</ListItemText>
|
||||
</MenuItem>
|
||||
)),
|
||||
index < menu.subMenu.length - 1 && (
|
||||
<Divider key={`divider-${index}`} />
|
||||
),
|
||||
])}
|
||||
</Menu>
|
||||
</Stack>
|
||||
);
|
||||
{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>
|
||||
<Link fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<ListItemText>{sub.title}</ListItemText>
|
||||
</MenuItem>
|
||||
)),
|
||||
index < menu.subMenu.length - 1 && <Divider key={`divider-${index}`} />,
|
||||
])}
|
||||
</Menu>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default HeaderMenu;
|
||||
|
||||
@@ -4,16 +4,16 @@ import { Chip } from "@mui/material";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
const SidebarBadge = ({ badge, chipProps = {} }) => {
|
||||
const { data } = useSidebarBadge();
|
||||
const [value, setValue] = useState();
|
||||
const { data } = useSidebarBadge();
|
||||
const [value, setValue] = useState();
|
||||
|
||||
useEffect(() => {
|
||||
setValue(getValueByPath(data, badge));
|
||||
}, [data, badge]);
|
||||
useEffect(() => {
|
||||
setValue(getValueByPath(data, badge));
|
||||
}, [data, badge]);
|
||||
|
||||
if (!data) return null;
|
||||
if (!value) return null;
|
||||
if (!data) 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;
|
||||
|
||||
@@ -1,92 +1,67 @@
|
||||
import { ExpandLess, ExpandMore } from "@mui/icons-material";
|
||||
import {
|
||||
Collapse,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemButton,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
Stack,
|
||||
} from "@mui/material";
|
||||
import { Collapse, List, ListItem, ListItemButton, ListItemIcon, ListItemText, Stack } from "@mui/material";
|
||||
import Link from "next/link";
|
||||
import SidebarBadge from "./SidebarBadge";
|
||||
import SidebarSubitems from "./SidebarSubitems";
|
||||
|
||||
const SidebarListItems = ({ menuItem, dispatch }) => {
|
||||
return (
|
||||
<>
|
||||
<ListItem disablePadding divider>
|
||||
<ListItemButton
|
||||
sx={{ p: 0.5 }}
|
||||
disableGutters
|
||||
selected={menuItem.selected}
|
||||
component={menuItem.type === "page" ? Link : null}
|
||||
href={menuItem.type === "page" ? menuItem.route : null}
|
||||
onClick={() => {
|
||||
if (menuItem.type !== "page") {
|
||||
dispatch({ type: "COLLAPSE_MENU", id: menuItem.id });
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ListItemIcon
|
||||
sx={{
|
||||
minWidth: 0,
|
||||
justifyContent: "center",
|
||||
color: "primary.main",
|
||||
width: 40,
|
||||
height: 24,
|
||||
}}
|
||||
>
|
||||
{menuItem.icon}
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={menuItem.label} />
|
||||
{menuItem.badges && (
|
||||
<Stack direction={"row"} sx={{ px: 0.5 }} spacing={0.5}>
|
||||
{menuItem.badges.map((badge, i) => (
|
||||
<SidebarBadge
|
||||
chipProps={{ color: badge.color || "primary" }}
|
||||
key={badge.key}
|
||||
badge={badge.key}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
)}
|
||||
{menuItem.hasSubitems ? (
|
||||
menuItem.showSubitems ? (
|
||||
<ExpandLess />
|
||||
) : (
|
||||
<ExpandMore />
|
||||
)
|
||||
) : null}
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
<Collapse
|
||||
in={menuItem.showSubitems}
|
||||
timeout="auto"
|
||||
mountOnEnter={true}
|
||||
unmountOnExit={true}
|
||||
>
|
||||
{menuItem.hasSubitems ? (
|
||||
<List
|
||||
disablePadding
|
||||
dense
|
||||
sx={{
|
||||
background: "#00000008",
|
||||
}}
|
||||
>
|
||||
{menuItem.Subitems.map((subitem, index) => {
|
||||
return (
|
||||
<SidebarSubitems
|
||||
key={index}
|
||||
dispatch={dispatch}
|
||||
subitem={subitem}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
) : null}
|
||||
</Collapse>
|
||||
</>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<ListItem disablePadding divider>
|
||||
<ListItemButton
|
||||
sx={{ p: 0.5 }}
|
||||
disableGutters
|
||||
selected={menuItem.selected}
|
||||
component={menuItem.type === "page" ? Link : null}
|
||||
href={menuItem.type === "page" ? menuItem.route : null}
|
||||
onClick={() => {
|
||||
if (menuItem.type !== "page") {
|
||||
dispatch({ type: "COLLAPSE_MENU", id: menuItem.id });
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ListItemIcon
|
||||
sx={{
|
||||
minWidth: 0,
|
||||
justifyContent: "center",
|
||||
color: "primary.main",
|
||||
width: 40,
|
||||
height: 24,
|
||||
}}
|
||||
>
|
||||
{menuItem.icon}
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={menuItem.label} />
|
||||
{menuItem.badges && (
|
||||
<Stack direction={"row"} sx={{ px: 0.5 }} spacing={0.5}>
|
||||
{menuItem.badges.map((badge, i) => (
|
||||
<SidebarBadge
|
||||
chipProps={{ color: badge.color || "primary" }}
|
||||
key={badge.key}
|
||||
badge={badge.key}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
)}
|
||||
{menuItem.hasSubitems ? menuItem.showSubitems ? <ExpandLess /> : <ExpandMore /> : null}
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
<Collapse in={menuItem.showSubitems} timeout="auto" mountOnEnter={true} unmountOnExit={true}>
|
||||
{menuItem.hasSubitems ? (
|
||||
<List
|
||||
disablePadding
|
||||
dense
|
||||
sx={{
|
||||
background: "#00000008",
|
||||
}}
|
||||
>
|
||||
{menuItem.Subitems.map((subitem, index) => {
|
||||
return <SidebarSubitems key={index} dispatch={dispatch} subitem={subitem} />;
|
||||
})}
|
||||
</List>
|
||||
) : null}
|
||||
</Collapse>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default SidebarListItems;
|
||||
|
||||
@@ -9,120 +9,106 @@ import SidebarListItems from "./SidebarListItems";
|
||||
import Profile from "@/core/components/Profile";
|
||||
|
||||
function selectPage(item, route) {
|
||||
if (item.type === "page") {
|
||||
return {
|
||||
...item,
|
||||
selected: item.route === route,
|
||||
showSubitems: item.route === route,
|
||||
};
|
||||
} else if (item.Subitems && Array.isArray(item.Subitems)) {
|
||||
const updatedSubitems = item.Subitems.map((subitem) =>
|
||||
selectPage(subitem, route),
|
||||
);
|
||||
return {
|
||||
...item,
|
||||
Subitems: updatedSubitems,
|
||||
showSubitems: updatedSubitems.some(
|
||||
(subitem) => subitem.showSubitems || subitem.route === route,
|
||||
),
|
||||
};
|
||||
}
|
||||
return item;
|
||||
if (item.type === "page") {
|
||||
return {
|
||||
...item,
|
||||
selected: item.route === route,
|
||||
showSubitems: item.route === route,
|
||||
};
|
||||
} else if (item.Subitems && Array.isArray(item.Subitems)) {
|
||||
const updatedSubitems = item.Subitems.map((subitem) => selectPage(subitem, route));
|
||||
return {
|
||||
...item,
|
||||
Subitems: updatedSubitems,
|
||||
showSubitems: updatedSubitems.some((subitem) => subitem.showSubitems || subitem.route === route),
|
||||
};
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
function toggleSubitems(items, actionId) {
|
||||
return items.map((item) =>
|
||||
actionId === item.id
|
||||
? { ...item, showSubitems: !item.showSubitems }
|
||||
: { ...item, showSubitems: false },
|
||||
);
|
||||
return items.map((item) =>
|
||||
actionId === item.id ? { ...item, showSubitems: !item.showSubitems } : { ...item, showSubitems: false }
|
||||
);
|
||||
}
|
||||
|
||||
function reducer(state, action) {
|
||||
switch (action.type) {
|
||||
case "UPDATE_MENU":
|
||||
const _permissions = action.permissions || [];
|
||||
return filterMenuItems(state, ["all", ..._permissions]);
|
||||
switch (action.type) {
|
||||
case "UPDATE_MENU":
|
||||
const _permissions = action.permissions || [];
|
||||
return filterMenuItems(state, ["all", ..._permissions]);
|
||||
|
||||
case "COLLAPSE_MENU":
|
||||
return state.map((item) => ({
|
||||
...item,
|
||||
showSubitems: item.id === action.id ? !item.showSubitems : false,
|
||||
}));
|
||||
case "COLLAPSE_MENU":
|
||||
return state.map((item) => ({
|
||||
...item,
|
||||
showSubitems: item.id === action.id ? !item.showSubitems : false,
|
||||
}));
|
||||
|
||||
case "COLLAPSE_SUB_ITEMS":
|
||||
return state.map((item) =>
|
||||
item.hasSubitems
|
||||
? { ...item, Subitems: toggleSubitems(item.Subitems, action.id) }
|
||||
: item,
|
||||
);
|
||||
case "COLLAPSE_SUB_ITEMS":
|
||||
return state.map((item) =>
|
||||
item.hasSubitems ? { ...item, Subitems: toggleSubitems(item.Subitems, action.id) } : item
|
||||
);
|
||||
|
||||
case "COLLAPSE_SUB_SECOND_ITEMS":
|
||||
return state.map((item) =>
|
||||
item.hasSubitems
|
||||
? {
|
||||
...item,
|
||||
Subitems: item.Subitems.map((subitem) =>
|
||||
subitem.hasSubitems
|
||||
? {
|
||||
...subitem,
|
||||
Subitems: toggleSubitems(subitem.Subitems, action.id),
|
||||
}
|
||||
: subitem,
|
||||
),
|
||||
}
|
||||
: item,
|
||||
);
|
||||
case "COLLAPSE_SUB_SECOND_ITEMS":
|
||||
return state.map((item) =>
|
||||
item.hasSubitems
|
||||
? {
|
||||
...item,
|
||||
Subitems: item.Subitems.map((subitem) =>
|
||||
subitem.hasSubitems
|
||||
? {
|
||||
...subitem,
|
||||
Subitems: toggleSubitems(subitem.Subitems, action.id),
|
||||
}
|
||||
: subitem
|
||||
),
|
||||
}
|
||||
: item
|
||||
);
|
||||
|
||||
case "SELECTED":
|
||||
return state.map((item) => selectPage(item, action.route));
|
||||
case "SELECTED":
|
||||
return state.map((item) => selectPage(item, action.route));
|
||||
|
||||
default:
|
||||
throw new Error();
|
||||
}
|
||||
default:
|
||||
throw new Error();
|
||||
}
|
||||
}
|
||||
|
||||
const SidebarMenu = () => {
|
||||
const { data: userPermissions } = usePermissions();
|
||||
const [menuItems, dispatch] = useReducer(reducer, pageMenu);
|
||||
const pathname = usePathname();
|
||||
const [selectedKey, setSelectedKey] = useState(null);
|
||||
const { data: userPermissions } = usePermissions();
|
||||
const [menuItems, dispatch] = useReducer(reducer, pageMenu);
|
||||
const pathname = usePathname();
|
||||
const [selectedKey, setSelectedKey] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch({ type: "SELECTED", route: pathname });
|
||||
setSelectedKey(pathname);
|
||||
}, [userPermissions, pathname]);
|
||||
useEffect(() => {
|
||||
dispatch({ type: "SELECTED", route: pathname });
|
||||
setSelectedKey(pathname);
|
||||
}, [userPermissions, pathname]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!userPermissions) return;
|
||||
dispatch({ type: "UPDATE_MENU", permissions: userPermissions });
|
||||
}, [userPermissions]);
|
||||
useEffect(() => {
|
||||
if (!userPermissions) return;
|
||||
dispatch({ type: "UPDATE_MENU", permissions: userPermissions });
|
||||
}, [userPermissions]);
|
||||
|
||||
useEffect(() => {
|
||||
selectedKey &&
|
||||
document.querySelector(`[data-route="${selectedKey}"]`)?.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "center",
|
||||
});
|
||||
}, [selectedKey]);
|
||||
useEffect(() => {
|
||||
selectedKey &&
|
||||
document.querySelector(`[data-route="${selectedKey}"]`)?.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "center",
|
||||
});
|
||||
}, [selectedKey]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Profile />
|
||||
{userPermissions && (
|
||||
<List sx={{ overflow: "auto", flex: 1 }} disablePadding dense>
|
||||
{menuItems.map((menuItem) => {
|
||||
return (
|
||||
<SidebarListItems
|
||||
dispatch={dispatch}
|
||||
key={menuItem.id}
|
||||
menuItem={menuItem}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<Profile />
|
||||
{userPermissions && (
|
||||
<List sx={{ overflow: "auto", flex: 1 }} disablePadding dense>
|
||||
{menuItems.map((menuItem) => {
|
||||
return <SidebarListItems dispatch={dispatch} key={menuItem.id} menuItem={menuItem} />;
|
||||
})}
|
||||
</List>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default SidebarMenu;
|
||||
|
||||
@@ -1,96 +1,76 @@
|
||||
import { ExpandLess, ExpandMore } from "@mui/icons-material";
|
||||
import {
|
||||
Box,
|
||||
Collapse,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemButton,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
Stack,
|
||||
} from "@mui/material";
|
||||
import { Box, Collapse, List, ListItem, ListItemButton, ListItemIcon, ListItemText, Stack } from "@mui/material";
|
||||
import Link from "next/link";
|
||||
import SidebarBadge from "./SidebarBadge";
|
||||
|
||||
const SidebarSubitems = ({ subitem, dispatch }) => {
|
||||
return (
|
||||
<>
|
||||
<ListItem disablePadding divider>
|
||||
<ListItemButton
|
||||
disableGutters
|
||||
sx={{ p: 0.5, pl: 1 }}
|
||||
selected={subitem.selected}
|
||||
component={subitem.type === "page" ? Link : null}
|
||||
href={subitem.type === "page" ? subitem.route : null}
|
||||
onClick={() => {
|
||||
if (subitem.type !== "page") {
|
||||
dispatch({ type: "COLLAPSE_SUB_ITEMS", id: subitem.id });
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ListItemIcon
|
||||
sx={{
|
||||
minWidth: 0,
|
||||
justifyContent: "center",
|
||||
width: 40,
|
||||
height: 24,
|
||||
}}
|
||||
>
|
||||
{subitem.icon}
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={subitem.label} />
|
||||
{subitem.badges && (
|
||||
<Stack direction={"row"} sx={{ px: 0.5 }} spacing={0.5}>
|
||||
{subitem.badges.map((badge, i) => (
|
||||
<SidebarBadge
|
||||
chipProps={{
|
||||
color: badge.color || "default",
|
||||
variant: "outlined",
|
||||
}}
|
||||
key={badge.key}
|
||||
badge={badge.key}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
)}
|
||||
{subitem.hasSubitems ? (
|
||||
subitem.showSubitems ? (
|
||||
<ExpandLess />
|
||||
) : (
|
||||
<ExpandMore />
|
||||
)
|
||||
) : (
|
||||
<Box sx={{ width: 29 }} />
|
||||
)}
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
<Collapse
|
||||
in={subitem.showSubitems}
|
||||
timeout="auto"
|
||||
mountOnEnter={true}
|
||||
unmountOnExit={true}
|
||||
>
|
||||
{subitem.hasSubitems ? (
|
||||
<List
|
||||
disablePadding
|
||||
dense
|
||||
sx={{
|
||||
background: "#00000008",
|
||||
}}
|
||||
>
|
||||
{subitem.Subitems.map((subSubitem, index) => {
|
||||
return (
|
||||
<SidebarSubitems
|
||||
key={index}
|
||||
dispatch={dispatch}
|
||||
subitem={subSubitem}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
) : null}
|
||||
</Collapse>
|
||||
</>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<ListItem disablePadding divider>
|
||||
<ListItemButton
|
||||
disableGutters
|
||||
sx={{ p: 0.5, pl: 1 }}
|
||||
selected={subitem.selected}
|
||||
component={subitem.type === "page" ? Link : null}
|
||||
href={subitem.type === "page" ? subitem.route : null}
|
||||
onClick={() => {
|
||||
if (subitem.type !== "page") {
|
||||
dispatch({ type: "COLLAPSE_SUB_ITEMS", id: subitem.id });
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ListItemIcon
|
||||
sx={{
|
||||
minWidth: 0,
|
||||
justifyContent: "center",
|
||||
width: 40,
|
||||
height: 24,
|
||||
}}
|
||||
>
|
||||
{subitem.icon}
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={subitem.label} />
|
||||
{subitem.badges && (
|
||||
<Stack direction={"row"} sx={{ px: 0.5 }} spacing={0.5}>
|
||||
{subitem.badges.map((badge, i) => (
|
||||
<SidebarBadge
|
||||
chipProps={{
|
||||
color: badge.color || "default",
|
||||
variant: "outlined",
|
||||
}}
|
||||
key={badge.key}
|
||||
badge={badge.key}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
)}
|
||||
{subitem.hasSubitems ? (
|
||||
subitem.showSubitems ? (
|
||||
<ExpandLess />
|
||||
) : (
|
||||
<ExpandMore />
|
||||
)
|
||||
) : (
|
||||
<Box sx={{ width: 29 }} />
|
||||
)}
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
<Collapse in={subitem.showSubitems} timeout="auto" mountOnEnter={true} unmountOnExit={true}>
|
||||
{subitem.hasSubitems ? (
|
||||
<List
|
||||
disablePadding
|
||||
dense
|
||||
sx={{
|
||||
background: "#00000008",
|
||||
}}
|
||||
>
|
||||
{subitem.Subitems.map((subSubitem, index) => {
|
||||
return <SidebarSubitems key={index} dispatch={dispatch} subitem={subSubitem} />;
|
||||
})}
|
||||
</List>
|
||||
) : null}
|
||||
</Collapse>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default SidebarSubitems;
|
||||
|
||||
@@ -1,17 +1,7 @@
|
||||
"use client";
|
||||
import ChevronRightIcon from "@mui/icons-material/ChevronRight";
|
||||
import MenuIcon from "@mui/icons-material/Menu";
|
||||
import {
|
||||
Box,
|
||||
Drawer,
|
||||
IconButton,
|
||||
Stack,
|
||||
styled,
|
||||
Toolbar,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import { Box, Drawer, IconButton, Stack, styled, Toolbar, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import MuiAppBar from "@mui/material/AppBar";
|
||||
import { useState, useEffect } from "react";
|
||||
import HeaderMenu from "./HeaderMenu";
|
||||
@@ -21,170 +11,157 @@ import SidebarMenu from "./Sidebar/SidebarMenu";
|
||||
|
||||
const drawerWidth = 300;
|
||||
|
||||
const Main = styled(Stack, { shouldForwardProp: (prop) => prop !== "open" })(
|
||||
({ theme, open }) => ({
|
||||
const Main = styled(Stack, { shouldForwardProp: (prop) => prop !== "open" })(({ theme, open }) => ({
|
||||
flexGrow: 1,
|
||||
height: "100%",
|
||||
transition: theme.transitions.create("margin", {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.leavingScreen,
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.leavingScreen,
|
||||
}),
|
||||
marginLeft: `-${drawerWidth}px`,
|
||||
...(open && {
|
||||
transition: theme.transitions.create("margin", {
|
||||
easing: theme.transitions.easing.easeOut,
|
||||
duration: theme.transitions.duration.enteringScreen,
|
||||
}),
|
||||
marginLeft: 0,
|
||||
transition: theme.transitions.create("margin", {
|
||||
easing: theme.transitions.easing.easeOut,
|
||||
duration: theme.transitions.duration.enteringScreen,
|
||||
}),
|
||||
marginLeft: 0,
|
||||
}),
|
||||
}),
|
||||
);
|
||||
}));
|
||||
|
||||
const AppBar = styled(MuiAppBar, {
|
||||
shouldForwardProp: (prop) => prop !== "open",
|
||||
shouldForwardProp: (prop) => prop !== "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"], {
|
||||
easing: theme.transitions.easing.easeOut,
|
||||
duration: theme.transitions.duration.enteringScreen,
|
||||
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"], {
|
||||
easing: theme.transitions.easing.easeOut,
|
||||
duration: theme.transitions.duration.enteringScreen,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
}));
|
||||
|
||||
const DrawerHeader = styled("div")(({ theme }) => ({
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
padding: theme.spacing(0, 1),
|
||||
...theme.mixins.toolbar,
|
||||
minHeight: "40px !important",
|
||||
justifyContent: "flex-end",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
padding: theme.spacing(0, 1),
|
||||
...theme.mixins.toolbar,
|
||||
minHeight: "40px !important",
|
||||
justifyContent: "flex-end",
|
||||
}));
|
||||
|
||||
const HeaderWithSidebar = ({ children }) => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down("md")); // Detect mobile/tablet screen
|
||||
const [open, setOpen] = useState(!isMobile); // Initial state based on screen size
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down("md")); // Detect mobile/tablet screen
|
||||
const [open, setOpen] = useState(!isMobile); // Initial state based on screen size
|
||||
|
||||
useEffect(() => {
|
||||
setOpen(!isMobile); // Toggle state when screen size changes
|
||||
}, [isMobile]);
|
||||
useEffect(() => {
|
||||
setOpen(!isMobile); // Toggle state when screen size changes
|
||||
}, [isMobile]);
|
||||
|
||||
const now = moment().locale("fa").format("YYYY/MM/DD");
|
||||
const now = moment().locale("fa").format("YYYY/MM/DD");
|
||||
|
||||
const handleDrawerOpen = () => {
|
||||
setOpen(true);
|
||||
};
|
||||
const handleDrawerClose = () => {
|
||||
setOpen(false);
|
||||
};
|
||||
const handleDrawerOpen = () => {
|
||||
setOpen(true);
|
||||
};
|
||||
const handleDrawerClose = () => {
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
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 />
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
overflowY: "auto",
|
||||
p: 1,
|
||||
overflowX: "hidden",
|
||||
}}
|
||||
sx={{
|
||||
display: "flex",
|
||||
height: `100%`,
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
{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>
|
||||
</Main>
|
||||
</Box>
|
||||
);
|
||||
);
|
||||
};
|
||||
|
||||
export default HeaderWithSidebar;
|
||||
|
||||
@@ -1,29 +1,25 @@
|
||||
import { Stack, Typography } from "@mui/material";
|
||||
|
||||
const ActionHeader = ({ tab }) => {
|
||||
return (
|
||||
<Stack
|
||||
sx={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
py: 2,
|
||||
backgroundColor: "primary.main",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
<Typography variant="subtitle1" sx={{ marginRight: 1, color: "#fff" }}>
|
||||
.... عملیات های مربوط به تماس:
|
||||
</Typography>
|
||||
<Typography
|
||||
data-testid="phone_number"
|
||||
variant="subtitle1"
|
||||
sx={{ color: "#fff" }}
|
||||
>
|
||||
{tab.phone_number} ....
|
||||
</Typography>
|
||||
</Stack>
|
||||
);
|
||||
return (
|
||||
<Stack
|
||||
sx={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
py: 2,
|
||||
backgroundColor: "primary.main",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
<Typography variant="subtitle1" sx={{ marginRight: 1, color: "#fff" }}>
|
||||
.... عملیات های مربوط به تماس:
|
||||
</Typography>
|
||||
<Typography data-testid="phone_number" variant="subtitle1" sx={{ color: "#fff" }}>
|
||||
{tab.phone_number} ....
|
||||
</Typography>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default ActionHeader;
|
||||
|
||||
@@ -2,26 +2,24 @@ import { Button, Grid } from "@mui/material";
|
||||
import { Controller } from "react-hook-form";
|
||||
|
||||
const CallActionCategoriesButton = ({ category, control }) => {
|
||||
return (
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<Button
|
||||
sx={{ py: 1.5 }}
|
||||
fullWidth
|
||||
variant={
|
||||
field.value === category.category_id ? "contained" : "outlined"
|
||||
}
|
||||
color={"primary"}
|
||||
onClick={() => {
|
||||
field.onChange(category.category_id);
|
||||
}}
|
||||
>
|
||||
{category.category_name}
|
||||
</Button>
|
||||
)}
|
||||
name={"category_id"}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<Button
|
||||
sx={{ py: 1.5 }}
|
||||
fullWidth
|
||||
variant={field.value === category.category_id ? "contained" : "outlined"}
|
||||
color={"primary"}
|
||||
onClick={() => {
|
||||
field.onChange(category.category_id);
|
||||
}}
|
||||
>
|
||||
{category.category_name}
|
||||
</Button>
|
||||
)}
|
||||
name={"category_id"}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export default CallActionCategoriesButton;
|
||||
|
||||
@@ -3,42 +3,42 @@ import { useEffect, useRef } from "react";
|
||||
import { Controller } from "react-hook-form";
|
||||
|
||||
const CallActionDescription = ({ control }) => {
|
||||
const inputRef = useRef(null);
|
||||
const inputRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
if (inputRef.current) {
|
||||
inputRef.current.focus();
|
||||
}
|
||||
}, 100);
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
if (inputRef.current) {
|
||||
inputRef.current.focus();
|
||||
}
|
||||
}, 100);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Divider sx={{ margin: 2 }}>
|
||||
<Chip label={"توضیحات تماس (اختیاری)"} variant="outlined" />
|
||||
</Divider>
|
||||
<Box sx={{ m: 2 }}>
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<TextField
|
||||
{...field}
|
||||
inputRef={inputRef}
|
||||
variant="outlined"
|
||||
placeholder={"لطفا توضیحات خود را وارد نمایید"}
|
||||
type={"text"}
|
||||
fullWidth
|
||||
multiline
|
||||
rows={8}
|
||||
/>
|
||||
)}
|
||||
name={"description"}
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<Divider sx={{ margin: 2 }}>
|
||||
<Chip label={"توضیحات تماس (اختیاری)"} variant="outlined" />
|
||||
</Divider>
|
||||
<Box sx={{ m: 2 }}>
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<TextField
|
||||
{...field}
|
||||
inputRef={inputRef}
|
||||
variant="outlined"
|
||||
placeholder={"لطفا توضیحات خود را وارد نمایید"}
|
||||
type={"text"}
|
||||
fullWidth
|
||||
multiline
|
||||
rows={8}
|
||||
/>
|
||||
)}
|
||||
name={"description"}
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default CallActionDescription;
|
||||
|
||||
@@ -3,36 +3,36 @@ import { useCallback } from "react";
|
||||
import { Controller, useWatch } from "react-hook-form";
|
||||
|
||||
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(
|
||||
(onChange) => {
|
||||
onChange(sub_category.subcategory_id);
|
||||
setTimeout(() => {
|
||||
onSubmit();
|
||||
}, 0);
|
||||
},
|
||||
[sub_category.subcategory_id],
|
||||
);
|
||||
const handleClick = useCallback(
|
||||
(onChange) => {
|
||||
onChange(sub_category.subcategory_id);
|
||||
setTimeout(() => {
|
||||
onSubmit();
|
||||
}, 0);
|
||||
},
|
||||
[sub_category.subcategory_id]
|
||||
);
|
||||
|
||||
return (
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return sub_category.category_id === category_id ? (
|
||||
<Button
|
||||
sx={{ py: 1.5 }}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
color={"primary"}
|
||||
onClick={() => handleClick(field.onChange)}
|
||||
>
|
||||
{sub_category.subcategory_name}
|
||||
</Button>
|
||||
) : null;
|
||||
}}
|
||||
name={"subcategory_id"}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<Controller
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return sub_category.category_id === category_id ? (
|
||||
<Button
|
||||
sx={{ py: 1.5 }}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
color={"primary"}
|
||||
onClick={() => handleClick(field.onChange)}
|
||||
>
|
||||
{sub_category.subcategory_name}
|
||||
</Button>
|
||||
) : null;
|
||||
}}
|
||||
name={"subcategory_id"}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export default CallActionSubcategoriesButton;
|
||||
|
||||
@@ -3,22 +3,18 @@ import CallActionCategoriesButton from "./CallActionCategoriesButton";
|
||||
import { Chip, Divider, Grid, Stack } from "@mui/material";
|
||||
|
||||
const CallActionsCategories = ({ control }) => {
|
||||
const { categoryLists } = useCategory();
|
||||
return (
|
||||
<>
|
||||
<Divider sx={{ margin: 2 }}>
|
||||
<Chip label={"موضوع ها"} variant="outlined" />
|
||||
</Divider>
|
||||
<Stack sx={{ m: 1, p: 1 }} direction={"row"} spacing={3}>
|
||||
{categoryLists.map((category) => (
|
||||
<CallActionCategoriesButton
|
||||
key={category.category_id}
|
||||
category={category}
|
||||
control={control}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
</>
|
||||
);
|
||||
const { categoryLists } = useCategory();
|
||||
return (
|
||||
<>
|
||||
<Divider sx={{ margin: 2 }}>
|
||||
<Chip label={"موضوع ها"} variant="outlined" />
|
||||
</Divider>
|
||||
<Stack sx={{ m: 1, p: 1 }} direction={"row"} spacing={3}>
|
||||
{categoryLists.map((category) => (
|
||||
<CallActionCategoriesButton key={category.category_id} category={category} control={control} />
|
||||
))}
|
||||
</Stack>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default CallActionsCategories;
|
||||
|
||||
@@ -3,25 +3,25 @@ import CallActionSubcategoriesButton from "./CallActionSubcategoriesButton";
|
||||
import { Chip, Divider, Grid, Stack } from "@mui/material";
|
||||
|
||||
const CallActionsSubcategories = ({ control, onSubmit }) => {
|
||||
const { subCategoryLists } = useCategory();
|
||||
return (
|
||||
<>
|
||||
<Divider sx={{ margin: 2 }}>
|
||||
<Chip label={"زیر موضوع ها"} variant="outlined" />
|
||||
</Divider>
|
||||
<Stack sx={{ m: 1, p: 1 }} direction={"row"} spacing={3}>
|
||||
{subCategoryLists.map((sub_category, index) => {
|
||||
return (
|
||||
<CallActionSubcategoriesButton
|
||||
key={index}
|
||||
sub_category={sub_category}
|
||||
control={control}
|
||||
onSubmit={onSubmit}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Stack>
|
||||
</>
|
||||
);
|
||||
const { subCategoryLists } = useCategory();
|
||||
return (
|
||||
<>
|
||||
<Divider sx={{ margin: 2 }}>
|
||||
<Chip label={"زیر موضوع ها"} variant="outlined" />
|
||||
</Divider>
|
||||
<Stack sx={{ m: 1, p: 1 }} direction={"row"} spacing={3}>
|
||||
{subCategoryLists.map((sub_category, index) => {
|
||||
return (
|
||||
<CallActionSubcategoriesButton
|
||||
key={index}
|
||||
sub_category={sub_category}
|
||||
control={control}
|
||||
onSubmit={onSubmit}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Stack>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default CallActionsSubcategories;
|
||||
|
||||
@@ -12,76 +12,70 @@ import CallActionsCategories from "./CallActionsCategories";
|
||||
import CallActionsSubcategories from "./CallActionsSubcategories";
|
||||
|
||||
const defaultValues = {
|
||||
description: "",
|
||||
category_id: "",
|
||||
subcategory_id: "",
|
||||
description: "",
|
||||
category_id: "",
|
||||
subcategory_id: "",
|
||||
};
|
||||
|
||||
const validationSchema = object({
|
||||
category_id: string().required("لطفا نام کاربری را وارد کنید!"),
|
||||
subcategory_id: string().required("لطفا رمز عبور را وارد کنید!"),
|
||||
category_id: string().required("لطفا نام کاربری را وارد کنید!"),
|
||||
subcategory_id: string().required("لطفا رمز عبور را وارد کنید!"),
|
||||
});
|
||||
|
||||
function CallActions({ tab }) {
|
||||
const requestServer = useRequest();
|
||||
const { closeCall } = useCall();
|
||||
const requestServer = useRequest();
|
||||
const { closeCall } = useCall();
|
||||
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
formState: { isSubmitting },
|
||||
} = useForm({
|
||||
defaultValues,
|
||||
resolver: yupResolver(validationSchema),
|
||||
mode: "all",
|
||||
});
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
formState: { isSubmitting },
|
||||
} = useForm({
|
||||
defaultValues,
|
||||
resolver: yupResolver(validationSchema),
|
||||
mode: "all",
|
||||
});
|
||||
|
||||
const onSubmit = handleSubmit(async (data) => {
|
||||
try {
|
||||
const formData = new FormData();
|
||||
data.description != "" &&
|
||||
formData.append("description", data.description);
|
||||
formData.append("category_id", data.category_id);
|
||||
formData.append("subcategory_id", data.subcategory_id);
|
||||
await requestServer(`${CALL_ACTION}/${tab.id}`, "post", {
|
||||
data: formData,
|
||||
});
|
||||
closeCall(tab.id);
|
||||
} catch (error) {}
|
||||
});
|
||||
const onSubmit = handleSubmit(async (data) => {
|
||||
try {
|
||||
const formData = new FormData();
|
||||
data.description != "" && formData.append("description", data.description);
|
||||
formData.append("category_id", data.category_id);
|
||||
formData.append("subcategory_id", data.subcategory_id);
|
||||
await requestServer(`${CALL_ACTION}/${tab.id}`, "post", {
|
||||
data: formData,
|
||||
});
|
||||
closeCall(tab.id);
|
||||
} catch (error) {}
|
||||
});
|
||||
|
||||
return (
|
||||
<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
|
||||
return (
|
||||
<Stack
|
||||
sx={{
|
||||
position: "absolute",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
opacity: 0.9,
|
||||
zIndex: 9999,
|
||||
height: "100%",
|
||||
}}
|
||||
>
|
||||
<LoadingHardPage
|
||||
width={80}
|
||||
height={80}
|
||||
loading={true}
|
||||
sx={{ position: "absolute" }}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
>
|
||||
<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={{
|
||||
position: "absolute",
|
||||
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;
|
||||
|
||||
@@ -1,39 +1,39 @@
|
||||
import { Typography } from "@mui/material";
|
||||
|
||||
const ErrorOrEmpty = ({ isErrorOrEmpty }) => {
|
||||
return (
|
||||
<>
|
||||
{isErrorOrEmpty === "error" ? (
|
||||
<Typography
|
||||
variant="subtitle1"
|
||||
sx={{
|
||||
color: "#837e7e",
|
||||
fontWeight: "500",
|
||||
letterSpacing: 1,
|
||||
textAlign: "center",
|
||||
mt: "50%",
|
||||
}}
|
||||
>
|
||||
خطا در دریافت تاریخچه تماس دریافتی
|
||||
</Typography>
|
||||
) : isErrorOrEmpty === "empty" ? (
|
||||
<Typography
|
||||
variant="subtitle1"
|
||||
sx={{
|
||||
color: "#837e7e",
|
||||
fontWeight: "500",
|
||||
letterSpacing: 1,
|
||||
textAlign: "center",
|
||||
mt: "50%",
|
||||
}}
|
||||
>
|
||||
تاریخچه ای برای تماس دریافتی یافت نشد
|
||||
</Typography>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
{isErrorOrEmpty === "error" ? (
|
||||
<Typography
|
||||
variant="subtitle1"
|
||||
sx={{
|
||||
color: "#837e7e",
|
||||
fontWeight: "500",
|
||||
letterSpacing: 1,
|
||||
textAlign: "center",
|
||||
mt: "50%",
|
||||
}}
|
||||
>
|
||||
خطا در دریافت تاریخچه تماس دریافتی
|
||||
</Typography>
|
||||
) : isErrorOrEmpty === "empty" ? (
|
||||
<Typography
|
||||
variant="subtitle1"
|
||||
sx={{
|
||||
color: "#837e7e",
|
||||
fontWeight: "500",
|
||||
letterSpacing: 1,
|
||||
textAlign: "center",
|
||||
mt: "50%",
|
||||
}}
|
||||
>
|
||||
تاریخچه ای برای تماس دریافتی یافت نشد
|
||||
</Typography>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ErrorOrEmpty;
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
import { Stack, Typography } from "@mui/material";
|
||||
|
||||
const HistoryHeader = ({ tab }) => {
|
||||
return (
|
||||
<Stack
|
||||
sx={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
py: 2,
|
||||
backgroundColor: "primary.main",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
<Typography variant="subtitle1" sx={{ marginRight: 1, color: "#fff" }}>
|
||||
.... تاریخچه تماس های:
|
||||
</Typography>
|
||||
<Typography variant="subtitle1" sx={{ color: "#fff" }}>
|
||||
{tab.phone_number} ....
|
||||
</Typography>
|
||||
</Stack>
|
||||
);
|
||||
return (
|
||||
<Stack
|
||||
sx={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
py: 2,
|
||||
backgroundColor: "primary.main",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
<Typography variant="subtitle1" sx={{ marginRight: 1, color: "#fff" }}>
|
||||
.... تاریخچه تماس های:
|
||||
</Typography>
|
||||
<Typography variant="subtitle1" sx={{ color: "#fff" }}>
|
||||
{tab.phone_number} ....
|
||||
</Typography>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default HistoryHeader;
|
||||
|
||||
@@ -3,28 +3,26 @@ import { Avatar, Box, Stack, Typography } from "@mui/material";
|
||||
import moment from "jalali-moment";
|
||||
|
||||
const PreviousOperatorData = ({ historyItem }) => {
|
||||
return (
|
||||
<Stack direction={"row"} spacing={2}>
|
||||
<Box>
|
||||
<Avatar sx={{ backgroundColor: "primary.light" }}>
|
||||
<HeadsetMicIcon />
|
||||
</Avatar>
|
||||
</Box>
|
||||
<Stack>
|
||||
<Stack direction={"row"} sx={{ alignItems: "end" }}>
|
||||
<Typography sx={{ mr: 1 }}>
|
||||
{historyItem.operator_full_name}
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ color: "secondary.main" }}>
|
||||
(کارشناس)
|
||||
</Typography>
|
||||
return (
|
||||
<Stack direction={"row"} spacing={2}>
|
||||
<Box>
|
||||
<Avatar sx={{ backgroundColor: "primary.light" }}>
|
||||
<HeadsetMicIcon />
|
||||
</Avatar>
|
||||
</Box>
|
||||
<Stack>
|
||||
<Stack direction={"row"} sx={{ alignItems: "end" }}>
|
||||
<Typography sx={{ mr: 1 }}>{historyItem.operator_full_name}</Typography>
|
||||
<Typography variant="caption" sx={{ color: "secondary.main" }}>
|
||||
(کارشناس)
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Typography variant="caption" sx={{ color: "secondary.main" }}>
|
||||
{moment(historyItem.created_at).locale("fa").fromNow()}
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Typography variant="caption" sx={{ color: "secondary.main" }}>
|
||||
{moment(historyItem.created_at).locale("fa").fromNow()}
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
);
|
||||
};
|
||||
|
||||
export default PreviousOperatorData;
|
||||
|
||||
@@ -1,60 +1,46 @@
|
||||
import { Chip, Divider, Stack } from "@mui/material";
|
||||
|
||||
const Topics = ({ historyItem }) => {
|
||||
return (
|
||||
<Stack spacing={1}>
|
||||
<Stack
|
||||
sx={{
|
||||
width: "100%",
|
||||
alignItems: "center",
|
||||
}}
|
||||
spacing={1}
|
||||
direction={"row"}
|
||||
>
|
||||
<Chip label={"موضوع"} size="small" variant="outlined" color="primary" />
|
||||
<Divider sx={{ flex: 1 }} />
|
||||
{historyItem.category_name && (
|
||||
<Chip label={historyItem.category_name} />
|
||||
)}
|
||||
</Stack>
|
||||
<Stack
|
||||
sx={{
|
||||
width: "100%",
|
||||
alignItems: "center",
|
||||
}}
|
||||
spacing={1}
|
||||
direction={"row"}
|
||||
>
|
||||
<Chip
|
||||
label={"زیر موضوع"}
|
||||
size="small"
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
/>
|
||||
<Divider sx={{ flex: 1 }} />
|
||||
{historyItem.subcategory_name && (
|
||||
<Chip label={historyItem.subcategory_name} />
|
||||
)}
|
||||
</Stack>
|
||||
<Stack
|
||||
sx={{
|
||||
width: "100%",
|
||||
alignItems: "center",
|
||||
}}
|
||||
spacing={1}
|
||||
direction={"row"}
|
||||
>
|
||||
<Chip
|
||||
label={"توضیحات تماس"}
|
||||
size="small"
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
/>
|
||||
<Divider sx={{ flex: 1 }} />
|
||||
{historyItem.description && <Chip label={historyItem.description} />}
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
return (
|
||||
<Stack spacing={1}>
|
||||
<Stack
|
||||
sx={{
|
||||
width: "100%",
|
||||
alignItems: "center",
|
||||
}}
|
||||
spacing={1}
|
||||
direction={"row"}
|
||||
>
|
||||
<Chip label={"موضوع"} size="small" variant="outlined" color="primary" />
|
||||
<Divider sx={{ flex: 1 }} />
|
||||
{historyItem.category_name && <Chip label={historyItem.category_name} />}
|
||||
</Stack>
|
||||
<Stack
|
||||
sx={{
|
||||
width: "100%",
|
||||
alignItems: "center",
|
||||
}}
|
||||
spacing={1}
|
||||
direction={"row"}
|
||||
>
|
||||
<Chip label={"زیر موضوع"} size="small" variant="outlined" color="primary" />
|
||||
<Divider sx={{ flex: 1 }} />
|
||||
{historyItem.subcategory_name && <Chip label={historyItem.subcategory_name} />}
|
||||
</Stack>
|
||||
<Stack
|
||||
sx={{
|
||||
width: "100%",
|
||||
alignItems: "center",
|
||||
}}
|
||||
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;
|
||||
|
||||
@@ -3,12 +3,12 @@ import PreviousOperatorData from "./PreviousOperatorData";
|
||||
import Topics from "./Topics.";
|
||||
|
||||
const ListItemOfCalls = ({ historyItem }) => {
|
||||
return (
|
||||
<Stack sx={{ p: 2 }} spacing={2}>
|
||||
<PreviousOperatorData historyItem={historyItem} />
|
||||
<Topics historyItem={historyItem} />
|
||||
</Stack>
|
||||
);
|
||||
return (
|
||||
<Stack sx={{ p: 2 }} spacing={2}>
|
||||
<PreviousOperatorData historyItem={historyItem} />
|
||||
<Topics historyItem={historyItem} />
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default ListItemOfCalls;
|
||||
|
||||
@@ -1,115 +1,108 @@
|
||||
import {
|
||||
Box,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemAvatar,
|
||||
ListItemText,
|
||||
Skeleton,
|
||||
} from "@mui/material";
|
||||
import { Box, List, ListItem, ListItemAvatar, ListItemText, Skeleton } from "@mui/material";
|
||||
|
||||
const LoadingHistory = () => {
|
||||
return (
|
||||
<>
|
||||
<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
|
||||
return (
|
||||
<>
|
||||
<List
|
||||
data-testid="loading-list"
|
||||
sx={{
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
width: "100%",
|
||||
bgcolor: "rgba(222,234,215,0.11)",
|
||||
}}
|
||||
>
|
||||
<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>
|
||||
</>
|
||||
);
|
||||
>
|
||||
<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={{
|
||||
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;
|
||||
|
||||
@@ -9,53 +9,52 @@ import { Headphones } from "@mui/icons-material";
|
||||
|
||||
const max_size = 10;
|
||||
const CallHistory = ({ tab }) => {
|
||||
const {
|
||||
firstItemOfHistory,
|
||||
historyList,
|
||||
isLoadingHistoryList,
|
||||
errorHistoryList,
|
||||
} = useCallerHistory(tab.id, tab.phone_number, max_size);
|
||||
const [showRestOfHistory, setShowRestOfHistory] = useState(false);
|
||||
const { firstItemOfHistory, historyList, isLoadingHistoryList, errorHistoryList } = useCallerHistory(
|
||||
tab.id,
|
||||
tab.phone_number,
|
||||
max_size
|
||||
);
|
||||
const [showRestOfHistory, setShowRestOfHistory] = useState(false);
|
||||
|
||||
return (
|
||||
<Stack
|
||||
sx={{
|
||||
height: "100%",
|
||||
}}
|
||||
>
|
||||
<HistoryHeader tab={tab} />
|
||||
<Stack sx={{ height: "100%", overflowY: "auto" }}>
|
||||
{isLoadingHistoryList ? (
|
||||
<LoadingHistory />
|
||||
) : errorHistoryList ? (
|
||||
<ErrorOrEmpty isErrorOrEmpty="error" />
|
||||
) : !firstItemOfHistory ? (
|
||||
<ErrorOrEmpty isErrorOrEmpty="empty" />
|
||||
) : (
|
||||
<Box>
|
||||
<ListItemOfCalls key={"first"} historyItem={firstItemOfHistory} />
|
||||
{historyList && (
|
||||
<Divider>
|
||||
<Chip
|
||||
icon={<Headphones />}
|
||||
onClick={() => setShowRestOfHistory(true)}
|
||||
label={"نمایش موارد بیشتر"}
|
||||
disabled={showRestOfHistory}
|
||||
/>
|
||||
</Divider>
|
||||
)}
|
||||
{showRestOfHistory &&
|
||||
historyList.map((historyItem, index) => (
|
||||
<>
|
||||
<ListItemOfCalls key={index} historyItem={historyItem} />
|
||||
<Divider />
|
||||
</>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
return (
|
||||
<Stack
|
||||
sx={{
|
||||
height: "100%",
|
||||
}}
|
||||
>
|
||||
<HistoryHeader tab={tab} />
|
||||
<Stack sx={{ height: "100%", overflowY: "auto" }}>
|
||||
{isLoadingHistoryList ? (
|
||||
<LoadingHistory />
|
||||
) : errorHistoryList ? (
|
||||
<ErrorOrEmpty isErrorOrEmpty="error" />
|
||||
) : !firstItemOfHistory ? (
|
||||
<ErrorOrEmpty isErrorOrEmpty="empty" />
|
||||
) : (
|
||||
<Box>
|
||||
<ListItemOfCalls key={"first"} historyItem={firstItemOfHistory} />
|
||||
{historyList && (
|
||||
<Divider>
|
||||
<Chip
|
||||
icon={<Headphones />}
|
||||
onClick={() => setShowRestOfHistory(true)}
|
||||
label={"نمایش موارد بیشتر"}
|
||||
disabled={showRestOfHistory}
|
||||
/>
|
||||
</Divider>
|
||||
)}
|
||||
{showRestOfHistory &&
|
||||
historyList.map((historyItem, index) => (
|
||||
<>
|
||||
<ListItemOfCalls key={index} historyItem={historyItem} />
|
||||
<Divider />
|
||||
</>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default CallHistory;
|
||||
|
||||
@@ -3,27 +3,27 @@ import CallActions from "./CallActions";
|
||||
import CallHistory from "./CallHistory";
|
||||
|
||||
const CallTabPanel = ({ tab }) => {
|
||||
return (
|
||||
<Stack
|
||||
role="tabpanel"
|
||||
id={`call-tabpanel-${tab.id}`}
|
||||
aria-labelledby={`call-tab-${tab.id}`}
|
||||
direction={"row"}
|
||||
sx={{
|
||||
height: "100%",
|
||||
display: !tab.active && "none",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
<Box sx={{ flex: 2 }}>
|
||||
<CallActions tab={tab} />
|
||||
</Box>
|
||||
<Divider variant="fullWidth" orientation="vertical" />
|
||||
<Box sx={{ flex: 1 }}>
|
||||
<CallHistory tab={tab} />
|
||||
</Box>
|
||||
</Stack>
|
||||
);
|
||||
return (
|
||||
<Stack
|
||||
role="tabpanel"
|
||||
id={`call-tabpanel-${tab.id}`}
|
||||
aria-labelledby={`call-tab-${tab.id}`}
|
||||
direction={"row"}
|
||||
sx={{
|
||||
height: "100%",
|
||||
display: !tab.active && "none",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
<Box sx={{ flex: 2 }}>
|
||||
<CallActions tab={tab} />
|
||||
</Box>
|
||||
<Divider variant="fullWidth" orientation="vertical" />
|
||||
<Box sx={{ flex: 1 }}>
|
||||
<CallHistory tab={tab} />
|
||||
</Box>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default CallTabPanel;
|
||||
|
||||
@@ -3,19 +3,14 @@ import { Stack, Typography } from "@mui/material";
|
||||
import CallTabTime from "./CallTabTime";
|
||||
|
||||
const CallTabDetails = ({ tab }) => {
|
||||
return (
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
spacing={3}
|
||||
>
|
||||
<Call />
|
||||
<Stack alignItems={"start"}>
|
||||
<Typography>{tab.phone_number}</Typography>
|
||||
<CallTabTime realTimeDate={tab.date} />
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
return (
|
||||
<Stack direction="row" justifyContent="center" alignItems="center" spacing={3}>
|
||||
<Call />
|
||||
<Stack alignItems={"start"}>
|
||||
<Typography>{tab.phone_number}</Typography>
|
||||
<CallTabTime realTimeDate={tab.date} />
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
export default CallTabDetails;
|
||||
|
||||
@@ -4,32 +4,31 @@ import { IconButton, Stack, Zoom } from "@mui/material";
|
||||
import CallTabDetails from "./CallTabDetails";
|
||||
|
||||
const CallTabLabel = ({ tab, index }) => {
|
||||
const { closeCall, activeCall } = useCall();
|
||||
const handleCloseClick = (id) => {
|
||||
closeCall(id);
|
||||
};
|
||||
const { closeCall, activeCall } = useCall();
|
||||
const handleCloseClick = (id) => {
|
||||
closeCall(id);
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack
|
||||
sx={{
|
||||
width: "100%",
|
||||
px: 1,
|
||||
borderRight:
|
||||
activeCall === index ? 0 : activeCall - 1 === index ? 0 : 1,
|
||||
borderRightColor: "divider",
|
||||
}}
|
||||
direction={"row"}
|
||||
alignItems={"center"}
|
||||
justifyContent={"space-between"}
|
||||
>
|
||||
<CallTabDetails tab={tab} />
|
||||
<Zoom in={tab.active}>
|
||||
<IconButton size="small" onClick={() => handleCloseClick(tab.id)}>
|
||||
<Close fontSize="small" />
|
||||
</IconButton>
|
||||
</Zoom>
|
||||
</Stack>
|
||||
);
|
||||
return (
|
||||
<Stack
|
||||
sx={{
|
||||
width: "100%",
|
||||
px: 1,
|
||||
borderRight: activeCall === index ? 0 : activeCall - 1 === index ? 0 : 1,
|
||||
borderRightColor: "divider",
|
||||
}}
|
||||
direction={"row"}
|
||||
alignItems={"center"}
|
||||
justifyContent={"space-between"}
|
||||
>
|
||||
<CallTabDetails tab={tab} />
|
||||
<Zoom in={tab.active}>
|
||||
<IconButton size="small" onClick={() => handleCloseClick(tab.id)}>
|
||||
<Close fontSize="small" />
|
||||
</IconButton>
|
||||
</Zoom>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default CallTabLabel;
|
||||
|
||||
@@ -3,26 +3,26 @@ import moment from "jalali-moment";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
const CallTabTime = ({ realTimeDate }) => {
|
||||
const [date, setDate] = useState("...");
|
||||
const changeTabTime = (tabTime) => {
|
||||
moment.relativeTimeThreshold("ss", 1);
|
||||
return moment(tabTime).locale("fa").fromNow();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setInterval(() => {
|
||||
setDate(changeTabTime(realTimeDate));
|
||||
}, 1000);
|
||||
|
||||
return () => {
|
||||
clearInterval(timer);
|
||||
const [date, setDate] = useState("...");
|
||||
const changeTabTime = (tabTime) => {
|
||||
moment.relativeTimeThreshold("ss", 1);
|
||||
return moment(tabTime).locale("fa").fromNow();
|
||||
};
|
||||
}, [realTimeDate]);
|
||||
|
||||
return (
|
||||
<Typography variant="caption" color="gray">
|
||||
{date}
|
||||
</Typography>
|
||||
);
|
||||
useEffect(() => {
|
||||
const timer = setInterval(() => {
|
||||
setDate(changeTabTime(realTimeDate));
|
||||
}, 1000);
|
||||
|
||||
return () => {
|
||||
clearInterval(timer);
|
||||
};
|
||||
}, [realTimeDate]);
|
||||
|
||||
return (
|
||||
<Typography variant="caption" color="gray">
|
||||
{date}
|
||||
</Typography>
|
||||
);
|
||||
};
|
||||
export default CallTabTime;
|
||||
|
||||
@@ -3,38 +3,38 @@ import { Box, Tab, Tabs } from "@mui/material";
|
||||
import CallTabLabel from "./CallTabLabel";
|
||||
|
||||
const CallTabsList = () => {
|
||||
const { calls, changeActiveTabCall, activeCall } = useCall();
|
||||
const handleChange = (event, newValue) => {
|
||||
changeActiveTabCall(newValue);
|
||||
};
|
||||
const { calls, changeActiveTabCall, activeCall } = useCall();
|
||||
const handleChange = (event, newValue) => {
|
||||
changeActiveTabCall(newValue);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ borderBottom: 1, borderColor: "divider" }}>
|
||||
<Tabs
|
||||
value={activeCall}
|
||||
onChange={handleChange}
|
||||
variant="scrollable"
|
||||
scrollButtons="auto"
|
||||
sx={{ background: "#e0e0e0" }}
|
||||
>
|
||||
{calls.map((call, index) => (
|
||||
<Tab
|
||||
component={"div"}
|
||||
sx={{
|
||||
width: 350,
|
||||
p: 0,
|
||||
py: 0.5,
|
||||
background: call.active ? "#fff" : null,
|
||||
borderTopRightRadius: call.active ? "16px" : 0,
|
||||
borderTopLeftRadius: call.active ? "16px" : 0,
|
||||
}}
|
||||
label={<CallTabLabel index={index} tab={call} />}
|
||||
key={call.id}
|
||||
/>
|
||||
))}
|
||||
</Tabs>
|
||||
</Box>
|
||||
);
|
||||
return (
|
||||
<Box sx={{ borderBottom: 1, borderColor: "divider" }}>
|
||||
<Tabs
|
||||
value={activeCall}
|
||||
onChange={handleChange}
|
||||
variant="scrollable"
|
||||
scrollButtons="auto"
|
||||
sx={{ background: "#e0e0e0" }}
|
||||
>
|
||||
{calls.map((call, index) => (
|
||||
<Tab
|
||||
component={"div"}
|
||||
sx={{
|
||||
width: 350,
|
||||
p: 0,
|
||||
py: 0.5,
|
||||
background: call.active ? "#fff" : null,
|
||||
borderTopRightRadius: call.active ? "16px" : 0,
|
||||
borderTopLeftRadius: call.active ? "16px" : 0,
|
||||
}}
|
||||
label={<CallTabLabel index={index} tab={call} />}
|
||||
key={call.id}
|
||||
/>
|
||||
))}
|
||||
</Tabs>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default CallTabsList;
|
||||
|
||||
@@ -3,15 +3,15 @@ import CallTabPanel from "./CallTabPanel";
|
||||
import CallTabsList from "./CallTabsList";
|
||||
|
||||
const CallTabs = () => {
|
||||
const { calls } = useCall();
|
||||
const { calls } = useCall();
|
||||
|
||||
return (
|
||||
<>
|
||||
<CallTabsList />
|
||||
{calls.map((call) => (
|
||||
<CallTabPanel key={call.id} tab={call} />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<CallTabsList />
|
||||
{calls.map((call) => (
|
||||
<CallTabPanel key={call.id} tab={call} />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default CallTabs;
|
||||
|
||||
@@ -7,42 +7,38 @@ import { useEffect } from "react";
|
||||
import CallTabs from "./CallTabs";
|
||||
|
||||
const CallWidgetDialog = () => {
|
||||
const { openCallDialog, setOpenCallDialog, calls, newCall } = useCall();
|
||||
const { socket } = useSocket();
|
||||
const { openCallDialog, setOpenCallDialog, calls, newCall } = useCall();
|
||||
const { socket } = useSocket();
|
||||
|
||||
useEffect(() => {
|
||||
const call = (_data, act) => {
|
||||
const data = JSON.parse(_data);
|
||||
newCall({
|
||||
id: data.call_id,
|
||||
phone_number: data.phone_number,
|
||||
});
|
||||
act();
|
||||
};
|
||||
socket.on("call", call);
|
||||
useEffect(() => {
|
||||
const call = (_data, act) => {
|
||||
const data = JSON.parse(_data);
|
||||
newCall({
|
||||
id: data.call_id,
|
||||
phone_number: data.phone_number,
|
||||
});
|
||||
act();
|
||||
};
|
||||
socket.on("call", call);
|
||||
|
||||
return () => {
|
||||
socket.off("call", call);
|
||||
};
|
||||
});
|
||||
return () => {
|
||||
socket.off("call", call);
|
||||
};
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (calls.length === 0) {
|
||||
setOpenCallDialog(false);
|
||||
return;
|
||||
}
|
||||
setOpenCallDialog(true);
|
||||
}, [calls.length]);
|
||||
useEffect(() => {
|
||||
if (calls.length === 0) {
|
||||
setOpenCallDialog(false);
|
||||
return;
|
||||
}
|
||||
setOpenCallDialog(true);
|
||||
}, [calls.length]);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
fullScreen
|
||||
open={openCallDialog}
|
||||
slots={{ transition: DialogTransition }}
|
||||
>
|
||||
<CallTabs />
|
||||
</Dialog>
|
||||
);
|
||||
return (
|
||||
<Dialog fullScreen open={openCallDialog} slots={{ transition: DialogTransition }}>
|
||||
<CallTabs />
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default CallWidgetDialog;
|
||||
|
||||
@@ -4,12 +4,12 @@ import { CategoriesProvider } from "@/lib/contexts/category";
|
||||
import CallWidgetDialog from "./CallWidgetDialog";
|
||||
|
||||
const CallWidget = () => {
|
||||
return (
|
||||
<CategoriesProvider>
|
||||
<CallProvider>
|
||||
<CallWidgetDialog />
|
||||
</CallProvider>
|
||||
</CategoriesProvider>
|
||||
);
|
||||
return (
|
||||
<CategoriesProvider>
|
||||
<CallProvider>
|
||||
<CallWidgetDialog />
|
||||
</CallProvider>
|
||||
</CategoriesProvider>
|
||||
);
|
||||
};
|
||||
export default CallWidget;
|
||||
|
||||
@@ -5,56 +5,49 @@ import { LocalizationProvider, MobileDatePicker } from "@mui/x-date-pickers";
|
||||
import { IconButton, InputAdornment } from "@mui/material";
|
||||
import ClearIcon from "@mui/icons-material/Clear";
|
||||
|
||||
const CustomDatePicker = ({
|
||||
dateValue,
|
||||
setDateValue,
|
||||
placeholder = "انتخاب تاریخ",
|
||||
size = "small",
|
||||
}) => {
|
||||
const handleDateChange = (newValue) => {
|
||||
setDateValue(newValue);
|
||||
};
|
||||
const CustomDatePicker = ({ dateValue, setDateValue, placeholder = "انتخاب تاریخ", size = "small" }) => {
|
||||
const handleDateChange = (newValue) => {
|
||||
setDateValue(newValue);
|
||||
};
|
||||
|
||||
return (
|
||||
<LocalizationProvider
|
||||
dateAdapter={AdapterDateFnsJalali}
|
||||
localeText={
|
||||
faIR.components.MuiLocalizationProvider.defaultProps.localeText
|
||||
}
|
||||
>
|
||||
<MobileDatePicker
|
||||
value={dateValue || null}
|
||||
onChange={handleDateChange}
|
||||
slotProps={{
|
||||
textField: {
|
||||
size: size,
|
||||
placeholder: placeholder,
|
||||
InputProps: {
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<IconButton
|
||||
size={size}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
setDateValue(null);
|
||||
}}
|
||||
sx={{
|
||||
color: "#bfbfbf",
|
||||
"&:hover": {
|
||||
backgroundColor: "rgba(189, 189, 189, 0.1)",
|
||||
color: "#363434",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<ClearIcon />
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
),
|
||||
},
|
||||
},
|
||||
}}
|
||||
></MobileDatePicker>
|
||||
</LocalizationProvider>
|
||||
);
|
||||
return (
|
||||
<LocalizationProvider
|
||||
dateAdapter={AdapterDateFnsJalali}
|
||||
localeText={faIR.components.MuiLocalizationProvider.defaultProps.localeText}
|
||||
>
|
||||
<MobileDatePicker
|
||||
value={dateValue || null}
|
||||
onChange={handleDateChange}
|
||||
slotProps={{
|
||||
textField: {
|
||||
size: size,
|
||||
placeholder: placeholder,
|
||||
InputProps: {
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<IconButton
|
||||
size={size}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
setDateValue(null);
|
||||
}}
|
||||
sx={{
|
||||
color: "#bfbfbf",
|
||||
"&:hover": {
|
||||
backgroundColor: "rgba(189, 189, 189, 0.1)",
|
||||
color: "#363434",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<ClearIcon />
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
),
|
||||
},
|
||||
},
|
||||
}}
|
||||
></MobileDatePicker>
|
||||
</LocalizationProvider>
|
||||
);
|
||||
};
|
||||
export default CustomDatePicker;
|
||||
|
||||
@@ -11,189 +11,169 @@ import { FA_DATATABLE_LOCALIZATION } from "./localization/fa/datatable";
|
||||
import DataTable_Paper from "./table/Paper";
|
||||
|
||||
const rowSelectionReducer = (state, action) => {
|
||||
switch (action.type) {
|
||||
case "TOGGLE_ROW":
|
||||
return { [action.payload]: true };
|
||||
case "RESET":
|
||||
return {};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
switch (action.type) {
|
||||
case "TOGGLE_ROW":
|
||||
return { [action.payload]: true };
|
||||
case "RESET":
|
||||
return {};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
const DataTable_Main = (props) => {
|
||||
const request = useRequest();
|
||||
const [rowSelection, dispatchRowSelection] = useReducer(
|
||||
rowSelectionReducer,
|
||||
{},
|
||||
);
|
||||
const { filterData, sortData, setSortData, hideData } = useDataTable();
|
||||
const {
|
||||
need_filter,
|
||||
table_url,
|
||||
user_id,
|
||||
page_name,
|
||||
table_name,
|
||||
columns,
|
||||
initialStateProps,
|
||||
TableToolbar,
|
||||
table_title,
|
||||
RowActions,
|
||||
specific_data,
|
||||
specialFilter,
|
||||
} = props;
|
||||
const flatColumns = flattenArrayOfObjects(columns, "columns");
|
||||
const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 10 });
|
||||
const [totalRowCount, setTotalRowCount] = useState(0);
|
||||
const flattenHideData = flattenObjectOfObjects(hideData);
|
||||
const onSortingChange = (event) => {
|
||||
setSortData(event);
|
||||
};
|
||||
const request = useRequest();
|
||||
const [rowSelection, dispatchRowSelection] = useReducer(rowSelectionReducer, {});
|
||||
const { filterData, sortData, setSortData, hideData } = useDataTable();
|
||||
const {
|
||||
need_filter,
|
||||
table_url,
|
||||
user_id,
|
||||
page_name,
|
||||
table_name,
|
||||
columns,
|
||||
initialStateProps,
|
||||
TableToolbar,
|
||||
table_title,
|
||||
RowActions,
|
||||
specific_data,
|
||||
specialFilter,
|
||||
} = props;
|
||||
const flatColumns = flattenArrayOfObjects(columns, "columns");
|
||||
const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 10 });
|
||||
const [totalRowCount, setTotalRowCount] = useState(0);
|
||||
const flattenHideData = flattenObjectOfObjects(hideData);
|
||||
const onSortingChange = (event) => {
|
||||
setSortData(event);
|
||||
};
|
||||
|
||||
const fetchUrl = useCallback(() => {
|
||||
const params = new URLSearchParams();
|
||||
params.set("start", `${pagination.pageIndex * pagination.pageSize}`);
|
||||
params.set("size", pagination.pageSize);
|
||||
if (specialFilter) {
|
||||
const filteredSpecialFilterData = Object.values(specialFilter).filter(
|
||||
(filter) => !isArrayEmpty(filter.value),
|
||||
);
|
||||
params.set("filters", JSON.stringify(filteredSpecialFilterData || []));
|
||||
} else {
|
||||
const filteredFilterData = DataTableFilterDataStructure(
|
||||
filterData,
|
||||
isArrayEmpty,
|
||||
);
|
||||
params.set(
|
||||
"filters",
|
||||
JSON.stringify(
|
||||
filteredFilterData.length === 0 ? [] : filteredFilterData,
|
||||
const fetchUrl = useCallback(() => {
|
||||
const params = new URLSearchParams();
|
||||
params.set("start", `${pagination.pageIndex * pagination.pageSize}`);
|
||||
params.set("size", pagination.pageSize);
|
||||
if (specialFilter) {
|
||||
const filteredSpecialFilterData = Object.values(specialFilter).filter(
|
||||
(filter) => !isArrayEmpty(filter.value)
|
||||
);
|
||||
params.set("filters", JSON.stringify(filteredSpecialFilterData || []));
|
||||
} else {
|
||||
const filteredFilterData = DataTableFilterDataStructure(filterData, isArrayEmpty);
|
||||
params.set("filters", JSON.stringify(filteredFilterData.length === 0 ? [] : filteredFilterData));
|
||||
}
|
||||
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) => {
|
||||
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} />
|
||||
)}
|
||||
</>
|
||||
),
|
||||
);
|
||||
}
|
||||
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]);
|
||||
renderRowActions: ({ row }) => <RowActions row={row} mutate={mutate} />,
|
||||
...props,
|
||||
});
|
||||
|
||||
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
|
||||
return (
|
||||
<DataTable_Paper
|
||||
table={table}
|
||||
table_title={table_title}
|
||||
need_filter={need_filter}
|
||||
mutate={mutate}
|
||||
filterData={specialFilter || filterData}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
),
|
||||
renderRowActions: ({ row }) => <RowActions row={row} mutate={mutate} />,
|
||||
...props,
|
||||
});
|
||||
|
||||
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}
|
||||
/>
|
||||
);
|
||||
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;
|
||||
|
||||
@@ -2,200 +2,190 @@ import { parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||
import { TableBody, Typography } from "@mui/material";
|
||||
import { useMRT_RowVirtualizer, useMRT_Rows } from "material-react-table";
|
||||
import { memo, useMemo } from "react";
|
||||
import DataTable_TableBodyRow, {
|
||||
Memo_DataTable_TableBodyRow,
|
||||
} from "./TableBodyRow";
|
||||
import DataTable_TableBodyRow, { Memo_DataTable_TableBodyRow } from "./TableBodyRow";
|
||||
|
||||
const DataTable_TableBody = ({ columnVirtualizer, table, ...rest }) => {
|
||||
const {
|
||||
getBottomRows,
|
||||
getIsSomeRowsPinned,
|
||||
getRowModel,
|
||||
getState,
|
||||
getTopRows,
|
||||
options: {
|
||||
enableStickyFooter,
|
||||
enableStickyHeader,
|
||||
layoutMode,
|
||||
localization,
|
||||
memoMode,
|
||||
muiTableBodyProps,
|
||||
renderDetailPanel,
|
||||
renderEmptyRowsFallback,
|
||||
rowPinningDisplayMode,
|
||||
},
|
||||
refs: { tableFooterRef, tableHeadRef, tablePaperRef },
|
||||
} = table;
|
||||
const { columnFilters, globalFilter, isFullScreen, rowPinning } = getState();
|
||||
const {
|
||||
getBottomRows,
|
||||
getIsSomeRowsPinned,
|
||||
getRowModel,
|
||||
getState,
|
||||
getTopRows,
|
||||
options: {
|
||||
enableStickyFooter,
|
||||
enableStickyHeader,
|
||||
layoutMode,
|
||||
localization,
|
||||
memoMode,
|
||||
muiTableBodyProps,
|
||||
renderDetailPanel,
|
||||
renderEmptyRowsFallback,
|
||||
rowPinningDisplayMode,
|
||||
},
|
||||
refs: { tableFooterRef, tableHeadRef, tablePaperRef },
|
||||
} = table;
|
||||
const { columnFilters, globalFilter, isFullScreen, rowPinning } = getState();
|
||||
|
||||
const tableBodyProps = {
|
||||
...parseFromValuesOrFunc(muiTableBodyProps, { table }),
|
||||
...rest,
|
||||
};
|
||||
const tableBodyProps = {
|
||||
...parseFromValuesOrFunc(muiTableBodyProps, { table }),
|
||||
...rest,
|
||||
};
|
||||
|
||||
const tableHeadHeight =
|
||||
((enableStickyHeader || isFullScreen) &&
|
||||
tableHeadRef.current?.clientHeight) ||
|
||||
0;
|
||||
const tableFooterHeight =
|
||||
(enableStickyFooter && tableFooterRef.current?.clientHeight) || 0;
|
||||
const tableHeadHeight = ((enableStickyHeader || isFullScreen) && tableHeadRef.current?.clientHeight) || 0;
|
||||
const tableFooterHeight = (enableStickyFooter && tableFooterRef.current?.clientHeight) || 0;
|
||||
|
||||
const pinnedRowIds = useMemo(() => {
|
||||
if (!rowPinning.bottom?.length && !rowPinning.top?.length) return [];
|
||||
return getRowModel()
|
||||
.rows.filter((row) => row.getIsPinned())
|
||||
.map((r) => r.id);
|
||||
}, [rowPinning, getRowModel().rows]);
|
||||
const pinnedRowIds = useMemo(() => {
|
||||
if (!rowPinning.bottom?.length && !rowPinning.top?.length) return [];
|
||||
return getRowModel()
|
||||
.rows.filter((row) => row.getIsPinned())
|
||||
.map((r) => r.id);
|
||||
}, [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 = {
|
||||
columnVirtualizer,
|
||||
numRows: rows.length,
|
||||
table,
|
||||
};
|
||||
const commonRowProps = {
|
||||
columnVirtualizer,
|
||||
numRows: rows.length,
|
||||
table,
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{!rowPinningDisplayMode?.includes("sticky") &&
|
||||
getIsSomeRowsPinned("top") && (
|
||||
<TableBody
|
||||
{...tableBodyProps}
|
||||
sx={(theme) => ({
|
||||
display: layoutMode?.startsWith("grid") ? "grid" : undefined,
|
||||
position: "sticky",
|
||||
top: tableHeadHeight - 1,
|
||||
zIndex: 1,
|
||||
...parseFromValuesOrFunc(tableBodyProps?.sx, theme),
|
||||
})}
|
||||
>
|
||||
{getTopRows().map((row, staticRowIndex) => {
|
||||
const props = {
|
||||
...commonRowProps,
|
||||
row,
|
||||
staticRowIndex,
|
||||
};
|
||||
return memoMode === "rows" ? (
|
||||
<Memo_DataTable_TableBodyRow key={row.id} {...props} />
|
||||
) : (
|
||||
<DataTable_TableBodyRow key={row.id} {...props} />
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
)}
|
||||
<TableBody
|
||||
{...tableBodyProps}
|
||||
sx={(theme) => ({
|
||||
display: layoutMode?.startsWith("grid") ? "grid" : undefined,
|
||||
height: rowVirtualizer
|
||||
? `${rowVirtualizer.getTotalSize()}px`
|
||||
: undefined,
|
||||
minHeight: !rows.length ? "100px" : undefined,
|
||||
position: "relative",
|
||||
...parseFromValuesOrFunc(tableBodyProps?.sx, theme),
|
||||
})}
|
||||
>
|
||||
{tableBodyProps?.children ??
|
||||
(!rows.length ? (
|
||||
<tr
|
||||
style={{
|
||||
display: layoutMode?.startsWith("grid") ? "grid" : undefined,
|
||||
}}
|
||||
return (
|
||||
<>
|
||||
{!rowPinningDisplayMode?.includes("sticky") && getIsSomeRowsPinned("top") && (
|
||||
<TableBody
|
||||
{...tableBodyProps}
|
||||
sx={(theme) => ({
|
||||
display: layoutMode?.startsWith("grid") ? "grid" : undefined,
|
||||
position: "sticky",
|
||||
top: tableHeadHeight - 1,
|
||||
zIndex: 1,
|
||||
...parseFromValuesOrFunc(tableBodyProps?.sx, theme),
|
||||
})}
|
||||
>
|
||||
{getTopRows().map((row, staticRowIndex) => {
|
||||
const props = {
|
||||
...commonRowProps,
|
||||
row,
|
||||
staticRowIndex,
|
||||
};
|
||||
return memoMode === "rows" ? (
|
||||
<Memo_DataTable_TableBodyRow key={row.id} {...props} />
|
||||
) : (
|
||||
<DataTable_TableBodyRow key={row.id} {...props} />
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
)}
|
||||
<TableBody
|
||||
{...tableBodyProps}
|
||||
sx={(theme) => ({
|
||||
display: layoutMode?.startsWith("grid") ? "grid" : undefined,
|
||||
height: rowVirtualizer ? `${rowVirtualizer.getTotalSize()}px` : undefined,
|
||||
minHeight: !rows.length ? "100px" : undefined,
|
||||
position: "relative",
|
||||
...parseFromValuesOrFunc(tableBodyProps?.sx, theme),
|
||||
})}
|
||||
>
|
||||
<td
|
||||
colSpan={table.getVisibleLeafColumns().length}
|
||||
style={{
|
||||
display: layoutMode?.startsWith("grid") ? "grid" : undefined,
|
||||
}}
|
||||
>
|
||||
{renderEmptyRowsFallback?.({ table }) ?? (
|
||||
<Typography
|
||||
sx={{
|
||||
color: "text.secondary",
|
||||
fontStyle: "italic",
|
||||
maxWidth: `min(100vw, ${tablePaperRef.current?.clientWidth ?? 360}px)`,
|
||||
py: "2rem",
|
||||
textAlign: "center",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
{globalFilter || columnFilters.length
|
||||
? localization.noResultsFound
|
||||
: localization.noRecordsToDisplay}
|
||||
</Typography>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
) : (
|
||||
<>
|
||||
{(virtualRows ?? rows).map((rowOrVirtualRow, staticRowIndex) => {
|
||||
let row = rowOrVirtualRow;
|
||||
if (rowVirtualizer) {
|
||||
if (renderDetailPanel) {
|
||||
if (rowOrVirtualRow.index % 2 === 1) {
|
||||
return null;
|
||||
} else {
|
||||
staticRowIndex = rowOrVirtualRow.index / 2;
|
||||
}
|
||||
} else {
|
||||
staticRowIndex = rowOrVirtualRow.index;
|
||||
}
|
||||
row = rows[staticRowIndex];
|
||||
}
|
||||
const props = {
|
||||
...commonRowProps,
|
||||
pinnedRowIds,
|
||||
row,
|
||||
rowVirtualizer,
|
||||
staticRowIndex,
|
||||
virtualRow: rowVirtualizer ? rowOrVirtualRow : undefined,
|
||||
};
|
||||
const key = `${row.id}-${row.index}`;
|
||||
return memoMode === "rows" ? (
|
||||
<Memo_DataTable_TableBodyRow key={key} {...props} />
|
||||
) : (
|
||||
<DataTable_TableBodyRow key={key} {...props} />
|
||||
);
|
||||
})}
|
||||
</>
|
||||
))}
|
||||
</TableBody>
|
||||
{!rowPinningDisplayMode?.includes("sticky") &&
|
||||
getIsSomeRowsPinned("bottom") && (
|
||||
<TableBody
|
||||
{...tableBodyProps}
|
||||
sx={(theme) => ({
|
||||
bottom: tableFooterHeight - 1,
|
||||
display: layoutMode?.startsWith("grid") ? "grid" : undefined,
|
||||
position: "sticky",
|
||||
zIndex: 1,
|
||||
...parseFromValuesOrFunc(tableBodyProps?.sx, theme),
|
||||
})}
|
||||
>
|
||||
{getBottomRows().map((row, staticRowIndex) => {
|
||||
const props = {
|
||||
...commonRowProps,
|
||||
row,
|
||||
staticRowIndex,
|
||||
};
|
||||
return memoMode === "rows" ? (
|
||||
<Memo_DataTable_TableBodyRow key={row.id} {...props} />
|
||||
) : (
|
||||
<DataTable_TableBodyRow key={row.id} {...props} />
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
{tableBodyProps?.children ??
|
||||
(!rows.length ? (
|
||||
<tr
|
||||
style={{
|
||||
display: layoutMode?.startsWith("grid") ? "grid" : undefined,
|
||||
}}
|
||||
>
|
||||
<td
|
||||
colSpan={table.getVisibleLeafColumns().length}
|
||||
style={{
|
||||
display: layoutMode?.startsWith("grid") ? "grid" : undefined,
|
||||
}}
|
||||
>
|
||||
{renderEmptyRowsFallback?.({ table }) ?? (
|
||||
<Typography
|
||||
sx={{
|
||||
color: "text.secondary",
|
||||
fontStyle: "italic",
|
||||
maxWidth: `min(100vw, ${tablePaperRef.current?.clientWidth ?? 360}px)`,
|
||||
py: "2rem",
|
||||
textAlign: "center",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
{globalFilter || columnFilters.length
|
||||
? localization.noResultsFound
|
||||
: localization.noRecordsToDisplay}
|
||||
</Typography>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
) : (
|
||||
<>
|
||||
{(virtualRows ?? rows).map((rowOrVirtualRow, staticRowIndex) => {
|
||||
let row = rowOrVirtualRow;
|
||||
if (rowVirtualizer) {
|
||||
if (renderDetailPanel) {
|
||||
if (rowOrVirtualRow.index % 2 === 1) {
|
||||
return null;
|
||||
} else {
|
||||
staticRowIndex = rowOrVirtualRow.index / 2;
|
||||
}
|
||||
} else {
|
||||
staticRowIndex = rowOrVirtualRow.index;
|
||||
}
|
||||
row = rows[staticRowIndex];
|
||||
}
|
||||
const props = {
|
||||
...commonRowProps,
|
||||
pinnedRowIds,
|
||||
row,
|
||||
rowVirtualizer,
|
||||
staticRowIndex,
|
||||
virtualRow: rowVirtualizer ? rowOrVirtualRow : undefined,
|
||||
};
|
||||
const key = `${row.id}-${row.index}`;
|
||||
return memoMode === "rows" ? (
|
||||
<Memo_DataTable_TableBodyRow key={key} {...props} />
|
||||
) : (
|
||||
<DataTable_TableBodyRow key={key} {...props} />
|
||||
);
|
||||
})}
|
||||
</>
|
||||
))}
|
||||
</TableBody>
|
||||
{!rowPinningDisplayMode?.includes("sticky") && getIsSomeRowsPinned("bottom") && (
|
||||
<TableBody
|
||||
{...tableBodyProps}
|
||||
sx={(theme) => ({
|
||||
bottom: tableFooterHeight - 1,
|
||||
display: layoutMode?.startsWith("grid") ? "grid" : undefined,
|
||||
position: "sticky",
|
||||
zIndex: 1,
|
||||
...parseFromValuesOrFunc(tableBodyProps?.sx, theme),
|
||||
})}
|
||||
>
|
||||
{getBottomRows().map((row, staticRowIndex) => {
|
||||
const props = {
|
||||
...commonRowProps,
|
||||
row,
|
||||
staticRowIndex,
|
||||
};
|
||||
return memoMode === "rows" ? (
|
||||
<Memo_DataTable_TableBodyRow key={row.id} {...props} />
|
||||
) : (
|
||||
<DataTable_TableBodyRow key={row.id} {...props} />
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default DataTable_TableBody;
|
||||
|
||||
export const Memo_DataTable_TableBody = memo(
|
||||
DataTable_TableBody,
|
||||
(prev, next) => prev.table.options.data === next.table.options.data,
|
||||
DataTable_TableBody,
|
||||
(prev, next) => prev.table.options.data === next.table.options.data
|
||||
);
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import {
|
||||
getCommonMRTCellStyles,
|
||||
parseFromValuesOrFunc,
|
||||
} from "@/core/utils/utils";
|
||||
import { getCommonMRTCellStyles, parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { Skeleton, TableCell } from "@mui/material";
|
||||
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_TableBodyCellValue from "./TableBodyCellValue";
|
||||
|
||||
const DataTable_TableBodyCell = ({
|
||||
cell,
|
||||
numRows,
|
||||
rowRef,
|
||||
staticColumnIndex,
|
||||
staticRowIndex,
|
||||
table,
|
||||
...rest
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const {
|
||||
getState,
|
||||
options: {
|
||||
columnResizeDirection,
|
||||
columnResizeMode,
|
||||
createDisplayMode,
|
||||
editDisplayMode,
|
||||
enableCellActions,
|
||||
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",
|
||||
const DataTable_TableBodyCell = ({ cell, numRows, rowRef, staticColumnIndex, staticRowIndex, table, ...rest }) => {
|
||||
const theme = useTheme();
|
||||
const {
|
||||
getState,
|
||||
options: {
|
||||
columnResizeDirection,
|
||||
columnResizeMode,
|
||||
createDisplayMode,
|
||||
editDisplayMode,
|
||||
enableCellActions,
|
||||
enableClickToCopy,
|
||||
enableColumnOrdering,
|
||||
enableColumnPinning,
|
||||
enableGrouping,
|
||||
layoutMode,
|
||||
mrtTheme: { draggingBorderColor, baseBackgroundColor },
|
||||
muiSkeletonProps,
|
||||
muiTableBodyCellProps,
|
||||
},
|
||||
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",
|
||||
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;
|
||||
|
||||
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>
|
||||
);
|
||||
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,
|
||||
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 const Memo_DataTable_TableBodyCell = memo(
|
||||
DataTable_TableBodyCell,
|
||||
(prev, next) => next.cell === prev.cell,
|
||||
);
|
||||
export const Memo_DataTable_TableBodyCell = memo(DataTable_TableBodyCell, (prev, next) => next.cell === prev.cell);
|
||||
|
||||
@@ -2,111 +2,102 @@ import { Box } from "@mui/material";
|
||||
|
||||
const allowedTypes = ["string", "number"];
|
||||
|
||||
const DataTable_TableBodyCellValue = ({
|
||||
cell,
|
||||
rowRef,
|
||||
staticColumnIndex,
|
||||
staticRowIndex,
|
||||
table,
|
||||
}) => {
|
||||
const {
|
||||
getState,
|
||||
options: {
|
||||
enableFilterMatchHighlighting,
|
||||
mrtTheme: { matchHighlightColor },
|
||||
},
|
||||
} = table;
|
||||
const { column, row } = cell;
|
||||
const { columnDef } = column;
|
||||
const { globalFilter, globalFilterFn } = getState();
|
||||
const filterValue = column.getFilterValue();
|
||||
const DataTable_TableBodyCellValue = ({ cell, rowRef, staticColumnIndex, staticRowIndex, table }) => {
|
||||
const {
|
||||
getState,
|
||||
options: {
|
||||
enableFilterMatchHighlighting,
|
||||
mrtTheme: { matchHighlightColor },
|
||||
},
|
||||
} = table;
|
||||
const { column, row } = cell;
|
||||
const { columnDef } = column;
|
||||
const { globalFilter, globalFilterFn } = getState();
|
||||
const filterValue = column.getFilterValue();
|
||||
|
||||
let renderedCellValue =
|
||||
cell.getIsAggregated() && columnDef.AggregatedCell
|
||||
? columnDef.AggregatedCell({
|
||||
cell,
|
||||
column,
|
||||
row,
|
||||
table,
|
||||
})
|
||||
: row.getIsGrouped() && !cell.getIsGrouped()
|
||||
? null
|
||||
: cell.getIsGrouped() && columnDef.GroupedCell
|
||||
? columnDef.GroupedCell({
|
||||
cell,
|
||||
column,
|
||||
row,
|
||||
table,
|
||||
})
|
||||
: undefined;
|
||||
let renderedCellValue =
|
||||
cell.getIsAggregated() && columnDef.AggregatedCell
|
||||
? columnDef.AggregatedCell({
|
||||
cell,
|
||||
column,
|
||||
row,
|
||||
table,
|
||||
})
|
||||
: row.getIsGrouped() && !cell.getIsGrouped()
|
||||
? null
|
||||
: cell.getIsGrouped() && columnDef.GroupedCell
|
||||
? columnDef.GroupedCell({
|
||||
cell,
|
||||
column,
|
||||
row,
|
||||
table,
|
||||
})
|
||||
: undefined;
|
||||
|
||||
const isGroupedValue = renderedCellValue !== undefined;
|
||||
const isGroupedValue = renderedCellValue !== undefined;
|
||||
|
||||
if (!isGroupedValue) {
|
||||
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 (!isGroupedValue) {
|
||||
renderedCellValue = cell.renderValue();
|
||||
}
|
||||
}
|
||||
|
||||
if (columnDef.Cell && !isGroupedValue) {
|
||||
renderedCellValue = columnDef.Cell({
|
||||
cell,
|
||||
column,
|
||||
renderedCellValue,
|
||||
row,
|
||||
rowRef,
|
||||
staticColumnIndex,
|
||||
staticRowIndex,
|
||||
table,
|
||||
});
|
||||
}
|
||||
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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return renderedCellValue;
|
||||
if (columnDef.Cell && !isGroupedValue) {
|
||||
renderedCellValue = columnDef.Cell({
|
||||
cell,
|
||||
column,
|
||||
renderedCellValue,
|
||||
row,
|
||||
rowRef,
|
||||
staticColumnIndex,
|
||||
staticRowIndex,
|
||||
table,
|
||||
});
|
||||
}
|
||||
|
||||
return renderedCellValue;
|
||||
};
|
||||
export default DataTable_TableBodyCellValue;
|
||||
|
||||
@@ -1,258 +1,216 @@
|
||||
import {
|
||||
commonCellBeforeAfterStyles,
|
||||
getCommonPinnedCellStyles,
|
||||
parseFromValuesOrFunc,
|
||||
} from "@/core/utils/utils";
|
||||
import { commonCellBeforeAfterStyles, getCommonPinnedCellStyles, parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { TableRow, alpha, darken, lighten } from "@mui/material";
|
||||
import { getIsRowSelected } from "material-react-table";
|
||||
import { memo, useMemo, useRef } from "react";
|
||||
import DataTable_TableBodyCell, {
|
||||
Memo_DataTable_TableBodyCell,
|
||||
} from "./TableBodyCell";
|
||||
import DataTable_TableBodyCell, { Memo_DataTable_TableBodyCell } from "./TableBodyCell";
|
||||
import DataTable_TableDetailPanel from "./TableDetailPanel";
|
||||
|
||||
const DataTable_TableBodyRow = ({
|
||||
columnVirtualizer,
|
||||
numRows,
|
||||
pinnedRowIds,
|
||||
row,
|
||||
rowVirtualizer,
|
||||
staticRowIndex,
|
||||
table,
|
||||
virtualRow,
|
||||
...rest
|
||||
columnVirtualizer,
|
||||
numRows,
|
||||
pinnedRowIds,
|
||||
row,
|
||||
rowVirtualizer,
|
||||
staticRowIndex,
|
||||
table,
|
||||
virtualRow,
|
||||
...rest
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const theme = useTheme();
|
||||
|
||||
const {
|
||||
getState,
|
||||
options: {
|
||||
enableRowOrdering,
|
||||
enableRowPinning,
|
||||
enableStickyFooter,
|
||||
enableStickyHeader,
|
||||
layoutMode,
|
||||
memoMode,
|
||||
mrtTheme: {
|
||||
baseBackgroundColor,
|
||||
pinnedRowBackgroundColor,
|
||||
selectedRowBackgroundColor,
|
||||
},
|
||||
muiTableBodyRowProps,
|
||||
renderDetailPanel,
|
||||
rowPinningDisplayMode,
|
||||
},
|
||||
refs: { tableFooterRef, tableHeadRef },
|
||||
setHoveredRow,
|
||||
} = table;
|
||||
const {
|
||||
density,
|
||||
draggingColumn,
|
||||
draggingRow,
|
||||
editingCell,
|
||||
editingRow,
|
||||
hoveredRow,
|
||||
isFullScreen,
|
||||
rowPinning,
|
||||
} = getState();
|
||||
const {
|
||||
getState,
|
||||
options: {
|
||||
enableRowOrdering,
|
||||
enableRowPinning,
|
||||
enableStickyFooter,
|
||||
enableStickyHeader,
|
||||
layoutMode,
|
||||
memoMode,
|
||||
mrtTheme: { baseBackgroundColor, pinnedRowBackgroundColor, selectedRowBackgroundColor },
|
||||
muiTableBodyRowProps,
|
||||
renderDetailPanel,
|
||||
rowPinningDisplayMode,
|
||||
},
|
||||
refs: { tableFooterRef, tableHeadRef },
|
||||
setHoveredRow,
|
||||
} = table;
|
||||
const { density, draggingColumn, draggingRow, editingCell, editingRow, hoveredRow, isFullScreen, rowPinning } =
|
||||
getState();
|
||||
|
||||
const visibleCells = row.getVisibleCells();
|
||||
const visibleCells = row.getVisibleCells();
|
||||
|
||||
const { virtualColumns, virtualPaddingLeft, virtualPaddingRight } =
|
||||
columnVirtualizer ?? {};
|
||||
const { virtualColumns, virtualPaddingLeft, virtualPaddingRight } = columnVirtualizer ?? {};
|
||||
|
||||
const isRowSelected = getIsRowSelected({ row, table });
|
||||
const isRowPinned = enableRowPinning && row.getIsPinned();
|
||||
const isDraggingRow = draggingRow?.id === row.id;
|
||||
const isHoveredRow = hoveredRow?.id === row.id;
|
||||
const isRowSelected = getIsRowSelected({ row, table });
|
||||
const isRowPinned = enableRowPinning && row.getIsPinned();
|
||||
const isDraggingRow = draggingRow?.id === row.id;
|
||||
const isHoveredRow = hoveredRow?.id === row.id;
|
||||
|
||||
const tableRowProps = {
|
||||
...parseFromValuesOrFunc(muiTableBodyRowProps, {
|
||||
row,
|
||||
staticRowIndex,
|
||||
table,
|
||||
}),
|
||||
...rest,
|
||||
};
|
||||
const tableRowProps = {
|
||||
...parseFromValuesOrFunc(muiTableBodyRowProps, {
|
||||
row,
|
||||
staticRowIndex,
|
||||
table,
|
||||
}),
|
||||
...rest,
|
||||
};
|
||||
|
||||
const [bottomPinnedIndex, topPinnedIndex] = useMemo(() => {
|
||||
if (
|
||||
!enableRowPinning ||
|
||||
!rowPinningDisplayMode?.includes("sticky") ||
|
||||
!pinnedRowIds ||
|
||||
!row.getIsPinned()
|
||||
)
|
||||
return [];
|
||||
return [
|
||||
[...pinnedRowIds].reverse().indexOf(row.id),
|
||||
pinnedRowIds.indexOf(row.id),
|
||||
];
|
||||
}, [pinnedRowIds, rowPinning]);
|
||||
const [bottomPinnedIndex, topPinnedIndex] = useMemo(() => {
|
||||
if (!enableRowPinning || !rowPinningDisplayMode?.includes("sticky") || !pinnedRowIds || !row.getIsPinned())
|
||||
return [];
|
||||
return [[...pinnedRowIds].reverse().indexOf(row.id), pinnedRowIds.indexOf(row.id)];
|
||||
}, [pinnedRowIds, rowPinning]);
|
||||
|
||||
const tableHeadHeight =
|
||||
((enableStickyHeader || isFullScreen) &&
|
||||
tableHeadRef.current?.clientHeight) ||
|
||||
0;
|
||||
const tableFooterHeight =
|
||||
(enableStickyFooter && tableFooterRef.current?.clientHeight) || 0;
|
||||
const tableHeadHeight = ((enableStickyHeader || isFullScreen) && 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 =
|
||||
density === "compact" ? 37 : density === "comfortable" ? 53 : 69;
|
||||
const defaultRowHeight = density === "compact" ? 37 : density === "comfortable" ? 53 : 69;
|
||||
|
||||
const customRowHeight =
|
||||
// @ts-ignore
|
||||
parseInt(tableRowProps?.style?.height ?? sx?.height, 10) || undefined;
|
||||
const customRowHeight =
|
||||
// @ts-ignore
|
||||
parseInt(tableRowProps?.style?.height ?? sx?.height, 10) || undefined;
|
||||
|
||||
const rowHeight = customRowHeight || defaultRowHeight;
|
||||
const rowHeight = customRowHeight || defaultRowHeight;
|
||||
|
||||
const handleDragEnter = (_e) => {
|
||||
if (enableRowOrdering && draggingRow) {
|
||||
setHoveredRow(row);
|
||||
}
|
||||
};
|
||||
const handleDragEnter = (_e) => {
|
||||
if (enableRowOrdering && draggingRow) {
|
||||
setHoveredRow(row);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDragOver = (e) => {
|
||||
e.preventDefault();
|
||||
};
|
||||
const handleDragOver = (e) => {
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
const rowRef = useRef(null);
|
||||
const rowRef = useRef(null);
|
||||
|
||||
const cellHighlightColor = isRowSelected
|
||||
? selectedRowBackgroundColor
|
||||
: isRowPinned
|
||||
? pinnedRowBackgroundColor
|
||||
: undefined;
|
||||
const cellHighlightColor = isRowSelected
|
||||
? selectedRowBackgroundColor
|
||||
: isRowPinned
|
||||
? pinnedRowBackgroundColor
|
||||
: undefined;
|
||||
|
||||
const cellHighlightColorHover =
|
||||
tableRowProps?.hover !== false
|
||||
? isRowSelected
|
||||
? cellHighlightColor
|
||||
: theme.palette.mode === "dark"
|
||||
? `${lighten(baseBackgroundColor, 0.2)}`
|
||||
: `${darken(baseBackgroundColor, 0.2)}`
|
||||
: undefined;
|
||||
const cellHighlightColorHover =
|
||||
tableRowProps?.hover !== false
|
||||
? isRowSelected
|
||||
? cellHighlightColor
|
||||
: theme.palette.mode === "dark"
|
||||
? `${lighten(baseBackgroundColor, 0.2)}`
|
||||
: `${darken(baseBackgroundColor, 0.2)}`
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableRow
|
||||
data-index={renderDetailPanel ? staticRowIndex * 2 : staticRowIndex}
|
||||
data-pinned={!!isRowPinned || undefined}
|
||||
data-selected={isRowSelected || undefined}
|
||||
onDragEnter={handleDragEnter}
|
||||
onDragOver={handleDragOver}
|
||||
ref={(node) => {
|
||||
if (node) {
|
||||
rowRef.current = node;
|
||||
rowVirtualizer?.measureElement(node);
|
||||
}
|
||||
}}
|
||||
selected={isRowSelected}
|
||||
{...tableRowProps}
|
||||
style={{
|
||||
transform: virtualRow
|
||||
? `translateY(${virtualRow.start}px)`
|
||||
: undefined,
|
||||
...tableRowProps?.style,
|
||||
}}
|
||||
sx={(theme) => ({
|
||||
"&:hover td:after": cellHighlightColorHover
|
||||
? {
|
||||
backgroundColor: alpha(cellHighlightColorHover, 0.3),
|
||||
...commonCellBeforeAfterStyles,
|
||||
}
|
||||
: undefined,
|
||||
bottom:
|
||||
!virtualRow && bottomPinnedIndex !== undefined && isRowPinned
|
||||
? `${bottomPinnedIndex * rowHeight + (enableStickyFooter ? tableFooterHeight - 1 : 0)}px`
|
||||
: undefined,
|
||||
boxSizing: "border-box",
|
||||
display: layoutMode?.startsWith("grid") ? "flex" : undefined,
|
||||
opacity: isRowPinned ? 0.97 : isDraggingRow || isHoveredRow ? 0.5 : 1,
|
||||
position: virtualRow
|
||||
? "absolute"
|
||||
: rowPinningDisplayMode?.includes("sticky") && isRowPinned
|
||||
? "sticky"
|
||||
: "relative",
|
||||
td: {
|
||||
...getCommonPinnedCellStyles({ table, theme }),
|
||||
},
|
||||
"td:after": cellHighlightColor
|
||||
? {
|
||||
backgroundColor: cellHighlightColor,
|
||||
...commonCellBeforeAfterStyles,
|
||||
}
|
||||
: undefined,
|
||||
top: virtualRow
|
||||
? 0
|
||||
: topPinnedIndex !== undefined && isRowPinned
|
||||
? `${
|
||||
topPinnedIndex * rowHeight +
|
||||
(enableStickyHeader || isFullScreen ? tableHeadHeight - 1 : 0)
|
||||
}px`
|
||||
: undefined,
|
||||
transition: virtualRow ? "none" : "all 150ms ease-in-out",
|
||||
width: "100%",
|
||||
zIndex:
|
||||
rowPinningDisplayMode?.includes("sticky") && isRowPinned ? 2 : 0,
|
||||
...sx,
|
||||
})}
|
||||
>
|
||||
{virtualPaddingLeft ? (
|
||||
<td style={{ display: "flex", width: virtualPaddingLeft }} />
|
||||
) : null}
|
||||
{(virtualColumns ?? visibleCells).map(
|
||||
(cellOrVirtualCell, staticColumnIndex) => {
|
||||
let cell = cellOrVirtualCell;
|
||||
if (columnVirtualizer) {
|
||||
staticColumnIndex = cellOrVirtualCell.index;
|
||||
cell = visibleCells[staticColumnIndex];
|
||||
}
|
||||
const props = {
|
||||
cell,
|
||||
numRows,
|
||||
rowRef,
|
||||
staticColumnIndex,
|
||||
staticRowIndex,
|
||||
table,
|
||||
};
|
||||
return cell ? (
|
||||
memoMode === "cells" &&
|
||||
cell.column.columnDef.columnDefType === "data" &&
|
||||
!draggingColumn &&
|
||||
!draggingRow &&
|
||||
editingCell?.id !== cell.id &&
|
||||
editingRow?.id !== row.id ? (
|
||||
<Memo_DataTable_TableBodyCell key={cell.id} {...props} />
|
||||
) : (
|
||||
<DataTable_TableBodyCell key={cell.id} {...props} />
|
||||
)
|
||||
) : null;
|
||||
},
|
||||
)}
|
||||
{virtualPaddingRight ? (
|
||||
<td style={{ display: "flex", width: virtualPaddingRight }} />
|
||||
) : null}
|
||||
</TableRow>
|
||||
{renderDetailPanel && !row.getIsGrouped() && (
|
||||
<DataTable_TableDetailPanel
|
||||
parentRowRef={rowRef}
|
||||
row={row}
|
||||
rowVirtualizer={rowVirtualizer}
|
||||
staticRowIndex={staticRowIndex}
|
||||
table={table}
|
||||
virtualRow={virtualRow}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<TableRow
|
||||
data-index={renderDetailPanel ? staticRowIndex * 2 : staticRowIndex}
|
||||
data-pinned={!!isRowPinned || undefined}
|
||||
data-selected={isRowSelected || undefined}
|
||||
onDragEnter={handleDragEnter}
|
||||
onDragOver={handleDragOver}
|
||||
ref={(node) => {
|
||||
if (node) {
|
||||
rowRef.current = node;
|
||||
rowVirtualizer?.measureElement(node);
|
||||
}
|
||||
}}
|
||||
selected={isRowSelected}
|
||||
{...tableRowProps}
|
||||
style={{
|
||||
transform: virtualRow ? `translateY(${virtualRow.start}px)` : undefined,
|
||||
...tableRowProps?.style,
|
||||
}}
|
||||
sx={(theme) => ({
|
||||
"&:hover td:after": cellHighlightColorHover
|
||||
? {
|
||||
backgroundColor: alpha(cellHighlightColorHover, 0.3),
|
||||
...commonCellBeforeAfterStyles,
|
||||
}
|
||||
: undefined,
|
||||
bottom:
|
||||
!virtualRow && bottomPinnedIndex !== undefined && isRowPinned
|
||||
? `${bottomPinnedIndex * rowHeight + (enableStickyFooter ? tableFooterHeight - 1 : 0)}px`
|
||||
: undefined,
|
||||
boxSizing: "border-box",
|
||||
display: layoutMode?.startsWith("grid") ? "flex" : undefined,
|
||||
opacity: isRowPinned ? 0.97 : isDraggingRow || isHoveredRow ? 0.5 : 1,
|
||||
position: virtualRow
|
||||
? "absolute"
|
||||
: rowPinningDisplayMode?.includes("sticky") && isRowPinned
|
||||
? "sticky"
|
||||
: "relative",
|
||||
td: {
|
||||
...getCommonPinnedCellStyles({ table, theme }),
|
||||
},
|
||||
"td:after": cellHighlightColor
|
||||
? {
|
||||
backgroundColor: cellHighlightColor,
|
||||
...commonCellBeforeAfterStyles,
|
||||
}
|
||||
: undefined,
|
||||
top: virtualRow
|
||||
? 0
|
||||
: topPinnedIndex !== undefined && isRowPinned
|
||||
? `${
|
||||
topPinnedIndex * rowHeight +
|
||||
(enableStickyHeader || isFullScreen ? tableHeadHeight - 1 : 0)
|
||||
}px`
|
||||
: undefined,
|
||||
transition: virtualRow ? "none" : "all 150ms ease-in-out",
|
||||
width: "100%",
|
||||
zIndex: rowPinningDisplayMode?.includes("sticky") && isRowPinned ? 2 : 0,
|
||||
...sx,
|
||||
})}
|
||||
>
|
||||
{virtualPaddingLeft ? <td style={{ display: "flex", width: virtualPaddingLeft }} /> : null}
|
||||
{(virtualColumns ?? visibleCells).map((cellOrVirtualCell, staticColumnIndex) => {
|
||||
let cell = cellOrVirtualCell;
|
||||
if (columnVirtualizer) {
|
||||
staticColumnIndex = cellOrVirtualCell.index;
|
||||
cell = visibleCells[staticColumnIndex];
|
||||
}
|
||||
const props = {
|
||||
cell,
|
||||
numRows,
|
||||
rowRef,
|
||||
staticColumnIndex,
|
||||
staticRowIndex,
|
||||
table,
|
||||
};
|
||||
return cell ? (
|
||||
memoMode === "cells" &&
|
||||
cell.column.columnDef.columnDefType === "data" &&
|
||||
!draggingColumn &&
|
||||
!draggingRow &&
|
||||
editingCell?.id !== cell.id &&
|
||||
editingRow?.id !== row.id ? (
|
||||
<Memo_DataTable_TableBodyCell key={cell.id} {...props} />
|
||||
) : (
|
||||
<DataTable_TableBodyCell key={cell.id} {...props} />
|
||||
)
|
||||
) : null;
|
||||
})}
|
||||
{virtualPaddingRight ? <td style={{ display: "flex", width: virtualPaddingRight }} /> : null}
|
||||
</TableRow>
|
||||
{renderDetailPanel && !row.getIsGrouped() && (
|
||||
<DataTable_TableDetailPanel
|
||||
parentRowRef={rowRef}
|
||||
row={row}
|
||||
rowVirtualizer={rowVirtualizer}
|
||||
staticRowIndex={staticRowIndex}
|
||||
table={table}
|
||||
virtualRow={virtualRow}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default DataTable_TableBodyRow;
|
||||
|
||||
export const Memo_DataTable_TableBodyRow = memo(
|
||||
DataTable_TableBodyRow,
|
||||
(prev, next) =>
|
||||
prev.row === next.row && prev.staticRowIndex === next.staticRowIndex,
|
||||
DataTable_TableBodyRow,
|
||||
(prev, next) => 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";
|
||||
|
||||
const DataTable_TableDetailPanel = ({
|
||||
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,
|
||||
parentRowRef,
|
||||
row,
|
||||
rowVirtualizer,
|
||||
staticRowIndex,
|
||||
table,
|
||||
});
|
||||
virtualRow,
|
||||
...rest
|
||||
}) => {
|
||||
const {
|
||||
getState,
|
||||
getVisibleLeafColumns,
|
||||
options: {
|
||||
layoutMode,
|
||||
mrtTheme: { baseBackgroundColor },
|
||||
muiDetailPanelProps,
|
||||
muiTableBodyRowProps,
|
||||
renderDetailPanel,
|
||||
},
|
||||
} = table;
|
||||
const { isLoading } = getState();
|
||||
|
||||
const tableCellProps = {
|
||||
...parseFromValuesOrFunc(muiDetailPanelProps, {
|
||||
row,
|
||||
table,
|
||||
}),
|
||||
...rest,
|
||||
};
|
||||
const tableRowProps = parseFromValuesOrFunc(muiTableBodyRowProps, {
|
||||
isDetailPanel: true,
|
||||
row,
|
||||
staticRowIndex,
|
||||
table,
|
||||
});
|
||||
|
||||
const DetailPanel = !isLoading && renderDetailPanel?.({ row, table });
|
||||
const tableCellProps = {
|
||||
...parseFromValuesOrFunc(muiDetailPanelProps, {
|
||||
row,
|
||||
table,
|
||||
}),
|
||||
...rest,
|
||||
};
|
||||
|
||||
return (
|
||||
<TableRow
|
||||
className="Mui-TableBodyCell-DetailPanel"
|
||||
data-index={renderDetailPanel ? staticRowIndex * 2 + 1 : staticRowIndex}
|
||||
ref={(node) => {
|
||||
if (node) {
|
||||
rowVirtualizer?.measureElement?.(node);
|
||||
}
|
||||
}}
|
||||
{...tableRowProps}
|
||||
sx={(theme) => ({
|
||||
display: layoutMode?.startsWith("grid") ? "flex" : undefined,
|
||||
position: virtualRow ? "absolute" : undefined,
|
||||
top: virtualRow
|
||||
? `${parentRowRef.current?.getBoundingClientRect()?.height}px`
|
||||
: undefined,
|
||||
transform: virtualRow
|
||||
? `translateY(${virtualRow?.start}px)`
|
||||
: undefined,
|
||||
width: "100%",
|
||||
...parseFromValuesOrFunc(tableRowProps?.sx, theme),
|
||||
})}
|
||||
>
|
||||
<TableCell
|
||||
className="Mui-TableBodyCell-DetailPanel"
|
||||
colSpan={getVisibleLeafColumns().length}
|
||||
{...tableCellProps}
|
||||
sx={(theme) => ({
|
||||
backgroundColor: virtualRow ? baseBackgroundColor : undefined,
|
||||
borderBottom: !row.getIsExpanded() ? "none" : undefined,
|
||||
display: layoutMode?.startsWith("grid") ? "flex" : undefined,
|
||||
py: !!DetailPanel && row.getIsExpanded() ? "1rem" : 0,
|
||||
transition: !virtualRow ? "all 150ms ease-in-out" : undefined,
|
||||
width: `100%`,
|
||||
...parseFromValuesOrFunc(tableCellProps?.sx, theme),
|
||||
})}
|
||||
>
|
||||
{virtualRow ? (
|
||||
row.getIsExpanded() && DetailPanel
|
||||
) : (
|
||||
<Collapse in={row.getIsExpanded()} mountOnEnter unmountOnExit>
|
||||
{DetailPanel}
|
||||
</Collapse>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
const DetailPanel = !isLoading && renderDetailPanel?.({ row, table });
|
||||
|
||||
return (
|
||||
<TableRow
|
||||
className="Mui-TableBodyCell-DetailPanel"
|
||||
data-index={renderDetailPanel ? staticRowIndex * 2 + 1 : staticRowIndex}
|
||||
ref={(node) => {
|
||||
if (node) {
|
||||
rowVirtualizer?.measureElement?.(node);
|
||||
}
|
||||
}}
|
||||
{...tableRowProps}
|
||||
sx={(theme) => ({
|
||||
display: layoutMode?.startsWith("grid") ? "flex" : undefined,
|
||||
position: virtualRow ? "absolute" : undefined,
|
||||
top: virtualRow ? `${parentRowRef.current?.getBoundingClientRect()?.height}px` : undefined,
|
||||
transform: virtualRow ? `translateY(${virtualRow?.start}px)` : undefined,
|
||||
width: "100%",
|
||||
...parseFromValuesOrFunc(tableRowProps?.sx, theme),
|
||||
})}
|
||||
>
|
||||
<TableCell
|
||||
className="Mui-TableBodyCell-DetailPanel"
|
||||
colSpan={getVisibleLeafColumns().length}
|
||||
{...tableCellProps}
|
||||
sx={(theme) => ({
|
||||
backgroundColor: virtualRow ? baseBackgroundColor : undefined,
|
||||
borderBottom: !row.getIsExpanded() ? "none" : undefined,
|
||||
display: layoutMode?.startsWith("grid") ? "flex" : undefined,
|
||||
py: !!DetailPanel && row.getIsExpanded() ? "1rem" : 0,
|
||||
transition: !virtualRow ? "all 150ms ease-in-out" : undefined,
|
||||
width: `100%`,
|
||||
...parseFromValuesOrFunc(tableCellProps?.sx, theme),
|
||||
})}
|
||||
>
|
||||
{virtualRow ? (
|
||||
row.getIsExpanded() && DetailPanel
|
||||
) : (
|
||||
<Collapse in={row.getIsExpanded()} mountOnEnter unmountOnExit>
|
||||
{DetailPanel}
|
||||
</Collapse>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
};
|
||||
export default DataTable_TableDetailPanel;
|
||||
|
||||
@@ -1,74 +1,68 @@
|
||||
import {
|
||||
getCommonTooltipProps,
|
||||
parseFromValuesOrFunc,
|
||||
} from "@/core/utils/utils";
|
||||
import { getCommonTooltipProps, parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||
import { Button, Tooltip } from "@mui/material";
|
||||
import { useState } from "react";
|
||||
|
||||
const DataTable_CopyButton = ({ cell, table, ...rest }) => {
|
||||
const {
|
||||
options: { localization, muiCopyButtonProps },
|
||||
} = table;
|
||||
const { column, row } = cell;
|
||||
const { columnDef } = column;
|
||||
const {
|
||||
options: { localization, muiCopyButtonProps },
|
||||
} = table;
|
||||
const { column, row } = cell;
|
||||
const { columnDef } = column;
|
||||
|
||||
const [copied, setCopied] = useState(false);
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
const handleCopy = (event, text) => {
|
||||
event.stopPropagation();
|
||||
navigator.clipboard.writeText(text);
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 4000);
|
||||
};
|
||||
const handleCopy = (event, text) => {
|
||||
event.stopPropagation();
|
||||
navigator.clipboard.writeText(text);
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 4000);
|
||||
};
|
||||
|
||||
const buttonProps = {
|
||||
...parseFromValuesOrFunc(muiCopyButtonProps, {
|
||||
cell,
|
||||
column,
|
||||
row,
|
||||
table,
|
||||
}),
|
||||
...parseFromValuesOrFunc(columnDef.muiCopyButtonProps, {
|
||||
cell,
|
||||
column,
|
||||
row,
|
||||
table,
|
||||
}),
|
||||
...rest,
|
||||
};
|
||||
const buttonProps = {
|
||||
...parseFromValuesOrFunc(muiCopyButtonProps, {
|
||||
cell,
|
||||
column,
|
||||
row,
|
||||
table,
|
||||
}),
|
||||
...parseFromValuesOrFunc(columnDef.muiCopyButtonProps, {
|
||||
cell,
|
||||
column,
|
||||
row,
|
||||
table,
|
||||
}),
|
||||
...rest,
|
||||
};
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
{...getCommonTooltipProps("top")}
|
||||
title={
|
||||
buttonProps?.title ??
|
||||
(copied ? localization.copiedToClipboard : localization.clickToCopy)
|
||||
}
|
||||
>
|
||||
<Button
|
||||
onClick={(e) => handleCopy(e, cell.getValue())}
|
||||
size="small"
|
||||
type="button"
|
||||
variant="text"
|
||||
{...buttonProps}
|
||||
sx={(theme) => ({
|
||||
backgroundColor: "transparent",
|
||||
border: "none",
|
||||
color: "inherit",
|
||||
cursor: "copy",
|
||||
fontFamily: "inherit",
|
||||
fontSize: "inherit",
|
||||
letterSpacing: "inherit",
|
||||
m: "-0.25rem",
|
||||
minWidth: "unset",
|
||||
py: 0,
|
||||
textAlign: "inherit",
|
||||
textTransform: "inherit",
|
||||
...parseFromValuesOrFunc(buttonProps?.sx, theme),
|
||||
})}
|
||||
title={undefined}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
return (
|
||||
<Tooltip
|
||||
{...getCommonTooltipProps("top")}
|
||||
title={buttonProps?.title ?? (copied ? localization.copiedToClipboard : localization.clickToCopy)}
|
||||
>
|
||||
<Button
|
||||
onClick={(e) => handleCopy(e, cell.getValue())}
|
||||
size="small"
|
||||
type="button"
|
||||
variant="text"
|
||||
{...buttonProps}
|
||||
sx={(theme) => ({
|
||||
backgroundColor: "transparent",
|
||||
border: "none",
|
||||
color: "inherit",
|
||||
cursor: "copy",
|
||||
fontFamily: "inherit",
|
||||
fontSize: "inherit",
|
||||
letterSpacing: "inherit",
|
||||
m: "-0.25rem",
|
||||
minWidth: "unset",
|
||||
py: 0,
|
||||
textAlign: "inherit",
|
||||
textTransform: "inherit",
|
||||
...parseFromValuesOrFunc(buttonProps?.sx, theme),
|
||||
})}
|
||||
title={undefined}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
export default DataTable_CopyButton;
|
||||
|
||||
@@ -1,52 +1,46 @@
|
||||
import {
|
||||
getCommonTooltipProps,
|
||||
parseFromValuesOrFunc,
|
||||
} from "@/core/utils/utils";
|
||||
import { getCommonTooltipProps, parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||
import { IconButton, Tooltip } from "@mui/material";
|
||||
|
||||
const DataTable_GrabHandleButton = ({ location, table, ...rest }) => {
|
||||
const {
|
||||
options: {
|
||||
icons: { DragHandleIcon },
|
||||
localization,
|
||||
},
|
||||
} = table;
|
||||
const {
|
||||
options: {
|
||||
icons: { DragHandleIcon },
|
||||
localization,
|
||||
},
|
||||
} = table;
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
{...getCommonTooltipProps("top")}
|
||||
title={rest?.title ?? localization.move}
|
||||
>
|
||||
<IconButton
|
||||
aria-label={rest.title ?? localization.move}
|
||||
disableRipple
|
||||
draggable="true"
|
||||
size="small"
|
||||
{...rest}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
rest?.onClick?.(e);
|
||||
}}
|
||||
sx={(theme) => ({
|
||||
"&:active": {
|
||||
cursor: "grabbing",
|
||||
},
|
||||
"&:hover": {
|
||||
backgroundColor: "transparent",
|
||||
opacity: 1,
|
||||
},
|
||||
cursor: "grab",
|
||||
m: "0 -0.1rem",
|
||||
opacity: location === "row" ? 1 : 0.5,
|
||||
p: "2px",
|
||||
transition: "all 150ms ease-in-out",
|
||||
...parseFromValuesOrFunc(rest?.sx, theme),
|
||||
})}
|
||||
title={undefined}
|
||||
>
|
||||
<DragHandleIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
);
|
||||
return (
|
||||
<Tooltip {...getCommonTooltipProps("top")} title={rest?.title ?? localization.move}>
|
||||
<IconButton
|
||||
aria-label={rest.title ?? localization.move}
|
||||
disableRipple
|
||||
draggable="true"
|
||||
size="small"
|
||||
{...rest}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
rest?.onClick?.(e);
|
||||
}}
|
||||
sx={(theme) => ({
|
||||
"&:active": {
|
||||
cursor: "grabbing",
|
||||
},
|
||||
"&:hover": {
|
||||
backgroundColor: "transparent",
|
||||
opacity: 1,
|
||||
},
|
||||
cursor: "grab",
|
||||
m: "0 -0.1rem",
|
||||
opacity: location === "row" ? 1 : 0.5,
|
||||
p: "2px",
|
||||
transition: "all 150ms ease-in-out",
|
||||
...parseFromValuesOrFunc(rest?.sx, theme),
|
||||
})}
|
||||
title={undefined}
|
||||
>
|
||||
<DragHandleIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
export default DataTable_GrabHandleButton;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const DataTable_ToggleFiltersButton = ({ table, ...rest }) => {
|
||||
return <></>;
|
||||
return <></>;
|
||||
};
|
||||
export default DataTable_ToggleFiltersButton;
|
||||
|
||||
@@ -12,122 +12,121 @@ import ScrollBox from "../../ScrollBox";
|
||||
import FilterBodyController from "./FilterBodyController";
|
||||
|
||||
function FilterBody({ columns, drawerState, setDrawerState }) {
|
||||
const { filterData, setFilterData } = useDataTable();
|
||||
const [validationSchema, setValidationSchema] = useState(Yup.object({}));
|
||||
const { filterData, setFilterData } = useDataTable();
|
||||
const [validationSchema, setValidationSchema] = useState(Yup.object({}));
|
||||
|
||||
console.log(filterData);
|
||||
console.log(filterData);
|
||||
|
||||
useEffect(() => {
|
||||
const schema = Yup.object(
|
||||
Object.keys(filterData).reduce((acc, key) => {
|
||||
const initialValue = filterData[key];
|
||||
if (initialValue.filterMode === "between") {
|
||||
acc[key] = Yup.object().shape({
|
||||
value: Yup.array()
|
||||
.of(Yup.string().nullable())
|
||||
.test({
|
||||
test(value, ctx) {
|
||||
const [start, end] = value || ["", ""];
|
||||
if (
|
||||
initialValue.datatype === "numeric" &&
|
||||
parseInt(end, 10) <= parseInt(start, 10)
|
||||
) {
|
||||
return ctx.createError({
|
||||
message: `مقدار وارده باید بیشتر از (${start}) باشد`,
|
||||
useEffect(() => {
|
||||
const schema = Yup.object(
|
||||
Object.keys(filterData).reduce((acc, key) => {
|
||||
const initialValue = filterData[key];
|
||||
if (initialValue.filterMode === "between") {
|
||||
acc[key] = Yup.object().shape({
|
||||
value: Yup.array()
|
||||
.of(Yup.string().nullable())
|
||||
.test({
|
||||
test(value, ctx) {
|
||||
const [start, end] = value || ["", ""];
|
||||
if (
|
||||
initialValue.datatype === "numeric" &&
|
||||
parseInt(end, 10) <= parseInt(start, 10)
|
||||
) {
|
||||
return ctx.createError({
|
||||
message: `مقدار وارده باید بیشتر از (${start}) باشد`,
|
||||
});
|
||||
} else if ((start && !end) || (!start && end)) {
|
||||
return ctx.createError({
|
||||
message: "این بخش را تکمیل نمایید",
|
||||
});
|
||||
}
|
||||
return true;
|
||||
},
|
||||
}),
|
||||
});
|
||||
} else if ((start && !end) || (!start && end)) {
|
||||
return ctx.createError({
|
||||
message: "این بخش را تکمیل نمایید",
|
||||
});
|
||||
}
|
||||
return true;
|
||||
},
|
||||
}),
|
||||
});
|
||||
}
|
||||
return acc;
|
||||
}, {}),
|
||||
}
|
||||
return acc;
|
||||
}, {})
|
||||
);
|
||||
|
||||
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>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
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;
|
||||
|
||||
@@ -2,24 +2,24 @@ import { Controller, useWatch } from "react-hook-form";
|
||||
import FilterBodyField from "./FilterBodyField";
|
||||
|
||||
const FilterBodyController = ({ column, control, reset, errors }) => {
|
||||
return (
|
||||
<Controller
|
||||
name={`${column.id}`}
|
||||
control={control}
|
||||
render={({ field: { value, name, onBlur, onChange } }) => {
|
||||
return (
|
||||
<FilterBodyField
|
||||
column={column}
|
||||
filterParameters={value}
|
||||
handleChange={onChange}
|
||||
handleBlur={onBlur}
|
||||
dependencyFieldValue={null}
|
||||
resetForm={() => reset()}
|
||||
errors={errors}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<Controller
|
||||
name={`${column.id}`}
|
||||
control={control}
|
||||
render={({ field: { value, name, onBlur, onChange } }) => {
|
||||
return (
|
||||
<FilterBodyField
|
||||
column={column}
|
||||
filterParameters={value}
|
||||
handleChange={onChange}
|
||||
handleBlur={onBlur}
|
||||
dependencyFieldValue={null}
|
||||
resetForm={() => reset()}
|
||||
errors={errors}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export default FilterBodyController;
|
||||
|
||||
@@ -1,32 +1,27 @@
|
||||
import { Controller, useWatch } from "react-hook-form";
|
||||
import FilterBodyField from "./FilterBodyField";
|
||||
|
||||
const FilterBodyControllerWithDependency = ({
|
||||
column,
|
||||
control,
|
||||
reset,
|
||||
errors,
|
||||
}) => {
|
||||
const dependencyField = useWatch({ control, name: column.dependencyId });
|
||||
const FilterBodyControllerWithDependency = ({ column, control, reset, errors }) => {
|
||||
const dependencyField = useWatch({ control, name: column.dependencyId });
|
||||
|
||||
return (
|
||||
<Controller
|
||||
name={`${column.id}`}
|
||||
control={control}
|
||||
render={({ field: { value, name, onBlur, onChange } }) => {
|
||||
return (
|
||||
<FilterBodyField
|
||||
column={column}
|
||||
filterParameters={value}
|
||||
handleChange={onChange}
|
||||
handleBlur={onBlur}
|
||||
dependencyFieldValue={dependencyField}
|
||||
resetForm={() => reset()}
|
||||
errors={errors}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<Controller
|
||||
name={`${column.id}`}
|
||||
control={control}
|
||||
render={({ field: { value, name, onBlur, onChange } }) => {
|
||||
return (
|
||||
<FilterBodyField
|
||||
column={column}
|
||||
filterParameters={value}
|
||||
handleChange={onChange}
|
||||
handleBlur={onBlur}
|
||||
dependencyFieldValue={dependencyField}
|
||||
resetForm={() => reset()}
|
||||
errors={errors}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export default FilterBodyControllerWithDependency;
|
||||
|
||||
@@ -8,98 +8,97 @@ import CustomTextFieldRange from "@/core/components/DataTable/filter/fieldsType/
|
||||
import { useState } from "react";
|
||||
|
||||
const columnFilterModeOptionFa = {
|
||||
equals: "برابر",
|
||||
notEquals: "نابرابر",
|
||||
contains: "شامل",
|
||||
lessThan: "کوچکتر",
|
||||
greaterThan: "بزرگتر",
|
||||
fuzzy: "فازی",
|
||||
between: "مابین",
|
||||
equals: "برابر",
|
||||
notEquals: "نابرابر",
|
||||
contains: "شامل",
|
||||
lessThan: "کوچکتر",
|
||||
greaterThan: "بزرگتر",
|
||||
fuzzy: "فازی",
|
||||
between: "مابین",
|
||||
};
|
||||
|
||||
function FilterBodyField({
|
||||
column,
|
||||
filterParameters,
|
||||
handleChange,
|
||||
handleBlur,
|
||||
dependencyFieldValue,
|
||||
setFieldValue,
|
||||
resetForm,
|
||||
errors,
|
||||
column,
|
||||
filterParameters,
|
||||
handleChange,
|
||||
handleBlur,
|
||||
dependencyFieldValue,
|
||||
setFieldValue,
|
||||
resetForm,
|
||||
errors,
|
||||
}) {
|
||||
const [anchorEl, setAnchorEl] = useState(null);
|
||||
const defaultFilterTranslation =
|
||||
columnFilterModeOptionFa[filterParameters.filterMode] ||
|
||||
filterParameters.filterMode;
|
||||
const [anchorEl, setAnchorEl] = useState(null);
|
||||
const defaultFilterTranslation =
|
||||
columnFilterModeOptionFa[filterParameters.filterMode] || filterParameters.filterMode;
|
||||
|
||||
const handleOpenFilterBox = (event) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
|
||||
const renderField = () => {
|
||||
const commonProps = {
|
||||
column,
|
||||
filterParameters,
|
||||
defaultFilterTranslation,
|
||||
handleOpenFilterBox,
|
||||
dependencyFieldValue,
|
||||
setFieldValue,
|
||||
handleChange,
|
||||
handleBlur,
|
||||
errors,
|
||||
const handleOpenFilterBox = (event) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
|
||||
switch (filterParameters.datatype) {
|
||||
case "numeric":
|
||||
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;
|
||||
const renderField = () => {
|
||||
const commonProps = {
|
||||
column,
|
||||
filterParameters,
|
||||
defaultFilterTranslation,
|
||||
handleOpenFilterBox,
|
||||
dependencyFieldValue,
|
||||
setFieldValue,
|
||||
handleChange,
|
||||
handleBlur,
|
||||
errors,
|
||||
};
|
||||
|
||||
case "array":
|
||||
if (filterParameters.filterMode === "equals") {
|
||||
return <CustomSelectMultiple {...commonProps} />;
|
||||
switch (filterParameters.datatype) {
|
||||
case "numeric":
|
||||
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 <CustomTextField {...commonProps} />;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{renderField()}
|
||||
{Array.isArray(column.columnFilterModeOptions) && (
|
||||
<FilterOptionList
|
||||
column={column}
|
||||
anchorEl={anchorEl}
|
||||
setAnchorEl={setAnchorEl}
|
||||
filterParameters={filterParameters}
|
||||
filterType={filterParameters.filterMode}
|
||||
handleChange={handleChange}
|
||||
resetForm={resetForm}
|
||||
filterOption={column.columnFilterModeOptions}
|
||||
columnFilterModeOptionFa={columnFilterModeOptionFa}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
{renderField()}
|
||||
{Array.isArray(column.columnFilterModeOptions) && (
|
||||
<FilterOptionList
|
||||
column={column}
|
||||
anchorEl={anchorEl}
|
||||
setAnchorEl={setAnchorEl}
|
||||
filterParameters={filterParameters}
|
||||
filterType={filterParameters.filterMode}
|
||||
handleChange={handleChange}
|
||||
resetForm={resetForm}
|
||||
filterOption={column.columnFilterModeOptions}
|
||||
columnFilterModeOptionFa={columnFilterModeOptionFa}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default FilterBodyField;
|
||||
|
||||
@@ -4,36 +4,34 @@ import { IconButton, Tooltip, Badge } from "@mui/material";
|
||||
import useDataTable from "@/lib/hooks/useDataTable";
|
||||
|
||||
function FilterButton({ drawerState, setDrawerState }) {
|
||||
const { filterData } = useDataTable();
|
||||
const isValueEmpty = (value) => {
|
||||
if (Array.isArray(value)) {
|
||||
return value.length === 0 || value.every((v) => v === "");
|
||||
}
|
||||
return value === "" || value === null || value === undefined;
|
||||
};
|
||||
const filteredFilterData = Object.values(filterData).filter(
|
||||
(filter) => !isValueEmpty(filter.value),
|
||||
);
|
||||
return (
|
||||
<Tooltip title="فیلتر">
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
setDrawerState(!drawerState);
|
||||
}}
|
||||
aria-label="filter table"
|
||||
>
|
||||
<Badge
|
||||
badgeContent={filteredFilterData.length}
|
||||
color="primary"
|
||||
anchorOrigin={{
|
||||
vertical: "top",
|
||||
horizontal: "left",
|
||||
}}
|
||||
>
|
||||
<FilterAltIcon />
|
||||
</Badge>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
);
|
||||
const { filterData } = useDataTable();
|
||||
const isValueEmpty = (value) => {
|
||||
if (Array.isArray(value)) {
|
||||
return value.length === 0 || value.every((v) => v === "");
|
||||
}
|
||||
return value === "" || value === null || value === undefined;
|
||||
};
|
||||
const filteredFilterData = Object.values(filterData).filter((filter) => !isValueEmpty(filter.value));
|
||||
return (
|
||||
<Tooltip title="فیلتر">
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
setDrawerState(!drawerState);
|
||||
}}
|
||||
aria-label="filter table"
|
||||
>
|
||||
<Badge
|
||||
badgeContent={filteredFilterData.length}
|
||||
color="primary"
|
||||
anchorOrigin={{
|
||||
vertical: "top",
|
||||
horizontal: "left",
|
||||
}}
|
||||
>
|
||||
<FilterAltIcon />
|
||||
</Badge>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
export default FilterButton;
|
||||
|
||||
@@ -4,35 +4,35 @@ import { Box, Drawer } from "@mui/material";
|
||||
import FilterDrawer from "../../FilterDrawer";
|
||||
|
||||
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 (
|
||||
<>
|
||||
<FilterButton drawerState={open} setDrawerState={setOpen} />
|
||||
<Drawer
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
sx={{
|
||||
overflowY: "hidden",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
height: "100%",
|
||||
zIndex: "1300",
|
||||
}}
|
||||
>
|
||||
<Box sx={{ width: { xs: 300, sm: 450 } }}>
|
||||
{open && (
|
||||
<FilterDrawer
|
||||
defaultValues={filterData}
|
||||
setFilterData={setFilterData}
|
||||
closeDrawer={closeDrawer}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Drawer>
|
||||
</>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<FilterButton drawerState={open} setDrawerState={setOpen} />
|
||||
<Drawer
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
sx={{
|
||||
overflowY: "hidden",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
height: "100%",
|
||||
zIndex: "1300",
|
||||
}}
|
||||
>
|
||||
<Box sx={{ width: { xs: 300, sm: 450 } }}>
|
||||
{open && (
|
||||
<FilterDrawer
|
||||
defaultValues={filterData}
|
||||
setFilterData={setFilterData}
|
||||
closeDrawer={closeDrawer}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Drawer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default FilterCustom;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user