diff --git a/.gitignore b/.gitignore
index 662e667..1e62a6d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -30,4 +30,5 @@ cypress/screenshots
.env*
.env
.idea
-package-lock.json
\ No newline at end of file
+package-lock.json
+ecosystem.config.js
\ No newline at end of file
diff --git a/ecosystem.config.js.example b/ecosystem.config.js.example
new file mode 100644
index 0000000..6847796
--- /dev/null
+++ b/ecosystem.config.js.example
@@ -0,0 +1,18 @@
+module.exports = {
+ apps: [{
+ name: "crm_app",
+ script: 'node_modules/next/dist/bin/next', // cluster mode run with node only, not npm
+ args: 'start -p 3003',
+ exec_mode: "cluster", // default fork
+ instances: "10",
+ kill_timeout: 4000,
+ wait_ready: true,
+ autorestart: true,
+ watch: false,
+ max_memory_restart: "1G",
+ log_date_format: "YYYY-MM-DD HH:mm Z",
+ env_prod: {
+ APP_ENV: 'prod' // APP_ENV=prod
+ }
+ }],
+};
\ No newline at end of file
diff --git a/example.env.local b/example.env.local
index 922eec0..cc363e7 100644
--- a/example.env.local
+++ b/example.env.local
@@ -1,5 +1,5 @@
NEXT_PUBLIC_API_NAME = "CRM Dashboard"
-NEXT_PUBLIC_API_VERSION = "0.0.1"
+NEXT_PUBLIC_API_VERSION = "1.21.6"
NEXT_PUBLIC_DEFAULT_LANGUAGE = "fa"
NEXT_PUBLIC_DEFAULT_DIRECTION = "rtl"
@@ -10,4 +10,8 @@ NEXT_PUBLIC_BASE_URL = "https://crm.witel.ir"
NEXT_PUBLIC_SERVER_SOCKET_URL = "wss://crmws.witel.ir"
NEXT_PUBLIC_POWERED_BY_URL = "https://witel.ir"
-NODE_ENV = "development"
\ No newline at end of file
+#["NEXT_PUBLIC_HAS_WIDGET" , "NEXT_PUBLIC_HAS_NOTIFICATION"]
+NEXT_PUBLIC_HAS_VALUE=["NEXT_PUBLIC_HAS_WIDGET"]
+
+NODE_ENV = "development"
+
diff --git a/mocks/handler.js b/mocks/handler.js
index fbb803a..232c91e 100644
--- a/mocks/handler.js
+++ b/mocks/handler.js
@@ -1,13 +1,15 @@
-import {userHandler} from "./handlers/user";
-import {permissionsHandler} from "./handlers/permissions";
-import {rolesHandler} from "./handlers/roles";
-import {provincesHandler} from "./handlers/provinces";
-import {expertsHandler} from "./handlers/experts";
+import { userHandler } from "./handlers/user";
+import { permissionsHandler } from "./handlers/permissions";
+import { rolesHandler } from "./handlers/roles";
+import { provincesHandler } from "./handlers/provinces";
+import { expertsHandler } from "./handlers/experts";
+import { categoriesHandler } from "./handlers/categories";
export const handler = [
...userHandler,
...permissionsHandler,
...rolesHandler,
...provincesHandler,
- ...expertsHandler
-]
\ No newline at end of file
+ ...expertsHandler,
+ ...categoriesHandler,
+];
diff --git a/mocks/handlers/categories.js b/mocks/handlers/categories.js
new file mode 100644
index 0000000..6864331
--- /dev/null
+++ b/mocks/handlers/categories.js
@@ -0,0 +1,25 @@
+import { rest } from "msw";
+import { GET_CATEGORY } from "@/core/data/apiRoutes";
+
+export const categoriesHandler = [
+ rest.get(GET_CATEGORY, (req, res, ctx) => {
+ return res(
+ ctx.json({
+ data: [
+ {
+ category_id: 1,
+ category_name: "اطلاعات راه",
+ subcategory_id: 1,
+ subcategory_name: "اطلاعات باز و بسته",
+ },
+ {
+ category_id: 3,
+ category_name: "سایر",
+ subcategory_id: 2,
+ subcategory_name: "مزاحم",
+ },
+ ],
+ })
+ );
+ }),
+];
diff --git a/mocks/handlers/user.js b/mocks/handlers/user.js
index 3f1fbe3..7e84703 100644
--- a/mocks/handlers/user.js
+++ b/mocks/handlers/user.js
@@ -4,10 +4,14 @@ import {GET_SIDEBAR_NOTIFICATION, GET_USER, SET_USER_PASSWORD} from "@/core/data
export const userHandler = [
rest.get(GET_USER, (req, res, ctx) => {
return res(ctx.json({
- data: {
+ data:{
id: 10,
- permissions: [
- "manage_users"
+ full_name: "Witel Company",
+ position: "Software Engineer",
+ permissions:[
+ "manage_users",
+ "manage_roles",
+ "manage_boss"
],
}
}))
diff --git a/public/locales/fa/app.json b/public/locales/fa/app.json
index ae51967..7d89691 100644
--- a/public/locales/fa/app.json
+++ b/public/locales/fa/app.json
@@ -261,5 +261,13 @@
"call_history_not_found": "تاریخچه ای برای تماس دریافتی یافت نشد",
"call_history_error_fetching": "خطا در دریافت تاریخچه تماس دریافتی",
"show_more": "نمایش موارد بیشتر"
+ },
+ "CallAction": {
+ "call_history_of": "عملیات های مربوط به تماس",
+ "categories" : "موضوع ها",
+ "subcategories" : "زیر موضوع ها",
+ "description" : "توضیحات تماس (اختیاری)",
+ "text_field_label" : "توضیحات",
+ "text_field_palacholder" : "لطفا توضیحات خود را وارد نمایید"
}
}
diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallActions/ActionHeader/__test__/index.test.js b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallActions/ActionHeader/__test__/index.test.js
new file mode 100644
index 0000000..69f9b52
--- /dev/null
+++ b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallActions/ActionHeader/__test__/index.test.js
@@ -0,0 +1,43 @@
+import {render, screen, waitFor} from "@testing-library/react";
+import MockAppWithProviders from "../../../../../../../../../../../mocks/AppWithProvider";
+import {AnswersProvider} from "@/lib/callWidget/contexts/answers";
+import ActionHeader from "..";
+
+const tab = {
+ active: true,
+ active_category_id: 1,
+ phone_number : "09134849737"
+}
+
+describe('Action Header Button from Call Actions Categories', () => {
+ describe('Rendering', () => {
+ it('should see header text', async () => {
+ render(
+
+
+
+
+
+ )
+ const headingElement = screen.getByRole('heading', {
+ name: /\.\.\.\. عملیات های مربوط به تماس:/i
+ })
+ await waitFor(() => {
+ expect(headingElement).toBeInTheDocument()
+ })
+ });
+ it('should see phone number', async () => {
+ render(
+
+
+
+
+
+ )
+ const phone_numberElement = screen.queryByText(/09134849737/i)
+ await waitFor(() => {
+ expect(phone_numberElement).toBeInTheDocument()
+ })
+ });
+ });
+});
\ No newline at end of file
diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallActions/ActionHeader/index.jsx b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallActions/ActionHeader/index.jsx
new file mode 100644
index 0000000..97b4297
--- /dev/null
+++ b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallActions/ActionHeader/index.jsx
@@ -0,0 +1,25 @@
+import {useTranslations} from "next-intl";
+import {Stack, Typography} from "@mui/material";
+
+const ActionHeader = ({tab}) => {
+ const t = useTranslations();
+ return (
+
+
+ .... {t("CallAction.call_history_of")}:
+
+
+ {tab.phone_number} ....
+
+
+ )
+}
+
+export default ActionHeader
\ No newline at end of file
diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallActions/CallActionCategories/CallActionCategoriesButton.jsx b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallActions/CallActionCategories/CallActionCategoriesButton.jsx
new file mode 100644
index 0000000..40c2b18
--- /dev/null
+++ b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallActions/CallActionCategories/CallActionCategoriesButton.jsx
@@ -0,0 +1,25 @@
+import { Button, Grid } from "@mui/material";
+import useCategories from "@/lib/callWidget/hooks/useCategories";
+
+const CallActionCategoriesButton = ({ category, tab }) => {
+ const { setActiveCategoryID } = useCategories();
+
+ return (
+
+
+
+
+
+ );
+};
+export default CallActionCategoriesButton;
diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallActions/CallActionCategories/__test__/CallActionCategoriesButton.test.js b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallActions/CallActionCategories/__test__/CallActionCategoriesButton.test.js
new file mode 100644
index 0000000..195df02
--- /dev/null
+++ b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallActions/CallActionCategories/__test__/CallActionCategoriesButton.test.js
@@ -0,0 +1,51 @@
+import {fireEvent, render, screen, waitFor} from "@testing-library/react";
+import MockAppWithProviders from "../../../../../../../../../../../mocks/AppWithProvider";
+import {AnswersProvider} from "@/lib/callWidget/contexts/answers";
+import CallActionCategoriesButton from "../CallActionCategoriesButton";
+
+const category = {
+ category_id: 1,
+ category_name: "اطلاعات راه ها",
+}
+const tab = {
+ active: true,
+ active_category_id: 1,
+}
+
+describe('Call Action Categories Button from Call Actions Categories', () => {
+ describe('Rendering', () => {
+ it('should categories names', async () => {
+ render(
+
+
+
+
+
+ )
+ const categoryElement = screen.queryByText("اطلاعات راه ها")
+ await waitFor(() => {
+ expect(categoryElement).toBeInTheDocument()
+ })
+ });
+ });
+ describe("Behavior", ()=>{
+ it("Should change the color when button clecked",async()=>{
+ render(
+
+
+
+
+
+ )
+ const buttonElement = screen.getByRole('button', {
+ name: /اطلاعات راه ها/i
+ })
+
+ fireEvent.click(buttonElement);
+
+ await waitFor(() => {
+ expect(buttonElement).toHaveStyle('background-color: rgb(12, 31, 23)');
+ })
+ })
+ })
+})
\ No newline at end of file
diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallActions/CallActionCategories/__test__/index.test.js b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallActions/CallActionCategories/__test__/index.test.js
new file mode 100644
index 0000000..ee223ff
--- /dev/null
+++ b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallActions/CallActionCategories/__test__/index.test.js
@@ -0,0 +1,29 @@
+import { AnswersProvider } from "@/lib/callWidget/contexts/answers";
+import CallActionsCategories from "..";
+import MockAppWithProviders from "../../../../../../../../../../../mocks/AppWithProvider";
+import { render, screen, waitFor } from "@testing-library/react";
+
+const tab = {
+ active: true,
+ active_category_id: 1,
+ id : 1,
+ phone_number : "09134849737"
+}
+
+describe('Call Action Categories Button from Call Actions Categories', () => {
+ describe('Rendering', () => {
+ it('should see divider text', async () => {
+ render(
+
+
+
+
+
+ )
+ const divider_textElement = screen.queryByText("موضوع ها")
+ await waitFor(() => {
+ expect(divider_textElement).toBeInTheDocument()
+ })
+ });
+ });
+})
\ No newline at end of file
diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallActions/CallActionCategories/index.jsx b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallActions/CallActionCategories/index.jsx
new file mode 100644
index 0000000..dc814ec
--- /dev/null
+++ b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallActions/CallActionCategories/index.jsx
@@ -0,0 +1,22 @@
+import { Chip, Divider, Grid, Stack } from "@mui/material";
+import useCategories from "@/lib/callWidget/hooks/useCategories";
+import CallActionCategoriesButton from "./CallActionCategoriesButton";
+import { useTranslations } from "next-intl";
+
+const CallActionsCategories = ({ tab }) => {
+ const { categoryLists } = useCategories();
+ const t = useTranslations();
+ return (
+ <>
+
+
+
+ {categoryLists.map((category) => {
+ return ;
+ })}
+
+
+ >
+ );
+};
+export default CallActionsCategories;
diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallActions/CallActionDescription/__test__/index.test.js b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallActions/CallActionDescription/__test__/index.test.js
new file mode 100644
index 0000000..b7e0543
--- /dev/null
+++ b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallActions/CallActionDescription/__test__/index.test.js
@@ -0,0 +1,54 @@
+import {fireEvent, render, screen, waitFor} from "@testing-library/react";
+import MockAppWithProviders from "../../../../../../../../../../../mocks/AppWithProvider";
+import {AnswersProvider} from "@/lib/callWidget/contexts/answers";
+import CallActionDescription from "..";
+
+describe('Action Header Button from Call Actions Categories', () => {
+ describe('Rendering', () => {
+ it('should see placeholder text', async () => {
+ render(
+
+
+
+
+
+ )
+ const textElement = screen.getByPlaceholderText('لطفا توضیحات خود را وارد نمایید')
+ await waitFor(() => {
+ expect(textElement).toBeInTheDocument()
+ })
+ });
+ it('should see header text', async () => {
+ render(
+
+
+
+
+
+ )
+ const textElement = screen.getByText('توضیحات تماس (اختیاری)')
+ await waitFor(() => {
+ expect(textElement).toBeInTheDocument()
+ })
+ });
+ });
+ describe("Behavior", ()=>{
+ it("Should see what write in input", async()=>{
+ const setDescription = jest.fn();
+ render(
+
+
+
+
+
+ )
+ const textElement = screen.getByPlaceholderText('لطفا توضیحات خود را وارد نمایید')
+
+ fireEvent.change(textElement, {target : {value : "امین"}})
+
+ await waitFor(() => {
+ expect(textElement).toHaveValue("امین")
+ })
+ })
+ })
+});
\ No newline at end of file
diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallActions/CallActionDescription/index.jsx b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallActions/CallActionDescription/index.jsx
new file mode 100644
index 0000000..6a29324
--- /dev/null
+++ b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallActions/CallActionDescription/index.jsx
@@ -0,0 +1,27 @@
+import { Box, Chip, Divider, TextField } from "@mui/material"
+import { useTranslations } from "next-intl";
+
+const CallActionDescription = ({setDescription}) => {
+ const t = useTranslations();
+ return(
+ <>
+
+
+ {
+ setDescription(e.target.value)
+ }}
+ />
+
+ >
+ )
+}
+export default CallActionDescription
\ No newline at end of file
diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallActions/CallActionSubcategories/CallActionSubcategoriesButton.jsx b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallActions/CallActionSubcategories/CallActionSubcategoriesButton.jsx
new file mode 100644
index 0000000..2b4a479
--- /dev/null
+++ b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallActions/CallActionSubcategories/CallActionSubcategoriesButton.jsx
@@ -0,0 +1,14 @@
+import { Button, Grid } from "@mui/material";
+
+const CallActionSubcategoriesButton = ({ sub_category, tab, handleSubcategoryButton }) => {
+
+ return sub_category.category_id === tab.active_category_id ? (
+
+
+
+ ) : null;
+};
+export default CallActionSubcategoriesButton;
diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallActions/CallActionSubcategories/__test__/CallActionSubcategoriesButton.test.js b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallActions/CallActionSubcategories/__test__/CallActionSubcategoriesButton.test.js
new file mode 100644
index 0000000..718f7cd
--- /dev/null
+++ b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallActions/CallActionSubcategories/__test__/CallActionSubcategoriesButton.test.js
@@ -0,0 +1,37 @@
+import {render, screen, waitFor} from "@testing-library/react";
+import MockAppWithProviders from "../../../../../../../../../../../mocks/AppWithProvider";
+import {AnswersProvider} from "@/lib/callWidget/contexts/answers";
+import CallActionSubcategoriesButton from "../CallActionSubcategoriesButton";
+
+const sub_category = {
+ category_id: 1,
+ category_name: "اطلاعات راه",
+ subcategory_id: 1,
+ subcategory_name: "اطلاعات باز و بسته"
+};
+
+const tab = {
+ active: true,
+ active_category_id: 1,
+ date: new Date(),
+ id: 1,
+ phone_number: "09134849737"
+};
+
+describe('Call Action Subcategories Button from Call Actions Categories', () => {
+ describe('Rendering', () => {
+ it('should render subcategory name', async () => {
+ render(
+
+
+
+
+
+ );
+ const subCategoryElement = screen.queryByText(/اطلاعات باز و بسته/i);
+ await waitFor(() => {
+ expect(subCategoryElement).toBeInTheDocument();
+ });
+ });
+ });
+});
\ No newline at end of file
diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallActions/CallActionSubcategories/__test__/index.test.js b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallActions/CallActionSubcategories/__test__/index.test.js
new file mode 100644
index 0000000..2195bde
--- /dev/null
+++ b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallActions/CallActionSubcategories/__test__/index.test.js
@@ -0,0 +1,29 @@
+import { AnswersProvider } from "@/lib/callWidget/contexts/answers";
+import CallActionsSubcategories from "..";
+import MockAppWithProviders from "../../../../../../../../../../../mocks/AppWithProvider";
+import { render, screen, waitFor } from "@testing-library/react";
+
+const tab = {
+ active: true,
+ active_category_id: 1,
+ id : 1,
+ phone_number : "09134849737"
+}
+
+describe('Call Action Categories Button from Call Actions Categories', () => {
+ describe('Rendering', () => {
+ it('should see divider text', async () => {
+ render(
+
+
+
+
+
+ )
+ const divider_textElement = screen.queryByText("زیر موضوع ها")
+ await waitFor(() => {
+ expect(divider_textElement).toBeInTheDocument()
+ })
+ });
+ });
+})
\ No newline at end of file
diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallActions/CallActionSubcategories/index.jsx b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallActions/CallActionSubcategories/index.jsx
new file mode 100644
index 0000000..0b88cfa
--- /dev/null
+++ b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallActions/CallActionSubcategories/index.jsx
@@ -0,0 +1,22 @@
+import useCategories from "@/lib/callWidget/hooks/useCategories";
+import { Chip, Divider, Grid, Stack } from "@mui/material";
+import CallActionSubcategoriesButton from "@/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallActions/CallActionSubcategories/CallActionSubcategoriesButton";
+import { useTranslations } from "next-intl";
+
+const CallActionsSubcategories = ({ tab, handleSubcategoryButton }) => {
+ const { subCategoryLists } = useCategories();
+ const t = useTranslations();
+ return (
+ <>
+
+
+
+ {subCategoryLists.map((sub_category, index) => {
+ return ;
+ })}
+
+
+ >
+ );
+};
+export default CallActionsSubcategories;
diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallActions/index.jsx b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallActions/index.jsx
index d1a68f7..082d701 100644
--- a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallActions/index.jsx
+++ b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallActions/index.jsx
@@ -1,7 +1,50 @@
-const CallActions = ({tab}) => {
+import CallActionsCategories from "./CallActionCategories";
+import CallActionsSubcategories from "./CallActionSubcategories";
+import ActionHeader from "./ActionHeader";
+import CallActionDescription from "./CallActionDescription";
+import {CALL_ACTION} from "@/core/data/apiRoutes";
+import useRequest from "@/lib/app/hooks/useRequest";
+import useAnswers from "@/lib/callWidget/hooks/useAnswers";
+import {useState} from "react";
+import {Stack} from "@mui/material";
+
+function CallActions({tab}) {
+ const [description, setDescription] = useState('')
+ const requestServer = useRequest({auth: true});
+ const {closeAnswerTab} = useAnswers();
+
+ const handleSubcategoryButton = (category_id, subcategory_id) => {
+ requestServer(`${CALL_ACTION}/${tab.id}`, "post", {
+ data: {
+ category_id,
+ subcategory_id,
+ description
+ },
+ success: {
+ notification: {show: false},
+ },
+ })
+ .then(() => {
+ closeAnswerTab(tab.id);
+ })
+ .catch(() => {
+ });
+ };
+
+
return (
- <>CallActions - {tab.id}>
- )
+
+
+
+
+
+
+
+
+ );
}
-export default CallActions
+export default CallActions;
diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallHistory/index.jsx b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallHistory/index.jsx
index c83bc49..1e940cb 100644
--- a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallHistory/index.jsx
+++ b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallHistory/index.jsx
@@ -25,7 +25,7 @@ const CallHistory = ({tab}) => {
overflow: "hidden"
}}>
-
+
{errorHistoryList ? (
) : isLoadingHistoryList ? (
diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/index.jsx b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/index.jsx
index 8fc1d87..be7e9d2 100644
--- a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/index.jsx
+++ b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/index.jsx
@@ -7,7 +7,7 @@ const CallTabPanel = ({tab}) => {
-
+
diff --git a/src/components/layouts/Dashboard/Sidebar/SidebarDrawer.jsx b/src/components/layouts/Dashboard/Sidebar/SidebarDrawer.jsx
index 33348a5..3f1d449 100644
--- a/src/components/layouts/Dashboard/Sidebar/SidebarDrawer.jsx
+++ b/src/components/layouts/Dashboard/Sidebar/SidebarDrawer.jsx
@@ -4,7 +4,7 @@ import SidebarList from "./SidebarList";
import useUser from "@/lib/app/hooks/useUser";
const SidebarDrawer = ({handleDrawerToggle}) => {
- const {user} = useUser()
+ const {user} = useUser();
const t = useTranslations();
return (
<>
diff --git a/src/components/layouts/Dashboard/Sidebar/SidebarList.jsx b/src/components/layouts/Dashboard/Sidebar/SidebarList.jsx
index 040b455..1e570f5 100644
--- a/src/components/layouts/Dashboard/Sidebar/SidebarList.jsx
+++ b/src/components/layouts/Dashboard/Sidebar/SidebarList.jsx
@@ -10,29 +10,15 @@ function reducer(state, action) {
case "COLLAPSE_MENU":
return state.map((itemsArr) =>
itemsArr.map((item) =>
- action.key == item.key
- ? {...item, showSubItem: !item.showSubItem}
- : item
+ action.key === item.key ? { ...item, showSubItem: !item.showSubItem } : item
)
);
case "SELECTED":
return state.map((itemsArr) =>
- itemsArr.map((item) => {
- if (item.type === "page") {
- if (action.route === item.route)
- return {...item, selected: true}
- else
- return {...item, selected: false}
- }
- return {...item}
- // item.subItem.map((subitem) => {
- // if (action.route === subitem.route)
- // return {...item, showSubItem: true, subItem: {...subitem, selected: true}}
- // else
- // return {...item, showSubItem: false, subItem: {...subitem, selected: false}}
- // })
- }
- )
+ itemsArr.map((item) => ({
+ ...item,
+ selected: item.type === "page" && action.route === item.route,
+ }))
);
default:
throw new Error();
@@ -54,7 +40,7 @@ export default function SidebarList({handleDrawerToggle}) {
{itemArr.map((item) =>
- {(user.permissions.includes(item.permission) || item.permission === "all") &&
+ {(user?.permissions?.includes(item.permission) || item.permission === "all") &&
{
const t = useTranslations();
- const {notification_count} = useNotification()
+ const { notification_count } = useNotification();
+
+ const hasSubItems = item.type === "menu" && item.subItem && item.subItem.length > 0;
+
+ const renderBadge = () => (
+
+
+
+ );
return (
<>
-
-
-
- }>
+
{
},
})}
onClick={() => {
- if (item.type == "menu") {
- dispatch({type: "COLLAPSE_MENU", key: item.key});
+ if (hasSubItems) {
+ dispatch({ type: "COLLAPSE_MENU", key: item.key });
}
- handleDrawerToggle();
}}
sx={{
minHeight: 48,
@@ -66,9 +69,7 @@ const SidebarListItem = ({item, dispatch, handleDrawerToggle}) => {
}
/>
-
- {item.type == "menu" &&
- (item.showSubItem ? : )}
+ {hasSubItems && (item.showSubItem ? : )}
{item.subItem && (
diff --git a/src/components/layouts/Dashboard/Sidebar/SidebarListSubItem.jsx b/src/components/layouts/Dashboard/Sidebar/SidebarListSubItem.jsx
index c9c8a36..1803b96 100644
--- a/src/components/layouts/Dashboard/Sidebar/SidebarListSubItem.jsx
+++ b/src/components/layouts/Dashboard/Sidebar/SidebarListSubItem.jsx
@@ -15,25 +15,27 @@ import useNotification from "@/lib/app/hooks/useNotification";
const SidebarListSubItem = ({item, handleDrawerToggle}) => {
const t = useTranslations();
- const {notification_count} = useNotification()
+ const {notification_count} = useNotification();
+
+ const renderBadge = (subitem) => (
+
+
+
+ );
return (
- {item.subItem.map((subitem, index) => (
-
-
-
- }>
+ {item.subItem.map((subitem) => (
+
{
+ describe('Rendering', () => {
+ it('renders system name in sidebar', async () => {
+ render();
+ await waitFor(()=>{
+ const systemName = screen.getByRole('heading', { level: 6 });
+ expect(systemName).toHaveTextContent('سامانه CRM');
+ })
+ });
+ it('renders user full name and position in sidebar', async () => {
+ localStorage.setItem("_token", 'token');
+ render();
+ await waitFor(()=>{
+ const userName = screen.queryByText(/Witel Company | Software Engineer/i);
+ expect(userName).toBeInTheDocument();
+ })
+ });
+ })
+})
diff --git a/src/components/layouts/Dashboard/Sidebar/__test__/SidebarList.test.js b/src/components/layouts/Dashboard/Sidebar/__test__/SidebarList.test.js
new file mode 100644
index 0000000..1897c9b
--- /dev/null
+++ b/src/components/layouts/Dashboard/Sidebar/__test__/SidebarList.test.js
@@ -0,0 +1,16 @@
+import {render, screen, waitFor} from "@testing-library/react";
+import MockAppWithProviders from "../../../../../../mocks/AppWithProvider";
+import SidebarList from "@/components/layouts/Dashboard/Sidebar/SidebarList";
+
+describe('SidebarList', () => {
+ describe('Rendering', () => {
+ it('renders sidebar items based on user permissions', async () => {
+ localStorage.setItem("_token", 'token');
+ render();
+ await waitFor(()=>{
+ const sidebarItem = screen.getByText('مدیریت نقش ها');
+ expect(sidebarItem).toBeInTheDocument();
+ })
+ });
+ })
+})
diff --git a/src/components/layouts/Dashboard/Sidebar/__test__/SidebarListItem.test.js b/src/components/layouts/Dashboard/Sidebar/__test__/SidebarListItem.test.js
new file mode 100644
index 0000000..18313c5
--- /dev/null
+++ b/src/components/layouts/Dashboard/Sidebar/__test__/SidebarListItem.test.js
@@ -0,0 +1,125 @@
+import AccessibilityIcon from "@mui/icons-material/Accessibility";
+import {fireEvent, render, screen} from "@testing-library/react";
+import SidebarListItem from "@/components/layouts/Dashboard/Sidebar/SidebarListItem";
+import MockAppWithProviders from "../../../../../../mocks/AppWithProvider";
+
+describe('SidebarListItemComponent', () => {
+ describe('Rendering', () => {
+ it('should show sidebarlistitem name in sidebar', () => {
+ const sampleItem={
+ key: "sidebar.role-management",
+ name : "role_management",
+ type: "page",
+ route: "/dashboard/role-management",
+ icon: ,
+ selected: false,
+ permission: "manage_roles",
+ }
+ render();
+ expect(screen.getByText('مدیریت نقش ها')).toBeInTheDocument();
+ });
+ it('should show sidebarlistitem icon in sidebar', () => {
+ const sampleItem={
+ key: "sidebar.role-management",
+ name : "role_management",
+ type: "page",
+ route: "/dashboard/role-management",
+ icon: ,
+ selected: false,
+ permission: "manage_roles",
+ }
+ render();
+ const iconElement = screen.getByLabelText("Sidebar Icon");
+ expect(iconElement).toBeInTheDocument();
+ });
+ it('should have selected class for subitem', () => {
+ const sampleItem={
+ key: "sidebar.role-management",
+ name : "role_management",
+ type: "page",
+ route: "/dashboard/role-management",
+ icon: ,
+ selected: true,
+ permission: "manage_roles",
+ }
+ render();
+ const element = screen.getByText('مدیریت نقش ها');
+ expect(element.parentElement.parentElement).toHaveClass('Mui-selected');
+ });
+
+ it('should not have selected class for subitem', () => {
+ const sampleItem={
+ key: "sidebar.role-management",
+ name : "role_management",
+ type: "menu",
+ route: "/dashboard/role-management",
+ icon: ,
+ selected: false,
+ permission: "manage_roles",
+ }
+ render();
+ const element = screen.getByText('مدیریت نقش ها');
+ expect(element.parentElement.parentElement).not.toHaveClass('Mui-selected');
+ });
+
+ it('should show sidebarlistitem secondary in sidebar', () => {
+ const sampleItem={
+ key: "sidebar.role-management",
+ name : "role_management",
+ secondary: "between",
+ type: "menu",
+ route: "/dashboard/role-management",
+ icon: ,
+ selected: false,
+ permission: "manage_roles",
+ }
+ render();
+ expect(screen.getByText('میان')).toBeInTheDocument();
+ });
+
+ it('should not show sidebarlistitem secondary in sidebar', () => {
+ const sampleItem={
+ key: "sidebar.role-management",
+ name : "role_management",
+ type: "menu",
+ route: "/dashboard/role-management",
+ icon: ,
+ selected: false,
+ permission: "manage_roles",
+ }
+ render();
+ expect(screen.queryByText('secondry')).not.toBeInTheDocument();
+ });
+ })
+ describe('Behavior', () => {
+ it('should call dispatch when hasSubItems is true and item is clicked', () => {
+ const dispatchMock = jest.fn();
+ const sampleItem={
+ key: "sidebar.role-management",
+ name : "role_management",
+ type: "menu",
+ route: "/dashboard/role-management",
+ icon: ,
+ selected: false,
+ permission: "manage_roles",
+ subItem :[
+ {
+ key: "greaterThan",
+ route: "/dashboard/role-management",
+ secondary: "between",
+ name : "sidebar.admin",
+ selected: true,
+ icon: ,
+ }
+ ]
+ }
+ render();
+ const listItem = screen.getByText('مدیریت نقش ها');
+ fireEvent.click(listItem);
+ expect(dispatchMock).toHaveBeenCalledWith({
+ type: 'COLLAPSE_MENU',
+ key: "sidebar.role-management",
+ });
+ });
+ })
+})
\ No newline at end of file
diff --git a/src/components/layouts/Dashboard/Sidebar/__test__/SidebarListSubitem.test.js b/src/components/layouts/Dashboard/Sidebar/__test__/SidebarListSubitem.test.js
new file mode 100644
index 0000000..9551937
--- /dev/null
+++ b/src/components/layouts/Dashboard/Sidebar/__test__/SidebarListSubitem.test.js
@@ -0,0 +1,103 @@
+import {render, screen} from "@testing-library/react";
+import MockAppWithProviders from "../../../../../../mocks/AppWithProvider";
+import SidebarListSubItem from "@/components/layouts/Dashboard/Sidebar/SidebarListSubItem";
+import AccessibilityIcon from "@mui/icons-material/Accessibility";
+
+describe('SidebarListSubItemComponent', () => {
+ describe('Rendering', () => {
+ const sampleItem={
+ showSubItem:true,
+ key: "sidebar.role-management",
+ name : "role_management",
+ type: "menu",
+ route: "/dashboard/role-management",
+ icon: ,
+ selected: false,
+ permission: "manage_roles",
+ subItem :[
+ {
+ key: "greaterThan",
+ route: "/dashboard/role-management",
+ secondary: "between",
+ name : "sidebar.admin",
+ selected: true,
+ icon: ,
+ }
+ ]
+ }
+ it('should show subitem name in sidebar', () => {
+ render();
+ expect(screen.getByText('بزرگتر از')).toBeInTheDocument();
+
+ });
+ it('should not show subitem name in sidebar', () => {
+ const sampleItem={
+ showSubItem:false,
+ key: "sidebar.role-management",
+ name : "role_management",
+ type: "menu",
+ route: "/dashboard/role-management",
+ icon: ,
+ selected: false,
+ permission: "manage_roles",
+ subItem :[
+ {
+ key: "greaterThan",
+ route: "/dashboard/role-management",
+ secondary: "between",
+ name : "sidebar.admin",
+ selected: false,
+ icon: ,
+ }
+ ]
+ }
+ render();
+ expect(screen.queryByText('مدیریت نقش ها')).not.toBeInTheDocument();
+ });
+ it('should show subitem secondary in sidebar', () => {
+ render();
+ expect(screen.getByText('میان')).toBeInTheDocument();
+ });
+
+ it('should not show subitem secondary in sidebar', () => {
+ render();
+ expect(screen.queryByText('secondry')).not.toBeInTheDocument();
+ });
+ it('should have selected class for subitem', () => {
+ render();
+ const parentElement = screen.queryByText('بزرگتر از').parentElement.parentElement;
+ expect(parentElement).toHaveClass('Mui-selected');
+ });
+
+ it('should not have selected class for subitem', () => {
+ const sampleItem={
+ showSubItem:true,
+ key: "sidebar.role-management",
+ name : "role_management",
+ type: "menu",
+ route: "/dashboard/role-management",
+ icon: ,
+ selected: false,
+ permission: "manage_roles",
+ subItem :[
+ {
+ key: "greaterThan",
+ route: "/dashboard/role-management",
+ secondary: "between",
+ name : "sidebar.admin",
+ selected: false,
+ icon: ,
+ }
+ ]
+ }
+ render();
+ const parentElement = screen.queryByText('بزرگتر از').parentElement.parentElement;
+ expect(parentElement).not.toHaveClass('Mui-selected');
+ });
+ it('should show subitem icon', () => {
+ render();
+ const iconElement = screen.getByLabelText("Sidebar Icon");
+ expect(iconElement).toBeInTheDocument();
+ });
+ })
+})
\ No newline at end of file
diff --git a/src/components/layouts/Dashboard/Sidebar/__test__/index.test.js b/src/components/layouts/Dashboard/Sidebar/__test__/index.test.js
new file mode 100644
index 0000000..740c384
--- /dev/null
+++ b/src/components/layouts/Dashboard/Sidebar/__test__/index.test.js
@@ -0,0 +1,17 @@
+import {render, screen, act} from "@testing-library/react";
+import MockAppWithProviders from "../../../../../../mocks/AppWithProvider";
+import Sidebar from "@/components/layouts/Dashboard/Sidebar";
+
+
+describe('Sidebar', () => {
+ describe('Rendering', () => {
+ it('Mobile Drawer is displayed when mobileOpen is true', () => {
+ const props={
+ mobileOpen: true,
+ };
+ render();
+ const mobileDrawer = screen.getByTestId('mobile-drawer');
+ expect(mobileDrawer).toBeVisible();
+ });
+ })
+})
diff --git a/src/components/layouts/Dashboard/Sidebar/index.jsx b/src/components/layouts/Dashboard/Sidebar/index.jsx
index 2498dfd..c0b6d6d 100644
--- a/src/components/layouts/Dashboard/Sidebar/index.jsx
+++ b/src/components/layouts/Dashboard/Sidebar/index.jsx
@@ -9,6 +9,7 @@ const Sidebar = (props) => {
aria-label="mailbox folders"
>
{
- setUpdateTime(moment().format("HH:mm | jYYYY/jM/jD"));
+ setUpdateTime(moment().format("HH:mm | jYYYY/jMM/jDD"));
}, [isValidating, languageApp]);
return (
diff --git a/src/core/data/apiRoutes.js b/src/core/data/apiRoutes.js
index 127d1df..f949dac 100644
--- a/src/core/data/apiRoutes.js
+++ b/src/core/data/apiRoutes.js
@@ -29,21 +29,16 @@ export const GET_USER = BASE_URL + "/api/profile/info";
//user data
//sidebar notification
-export const GET_SIDEBAR_NOTIFICATION = BASE_URL + "/dashboard/notification"
+export const GET_SIDEBAR_NOTIFICATION = BASE_URL + "/dashboard/notification";
//sidebar notification
// role management
-export const GET_ROLES =
- BASE_URL + "/api/roles"
-export const ADD_ROLE =
- BASE_URL + "/api/roles"
-export const UPDATE_ROLE =
- BASE_URL + "/api/roles"
-export const DELETE_ROLE =
- BASE_URL + "/api/roles"
+export const GET_ROLES = BASE_URL + "/api/roles";
+export const ADD_ROLE = BASE_URL + "/api/roles";
+export const UPDATE_ROLE = BASE_URL + "/api/roles";
+export const DELETE_ROLE = BASE_URL + "/api/roles";
-export const GET_PERMISSIONS_LIST =
- BASE_URL + "/api/permissions/list"
+export const GET_PERMISSIONS_LIST = BASE_URL + "/api/permissions/list";
//role management
//expert management
@@ -51,4 +46,9 @@ export const GET_EXPERTS = BASE_URL + "/api/users";
export const ADD_EXPERT = BASE_URL + "/api/users";
export const UPDATE_EXPERT = BASE_URL + "/api/users";
export const DELETE_EXPERT = BASE_URL + "/api/users";
-//expert management
\ No newline at end of file
+//expert management
+
+//call action history
+export const GET_CATEGORY = BASE_URL + "/api/categories";
+export const CALL_ACTION = BASE_URL + "/api/calls";
+//call action history
diff --git a/src/layouts/DashboardLayout.jsx b/src/layouts/DashboardLayout.jsx
index 37554cd..72344d6 100644
--- a/src/layouts/DashboardLayout.jsx
+++ b/src/layouts/DashboardLayout.jsx
@@ -1,11 +1,15 @@
import Dashboard from "src/components/layouts/Dashboard";
+import AppPermission from "@/middlewares/AppPermission";
+import CallWidget from "@/components/layouts/Dashboard/CallWidget";
const DashboardLayout = (props) => {
-
+ const permissions = ["NEXT_PUBLIC_HAS_WIDGET"];
return (
<>
- {/**/}
+
+
+
>
);
};
diff --git a/src/lib/app/contexts/socket.jsx b/src/lib/app/contexts/socket.jsx
index 68826e1..363faca 100644
--- a/src/lib/app/contexts/socket.jsx
+++ b/src/lib/app/contexts/socket.jsx
@@ -17,6 +17,7 @@ export const SocketProvider = ({children}) => {
const {directionApp} = useDirection();
const socket = useMemo(() => io(process.env.NEXT_PUBLIC_SERVER_SOCKET_URL, {
autoConnect: false,
+ transports: ['websocket'],
auth: {
token: ""
}
diff --git a/src/lib/app/hooks/useNotification.jsx b/src/lib/app/hooks/useNotification.jsx
index e1cb932..6e80bca 100644
--- a/src/lib/app/hooks/useNotification.jsx
+++ b/src/lib/app/hooks/useNotification.jsx
@@ -1,8 +1,12 @@
import useSWR from 'swr'
import {GET_SIDEBAR_NOTIFICATION} from "@/core/data/apiRoutes";
import useRequest from "@/lib/app/hooks/useRequest";
+const GLOBAL_HAS_VALUE = process.env.NEXT_PUBLIC_HAS_VALUE;
+const hasPermissionsValue = GLOBAL_HAS_VALUE ? JSON.parse(GLOBAL_HAS_VALUE) : [];
+const isNotificationEnabled = hasPermissionsValue.includes("NEXT_PUBLIC_HAS_NOTIFICATION");
const useNotification = () => {
+
const requestServer = useRequest({auth: true, notification: false})
//swr config
@@ -13,7 +17,7 @@ const useNotification = () => {
})
};
- const {data, mutate} = useSWR(GET_SIDEBAR_NOTIFICATION, fetcher)
+ const {data, mutate} = useSWR( isNotificationEnabled ? GET_SIDEBAR_NOTIFICATION : '', fetcher , {keepPreviousData:true})
//swr config
// render data
diff --git a/src/lib/callWidget/contexts/answers.jsx b/src/lib/callWidget/contexts/answers.jsx
index b5c9c0b..96a150c 100644
--- a/src/lib/callWidget/contexts/answers.jsx
+++ b/src/lib/callWidget/contexts/answers.jsx
@@ -1,31 +1,86 @@
-import {createContext, useReducer, useState} from "react";
+import { createContext, useEffect, useReducer, useState } from "react";
+import { GET_CATEGORY } from "@/core/data/apiRoutes";
+import useRequest from "@/lib/app/hooks/useRequest";
const reducer = (state, action) => {
switch (action.type) {
case "CHANGE_ACTIVE_TAB":
- return state.map((answer, index) => action.newValue === index ? {...answer, active: true} : {
- ...answer,
- active: false
- });
+ return state.map((answer, index) =>
+ action.newValue === index
+ ? { ...answer, active: true }
+ : {
+ ...answer,
+ active: false,
+ }
+ );
case "CHANGE_LIST":
- return [...action.newList]
+ return [...action.newList];
+ case "CHANGE_ACTIVE_ID":
+ return state.map((answer, index) =>
+ action.tab_id === answer.id
+ ? {
+ ...answer,
+ active_category_id: action.category_id,
+ }
+ : { ...answer }
+ );
default:
return state;
}
-}
-export const AnswersContext = createContext()
-export const AnswersProvider = ({children}) => {
- const [openCallDialog, setOpenCallDialog] = useState(false)
- const [activeTab, setActiveTab] = useState(0)
+};
+
+const submission = (handleCategories, operation) => {
+ switch (operation.type) {
+ case "SET_CATEGORIES":
+ return { ...handleCategories, categoryLists: operation.categoriesByAPI };
+ case "SET_SUBCATEGORIES":
+ return { ...handleCategories, subCategoryLists: operation.subCategoriesByAPI };
+ default:
+ return handleCategories;
+ }
+};
+
+export const AnswersContext = createContext();
+export const AnswersProvider = ({ children }) => {
+ const requestServer = useRequest({ auth: true, notification: false });
+ const [openCallDialog, setOpenCallDialog] = useState(false);
+ const [activeTab, setActiveTab] = useState(0);
+ const [handleCategories, operation] = useReducer(submission, {
+ categoryLists: [],
+ subCategoryLists: [],
+ });
const [state, dispatch] = useReducer(reducer, []);
- return {children}
-}
+ useEffect(() => {
+ requestServer(GET_CATEGORY, "get")
+ .then(({ data }) => {
+ const subCategoriesByAPI = data.data;
+ const categoriesByAPI = subCategoriesByAPI.reduce((accumulator, currentValue) => {
+ if (!accumulator.find((item) => item.category_id === currentValue.category_id)) {
+ accumulator.push(currentValue);
+ }
+ return accumulator;
+ }, []);
+ operation({ type: "SET_CATEGORIES", categoriesByAPI });
+ operation({ type: "SET_SUBCATEGORIES", subCategoriesByAPI });
+ })
+ .catch(() => {});
+ }, []);
+
+ return (
+
+ {children}
+
+ );
+};
diff --git a/src/lib/callWidget/contexts/categories.jsx b/src/lib/callWidget/contexts/categories.jsx
new file mode 100644
index 0000000..533dfc3
--- /dev/null
+++ b/src/lib/callWidget/contexts/categories.jsx
@@ -0,0 +1,59 @@
+import { createContext, useCallback, useEffect, useReducer } from "react";
+import useRequest from "@/lib/app/hooks/useRequest";
+import { GET_CATEGORY } from "@/core/data/apiRoutes";
+
+const reducer = (state, action) => {
+ switch (action.type) {
+ case "CHANGE_ACTIVE_ID":
+ return { ...state, active_id: action.id };
+ case "SET_CATEGORIES":
+ return { ...state, categoryLists: action.cat };
+ case "SET_SUBCATEGORIES":
+ return { ...state, subCategoryLists: action.subCat };
+ default:
+ return state;
+ }
+};
+export const CategoriesContext = createContext();
+
+export const CategoriesProvider = ({ children }) => {
+ const requestServer = useRequest({ auth: true, notification: false });
+ const [state, dispatch] = useReducer(reducer, {
+ categoryLists: [],
+ subCategoryLists: [],
+ active_id: null,
+ });
+
+ useEffect(() => {
+ requestServer(GET_CATEGORY, "get")
+ .then(({ data }) => {
+ const subCat = data.data;
+ const cat = subCat.reduce((accumulator, currentValue) => {
+ if (!accumulator.find((item) => item.category_id === currentValue.category_id)) {
+ accumulator.push(currentValue);
+ }
+ return accumulator;
+ }, []);
+ dispatch({ type: "SET_CATEGORIES", cat });
+ dispatch({ type: "SET_SUBCATEGORIES", subCat });
+ })
+ .catch(() => {});
+ }, []);
+
+ const setCategoryID = useCallback((id) => {
+ dispatch({ type: "CHANGE_ACTIVE_ID", id });
+ });
+
+ return (
+
+ {children}
+
+ );
+};
diff --git a/src/lib/callWidget/hooks/useAnswers.jsx b/src/lib/callWidget/hooks/useAnswers.jsx
index 68f8b46..149b1d3 100644
--- a/src/lib/callWidget/hooks/useAnswers.jsx
+++ b/src/lib/callWidget/hooks/useAnswers.jsx
@@ -1,38 +1,46 @@
-import {useContext} from "react";
-import {AnswersContext} from "@/lib/callWidget/contexts/answers";
+import { useContext } from "react";
+import { AnswersContext } from "@/lib/callWidget/contexts/answers";
const useAnswers = () => {
- const {state, dispatch, activeTab, setActiveTab} = useContext(AnswersContext)
+ const { state, dispatch, activeTab, setActiveTab } = useContext(AnswersContext);
const changeActiveTabAnswers = (newValue) => {
- dispatch({type: 'CHANGE_ACTIVE_TAB', newValue})
- setActiveTab(newValue)
- }
+ dispatch({ type: "CHANGE_ACTIVE_TAB", newValue });
+ setActiveTab(newValue);
+ };
const newAnswerTab = (data) => {
const newAnswer = {
id: data.id,
phone_number: data.phone_number,
date: new Date(),
- active: true
- }
- const newList = [...state, newAnswer]
- dispatch({type: 'CHANGE_LIST', newList})
- changeActiveTabAnswers(newList.length - 1)
- }
+ active: true,
+ active_category_id: null,
+ };
+ const newList = [...state, newAnswer];
+ dispatch({ type: "CHANGE_LIST", newList });
+ changeActiveTabAnswers(newList.length - 1);
+ };
const closeAnswerTab = (id) => {
- const index = state.findIndex(answer => answer.id === id)
- const newIndex = index === 0 ? 0 : index - 1
- const newList = [...state]
+ const index = state.findIndex((answer) => answer.id === id);
+ const newIndex = index === 0 ? 0 : index - 1;
+ const newList = [...state];
newList.splice(index, 1);
if (newList.length !== 0) {
- newList[newIndex].active = true
- setActiveTab(newIndex)
+ newList[newIndex].active = true;
+ setActiveTab(newIndex);
}
- dispatch({type: 'CHANGE_LIST', newList})
- }
+ dispatch({ type: "CHANGE_LIST", newList });
+ };
- return {answersList: state, activeTab, setActiveTab, changeActiveTabAnswers, closeAnswerTab, newAnswerTab}
-}
+ return {
+ answersList: state,
+ activeTab,
+ setActiveTab,
+ changeActiveTabAnswers,
+ closeAnswerTab,
+ newAnswerTab,
+ };
+};
-export default useAnswers
\ No newline at end of file
+export default useAnswers;
diff --git a/src/lib/callWidget/hooks/useCallerHistory.jsx b/src/lib/callWidget/hooks/useCallerHistory.jsx
index 68ee6d0..810209d 100644
--- a/src/lib/callWidget/hooks/useCallerHistory.jsx
+++ b/src/lib/callWidget/hooks/useCallerHistory.jsx
@@ -29,8 +29,8 @@ const useCallerHistory = (call_id, phone_number, max_size) => {
return {
firstItemOfHistory: validData.length === 0 ? false : validData[0],
historyList: validData.length < 2 ? false : validData.slice(1),
- isLoadingHistoryList: true,
- errorHistoryList: false
+ isLoadingHistoryList: isLoading,
+ errorHistoryList: !data
}
};
diff --git a/src/lib/callWidget/hooks/useCategories.jsx b/src/lib/callWidget/hooks/useCategories.jsx
new file mode 100644
index 0000000..423bd67
--- /dev/null
+++ b/src/lib/callWidget/hooks/useCategories.jsx
@@ -0,0 +1,17 @@
+import { useContext } from "react";
+import { AnswersContext } from "@/lib/callWidget/contexts/answers";
+
+const useCategories = () => {
+ const { categoryLists, subCategoryLists, active_category_id, dispatch } = useContext(AnswersContext);
+
+ const setActiveCategoryID = (tab_id, category_id) => {
+ dispatch({ type: "CHANGE_ACTIVE_ID", tab_id, category_id });
+ };
+ return {
+ categoryLists,
+ subCategoryLists,
+ active_category_id,
+ setActiveCategoryID,
+ };
+};
+export default useCategories;
diff --git a/src/middlewares/AppPermission.jsx b/src/middlewares/AppPermission.jsx
new file mode 100644
index 0000000..8f93bd3
--- /dev/null
+++ b/src/middlewares/AppPermission.jsx
@@ -0,0 +1,8 @@
+const GLOBAL_HAS_VALUE = process.env.NEXT_PUBLIC_HAS_VALUE;
+const hasPermissionsValue = GLOBAL_HAS_VALUE ? JSON.parse(GLOBAL_HAS_VALUE) : [];
+
+const AppPermission = ({ children , permissions }) => {
+ return permissions.some(permission => hasPermissionsValue.includes(permission)) ? <>{children}> : null;
+};
+
+export default AppPermission;
diff --git a/src/middlewares/__test__/AppPermission.test.js b/src/middlewares/__test__/AppPermission.test.js
new file mode 100644
index 0000000..82377ba
--- /dev/null
+++ b/src/middlewares/__test__/AppPermission.test.js
@@ -0,0 +1,26 @@
+import {render, screen, waitFor} from '@testing-library/react';
+import MockAppWithProviders from "../../../mocks/AppWithProvider";
+import AppPermission from "@/middlewares/AppPermission";
+
+describe('AppPermission From middlewares', () => {
+ describe('Behavior', () => {
+ it('show related child if permission exist', async () => {
+
+ const requiredPermissions = ["NEXT_PUBLIC_HAS_WIDGET"];
+ render({'children'});
+
+ await waitFor(()=>{
+ expect(screen.queryByText('children')).toBeInTheDocument();
+ })
+ });
+ it('do not show related child if permission does not exist', async () => {
+
+ const requiredPermissions = ["MISSING_PERMISSION"];
+ render();
+
+ await waitFor(() => {
+ expect(screen.queryByText('children')).not.toBeInTheDocument();
+ });
+ });
+ });
+});