diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d06af2b..27bf1b1 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,118 +1,118 @@ image: node:latest stages: - - build - - test - - test:build - - deploy + - 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 b9ae85f..18458fb 100644 --- a/mocks/handler.js +++ b/mocks/handler.js @@ -3,7 +3,10 @@ import { GET_ROLE_MANAGEMENT, GET_SIDEBAR_NOTIFICATION, GET_USER_ROUTE, - SET_USER_PASSWORD + SET_USER_PASSWORD, + EXPERT_MANAGEMENT, + GET_PROVINCE_LIST, + GET_ROLE_LIST, } from "@/core/data/apiRoutes"; import {rest} from "msw"; @@ -95,5 +98,49 @@ export const handler = [ ), ); }), - + rest.get(GET_PROVINCE_LIST, (req, res, ctx) => { + return res(ctx.json({ + data: [ + { + id: 1, + name: "آذربایجان شرقی" + }, + { + id: 3, + name: "اردبیل" + } + ] + })) + }), + 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: "مدیر" + } + ] + })) + }), + rest.post(EXPERT_MANAGEMENT, (req, res, ctx) => { + return res( + ctx.status(200), + ); + }), + rest.delete(`${EXPERT_MANAGEMENT}/:id`, (req, res, ctx) => { + return res( + ctx.status(200), + ); + }), + rest.post(`${EXPERT_MANAGEMENT}/:id`, (req, res, ctx) => { + return res( + ctx.status(200), + ); + }), ] \ No newline at end of file diff --git a/public/locales/fa/app.json b/public/locales/fa/app.json index 9e28007..c17c938 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,65 @@ "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": "ویرایش کارشناس" } } 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..9c4e763 --- /dev/null +++ b/src/components/dashboard/expert-management/DataTable/index.jsx @@ -0,0 +1,189 @@ +import DataTable from "@/core/components/DataTable"; +import MuiDatePicker from "@/core/components/MuiDatePicker"; +import {EXPERT_MANAGEMENT} 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: "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_fa", + 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, + 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..acac74c --- /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 {EXPERT_MANAGEMENT} 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, fetchUrl}) => { + 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(`${EXPERT_MANAGEMENT}`, 'post', {auth: true, data: formData}) + .then((response) => { + setOpenCreateDialog(false) + mutate(fetchUrl) + }).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..e50bc7e --- /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, fetchUrl}) => { + 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..03a9e82 --- /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 {EXPERT_MANAGEMENT} from "@/core/data/apiRoutes"; +import useRequest from "@/lib/app/hooks/useRequest"; +import {useTranslations} from "next-intl"; + +const Delete = ({rowId, fetchUrl, mutate}) => { + const t = useTranslations(); + const requestServer = useRequest({auth: true}); + const [openDeleteDialog, setOpenDeleteDialog] = useState(false); + const [isSubmitting, setIsSubmitting] = useState(false); + + const handleDelete = () => { + setIsSubmitting(true) + requestServer(`${EXPERT_MANAGEMENT}/${rowId}`, 'DELETE') + .then((response) => { + setOpenDeleteDialog(false); + mutate(fetchUrl) + }) + .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..3b60506 --- /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 {EXPERT_MANAGEMENT} from "@/core/data/apiRoutes"; +import PersonalInfo from "./PersonalInfo"; +import UserInfo from "./UserInfo"; +import RestInfo from "./RestInfo"; + +const UpdateContent = ({row, fetchUrl, 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(`${EXPERT_MANAGEMENT}/${row.original.id}`, 'post', {auth: true, data: formData}) + .then((response) => { + setOpenUpdateDialog(false) + mutate(fetchUrl) + }).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..55e7429 --- /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, fetchUrl, 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..1b0eff5 --- /dev/null +++ b/src/components/dashboard/expert-management/TableRowActions.jsx @@ -0,0 +1,28 @@ +import {Box} from "@mui/material"; +import Update from "./Form/UpdateForm" +import Delete from "./Form/DeleteForm"; +import ChangePassword from "./Form/ChangePassword"; + +const TableRowActions = ({row, mutate, fetchUrl}) => { + 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..7eb047b --- /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, fetchUrl}) { + 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/core/data/apiRoutes.js b/src/core/data/apiRoutes.js index 1a6e89c..34e4417 100644 --- a/src/core/data/apiRoutes.js +++ b/src/core/data/apiRoutes.js @@ -8,6 +8,14 @@ export const GET_USER_TOKEN = BASE_URL + "/api/auth/login"; 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 + //change password export const SET_USER_PASSWORD = BASE_URL + "/api/profile/change_password"; //end change password @@ -33,4 +41,8 @@ export const DELETE_ROLE_MANAGEMENT = export const GET_PERMISSIONS_LIST = BASE_URL + "/api/permissions/list" -//role management \ No newline at end of file +//role management + +//expert management +export const EXPERT_MANAGEMENT = 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/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/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 + }, + }; +}