diff --git a/.eslintrc.json b/.eslintrc.json index c4f29da..cb6d4e4 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,7 +1,5 @@ { "extends": [ - "next/core-web-vitals", - "plugin:testing-library/react", - "plugin:jest-dom/recommended" + "next/core-web-vitals" ] } diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..fd06551 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,118 @@ +image: node:latest + +stages: + - build + - test + - test:build + - deploy + +merge:build: + stage: build + script: + - npm install + - cp example.env.local .env.local + - cp example.env.local .env.test + only: + - merge_requests + artifacts: + paths: + - node_modules + - .env.local + - .env.test + +merge:test:jest: + stage: test + script: + - npm run test + only: + - merge_requests + artifacts: + paths: + - node_modules + - .env.local + - .env.test + +merge:test:lint: + stage: test + script: + - npm run lint + only: + - merge_requests + artifacts: + paths: + - node_modules + - .env.local + - .env.test + +#merge:test:build: +# stage: test:build +# script: +# - npm run build +# only: +# - merge_requests +# artifacts: +# paths: +# - node_modules +# - .env.local +# - .env.test + +build: + stage: build + script: + - npm install + - cp example.env.local .env.local + - cp example.env.local .env.test + only: + - develop + - main + artifacts: + paths: + - node_modules + - .env.local + - .env.test + +test:jest: + stage: test + script: + - npm run test + only: + - develop + - main + artifacts: + paths: + - node_modules + - .env.local + - .env.test + +test:lint: + stage: test + script: + - npm run lint + only: + - develop + - main + artifacts: + paths: + - node_modules + - .env.local + - .env.test + +#test:build: +# stage: test:build +# script: +# - npm run build +# only: +# - develop +# - main +# artifacts: +# paths: +# - node_modules +# - .env.local +# - .env.test + +deploy: + stage: deploy + script: + - echo "Run deploy project" + only: + - main \ No newline at end of file diff --git a/mocks/handler.js b/mocks/handler.js index fee5afa..fbb803a 100644 --- a/mocks/handler.js +++ b/mocks/handler.js @@ -1,92 +1,13 @@ -import {GET_PERMISSIONS_LIST, GET_ROLE_MANAGEMENT, GET_USER_ROUTE, SET_USER_PASSWORD} from "@/core/data/apiRoutes"; -import {rest} from "msw"; +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"; export const handler = [ - rest.get(GET_USER_ROUTE, (req, res, ctx) => { - return res(ctx.json({ - data:{ - id: 10, - full_name: "Witel Company", - position: "Software Engineer", - permissions:[ - "manage_users", - "manage_roles", - "manage_boss" - ], - } - })) - }), - rest.post(SET_USER_PASSWORD, (req, res, ctx) => { - return res( - ctx.status(200), - ); - }), - rest.get(GET_PERMISSIONS_LIST, (req, res, ctx) => { - return res(ctx.json( - { - data:[ - { - id: 1, - name: "manage_passenger_office_navgan", - name_fa: "مدیریت کارتابل رییس اداره مسافری استان" - }, - { - id: 2, - name: "manage_province_working_group_navgan", - name_fa:"مدیریت کارتابل کارگروه استانی" - } - ] - } - )) - }), - rest.get(GET_ROLE_MANAGEMENT, (req, res, ctx) => { - return res( - ctx.json( - { - data : [ - { - created_at: "2023-10-01T07:20:07.000000Z", - guard_name: "api", - id: 1, - name: "admin", - name_fa: "ادمین", - permissions: [ - { - id: 1, - name: "manage_users", - }, - { - id: 2, - name: "manage_roles", - }, - ], - updated_at: "2023-10-10T07:38:12.000000Z" - }, - { - created_at: "2023-10-01T07:20:07.000000Z", - guard_name: "api", - id: 2, - name: "manager", - name_fa: "مدیر", - permissions: [ - { - id: 1, - name: "manage_users", - }, - { - id: 2, - name: "manage_roles", - }, - ], - updated_at: "2023-10-10T07:38:12.000000Z" - } - ], - meta : { - totalRowCount : 2 - } - } - ), - ); - }), - + ...userHandler, + ...permissionsHandler, + ...rolesHandler, + ...provincesHandler, + ...expertsHandler ] \ No newline at end of file diff --git a/mocks/handlers/experts.js b/mocks/handlers/experts.js new file mode 100644 index 0000000..1bf2a31 --- /dev/null +++ b/mocks/handlers/experts.js @@ -0,0 +1,20 @@ +import {rest} from "msw"; +import {ADD_EXPERT, DELETE_EXPERT, UPDATE_EXPERT} from "@/core/data/apiRoutes"; + +export const expertsHandler = [ + rest.post(ADD_EXPERT, (req, res, ctx) => { + return res( + ctx.status(200), + ); + }), + rest.delete(`${DELETE_EXPERT}/:id`, (req, res, ctx) => { + return res( + ctx.status(200), + ); + }), + rest.post(`${UPDATE_EXPERT}/:id`, (req, res, ctx) => { + return res( + ctx.status(200), + ); + }), +] \ No newline at end of file diff --git a/mocks/handlers/permissions.js b/mocks/handlers/permissions.js new file mode 100644 index 0000000..dfe792b --- /dev/null +++ b/mocks/handlers/permissions.js @@ -0,0 +1,23 @@ +import {rest} from "msw"; +import {GET_PERMISSIONS_LIST} from "@/core/data/apiRoutes"; + +export const permissionsHandler = [ + rest.get(GET_PERMISSIONS_LIST, (req, res, ctx) => { + return res(ctx.json( + { + data: [ + { + id: 1, + name: "manage_passenger_office_navgan", + name_fa: "مدیریت کارتابل رییس اداره مسافری استان" + }, + { + id: 2, + name: "manage_province_working_group_navgan", + name_fa: "مدیریت کارتابل کارگروه استانی" + } + ] + } + )) + }) +] \ No newline at end of file diff --git a/mocks/handlers/provinces.js b/mocks/handlers/provinces.js new file mode 100644 index 0000000..9065c12 --- /dev/null +++ b/mocks/handlers/provinces.js @@ -0,0 +1,19 @@ +import {rest} from "msw"; +import {GET_PROVINCE_LIST} from "@/core/data/apiRoutes"; + +export const provincesHandler = [ + rest.get(GET_PROVINCE_LIST, (req, res, ctx) => { + return res(ctx.json({ + data: [ + { + id: 1, + name: "آذربایجان شرقی" + }, + { + id: 3, + name: "اردبیل" + } + ] + })) + }), +] \ No newline at end of file diff --git a/mocks/handlers/roles.js b/mocks/handlers/roles.js new file mode 100644 index 0000000..1c26ef5 --- /dev/null +++ b/mocks/handlers/roles.js @@ -0,0 +1,70 @@ +import {rest} from "msw"; +import {GET_ROLE_LIST, GET_ROLES} from "@/core/data/apiRoutes"; + +export const rolesHandler = [ + rest.get(GET_ROLES, (req, res, ctx) => { + return res( + ctx.json( + { + data: [ + { + created_at: "2023-10-01T07:20:07.000000Z", + guard_name: "api", + id: 1, + name: "admin", + name_fa: "ادمین", + permissions: [ + { + id: 1, + name: "manage_users", + }, + { + id: 2, + name: "manage_roles", + }, + ], + updated_at: "2023-10-10T07:38:12.000000Z" + }, + { + created_at: "2023-10-01T07:20:07.000000Z", + guard_name: "api", + id: 2, + name: "manager", + name_fa: "مدیر", + permissions: [ + { + id: 1, + name: "manage_users", + }, + { + id: 2, + name: "manage_roles", + }, + ], + updated_at: "2023-10-10T07:38:12.000000Z" + } + ], + meta: { + totalRowCount: 2 + } + } + ), + ); + }), + rest.get(GET_ROLE_LIST, (req, res, ctx) => { + return res(ctx.json({ + data: [ + { + id: 1, + name: "admin", + name_fa: "ادمین" + }, + { + id: 2, + name: "manager", + name_fa: "مدیر" + } + ] + })) + }), +] \ No newline at end of file diff --git a/mocks/handlers/user.js b/mocks/handlers/user.js new file mode 100644 index 0000000..7e84703 --- /dev/null +++ b/mocks/handlers/user.js @@ -0,0 +1,29 @@ +import {rest} from "msw"; +import {GET_SIDEBAR_NOTIFICATION, GET_USER, SET_USER_PASSWORD} from "@/core/data/apiRoutes"; + +export const userHandler = [ + rest.get(GET_USER, (req, res, ctx) => { + return res(ctx.json({ + data:{ + id: 10, + full_name: "Witel Company", + position: "Software Engineer", + permissions:[ + "manage_users", + "manage_roles", + "manage_boss" + ], + } + })) + }), + rest.get(GET_SIDEBAR_NOTIFICATION, (req, res, ctx) => { + return res(ctx.json({ + data: [] + })) + }), + rest.post(SET_USER_PASSWORD, (req, res, ctx) => { + return res( + ctx.status(200), + ); + }), +] \ No newline at end of file diff --git a/public/fontiran.scss b/public/fontiran.css similarity index 100% rename from public/fontiran.scss rename to public/fontiran.css diff --git a/public/locales/fa/app.json b/public/locales/fa/app.json index 9e28007..0a1c31b 100644 --- a/public/locales/fa/app.json +++ b/public/locales/fa/app.json @@ -35,7 +35,8 @@ "change-password": "تغییر رمز عبور", "edit-profile": "ویرایش پروفایل", "role-management": "مدیریت نقش ها", - "admin": "مدیریت" + "admin": "مدیریت", + "expert-management": "مدیریت کارشناسان" }, "secondary": { }, @@ -74,7 +75,8 @@ "dashboard_page": "داشبورد", "change_password": "تغییر رمز عبور", "edit_profile": "ویرایش پروفایل", - "role_management_page": "مدیریت نقش ها" + "role_management_page": "مدیریت نقش ها", + "expert_management": "مدیریت کارشناسان" }, "MuiDatePicker": { "date_picker_birthday": "تاریخ" @@ -188,5 +190,75 @@ "button-cancel": "انصراف", "typography": "آیا از حدف این مورد اطمینان دارید ؟", "button-delete": "حذف کردن" + }, + "ExpertMangement": { + "id": "کد یکتا", + "name": "نام کامل", + "username": "نام کاربری", + "email": "پست الکترونیک", + "phone_number": "شماره همراه", + "national_id": "کد ملی", + "position": "سمت", + "province_fa": "استان", + "role_name": "نقش", + "gender": "جنسیت", + "male": "مرد", + "female": "زن", + "updated_at": "آخرین بروزرسانی", + "create": "افزودن کارشناس", + "personal_info": "مشخصات کارشناس", + "user_info": "اطلاعات کاربری", + "rest_info": "اطلاعات تکمیلی", + "text_field_full_name": "نام کامل", + "text_field_username": "نام کاربری", + "text_field_email": "پست الکترونیک", + "text_field_phone_number": "شماره همراه", + "text_field_telephone_id": "کد تلفن", + "text_field_gender": "جنسیت", + "text_field_national_id": "کد ملی", + "text_field_password": "رمز عبور", + "text_field_position": "سمت", + "text_field_province_id": "استان", + "text_field_roles": "نقش", + "text_field_loading_provinces_list": "درحال دریافت شهر ها", + "text_field_error_fetching_provinces": "خطا در دریافت شهر ها", + "text_field_loading_roles_list": "درحال دریافت نقش ها", + "text_field_error_fetching_roles": "خطا در دریافت نقش ها", + "text_field_please_select_province": "ابتدا استان خود را انتخاب کنید", + "text_field_loading_cities_list": "درحال دریافت شهر ها", + "text_field_error_fetching_cities": "خطا در دریافت شهر ها", + "text_field_new_password": "رمز عبور جدید", + "error_message_full_name": "نام کامل خود را وارد کنید", + "error_message_username": "نام کاربری خود را وارد کنید", + "error_message_email": "پست الکترونیک خود را وارد کنید", + "error_message_phone_number": "شماره همراه خود را وارد کنید", + "error_message_telephone_id": "کد تلفن خود را وارد کنید", + "error_message_gender": "جنسیت خود را وارد کنید", + "error_message_national_id": "کد ملی خود را وارد کنید", + "error_message_password": "رمز عبور خود را وارد کنید", + "error_message_password_regex": "رمز عبور باید حداقل 8 کاراکتر و متشکل از عدد، حرف یا سمبل باشد", + "error_message_position": "سمت خود را وارد کنید", + "error_message_province_id": "استان خود را وارد کنید", + "error_message_roles": "نقش خود را وارد کنید", + "error_message_new_password": "رمز عبور جدید را وارد کنید", + "error_message_new_password_regex": "رمز عبور باید حداقل 8 کاراکتر و متشکل از عدد، حرف یا سمبل باشد", + "button_cancel": "بستن", + "button_confirm": "ثبت", + "delete_expert": "حذف کارشناس", + "are_you_sure_text": "آیا از انجام این عملیات اطمینان دارید؟", + "button_delete": "حذف", + "delete_tooltip": "حذف", + "update_tooltip": "ویرایش", + "update_expert": "ویرایش کارشناس" + }, + "CallHistory": { + "category_name": "موضوع", + "subcategory_name": "زیر موضوع", + "operator": "اپراتور", + "date": "تاریخ", + "call_history_of": "تاریخچه تماس های", + "call_history_not_found": "تاریخچه ای برای تماس دریافتی یافت نشد", + "call_history_error_fetching": "خطا در دریافت تاریخچه تماس دریافتی", + "show_more": "نمایش موارد بیشتر" } } diff --git a/src/components/dashboard/expert-management/DataTable/__tests__/index.test.js b/src/components/dashboard/expert-management/DataTable/__tests__/index.test.js new file mode 100644 index 0000000..0ed26eb --- /dev/null +++ b/src/components/dashboard/expert-management/DataTable/__tests__/index.test.js @@ -0,0 +1,33 @@ +import {render, screen} from "@testing-library/react"; +import MockAppWithProviders from "../../../../../../mocks/AppWithProvider"; +import ExpertManagementDataTable from "@/components/dashboard/expert-management/DataTable"; + +describe("ExpertManagementDatatable Component From Expert Management", () => { + describe("Rendering", () => { + it("Table Headers Rendered", () => { + render(); + const idHeader = screen.queryByText("کد یکتا"); + const nameHeader = screen.queryByText("نام کامل"); + const usernameHeader = screen.queryByText("نام کاربری"); + const emailHeader = screen.queryByText("پست الکترونیک"); + const phone_numberHeader = screen.queryByText("شماره همراه"); + const national_idHeader = screen.queryByText("کد ملی"); + const positionHeader = screen.queryByText("سمت"); + const province_nameHeader = screen.queryByText("استان"); + const role_nameHeader = screen.queryByText("نقش"); + const genderHeader = screen.queryByText("جنسیت"); + const updated_atHeader = screen.queryByText("آخرین بروزرسانی"); + expect(idHeader).toBeInTheDocument(); + expect(nameHeader).toBeInTheDocument(); + expect(usernameHeader).toBeInTheDocument(); + expect(emailHeader).toBeInTheDocument(); + expect(phone_numberHeader).toBeInTheDocument(); + expect(national_idHeader).toBeInTheDocument(); + expect(positionHeader).toBeInTheDocument(); + expect(province_nameHeader).toBeInTheDocument(); + expect(role_nameHeader).toBeInTheDocument(); + expect(genderHeader).toBeInTheDocument(); + expect(updated_atHeader).toBeInTheDocument(); + }); + }); +}); \ No newline at end of file diff --git a/src/components/dashboard/expert-management/DataTable/index.jsx b/src/components/dashboard/expert-management/DataTable/index.jsx new file mode 100644 index 0000000..3f76ca5 --- /dev/null +++ b/src/components/dashboard/expert-management/DataTable/index.jsx @@ -0,0 +1,190 @@ +import DataTable from "@/core/components/DataTable"; +import MuiDatePicker from "@/core/components/MuiDatePicker"; +import {GET_EXPERTS} from "@/core/data/apiRoutes"; +import TableToolbar from "../TableToolbar"; +import TableRowActions from "../TableRowActions"; +import {Box, Typography} from "@mui/material"; +import {useTranslations} from "next-intl"; +import {useMemo} from "react"; +import moment from "jalali-moment"; + +function ExpertManagementDataTable() { + const t = useTranslations(); + const columns = useMemo( + () => [ + { + accessorFn: (row) => row.id, + id: "id", + header: t("ExpertMangement.id"), + enableColumnFilter: true, + datatype: "numeric", + filterFn: "equals", + columnFilterModeOptions: [ + "equals", + "notEquals", + "contains", + "lessThan", + "greaterThan", + "between", + ], + Cell: ({renderedCellValue}) => ( + {renderedCellValue} + ), + }, + { + accessorFn: (row) => row.full_name, + id: "full_name", + header: t("ExpertMangement.name"), + enableColumnFilter: true, + datatype: "text", + filterFn: "contains", + columnFilterModeOptions: ["contains", "equals", "notEquals"], + Cell: ({renderedCellValue}) => ( + {renderedCellValue} + ), + }, + { + accessorFn: (row) => row.username, + id: "username", + header: t("ExpertMangement.username"), + enableColumnFilter: true, + datatype: "text", + filterFn: "contains", + columnFilterModeOptions: ["contains", "equals", "notEquals"], + Cell: ({renderedCellValue}) => ( + {renderedCellValue} + ), + }, + { + accessorFn: (row) => row.email, + id: "email", + header: t("ExpertMangement.email"), + enableColumnFilter: true, + datatype: "text", + filterFn: "contains", + columnFilterModeOptions: ["contains", "equals", "notEquals"], + Cell: ({renderedCellValue}) => ( + {renderedCellValue} + ), + }, + { + accessorFn: (row) => row.phone_number, + id: "phone_number", + header: t("ExpertMangement.phone_number"), + enableColumnFilter: true, + datatype: "numeric", + filterFn: "equals", + columnFilterModeOptions: ["contains", "equals", "notEquals"], + Cell: ({renderedCellValue}) => ( + {renderedCellValue} + ), + }, + { + accessorFn: (row) => row.national_id, + id: "national_id", + header: t("ExpertMangement.national_id"), + enableColumnFilter: true, + datatype: "numeric", + filterFn: "equals", + columnFilterModeOptions: ["contains", "equals", "notEquals"], + Cell: ({renderedCellValue}) => ( + {renderedCellValue} + ), + }, + { + accessorFn: (row) => row.position, + id: "position", + header: t("ExpertMangement.position"), + enableColumnFilter: true, + datatype: "text", + filterFn: "contains", + columnFilterModeOptions: ["contains", "equals", "notEquals"], + Cell: ({renderedCellValue}) => ( + {renderedCellValue} + ), + }, + { + accessorFn: (row) => row.province_fa, + id: "province_id", + header: t("ExpertMangement.province_fa"), + enableColumnFilter: false, + datatype: "text", + filterFn: "contains", + columnFilterModeOptions: ["contains", "equals", "notEquals"], + Cell: ({renderedCellValue}) => ( + {renderedCellValue} + ), + }, + { + accessorFn: (row) => row.roles[0]?.name_fa, + id: "roles[0].name_fa", + header: t("ExpertMangement.role_name"), + enableColumnFilter: false, + enableSorting: false, + datatype: "text", + filterFn: "contains", + columnFilterModeOptions: ["contains", "equals", "notEquals"], + Cell: ({renderedCellValue}) => ( + {renderedCellValue} + ), + }, + { + accessorFn: (row) => row.gender, + id: "gender", + header: t("ExpertMangement.gender"), + enableColumnFilter: false, + datatype: "text", + filterFn: "contains", + columnFilterModeOptions: ["contains", "equals", "notEquals"], + Cell: ({renderedCellValue}) => ( + {renderedCellValue === "male" ? t("ExpertMangement.male") : t("ExpertMangement.female")} + ), + }, + { + accessorFn: (row) => + moment(row.updated_at).locale("fa").format("HH:mm | yyyy/MM/DD"), + id: "updated_at", + header: t("ExpertMangement.updated_at"), + enableColumnFilter: true, + datatype: "date", + filterFn: "lessThan", + columnFilterModeOptions: ["lessThan", "greaterThan"], + Cell: ({renderedCellValue}) => { + return {renderedCellValue}; + }, + Header: ({column}) => <>{column.columnDef.header}, + Filter: ({column}) => { + return ( + + ); + }, + }, + ], + [] + ); + return ( + + + + ); +} + +export default ExpertManagementDataTable; \ No newline at end of file diff --git a/src/components/dashboard/expert-management/Form/ChangePassword/index.jsx b/src/components/dashboard/expert-management/Form/ChangePassword/index.jsx new file mode 100644 index 0000000..ea3d11c --- /dev/null +++ b/src/components/dashboard/expert-management/Form/ChangePassword/index.jsx @@ -0,0 +1,7 @@ +const ChangePassword = () => { + return ( + <> + ) +} + +export default ChangePassword \ No newline at end of file diff --git a/src/components/dashboard/expert-management/Form/CreateForm/CreateContent/PersonalInfo/__tests__/index.test.js b/src/components/dashboard/expert-management/Form/CreateForm/CreateContent/PersonalInfo/__tests__/index.test.js new file mode 100644 index 0000000..913c37a --- /dev/null +++ b/src/components/dashboard/expert-management/Form/CreateForm/CreateContent/PersonalInfo/__tests__/index.test.js @@ -0,0 +1,219 @@ +import {act, fireEvent, render, screen, waitFor} from "@testing-library/react"; +import React from 'react'; +import MockAppWithProviders from "../../../../../../../../../mocks/AppWithProvider"; +import * as Yup from "yup"; +import {Formik} from "formik"; +import PersonalInfo from "@/components/dashboard/expert-management/Form/CreateForm/CreateContent/PersonalInfo"; +import {useTranslations} from "next-intl"; +import Province from "@/components/dashboard/expert-management/Form/CreateForm/CreateContent/RestInfo/Province"; + + +const CreateFormMock = ({Component}) => { + const t = useTranslations(); + const validationSchema = Yup.object().shape({ + full_name: Yup.string().required(t("ExpertMangement.error_message_full_name")), + email: Yup.string().required(t("ExpertMangement.error_message_email")), + phone_number: Yup.string().required(t("ExpertMangement.error_message_phone_number")), + telephone_id: Yup.string().required(t("ExpertMangement.error_message_telephone_id")), + national_id: Yup.string().required(t("ExpertMangement.error_message_national_id")), + gender: Yup.string().required(t("ExpertMangement.error_message_gender")) + }); + return ( + { + }} + > + {formikProps => ()} + + ); +}; + + +describe("PersonalInfo Component From Expert Management (create)", () => { + describe("Rendering", () => { + it("full_name TextField Rendered", async () => { + render(); + await waitFor(() => { + const fullNameTextField = screen.queryByLabelText('نام کامل'); + expect(fullNameTextField).toBeInTheDocument(); + }); + }); + it("Email TextField Rendered", async () => { + render(); + await waitFor(() => { + const emailTextField = screen.queryByLabelText('پست الکترونیک'); + expect(emailTextField).toBeInTheDocument(); + }); + }); + it("Phone Number TextField Rendered", async () => { + render(); + await waitFor(() => { + const phoneNumberTextField = screen.queryByLabelText('شماره همراه'); + expect(phoneNumberTextField).toBeInTheDocument(); + }); + }); + it("telephone id TextField Rendered", async () => { + render(); + await waitFor(() => { + const telephoneIdTextField = screen.queryByLabelText('کد تلفن'); + expect(telephoneIdTextField).toBeInTheDocument(); + }); + }); + it("National Id TextField Rendered", async () => { + render(); + await waitFor(() => { + const nationalIdTextField = screen.queryByLabelText('کد ملی'); + expect(nationalIdTextField).toBeInTheDocument(); + }); + }); + it("Gender Select Box Rendered", async () => { + render(); + await waitFor(() => { + const genderSelectBox = screen.getByTestId('select-box'); + expect(genderSelectBox).toBeInTheDocument(); + }); + }); + }); + describe("Behavioral", () => { + it("Name TextField Work Correctly", async () => { + render(); + const nameTextField = screen.queryByLabelText('نام کامل'); + fireEvent.change(nameTextField, {target: {value: 'exampleName'}}); + await act(() => { + expect(nameTextField).toHaveValue('exampleName'); + }) + }); + it("Email TextField Work Correctly", async () => { + render(); + const emailTextField = screen.queryByLabelText('پست الکترونیک'); + fireEvent.change(emailTextField, {target: {value: 'exampleEmail'}}); + await act(() => { + expect(emailTextField).toHaveValue('exampleEmail'); + }) + }); + it("Phone Number TextField Work Correctly", async () => { + render(); + const phoneNumberTextField = screen.queryByLabelText('شماره همراه'); + fireEvent.change(phoneNumberTextField, {target: {value: 'examplePhoneNumber'}}); + await act(() => { + expect(phoneNumberTextField).toHaveValue('examplePhoneNumber'); + }) + }); + it("Telephone Id TextField Work Correctly", async () => { + render(); + const telephoneIdTextField = screen.queryByLabelText('کد تلفن'); + fireEvent.change(telephoneIdTextField, {target: {value: 'exampleTelephoneId'}}); + await act(() => { + expect(telephoneIdTextField).toHaveValue('exampleTelephoneId'); + }) + }); + it("National Id TextField Work Correctly", async () => { + render(); + const nationalIdTextField = screen.queryByLabelText('کد ملی'); + fireEvent.change(nationalIdTextField, {target: {value: 'exampleNationalId'}}); + await act(() => { + expect(nationalIdTextField).toHaveValue('exampleNationalId'); + }) + }); + it("Gender Select Box Work Correctly", async () => { + render(); + const genderSelectBox = screen.getByTestId('select-box'); + const genderSelectOpener = await waitFor(() => screen.getByTestId("option-opener-gender")); + + await waitFor(() => { + expect(genderSelectBox).toBeInTheDocument(); + }); + + fireEvent.mouseDown(genderSelectOpener); + await waitFor(() => { + const selectItem = screen.queryByText("مرد"); + expect(selectItem).toBeInTheDocument(); + }); + + }); + }); + describe("validation", () => { + it('Should See Error When Name Input Is Empty', async () => { + render(); + + const nameInput = screen.queryByLabelText('نام کامل'); + + fireEvent.change(nameInput, {target: {value: ''}}); + fireEvent.blur(nameInput); + + await waitFor(() => { + expect(screen.queryByText("نام کامل خود را وارد کنید")).toBeInTheDocument() + }); + }); + it('Should See Error When Email Input Is Empty', async () => { + render(); + + const emailInput = screen.queryByLabelText('پست الکترونیک'); + + fireEvent.change(emailInput, {target: {value: ''}}); + fireEvent.blur(emailInput); + + await waitFor(() => { + expect(screen.queryByText("پست الکترونیک خود را وارد کنید")).toBeInTheDocument() + }); + }); + it('Should See Error When Phone Number Input Is Empty', async () => { + render(); + + const phoneNumberInput = screen.queryByLabelText('شماره همراه'); + + fireEvent.change(phoneNumberInput, {target: {value: ''}}); + fireEvent.blur(phoneNumberInput); + + await waitFor(() => { + expect(screen.queryByText("شماره همراه خود را وارد کنید")).toBeInTheDocument() + }); + }); + it('Should See Error When telephone Id Input Is Empty', async () => { + render(); + + const telephoneIdTextField = screen.queryByLabelText('کد تلفن'); + + fireEvent.change(telephoneIdTextField, {target: {value: ''}}); + fireEvent.blur(telephoneIdTextField); + + await waitFor(() => { + expect(screen.queryByText("کد تلفن خود را وارد کنید")).toBeInTheDocument() + }); + }); + it('Should See Error When National Id Input Is Empty', async () => { + render(); + + const nationalIdInput = screen.queryByLabelText('کد ملی'); + + fireEvent.change(nationalIdInput, {target: {value: ''}}); + fireEvent.blur(nationalIdInput); + + await waitFor(() => { + expect(screen.queryByText("کد ملی خود را وارد کنید")).toBeInTheDocument() + }); + }); + it('Should Select An Item With Valid Value of gender', async () => { + render(); + const genderInput = screen.getByTestId("input-gender") + const genderSelectOpener = await waitFor(() => screen.getByTestId("option-opener-gender")); + + fireEvent.mouseDown(genderSelectOpener); + const selectItem = await waitFor(() => screen.queryByText("مرد")); + expect(selectItem).toBeInTheDocument(); + fireEvent.click(selectItem); + await waitFor(() => { + expect(genderInput.value).toBe('male') + }); + }); + }) +}); \ No newline at end of file diff --git a/src/components/dashboard/expert-management/Form/CreateForm/CreateContent/PersonalInfo/index.jsx b/src/components/dashboard/expert-management/Form/CreateForm/CreateContent/PersonalInfo/index.jsx new file mode 100644 index 0000000..f92bf38 --- /dev/null +++ b/src/components/dashboard/expert-management/Form/CreateForm/CreateContent/PersonalInfo/index.jsx @@ -0,0 +1,128 @@ +import {FormControl, FormHelperText, Grid, InputLabel, MenuItem, Select, TextField} from "@mui/material"; +import {useTranslations} from "next-intl"; + +const PersonalInfo = ({formik}) => { + const t = useTranslations(); + + const genderList = [ + {id: 1, name_en: "male", name_fa: "مرد"}, + {id: 2, name_en: "female", name_fa: "زن"} + ] + + return ( + + + + + + + + + + + + + + + + + + + {t("ExpertMangement.text_field_gender")} + + + {formik.touched.gender && formik.errors.gender ? formik.errors.gender : ""} + + + + + ) +} +export default PersonalInfo \ No newline at end of file diff --git a/src/components/dashboard/expert-management/Form/CreateForm/CreateContent/RestInfo/PositionAndRole/__tests__/index.test.js b/src/components/dashboard/expert-management/Form/CreateForm/CreateContent/RestInfo/PositionAndRole/__tests__/index.test.js new file mode 100644 index 0000000..6f32bac --- /dev/null +++ b/src/components/dashboard/expert-management/Form/CreateForm/CreateContent/RestInfo/PositionAndRole/__tests__/index.test.js @@ -0,0 +1,96 @@ +import {fireEvent, render, screen, waitFor} from "@testing-library/react"; +import React from 'react'; +import {useTranslations} from "next-intl"; +import * as Yup from "yup"; +import {Formik} from "formik"; +import MockAppWithProviders from "../../../../../../../../../../mocks/AppWithProvider"; +import PositionAndRole from "../../PositionAndRole"; + +const CreateFormMock = ({Component}) => { + const t = useTranslations(); + const validationSchema = Yup.object().shape({ + position: Yup.string().required(t("ExpertMangement.error_message_position")), + roles: Yup.string().required(t("ExpertMangement.error_message_roles")) + }); + return ( + { + }} + > + {formikProps => ()} + + ); +}; + +describe("PositionAndRole Component From Expert Management (create)", () => { + describe("Rendering", () => { + it("Position TextField Rendered", async () => { + render(); + await waitFor(() => { + const positionTextField = screen.queryByLabelText('سمت'); + expect(positionTextField).toBeInTheDocument(); + }); + }); + it("Role Select Box Rendered", async () => { + render(); + const roleSelect = screen.getByTestId('select-box'); + await waitFor(() => { + expect(roleSelect).toBeInTheDocument(); + }); + }); + }); + describe("Behavioral", () => { + it("Position TextField Work Correctly", async () => { + render(); + const positionTextField = screen.queryByLabelText('سمت'); + fireEvent.change(positionTextField, {target: {value: 'examplePosition'}}); + await waitFor(() => { + expect(positionTextField).toHaveValue('examplePosition'); + }) + }); + it("Role Select Box Work Correctly", async () => { + render(); + const roleSelect = screen.getByTestId("option-opener-role"); + await waitFor(() => { + expect(roleSelect).toBeInTheDocument(); + }); + fireEvent.mouseDown(roleSelect); + await waitFor(() => { + const selectItem = screen.queryByText("ادمین"); + expect(selectItem).toBeInTheDocument(); + }); + }); + }); + describe("validation", () => { + it('Should See Error When Position Input Is Empty', async () => { + render(); + + const positionInput = screen.queryByLabelText('سمت'); + + fireEvent.change(positionInput, {target: {value: ''}}); + fireEvent.blur(positionInput); + + await waitFor(() => { + expect(screen.queryByText("سمت خود را وارد کنید")).toBeInTheDocument() + }); + }); + it('Should Select An Item With Valid Value of role', async () => { + render(); + const roleInput = await waitFor(() => screen.getByTestId("input-role-id")); + const roleSelectOpener = await waitFor(() => screen.getByTestId("option-opener-role")); + + fireEvent.mouseDown(roleSelectOpener); + const selectItem = await waitFor(() => screen.queryByText("ادمین")); + expect(selectItem).toBeInTheDocument(); + fireEvent.click(selectItem); + await waitFor(() => { + expect(roleInput.value).toBe('1') + }); + }); + }) +}); \ No newline at end of file diff --git a/src/components/dashboard/expert-management/Form/CreateForm/CreateContent/RestInfo/PositionAndRole/index.jsx b/src/components/dashboard/expert-management/Form/CreateForm/CreateContent/RestInfo/PositionAndRole/index.jsx new file mode 100644 index 0000000..6befbff --- /dev/null +++ b/src/components/dashboard/expert-management/Form/CreateForm/CreateContent/RestInfo/PositionAndRole/index.jsx @@ -0,0 +1,74 @@ +import {FormControl, FormHelperText, Grid, InputLabel, MenuItem, Select, TextField} from "@mui/material"; +import {useTranslations} from "next-intl"; +import useRequest from "@/lib/app/hooks/useRequest"; +import useRole from "@/lib/app/hooks/useRole"; +import {log} from "next/dist/server/typescript/utils"; + +const PositionAndRole = ({formik}) => { + const t = useTranslations(); + const requestServer = useRequest() + const {roleList, isLoadingRoleList, errorRoleList} = useRole(); + + return ( + + + + + + + {t("ExpertMangement.text_field_roles")} + + + {formik.touched.roles && formik.errors.roles ? formik.errors.roles : ""} + + + + + ) +} +export default PositionAndRole \ No newline at end of file diff --git a/src/components/dashboard/expert-management/Form/CreateForm/CreateContent/RestInfo/Province/__tests__/index.test.js b/src/components/dashboard/expert-management/Form/CreateForm/CreateContent/RestInfo/Province/__tests__/index.test.js new file mode 100644 index 0000000..87da19f --- /dev/null +++ b/src/components/dashboard/expert-management/Form/CreateForm/CreateContent/RestInfo/Province/__tests__/index.test.js @@ -0,0 +1,71 @@ +import {fireEvent, render, screen, waitFor} from "@testing-library/react"; +import React from 'react'; +import {useTranslations} from "next-intl"; +import * as Yup from "yup"; +import {Formik} from "formik"; +import Province from "../../Province"; +import MockAppWithProviders from "../../../../../../../../../../mocks/AppWithProvider"; + +const CreateFormMock = ({Component}) => { + const t = useTranslations(); + const validationSchema = Yup.object().shape({ + province_id: Yup.string().required(t("ExpertMangement.error_message_province_id")), + }); + return ( + { + }} + > + {formikProps => ()} + + ); +}; + +describe("Province Component From Expert Management (create)", () => { + describe("Rendering", () => { + it("Province Select Box Rendered", async () => { + render(); + await waitFor(() => { + const provinceSelectBox = screen.getByTestId('select-box'); + expect(provinceSelectBox).toBeInTheDocument(); + }); + }); + }); + describe("Behavioral", () => { + it("Province Select Box Work Correctly", async () => { + render(); + const provinceSelectBox = screen.getByTestId('select-box'); + const provinceSelectOpener = await waitFor(() => screen.getByTestId("option-opener-province")); + + await waitFor(() => { + expect(provinceSelectBox).toBeInTheDocument(); + }); + + fireEvent.mouseDown(provinceSelectOpener); + await waitFor(() => { + const selectItem = screen.queryByText("آذربایجان شرقی"); + expect(selectItem).toBeInTheDocument(); + }); + + }); + }); + describe("validation", () => { + it('Should Select An Item With Valid Value of province', async () => { + render(); + const provinceInput = screen.getByTestId("input-province-id") + const provinceSelectOpener = await waitFor(() => screen.getByTestId("option-opener-province")); + + fireEvent.mouseDown(provinceSelectOpener); + const selectItem = await waitFor(() => screen.queryByText("آذربایجان شرقی")); + expect(selectItem).toBeInTheDocument(); + fireEvent.click(selectItem); + await waitFor(() => { + expect(provinceInput.value).toBe('1') + }); + }); + }) +}); \ No newline at end of file diff --git a/src/components/dashboard/expert-management/Form/CreateForm/CreateContent/RestInfo/Province/index.jsx b/src/components/dashboard/expert-management/Form/CreateForm/CreateContent/RestInfo/Province/index.jsx new file mode 100644 index 0000000..5ac94b2 --- /dev/null +++ b/src/components/dashboard/expert-management/Form/CreateForm/CreateContent/RestInfo/Province/index.jsx @@ -0,0 +1,61 @@ +import {FormControl, FormHelperText, Grid, InputLabel, MenuItem, Select} from "@mui/material"; +import {useTranslations} from "next-intl"; +import useProvince from "@/lib/app/hooks/useProvince"; +import useRequest from "@/lib/app/hooks/useRequest"; + +const Province = ({formik}) => { + const t = useTranslations(); + const requestServer = useRequest() + const {provinceList, isLoadingProvinceList, errorProvinceList} = useProvince(); + + return ( + + + + {t("ExpertMangement.text_field_province_id")} + + + {formik.touched.province_id && formik.errors.province_id ? formik.errors.province_id : ""} + + + + + ) +} +export default Province \ No newline at end of file diff --git a/src/components/dashboard/expert-management/Form/CreateForm/CreateContent/RestInfo/index.jsx b/src/components/dashboard/expert-management/Form/CreateForm/CreateContent/RestInfo/index.jsx new file mode 100644 index 0000000..072c096 --- /dev/null +++ b/src/components/dashboard/expert-management/Form/CreateForm/CreateContent/RestInfo/index.jsx @@ -0,0 +1,12 @@ +import Province from "./Province"; +import PositionAndRole from "./PositionAndRole"; + +const RestInfo = ({formik}) => { + return ( + <> + + + + ) +} +export default RestInfo \ No newline at end of file diff --git a/src/components/dashboard/expert-management/Form/CreateForm/CreateContent/UserInfo/__test__/index.test.js b/src/components/dashboard/expert-management/Form/CreateForm/CreateContent/UserInfo/__test__/index.test.js new file mode 100644 index 0000000..519cf9d --- /dev/null +++ b/src/components/dashboard/expert-management/Form/CreateForm/CreateContent/UserInfo/__test__/index.test.js @@ -0,0 +1,123 @@ +import {act, fireEvent, render, screen, waitFor} from "@testing-library/react"; +import React from 'react'; +import MockAppWithProviders from "../../../../../../../../../mocks/AppWithProvider"; +import {useTranslations} from "next-intl"; +import * as Yup from "yup"; +import {Formik} from "formik"; +import UserInfo from "../../UserInfo"; + +const CreateFormMock = ({Component}) => { + const t = useTranslations(); + const validationSchema = Yup.object().shape({ + username: Yup.string().required(t("ExpertMangement.error_message_username")), + password: Yup.string() + .required(t("ExpertMangement.error_message_password")) + .matches( + /^(?=.*[a-zA-Z])(?=.*\d).{8,}$/, + t("ExpertMangement.error_message_password_regex") + ), + }); + return ( + { + }} + > + {formikProps => ()} + + ); +}; + +describe("UserInfo Component From Expert Management (create)", () => { + describe("Rendering", () => { + it("UserName TextField Rendered", async () => { + render(); + await waitFor(() => { + const usernameTextField = screen.queryByLabelText('نام کاربری'); + expect(usernameTextField).toBeInTheDocument(); + }); + }); + it("Password TextField Rendered", async () => { + render(); + await waitFor(() => { + const passwordTextField = screen.queryByLabelText('رمز عبور'); + expect(passwordTextField).toBeInTheDocument(); + }); + + }); + }); + describe("Behavioral", () => { + it("Username TextField Work Correctly", async () => { + render(); + const usernameTextField = screen.queryByLabelText('نام کاربری'); + fireEvent.change(usernameTextField, {target: {value: 'exampleUsername'}}); + await act(() => { + expect(usernameTextField).toHaveValue('exampleUsername'); + }) + }); + it("Password TextField Work Correctly", async () => { + render(); + const passwordTextField = screen.queryByLabelText('رمز عبور'); + fireEvent.change(passwordTextField, {target: {value: 'examplePassword'}}); + await act(() => { + expect(passwordTextField).toHaveValue('examplePassword'); + }) + }); + }); + describe("validation", () => { + it('Should See Error When Username Input Is Empty', async () => { + render(); + + const usernameInput = screen.queryByLabelText('نام کاربری'); + + fireEvent.change(usernameInput, {target: {value: ''}}); + fireEvent.blur(usernameInput); + + await waitFor(() => { + expect(screen.queryByText("نام کاربری خود را وارد کنید")).toBeInTheDocument() + }); + }); + it('Should See Error When Password Input Is Empty', async () => { + render(); + + const passwordInput = screen.queryByLabelText('رمز عبور'); + + fireEvent.change(passwordInput, {target: {value: ''}}); + fireEvent.blur(passwordInput); + + await waitFor(() => { + expect(screen.queryByText("رمز عبور خود را وارد کنید")).toBeInTheDocument() + }); + }); + it('Should See Error When Password Field Is Not On Correct Format', async () => { + render(); + + const passwordInput = screen.queryByLabelText('رمز عبور'); + + // check without text or symbol + fireEvent.change(passwordInput, {target: {value: '12345678'}}); + fireEvent.blur(passwordInput); + await waitFor(() => { + expect(screen.queryByText("رمز عبور باید حداقل 8 کاراکتر و متشکل از عدد، حرف یا سمبل باشد")).toBeInTheDocument() + }); + + // check without number + fireEvent.change(passwordInput, {target: {value: 'abcdefgh'}}); + fireEvent.blur(passwordInput); + await waitFor(() => { + expect(screen.queryByText("رمز عبور باید حداقل 8 کاراکتر و متشکل از عدد، حرف یا سمبل باشد")).toBeInTheDocument() + }); + + // check under 8 character + fireEvent.change(passwordInput, {target: {value: '11Sa'}}); + fireEvent.blur(passwordInput); + await waitFor(() => { + expect(screen.queryByText("رمز عبور باید حداقل 8 کاراکتر و متشکل از عدد، حرف یا سمبل باشد")).toBeInTheDocument() + }); + }); + }) +}); \ No newline at end of file diff --git a/src/components/dashboard/expert-management/Form/CreateForm/CreateContent/UserInfo/index.jsx b/src/components/dashboard/expert-management/Form/CreateForm/CreateContent/UserInfo/index.jsx new file mode 100644 index 0000000..2455b26 --- /dev/null +++ b/src/components/dashboard/expert-management/Form/CreateForm/CreateContent/UserInfo/index.jsx @@ -0,0 +1,61 @@ +import {Grid, IconButton, InputAdornment, TextField} from "@mui/material"; +import {useTranslations} from "next-intl"; +import {Visibility, VisibilityOff} from "@mui/icons-material"; +import {useState} from "react"; + +const UserInfo = ({formik}) => { + const t = useTranslations(); + const [showPassword, setShowPassword] = useState(false); + + const handleClickShowPassword = () => { + setShowPassword(!showPassword); + }; + + return ( + <> + + + + + + + + {showPassword ? : } + + + ), + }} + sx={{width: "100%"}} + /> + + + + ) +} +export default UserInfo \ No newline at end of file diff --git a/src/components/dashboard/expert-management/Form/CreateForm/CreateContent/__tests__/index.test.js b/src/components/dashboard/expert-management/Form/CreateForm/CreateContent/__tests__/index.test.js new file mode 100644 index 0000000..85751c5 --- /dev/null +++ b/src/components/dashboard/expert-management/Form/CreateForm/CreateContent/__tests__/index.test.js @@ -0,0 +1,67 @@ +import {fireEvent, render, screen, waitFor} from "@testing-library/react"; +import React from 'react'; +import CreateContent from "../../CreateContent"; +import MockAppWithProviders from "../../../../../../../../mocks/AppWithProvider"; + +function selectDropdownItem(screen, openerTestId, itemText) { + const opener = screen.getByTestId(openerTestId); + fireEvent.mouseDown(opener); + const selectItem = screen.queryByText(itemText); + fireEvent.click(selectItem); +} + +function setInputValue(screen, inputElement, value) { + fireEvent.change(inputElement, {target: {value}}); +} + +describe("CreateContent Component From Expert Management (create)", () => { + describe("Rendering", () => { + it("close button Rendered", async () => { + render(); + await waitFor(() => { + const cancelBtn = screen.queryByText('بستن'); + expect(cancelBtn).toBeInTheDocument(); + }); + }); + it("confirm button Rendered", async () => { + render(); + await waitFor(() => { + const cancelBtn = screen.queryByText('ثبت'); + expect(cancelBtn).toBeInTheDocument(); + }); + }); + }); + describe("Form Submission", () => { + it('Should enable the submit button when the inputs are valid', async () => { + render(); + + const submitButton = screen.queryByText('ثبت'); + + const nameInput = screen.queryByLabelText('نام کامل'); + const usernameInput = screen.queryByLabelText('نام کاربری'); + const emailInput = screen.queryByLabelText('پست الکترونیک'); + const phoneNumberInput = screen.queryByLabelText('شماره همراه'); + const telephoneIdInput = screen.queryByLabelText('کد تلفن'); + const nationalIdInput = screen.queryByLabelText('کد ملی'); + const passwordInput = screen.queryByLabelText('رمز عبور'); + const positionInput = screen.queryByLabelText('سمت'); + + selectDropdownItem(screen, "option-opener-province", "آذربایجان شرقی"); + selectDropdownItem(screen, "option-opener-role", "ادمین"); + selectDropdownItem(screen, "option-opener-gender", "مرد"); + + setInputValue(screen, nameInput, 'nameTest'); + setInputValue(screen, usernameInput, 'usernameTest'); + setInputValue(screen, emailInput, 'emailTest'); + setInputValue(screen, phoneNumberInput, 'phoneNumberTest'); + setInputValue(screen, telephoneIdInput, 'telephoneIdTest'); + setInputValue(screen, nationalIdInput, 'nationalIdTest'); + setInputValue(screen, passwordInput, 'passwordTest'); + setInputValue(screen, positionInput, 'positionTest'); + + await waitFor(() => { + expect(submitButton).not.toBeDisabled(); + }); + }); + }) +}); \ No newline at end of file diff --git a/src/components/dashboard/expert-management/Form/CreateForm/CreateContent/index.jsx b/src/components/dashboard/expert-management/Form/CreateForm/CreateContent/index.jsx new file mode 100644 index 0000000..084da99 --- /dev/null +++ b/src/components/dashboard/expert-management/Form/CreateForm/CreateContent/index.jsx @@ -0,0 +1,116 @@ +import {Box, Button, Chip, DialogActions, DialogContent, Divider,} from "@mui/material"; +import * as Yup from "yup"; +import {useTranslations} from "next-intl"; +import {useFormik} from "formik"; +import useRequest from "@/lib/app/hooks/useRequest"; +import {ADD_EXPERT} from "@/core/data/apiRoutes"; +import PersonalInfo from "@/components/dashboard/expert-management/Form/CreateForm/CreateContent/PersonalInfo"; +import UserInfo from "@/components/dashboard/expert-management/Form/CreateForm/CreateContent/UserInfo"; +import RestInfo from "@/components/dashboard/expert-management/Form/CreateForm/CreateContent/RestInfo"; + +const CreateContent = ({setOpenCreateDialog, mutate}) => { + const t = useTranslations(); + const requestServer = useRequest() + + const validationSchema = Yup.object().shape({ + full_name: Yup.string().required(t("ExpertMangement.error_message_full_name")), + username: Yup.string().required(t("ExpertMangement.error_message_username")), + email: Yup.string().required(t("ExpertMangement.error_message_email")), + phone_number: Yup.string().required(t("ExpertMangement.error_message_phone_number")), + telephone_id: Yup.string().required(t("ExpertMangement.error_message_telephone_id")), + gender: Yup.string().required(t("ExpertMangement.error_message_gender")), + national_id: Yup.string().required(t("ExpertMangement.error_message_national_id")), + password: Yup.string() + .required(t("ExpertMangement.error_message_password")) + .matches( + /^(?=.*[a-zA-Z])(?=.*\d).{8,}$/, + t("ExpertMangement.error_message_password_regex") + ), + position: Yup.string().required(t("ExpertMangement.error_message_position")), + province_id: Yup.string().required(t("ExpertMangement.error_message_province_id")), + roles: Yup.string().required(t("ExpertMangement.error_message_roles")) + }); + + const formik = useFormik({ + initialValues: { + full_name: "", + username: "", + email: "", + phone_number: "", + telephone_id: "", + gender: "", + national_id: "", + password: "", + position: "", + province_id: "", + roles: "" + }, + validationSchema, + onSubmit: (values, {setSubmitting}) => { + const formData = new FormData(); + formData.append("full_name", values.full_name); + formData.append("national_id", values.national_id); + formData.append("phone_number", values.phone_number); + formData.append("telephone_id", values.telephone_id); + formData.append("gender", values.gender); + formData.append("email", values.email); + formData.append("username", values.username); + formData.append("password", values.password); + formData.append("province_id", values.province_id); + formData.append("position", values.position); + formData.append("roles", values.roles); + + requestServer(`${ADD_EXPERT}`, 'post', {auth: true, data: formData}) + .then((response) => { + setOpenCreateDialog(false) + mutate() + }).catch(() => { + }).finally(() => { + setSubmitting(false); + }); + }, + }); + + return ( + <> + + + + + + + + + + + + + + + + + + + + + + + + + + ) +} + +export default CreateContent \ No newline at end of file diff --git a/src/components/dashboard/expert-management/Form/CreateForm/__tests__/index.test.js b/src/components/dashboard/expert-management/Form/CreateForm/__tests__/index.test.js new file mode 100644 index 0000000..8bf825d --- /dev/null +++ b/src/components/dashboard/expert-management/Form/CreateForm/__tests__/index.test.js @@ -0,0 +1,131 @@ +import {act, fireEvent, render, screen, waitFor} from "@testing-library/react"; +import React from 'react'; +import MockAppWithProviders from "../../../../../../../mocks/AppWithProvider"; +import CreateForm from "../../CreateForm"; + +function selectDropdownItem(screen, openerTestId, itemText) { + const opener = screen.getByTestId(openerTestId); + fireEvent.mouseDown(opener); + const selectItem = screen.queryByText(itemText); + fireEvent.click(selectItem); +} + +function setInputValue(screen, inputElement, value) { + fireEvent.change(inputElement, {target: {value}}); +} + +describe("CreateForm Component From Expert Management", () => { + describe("Rendering", () => { + it("Create Button Rendered", () => { + render(); + const CreateButton = screen.getByRole('button', {name: "افزودن کارشناس"}); + expect(CreateButton).toBeInTheDocument(); + }); + it("Dialog Header Rendered", () => { + render(); + const CreateDialogHeader = screen.queryByText("افزودن کارشناس"); + expect(CreateDialogHeader).toBeInTheDocument(); + }); + }); + describe("Behavioral", () => { + it("by Clicking Create Button Dialog Should Append To Document", async () => { + render(); + const CreateButton = screen.getByRole('button', {name: "افزودن کارشناس"}); + fireEvent.click(CreateButton); + await act(() => { + const CreateDialog = screen.getByRole('dialog', {name: "افزودن کارشناس"}) + expect(CreateDialog).toBeInTheDocument(); + }); + }); + }); + describe("Form Submission", () => { + it('Should request to api and if get success close dialog', async () => { + render(); + + const CreateButton = screen.getByRole('button', {name: "افزودن کارشناس"}); + fireEvent.click(CreateButton); + + const submitButton = screen.queryByText('ثبت'); + + const nameInput = screen.queryByLabelText('نام کامل'); + const usernameInput = screen.queryByLabelText('نام کاربری'); + const emailInput = screen.queryByLabelText('پست الکترونیک'); + const phoneNumberInput = screen.queryByLabelText('شماره همراه'); + const telephoneIdInput = screen.queryByLabelText('کد تلفن'); + const nationalIdInput = screen.queryByLabelText('کد ملی'); + const passwordInput = screen.queryByLabelText('رمز عبور'); + const positionInput = screen.queryByLabelText('سمت'); + + selectDropdownItem(screen, "option-opener-province", "آذربایجان شرقی"); + selectDropdownItem(screen, "option-opener-role", "ادمین"); + selectDropdownItem(screen, "option-opener-gender", "مرد"); + + setInputValue(screen, nameInput, 'nameTest'); + setInputValue(screen, usernameInput, 'usernameTest'); + setInputValue(screen, emailInput, 'emailTest@gmail.com'); + setInputValue(screen, phoneNumberInput, '0914577458'); + setInputValue(screen, telephoneIdInput, '091'); + setInputValue(screen, nationalIdInput, 'nationalIdTest'); + setInputValue(screen, passwordInput, 'passwordTest12'); + setInputValue(screen, positionInput, 'positionTest'); + + expect(submitButton).not.toBeDisabled(); + fireEvent.click(submitButton); + + await waitFor(() => { + const dialogContent = screen.queryByTestId('create-dialog-content'); + expect(dialogContent).not.toBeInTheDocument(); + }); + }); + it('Should request to api and if get error keep previous data', async () => { + render(); + + const CreateButton = screen.getByRole('button', {name: "افزودن کارشناس"}); + fireEvent.click(CreateButton); + + const submitButton = screen.queryByText('ثبت'); + + const nameInput = screen.queryByLabelText('نام کامل'); + const usernameInput = screen.queryByLabelText('نام کاربری'); + const emailInput = screen.queryByLabelText('پست الکترونیک'); + const phoneNumberInput = screen.queryByLabelText('شماره همراه'); + const telephoneIdInput = screen.queryByLabelText('کد تلفن'); + const nationalIdInput = screen.queryByLabelText('کد ملی'); + const passwordInput = screen.queryByLabelText('رمز عبور'); + const positionInput = screen.queryByLabelText('سمت'); + const provinceInput = screen.getByTestId("input-province-id") + const roleInput = screen.getByTestId("input-role-id") + const genderInput = screen.getByTestId("input-gender") + + selectDropdownItem(screen, "option-opener-province", "آذربایجان شرقی"); + selectDropdownItem(screen, "option-opener-role", "ادمین"); + selectDropdownItem(screen, "option-opener-gender", "مرد"); + + setInputValue(screen, nameInput, 'nameTest'); + setInputValue(screen, usernameInput, 'usernameTest'); + setInputValue(screen, emailInput, 'emailTest@gmail.com'); + setInputValue(screen, phoneNumberInput, '0914577458'); + setInputValue(screen, telephoneIdInput, '091'); + setInputValue(screen, nationalIdInput, 'nationalIdTest'); + setInputValue(screen, passwordInput, 'passwordTest12'); + setInputValue(screen, positionInput, 'positionTest'); + + expect(submitButton).not.toBeDisabled(); + await fireEvent.click(submitButton); + + await waitFor(() => { + expect(nameInput).toHaveValue('nameTest'); + expect(usernameInput).toHaveValue('usernameTest'); + expect(emailInput).toHaveValue('emailTest@gmail.com'); + expect(phoneNumberInput).toHaveValue('0914577458'); + expect(telephoneIdInput).toHaveValue('091'); + expect(nationalIdInput).toHaveValue('nationalIdTest'); + expect(passwordInput).toHaveValue('passwordTest12'); + expect(positionInput).toHaveValue('positionTest'); + expect(provinceInput).toHaveValue('1'); + expect(roleInput).toHaveValue('1'); + expect(genderInput).toHaveValue('male'); + }); + }); + }) +}); \ No newline at end of file diff --git a/src/components/dashboard/expert-management/Form/CreateForm/index.jsx b/src/components/dashboard/expert-management/Form/CreateForm/index.jsx new file mode 100644 index 0000000..92f822f --- /dev/null +++ b/src/components/dashboard/expert-management/Form/CreateForm/index.jsx @@ -0,0 +1,39 @@ +import {Button, Dialog, DialogTitle, Stack, Tooltip} from "@mui/material"; +import {useTranslations} from "next-intl"; +import DataSaverOnIcon from "@mui/icons-material/DataSaverOn"; +import {useState} from "react"; +import CreateContent from "./CreateContent"; + +const Create = ({mutate}) => { + const t = useTranslations(); + const [openCreateDialog, setOpenCreateDialog] = useState(false); + + return ( + <> + + + + + + + {t("ExpertMangement.create")} + + + + ) +} + +export default Create \ No newline at end of file diff --git a/src/components/dashboard/expert-management/Form/DeleteForm/__tests__/index.test.js b/src/components/dashboard/expert-management/Form/DeleteForm/__tests__/index.test.js new file mode 100644 index 0000000..f2e52b0 --- /dev/null +++ b/src/components/dashboard/expert-management/Form/DeleteForm/__tests__/index.test.js @@ -0,0 +1,66 @@ +import {fireEvent, render, screen, waitFor} from "@testing-library/react"; +import MockAppWithProviders from "../../../../../../../mocks/AppWithProvider"; +import React from "react"; +import DeleteForm from "@/components/dashboard/expert-management/Form/DeleteForm"; + +describe("CreateForm Component From Expert Management", () => { + describe("Rendering", () => { + it("Delete Expert Button And Tooltip Rendered", () => { + render(); + const DeleteButton = screen.getByTestId('delete-button'); + expect(DeleteButton).toBeInTheDocument(); + }); + it("Delete Dialog Rendered", () => { + render(); + const DeleteButton = screen.getByTestId('delete-button'); + fireEvent.click(DeleteButton); + const DeleteDialogHeader = screen.queryByText("حذف کارشناس"); + expect(DeleteDialogHeader).toBeInTheDocument(); + }); + it("Delete Dialog Text Rendered", () => { + render(); + const DeleteButton = screen.getByTestId('delete-button'); + fireEvent.click(DeleteButton); + const DeleteDialogText = screen.queryByText("آیا از انجام این عملیات اطمینان دارید؟"); + expect(DeleteDialogText).toBeInTheDocument(); + }); + it("Delete Dialog Delete Button Rendered", () => { + render(); + const DeleteButton = screen.getByTestId('delete-button'); + fireEvent.click(DeleteButton); + const DeleteButtonDialog = screen.getByRole('button', {name: "حذف"}); + expect(DeleteButtonDialog).toBeInTheDocument(); + }); + it("Delete Dialog Cancel Button Rendered", () => { + render(); + const DeleteButton = screen.getByTestId('delete-button'); + fireEvent.click(DeleteButton); + const CancelButtonDialog = screen.getByRole('button', {name: "بستن"}); + expect(CancelButtonDialog).toBeInTheDocument(); + }); + }); + describe("Behavioral", () => { + it("By Clicking Delete Button Dialog Should Append To Document", async () => { + render(); + const DeleteButton = screen.getByTestId('delete-button'); + fireEvent.click(DeleteButton); + await waitFor(() => { + const DeleteDialog = screen.getByRole('dialog', {name: "حذف کارشناس"}) + expect(DeleteDialog).toBeInTheDocument(); + }); + }); + }); + describe("Form Submission", () => { + it("By Clicking Delete Button Request To Api And Delete Item", async () => { + render(); + const DeleteButton = screen.getByTestId('delete-button'); + fireEvent.click(DeleteButton); + const DeleteButtonDialog = screen.getByRole('button', {name: "حذف"}); + fireEvent.click(DeleteButtonDialog); + await waitFor(() => { + const DeleteDialog = screen.queryByRole('dialog', {name: "حذف کارشناس"}); + expect(DeleteDialog).not.toBeInTheDocument(); + }); + }); + }); +}); \ No newline at end of file diff --git a/src/components/dashboard/expert-management/Form/DeleteForm/index.jsx b/src/components/dashboard/expert-management/Form/DeleteForm/index.jsx new file mode 100644 index 0000000..0d8e592 --- /dev/null +++ b/src/components/dashboard/expert-management/Form/DeleteForm/index.jsx @@ -0,0 +1,74 @@ +import { + Button, + Dialog, + DialogActions, + DialogContent, + DialogContentText, + DialogTitle, + IconButton, + Tooltip +} from "@mui/material"; +import DeleteIcon from '@mui/icons-material/Delete'; +import {useState} from "react"; +import {DELETE_EXPERT} from "@/core/data/apiRoutes"; +import useRequest from "@/lib/app/hooks/useRequest"; +import {useTranslations} from "next-intl"; + +const Delete = ({rowId, mutate}) => { + const t = useTranslations(); + const requestServer = useRequest({auth: true}); + const [openDeleteDialog, setOpenDeleteDialog] = useState(false); + const [isSubmitting, setIsSubmitting] = useState(false); + + const handleDelete = () => { + setIsSubmitting(true) + requestServer(`${DELETE_EXPERT}/${rowId}`, 'DELETE') + .then((response) => { + setOpenDeleteDialog(false); + mutate() + }) + .catch(() => { + }) + .finally(() => { + setIsSubmitting(false) + }); + }; + + return ( + <> + + { + setOpenDeleteDialog(true); + }} + > + + + + + {t("ExpertMangement.delete_expert")} + + {t("ExpertMangement.are_you_sure_text")} + + + + + + + + ); +}; +export default Delete; \ No newline at end of file diff --git a/src/components/dashboard/expert-management/Form/UpdateForm/UpdateContent/PersonalInfo/__tests__/index.test.js b/src/components/dashboard/expert-management/Form/UpdateForm/UpdateContent/PersonalInfo/__tests__/index.test.js new file mode 100644 index 0000000..b8692d5 --- /dev/null +++ b/src/components/dashboard/expert-management/Form/UpdateForm/UpdateContent/PersonalInfo/__tests__/index.test.js @@ -0,0 +1,219 @@ +import {act, fireEvent, render, screen, waitFor} from "@testing-library/react"; +import React from 'react'; +import MockAppWithProviders from "../../../../../../../../../mocks/AppWithProvider"; +import * as Yup from "yup"; +import {Formik} from "formik"; +import PersonalInfo from "@/components/dashboard/expert-management/Form/CreateForm/CreateContent/PersonalInfo"; +import {useTranslations} from "next-intl"; +import Province from "@/components/dashboard/expert-management/Form/CreateForm/CreateContent/RestInfo/Province"; + + +const CreateFormMock = ({Component}) => { + const t = useTranslations(); + const validationSchema = Yup.object().shape({ + full_name: Yup.string().required(t("ExpertMangement.error_message_full_name")), + email: Yup.string().required(t("ExpertMangement.error_message_email")), + phone_number: Yup.string().required(t("ExpertMangement.error_message_phone_number")), + telephone_id: Yup.string().required(t("ExpertMangement.error_message_telephone_id")), + national_id: Yup.string().required(t("ExpertMangement.error_message_national_id")), + gender: Yup.string().required(t("ExpertMangement.error_message_gender")) + }); + return ( + { + }} + > + {formikProps => ()} + + ); +}; + + +describe("PersonalInfo Component From Expert Management (update)", () => { + describe("Rendering", () => { + it("full_name TextField Rendered", async () => { + render(); + await waitFor(() => { + const fullNameTextField = screen.queryByLabelText('نام کامل'); + expect(fullNameTextField).toBeInTheDocument(); + }); + }); + it("Email TextField Rendered", async () => { + render(); + await waitFor(() => { + const emailTextField = screen.queryByLabelText('پست الکترونیک'); + expect(emailTextField).toBeInTheDocument(); + }); + }); + it("Phone Number TextField Rendered", async () => { + render(); + await waitFor(() => { + const phoneNumberTextField = screen.queryByLabelText('شماره همراه'); + expect(phoneNumberTextField).toBeInTheDocument(); + }); + }); + it("telephone id TextField Rendered", async () => { + render(); + await waitFor(() => { + const telephoneIdTextField = screen.queryByLabelText('کد تلفن'); + expect(telephoneIdTextField).toBeInTheDocument(); + }); + }); + it("National Id TextField Rendered", async () => { + render(); + await waitFor(() => { + const nationalIdTextField = screen.queryByLabelText('کد ملی'); + expect(nationalIdTextField).toBeInTheDocument(); + }); + }); + it("Gender Select Box Rendered", async () => { + render(); + await waitFor(() => { + const genderSelectBox = screen.getByTestId('select-box'); + expect(genderSelectBox).toBeInTheDocument(); + }); + }); + }); + describe("Behavioral", () => { + it("Name TextField Work Correctly", async () => { + render(); + const nameTextField = screen.queryByLabelText('نام کامل'); + fireEvent.change(nameTextField, {target: {value: 'exampleName'}}); + await act(() => { + expect(nameTextField).toHaveValue('exampleName'); + }) + }); + it("Email TextField Work Correctly", async () => { + render(); + const emailTextField = screen.queryByLabelText('پست الکترونیک'); + fireEvent.change(emailTextField, {target: {value: 'exampleEmail'}}); + await act(() => { + expect(emailTextField).toHaveValue('exampleEmail'); + }) + }); + it("Phone Number TextField Work Correctly", async () => { + render(); + const phoneNumberTextField = screen.queryByLabelText('شماره همراه'); + fireEvent.change(phoneNumberTextField, {target: {value: 'examplePhoneNumber'}}); + await act(() => { + expect(phoneNumberTextField).toHaveValue('examplePhoneNumber'); + }) + }); + it("Telephone Id TextField Work Correctly", async () => { + render(); + const telephoneIdTextField = screen.queryByLabelText('کد تلفن'); + fireEvent.change(telephoneIdTextField, {target: {value: 'exampleTelephoneId'}}); + await act(() => { + expect(telephoneIdTextField).toHaveValue('exampleTelephoneId'); + }) + }); + it("National Id TextField Work Correctly", async () => { + render(); + const nationalIdTextField = screen.queryByLabelText('کد ملی'); + fireEvent.change(nationalIdTextField, {target: {value: 'exampleNationalId'}}); + await act(() => { + expect(nationalIdTextField).toHaveValue('exampleNationalId'); + }) + }); + it("Gender Select Box Work Correctly", async () => { + render(); + const genderSelectBox = screen.getByTestId('select-box'); + const genderSelectOpener = await waitFor(() => screen.getByTestId("option-opener-gender")); + + await waitFor(() => { + expect(genderSelectBox).toBeInTheDocument(); + }); + + fireEvent.mouseDown(genderSelectOpener); + await waitFor(() => { + const selectItem = screen.queryByText("مرد"); + expect(selectItem).toBeInTheDocument(); + }); + + }); + }); + describe("validation", () => { + it('Should See Error When Name Input Is Empty', async () => { + render(); + + const nameInput = screen.queryByLabelText('نام کامل'); + + fireEvent.change(nameInput, {target: {value: ''}}); + fireEvent.blur(nameInput); + + await waitFor(() => { + expect(screen.queryByText("نام کامل خود را وارد کنید")).toBeInTheDocument() + }); + }); + it('Should See Error When Email Input Is Empty', async () => { + render(); + + const emailInput = screen.queryByLabelText('پست الکترونیک'); + + fireEvent.change(emailInput, {target: {value: ''}}); + fireEvent.blur(emailInput); + + await waitFor(() => { + expect(screen.queryByText("پست الکترونیک خود را وارد کنید")).toBeInTheDocument() + }); + }); + it('Should See Error When Phone Number Input Is Empty', async () => { + render(); + + const phoneNumberInput = screen.queryByLabelText('شماره همراه'); + + fireEvent.change(phoneNumberInput, {target: {value: ''}}); + fireEvent.blur(phoneNumberInput); + + await waitFor(() => { + expect(screen.queryByText("شماره همراه خود را وارد کنید")).toBeInTheDocument() + }); + }); + it('Should See Error When telephone Id Input Is Empty', async () => { + render(); + + const telephoneIdTextField = screen.queryByLabelText('کد تلفن'); + + fireEvent.change(telephoneIdTextField, {target: {value: ''}}); + fireEvent.blur(telephoneIdTextField); + + await waitFor(() => { + expect(screen.queryByText("کد تلفن خود را وارد کنید")).toBeInTheDocument() + }); + }); + it('Should See Error When National Id Input Is Empty', async () => { + render(); + + const nationalIdInput = screen.queryByLabelText('کد ملی'); + + fireEvent.change(nationalIdInput, {target: {value: ''}}); + fireEvent.blur(nationalIdInput); + + await waitFor(() => { + expect(screen.queryByText("کد ملی خود را وارد کنید")).toBeInTheDocument() + }); + }); + it('Should Select An Item With Valid Value of gender', async () => { + render(); + const genderInput = screen.getByTestId("input-gender") + const genderSelectOpener = await waitFor(() => screen.getByTestId("option-opener-gender")); + + fireEvent.mouseDown(genderSelectOpener); + const selectItem = await waitFor(() => screen.queryByText("مرد")); + expect(selectItem).toBeInTheDocument(); + fireEvent.click(selectItem); + await waitFor(() => { + expect(genderInput.value).toBe('male') + }); + }); + }) +}); \ No newline at end of file diff --git a/src/components/dashboard/expert-management/Form/UpdateForm/UpdateContent/PersonalInfo/index.jsx b/src/components/dashboard/expert-management/Form/UpdateForm/UpdateContent/PersonalInfo/index.jsx new file mode 100644 index 0000000..f92bf38 --- /dev/null +++ b/src/components/dashboard/expert-management/Form/UpdateForm/UpdateContent/PersonalInfo/index.jsx @@ -0,0 +1,128 @@ +import {FormControl, FormHelperText, Grid, InputLabel, MenuItem, Select, TextField} from "@mui/material"; +import {useTranslations} from "next-intl"; + +const PersonalInfo = ({formik}) => { + const t = useTranslations(); + + const genderList = [ + {id: 1, name_en: "male", name_fa: "مرد"}, + {id: 2, name_en: "female", name_fa: "زن"} + ] + + return ( + + + + + + + + + + + + + + + + + + + {t("ExpertMangement.text_field_gender")} + + + {formik.touched.gender && formik.errors.gender ? formik.errors.gender : ""} + + + + + ) +} +export default PersonalInfo \ No newline at end of file diff --git a/src/components/dashboard/expert-management/Form/UpdateForm/UpdateContent/RestInfo/PositionAndRole/__tests__/index.test.js b/src/components/dashboard/expert-management/Form/UpdateForm/UpdateContent/RestInfo/PositionAndRole/__tests__/index.test.js new file mode 100644 index 0000000..d6062c5 --- /dev/null +++ b/src/components/dashboard/expert-management/Form/UpdateForm/UpdateContent/RestInfo/PositionAndRole/__tests__/index.test.js @@ -0,0 +1,96 @@ +import {fireEvent, render, screen, waitFor} from "@testing-library/react"; +import React from 'react'; +import {useTranslations} from "next-intl"; +import * as Yup from "yup"; +import {Formik} from "formik"; +import MockAppWithProviders from "../../../../../../../../../../mocks/AppWithProvider"; +import PositionAndRole from "../../PositionAndRole"; + +const CreateFormMock = ({Component}) => { + const t = useTranslations(); + const validationSchema = Yup.object().shape({ + position: Yup.string().required(t("ExpertMangement.error_message_position")), + roles: Yup.string().required(t("ExpertMangement.error_message_roles")) + }); + return ( + { + }} + > + {formikProps => ()} + + ); +}; + +describe("PositionAndRole Component From Expert Management (update)", () => { + describe("Rendering", () => { + it("Position TextField Rendered", async () => { + render(); + await waitFor(() => { + const positionTextField = screen.queryByLabelText('سمت'); + expect(positionTextField).toBeInTheDocument(); + }); + }); + it("Role Select Box Rendered", async () => { + render(); + const roleSelect = screen.getByTestId('select-box'); + await waitFor(() => { + expect(roleSelect).toBeInTheDocument(); + }); + }); + }); + describe("Behavioral", () => { + it("Position TextField Work Correctly", async () => { + render(); + const positionTextField = screen.queryByLabelText('سمت'); + fireEvent.change(positionTextField, {target: {value: 'examplePosition'}}); + await waitFor(() => { + expect(positionTextField).toHaveValue('examplePosition'); + }) + }); + it("Role Select Box Work Correctly", async () => { + render(); + const roleSelect = screen.getByTestId("option-opener-role"); + await waitFor(() => { + expect(roleSelect).toBeInTheDocument(); + }); + fireEvent.mouseDown(roleSelect); + await waitFor(() => { + const selectItem = screen.queryByText("ادمین"); + expect(selectItem).toBeInTheDocument(); + }); + }); + }); + describe("validation", () => { + it('Should See Error When Position Input Is Empty', async () => { + render(); + + const positionInput = screen.queryByLabelText('سمت'); + + fireEvent.change(positionInput, {target: {value: ''}}); + fireEvent.blur(positionInput); + + await waitFor(() => { + expect(screen.queryByText("سمت خود را وارد کنید")).toBeInTheDocument() + }); + }); + it('Should Select An Item With Valid Value of role', async () => { + render(); + const roleInput = await waitFor(() => screen.getByTestId("input-role-id")); + const roleSelectOpener = await waitFor(() => screen.getByTestId("option-opener-role")); + + fireEvent.mouseDown(roleSelectOpener); + const selectItem = await waitFor(() => screen.queryByText("ادمین")); + expect(selectItem).toBeInTheDocument(); + fireEvent.click(selectItem); + await waitFor(() => { + expect(roleInput.value).toBe('1') + }); + }); + }) +}); \ No newline at end of file diff --git a/src/components/dashboard/expert-management/Form/UpdateForm/UpdateContent/RestInfo/PositionAndRole/index.jsx b/src/components/dashboard/expert-management/Form/UpdateForm/UpdateContent/RestInfo/PositionAndRole/index.jsx new file mode 100644 index 0000000..4fc402a --- /dev/null +++ b/src/components/dashboard/expert-management/Form/UpdateForm/UpdateContent/RestInfo/PositionAndRole/index.jsx @@ -0,0 +1,75 @@ +import {FormControl, FormHelperText, Grid, InputLabel, MenuItem, Select, TextField} from "@mui/material"; +import {useTranslations} from "next-intl"; +import useRequest from "@/lib/app/hooks/useRequest"; +import useRole from "@/lib/app/hooks/useRole"; +import {log} from "next/dist/server/typescript/utils"; + +const PositionAndRole = ({formik}) => { + const t = useTranslations(); + const requestServer = useRequest() + const {roleList, isLoadingRoleList, errorRoleList} = useRole(); + + return ( + + + + + + + {t("ExpertMangement.text_field_roles")} + + + {formik.touched.roles && formik.errors.roles ? formik.errors.roles : ""} + + + + + ) +} +export default PositionAndRole \ No newline at end of file diff --git a/src/components/dashboard/expert-management/Form/UpdateForm/UpdateContent/RestInfo/Province/__tests__/index.test.js b/src/components/dashboard/expert-management/Form/UpdateForm/UpdateContent/RestInfo/Province/__tests__/index.test.js new file mode 100644 index 0000000..d727188 --- /dev/null +++ b/src/components/dashboard/expert-management/Form/UpdateForm/UpdateContent/RestInfo/Province/__tests__/index.test.js @@ -0,0 +1,71 @@ +import {fireEvent, render, screen, waitFor} from "@testing-library/react"; +import React from 'react'; +import {useTranslations} from "next-intl"; +import * as Yup from "yup"; +import {Formik} from "formik"; +import Province from "../../Province"; +import MockAppWithProviders from "../../../../../../../../../../mocks/AppWithProvider"; + +const CreateFormMock = ({Component}) => { + const t = useTranslations(); + const validationSchema = Yup.object().shape({ + province_id: Yup.string().required(t("ExpertMangement.error_message_province_id")), + }); + return ( + { + }} + > + {formikProps => ()} + + ); +}; + +describe("Province Component From Expert Management (update)", () => { + describe("Rendering", () => { + it("Province Select Box Rendered", async () => { + render(); + await waitFor(() => { + const provinceSelectBox = screen.getByTestId('select-box'); + expect(provinceSelectBox).toBeInTheDocument(); + }); + }); + }); + describe("Behavioral", () => { + it("Province Select Box Work Correctly", async () => { + render(); + const provinceSelectBox = screen.getByTestId('select-box'); + const provinceSelectOpener = await waitFor(() => screen.getByTestId("option-opener-province")); + + await waitFor(() => { + expect(provinceSelectBox).toBeInTheDocument(); + }); + + fireEvent.mouseDown(provinceSelectOpener); + await waitFor(() => { + const selectItem = screen.queryByText("آذربایجان شرقی"); + expect(selectItem).toBeInTheDocument(); + }); + + }); + }); + describe("validation", () => { + it('Should Select An Item With Valid Value of province', async () => { + render(); + const provinceInput = screen.getByTestId("input-province-id") + const provinceSelectOpener = await waitFor(() => screen.getByTestId("option-opener-province")); + + fireEvent.mouseDown(provinceSelectOpener); + const selectItem = await waitFor(() => screen.queryByText("آذربایجان شرقی")); + expect(selectItem).toBeInTheDocument(); + fireEvent.click(selectItem); + await waitFor(() => { + expect(provinceInput.value).toBe('1') + }); + }); + }) +}); \ No newline at end of file diff --git a/src/components/dashboard/expert-management/Form/UpdateForm/UpdateContent/RestInfo/Province/index.jsx b/src/components/dashboard/expert-management/Form/UpdateForm/UpdateContent/RestInfo/Province/index.jsx new file mode 100644 index 0000000..763465e --- /dev/null +++ b/src/components/dashboard/expert-management/Form/UpdateForm/UpdateContent/RestInfo/Province/index.jsx @@ -0,0 +1,61 @@ +import {FormControl, FormHelperText, Grid, InputLabel, MenuItem, Select} from "@mui/material"; +import {useTranslations} from "next-intl"; +import useProvince from "@/lib/app/hooks/useProvince"; +import useRequest from "@/lib/app/hooks/useRequest"; + +const Province = ({formik}) => { + const t = useTranslations(); + const requestServer = useRequest() + const {provinceList, isLoadingProvinceList, errorProvinceList} = useProvince(); + + return ( + + + + {t("ExpertMangement.text_field_province_id")} + + + {formik.touched.province_id && formik.errors.province_id ? formik.errors.province_id : ""} + + + + + ) +} +export default Province \ No newline at end of file diff --git a/src/components/dashboard/expert-management/Form/UpdateForm/UpdateContent/RestInfo/index.jsx b/src/components/dashboard/expert-management/Form/UpdateForm/UpdateContent/RestInfo/index.jsx new file mode 100644 index 0000000..36c1877 --- /dev/null +++ b/src/components/dashboard/expert-management/Form/UpdateForm/UpdateContent/RestInfo/index.jsx @@ -0,0 +1,13 @@ +import Province from "@/components/dashboard/expert-management/Form/UpdateForm/UpdateContent/RestInfo/Province"; +import PositionAndRole + from "@/components/dashboard/expert-management/Form/UpdateForm/UpdateContent/RestInfo/PositionAndRole"; + +const RestInfo = ({formik}) => { + return ( + <> + + + + ) +} +export default RestInfo \ No newline at end of file diff --git a/src/components/dashboard/expert-management/Form/UpdateForm/UpdateContent/UserInfo/__tests__/index.test.js b/src/components/dashboard/expert-management/Form/UpdateForm/UpdateContent/UserInfo/__tests__/index.test.js new file mode 100644 index 0000000..3fd341d --- /dev/null +++ b/src/components/dashboard/expert-management/Form/UpdateForm/UpdateContent/UserInfo/__tests__/index.test.js @@ -0,0 +1,62 @@ +import {act, fireEvent, render, screen, waitFor} from "@testing-library/react"; +import React from 'react'; +import MockAppWithProviders from "../../../../../../../../../mocks/AppWithProvider"; +import {useTranslations} from "next-intl"; +import * as Yup from "yup"; +import {Formik} from "formik"; +import UserInfo from "../../UserInfo"; + +const CreateFormMock = ({Component}) => { + const t = useTranslations(); + const validationSchema = Yup.object().shape({ + username: Yup.string().required(t("ExpertMangement.error_message_username")), + }); + return ( + { + }} + > + {formikProps => ()} + + ); +}; + +describe("UserInfo Component From Expert Management (update)", () => { + describe("Rendering", () => { + it("UserName TextField Rendered", async () => { + render(); + await waitFor(() => { + const usernameTextField = screen.queryByLabelText('نام کاربری'); + expect(usernameTextField).toBeInTheDocument(); + }); + }); + }); + describe("Behavioral", () => { + it("Username TextField Work Correctly", async () => { + render(); + const usernameTextField = screen.queryByLabelText('نام کاربری'); + fireEvent.change(usernameTextField, {target: {value: 'exampleUsername'}}); + await act(() => { + expect(usernameTextField).toHaveValue('exampleUsername'); + }) + }); + }); + describe("validation", () => { + it('Should See Error When Username Input Is Empty', async () => { + render(); + + const usernameInput = screen.queryByLabelText('نام کاربری'); + + fireEvent.change(usernameInput, {target: {value: ''}}); + fireEvent.blur(usernameInput); + + await waitFor(() => { + expect(screen.queryByText("نام کاربری خود را وارد کنید")).toBeInTheDocument() + }); + }); + }) +}); \ No newline at end of file diff --git a/src/components/dashboard/expert-management/Form/UpdateForm/UpdateContent/UserInfo/index.jsx b/src/components/dashboard/expert-management/Form/UpdateForm/UpdateContent/UserInfo/index.jsx new file mode 100644 index 0000000..d592ea5 --- /dev/null +++ b/src/components/dashboard/expert-management/Form/UpdateForm/UpdateContent/UserInfo/index.jsx @@ -0,0 +1,29 @@ +import {Grid, TextField} from "@mui/material"; +import {useTranslations} from "next-intl"; + +const UserInfo = ({formik}) => { + const t = useTranslations(); + + return ( + <> + + + + + + + ) +} +export default UserInfo \ No newline at end of file diff --git a/src/components/dashboard/expert-management/Form/UpdateForm/UpdateContent/__tests__/index.test.js b/src/components/dashboard/expert-management/Form/UpdateForm/UpdateContent/__tests__/index.test.js new file mode 100644 index 0000000..765bc09 --- /dev/null +++ b/src/components/dashboard/expert-management/Form/UpdateForm/UpdateContent/__tests__/index.test.js @@ -0,0 +1,87 @@ +import {fireEvent, render, screen, waitFor} from "@testing-library/react"; +import React from 'react'; +import MockAppWithProviders from "../../../../../../../../mocks/AppWithProvider"; +import UpdateContent from "../../UpdateContent"; + +const row = { + original: { + avatar: null, + created_at: "2023-10-10T13:05:01.000000Z", + email: "fisher.sigrid@yahoo.com", + full_name: "Anibal Labadie", + gender: "female", + id: 85, + national_id: "5944646138", + phone_number: "09731311720", + position: "boss1", + province_fa: "اردبیل", + province_id: 3, + roles: [{ + created_at: "2023-10-10T13:03:18.000000Z", + guard_name: "api", + id: 2, + name: "manager", + name_fa: "مدیر" + }], + telephone_id: "tel-3110", + updated_at: "2023-10-17T10:57:12.000000Z", + username: "florence.kihn", + } +} + +function selectDropdownItem(screen, openerTestId, itemText) { + const opener = screen.getByTestId(openerTestId); + fireEvent.mouseDown(opener); + const selectItem = screen.queryByText(itemText); + fireEvent.click(selectItem); +} + +function setInputValue(screen, inputElement, value) { + fireEvent.change(inputElement, {target: {value}}); +} + +describe("UpdateContent Component From Expert Management (update)", () => { + describe("Rendering", () => { + it("close button Rendered", async () => { + render(); + await waitFor(() => { + const cancelBtn = screen.queryByText('بستن'); + expect(cancelBtn).toBeInTheDocument(); + }); + }); + it("confirm button Rendered", async () => { + render(); + await waitFor(() => { + const confirmBtn = screen.queryByText('ثبت'); + expect(confirmBtn).toBeInTheDocument(); + }); + }); + }); + describe("Form Submission", () => { + it('Should enable the submit button when the inputs are valid', async () => { + render(); + + const submitButton = screen.queryByText('ثبت'); + + const nameInput = screen.queryByLabelText('نام کامل'); + const usernameInput = screen.queryByLabelText('نام کاربری'); + const emailInput = screen.queryByLabelText('پست الکترونیک'); + const phoneNumberInput = screen.queryByLabelText('شماره همراه'); + const telephoneIdInput = screen.queryByLabelText('کد تلفن'); + const nationalIdInput = screen.queryByLabelText('کد ملی'); + const positionInput = screen.queryByLabelText('سمت'); + + setInputValue(screen, nameInput, 'nameTest'); + setInputValue(screen, usernameInput, 'usernameTest'); + setInputValue(screen, emailInput, 'emailTest'); + setInputValue(screen, phoneNumberInput, 'phoneNumberTest'); + setInputValue(screen, telephoneIdInput, 'telephoneIdTest'); + setInputValue(screen, nationalIdInput, 'nationalIdTest'); + setInputValue(screen, positionInput, 'positionTest'); + + await waitFor(() => { + expect(submitButton).not.toBeDisabled(); + }); + }); + }) +}); \ No newline at end of file diff --git a/src/components/dashboard/expert-management/Form/UpdateForm/UpdateContent/index.jsx b/src/components/dashboard/expert-management/Form/UpdateForm/UpdateContent/index.jsx new file mode 100644 index 0000000..1f482dc --- /dev/null +++ b/src/components/dashboard/expert-management/Form/UpdateForm/UpdateContent/index.jsx @@ -0,0 +1,108 @@ +import {Box, Button, Chip, DialogActions, DialogContent, Divider,} from "@mui/material"; +import * as Yup from "yup"; +import {useTranslations} from "next-intl"; +import {useFormik} from "formik"; +import useRequest from "@/lib/app/hooks/useRequest"; +import {UPDATE_EXPERT} from "@/core/data/apiRoutes"; +import PersonalInfo from "./PersonalInfo"; +import UserInfo from "./UserInfo"; +import RestInfo from "./RestInfo"; + +const UpdateContent = ({row, mutate, setOpenUpdateDialog}) => { + const t = useTranslations(); + const requestServer = useRequest() + + const validationSchema = Yup.object().shape({ + full_name: Yup.string().required(t("ExpertMangement.error_message_full_name")), + username: Yup.string().required(t("ExpertMangement.error_message_username")), + email: Yup.string().required(t("ExpertMangement.error_message_email")), + phone_number: Yup.string().required(t("ExpertMangement.error_message_phone_number")), + telephone_id: Yup.string().required(t("ExpertMangement.error_message_telephone_id")), + gender: Yup.string().required(t("ExpertMangement.error_message_gender")), + national_id: Yup.string().required(t("ExpertMangement.error_message_national_id")), + position: Yup.string().required(t("ExpertMangement.error_message_position")), + province_id: Yup.string().required(t("ExpertMangement.error_message_province_id")), + roles: Yup.string().required(t("ExpertMangement.error_message_roles")) + }); + + const formik = useFormik({ + initialValues: { + full_name: row.original.full_name, + username: row.original.username, + email: row.original.email, + phone_number: row.original.phone_number, + telephone_id: row.original.telephone_id, + gender: row.original.gender, + national_id: row.original.national_id, + position: row.original.position, + province_id: row.original.province_id, + roles: row.original.roles[0]?.id + }, + validationSchema, + onSubmit: (values, {setSubmitting}) => { + const formData = new FormData(); + formData.append("full_name", values.full_name); + formData.append("national_id", values.national_id); + formData.append("phone_number", values.phone_number); + formData.append("telephone_id", values.telephone_id); + formData.append("gender", values.gender); + formData.append("email", values.email); + formData.append("username", values.username); + formData.append("province_id", values.province_id); + formData.append("position", values.position); + formData.append("roles", values.roles); + + requestServer(`${UPDATE_EXPERT}/${row.original.id}`, 'post', {auth: true, data: formData}) + .then((response) => { + setOpenUpdateDialog(false) + mutate() + }).catch(() => { + }).finally(() => { + setSubmitting(false); + }); + }, + }); + + return ( + <> + + + + + + + + + + + + + + + + + + + + + + + + + + ) +} + +export default UpdateContent \ No newline at end of file diff --git a/src/components/dashboard/expert-management/Form/UpdateForm/__tests__/index.test.js b/src/components/dashboard/expert-management/Form/UpdateForm/__tests__/index.test.js new file mode 100644 index 0000000..f824d6d --- /dev/null +++ b/src/components/dashboard/expert-management/Form/UpdateForm/__tests__/index.test.js @@ -0,0 +1,147 @@ +import {act, fireEvent, render, screen, waitFor} from "@testing-library/react"; +import React from 'react'; +import MockAppWithProviders from "../../../../../../../mocks/AppWithProvider"; +import UpdateForm from "../../UpdateForm"; + +const row = { + original: { + avatar: null, + created_at: "2023-10-10T13:05:01.000000Z", + email: "fisher.sigrid@yahoo.com", + full_name: "Anibal Labadie", + gender: "female", + id: 85, + national_id: "5944646138", + phone_number: "09731311720", + position: "boss1", + province_fa: "اردبیل", + province_id: 3, + roles: [{ + created_at: "2023-10-10T13:03:18.000000Z", + guard_name: "api", + id: 2, + name: "manager", + name_fa: "مدیر" + }], + telephone_id: "tel-3110", + updated_at: "2023-10-17T10:57:12.000000Z", + username: "florence.kihn", + } +} + +function selectDropdownItem(screen, openerTestId, itemText) { + const opener = screen.getByTestId(openerTestId); + fireEvent.mouseDown(opener); + const selectItem = screen.queryByText(itemText); + fireEvent.click(selectItem); +} + +function setInputValue(screen, inputElement, value) { + fireEvent.change(inputElement, {target: {value}}); +} + +describe("CreateForm Component From Expert Management", () => { + describe("Rendering", () => { + it("Update Expert Button Rendered", () => { + render(); + const UpdateButton = screen.getByTestId('update-button'); + expect(UpdateButton).toBeInTheDocument(); + }); + }); + describe("Behavioral", () => { + it("by Clicking Update Button Dialog Should Append To Document", async () => { + render(); + const UpdateButton = screen.getByTestId('update-button'); + fireEvent.click(UpdateButton); + await act(() => { + const UpdateDialog = screen.getByRole('dialog', {name: "ویرایش کارشناس"}) + expect(UpdateDialog).toBeInTheDocument(); + }); + }); + }); + describe("Form Submission", () => { + it('Should request to api and if get success close dialog', async () => { + render(); + + const UpdateButton = screen.getByTestId('update-button'); + fireEvent.click(UpdateButton); + + const submitButton = screen.queryByText('ثبت'); + + const nameInput = screen.queryByLabelText('نام کامل'); + const usernameInput = screen.queryByLabelText('نام کاربری'); + const emailInput = screen.queryByLabelText('پست الکترونیک'); + const phoneNumberInput = screen.queryByLabelText('شماره همراه'); + const telephoneIdInput = screen.queryByLabelText('کد تلفن'); + const nationalIdInput = screen.queryByLabelText('کد ملی'); + const positionInput = screen.queryByLabelText('سمت'); + + selectDropdownItem(screen, "option-opener-province", "آذربایجان شرقی"); + selectDropdownItem(screen, "option-opener-role", "ادمین"); + selectDropdownItem(screen, "option-opener-gender", "مرد"); + + setInputValue(screen, nameInput, 'nameTest'); + setInputValue(screen, usernameInput, 'usernameTest'); + setInputValue(screen, emailInput, 'email@gmail.com'); + setInputValue(screen, phoneNumberInput, '0914577458'); + setInputValue(screen, telephoneIdInput, '091'); + setInputValue(screen, nationalIdInput, 'nationalIdTest'); + setInputValue(screen, positionInput, 'positionTest'); + + expect(submitButton).not.toBeDisabled(); + fireEvent.click(submitButton); + + await waitFor(() => { + const dialogContent = screen.queryByTestId('update-dialog-content'); + expect(dialogContent).not.toBeInTheDocument(); + }); + }); + it('Should request to api and if get error keep previous data', async () => { + render(); + + const UpdateButton = screen.getByTestId('update-button'); + fireEvent.click(UpdateButton); + + const submitButton = screen.queryByText('ثبت'); + + const nameInput = screen.queryByLabelText('نام کامل'); + const usernameInput = screen.queryByLabelText('نام کاربری'); + const emailInput = screen.queryByLabelText('پست الکترونیک'); + const phoneNumberInput = screen.queryByLabelText('شماره همراه'); + const telephoneIdInput = screen.queryByLabelText('کد تلفن'); + const nationalIdInput = screen.queryByLabelText('کد ملی'); + const positionInput = screen.queryByLabelText('سمت'); + const provinceInput = screen.getByTestId("input-province-id") + const roleInput = screen.getByTestId("input-role-id") + const genderInput = screen.getByTestId("input-gender") + + selectDropdownItem(screen, "option-opener-province", "آذربایجان شرقی"); + selectDropdownItem(screen, "option-opener-role", "ادمین"); + selectDropdownItem(screen, "option-opener-gender", "مرد"); + + setInputValue(screen, nameInput, 'nameTest'); + setInputValue(screen, usernameInput, 'usernameTest'); + setInputValue(screen, emailInput, 'emailTest@gmail.com'); + setInputValue(screen, phoneNumberInput, '0914577458'); + setInputValue(screen, telephoneIdInput, '091'); + setInputValue(screen, nationalIdInput, 'nationalIdTest'); + setInputValue(screen, positionInput, 'positionTest'); + + expect(submitButton).not.toBeDisabled(); + await fireEvent.click(submitButton); + + await waitFor(() => { + expect(nameInput).toHaveValue('nameTest'); + expect(usernameInput).toHaveValue('usernameTest'); + expect(emailInput).toHaveValue('emailTest@gmail.com'); + expect(phoneNumberInput).toHaveValue('0914577458'); + expect(telephoneIdInput).toHaveValue('091'); + expect(nationalIdInput).toHaveValue('nationalIdTest'); + expect(positionInput).toHaveValue('positionTest'); + expect(provinceInput).toHaveValue('1'); + expect(roleInput).toHaveValue('1'); + expect(genderInput).toHaveValue('male'); + }); + }); + }) +}); \ No newline at end of file diff --git a/src/components/dashboard/expert-management/Form/UpdateForm/index.jsx b/src/components/dashboard/expert-management/Form/UpdateForm/index.jsx new file mode 100644 index 0000000..27fae5c --- /dev/null +++ b/src/components/dashboard/expert-management/Form/UpdateForm/index.jsx @@ -0,0 +1,33 @@ +import {useTranslations} from "next-intl"; +import {Dialog, DialogTitle, IconButton, Tooltip} from "@mui/material"; +import EditIcon from '@mui/icons-material/Edit'; +import {useState} from "react"; +import UpdateContent from "./UpdateContent"; + + +const Update = ({row, mutate}) => { + const t = useTranslations(); + const [openUpdateDialog, setOpenUpdateDialog] = useState(false); + + return ( + <> + + { + setOpenUpdateDialog(true) + }} + > + + + + + {t("ExpertMangement.update_expert")} + + + + ); +}; +export default Update; \ No newline at end of file diff --git a/src/components/dashboard/expert-management/TableRowActions.jsx b/src/components/dashboard/expert-management/TableRowActions.jsx new file mode 100644 index 0000000..91e778e --- /dev/null +++ b/src/components/dashboard/expert-management/TableRowActions.jsx @@ -0,0 +1,24 @@ +import {Box} from "@mui/material"; +import Update from "./Form/UpdateForm" +import Delete from "./Form/DeleteForm"; + +const TableRowActions = ({row, mutate}) => { + return ( + + + {/**/} + + + ); +}; + +export default TableRowActions; diff --git a/src/components/dashboard/expert-management/TableToolbar.jsx b/src/components/dashboard/expert-management/TableToolbar.jsx new file mode 100644 index 0000000..24f26f7 --- /dev/null +++ b/src/components/dashboard/expert-management/TableToolbar.jsx @@ -0,0 +1,14 @@ +import {useTranslations} from "next-intl"; +import Create from "./Form/CreateForm"; +import {Box} from "@mui/material"; + +function TableToolbar({mutate}) { + const t = useTranslations(); + return ( + + + + ); +} + +export default TableToolbar; diff --git a/src/components/dashboard/expert-management/index.jsx b/src/components/dashboard/expert-management/index.jsx new file mode 100644 index 0000000..632d89b --- /dev/null +++ b/src/components/dashboard/expert-management/index.jsx @@ -0,0 +1,12 @@ +import DashboardLayouts from "@/layouts/DashboardLayout"; +import ExpertManagementDataTable from "@/components/dashboard/expert-management/DataTable"; + +function DashboardExpertManagementComponent() { + return ( + + + + ); +} + +export default DashboardExpertManagementComponent; diff --git a/src/components/dashboard/role-management/Form/CreateForm/CreateContent.jsx b/src/components/dashboard/role-management/Form/CreateForm/CreateContent.jsx index db28f49..d06659b 100644 --- a/src/components/dashboard/role-management/Form/CreateForm/CreateContent.jsx +++ b/src/components/dashboard/role-management/Form/CreateForm/CreateContent.jsx @@ -1,24 +1,27 @@ import { Button, - Checkbox, CircularProgress, + Checkbox, + CircularProgress, DialogActions, - DialogContent, DialogTitle, + DialogContent, + DialogTitle, FormControl, FormControlLabel, FormHelperText, FormLabel, Grid, Stack, - TextField, Typography + TextField, + Typography } from "@mui/material"; import {useTranslations} from "next-intl"; import useRequest from "@/lib/app/hooks/useRequest"; import * as Yup from "yup"; import {useFormik} from "formik"; -import {ADD_ROLE_MANAGEMENT} from "@/core/data/apiRoutes"; +import {ADD_ROLE} from "@/core/data/apiRoutes"; import usePermissions from "@/lib/app/hooks/usePermissions"; -const CreateContent = ({mutate, fetchUrl, setOpenConfirmDialog}) => { +const CreateContent = ({mutate, setOpenConfirmDialog}) => { const t = useTranslations(); const requestServer = useRequest({auth: true}) const {permissions_list, isLoading} = usePermissions() @@ -40,11 +43,11 @@ const CreateContent = ({mutate, fetchUrl, setOpenConfirmDialog}) => { formData.append(`permissions[${i}]`, values.permissions[i]); } - requestServer(ADD_ROLE_MANAGEMENT, 'post', { + requestServer(ADD_ROLE, 'post', { data: formData, }).then(() => { setOpenConfirmDialog(false) - mutate(fetchUrl) + mutate() update_notification() }).catch(() => { }).finally(() => { @@ -95,38 +98,40 @@ const CreateContent = ({mutate, fetchUrl, setOpenConfirmDialog}) => { fullWidth > {formik.touched.permissions && formik.errors.permissions} - {isLoading ? - - - {t("AddDialog.loading_permissions_list")} - - :( - - <> - {permissions_list.map((permission) => ( - - { - if (e.target.checked) { - formik.setFieldValue("permissions", [...formik.values.permissions, permission.id]) - } else { - formik.setFieldValue("permissions", formik.values.permissions.filter((id) => id !== permission.id)) - } - }} - /> - } - label={permission.name_fa} - /> - - ))} - - - ) - } + {isLoading ? + + + {t("AddDialog.loading_permissions_list")} + + : ( + + <> + {permissions_list.map((permission) => ( + + { + if (e.target.checked) { + formik.setFieldValue("permissions", [...formik.values.permissions, permission.id]) + } else { + formik.setFieldValue("permissions", formik.values.permissions.filter((id) => id !== permission.id)) + } + }} + /> + } + label={permission.name_fa} + /> + + ))} + + + ) + } diff --git a/src/components/dashboard/role-management/Form/CreateForm/index.jsx b/src/components/dashboard/role-management/Form/CreateForm/index.jsx index 2265035..455d9a2 100644 --- a/src/components/dashboard/role-management/Form/CreateForm/index.jsx +++ b/src/components/dashboard/role-management/Form/CreateForm/index.jsx @@ -1,10 +1,10 @@ -import {Button, Dialog, DialogTitle, Stack, Tooltip} from "@mui/material"; +import {Button, Dialog, Stack, Tooltip} from "@mui/material"; import DataSaverOnIcon from "@mui/icons-material/DataSaverOn"; import {useTranslations} from "next-intl"; import {useState} from "react"; import CreateContent from "./CreateContent"; -const CreateForm = ({mutate, fetchUrl}) => { +const CreateForm = ({mutate}) => { const t = useTranslations(); const [openConfirmDialog, setOpenConfirmDialog] = useState(false); @@ -26,7 +26,7 @@ const CreateForm = ({mutate, fetchUrl}) => { - + ) diff --git a/src/components/dashboard/role-management/Form/DeleteForm/DeleteContent.jsx b/src/components/dashboard/role-management/Form/DeleteForm/DeleteContent.jsx index 52674a5..f1580ce 100644 --- a/src/components/dashboard/role-management/Form/DeleteForm/DeleteContent.jsx +++ b/src/components/dashboard/role-management/Form/DeleteForm/DeleteContent.jsx @@ -1,26 +1,26 @@ import {Button, DialogActions, DialogContent, DialogTitle, Typography} from "@mui/material"; -import {DELETE_ROLE_MANAGEMENT} from "@/core/data/apiRoutes"; +import {DELETE_ROLE} from "@/core/data/apiRoutes"; import {useState} from "react"; import useRequest from "@/lib/app/hooks/useRequest"; import useNotification from "@/lib/app/hooks/useNotification"; import {useTranslations} from "next-intl"; -const DeleteContent = ({rowId, fetchUrl, mutate, setOpenConfirmDialog}) => { +const DeleteContent = ({rowId, mutate, setOpenConfirmDialog}) => { const t = useTranslations(); const [isSubmitting, setIsSubmitting] = useState(false) const requestServer = useRequest({auth: true}) const {update_notification} = useNotification() const handleSubmit = () => { setIsSubmitting(true) - requestServer(`${DELETE_ROLE_MANAGEMENT}/${rowId}`, 'delete').then((response) => { - mutate(fetchUrl) + requestServer(`${DELETE_ROLE}/${rowId}`, 'delete').then((response) => { + mutate() update_notification() }).catch(() => { }).finally(() => { setIsSubmitting(false) }); } - return( + return ( <> {t("DeleteDialog.delete")} @@ -39,4 +39,4 @@ const DeleteContent = ({rowId, fetchUrl, mutate, setOpenConfirmDialog}) => { ) } -export default DeleteContent \ No newline at end of file +export default DeleteContent \ No newline at end of file diff --git a/src/components/dashboard/role-management/Form/DeleteForm/__test__/DeleteContent.test.js b/src/components/dashboard/role-management/Form/DeleteForm/__test__/DeleteContent.test.js index 580dcb9..7258e10 100644 --- a/src/components/dashboard/role-management/Form/DeleteForm/__test__/DeleteContent.test.js +++ b/src/components/dashboard/role-management/Form/DeleteForm/__test__/DeleteContent.test.js @@ -1,51 +1,51 @@ -import {act, render, screen} from "@testing-library/react"; +import {act, render, screen, waitFor} from "@testing-library/react"; import MockAppWithProviders from "../../../../../../../mocks/AppWithProvider"; import DeleteContent from "@/components/dashboard/role-management/Form/DeleteForm/DeleteContent"; import UpdateContent from "@/components/dashboard/role-management/Form/UpdateForm/UpdateContent"; -describe("Create Content component from Create Form Component in Role Management Component",()=> { +describe("Create Content component from Create Form Component in Role Management Component", () => { describe("Rendering", () => { it('should see DeleteDialog text in the top ', async () => { render( - + ) const textElement = screen.queryByText("حذف") - act(()=>{ + await waitFor(() => { expect(textElement).toBeInTheDocument() }) }) it('should see DeleteDialog text content ', async () => { render( - + ) const textElement = screen.queryByText("آیا از حدف این مورد اطمینان دارید ؟") - act(()=>{ + await waitFor(() => { expect(textElement).toBeInTheDocument() }) }) - it('should see delete text in the delete button ', () => { + it('should see delete text in the delete button ', async () => { render( - + ) const buttonElement = screen.queryByText("حذف کردن") - act(()=>{ + await waitFor(() => { expect(buttonElement).toBeInTheDocument() }) }); - it('should see cancel text in the cancel button ', () => { + it('should see cancel text in the cancel button ', async () => { render( - + ) const buttonElement = screen.queryByText("انصراف") - act(()=>{ + await waitFor(() => { expect(buttonElement).toBeInTheDocument() }) }); diff --git a/src/components/dashboard/role-management/Form/DeleteForm/index.jsx b/src/components/dashboard/role-management/Form/DeleteForm/index.jsx index b22a907..4ff70bd 100644 --- a/src/components/dashboard/role-management/Form/DeleteForm/index.jsx +++ b/src/components/dashboard/role-management/Form/DeleteForm/index.jsx @@ -1,13 +1,10 @@ import {useTranslations} from "next-intl"; import {useState} from "react"; -import { - Dialog, IconButton, - Tooltip, -} from "@mui/material"; +import {Dialog, IconButton, Tooltip} from "@mui/material"; import DeleteIcon from '@mui/icons-material/Delete'; import DeleteContent from "@/components/dashboard/role-management/Form/DeleteForm/DeleteContent"; -const DeleteForm = ({rowId, fetchUrl, mutate}) => { +const DeleteForm = ({rowId, mutate}) => { const t = useTranslations(); const [openConfirmDialog, setOpenConfirmDialog] = useState(false); @@ -27,7 +24,7 @@ const DeleteForm = ({rowId, fetchUrl, mutate}) => { - + ); diff --git a/src/components/dashboard/role-management/Form/UpdateForm/UpdateContent.jsx b/src/components/dashboard/role-management/Form/UpdateForm/UpdateContent.jsx index 0815356..3fee10a 100644 --- a/src/components/dashboard/role-management/Form/UpdateForm/UpdateContent.jsx +++ b/src/components/dashboard/role-management/Form/UpdateForm/UpdateContent.jsx @@ -1,25 +1,28 @@ import { - Box, Button, - Checkbox, CircularProgress, DialogActions, - DialogContent, DialogTitle, + Checkbox, + CircularProgress, + DialogActions, + DialogContent, + DialogTitle, FormControl, FormControlLabel, FormHelperText, FormLabel, Grid, Stack, - TextField, Typography + TextField, + Typography } from "@mui/material"; import {useFormik} from "formik"; -import {UPDATE_ROLE_MANAGEMENT} from "@/core/data/apiRoutes"; +import {UPDATE_ROLE} from "@/core/data/apiRoutes"; import * as Yup from "yup"; import usePermissions from "@/lib/app/hooks/usePermissions"; import useNotification from "@/lib/app/hooks/useNotification"; import useRequest from "@/lib/app/hooks/useRequest"; import {useTranslations} from "next-intl"; -const UpdateContent = ({mutate, row, fetchUrl, setOpenConfirmDialog}) => { +const UpdateContent = ({mutate, row, setOpenConfirmDialog}) => { const t = useTranslations(); const requestServer = useRequest({auth: true}) const {update_notification} = useNotification() @@ -44,10 +47,11 @@ const UpdateContent = ({mutate, row, fetchUrl, setOpenConfirmDialog}) => { formData.append(`permissions[${i}]`, values.permissions[i]); } - requestServer(`${UPDATE_ROLE_MANAGEMENT}/${row.getValue("id")}`, 'post', { + requestServer(`${UPDATE_ROLE}/${row.getValue("id")}`, 'post', { data: formData, }).then((response) => { - mutate(fetchUrl) + setOpenConfirmDialog(false) + mutate() update_notification() }).catch(() => { }).finally(() => { @@ -56,7 +60,7 @@ const UpdateContent = ({mutate, row, fetchUrl, setOpenConfirmDialog}) => { }, }); - return( + return ( <> {t("UpdateDialog.update")} @@ -96,11 +100,13 @@ const UpdateContent = ({mutate, row, fetchUrl, setOpenConfirmDialog}) => { > {formik.touched.permissions && formik.errors.permissions} {isLoading ? - + - {t("UpdateDialog.loading_permissions_list")} + {t("UpdateDialog.loading_permissions_list")} - :( + : ( <> {permissions_list.map((permission) => ( diff --git a/src/components/dashboard/role-management/Form/UpdateForm/index.jsx b/src/components/dashboard/role-management/Form/UpdateForm/index.jsx index 018fe32..f9defe7 100644 --- a/src/components/dashboard/role-management/Form/UpdateForm/index.jsx +++ b/src/components/dashboard/role-management/Form/UpdateForm/index.jsx @@ -4,13 +4,13 @@ import {useState} from "react"; import UpdateContent from "@/components/dashboard/role-management/Form/UpdateForm/UpdateContent"; import EditIcon from "@mui/icons-material/Edit"; -const UpdateForm = ({mutate, fetchUrl, row}) => { +const UpdateForm = ({mutate, row}) => { const t = useTranslations(); const [openConfirmDialog, setOpenConfirmDialog] = useState(false); return ( - + { setOpenConfirmDialog(true); }} > - + - + ) diff --git a/src/components/dashboard/role-management/RoleManagementComponent.jsx b/src/components/dashboard/role-management/RoleManagementComponent.jsx index 0121c00..72cba70 100644 --- a/src/components/dashboard/role-management/RoleManagementComponent.jsx +++ b/src/components/dashboard/role-management/RoleManagementComponent.jsx @@ -1,5 +1,5 @@ import DataTable from "@/core/components/DataTable"; -import {GET_ROLE_MANAGEMENT} from "@/core/data/apiRoutes"; +import {GET_ROLES} from "@/core/data/apiRoutes"; import TableRowActions from "@/components/dashboard/role-management/TableRowActions"; import {Box, Typography} from "@mui/material"; import {useTranslations} from "next-intl"; @@ -65,10 +65,10 @@ const RoleManagementComponent = () => { datatype: "numeric", Cell: ({renderedCellValue}) => ({renderedCellValue}), }], []); - return( + return ( { +const TableRowActions = ({row, mutate}) => { return ( diff --git a/src/components/dashboard/role-management/TableToolbar.jsx b/src/components/dashboard/role-management/TableToolbar.jsx index 14dfef1..6a739a7 100644 --- a/src/components/dashboard/role-management/TableToolbar.jsx +++ b/src/components/dashboard/role-management/TableToolbar.jsx @@ -1,10 +1,10 @@ import {useTranslations} from "next-intl"; import CreateForm from "@/components/dashboard/role-management/Form/CreateForm"; -function TableToolbar({mutate, fetchUrl}) { +function TableToolbar({mutate}) { const t = useTranslations(); - return + return } diff --git a/src/components/dashboard/role-management/index.jsx b/src/components/dashboard/role-management/index.jsx index 5251c8b..6376b30 100644 --- a/src/components/dashboard/role-management/index.jsx +++ b/src/components/dashboard/role-management/index.jsx @@ -1,8 +1,8 @@ import RoleManagementComponent from "@/components/dashboard/role-management/RoleManagementComponent"; import DashboardLayout from "@/layouts/DashboardLayout"; +import moment from "jalali-moment"; function DashboardRoleManagementComponent() { - return ( diff --git a/src/components/first/__tests__/index.test.js b/src/components/first/__tests__/index.test.js index 6710f8c..8ad9790 100644 --- a/src/components/first/__tests__/index.test.js +++ b/src/components/first/__tests__/index.test.js @@ -3,7 +3,7 @@ import FirstComponent from "@/components/first"; import MockAppWithProviders from "../../../../mocks/AppWithProvider"; import {server} from "../../../../mocks/server"; import {rest} from "msw"; -import {GET_USER_ROUTE} from "@/core/data/apiRoutes"; +import {GET_USER} from "@/core/data/apiRoutes"; describe("First Component From First Page", () => { describe("Rendering", () => { @@ -46,7 +46,7 @@ describe("First Component From First Page", () => { }); it("Show Login Button And Do Not Show Dashboard Button When User Authentication Is Expired", async () => { localStorage.setItem("_token", 'token'); - server.use([rest.get(GET_USER_ROUTE, (req, res, ctx) => { + server.use([rest.get(GET_USER, (req, res, ctx) => { return res(ctx.status(403)) })]) render(); diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallPanel/CallActions/index.jsx b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallPanel/CallActions/index.jsx deleted file mode 100644 index c7058fe..0000000 --- a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallPanel/CallActions/index.jsx +++ /dev/null @@ -1,10 +0,0 @@ -import useAnswers from "@/lib/callWidget/hooks/useAnswers"; - -const CallActions = () => { - const {answer} = useAnswers() - return ( - <>CallActions - {answer.id} - ) -} - -export default CallActions \ No newline at end of file diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallPanel/CallHistory/index.jsx b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallPanel/CallHistory/index.jsx deleted file mode 100644 index ed053d2..0000000 --- a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallPanel/CallHistory/index.jsx +++ /dev/null @@ -1,10 +0,0 @@ -import useAnswers from "@/lib/callWidget/hooks/useAnswers"; - -const CallHistory = () => { - const {answer} = useAnswers() - return ( - <>CallHistory - {answer.id} - ) -} - -export default CallHistory \ No newline at end of file diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallPanel/index.jsx b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallPanel/index.jsx deleted file mode 100644 index 9fa1b72..0000000 --- a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallPanel/index.jsx +++ /dev/null @@ -1,18 +0,0 @@ -import {Grid} from "@mui/material"; -import CallActions from "@/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallPanel/CallActions"; -import CallHistory from "@/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallPanel/CallHistory"; - -const CallPanel = () => { - return ( - - - - - - - - - ) -} - -export default CallPanel \ No newline at end of file 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 new file mode 100644 index 0000000..d1a68f7 --- /dev/null +++ b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallActions/index.jsx @@ -0,0 +1,7 @@ +const CallActions = ({tab}) => { + return ( + <>CallActions - {tab.id} + ) +} + +export default CallActions diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallHistory/ErrorOrEmpty/__tests__/index.test.js b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallHistory/ErrorOrEmpty/__tests__/index.test.js new file mode 100644 index 0000000..91d5651 --- /dev/null +++ b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallHistory/ErrorOrEmpty/__tests__/index.test.js @@ -0,0 +1,19 @@ +import {render, screen} from "@testing-library/react"; +import ErrorOrEmpty from "../../ErrorOrEmpty"; +import MockAppWithProviders from "../../../../../../../../../../../mocks/AppWithProvider"; + +describe("ErrorOrEmpty Component From call history", () => { + describe("Rendering", () => { + it("error fetching history of call text rendered", () => { + render(); + const errorText = screen.queryByText('خطا در دریافت تاریخچه تماس دریافتی'); + expect(errorText).toBeInTheDocument(); + }); + it("empty fetching history of call text rendered", () => { + render(); + const emptyText = screen.queryByText('تاریخچه ای برای تماس دریافتی یافت نشد'); + expect(emptyText).toBeInTheDocument(); + + }); + }); +}); \ No newline at end of file diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallHistory/ErrorOrEmpty/index.jsx b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallHistory/ErrorOrEmpty/index.jsx new file mode 100644 index 0000000..bb0f818 --- /dev/null +++ b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallHistory/ErrorOrEmpty/index.jsx @@ -0,0 +1,33 @@ +import {useTranslations} from "next-intl"; +import {Typography} from "@mui/material"; + +const ErrorOrEmpty = ({isErrorOrEmpty}) => { + const t = useTranslations(); + return ( + <> + {isErrorOrEmpty === "error" ? + + {t("CallHistory.call_history_error_fetching")} + + : isErrorOrEmpty === "empty" ? + + {t("CallHistory.call_history_not_found")} + : <> + } + + ) +} + +export default ErrorOrEmpty diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallHistory/HistoryHeader/__tests__/index.test.js b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallHistory/HistoryHeader/__tests__/index.test.js new file mode 100644 index 0000000..51d700e --- /dev/null +++ b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallHistory/HistoryHeader/__tests__/index.test.js @@ -0,0 +1,21 @@ +import {render, screen} from "@testing-library/react"; +import HistoryHeader from "../../HistoryHeader"; +import MockAppWithProviders from "../../../../../../../../../../../mocks/AppWithProvider"; + +describe("HistoryHeader Component From call history", () => { + describe("Rendering", () => { + const tab = { + phone_number: "09111111111" + } + it("text of header rendered", () => { + render(); + const textHeader = screen.queryByText(/تاریخچه تماس های/i); + expect(textHeader).toBeInTheDocument(); + }); + it("caller number in header rendered", () => { + render(); + const callerNumber = screen.queryByText(/09111111111/i); + expect(callerNumber).toBeInTheDocument(); + }); + }); +}); \ No newline at end of file diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallHistory/HistoryHeader/index.jsx b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallHistory/HistoryHeader/index.jsx new file mode 100644 index 0000000..21a3270 --- /dev/null +++ b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallHistory/HistoryHeader/index.jsx @@ -0,0 +1,25 @@ +import {useTranslations} from "next-intl"; +import {Stack, Typography} from "@mui/material"; + +const HistoryHeader = ({tab}) => { + const t = useTranslations(); + return ( + + + .... {t("CallHistory.call_history_of")}: + + + {tab.phone_number} .... + + + ) +} + +export default HistoryHeader diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallHistory/ListItemOfCalls/PreviousOperatorData/__tests__/index.test.js b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallHistory/ListItemOfCalls/PreviousOperatorData/__tests__/index.test.js new file mode 100644 index 0000000..4fd4519 --- /dev/null +++ b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallHistory/ListItemOfCalls/PreviousOperatorData/__tests__/index.test.js @@ -0,0 +1,22 @@ +import {render, screen} from "@testing-library/react"; +import MockAppWithProviders from "../../../../../../../../../../../../mocks/AppWithProvider"; +import PreviousOperatorData from "../../PreviousOperatorData"; + +describe("PreviousOperatorData Component From call history", () => { + describe("Rendering", () => { + const historyItem = { + operator_name: "test name", + answer_date: "test date" + } + it("operator name rendered", () => { + render(); + const operatorName = screen.queryByText("test name"); + expect(operatorName).toBeInTheDocument(); + }); + it("operator answer date rendered", () => { + render(); + const operatorAnswerDate = screen.queryByText("test date"); + expect(operatorAnswerDate).toBeInTheDocument(); + }); + }); +}); \ No newline at end of file diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallHistory/ListItemOfCalls/PreviousOperatorData/index.jsx b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallHistory/ListItemOfCalls/PreviousOperatorData/index.jsx new file mode 100644 index 0000000..afd2ad7 --- /dev/null +++ b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallHistory/ListItemOfCalls/PreviousOperatorData/index.jsx @@ -0,0 +1,28 @@ +import {useTranslations} from "next-intl"; +import {Avatar, Box, ListItem, ListItemAvatar, ListItemText, Typography} from "@mui/material"; +import HeadsetMicIcon from '@mui/icons-material/HeadsetMic'; + +const PreviousOperatorData = ({historyItem}) => { + const t = useTranslations(); + return ( + + + + + + + + + {historyItem.operator_name} + + + ({t("CallHistory.operator")}) + + + } secondary={historyItem.answer_date}/> + + ) +} + +export default PreviousOperatorData diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallHistory/ListItemOfCalls/Topics/__tests__/index.test.js b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallHistory/ListItemOfCalls/Topics/__tests__/index.test.js new file mode 100644 index 0000000..d9d77e4 --- /dev/null +++ b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallHistory/ListItemOfCalls/Topics/__tests__/index.test.js @@ -0,0 +1,22 @@ +import {render, screen} from "@testing-library/react"; +import MockAppWithProviders from "../../../../../../../../../../../../mocks/AppWithProvider"; +import Topics from "../../Topics"; + +describe("Topics Component From call history", () => { + describe("Rendering", () => { + const historyItem = { + category_name: "test category", + subcategory_name: "test sub category" + } + it("operator name rendered", () => { + render(); + const operatorName = screen.queryByText("test category"); + expect(operatorName).toBeInTheDocument(); + }); + it("operator answer date rendered", () => { + render(); + const operatorAnswerDate = screen.queryByText("test sub category"); + expect(operatorAnswerDate).toBeInTheDocument(); + }); + }); +}); \ No newline at end of file diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallHistory/ListItemOfCalls/Topics/index.jsx b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallHistory/ListItemOfCalls/Topics/index.jsx new file mode 100644 index 0000000..3b57744 --- /dev/null +++ b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallHistory/ListItemOfCalls/Topics/index.jsx @@ -0,0 +1,43 @@ +import {useTranslations} from "next-intl"; +import {Box, List, ListItem, Typography} from "@mui/material"; + +const Topics = ({historyItem}) => { + const t = useTranslations(); + return ( + + + + + + {t("CallHistory.category_name")}: + + + {historyItem.category_name} + + + + + + + + + {t("CallHistory.subcategory_name")}: + + + {historyItem.subcategory_name} + + + + + + ) +} + +export default Topics diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallHistory/ListItemOfCalls/index.jsx b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallHistory/ListItemOfCalls/index.jsx new file mode 100644 index 0000000..7af4fd5 --- /dev/null +++ b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallHistory/ListItemOfCalls/index.jsx @@ -0,0 +1,21 @@ +import {useTranslations} from "next-intl"; +import {Grow, List} from "@mui/material"; +import PreviousOperatorData from "./PreviousOperatorData"; +import Topics from "./Topics"; + +const ListItemOfCalls = ({historyItem}) => { + const t = useTranslations(); + + // console.log(historyItem) + + return ( + + + + + + + ) +} + +export default ListItemOfCalls diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallHistory/LoadingHistory/__tests__/intex.test.js b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallHistory/LoadingHistory/__tests__/intex.test.js new file mode 100644 index 0000000..1592a02 --- /dev/null +++ b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallHistory/LoadingHistory/__tests__/intex.test.js @@ -0,0 +1,68 @@ +import {render, screen} from "@testing-library/react"; +import LoadingHistory from "../../LoadingHistory"; +import MockAppWithProviders from "../../../../../../../../../../../mocks/AppWithProvider"; + +describe("LoadingHistory Component From call history", () => { + describe("Rendering", () => { + it("loading list rendered", () => { + render(); + const loadingList = screen.queryByTestId("loading-list"); + expect(loadingList).toBeInTheDocument(); + }); + it("loading list item avatar rendered", () => { + render(); + const loadingListItemAvatar = screen.queryByTestId("loading-listItemAvatar"); + expect(loadingListItemAvatar).toBeInTheDocument(); + }); + it("loading list item text rendered", () => { + render(); + const loadingListItemText = screen.queryByTestId("loading-listItemText"); + expect(loadingListItemText).toBeInTheDocument(); + }); + it("loading list item category rendered", () => { + render(); + const loadingListItemCat = screen.queryByTestId("loading-listItem-cat"); + expect(loadingListItemCat).toBeInTheDocument(); + }); + it("loading list item sub category rendered", () => { + render(); + const loadingListItemSubCat = screen.queryByTestId("loading-listItem-subCat"); + expect(loadingListItemSubCat).toBeInTheDocument(); + }); + it("operator avatar skeleton rendered", () => { + render(); + const operatorAvatarSkeleton = screen.queryByTestId("operator-avatar-skeleton"); + expect(operatorAvatarSkeleton).toBeInTheDocument(); + }); + it("operator name skeleton rendered", () => { + render(); + const operatorNameSkeleton = screen.queryByTestId("operator-name-skeleton"); + expect(operatorNameSkeleton).toBeInTheDocument(); + }); + it("operator answer date skeleton rendered", () => { + render(); + const operatorAnswerDateSkeleton = screen.queryByTestId("operator-answer-date-skeleton"); + expect(operatorAnswerDateSkeleton).toBeInTheDocument(); + }); + it("operator category header skeleton rendered", () => { + render(); + const operatorCategoryHeaderSkeleton = screen.queryByTestId("operator-category-header-skeleton"); + expect(operatorCategoryHeaderSkeleton).toBeInTheDocument(); + }); + it("operator category value skeleton rendered", () => { + render(); + const operatorCategoryValueSkeleton = screen.queryByTestId("operator-category-value-skeleton"); + expect(operatorCategoryValueSkeleton).toBeInTheDocument(); + }); + it("operator subCategory header skeleton rendered", () => { + render(); + const operatorSubCategoryHeaderSkeleton = screen.queryByTestId("operator-subCategory-header-skeleton"); + expect(operatorSubCategoryHeaderSkeleton).toBeInTheDocument(); + }); + it("operator subCategory value skeleton rendered", () => { + render(); + const operatorSubCategoryValueSkeleton = screen.queryByTestId("operator-subCategory-value-skeleton"); + expect(operatorSubCategoryValueSkeleton).toBeInTheDocument(); + }); + }); +}); \ No newline at end of file diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallHistory/LoadingHistory/index.jsx b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallHistory/LoadingHistory/index.jsx new file mode 100644 index 0000000..2c9900e --- /dev/null +++ b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallHistory/LoadingHistory/index.jsx @@ -0,0 +1,86 @@ +import {useTranslations} from "next-intl"; +import {Box, List, ListItem, ListItemAvatar, ListItemText, Skeleton} from "@mui/material"; + +const LoadingHistory = () => { + const t = useTranslations(); + return ( + <> + + + + + + + + + } secondary={ + + }/> + + + + + + + + + + + + + + + + + + + + + + + ) +} + +export default LoadingHistory 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 new file mode 100644 index 0000000..c83bc49 --- /dev/null +++ b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallHistory/index.jsx @@ -0,0 +1,65 @@ +import {useTranslations} from "next-intl"; +import {Box, Chip, Divider, Stack} from "@mui/material"; +import HeadphonesIcon from '@mui/icons-material/Headphones'; +import LoadingHistory from "./LoadingHistory"; +import HistoryHeader from "./HistoryHeader"; +import ErrorOrEmpty from "./ErrorOrEmpty"; +import useCallerHistory from "@/lib/callWidget/hooks/useCallerHistory"; +import ListItemOfCalls from "./ListItemOfCalls"; +import {useState} from "react"; + +const max_size = 10; +const CallHistory = ({tab}) => { + const t = useTranslations(); + const { + firstItemOfHistory, + historyList, + isLoadingHistoryList, + errorHistoryList + } = useCallerHistory(tab.id, tab.phone_number, max_size); + const [showRestOfHistory, setShowRestOfHistory] = useState(false); + + return ( + + + + {errorHistoryList ? ( + + ) : isLoadingHistoryList ? ( + + ) : !firstItemOfHistory ? ( + + ) : ( + + + {historyList && ( + + } + onClick={() => setShowRestOfHistory(true)} + label={t("show_more")} + disabled={showRestOfHistory} + /> + + )} + {showRestOfHistory && ( + <> + {historyList.map((historyItem, index) => ( + <> + + + + ))} + ) + } + + )} + + + ) +} + +export default CallHistory diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/index.jsx b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/index.jsx new file mode 100644 index 0000000..8fc1d87 --- /dev/null +++ b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/index.jsx @@ -0,0 +1,23 @@ +import {Divider, Grid} from "@mui/material"; +import CallActions from "@/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallActions"; +import CallHistory from "@/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallHistory"; + +const CallTabPanel = ({tab}) => { + return ( + + + + + + + + + + + + ) +} + +export default CallTabPanel \ No newline at end of file diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabsList/CallTabDetails.jsx b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabsList/CallTabDetails.jsx new file mode 100644 index 0000000..9427201 --- /dev/null +++ b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabsList/CallTabDetails.jsx @@ -0,0 +1,17 @@ +import {Stack, Typography} from "@mui/material"; +import CallIcon from '@mui/icons-material/Call'; +import CallTabTime from "@/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabsList/CallTabTime"; + +const CallTabDetails = ({tab}) => { + + return ( + + + + {tab.phone_number} + + + + ) +} +export default CallTabDetails \ No newline at end of file diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabsList/CallTabLabel.jsx b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabsList/CallTabLabel.jsx new file mode 100644 index 0000000..e49bcdc --- /dev/null +++ b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabsList/CallTabLabel.jsx @@ -0,0 +1,30 @@ +import {IconButton, Stack, Zoom} from "@mui/material"; +import CloseIcon from '@mui/icons-material/Close'; +import useAnswers from "@/lib/callWidget/hooks/useAnswers"; +import CallTabDetails + from "@/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabsList/CallTabDetails"; + +const CallTabLabel = ({tab, index}) => { + const {closeAnswerTab, activeTab} = useAnswers() + const handleCloseClick = (id) => { + closeAnswerTab(id) + }; + + return ( + + + + handleCloseClick(tab.id)}> + + + + + ) +} + +export default CallTabLabel diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabsList/CallTabTime.jsx b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabsList/CallTabTime.jsx new file mode 100644 index 0000000..5d627e4 --- /dev/null +++ b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabsList/CallTabTime.jsx @@ -0,0 +1,29 @@ +import {Typography} from "@mui/material"; +import {useEffect, useState} from "react"; +import moment from "jalali-moment"; +import useLanguage from "@/lib/app/hooks/useLanguage"; + +const CallTabTime = ({realTimeDate}) => { + const {languageApp} = useLanguage() + const [date, setDate] = useState('...') + const changeTabTime = (tabTime) => { + moment.relativeTimeThreshold('ss', 1) + return moment(tabTime).locale(languageApp).fromNow() + } + + useEffect(() => { + const timer = setInterval(() => { + setDate(changeTabTime(realTimeDate)) + }, 1000) + + return () => { + clearInterval(timer) + } + }, [realTimeDate]); + + return ( + {date} + ) +} +export default CallTabTime \ No newline at end of file diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabsList/__test__/CallTabDetails.test.js b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabsList/__test__/CallTabDetails.test.js new file mode 100644 index 0000000..7d50bff --- /dev/null +++ b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabsList/__test__/CallTabDetails.test.js @@ -0,0 +1,28 @@ +import {render, screen, waitFor} from "@testing-library/react"; +import MockAppWithProviders from "../../../../../../../../../mocks/AppWithProvider"; +import CallTabDetails + from "@/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabsList/CallTabDetails"; + + +const tab = { + phone_number: "09134849737", + id: 1, + date: new Date() +} + +describe("Call Tab Details from Call Tan List", () => { + describe("Rendering", () => { + it('Should see the phone number on tab detail', async () => { + render( + + + + ) + const phoneNumberElement = screen.getByText("09134849737") + await waitFor(() => { + expect(phoneNumberElement).toHaveTextContent("09134849737") + }) + }); + + }) +}) \ No newline at end of file diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabsList/__test__/CallTabTime.test.js b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabsList/__test__/CallTabTime.test.js new file mode 100644 index 0000000..31b9a1b --- /dev/null +++ b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabsList/__test__/CallTabTime.test.js @@ -0,0 +1,25 @@ +import {render, screen, waitFor} from "@testing-library/react"; +import MockAppWithProviders from "../../../../../../../../../mocks/AppWithProvider"; +import CallTabTime from "@/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabsList/CallTabTime"; + +const tab = { + phone_number: "09134849737", + id: 1, + date: new Date() +} + +describe("Call Tab Details from Call Tan List", () => { + describe("Rendering", () => { + it('Should see the date on tab detail', async () => { + render( + + + + ) + + await waitFor(() => { + expect(screen.getByText(/ثانیه پیش/i)).toBeInTheDocument() + }) + }); + }) +}) \ No newline at end of file diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabsList/index.jsx b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabsList/index.jsx new file mode 100644 index 0000000..ff26544 --- /dev/null +++ b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabsList/index.jsx @@ -0,0 +1,33 @@ +import {Box, Tab, Tabs} from "@mui/material"; +import CallTabLabel + from "@/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabsList/CallTabLabel"; +import useAnswers from "@/lib/callWidget/hooks/useAnswers"; + +const CallTabsList = () => { + const {answersList, changeActiveTabAnswers, activeTab} = useAnswers() + const handleChange = (event, newValue) => { + changeActiveTabAnswers(newValue) + }; + + return ( + + + {answersList.map((answer, index) => ( + } key = {answer.id}/> + ))} + + + ) +} + +export default CallTabsList diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/index.jsx b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/index.jsx new file mode 100644 index 0000000..8a96506 --- /dev/null +++ b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/index.jsx @@ -0,0 +1,17 @@ +import CallTabPanel from "@/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel"; +import useAnswers from "@/lib/callWidget/hooks/useAnswers"; +import CallTabsList from "src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabsList"; + +const CallTabs = () => { + const {answersList} = useAnswers() + + return ( + <> + + {answersList.map((answer) => ( + + ))} + + ) +} +export default CallTabs \ No newline at end of file diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/index.jsx b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/index.jsx index 84e443c..e7d4278 100644 --- a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/index.jsx +++ b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/index.jsx @@ -4,18 +4,18 @@ import {useEffect} from "react"; import useCallDialog from "@/lib/callWidget/hooks/useCallDialog"; import useAnswers from "@/lib/callWidget/hooks/useAnswers"; import useSocket from "@/lib/app/hooks/useSocket"; -import CallPanel from "@/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallPanel"; +import CallTabs from "@/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs"; const CallWidgetDialog = () => { const {openCallDialog, setOpenCallDialog} = useCallDialog() - const {answer, newAnswer} = useAnswers() + const {answersList, newAnswerTab} = useAnswers() const socket = useSocket() useEffect(() => { const answerCall = (_data, act) => { const data = JSON.parse(_data) - newAnswer({ - id: data.id, + newAnswerTab({ + id: data.call_id, phone_number: data.phone_number }) act() @@ -28,22 +28,20 @@ const CallWidgetDialog = () => { }); useEffect(() => { - if (answer) { - setOpenCallDialog(true) + if (answersList.length === 0) { + setOpenCallDialog(false) return } - setOpenCallDialog(false) - }, [answer]); + setOpenCallDialog(true) + }, [answersList.length]); return ( - {answer && ( - - )} + ) } diff --git a/src/components/layouts/Dashboard/Header/index.jsx b/src/components/layouts/Dashboard/Header/index.jsx index 8760960..e8af065 100644 --- a/src/components/layouts/Dashboard/Header/index.jsx +++ b/src/components/layouts/Dashboard/Header/index.jsx @@ -1,6 +1,8 @@ import MenuIcon from "@mui/icons-material/Menu"; -import {AppBar, Box, Container, CssBaseline, IconButton, Stack, Toolbar, useTheme} from "@mui/material"; import ProfileMenu from "./ProfileMenu"; +import {useTheme} from "@mui/material/styles"; +import CssBaseline from "@mui/material/CssBaseline"; +import {AppBar, Box, Container, IconButton, Stack, Toolbar} from "@mui/material"; function Header({drawerWidth, handleDrawerToggle}) { const theme = useTheme(); diff --git a/src/core/components/DataTable.jsx b/src/core/components/DataTable.jsx index adedf3d..32259fc 100644 --- a/src/core/components/DataTable.jsx +++ b/src/core/components/DataTable.jsx @@ -121,7 +121,7 @@ function DataTable(props) { }} enableColumnFilterModes muiTablePaperProps={{elevation: 0}} - rowCount={data?.meta.totalRowCount} + rowCount={data?.meta?.totalRowCount ?? 0} onColumnFilterFnsChange={setColumnFilterFns} onColumnFiltersChange={setColumnFilters} onPaginationChange={setPagination} @@ -130,7 +130,7 @@ function DataTable(props) { renderTopToolbarCustomActions={({table}) => ( <> {props.enableCustomToolbar /* send condition */ - ? /* send component */ + ? /* send component */ : ""} )} @@ -163,7 +163,7 @@ function DataTable(props) { }} positionActionsColumn={"last"} enableRowActions={props.enableRowActions} - renderRowActions={({row}) => } + renderRowActions={({row}) => } {...props} /> ); diff --git a/src/core/components/LoadingHardPage.jsx b/src/core/components/LoadingHardPage.jsx index 4bbdbf4..5d00cd0 100644 --- a/src/core/components/LoadingHardPage.jsx +++ b/src/core/components/LoadingHardPage.jsx @@ -1,5 +1,6 @@ -import {Backdrop, Box, styled} from "@mui/material"; import SvgLoading from "@/core/components/svgs/SvgLoading"; +import {Backdrop, Box} from "@mui/material"; +import {styled} from "@mui/material/styles"; const LoadingImage = styled(Box)({ "@keyframes load": { diff --git a/src/core/components/StyledForm.jsx b/src/core/components/StyledForm.jsx index 41eeabc..253fc12 100644 --- a/src/core/components/StyledForm.jsx +++ b/src/core/components/StyledForm.jsx @@ -1,5 +1,5 @@ -import {styled} from "@mui/material"; import {Form} from "formik"; +import {styled} from "@mui/material/styles"; const StyledForm = styled(Form)``; diff --git a/src/core/components/svgs/Svg403.jsx b/src/core/components/svgs/Svg403.jsx index 720828c..d706ed0 100644 --- a/src/core/components/svgs/Svg403.jsx +++ b/src/core/components/svgs/Svg403.jsx @@ -1,4 +1,4 @@ -import {useTheme} from "@mui/material"; +import {useTheme} from "@mui/material/styles"; const Svg403 = ({width, height}) => { const theme = useTheme() diff --git a/src/core/components/svgs/Svg404.jsx b/src/core/components/svgs/Svg404.jsx index e46ab12..159d7c6 100644 --- a/src/core/components/svgs/Svg404.jsx +++ b/src/core/components/svgs/Svg404.jsx @@ -1,4 +1,4 @@ -import {useTheme} from "@mui/material"; +import {useTheme} from "@mui/material/styles"; const Svg404 = ({width, height}) => { const theme = useTheme() diff --git a/src/core/components/svgs/SvgChangePassword.jsx b/src/core/components/svgs/SvgChangePassword.jsx index a7ee82f..1221d28 100644 --- a/src/core/components/svgs/SvgChangePassword.jsx +++ b/src/core/components/svgs/SvgChangePassword.jsx @@ -1,4 +1,4 @@ -import {useTheme} from "@mui/material"; +import {useTheme} from "@mui/material/styles"; const SvgChangePassword = ({width, height}) => { const theme = useTheme() diff --git a/src/core/components/svgs/SvgDashboard.jsx b/src/core/components/svgs/SvgDashboard.jsx index a5b40e0..a636bf4 100644 --- a/src/core/components/svgs/SvgDashboard.jsx +++ b/src/core/components/svgs/SvgDashboard.jsx @@ -1,9 +1,9 @@ -import {useTheme} from "@mui/material"; +import {useTheme} from "@mui/material/styles"; const SvgDashboard = ({width, height}) => { const theme = useTheme() const fillColor = theme.palette.primary.main - + return ( diff --git a/src/core/components/svgs/SvgLoading.jsx b/src/core/components/svgs/SvgLoading.jsx index f9cea4a..9ff61ff 100644 --- a/src/core/components/svgs/SvgLoading.jsx +++ b/src/core/components/svgs/SvgLoading.jsx @@ -1,4 +1,4 @@ -import {useTheme} from "@mui/material"; +import {useTheme} from "@mui/material/styles"; const SvgLoading = ({width, height}) => { const theme = useTheme() diff --git a/src/core/components/svgs/SvgLogin.jsx b/src/core/components/svgs/SvgLogin.jsx index 643c195..edf3b51 100644 --- a/src/core/components/svgs/SvgLogin.jsx +++ b/src/core/components/svgs/SvgLogin.jsx @@ -1,4 +1,4 @@ -import {useTheme} from "@mui/material"; +import {useTheme} from "@mui/material/styles"; const SvgLogin = ({width, height}) => { const theme = useTheme() diff --git a/src/core/data/apiRoutes.js b/src/core/data/apiRoutes.js index 4d0231f..127d1df 100644 --- a/src/core/data/apiRoutes.js +++ b/src/core/data/apiRoutes.js @@ -4,24 +4,51 @@ const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL; export const GET_USER_TOKEN = BASE_URL + "/api/auth/login"; //end login +//edit profile +export const UPDATE_AVATAR = BASE_URL + ""; +//end edit profile + +// role list +export const GET_ROLE_LIST = BASE_URL + "/api/roles/list"; +// role list + +// province list +export const GET_PROVINCE_LIST = BASE_URL + "/api/provinces"; +// province list + +// province list +export const GET_CALLER_HISTORY = BASE_URL + "/api/calls/list"; +// province list + //change password export const SET_USER_PASSWORD = BASE_URL + "/api/profile/change_password"; //end change password //user data -export const GET_USER_ROUTE = BASE_URL + "/api/profile/info"; +export const GET_USER = BASE_URL + "/api/profile/info"; //user data +//sidebar notification +export const GET_SIDEBAR_NOTIFICATION = BASE_URL + "/dashboard/notification" +//sidebar notification + // role management -export const GET_ROLE_MANAGEMENT = +export const GET_ROLES = BASE_URL + "/api/roles" -export const ADD_ROLE_MANAGEMENT = +export const ADD_ROLE = BASE_URL + "/api/roles" -export const UPDATE_ROLE_MANAGEMENT = +export const UPDATE_ROLE = BASE_URL + "/api/roles" -export const DELETE_ROLE_MANAGEMENT = +export const DELETE_ROLE = BASE_URL + "/api/roles" export const GET_PERMISSIONS_LIST = BASE_URL + "/api/permissions/list" -//role management \ No newline at end of file +//role management + +//expert management +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 diff --git a/src/core/data/sidebarMenu.jsx b/src/core/data/sidebarMenu.jsx index 2aeea36..5c14462 100644 --- a/src/core/data/sidebarMenu.jsx +++ b/src/core/data/sidebarMenu.jsx @@ -1,5 +1,6 @@ import SpaceDashboardIcon from "@mui/icons-material/SpaceDashboard"; import AccessibilityIcon from '@mui/icons-material/Accessibility'; +import ManageAccountsIcon from '@mui/icons-material/ManageAccounts'; const sidebarMenu = [ [ @@ -11,6 +12,15 @@ const sidebarMenu = [ selected: false, permission: "all", }, + { + key: "sidebar.expert-management", + type: "page", + name: "expert_management", + route: "/dashboard/expert-management", + icon: , + selected: false, + permission: "all", + }, { key: "sidebar.role-management", name : "role_management", diff --git a/src/lib/app/contexts/user.jsx b/src/lib/app/contexts/user.jsx index 899ac1b..8bf29cc 100644 --- a/src/lib/app/contexts/user.jsx +++ b/src/lib/app/contexts/user.jsx @@ -1,4 +1,4 @@ -import {GET_USER_ROUTE} from "@/core/data/apiRoutes"; +import {GET_USER} from "@/core/data/apiRoutes"; import axios from "axios"; import {createContext, useCallback, useEffect, useReducer} from "react"; import {errorRequest, errorResponse, errorSetting} from "@/core/utils/errorHandler"; @@ -71,7 +71,7 @@ export const UserProvider = ({children}) => { (callback = () => { }) => { axios - .get(GET_USER_ROUTE, { + .get(GET_USER, { headers: {authorization: `Bearer ${state.token}`}, }) .then(({data}) => { diff --git a/src/lib/app/hooks/useNotification.jsx b/src/lib/app/hooks/useNotification.jsx index 075c547..6453ee8 100644 --- a/src/lib/app/hooks/useNotification.jsx +++ b/src/lib/app/hooks/useNotification.jsx @@ -16,10 +16,9 @@ const useNotification = () => { }; const {data, mutate} = useSWR( isNotificationEnabled ? GET_SIDEBAR_NOTIFICATION : '', fetcher , {keepPreviousData:true}) - const notification_count = data //swr config // render data - return {notification_count, update_notification: mutate} + return {notification_count: data, update_notification: mutate} } export default useNotification \ No newline at end of file diff --git a/src/lib/app/hooks/useProvince.jsx b/src/lib/app/hooks/useProvince.jsx new file mode 100644 index 0000000..2bc8c6b --- /dev/null +++ b/src/lib/app/hooks/useProvince.jsx @@ -0,0 +1,30 @@ +import {GET_PROVINCE_LIST} from "@/core/data/apiRoutes"; +import useRequest from "@/lib/app/hooks/useRequest"; +import useSWR from "swr"; + +const useProvince = () => { + const requestServer = useRequest({auth: true, notification: false}) + + //swr config + const fetcher = (...args) => { + return requestServer(args, 'get').then(({data}) => { + return data.data; + }).catch(() => { + }) + }; + + const {data, isLoading} = useSWR(GET_PROVINCE_LIST, fetcher, { + revalidateIfStale: false, + revalidateOnFocus: false, + revalidateOnReconnect: false, + keepPreviousData: true + }); + + return { + provinceList: data, + isLoadingProvinceList: isLoading, + errorProvinceList: !data + } +}; + +export default useProvince; \ No newline at end of file diff --git a/src/lib/app/hooks/useRole.jsx b/src/lib/app/hooks/useRole.jsx new file mode 100644 index 0000000..b4c231c --- /dev/null +++ b/src/lib/app/hooks/useRole.jsx @@ -0,0 +1,30 @@ +import {GET_ROLE_LIST} from "@/core/data/apiRoutes"; +import useRequest from "@/lib/app/hooks/useRequest"; +import useSWR from "swr"; + +const useRole = () => { + const requestServer = useRequest({auth: true, notification: false}) + + //swr config + const fetcher = (...args) => { + return requestServer(args, 'get').then(({data}) => { + return data.data; + }).catch(() => { + }) + }; + + const {data, isLoading} = useSWR(GET_ROLE_LIST, fetcher, { + revalidateIfStale: false, + revalidateOnFocus: false, + revalidateOnReconnect: false, + keepPreviousData: true + }); + + return { + roleList: data, + isLoadingRoleList: isLoading, + errorRoleList: !data + } +}; + +export default useRole; \ No newline at end of file diff --git a/src/lib/callWidget/contexts/answers.jsx b/src/lib/callWidget/contexts/answers.jsx index adbca5c..b5c9c0b 100644 --- a/src/lib/callWidget/contexts/answers.jsx +++ b/src/lib/callWidget/contexts/answers.jsx @@ -2,10 +2,13 @@ import {createContext, useReducer, useState} from "react"; const reducer = (state, action) => { switch (action.type) { - case "CREATE_ANSWER": - return {...action.data} - case "DELETE_ANSWER": - return null + case "CHANGE_ACTIVE_TAB": + return state.map((answer, index) => action.newValue === index ? {...answer, active: true} : { + ...answer, + active: false + }); + case "CHANGE_LIST": + return [...action.newList] default: return state; } @@ -13,13 +16,16 @@ const reducer = (state, action) => { export const AnswersContext = createContext() export const AnswersProvider = ({children}) => { const [openCallDialog, setOpenCallDialog] = useState(false) - const [state, dispatch] = useReducer(reducer, null); + const [activeTab, setActiveTab] = useState(0) + const [state, dispatch] = useReducer(reducer, []); return {children} } diff --git a/src/lib/callWidget/hooks/useAnswers.jsx b/src/lib/callWidget/hooks/useAnswers.jsx index ec606db..68f8b46 100644 --- a/src/lib/callWidget/hooks/useAnswers.jsx +++ b/src/lib/callWidget/hooks/useAnswers.jsx @@ -2,16 +2,37 @@ import {useContext} from "react"; import {AnswersContext} from "@/lib/callWidget/contexts/answers"; const useAnswers = () => { - const {state, dispatch} = useContext(AnswersContext) + const {state, dispatch, activeTab, setActiveTab} = useContext(AnswersContext) - const newAnswer = (data) => { - dispatch({type: 'CREATE_ANSWER', data}) + const changeActiveTabAnswers = (newValue) => { + dispatch({type: 'CHANGE_ACTIVE_TAB', newValue}) + setActiveTab(newValue) } - const deleteAnswer = () => { - dispatch({type: 'DELETE_ANSWER'}) + 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) } - return {answer: state, newAnswer, deleteAnswer} + const closeAnswerTab = (id) => { + 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) + } + dispatch({type: 'CHANGE_LIST', newList}) + } + + return {answersList: state, activeTab, setActiveTab, changeActiveTabAnswers, closeAnswerTab, newAnswerTab} } export default useAnswers \ No newline at end of file diff --git a/src/lib/callWidget/hooks/useCallerHistory.jsx b/src/lib/callWidget/hooks/useCallerHistory.jsx new file mode 100644 index 0000000..68ee6d0 --- /dev/null +++ b/src/lib/callWidget/hooks/useCallerHistory.jsx @@ -0,0 +1,37 @@ +import useRequest from "@/lib/app/hooks/useRequest"; +import {useEffect, useState} from "react"; +import useSWR from "swr"; +import {GET_CALLER_HISTORY} from "@/core/data/apiRoutes"; + +const useCallerHistory = (call_id, phone_number, max_size) => { + const requestServer = useRequest({auth: true, notification: false}); + const [validData, setValidData] = useState([]); + + //swr config + const fetcher = (...args) => { + return requestServer(args, 'get').then(({data}) => { + return data.data; + }).catch(() => { + }) + }; + + const {data, isLoading} = useSWR(`${GET_CALLER_HISTORY}?phone_number=${phone_number}&size=${max_size}`, fetcher, { + revalidateIfStale: false, + revalidateOnFocus: false, + revalidateOnReconnect: false, + keepPreviousData: true + }); + + useEffect(() => { + data && setValidData(data.filter((item) => item.id != call_id)); + }, [data, call_id]); + + return { + firstItemOfHistory: validData.length === 0 ? false : validData[0], + historyList: validData.length < 2 ? false : validData.slice(1), + isLoadingHistoryList: true, + errorHistoryList: false + } +}; + +export default useCallerHistory; \ No newline at end of file diff --git a/src/pages/_app.jsx b/src/pages/_app.jsx index c5fad77..4cd2a1d 100644 --- a/src/pages/_app.jsx +++ b/src/pages/_app.jsx @@ -1,4 +1,4 @@ -import "&/fontiran.scss"; +import "&/fontiran.css"; import AppLayout from "@/layouts/AppLayout"; import MuiLayout from "@/layouts/MuiLayout"; import {LanguageProvider} from "@/lib/app/contexts/language"; @@ -14,11 +14,11 @@ const App = ({Component, pageProps}) => { return (<> - - - {pageProps.messages ? : ''} + + + {pageProps.messages ? : ''} - + diff --git a/src/pages/dashboard/expert-management/index.jsx b/src/pages/dashboard/expert-management/index.jsx new file mode 100644 index 0000000..60dc6e2 --- /dev/null +++ b/src/pages/dashboard/expert-management/index.jsx @@ -0,0 +1,23 @@ +import WithAuthMiddleware from "@/middlewares/WithAuth"; +import {parse} from "next-useragent"; +import DashboardExpertManagementComponent from "@/components/dashboard/expert-management"; + +export default function ExpertManagement() { + return ( + + + + ); +} + +export async function getServerSideProps({req, locale}) { + const {isBot} = parse(req.headers["user-agent"]); + return { + props: { + messages: (await import(`&/locales/${locale}/app.json`)).default, + title: "Dashboard.expert_management", + isBot, + locale + }, + }; +}