CFE-6 Merge branch 'develop' into 'bugfix/CFE-6_fix_toas_containers'
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -31,3 +31,4 @@ cypress/screenshots
|
|||||||
.env
|
.env
|
||||||
.idea
|
.idea
|
||||||
package-lock.json
|
package-lock.json
|
||||||
|
ecosystem.config.js
|
||||||
18
ecosystem.config.js.example
Normal file
18
ecosystem.config.js.example
Normal file
@@ -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
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
};
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
NEXT_PUBLIC_API_NAME = "CRM Dashboard"
|
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_LANGUAGE = "fa"
|
||||||
NEXT_PUBLIC_DEFAULT_DIRECTION = "rtl"
|
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_SERVER_SOCKET_URL = "wss://crmws.witel.ir"
|
||||||
NEXT_PUBLIC_POWERED_BY_URL = "https://witel.ir"
|
NEXT_PUBLIC_POWERED_BY_URL = "https://witel.ir"
|
||||||
|
|
||||||
|
#["NEXT_PUBLIC_HAS_WIDGET" , "NEXT_PUBLIC_HAS_NOTIFICATION"]
|
||||||
|
NEXT_PUBLIC_HAS_VALUE=["NEXT_PUBLIC_HAS_WIDGET"]
|
||||||
|
|
||||||
NODE_ENV = "development"
|
NODE_ENV = "development"
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
import {userHandler} from "./handlers/user";
|
import { userHandler } from "./handlers/user";
|
||||||
import {permissionsHandler} from "./handlers/permissions";
|
import { permissionsHandler } from "./handlers/permissions";
|
||||||
import {rolesHandler} from "./handlers/roles";
|
import { rolesHandler } from "./handlers/roles";
|
||||||
import {provincesHandler} from "./handlers/provinces";
|
import { provincesHandler } from "./handlers/provinces";
|
||||||
import {expertsHandler} from "./handlers/experts";
|
import { expertsHandler } from "./handlers/experts";
|
||||||
|
import { categoriesHandler } from "./handlers/categories";
|
||||||
|
|
||||||
export const handler = [
|
export const handler = [
|
||||||
...userHandler,
|
...userHandler,
|
||||||
...permissionsHandler,
|
...permissionsHandler,
|
||||||
...rolesHandler,
|
...rolesHandler,
|
||||||
...provincesHandler,
|
...provincesHandler,
|
||||||
...expertsHandler
|
...expertsHandler,
|
||||||
]
|
...categoriesHandler,
|
||||||
|
];
|
||||||
|
|||||||
25
mocks/handlers/categories.js
Normal file
25
mocks/handlers/categories.js
Normal file
@@ -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: "مزاحم",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
];
|
||||||
@@ -4,10 +4,14 @@ import {GET_SIDEBAR_NOTIFICATION, GET_USER, SET_USER_PASSWORD} from "@/core/data
|
|||||||
export const userHandler = [
|
export const userHandler = [
|
||||||
rest.get(GET_USER, (req, res, ctx) => {
|
rest.get(GET_USER, (req, res, ctx) => {
|
||||||
return res(ctx.json({
|
return res(ctx.json({
|
||||||
data: {
|
data:{
|
||||||
id: 10,
|
id: 10,
|
||||||
permissions: [
|
full_name: "Witel Company",
|
||||||
"manage_users"
|
position: "Software Engineer",
|
||||||
|
permissions:[
|
||||||
|
"manage_users",
|
||||||
|
"manage_roles",
|
||||||
|
"manage_boss"
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -261,5 +261,13 @@
|
|||||||
"call_history_not_found": "تاریخچه ای برای تماس دریافتی یافت نشد",
|
"call_history_not_found": "تاریخچه ای برای تماس دریافتی یافت نشد",
|
||||||
"call_history_error_fetching": "خطا در دریافت تاریخچه تماس دریافتی",
|
"call_history_error_fetching": "خطا در دریافت تاریخچه تماس دریافتی",
|
||||||
"show_more": "نمایش موارد بیشتر"
|
"show_more": "نمایش موارد بیشتر"
|
||||||
|
},
|
||||||
|
"CallAction": {
|
||||||
|
"call_history_of": "عملیات های مربوط به تماس",
|
||||||
|
"categories" : "موضوع ها",
|
||||||
|
"subcategories" : "زیر موضوع ها",
|
||||||
|
"description" : "توضیحات تماس (اختیاری)",
|
||||||
|
"text_field_label" : "توضیحات",
|
||||||
|
"text_field_palacholder" : "لطفا توضیحات خود را وارد نمایید"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(
|
||||||
|
<MockAppWithProviders>
|
||||||
|
<AnswersProvider>
|
||||||
|
<ActionHeader tab={tab} />
|
||||||
|
</AnswersProvider>
|
||||||
|
</MockAppWithProviders>
|
||||||
|
)
|
||||||
|
const headingElement = screen.getByRole('heading', {
|
||||||
|
name: /\.\.\.\. عملیات های مربوط به تماس:/i
|
||||||
|
})
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(headingElement).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
it('should see phone number', async () => {
|
||||||
|
render(
|
||||||
|
<MockAppWithProviders>
|
||||||
|
<AnswersProvider>
|
||||||
|
<ActionHeader tab={tab} />
|
||||||
|
</AnswersProvider>
|
||||||
|
</MockAppWithProviders>
|
||||||
|
)
|
||||||
|
const phone_numberElement = screen.queryByText(/09134849737/i)
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(phone_numberElement).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import {useTranslations} from "next-intl";
|
||||||
|
import {Stack, Typography} from "@mui/material";
|
||||||
|
|
||||||
|
const ActionHeader = ({tab}) => {
|
||||||
|
const t = useTranslations();
|
||||||
|
return (
|
||||||
|
<Stack sx={{
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
py: 2,
|
||||||
|
backgroundColor: "primary.main",
|
||||||
|
overflow: "hidden"
|
||||||
|
}}>
|
||||||
|
<Typography variant="subtitle1" sx={{marginRight: 1, color: "#fff"}}>
|
||||||
|
.... {t("CallAction.call_history_of")}:
|
||||||
|
</Typography>
|
||||||
|
<Typography data-testid="phone_number" variant="subtitle1" sx={{color: "#fff"}}>
|
||||||
|
{tab.phone_number} ....
|
||||||
|
</Typography>
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ActionHeader
|
||||||
@@ -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 (
|
||||||
|
|
||||||
|
<Grid item xs={4}>
|
||||||
|
<Button
|
||||||
|
sx={{py : 1.5}}
|
||||||
|
fullWidth
|
||||||
|
variant={"contained"}
|
||||||
|
color={tab.active_category_id === category.category_id ? "primary" : "inherit"}
|
||||||
|
onClick={() => {
|
||||||
|
setActiveCategoryID(tab.id, category.category_id);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{category.category_name}
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default CallActionCategoriesButton;
|
||||||
@@ -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(
|
||||||
|
<MockAppWithProviders>
|
||||||
|
<AnswersProvider>
|
||||||
|
<CallActionCategoriesButton tab={tab} category={category}/>
|
||||||
|
</AnswersProvider>
|
||||||
|
</MockAppWithProviders>
|
||||||
|
)
|
||||||
|
const categoryElement = screen.queryByText("اطلاعات راه ها")
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(categoryElement).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe("Behavior", ()=>{
|
||||||
|
it("Should change the color when button clecked",async()=>{
|
||||||
|
render(
|
||||||
|
<MockAppWithProviders>
|
||||||
|
<AnswersProvider>
|
||||||
|
<CallActionCategoriesButton tab={tab} category={category}/>
|
||||||
|
</AnswersProvider>
|
||||||
|
</MockAppWithProviders>
|
||||||
|
)
|
||||||
|
const buttonElement = screen.getByRole('button', {
|
||||||
|
name: /اطلاعات راه ها/i
|
||||||
|
})
|
||||||
|
|
||||||
|
fireEvent.click(buttonElement);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(buttonElement).toHaveStyle('background-color: rgb(12, 31, 23)');
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -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(
|
||||||
|
<MockAppWithProviders>
|
||||||
|
<AnswersProvider>
|
||||||
|
<CallActionsCategories tab={tab}/>
|
||||||
|
</AnswersProvider>
|
||||||
|
</MockAppWithProviders>
|
||||||
|
)
|
||||||
|
const divider_textElement = screen.queryByText("موضوع ها")
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(divider_textElement).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
@@ -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 (
|
||||||
|
<>
|
||||||
|
<Divider sx={{margin : 2}}><Chip label={t("CallAction.categories")} variant="outlined"/></Divider>
|
||||||
|
<Stack sx={{m : 1, p : 1}} direction={"row"}>
|
||||||
|
<Grid container spacing={2}>
|
||||||
|
{categoryLists.map((category) => {
|
||||||
|
return <CallActionCategoriesButton tab={tab} key={category.category_id} category={category} />;
|
||||||
|
})}
|
||||||
|
</Grid>
|
||||||
|
</Stack>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default CallActionsCategories;
|
||||||
@@ -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(
|
||||||
|
<MockAppWithProviders>
|
||||||
|
<AnswersProvider>
|
||||||
|
<CallActionDescription />
|
||||||
|
</AnswersProvider>
|
||||||
|
</MockAppWithProviders>
|
||||||
|
)
|
||||||
|
const textElement = screen.getByPlaceholderText('لطفا توضیحات خود را وارد نمایید')
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(textElement).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
it('should see header text', async () => {
|
||||||
|
render(
|
||||||
|
<MockAppWithProviders>
|
||||||
|
<AnswersProvider>
|
||||||
|
<CallActionDescription />
|
||||||
|
</AnswersProvider>
|
||||||
|
</MockAppWithProviders>
|
||||||
|
)
|
||||||
|
const textElement = screen.getByText('توضیحات تماس (اختیاری)')
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(textElement).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe("Behavior", ()=>{
|
||||||
|
it("Should see what write in input", async()=>{
|
||||||
|
const setDescription = jest.fn();
|
||||||
|
render(
|
||||||
|
<MockAppWithProviders>
|
||||||
|
<AnswersProvider>
|
||||||
|
<CallActionDescription setDescription={setDescription} />
|
||||||
|
</AnswersProvider>
|
||||||
|
</MockAppWithProviders>
|
||||||
|
)
|
||||||
|
const textElement = screen.getByPlaceholderText('لطفا توضیحات خود را وارد نمایید')
|
||||||
|
|
||||||
|
fireEvent.change(textElement, {target : {value : "امین"}})
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(textElement).toHaveValue("امین")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { Box, Chip, Divider, TextField } from "@mui/material"
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
|
const CallActionDescription = ({setDescription}) => {
|
||||||
|
const t = useTranslations();
|
||||||
|
return(
|
||||||
|
<>
|
||||||
|
<Divider sx={{margin : 2}}><Chip label={t("CallAction.description")} variant="outlined"/></Divider>
|
||||||
|
<Box sx={{m : 2}}>
|
||||||
|
<TextField
|
||||||
|
variant="outlined"
|
||||||
|
placeholder={t(
|
||||||
|
"CallAction.text_field_palacholder"
|
||||||
|
)}
|
||||||
|
type={"text"}
|
||||||
|
fullWidth
|
||||||
|
multiline
|
||||||
|
rows={8}
|
||||||
|
onChange={(e)=>{
|
||||||
|
setDescription(e.target.value)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default CallActionDescription
|
||||||
@@ -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 ? (
|
||||||
|
<Grid item xs={3}>
|
||||||
|
<Button sx={{py : 1.5}} fullWidth color="inherit"
|
||||||
|
onClick={()=>{handleSubcategoryButton(sub_category.category_id, sub_category.subcategory_id)}} variant={"contained"}>
|
||||||
|
{sub_category.subcategory_name}
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
) : null;
|
||||||
|
};
|
||||||
|
export default CallActionSubcategoriesButton;
|
||||||
@@ -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(
|
||||||
|
<MockAppWithProviders>
|
||||||
|
<AnswersProvider>
|
||||||
|
<CallActionSubcategoriesButton sub_category={sub_category} tab={tab} />
|
||||||
|
</AnswersProvider>
|
||||||
|
</MockAppWithProviders>
|
||||||
|
);
|
||||||
|
const subCategoryElement = screen.queryByText(/اطلاعات باز و بسته/i);
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(subCategoryElement).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -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(
|
||||||
|
<MockAppWithProviders>
|
||||||
|
<AnswersProvider>
|
||||||
|
<CallActionsSubcategories tab={tab}/>
|
||||||
|
</AnswersProvider>
|
||||||
|
</MockAppWithProviders>
|
||||||
|
)
|
||||||
|
const divider_textElement = screen.queryByText("زیر موضوع ها")
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(divider_textElement).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
@@ -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 (
|
||||||
|
<>
|
||||||
|
<Divider sx={{margin : 2}}><Chip label={t("CallAction.subcategories")} variant="outlined"/></Divider>
|
||||||
|
<Stack sx={{m : 1, p : 1 }} direction={"row"}>
|
||||||
|
<Grid container spacing={2}>
|
||||||
|
{subCategoryLists.map((sub_category, index) => {
|
||||||
|
return <CallActionSubcategoriesButton key={index} tab={tab} sub_category={sub_category} handleSubcategoryButton={handleSubcategoryButton}/>;
|
||||||
|
})}
|
||||||
|
</Grid>
|
||||||
|
</Stack>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default CallActionsSubcategories;
|
||||||
@@ -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 (
|
return (
|
||||||
<>CallActions - {tab.id}</>
|
<Stack sx={{
|
||||||
)
|
height: "100%",
|
||||||
|
overflow: "hidden"
|
||||||
|
}}>
|
||||||
|
<ActionHeader tab={tab}/>
|
||||||
|
<Stack sx={{height: "100%", overflow: "scroll"}}>
|
||||||
|
<CallActionDescription setDescription={setDescription}/>
|
||||||
|
<CallActionsCategories tab={tab}/>
|
||||||
|
<CallActionsSubcategories handleSubcategoryButton={handleSubcategoryButton} tab={tab}/>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CallActions
|
export default CallActions;
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ const CallHistory = ({tab}) => {
|
|||||||
overflow: "hidden"
|
overflow: "hidden"
|
||||||
}}>
|
}}>
|
||||||
<HistoryHeader tab={tab}/>
|
<HistoryHeader tab={tab}/>
|
||||||
<Stack sx={{overflow: "scroll"}}>
|
<Stack sx={{height: "100%", overflow: "scroll"}}>
|
||||||
{errorHistoryList ? (
|
{errorHistoryList ? (
|
||||||
<ErrorOrEmpty isErrorOrEmpty="error"/>
|
<ErrorOrEmpty isErrorOrEmpty="error"/>
|
||||||
) : isLoadingHistoryList ? (
|
) : isLoadingHistoryList ? (
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ const CallTabPanel = ({tab}) => {
|
|||||||
<Grid container columns={{xs: 3}} sx={{display: !tab.active && 'none', height: '100%', overflow: "hidden"}}
|
<Grid container columns={{xs: 3}} sx={{display: !tab.active && 'none', height: '100%', overflow: "hidden"}}
|
||||||
role="tabpanel"
|
role="tabpanel"
|
||||||
id={`answer-tabpanel-${tab.id}`} aria-labelledby={`answer-tab-${tab.id}`}>
|
id={`answer-tabpanel-${tab.id}`} aria-labelledby={`answer-tab-${tab.id}`}>
|
||||||
<Grid item xs={2}>
|
<Grid item xs={2} sx={{height: "100%", overflow: "scroll"}}>
|
||||||
<CallActions tab={tab}/>
|
<CallActions tab={tab}/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item>
|
<Grid item>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import SidebarList from "./SidebarList";
|
|||||||
import useUser from "@/lib/app/hooks/useUser";
|
import useUser from "@/lib/app/hooks/useUser";
|
||||||
|
|
||||||
const SidebarDrawer = ({handleDrawerToggle}) => {
|
const SidebarDrawer = ({handleDrawerToggle}) => {
|
||||||
const {user} = useUser()
|
const {user} = useUser();
|
||||||
const t = useTranslations();
|
const t = useTranslations();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -10,29 +10,15 @@ function reducer(state, action) {
|
|||||||
case "COLLAPSE_MENU":
|
case "COLLAPSE_MENU":
|
||||||
return state.map((itemsArr) =>
|
return state.map((itemsArr) =>
|
||||||
itemsArr.map((item) =>
|
itemsArr.map((item) =>
|
||||||
action.key == item.key
|
action.key === item.key ? { ...item, showSubItem: !item.showSubItem } : item
|
||||||
? {...item, showSubItem: !item.showSubItem}
|
|
||||||
: item
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
case "SELECTED":
|
case "SELECTED":
|
||||||
return state.map((itemsArr) =>
|
return state.map((itemsArr) =>
|
||||||
itemsArr.map((item) => {
|
itemsArr.map((item) => ({
|
||||||
if (item.type === "page") {
|
...item,
|
||||||
if (action.route === item.route)
|
selected: item.type === "page" && 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}}
|
|
||||||
// })
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
throw new Error();
|
throw new Error();
|
||||||
@@ -54,7 +40,7 @@ export default function SidebarList({handleDrawerToggle}) {
|
|||||||
<Fragment key={index}>
|
<Fragment key={index}>
|
||||||
{itemArr.map((item) =>
|
{itemArr.map((item) =>
|
||||||
<Fragment key={item.key}>
|
<Fragment key={item.key}>
|
||||||
{(user.permissions.includes(item.permission) || item.permission === "all") &&
|
{(user?.permissions?.includes(item.permission) || item.permission === "all") &&
|
||||||
<SidebarListItem
|
<SidebarListItem
|
||||||
item={item}
|
item={item}
|
||||||
dispatch={dispatch}
|
dispatch={dispatch}
|
||||||
|
|||||||
@@ -9,23 +9,27 @@ import useNotification from "@/lib/app/hooks/useNotification";
|
|||||||
|
|
||||||
const SidebarListItem = ({item, dispatch, handleDrawerToggle}) => {
|
const SidebarListItem = ({item, dispatch, handleDrawerToggle}) => {
|
||||||
const t = useTranslations();
|
const t = useTranslations();
|
||||||
const {notification_count} = useNotification()
|
const { notification_count } = useNotification();
|
||||||
|
|
||||||
|
const hasSubItems = item.type === "menu" && item.subItem && item.subItem.length > 0;
|
||||||
|
|
||||||
|
const renderBadge = () => (
|
||||||
|
<IconButton>
|
||||||
|
<Badge
|
||||||
|
badgeContent={notification_count ? notification_count[item.name] : 0}
|
||||||
|
color="error"
|
||||||
|
variant="standard"
|
||||||
|
anchorOrigin={{
|
||||||
|
vertical: "top",
|
||||||
|
horizontal: "right",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</IconButton>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ListItem disablePadding secondaryAction={
|
<ListItem disablePadding secondaryAction={renderBadge()}>
|
||||||
<IconButton>
|
|
||||||
<Badge
|
|
||||||
badgeContent={notification_count ? notification_count[item.name] : 0}
|
|
||||||
color="error"
|
|
||||||
variant="standard"
|
|
||||||
anchorOrigin={{
|
|
||||||
vertical: "top",
|
|
||||||
horizontal: "right",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</IconButton>
|
|
||||||
}>
|
|
||||||
<ListItemButton
|
<ListItemButton
|
||||||
selected={item.selected}
|
selected={item.selected}
|
||||||
{...(item.type == "page" && {
|
{...(item.type == "page" && {
|
||||||
@@ -35,10 +39,9 @@ const SidebarListItem = ({item, dispatch, handleDrawerToggle}) => {
|
|||||||
},
|
},
|
||||||
})}
|
})}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (item.type == "menu") {
|
if (hasSubItems) {
|
||||||
dispatch({type: "COLLAPSE_MENU", key: item.key});
|
dispatch({ type: "COLLAPSE_MENU", key: item.key });
|
||||||
}
|
}
|
||||||
handleDrawerToggle();
|
|
||||||
}}
|
}}
|
||||||
sx={{
|
sx={{
|
||||||
minHeight: 48,
|
minHeight: 48,
|
||||||
@@ -66,9 +69,7 @@ const SidebarListItem = ({item, dispatch, handleDrawerToggle}) => {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{hasSubItems && (item.showSubItem ? <ExpandLess /> : <ExpandMore />)}
|
||||||
{item.type == "menu" &&
|
|
||||||
(item.showSubItem ? <ExpandLess/> : <ExpandMore/>)}
|
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
{item.subItem && (
|
{item.subItem && (
|
||||||
|
|||||||
@@ -15,25 +15,27 @@ import useNotification from "@/lib/app/hooks/useNotification";
|
|||||||
|
|
||||||
const SidebarListSubItem = ({item, handleDrawerToggle}) => {
|
const SidebarListSubItem = ({item, handleDrawerToggle}) => {
|
||||||
const t = useTranslations();
|
const t = useTranslations();
|
||||||
const {notification_count} = useNotification()
|
const {notification_count} = useNotification();
|
||||||
|
|
||||||
|
const renderBadge = (subitem) => (
|
||||||
|
<IconButton>
|
||||||
|
<Badge
|
||||||
|
badgeContent={notification_count ? notification_count[subitem.name] : 0}
|
||||||
|
color="error"
|
||||||
|
variant="standard"
|
||||||
|
anchorOrigin={{
|
||||||
|
vertical: "top",
|
||||||
|
horizontal: "right",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</IconButton>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Collapse in={item.showSubItem} timeout="auto" mountOnEnter={true} unmountOnExit={true}>
|
<Collapse in={item.showSubItem} timeout="auto" mountOnEnter={true} unmountOnExit={true}>
|
||||||
<List component="div" disablePadding sx={{bgcolor: "#f6f6f6", pr: 1}}>
|
<List component="div" disablePadding sx={{bgcolor: "#f6f6f6", pr: 1}}>
|
||||||
{item.subItem.map((subitem, index) => (
|
{item.subItem.map((subitem) => (
|
||||||
<ListItem key={subitem.key} disablePadding secondaryAction={
|
<ListItem key={subitem.key} disablePadding secondaryAction={renderBadge(subitem)}>
|
||||||
<IconButton>
|
|
||||||
<Badge
|
|
||||||
badgeContent={notification_count ? notification_count[subitem.name] : 0}
|
|
||||||
color="error"
|
|
||||||
variant="standard"
|
|
||||||
anchorOrigin={{
|
|
||||||
vertical: "top",
|
|
||||||
horizontal: "right",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</IconButton>
|
|
||||||
}>
|
|
||||||
<ListItemButton
|
<ListItemButton
|
||||||
selected={subitem.selected}
|
selected={subitem.selected}
|
||||||
component={NextLinkComposed}
|
component={NextLinkComposed}
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import {render, screen, waitFor} from "@testing-library/react";
|
||||||
|
import MockAppWithProviders from "../../../../../../mocks/AppWithProvider";
|
||||||
|
import SidebarDrawer from "@/components/layouts/Dashboard/Sidebar/SidebarDrawer";
|
||||||
|
|
||||||
|
describe('SidebarDrawer', () => {
|
||||||
|
describe('Rendering', () => {
|
||||||
|
it('renders system name in sidebar', async () => {
|
||||||
|
render(<MockAppWithProviders><SidebarDrawer/></MockAppWithProviders>);
|
||||||
|
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(<MockAppWithProviders><SidebarDrawer/></MockAppWithProviders>);
|
||||||
|
await waitFor(()=>{
|
||||||
|
const userName = screen.queryByText(/Witel Company | Software Engineer/i);
|
||||||
|
expect(userName).toBeInTheDocument();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -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(<MockAppWithProviders><SidebarList/></MockAppWithProviders>);
|
||||||
|
await waitFor(()=>{
|
||||||
|
const sidebarItem = screen.getByText('مدیریت نقش ها');
|
||||||
|
expect(sidebarItem).toBeInTheDocument();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -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: <AccessibilityIcon />,
|
||||||
|
selected: false,
|
||||||
|
permission: "manage_roles",
|
||||||
|
}
|
||||||
|
render(<MockAppWithProviders><SidebarListItem item={sampleItem} /></MockAppWithProviders>);
|
||||||
|
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: <AccessibilityIcon aria-label="Sidebar Icon"/>,
|
||||||
|
selected: false,
|
||||||
|
permission: "manage_roles",
|
||||||
|
}
|
||||||
|
render(<MockAppWithProviders><SidebarListItem item={sampleItem} /></MockAppWithProviders>);
|
||||||
|
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: <AccessibilityIcon />,
|
||||||
|
selected: true,
|
||||||
|
permission: "manage_roles",
|
||||||
|
}
|
||||||
|
render(<MockAppWithProviders><SidebarListItem item={sampleItem} /></MockAppWithProviders>);
|
||||||
|
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: <AccessibilityIcon />,
|
||||||
|
selected: false,
|
||||||
|
permission: "manage_roles",
|
||||||
|
}
|
||||||
|
render(<MockAppWithProviders><SidebarListItem item={sampleItem} /></MockAppWithProviders>);
|
||||||
|
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: <AccessibilityIcon />,
|
||||||
|
selected: false,
|
||||||
|
permission: "manage_roles",
|
||||||
|
}
|
||||||
|
render(<MockAppWithProviders><SidebarListItem item={sampleItem} /></MockAppWithProviders>);
|
||||||
|
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: <AccessibilityIcon />,
|
||||||
|
selected: false,
|
||||||
|
permission: "manage_roles",
|
||||||
|
}
|
||||||
|
render(<MockAppWithProviders><SidebarListItem item={sampleItem} /></MockAppWithProviders>);
|
||||||
|
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: <AccessibilityIcon />,
|
||||||
|
selected: false,
|
||||||
|
permission: "manage_roles",
|
||||||
|
subItem :[
|
||||||
|
{
|
||||||
|
key: "greaterThan",
|
||||||
|
route: "/dashboard/role-management",
|
||||||
|
secondary: "between",
|
||||||
|
name : "sidebar.admin",
|
||||||
|
selected: true,
|
||||||
|
icon: <AccessibilityIcon />,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
render(<MockAppWithProviders><SidebarListItem item={sampleItem} dispatch={dispatchMock}/></MockAppWithProviders>);
|
||||||
|
const listItem = screen.getByText('مدیریت نقش ها');
|
||||||
|
fireEvent.click(listItem);
|
||||||
|
expect(dispatchMock).toHaveBeenCalledWith({
|
||||||
|
type: 'COLLAPSE_MENU',
|
||||||
|
key: "sidebar.role-management",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -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: <AccessibilityIcon />,
|
||||||
|
selected: false,
|
||||||
|
permission: "manage_roles",
|
||||||
|
subItem :[
|
||||||
|
{
|
||||||
|
key: "greaterThan",
|
||||||
|
route: "/dashboard/role-management",
|
||||||
|
secondary: "between",
|
||||||
|
name : "sidebar.admin",
|
||||||
|
selected: true,
|
||||||
|
icon: <AccessibilityIcon aria-label="Sidebar Icon"/>,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
it('should show subitem name in sidebar', () => {
|
||||||
|
render(<MockAppWithProviders><SidebarListSubItem item={sampleItem} /></MockAppWithProviders>);
|
||||||
|
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: <AccessibilityIcon />,
|
||||||
|
selected: false,
|
||||||
|
permission: "manage_roles",
|
||||||
|
subItem :[
|
||||||
|
{
|
||||||
|
key: "greaterThan",
|
||||||
|
route: "/dashboard/role-management",
|
||||||
|
secondary: "between",
|
||||||
|
name : "sidebar.admin",
|
||||||
|
selected: false,
|
||||||
|
icon: <AccessibilityIcon />,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
render(<MockAppWithProviders><SidebarListSubItem item={sampleItem} /></MockAppWithProviders>);
|
||||||
|
expect(screen.queryByText('مدیریت نقش ها')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
it('should show subitem secondary in sidebar', () => {
|
||||||
|
render(<MockAppWithProviders><SidebarListSubItem item={sampleItem} /></MockAppWithProviders>);
|
||||||
|
expect(screen.getByText('میان')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not show subitem secondary in sidebar', () => {
|
||||||
|
render(<MockAppWithProviders><SidebarListSubItem item={sampleItem} /></MockAppWithProviders>);
|
||||||
|
expect(screen.queryByText('secondry')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
it('should have selected class for subitem', () => {
|
||||||
|
render(<MockAppWithProviders><SidebarListSubItem item={sampleItem} /></MockAppWithProviders>);
|
||||||
|
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: <AccessibilityIcon />,
|
||||||
|
selected: false,
|
||||||
|
permission: "manage_roles",
|
||||||
|
subItem :[
|
||||||
|
{
|
||||||
|
key: "greaterThan",
|
||||||
|
route: "/dashboard/role-management",
|
||||||
|
secondary: "between",
|
||||||
|
name : "sidebar.admin",
|
||||||
|
selected: false,
|
||||||
|
icon: <AccessibilityIcon />,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
render(<MockAppWithProviders><SidebarListSubItem item={sampleItem} /></MockAppWithProviders>);
|
||||||
|
const parentElement = screen.queryByText('بزرگتر از').parentElement.parentElement;
|
||||||
|
expect(parentElement).not.toHaveClass('Mui-selected');
|
||||||
|
});
|
||||||
|
it('should show subitem icon', () => {
|
||||||
|
render(<MockAppWithProviders><SidebarListSubItem item={sampleItem} /></MockAppWithProviders>);
|
||||||
|
const iconElement = screen.getByLabelText("Sidebar Icon");
|
||||||
|
expect(iconElement).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -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(<MockAppWithProviders><Sidebar {...props}/></MockAppWithProviders>);
|
||||||
|
const mobileDrawer = screen.getByTestId('mobile-drawer');
|
||||||
|
expect(mobileDrawer).toBeVisible();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -9,6 +9,7 @@ const Sidebar = (props) => {
|
|||||||
aria-label="mailbox folders"
|
aria-label="mailbox folders"
|
||||||
>
|
>
|
||||||
<Drawer
|
<Drawer
|
||||||
|
data-testid="mobile-drawer"
|
||||||
container={props.container}
|
container={props.container}
|
||||||
variant="temporary"
|
variant="temporary"
|
||||||
open={props.mobileOpen}
|
open={props.mobileOpen}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ function DataTable(props) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const [updateTime, setUpdateTime] = useState(
|
const [updateTime, setUpdateTime] = useState(
|
||||||
moment().format("HH:mm | jYYYY/jM/jD")
|
moment().format("HH:mm | jYYYY/jMM/jDD")
|
||||||
);
|
);
|
||||||
|
|
||||||
const tableLocalization = useMemo(
|
const tableLocalization = useMemo(
|
||||||
@@ -84,7 +84,7 @@ function DataTable(props) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setUpdateTime(moment().format("HH:mm | jYYYY/jM/jD"));
|
setUpdateTime(moment().format("HH:mm | jYYYY/jMM/jDD"));
|
||||||
}, [isValidating, languageApp]);
|
}, [isValidating, languageApp]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -29,21 +29,16 @@ export const GET_USER = BASE_URL + "/api/profile/info";
|
|||||||
//user data
|
//user data
|
||||||
|
|
||||||
//sidebar notification
|
//sidebar notification
|
||||||
export const GET_SIDEBAR_NOTIFICATION = BASE_URL + "/dashboard/notification"
|
export const GET_SIDEBAR_NOTIFICATION = BASE_URL + "/dashboard/notification";
|
||||||
//sidebar notification
|
//sidebar notification
|
||||||
|
|
||||||
// role management
|
// role management
|
||||||
export const GET_ROLES =
|
export const GET_ROLES = BASE_URL + "/api/roles";
|
||||||
BASE_URL + "/api/roles"
|
export const ADD_ROLE = BASE_URL + "/api/roles";
|
||||||
export const ADD_ROLE =
|
export const UPDATE_ROLE = BASE_URL + "/api/roles";
|
||||||
BASE_URL + "/api/roles"
|
export const DELETE_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 =
|
export const GET_PERMISSIONS_LIST = BASE_URL + "/api/permissions/list";
|
||||||
BASE_URL + "/api/permissions/list"
|
|
||||||
//role management
|
//role management
|
||||||
|
|
||||||
//expert management
|
//expert management
|
||||||
@@ -52,3 +47,8 @@ export const ADD_EXPERT = BASE_URL + "/api/users";
|
|||||||
export const UPDATE_EXPERT = BASE_URL + "/api/users";
|
export const UPDATE_EXPERT = BASE_URL + "/api/users";
|
||||||
export const DELETE_EXPERT = BASE_URL + "/api/users";
|
export const DELETE_EXPERT = BASE_URL + "/api/users";
|
||||||
//expert management
|
//expert management
|
||||||
|
|
||||||
|
//call action history
|
||||||
|
export const GET_CATEGORY = BASE_URL + "/api/categories";
|
||||||
|
export const CALL_ACTION = BASE_URL + "/api/calls";
|
||||||
|
//call action history
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
import Dashboard from "src/components/layouts/Dashboard";
|
import Dashboard from "src/components/layouts/Dashboard";
|
||||||
|
import AppPermission from "@/middlewares/AppPermission";
|
||||||
|
import CallWidget from "@/components/layouts/Dashboard/CallWidget";
|
||||||
|
|
||||||
const DashboardLayout = (props) => {
|
const DashboardLayout = (props) => {
|
||||||
|
const permissions = ["NEXT_PUBLIC_HAS_WIDGET"];
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Dashboard {...props}/>
|
<Dashboard {...props}/>
|
||||||
{/*<CallWidget/>*/}
|
<AppPermission permissions={permissions}>
|
||||||
|
<CallWidget />
|
||||||
|
</AppPermission>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ export const SocketProvider = ({children}) => {
|
|||||||
const {directionApp} = useDirection();
|
const {directionApp} = useDirection();
|
||||||
const socket = useMemo(() => io(process.env.NEXT_PUBLIC_SERVER_SOCKET_URL, {
|
const socket = useMemo(() => io(process.env.NEXT_PUBLIC_SERVER_SOCKET_URL, {
|
||||||
autoConnect: false,
|
autoConnect: false,
|
||||||
|
transports: ['websocket'],
|
||||||
auth: {
|
auth: {
|
||||||
token: ""
|
token: ""
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
import useSWR from 'swr'
|
import useSWR from 'swr'
|
||||||
import {GET_SIDEBAR_NOTIFICATION} from "@/core/data/apiRoutes";
|
import {GET_SIDEBAR_NOTIFICATION} from "@/core/data/apiRoutes";
|
||||||
import useRequest from "@/lib/app/hooks/useRequest";
|
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 useNotification = () => {
|
||||||
|
|
||||||
const requestServer = useRequest({auth: true, notification: false})
|
const requestServer = useRequest({auth: true, notification: false})
|
||||||
|
|
||||||
//swr config
|
//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
|
//swr config
|
||||||
|
|
||||||
// render data
|
// render data
|
||||||
|
|||||||
@@ -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) => {
|
const reducer = (state, action) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case "CHANGE_ACTIVE_TAB":
|
case "CHANGE_ACTIVE_TAB":
|
||||||
return state.map((answer, index) => action.newValue === index ? {...answer, active: true} : {
|
return state.map((answer, index) =>
|
||||||
...answer,
|
action.newValue === index
|
||||||
active: false
|
? { ...answer, active: true }
|
||||||
});
|
: {
|
||||||
|
...answer,
|
||||||
|
active: false,
|
||||||
|
}
|
||||||
|
);
|
||||||
case "CHANGE_LIST":
|
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:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
export const AnswersContext = createContext()
|
|
||||||
export const AnswersProvider = ({children}) => {
|
const submission = (handleCategories, operation) => {
|
||||||
const [openCallDialog, setOpenCallDialog] = useState(false)
|
switch (operation.type) {
|
||||||
const [activeTab, setActiveTab] = useState(0)
|
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, []);
|
const [state, dispatch] = useReducer(reducer, []);
|
||||||
|
|
||||||
return <AnswersContext.Provider
|
useEffect(() => {
|
||||||
value = {{
|
requestServer(GET_CATEGORY, "get")
|
||||||
openCallDialog,
|
.then(({ data }) => {
|
||||||
setOpenCallDialog,
|
const subCategoriesByAPI = data.data;
|
||||||
state,
|
const categoriesByAPI = subCategoriesByAPI.reduce((accumulator, currentValue) => {
|
||||||
dispatch,
|
if (!accumulator.find((item) => item.category_id === currentValue.category_id)) {
|
||||||
activeTab,
|
accumulator.push(currentValue);
|
||||||
setActiveTab
|
}
|
||||||
}}>{children}</AnswersContext.Provider>
|
return accumulator;
|
||||||
}
|
}, []);
|
||||||
|
operation({ type: "SET_CATEGORIES", categoriesByAPI });
|
||||||
|
operation({ type: "SET_SUBCATEGORIES", subCategoriesByAPI });
|
||||||
|
})
|
||||||
|
.catch(() => {});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AnswersContext.Provider
|
||||||
|
value={{
|
||||||
|
openCallDialog,
|
||||||
|
setOpenCallDialog,
|
||||||
|
state,
|
||||||
|
dispatch,
|
||||||
|
activeTab,
|
||||||
|
setActiveTab,
|
||||||
|
categoryLists: handleCategories.categoryLists,
|
||||||
|
subCategoryLists: handleCategories.subCategoryLists,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</AnswersContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
59
src/lib/callWidget/contexts/categories.jsx
Normal file
59
src/lib/callWidget/contexts/categories.jsx
Normal file
@@ -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 (
|
||||||
|
<CategoriesContext.Provider
|
||||||
|
value={{
|
||||||
|
categoryLists: state.categoryLists,
|
||||||
|
subCategoryLists: state.subCategoryLists,
|
||||||
|
active_id: state.active_id,
|
||||||
|
setCategoryID,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</CategoriesContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,38 +1,46 @@
|
|||||||
import {useContext} from "react";
|
import { useContext } from "react";
|
||||||
import {AnswersContext} from "@/lib/callWidget/contexts/answers";
|
import { AnswersContext } from "@/lib/callWidget/contexts/answers";
|
||||||
|
|
||||||
const useAnswers = () => {
|
const useAnswers = () => {
|
||||||
const {state, dispatch, activeTab, setActiveTab} = useContext(AnswersContext)
|
const { state, dispatch, activeTab, setActiveTab } = useContext(AnswersContext);
|
||||||
|
|
||||||
const changeActiveTabAnswers = (newValue) => {
|
const changeActiveTabAnswers = (newValue) => {
|
||||||
dispatch({type: 'CHANGE_ACTIVE_TAB', newValue})
|
dispatch({ type: "CHANGE_ACTIVE_TAB", newValue });
|
||||||
setActiveTab(newValue)
|
setActiveTab(newValue);
|
||||||
}
|
};
|
||||||
const newAnswerTab = (data) => {
|
const newAnswerTab = (data) => {
|
||||||
const newAnswer = {
|
const newAnswer = {
|
||||||
id: data.id,
|
id: data.id,
|
||||||
phone_number: data.phone_number,
|
phone_number: data.phone_number,
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
active: true
|
active: true,
|
||||||
}
|
active_category_id: null,
|
||||||
const newList = [...state, newAnswer]
|
};
|
||||||
dispatch({type: 'CHANGE_LIST', newList})
|
const newList = [...state, newAnswer];
|
||||||
changeActiveTabAnswers(newList.length - 1)
|
dispatch({ type: "CHANGE_LIST", newList });
|
||||||
}
|
changeActiveTabAnswers(newList.length - 1);
|
||||||
|
};
|
||||||
|
|
||||||
const closeAnswerTab = (id) => {
|
const closeAnswerTab = (id) => {
|
||||||
const index = state.findIndex(answer => answer.id === id)
|
const index = state.findIndex((answer) => answer.id === id);
|
||||||
const newIndex = index === 0 ? 0 : index - 1
|
const newIndex = index === 0 ? 0 : index - 1;
|
||||||
const newList = [...state]
|
const newList = [...state];
|
||||||
newList.splice(index, 1);
|
newList.splice(index, 1);
|
||||||
if (newList.length !== 0) {
|
if (newList.length !== 0) {
|
||||||
newList[newIndex].active = true
|
newList[newIndex].active = true;
|
||||||
setActiveTab(newIndex)
|
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
|
export default useAnswers;
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ const useCallerHistory = (call_id, phone_number, max_size) => {
|
|||||||
return {
|
return {
|
||||||
firstItemOfHistory: validData.length === 0 ? false : validData[0],
|
firstItemOfHistory: validData.length === 0 ? false : validData[0],
|
||||||
historyList: validData.length < 2 ? false : validData.slice(1),
|
historyList: validData.length < 2 ? false : validData.slice(1),
|
||||||
isLoadingHistoryList: true,
|
isLoadingHistoryList: isLoading,
|
||||||
errorHistoryList: false
|
errorHistoryList: !data
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
17
src/lib/callWidget/hooks/useCategories.jsx
Normal file
17
src/lib/callWidget/hooks/useCategories.jsx
Normal file
@@ -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;
|
||||||
8
src/middlewares/AppPermission.jsx
Normal file
8
src/middlewares/AppPermission.jsx
Normal file
@@ -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;
|
||||||
26
src/middlewares/__test__/AppPermission.test.js
Normal file
26
src/middlewares/__test__/AppPermission.test.js
Normal file
@@ -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(<MockAppWithProviders><AppPermission permissions={requiredPermissions}/>{'children'}</MockAppWithProviders>);
|
||||||
|
|
||||||
|
await waitFor(()=>{
|
||||||
|
expect(screen.queryByText('children')).toBeInTheDocument();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
it('do not show related child if permission does not exist', async () => {
|
||||||
|
|
||||||
|
const requiredPermissions = ["MISSING_PERMISSION"];
|
||||||
|
render(<MockAppWithProviders><AppPermission permissions={requiredPermissions} /></MockAppWithProviders>);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.queryByText('children')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user