diff --git a/mocks/handler.js b/mocks/handler.js index 3a8f75d..45183f9 100644 --- a/mocks/handler.js +++ b/mocks/handler.js @@ -1,10 +1,42 @@ -import {GET_USER_ROUTE} from "@/core/data/apiRoutes"; +import {GET_PROVINCE_LIST, GET_ROLE_LIST, GET_USER_ROUTE} from "@/core/data/apiRoutes"; import {rest} from "msw"; -export const handler = [rest.get(GET_USER_ROUTE, (req, res, ctx) => { - return res(ctx.json({ - data: { - id: 10 - } - })) -})] \ No newline at end of file +export const handler = [ + rest.get(GET_USER_ROUTE, (req, res, ctx) => { + return res(ctx.json({ + data: { + id: 10 + } + })) + }), + 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: "passenger_office_chief", + name_fa: "رئیس اداره مسافری استان" + }, + { + id: 4, + name: "province_manager", + name_fa: "مدیر کل استانی" + } + ] + })) + }) +] \ No newline at end of file diff --git a/public/locales/fa/app.json b/public/locales/fa/app.json index 06b811d..4f7eef8 100644 --- a/public/locales/fa/app.json +++ b/public/locales/fa/app.json @@ -133,6 +133,40 @@ "city_name": "شهر", "role_name": "نقش", "updated_at": "آخرین بروزرسانی", - "create": "افزودن کارشناس" + "create": "افزودن کارشناس", + "personal_info": "مشخصات کارشناس", + "user_info": "اطلاعات کاربری", + "rest_info": "اطلاعات تکمیلی", + "text_field_name": "نام", + "text_field_username": "نام کاربری", + "text_field_email": "پست الکترونیک", + "text_field_phone_number": "شماره همراه", + "text_field_national_id": "کد ملی", + "text_field_password": "رمز عبور", + "text_field_position": "سمت", + "text_field_province_id": "استان", + "text_field_city_id": "شهر", + "text_field_role_id": "نقش", + "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_name": "نام خود را وارد کنید", + "error_message_username": "نام کاربری خود را وارد کنید", + "error_message_email": "پست الکترونیک خود را وارد کنید", + "error_message_phone_number": "شماره همراه خود را وارد کنید", + "error_message_national_id": "کد ملی خود را وارد کنید", + "error_message_password": "رمز عبور خود را وارد کنید", + "error_message_password_regex": "رمز عبور باید حداقل 8 کاراکتر و متشکل از عدد، حرف یا سمبل باشد", + "error_message_position": "سمت خود را وارد کنید", + "error_message_province_id": "استان خود را وارد کنید", + "error_message_city_id": "شهر خود را وارد کنید", + "error_message_role_id": "نقش خود را وارد کنید", + "error_message_new_password": "رمز عبور جدید را وارد کنید", + "error_message_new_password_regex": "رمز عبور باید حداقل 8 کاراکتر و متشکل از عدد، حرف یا سمبل باشد" } } 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 index e69de29..cf93005 100644 --- 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 @@ -0,0 +1,83 @@ +import {act, render, screen, waitFor} from "@testing-library/react"; +import React from 'react'; +import CreateContent from "../../CreateContent"; +import MockAppWithProviders from "../../../../../../../../mocks/AppWithProvider"; + +describe("CreateContent Component From Expert Management", () => { + describe("Rendering", () => { + it("Name TextField Rendered", async () => { + render(); + await waitFor(() => { + const nameTextField = screen.queryByLabelText('نام'); + expect(nameTextField).toBeInTheDocument(); + }); + }); + it("UserName TextField Rendered", async () => { + render(); + await waitFor(() => { + const nameTextField = screen.queryByLabelText('نام کاربری'); + expect(nameTextField).toBeInTheDocument(); + }); + }); + it("Email TextField Rendered", async () => { + render(); + await waitFor(() => { + const nameTextField = screen.queryByLabelText('پست الکترونیک'); + expect(nameTextField).toBeInTheDocument(); + }); + }); + it("Phone Number TextField Rendered", async () => { + render(); + await waitFor(() => { + const nameTextField = screen.queryByLabelText('شماره همراه'); + expect(nameTextField).toBeInTheDocument(); + }); + }); + it("National Id TextField Rendered", async () => { + render(); + await waitFor(() => { + const nameTextField = screen.queryByLabelText('کد ملی'); + expect(nameTextField).toBeInTheDocument(); + }); + }); + it("Password TextField Rendered", async () => { + render(); + await waitFor(() => { + const nameTextField = screen.queryByLabelText('رمز عبور'); + expect(nameTextField).toBeInTheDocument(); + }); + + }); + it("Position TextField Rendered", async () => { + render(); + await waitFor(() => { + const nameTextField = screen.queryByLabelText('سمت'); + expect(nameTextField).toBeInTheDocument(); + }); + }); + it("Province TextField Rendered", async () => { + render(); + await waitFor(() => { + const nameTextField = screen.getByTestId('province-select'); + expect(nameTextField).toBeInTheDocument(); + }); + }); + it("City TextField Rendered", async () => { + render(); + await waitFor(() => { + const nameTextField = screen.getByTestId('city-select'); + expect(nameTextField).toBeInTheDocument(); + }); + }); + it("Role TextField Rendered", async () => { + render(); + await waitFor(() => { + const nameTextField = screen.getByTestId('role-select'); + expect(nameTextField).toBeInTheDocument(); + }); + }); + }); + describe("Behavioral", () => { + + }); +}); \ 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 index 7ce4fcf..8f24ef4 100644 --- a/src/components/dashboard/expert-management/Form/CreateForm/CreateContent/index.jsx +++ b/src/components/dashboard/expert-management/Form/CreateForm/CreateContent/index.jsx @@ -1,7 +1,377 @@ -const CreateContent = () => { +import { + Box, + Button, + Chip, + DialogActions, + DialogContent, + Divider, + FormControl, + FormHelperText, + Grid, + IconButton, + InputAdornment, + InputLabel, + MenuItem, + Select, + TextField, +} from "@mui/material"; +import * as Yup from "yup"; +import {useTranslations} from "next-intl"; +import {useFormik} from "formik"; +import {useState} from "react"; +import useRequest from "@/lib/app/hooks/useRequest"; +import {CREATE_EXPERT_MANAGEMENT, GET_CITY_LIST} from "@/core/data/apiRoutes"; +import useRole from "@/lib/app/hooks/useRole"; +import {Visibility, VisibilityOff} from "@mui/icons-material"; +import useProvince from "@/lib/app/hooks/useProvince"; + +const CreateContent = ({setOpenCreateDialog, mutate, fetchUrl}) => { + const t = useTranslations(); + const requestServer = useRequest() + const [cityList, setCityList] = useState([]); + const [cityTextField, setCityTextField] = useState(t("ExpertMangement.text_field_please_select_province")); + const {provinceList, isLoadingProvinceList, errorProvinceList} = useProvince(); + const {roleList, isLoadingRoleList, errorRoleList} = useRole(); + const [showPassword, setShowPassword] = useState(false); + + const handleClickShowPassword = () => { + setShowPassword(!showPassword); + }; + + function getCities(province_id) { + formik.setFieldTouched("city_id", false, false); + formik.setFieldValue("city_id", "") + setCityTextField(t("ExpertMangement.text_field_loading_cities_list")) + requestServer(`${GET_CITY_LIST}?province_id=${province_id}`, "get", {auth: true, notification: false}) + .then(({data}) => { + const result = data.data; + const formattedData = result.map((city, index) => ({ + id: index, + name: city.name, + value: city.id, + })); + setCityList(formattedData); + setCityTextField(t("ExpertMangement.text_field_city_id")) + }).catch(() => { + setCityTextField(t("ExpertMangement.text_field_error_fetching_cities")) + }); + } + + const validationSchema = Yup.object().shape({ + name: Yup.string().required(t("ExpertMangement.error_message_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")), + 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")), + city_id: Yup.string().required(t("ExpertMangement.error_message_city_id")), + role_id: Yup.string().required(t("ExpertMangement.error_message_role_id")) + }); + + const formik = useFormik({ + initialValues: { + name: "", + username: "", + email: "", + phone_number: "", + national_id: "", + password: "", + position: "", + province_id: "", + city_id: "", + role_id: "" + }, + validationSchema, + onSubmit: (values, {setSubmitting}) => { + const formData = new FormData(); + formData.append("name", values.name); + formData.append("national_id", values.national_id); + formData.append("phone_number", values.phone_number); + formData.append("email", values.email); + formData.append("username", values.username); + formData.append("password", values.password); + formData.append("province_id", values.province_id); + formData.append("city_id", values.city_id); + formData.append("position", values.position); + formData.append("role_id", values.role_id); + + requestServer(`${CREATE_EXPERT_MANAGEMENT}`, 'post', {auth: true, data: formData}) + .then((response) => { + mutate(fetchUrl) + setOpenCreateDialog(false) + }).catch(() => { + }).finally(() => { + setSubmitting(false); + }); + }, + }); + return ( <> - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {showPassword ? : } + + + ), + }} + sx={{width: "100%"}} + /> + + + + + + + + + + + {t("ExpertMangement.text_field_province_id")} + + + {formik.touched.province_id && formik.errors.province_id ? formik.errors.province_id : ""} + + + + + + {cityTextField} + + + {formik.touched.city_id && formik.errors.city_id ? formik.errors.city_id : ""} + + + + + + + + + + + {t("ExpertMangement.text_field_role_id")} + + + {formik.touched.role_id && formik.errors.role_id ? formik.errors.role_id : ""} + + + + + + + ) } diff --git a/src/core/data/apiRoutes.js b/src/core/data/apiRoutes.js index 543e9a4..6e604c1 100644 --- a/src/core/data/apiRoutes.js +++ b/src/core/data/apiRoutes.js @@ -1,5 +1,14 @@ const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL; +// role list +export const GET_ROLE_LIST = BASE_URL + "/dashboard/roles/list"; +// role list + +// province/city list +export const GET_PROVINCE_LIST = BASE_URL + "/api/provinces"; +export const GET_CITY_LIST = BASE_URL + "/api/cities"; +// province/city list + //login export const GET_USER_TOKEN = BASE_URL + "/api/auth/login"; //end login @@ -14,4 +23,5 @@ export const GET_USER_ROUTE = BASE_URL + "/api/profile/info"; //expert management export const GET_EXPERT_MANAGEMENT_LIST = BASE_URL + "/dashboard/experts/show"; +export const CREATE_EXPERT_MANAGEMENT = BASE_URL + "/dashboard/experts/store"; //expert management \ No newline at end of file diff --git a/src/lib/app/hooks/useProvince.jsx b/src/lib/app/hooks/useProvince.jsx new file mode 100644 index 0000000..359eff4 --- /dev/null +++ b/src/lib/app/hooks/useProvince.jsx @@ -0,0 +1,29 @@ +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 + }); + + 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..fc3dd18 --- /dev/null +++ b/src/lib/app/hooks/useRole.jsx @@ -0,0 +1,29 @@ +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 + }); + + return { + roleList: data, + isLoadingRoleList: isLoading, + errorRoleList: !data + } +}; + +export default useRole; \ No newline at end of file