diff --git a/.eslintrc.json b/.eslintrc.json
index c4f29da..cb6d4e4 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -1,7 +1,5 @@
{
"extends": [
- "next/core-web-vitals",
- "plugin:testing-library/react",
- "plugin:jest-dom/recommended"
+ "next/core-web-vitals"
]
}
diff --git a/mocks/handler.js b/mocks/handler.js
index da1bc4e..18458fb 100644
--- a/mocks/handler.js
+++ b/mocks/handler.js
@@ -1,9 +1,12 @@
import {
- CREATE_EXPERT_MANAGEMENT,
+ GET_PERMISSIONS_LIST,
+ GET_ROLE_MANAGEMENT,
+ GET_SIDEBAR_NOTIFICATION,
+ GET_USER_ROUTE,
+ SET_USER_PASSWORD,
EXPERT_MANAGEMENT,
GET_PROVINCE_LIST,
GET_ROLE_LIST,
- GET_USER_ROUTE
} from "@/core/data/apiRoutes";
import {rest} from "msw";
@@ -11,10 +14,90 @@ export const handler = [
rest.get(GET_USER_ROUTE, (req, res, ctx) => {
return res(ctx.json({
data: {
- id: 10
+ id: 10,
+ permissions: [
+ "manage_users"
+ ],
}
}))
}),
+ rest.get(GET_SIDEBAR_NOTIFICATION, (req, res, ctx) => {
+ return res(ctx.json({
+ data: []
+ }))
+ }),
+ rest.post(SET_USER_PASSWORD, (req, res, ctx) => {
+ return res(
+ ctx.status(200),
+ );
+ }),
+ rest.get(GET_PERMISSIONS_LIST, (req, res, ctx) => {
+ return res(ctx.json(
+ {
+ data: [
+ {
+ id: 1,
+ name: "manage_passenger_office_navgan",
+ name_fa: "مدیریت کارتابل رییس اداره مسافری استان"
+ },
+ {
+ id: 2,
+ name: "manage_province_working_group_navgan",
+ name_fa: "مدیریت کارتابل کارگروه استانی"
+ }
+ ]
+ }
+ ))
+ }),
+ rest.get(GET_ROLE_MANAGEMENT, (req, res, ctx) => {
+ return res(
+ ctx.json(
+ {
+ data: [
+ {
+ created_at: "2023-10-01T07:20:07.000000Z",
+ guard_name: "api",
+ id: 1,
+ name: "admin",
+ name_fa: "ادمین",
+ permissions: [
+ {
+ id: 1,
+ name: "manage_users",
+ },
+ {
+ id: 2,
+ name: "manage_roles",
+ },
+ ],
+ updated_at: "2023-10-10T07:38:12.000000Z"
+ },
+ {
+ created_at: "2023-10-01T07:20:07.000000Z",
+ guard_name: "api",
+ id: 2,
+ name: "manager",
+ name_fa: "مدیر",
+ permissions: [
+ {
+ id: 1,
+ name: "manage_users",
+ },
+ {
+ id: 2,
+ name: "manage_roles",
+ },
+ ],
+ updated_at: "2023-10-10T07:38:12.000000Z"
+ }
+ ],
+ meta: {
+ totalRowCount: 2
+ }
+ }
+ ),
+ );
+ }),
rest.get(GET_PROVINCE_LIST, (req, res, ctx) => {
return res(ctx.json({
data: [
diff --git a/package.json b/package.json
index 5d6417a..f82c9f2 100644
--- a/package.json
+++ b/package.json
@@ -64,7 +64,7 @@
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"msw": "^1.3.1",
- "next-router-mock": "^0.9.9",
+ "next-router-mock": "^0.9.10",
"run-script-os": "^1.1.6"
}
}
diff --git a/public/locales/fa/app.json b/public/locales/fa/app.json
index 3774d17..865f83d 100644
--- a/public/locales/fa/app.json
+++ b/public/locales/fa/app.json
@@ -15,6 +15,8 @@
"between": "میان",
"online_message": "شما به اینترنت وصل هستید",
"offline_message": "اتصال شما به اینترنت قطع شده است",
+ "socket_is_connect_message": "شما به سوکت وصل هستید",
+ "socket_is_not_connect_message": "اتصال شما به سوکت قطع شده است",
"header": {
"open_profile": "پروفایل",
"edit_profile": " پروفایل",
@@ -32,6 +34,7 @@
"dashboard": "داشبورد",
"change-password": "تغییر رمز عبور",
"edit-profile": "ویرایش پروفایل",
+ "role-management": "مدیریت نقش ها",
"admin": "مدیریت",
"expert-management": "مدیریت کارشناسان"
},
@@ -41,7 +44,7 @@
"typography_your_login_is_valid_and_you_do_not_need_to_login_again": "شما دسترسی لازم به این صفحه را دارید نیاز به ورود مجدد نیست.",
"typography_your_access_to_this_page_has_expired_Please_login_again": "دسترسی شما منقضی شده است لطفا دوباره ورود نمایید.",
"typography_redirect_to": "درحال رفتن به صفحه",
- "typography_routing_previuos_page": "صفحه قبل...",
+ "typography_routing_previuos_page": " قبل...",
"typography_routing_dashbaord_page": "داشبورد..."
},
"Permission": {
@@ -72,7 +75,8 @@
"dashboard_page": "داشبورد",
"change_password": "تغییر رمز عبور",
"edit_profile": "ویرایش پروفایل",
- "expert_management": "داشبورد"
+ "role_management_page": "مدیریت نقش ها",
+ "expert_management": "مدیریت کارشناسان"
},
"MuiDatePicker": {
"date_picker_birthday": "تاریخ"
@@ -82,7 +86,9 @@
"label_current_password": "رمز عبور فعلی",
"label_new_password": "رمز عبور جدید",
"label_confirm_password": "تکرار رمز عبور جدید",
- "error_message_required": "اجباری !",
+ "error_message_current_password_required": "وارد کردن رمز عبور فعلی الزامیست!",
+ "error_message_new_password_required": "وارد کردن رمز عبور جدید الزامیست!",
+ "error_message_confirm_password_required": "وارد کردن تکرار رمز عبور جدید الزامیست!",
"error_message_password_length": "رمز عبور باید حداقل 8 کاراکتر باشد.",
"error_message_password_match": "پسورد ها یکسان هستند",
"error_message_password_not_match": "پسورد و تکرار رمز عبور باید یکسان باشد"
@@ -121,6 +127,70 @@
"delete": "پاک کردن",
"uploadfile_error": "حجم فایل بیشتر از 2 مگابایت می باشد"
},
+ "RoleManagement": {
+ "id": "کد یکتا",
+ "name_fa": "نام فارسی ",
+ "name": "نام انگلیسی",
+ "created_at": "تاریخ درخواست",
+ "updated_at": "تاریخ بروزرسانی"
+ },
+ "AddDialog": {
+ "add": "افزودن",
+ "name_en": "نام انگلیسی",
+ "name_en_error": "وارد کردن نام انگلیسی الزامیست",
+ "name_fa": "نام فارسی",
+ "name_fa_error": "وارد کردن نام فارسی الزامیست",
+ "permission": "دسترسی",
+ "permission_min_error": "حداقل باید یک دسترسی انتخاب شود",
+ "phone_number": "شماره تلفن",
+ "phone_number_positive": "شماره تلفن باید مثبت باشد",
+ "phone_number_error": "وارد کردن شماره تلفن الزامیست",
+ "phone_number_number": "شماره تلفن باید شامل اعداد باشد",
+ "phone_number_max": "شماره تلفن باید شامل 11 رقم باشد",
+ "national_id_positive": "کد ملی باید مثبت باشد",
+ "national_id_number": "کد ملی باید شامل اعداد باشد",
+ "national_id_max": "کد ملی باید شامل 10 رقم باشد",
+ "national_id": "کد ملی",
+ "national_id_error": "وارد کردن کد ملی الزامیست",
+ "type_id_error": "وارد کردن نوع کاربر الزامیست",
+ "navgan_id_error": "وارد کردن کد ناوگان الزامیست",
+ "refahi": "رفاهی",
+ "navgan": "ناوگان",
+ "type_id": "نوع کاربر",
+ "navgan_id": "کد ناوگان",
+ "button-cancel": "انصراف",
+ "loading_permissions_list": "درحال دریافت لیست دسترسی ها",
+ "button-add": "ثبت"
+ },
+ "UpdateDialog": {
+ "update": "ویرایش",
+ "description": "توضیحات خود را وارد نمائید",
+ "description_error": "وارد کردن توضیحات الزامی است!",
+ "next-state-id": "وضعیت",
+ "next-state-id-error": "وارد کردن وضعیت الزامیست",
+ "update-tooltip": "ویرایش",
+ "button-cancel": "بستن",
+ "refahi": "رفاهی",
+ "navgan": "ناوگان",
+ "phone_number": "شماره تلفن",
+ "national_id": "کد ملی",
+ "name_en": "نام انگلیسی",
+ "name_en_error": "وارد کردن نام انگلیسی الزامیست",
+ "name_fa": "نام فارسی",
+ "name_fa_error": "وارد کردن نام فارسی الزامیست",
+ "permission": "دسترسی",
+ "permission_min_error": "حداقل باید یک دسترسی انتخاب شود",
+ "type_id": "نوع کاربر",
+ "navgan_id": "کد ناوگان",
+ "loading_permissions_list": "درحال دریافت لیست دسترسی ها",
+ "button-update": "ثبت"
+ },
+ "DeleteDialog": {
+ "delete": "حذف",
+ "button-cancel": "انصراف",
+ "typography": "آیا از حدف این مورد اطمینان دارید ؟",
+ "button-delete": "حذف کردن"
+ },
"ExpertMangement": {
"id": "کد یکتا",
"name": "نام کامل",
diff --git a/src/components/dashboard/change-password/change-password-form/__test__/index.test.js b/src/components/dashboard/change-password/change-password-form/__test__/index.test.js
new file mode 100644
index 0000000..503b571
--- /dev/null
+++ b/src/components/dashboard/change-password/change-password-form/__test__/index.test.js
@@ -0,0 +1,305 @@
+import { render, screen, waitFor, fireEvent , act } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import MockAppWithProviders from "../../../../../../mocks/AppWithProvider";
+import ChangePasswordForm from "@/components/dashboard/change-password/change-password-form";
+import {SET_USER_PASSWORD} from "@/core/data/apiRoutes";
+import {server} from "../../../../../../mocks/server";
+import {rest} from "msw";
+
+describe('Change Password Form Component From Change Password Page', () => {
+ describe('Rendering', () => {
+ it('Should see change password heading',() => {
+ render();
+ const changePasswordElement = screen.getByRole('heading', { level: 4 });
+ expect(changePasswordElement).toHaveTextContent('تغییر رمز عبور');
+ });
+ it('Should see the label for current password field', () => {
+ render();
+ const currentPasswordLabel = screen.queryByLabelText('رمز عبور فعلی');
+ expect(currentPasswordLabel).toBeInTheDocument();
+ });
+ it('Should see the label for new password field', () => {
+ render();
+ const newPasswordLabel = screen.queryByLabelText('رمز عبور جدید');
+ expect(newPasswordLabel).toBeInTheDocument();
+ });
+ it('Should see the label for confirm password field', () => {
+ render();
+ const confirmPasswordLabel = screen.queryByLabelText('تکرار رمز عبور جدید');
+ expect(confirmPasswordLabel).toBeInTheDocument();
+ });
+ it('Should see the submit button with the submit label and should be disable', () => {
+ render();
+ const submitButton = screen.getByRole('button', { name: 'ثبت' });
+ expect(submitButton).toBeInTheDocument();
+ expect(submitButton).toBeDisabled();
+ });
+ it('Should have empty input fields and not see any validation error for current password, new password, and confirm password', () => {
+ render();
+ const currentPasswordInput = screen.queryByLabelText('رمز عبور فعلی');
+ const newPasswordInput = screen.queryByLabelText('رمز عبور جدید');
+ const confirmPasswordInput = screen.queryByLabelText('تکرار رمز عبور جدید');
+
+ expect(currentPasswordInput).toHaveValue('');
+ expect(newPasswordInput).toHaveValue('');
+ expect(confirmPasswordInput).toHaveValue('');
+
+ expect(screen.queryByText("وارد کردن رمز عبور فعلی الزامیست!")).not.toBeInTheDocument();
+ expect(screen.queryByText("وارد کردن رمز عبور جدید الزامیست!")).not.toBeInTheDocument();
+ expect(screen.queryByText("وارد کردن تکرار رمز عبور جدید الزامیست!")).not.toBeInTheDocument();
+
+ });
+ });
+ describe("Behavior", ()=>{
+ it('Should fill the current password field and not see any error while its not empty', async () => {
+ render();
+
+ const currentPasswordInput = screen.queryByLabelText('رمز عبور فعلی');
+
+ // Simulate typing something in the current password input
+ fireEvent.change(currentPasswordInput, { target: { value: 'witel@fani0' } });
+
+ await waitFor(() => {
+ expect(screen.queryByText("وارد کردن رمز عبور فعلی الزامیست!")).not.toBeInTheDocument();
+ expect(currentPasswordInput).toHaveValue('witel@fani0');
+ });
+ });
+ it('Should fill the new password field and not see any error while its not empty', async () => {
+ render();
+
+ const newPasswordInput = screen.queryByLabelText('رمز عبور جدید');
+
+ // Simulate typing something in the new password input
+ fireEvent.change(newPasswordInput, { target: { value: 'witel@fani1' } });
+
+ await waitFor(() => {
+ expect(screen.queryByText("وارد کردن رمز عبور جدید الزامیست!")).not.toBeInTheDocument();
+ expect(newPasswordInput).toHaveValue('witel@fani1');
+ });
+ });
+ it('Should fill the confirm new password field and not see any error while its not empty', async () => {
+ render();
+
+ const confirmPasswordInput = screen.queryByLabelText('تکرار رمز عبور جدید');
+
+ // Simulate typing something in the confirmation new password input
+ fireEvent.change(confirmPasswordInput, { target: { value: 'witel@fani1' } });
+
+ await waitFor(() => {
+ expect(screen.queryByText("وارد کردن تکرار رمز عبور جدید الزامیست!")).not.toBeInTheDocument();
+ expect(confirmPasswordInput).toHaveValue('witel@fani1');
+ });
+ });
+ });
+ describe("Validation", ()=>{
+ it('Should see error while current password is empty on blur event', async () => {
+ render();
+
+ // Simulate blur event on the current password input
+ const currentPasswordInput = screen.queryByLabelText('رمز عبور فعلی');
+
+ // Clear the input field
+ fireEvent.change(currentPasswordInput, { target: { value: '' } });
+
+ fireEvent.blur(currentPasswordInput);
+
+ await waitFor(()=>{
+ expect(screen.queryByText("وارد کردن رمز عبور فعلی الزامیست!")).toBeInTheDocument()
+ });
+ });
+ it('Should see error while new password is empty on blur event', async () => {
+ render();
+
+ // Simulate blur event on the new password input
+ const newPasswordInput = screen.queryByLabelText('رمز عبور جدید');
+
+ // Clear the input field
+ fireEvent.change(newPasswordInput, { target: { value: '' } });
+
+ fireEvent.blur(newPasswordInput);
+
+ await waitFor(()=>{
+ expect(screen.queryByText("وارد کردن رمز عبور جدید الزامیست!")).toBeInTheDocument()
+ });
+ });
+ it('Should see error while confirm new password is empty on blur event', async () => {
+ render();
+
+ // Simulate blur event on the new password input
+ const confirmPasswordInput = screen.queryByLabelText('تکرار رمز عبور جدید');
+
+ // Clear the input field
+ fireEvent.change(confirmPasswordInput, { target: { value: '' } });
+
+ fireEvent.blur(confirmPasswordInput);
+
+ await waitFor(()=>{
+ expect(screen.queryByText("وارد کردن تکرار رمز عبور جدید الزامیست!")).toBeInTheDocument()
+ });
+ });
+ it('Should see error when new password is less than 8 characters', async () => {
+ render();
+
+ const newPasswordInput = screen.queryByLabelText('رمز عبور جدید');
+ const submitButton = screen.getByRole('button', { name: 'ثبت' });
+
+ // Simulate entering a short new password
+ fireEvent.change(newPasswordInput, { target: { value: 'test' } });
+ fireEvent.blur(newPasswordInput);
+
+ await waitFor(() => {
+ expect(screen.queryByText("رمز عبور باید حداقل 8 کاراکتر باشد.")).toBeInTheDocument();
+ });
+
+ // Simulate clicking the submit button
+ fireEvent.click(submitButton);
+
+ await waitFor(() => {
+ expect(screen.queryByText("رمز عبور باید حداقل 8 کاراکتر باشد.")).toBeInTheDocument();
+ });
+ });
+ it('Should not see error when new password are 8 or more characters', async () => {
+ render();
+
+ const newPasswordInput = screen.queryByLabelText('رمز عبور جدید');
+ const submitButton = screen.getByRole('button', { name: 'ثبت' });
+
+ // Simulate entering a valid new password
+ fireEvent.change(newPasswordInput, { target: { value: 'Witel@fani1' } });
+ fireEvent.blur(newPasswordInput);
+
+ await waitFor(() => {
+ expect(screen.queryByText("رمز عبور باید حداقل 8 کاراکتر باشد.")).not.toBeInTheDocument();
+ });
+
+ // Simulate clicking the submit button
+ fireEvent.click(submitButton);
+
+ await waitFor(() => {
+ expect(screen.queryByText("رمز عبور باید حداقل 8 کاراکتر باشد.")).not.toBeInTheDocument();
+ });
+ });
+ it('Should see error when new password and confirm password do not match on blur event and submit click', async () => {
+ render();
+
+ const newPasswordInput = screen.queryByLabelText('رمز عبور جدید');
+ const confirmPasswordInput = screen.queryByLabelText('تکرار رمز عبور جدید');
+ const submitButton = screen.getByRole('button', { name: 'ثبت' });
+
+ // Simulate entering a valid new password
+ fireEvent.change(newPasswordInput, { target: { value: 'Witel@fani1' } });
+ fireEvent.blur(newPasswordInput);
+
+ // Simulate entering a different value in the confirm password field
+ fireEvent.change(confirmPasswordInput, { target: { value: 'MismatchedPassword' } });
+ fireEvent.blur(confirmPasswordInput);
+
+ await waitFor(() => {
+ expect(screen.queryByText("پسورد و تکرار رمز عبور باید یکسان باشد")).toBeInTheDocument();
+ });
+
+ // Simulate clicking the submit button
+ fireEvent.click(submitButton);
+
+ await waitFor(() => {
+ expect(screen.queryByText("پسورد و تکرار رمز عبور باید یکسان باشد")).toBeInTheDocument();
+ });
+ });
+ it('Should not see error when new password and confirm password match on blur event and submit click', async () => {
+ render();
+
+ const newPasswordInput = screen.queryByLabelText('رمز عبور جدید');
+ const confirmPasswordInput = screen.queryByLabelText('تکرار رمز عبور جدید');
+ const submitButton = screen.getByRole('button', { name: 'ثبت' });
+
+ // Simulate entering a valid new password
+ fireEvent.change(newPasswordInput, { target: { value: 'Witel@fani1' } });
+ fireEvent.blur(newPasswordInput);
+
+ // Simulate entering the same value in the confirmation password field
+ fireEvent.change(confirmPasswordInput, { target: { value: 'Witel@fani1' } });
+ fireEvent.blur(confirmPasswordInput);
+
+ await waitFor(() => {
+ expect(screen.queryByText("پسورد و تکرار رمز عبور باید یکسان باشد")).not.toBeInTheDocument();
+ });
+
+ // Simulate clicking the submit button
+ fireEvent.click(submitButton);
+
+ await waitFor(() => {
+ expect(screen.queryByText("پسورد و تکرار رمز عبور باید یکسان باشد")).not.toBeInTheDocument();
+ });
+ });
+ });
+ describe("Form Submission", () => {
+ it('Should enable the submit button when the inputs are valid', async () => {
+ render();
+
+ const submitButton = screen.getByRole('button', { name: 'ثبت' });
+
+ // Simulate entering valid values in the form fields
+ const currentPasswordInput = screen.queryByLabelText('رمز عبور فعلی');
+ const newPasswordInput = screen.queryByLabelText('رمز عبور جدید');
+ const confirmPasswordInput = screen.queryByLabelText('تکرار رمز عبور جدید');
+
+ fireEvent.change(currentPasswordInput, { target: { value: 'Witel@fani0' } });
+ fireEvent.change(newPasswordInput, { target: { value: 'Witel@fani1' } });
+ fireEvent.change(confirmPasswordInput, { target: { value: 'Witel@fani1' } });
+
+ // Check if the submit button is enabled
+ await waitFor(() => {
+ expect(submitButton).not.toBeDisabled();
+ });
+ });
+ it('Should request to api and resetform in success response', async () => {
+ // Render the component
+ render();
+
+ // Fill in the form fields
+ const currentPasswordInput = screen.queryByLabelText('رمز عبور فعلی');
+ const newPasswordInput = screen.queryByLabelText('رمز عبور جدید');
+ const confirmPasswordInput = screen.queryByLabelText('تکرار رمز عبور جدید');
+
+ fireEvent.change(currentPasswordInput, { target: { value: 'Witel@fani0' } });
+ fireEvent.change(newPasswordInput, { target: { value: 'Witel@fani1' } });
+ fireEvent.change(confirmPasswordInput, { target: { value: 'Witel@fani1' } });
+
+ // Submit the form
+ const submitButton = screen.getByRole('button', { name: 'ثبت' });
+ await userEvent.click(submitButton);
+
+ await waitFor(() => {
+ expect(currentPasswordInput).toHaveValue('');
+ expect(newPasswordInput).toHaveValue('');
+ expect(confirmPasswordInput).toHaveValue('');
+ });
+ });
+ it('Should request to api and not resetform in error response', async () => {
+ // Render the component
+ render();
+ server.use([rest.get(SET_USER_PASSWORD, (req, res, ctx) => {
+ return res(ctx.status(422))
+ })])
+
+ // Fill in the form fields wrong
+ const currentPasswordInput = screen.queryByLabelText('رمز عبور فعلی');
+ const newPasswordInput = screen.queryByLabelText('رمز عبور جدید');
+ const confirmPasswordInput = screen.queryByLabelText('تکرار رمز عبور جدید');
+
+ fireEvent.change(currentPasswordInput, { target: { value: 'incorrect' } });
+ fireEvent.change(newPasswordInput, { target: { value: 'Witel@fani1' } });
+ fireEvent.change(confirmPasswordInput, { target: { value: 'Witel@fani1' } });
+
+ // Submit the form
+ const submitButton = screen.getByRole('button', { name: 'ثبت' });
+ await userEvent.click(submitButton);
+
+ await waitFor(() => {
+ expect(currentPasswordInput.value).toBe('incorrect');
+ expect(newPasswordInput.value).toBe('Witel@fani1');
+ expect(confirmPasswordInput.value).toBe('Witel@fani1');
+ });
+ });
+ });
+})
\ No newline at end of file
diff --git a/src/components/dashboard/change-password/change-password-form/index.js b/src/components/dashboard/change-password/change-password-form/index.js
new file mode 100644
index 0000000..9b7704a
--- /dev/null
+++ b/src/components/dashboard/change-password/change-password-form/index.js
@@ -0,0 +1,137 @@
+import {Formik, Form} from "formik";
+import * as Yup from "yup";
+import {useTranslations} from "next-intl";
+import {Button, Paper, Stack, Typography} from "@mui/material";
+import PasswordField from "@/core/components/PasswordField";
+import StyledForm from "@/core/components/StyledForm";
+import useRequest from "@/lib/app/hooks/useRequest";
+import {SET_USER_PASSWORD} from "@/core/data/apiRoutes";
+
+const ChangePasswordForm = ({onSubmit}) => {
+ const t = useTranslations();
+ const requestServer = useRequest({auth: true})
+
+ const handleSubmit = (values, {setSubmitting, resetForm}) => {
+ requestServer(SET_USER_PASSWORD, 'post', {
+ data: {
+ current_password: values.current_password,
+ new_password: values.new_password,
+ new_password_confirmation: values.new_password_confirmation,
+ },
+ }).then((response) => {
+ resetForm();
+ }).catch(() => {
+ }).finally(() => {
+ setSubmitting(false);
+ });
+ };
+ const initialValues = {
+ current_password: "",
+ new_password: "",
+ new_password_confirmation: "",
+ };
+ const validationSchema = Yup.object().shape({
+ current_password: Yup.string().required(
+ t("ChangePassword.error_message_current_password_required")
+ ),
+ new_password: Yup.string()
+ .min(8, t("ChangePassword.error_message_password_length"))
+ .required(t("ChangePassword.error_message_new_password_required")),
+ new_password_confirmation: Yup.string()
+ .required(t("ChangePassword.error_message_confirm_password_required"))
+ .test(
+ t("ChangePassword.error_message_password_match"),
+ t("ChangePassword.error_message_password_not_match"), // Use the correct error message here
+ function (value) {
+ return this.parent.new_password === value;
+ }
+ ),
+ });
+
+ return (
+
+ {(props) => (
+ {
+ e.preventDefault();
+ props.handleSubmit();
+ }}
+ >
+
+
+
+ {t("ChangePassword.typography_change_password")}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )}
+
+ );
+};
+
+export default ChangePasswordForm;
diff --git a/src/components/dashboard/change-password/index.jsx b/src/components/dashboard/change-password/index.jsx
index a69a969..74896a6 100644
--- a/src/components/dashboard/change-password/index.jsx
+++ b/src/components/dashboard/change-password/index.jsx
@@ -1,149 +1,18 @@
import CenterLayout from "@/layouts/CenterLayout";
-import DashboardLayouts from "@/layouts/DashboardLayout";
-import {Button, Container, Paper, Stack, Typography,} from "@mui/material";
-import {Formik} from "formik";
-import * as Yup from "yup";
-import {useTranslations} from "next-intl";
-import PasswordField from "@/core/components/PasswordField";
-import StyledForm from "@/core/components/StyledForm";
-import {SET_USER_PASSWORD} from "@/core/data/apiRoutes";
-import SvgChangePassword from "@/core/components/svgs/SvgChangePassword";
-import useRequest from "@/lib/app/hooks/useRequest";
+import DashboardLayout from "@/layouts/DashboardLayout";
+import {Container} from "@mui/material";
+import ChangePasswordForm from "@/components/dashboard/change-password/change-password-form";
const DashboardChangePasswordComponent = () => {
- const t = useTranslations();
- const requestServer = useRequest({auth: true})
-
- const handleSubmit = (values, {setSubmitting, resetForm}) => {
- requestServer(SET_USER_PASSWORD, 'post', {
- data: {
- current_password: values.current_password,
- new_password: values.new_password,
- new_password_confirmation: values.new_password_confirmation,
- },
- }).then((response) => {
- resetForm();
- }).catch(() => {
- }).finally(() => {
- setSubmitting(false);
- });
- };
- const initialValues = {
- current_password: "",
- new_password: "",
- new_password_confirmation: "",
- };
- const validationSchema = Yup.object().shape({
- current_password: Yup.string().required(
- t("ChangePassword.error_message_required")
- ),
- new_password: Yup.string()
- .min(8, t("ChangePassword.error_message_password_length"))
- .required(t("ChangePassword.error_message_required")),
- new_password_confirmation: Yup.string()
- .required(t("ChangePassword.error_message_required"))
- .test(
- t("ChangePassword.error_message_password_match"),
- t("ChangePassword.error_message_password_not_match"),
- function (value) {
- return this.parent.new_password === value;
- }
- ),
- });
return (
-
+
-
- {(props) => (
- {
- e.preventDefault();
- props.handleSubmit();
- }}
- >
-
-
-
-
- {t("ChangePassword.typography_change_password")}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- )}
-
+
-
+
);
};
export default DashboardChangePasswordComponent;
diff --git a/src/components/dashboard/edit-profile/index.jsx b/src/components/dashboard/edit-profile/index.jsx
index 394a7b0..e35e3bf 100644
--- a/src/components/dashboard/edit-profile/index.jsx
+++ b/src/components/dashboard/edit-profile/index.jsx
@@ -1,6 +1,6 @@
import StyledForm from "@/core/components/StyledForm";
import CenterLayout from "@/layouts/CenterLayout";
-import DashboardLayouts from "@/layouts/DashboardLayout";
+import DashboardLayout from "@/layouts/DashboardLayout";
import useUser from "@/lib/app/hooks/useUser";
import {Box, Container, Grid, Paper, Stack, TextField, Typography,} from "@mui/material";
import * as Yup from "yup";
@@ -54,7 +54,7 @@ const DashboardEditProfile = () => {
const validationSchema = Yup.object().shape({});
return (
-
+
{
-
+
);
};
diff --git a/src/components/dashboard/first/index.jsx b/src/components/dashboard/first/index.jsx
index 1785a67..841d677 100644
--- a/src/components/dashboard/first/index.jsx
+++ b/src/components/dashboard/first/index.jsx
@@ -1,7 +1,7 @@
-import DashboardLayouts from "@/layouts/DashboardLayout";
+import DashboardLayout from "@/layouts/DashboardLayout";
const DashboardFirstComponent = () => {
- return ;
+ return ;
};
export default DashboardFirstComponent;
diff --git a/src/components/dashboard/role-management/Form/CreateForm/CreateContent.jsx b/src/components/dashboard/role-management/Form/CreateForm/CreateContent.jsx
new file mode 100644
index 0000000..db28f49
--- /dev/null
+++ b/src/components/dashboard/role-management/Form/CreateForm/CreateContent.jsx
@@ -0,0 +1,148 @@
+import {
+ Button,
+ Checkbox, CircularProgress,
+ DialogActions,
+ DialogContent, DialogTitle,
+ FormControl,
+ FormControlLabel,
+ FormHelperText,
+ FormLabel,
+ Grid,
+ Stack,
+ TextField, Typography
+} from "@mui/material";
+import {useTranslations} from "next-intl";
+import useRequest from "@/lib/app/hooks/useRequest";
+import * as Yup from "yup";
+import {useFormik} from "formik";
+import {ADD_ROLE_MANAGEMENT} from "@/core/data/apiRoutes";
+import usePermissions from "@/lib/app/hooks/usePermissions";
+
+const CreateContent = ({mutate, fetchUrl, setOpenConfirmDialog}) => {
+ const t = useTranslations();
+ const requestServer = useRequest({auth: true})
+ const {permissions_list, isLoading} = usePermissions()
+ const validationSchema = Yup.object().shape({
+ name_en: Yup.string().required(t("AddDialog.name_en_error")),
+ name_fa: Yup.string().required(t("AddDialog.name_fa_error")),
+ permissions: Yup.array().min(1, t("AddDialog.permission_min_error")).required(t("AddDialog.permission")),
+ });
+ const formik = useFormik({
+ initialValues: {
+ name_en: "",
+ name_fa: "",
+ permissions: [],
+ }, validationSchema, onSubmit: (values, {setSubmitting}) => {
+ const formData = new FormData();
+ formData.append("name", values.name_en);
+ formData.append("name_fa", values.name_fa);
+ for (let i = 0; i < values.permissions.length; i++) {
+ formData.append(`permissions[${i}]`, values.permissions[i]);
+ }
+
+ requestServer(ADD_ROLE_MANAGEMENT, 'post', {
+ data: formData,
+ }).then(() => {
+ setOpenConfirmDialog(false)
+ mutate(fetchUrl)
+ update_notification()
+ }).catch(() => {
+ }).finally(() => {
+ setSubmitting(false);
+ });
+ },
+ });
+
+ return (
+ <>
+ {t("AddDialog.add")}
+
+
+
+
+
+
+
+ {t("AddDialog.permission")}
+
+ {formik.touched.permissions && formik.errors.permissions}
+ {isLoading ?
+
+
+ {t("AddDialog.loading_permissions_list")}
+
+ :(
+
+ <>
+ {permissions_list.map((permission) => (
+
+ {
+ if (e.target.checked) {
+ formik.setFieldValue("permissions", [...formik.values.permissions, permission.id])
+ } else {
+ formik.setFieldValue("permissions", formik.values.permissions.filter((id) => id !== permission.id))
+ }
+ }}
+ />
+ }
+ label={permission.name_fa}
+ />
+
+ ))}
+ >
+
+ )
+ }
+
+
+
+
+
+
+
+
+
+ >
+ )
+}
+export default CreateContent
\ No newline at end of file
diff --git a/src/components/dashboard/role-management/Form/CreateForm/__test__/CreateContent.test.js b/src/components/dashboard/role-management/Form/CreateForm/__test__/CreateContent.test.js
new file mode 100644
index 0000000..610e224
--- /dev/null
+++ b/src/components/dashboard/role-management/Form/CreateForm/__test__/CreateContent.test.js
@@ -0,0 +1,167 @@
+import {act, fireEvent, render, screen, waitFor} from "@testing-library/react";
+import MockAppWithProviders from "../../../../../../../mocks/AppWithProvider";
+import CreateContent from "@/components/dashboard/role-management/Form/CreateForm/CreateContent";
+
+describe("Create Content component from Create Form Component in Role Management Component",()=>{
+ describe("Rendering", ()=>{
+ it('should see AddDialog text in the top ', async () => {
+ render(
+
+
+
+ )
+ const textElement = screen.queryByText("افزودن")
+ await waitFor(()=>{
+ expect(textElement).toBeInTheDocument()
+ })
+ });
+ it('should see name_en text', async () => {
+ render(
+
+
+
+ )
+ const textElement = screen.queryByLabelText("نام انگلیسی")
+ await waitFor(()=>{
+ expect(textElement).toBeInTheDocument()
+ })
+ });
+ it('should see name_fa text', async () => {
+ render(
+
+
+
+ )
+ const textElement = screen.queryByLabelText("نام فارسی")
+ await waitFor(()=>{
+ expect(textElement).toBeInTheDocument()
+ })
+ });
+ it('should see name_fa text', async () => {
+ render(
+
+
+
+ )
+ const textElement = screen.queryByLabelText("نام فارسی")
+ await waitFor(()=>{
+ expect(textElement).toBeInTheDocument()
+ })
+ });
+ })
+ describe("Behavior",()=>{
+ it('should see what fill in the name_en input', async () => {
+ render(
+
+
+
+ )
+ const nameEnglishInput = screen.getByLabelText('نام انگلیسی');
+
+ fireEvent.change(nameEnglishInput, {target: {value: 'testuser'}});
+ await act(()=>{
+ // Simulate user input
+ expect(nameEnglishInput).toHaveValue('testuser');
+ })
+ });
+ it('should see what fill in the name_fa input', async () => {
+ render(
+
+
+
+ )
+ const nameFarsiInput = screen.getByLabelText('نام فارسی');
+
+ fireEvent.change(nameFarsiInput, {target: {value: 'testuser'}});
+ await act(()=>{
+ // Simulate user input
+ expect(nameFarsiInput).toHaveValue('testuser');
+ })
+ });
+ it('should return permissions_list', async () => {
+ render(
+
+
+
+ )
+ const checkboxElement = await screen.findAllByTestId("PermissionList-checkbox")
+ expect(checkboxElement).toHaveLength(2)
+ });
+ it('should get checked if permission list item get clicked', async () => {
+ render(
+
+
+
+ )
+ const checkboxElement = screen.getByLabelText("مدیریت کارتابل رییس اداره مسافری استان")
+
+ await waitFor(()=>{
+ expect(checkboxElement).not.toBeChecked();
+ })
+
+ fireEvent.click(checkboxElement);
+
+ await waitFor(()=>{
+ expect(checkboxElement).toBeChecked();
+ })
+ });
+ })
+ describe("Validation",()=>{
+ it('should see error text when name_fa input is empty', async () => {
+ render(
+
+
+
+ )
+ const name_faInput = screen.getByLabelText("نام فارسی")
+ expect(screen.queryByText("وارد کردن نام فارسی الزامیست")).not.toBeInTheDocument()
+ fireEvent.blur(name_faInput);
+
+ await waitFor(()=>{
+ expect(screen.queryByText("وارد کردن نام فارسی الزامیست")).toBeInTheDocument()
+ })
+ fireEvent.change(name_faInput, {target : {value : "نام فارسی"}})
+
+ await waitFor(()=>{
+ expect(screen.queryByText("وارد کردن نام فارسی الزامیست")).not.toBeInTheDocument()
+ })
+ })
+ it('should see error text when name_en input is empty', async () => {
+ render(
+
+
+
+ )
+ const name_enInput = screen.getByLabelText("نام انگلیسی")
+ expect(screen.queryByText("وارد کردن نام انگلیسی الزامیست")).not.toBeInTheDocument()
+ fireEvent.blur(name_enInput);
+
+ await waitFor(()=>{
+ expect(screen.queryByText("وارد کردن نام انگلیسی الزامیست")).toBeInTheDocument()
+ })
+ fireEvent.change(name_enInput, {target : {value : "english name"}})
+
+ await waitFor(()=>{
+ expect(screen.queryByText("وارد کردن نام انگلیسی الزامیست")).not.toBeInTheDocument()
+ })
+ })
+ it('should see errors when click on submit button and field not completed', async () => {
+ render(
+
+
+
+ )
+ const submitButtonElement = screen.queryByText("ثبت")
+ fireEvent.click(submitButtonElement)
+ await waitFor(()=>{
+ expect(screen.queryByText("وارد کردن نام انگلیسی الزامیست")).toBeInTheDocument()
+ })
+ await waitFor(()=>{
+ expect(screen.queryByText("وارد کردن نام فارسی الزامیست")).toBeInTheDocument()
+ })
+ await waitFor(()=>{
+ expect(screen.queryByText("حداقل باید یک دسترسی انتخاب شود")).toBeInTheDocument()
+ })
+ });
+ })
+})
\ No newline at end of file
diff --git a/src/components/dashboard/role-management/Form/CreateForm/__test__/index.test.js b/src/components/dashboard/role-management/Form/CreateForm/__test__/index.test.js
new file mode 100644
index 0000000..7bc9db2
--- /dev/null
+++ b/src/components/dashboard/role-management/Form/CreateForm/__test__/index.test.js
@@ -0,0 +1,34 @@
+import {fireEvent, queryByText, render, screen, waitFor} from "@testing-library/react";
+import CreateForm from "@/components/dashboard/role-management/Form/CreateForm";
+import MockAppWithProviders from "../../../../../../../mocks/AppWithProvider";
+
+describe("Create Form Component from Role Management Component",()=>{
+ describe("Rendering",()=>{
+
+ it('should see Dialog text', async () => {
+ render(
+
+
+
+ )
+ const textElement = screen.queryByText("افزودن")
+ await waitFor(()=>{
+ expect(textElement).toBeInTheDocument()
+ })
+ });
+
+ it('should see Tooltip text when mouse over', async () => {
+ render(
+
+
+
+ )
+ const buttonElement = screen.getByText("افزودن");
+ fireEvent.mouseOver(buttonElement);
+ const tooltipTextElement = screen.getByText("افزودن");
+ await waitFor(()=>{
+ expect(tooltipTextElement).toBeInTheDocument()
+ })
+ });
+ })
+})
\ No newline at end of file
diff --git a/src/components/dashboard/role-management/Form/CreateForm/index.jsx b/src/components/dashboard/role-management/Form/CreateForm/index.jsx
new file mode 100644
index 0000000..2265035
--- /dev/null
+++ b/src/components/dashboard/role-management/Form/CreateForm/index.jsx
@@ -0,0 +1,34 @@
+import {Button, Dialog, DialogTitle, Stack, Tooltip} from "@mui/material";
+import DataSaverOnIcon from "@mui/icons-material/DataSaverOn";
+import {useTranslations} from "next-intl";
+import {useState} from "react";
+import CreateContent from "./CreateContent";
+
+const CreateForm = ({mutate, fetchUrl}) => {
+ const t = useTranslations();
+ const [openConfirmDialog, setOpenConfirmDialog] = useState(false);
+
+ return (
+
+
+
+
+
+
+ )
+}
+export default CreateForm
\ No newline at end of file
diff --git a/src/components/dashboard/role-management/Form/DeleteForm/DeleteContent.jsx b/src/components/dashboard/role-management/Form/DeleteForm/DeleteContent.jsx
new file mode 100644
index 0000000..52674a5
--- /dev/null
+++ b/src/components/dashboard/role-management/Form/DeleteForm/DeleteContent.jsx
@@ -0,0 +1,42 @@
+import {Button, DialogActions, DialogContent, DialogTitle, Typography} from "@mui/material";
+import {DELETE_ROLE_MANAGEMENT} from "@/core/data/apiRoutes";
+import {useState} from "react";
+import useRequest from "@/lib/app/hooks/useRequest";
+import useNotification from "@/lib/app/hooks/useNotification";
+import {useTranslations} from "next-intl";
+
+const DeleteContent = ({rowId, fetchUrl, mutate, setOpenConfirmDialog}) => {
+ const t = useTranslations();
+ const [isSubmitting, setIsSubmitting] = useState(false)
+ const requestServer = useRequest({auth: true})
+ const {update_notification} = useNotification()
+ const handleSubmit = () => {
+ setIsSubmitting(true)
+ requestServer(`${DELETE_ROLE_MANAGEMENT}/${rowId}`, 'delete').then((response) => {
+ mutate(fetchUrl)
+ update_notification()
+ }).catch(() => {
+ }).finally(() => {
+ setIsSubmitting(false)
+ });
+ }
+ return(
+ <>
+ {t("DeleteDialog.delete")}
+
+ {t("DeleteDialog.typography")}
+
+
+
+
+
+ >
+ )
+}
+export default DeleteContent
\ No newline at end of file
diff --git a/src/components/dashboard/role-management/Form/DeleteForm/__test__/DeleteContent.test.js b/src/components/dashboard/role-management/Form/DeleteForm/__test__/DeleteContent.test.js
new file mode 100644
index 0000000..580dcb9
--- /dev/null
+++ b/src/components/dashboard/role-management/Form/DeleteForm/__test__/DeleteContent.test.js
@@ -0,0 +1,53 @@
+import {act, render, screen} from "@testing-library/react";
+import MockAppWithProviders from "../../../../../../../mocks/AppWithProvider";
+import DeleteContent from "@/components/dashboard/role-management/Form/DeleteForm/DeleteContent";
+import UpdateContent from "@/components/dashboard/role-management/Form/UpdateForm/UpdateContent";
+
+describe("Create Content component from Create Form Component in Role Management Component",()=> {
+ describe("Rendering", () => {
+ it('should see DeleteDialog text in the top ', async () => {
+ render(
+
+
+
+ )
+ const textElement = screen.queryByText("حذف")
+ act(()=>{
+ expect(textElement).toBeInTheDocument()
+ })
+ })
+ it('should see DeleteDialog text content ', async () => {
+ render(
+
+
+
+ )
+ const textElement = screen.queryByText("آیا از حدف این مورد اطمینان دارید ؟")
+ act(()=>{
+ expect(textElement).toBeInTheDocument()
+ })
+ })
+ it('should see delete text in the delete button ', () => {
+ render(
+
+
+
+ )
+ const buttonElement = screen.queryByText("حذف کردن")
+ act(()=>{
+ expect(buttonElement).toBeInTheDocument()
+ })
+ });
+ it('should see cancel text in the cancel button ', () => {
+ render(
+
+
+
+ )
+ const buttonElement = screen.queryByText("انصراف")
+ act(()=>{
+ expect(buttonElement).toBeInTheDocument()
+ })
+ });
+ })
+})
\ No newline at end of file
diff --git a/src/components/dashboard/role-management/Form/DeleteForm/__test__/index.test.js b/src/components/dashboard/role-management/Form/DeleteForm/__test__/index.test.js
new file mode 100644
index 0000000..d8eaaa2
--- /dev/null
+++ b/src/components/dashboard/role-management/Form/DeleteForm/__test__/index.test.js
@@ -0,0 +1,21 @@
+import {fireEvent, render, screen, waitFor} from "@testing-library/react";
+import MockAppWithProviders from "../../../../../../../mocks/AppWithProvider";
+import DeleteForm from "@/components/dashboard/role-management/Form/DeleteForm";
+import UpdateForm from "@/components/dashboard/role-management/Form/UpdateForm";
+
+describe("Create Form Component from Role Management Component",()=> {
+ describe("Rendering", () => {
+ it('should see Dialog text when mouse is over', async () => {
+ render(
+
+
+
+ )
+ const textElement = screen.getByTestId("dialog_tooltip")
+ fireEvent.mouseOver(textElement)
+ await waitFor(()=>{
+ expect(screen.getByText("حذف")).toBeInTheDocument();
+ })
+ });
+ })
+})
\ No newline at end of file
diff --git a/src/components/dashboard/role-management/Form/DeleteForm/index.jsx b/src/components/dashboard/role-management/Form/DeleteForm/index.jsx
new file mode 100644
index 0000000..b22a907
--- /dev/null
+++ b/src/components/dashboard/role-management/Form/DeleteForm/index.jsx
@@ -0,0 +1,35 @@
+import {useTranslations} from "next-intl";
+import {useState} from "react";
+import {
+ Dialog, IconButton,
+ Tooltip,
+} from "@mui/material";
+import DeleteIcon from '@mui/icons-material/Delete';
+import DeleteContent from "@/components/dashboard/role-management/Form/DeleteForm/DeleteContent";
+
+const DeleteForm = ({rowId, fetchUrl, mutate}) => {
+ const t = useTranslations();
+ const [openConfirmDialog, setOpenConfirmDialog] = useState(false);
+
+
+ return (
+ <>
+
+ {
+ setOpenConfirmDialog(true)
+ }}
+ >
+
+
+
+
+ >
+ );
+};
+export default DeleteForm;
\ No newline at end of file
diff --git a/src/components/dashboard/role-management/Form/UpdateForm/UpdateContent.jsx b/src/components/dashboard/role-management/Form/UpdateForm/UpdateContent.jsx
new file mode 100644
index 0000000..0815356
--- /dev/null
+++ b/src/components/dashboard/role-management/Form/UpdateForm/UpdateContent.jsx
@@ -0,0 +1,147 @@
+import {
+ Box,
+ Button,
+ Checkbox, CircularProgress, DialogActions,
+ DialogContent, DialogTitle,
+ FormControl,
+ FormControlLabel,
+ FormHelperText,
+ FormLabel,
+ Grid,
+ Stack,
+ TextField, Typography
+} from "@mui/material";
+import {useFormik} from "formik";
+import {UPDATE_ROLE_MANAGEMENT} from "@/core/data/apiRoutes";
+import * as Yup from "yup";
+import usePermissions from "@/lib/app/hooks/usePermissions";
+import useNotification from "@/lib/app/hooks/useNotification";
+import useRequest from "@/lib/app/hooks/useRequest";
+import {useTranslations} from "next-intl";
+
+const UpdateContent = ({mutate, row, fetchUrl, setOpenConfirmDialog}) => {
+ const t = useTranslations();
+ const requestServer = useRequest({auth: true})
+ const {update_notification} = useNotification()
+ const {permissions_list, isLoading} = usePermissions()
+
+ const validationSchema = Yup.object().shape({
+ name_en: Yup.string().required(t("UpdateDialog.name_en_error")),
+ name_fa: Yup.string().required(t("UpdateDialog.name_fa_error")),
+ permissions: Yup.array().min(1, t("UpdateDialog.permission_min_error")).required(t("UpdateDialog.permission")),
+ });
+
+ const formik = useFormik({
+ initialValues: {
+ name_en: row.getValue("name"),
+ name_fa: row.getValue("name_fa"),
+ permissions: row.original.permissions.map((obj) => obj.id),
+ }, validationSchema, onSubmit: (values, {setSubmitting}) => {
+ const formData = new FormData();
+ formData.append("name", values.name_en);
+ formData.append("name_fa", values.name_fa);
+ for (let i = 0; i < values.permissions.length; i++) {
+ formData.append(`permissions[${i}]`, values.permissions[i]);
+ }
+
+ requestServer(`${UPDATE_ROLE_MANAGEMENT}/${row.getValue("id")}`, 'post', {
+ data: formData,
+ }).then((response) => {
+ mutate(fetchUrl)
+ update_notification()
+ }).catch(() => {
+ }).finally(() => {
+ setSubmitting(false);
+ });
+ },
+ });
+
+ return(
+ <>
+ {t("UpdateDialog.update")}
+
+
+
+
+
+
+
+ {t("UpdateDialog.permission")}
+
+ {formik.touched.permissions && formik.errors.permissions}
+ {isLoading ?
+
+
+ {t("UpdateDialog.loading_permissions_list")}
+
+ :(
+
+ <>
+ {permissions_list.map((permission) => (
+
+ {
+ if (e.target.checked) {
+ formik.setFieldValue("permissions", [...formik.values.permissions, permission.id])
+ } else {
+ formik.setFieldValue("permissions", formik.values.permissions.filter((id) => id !== permission.id))
+ }
+ }}
+ />
+ }
+ label={permission.name_fa}
+ />
+
+ ))}
+ >
+
+ )}
+
+
+
+
+
+
+
+
+
+ >
+ )
+}
+export default UpdateContent
\ No newline at end of file
diff --git a/src/components/dashboard/role-management/Form/UpdateForm/__test__/UpdateContent.test.js b/src/components/dashboard/role-management/Form/UpdateForm/__test__/UpdateContent.test.js
new file mode 100644
index 0000000..caaa470
--- /dev/null
+++ b/src/components/dashboard/role-management/Form/UpdateForm/__test__/UpdateContent.test.js
@@ -0,0 +1,209 @@
+import {act, fireEvent, render, screen, waitFor} from "@testing-library/react";
+import MockAppWithProviders from "../../../../../../../mocks/AppWithProvider";
+import UpdateContent from "@/components/dashboard/role-management/Form/UpdateForm/UpdateContent";
+const row = {
+ id : 0,
+ getValue : name => {
+ if (name === "name") {
+ return "manage_passenger_office_navgan";
+ }
+ else if (name === "name_fa") {
+ return "مدیریت کارتابل رییس اداره مسافری استان";
+ }
+ },
+ original : {
+ permissions : [
+ {
+ id: 1,
+ name: "manage_passenger_office_navgan",
+ name_fa: "مدیریت کارتابل رییس اداره مسافری استان"
+ },
+ {
+ id: 2,
+ name: "manage_passenger_office_navgan",
+ name_fa: "مدیریت کارتابل رییس اداره مسافری استان"
+ }
+ ]
+ }
+}
+
+describe("Create Content component from Create Form Component in Role Management Component",()=>{
+ describe("Rendering", ()=>{
+ it('should see AddDialog text in the top ', async () => {
+ render(
+
+
+
+ )
+ const textElement = screen.queryByText("ویرایش")
+ await waitFor(()=>{
+ expect(textElement).toBeInTheDocument()
+ })
+ });
+ it('should see name_en text', async () => {
+ render(
+
+
+
+ )
+ const textElement = screen.queryByLabelText("نام انگلیسی")
+ await waitFor(()=>{
+ expect(textElement).toBeInTheDocument()
+ })
+ });
+ it('should see name_fa text', async () => {
+ render(
+
+
+
+ )
+ const textElement = screen.queryByLabelText("نام فارسی")
+ await waitFor(()=>{
+ expect(textElement).toBeInTheDocument()
+ })
+ });
+ it('should see update text in the submit button ', async () => {
+ render(
+
+
+
+ )
+ const buttonElement = screen.queryByText("ثبت")
+ await waitFor(()=>{
+ expect(buttonElement).toBeInTheDocument()
+ })
+ });
+ it('should see cancel text in the cancel button ', async () => {
+ render(
+
+
+
+ )
+ const buttonElement = screen.queryByText("بستن")
+ await waitFor(()=>{
+ expect(buttonElement).toBeInTheDocument()
+ })
+ });
+ })
+
+ describe("Behavior", ()=>{
+ it('should see what fill in the name_en input', async () => {
+ render(
+
+
+
+ )
+ const nameEnglishInput = screen.getByLabelText('نام انگلیسی');
+
+ fireEvent.change(nameEnglishInput, {target: {value: 'testuser'}});
+ await waitFor(()=>{
+ // Simulate user input
+ expect(nameEnglishInput).toHaveValue('testuser');
+ })
+ });
+ it('should see what fill in the name_fa input', async () => {
+ render(
+
+
+
+ )
+ const nameFarsiInput = screen.getByLabelText('نام فارسی');
+
+ fireEvent.change(nameFarsiInput, {target: {value: 'testuser'}});
+ await waitFor(()=>{
+ // Simulate user input
+ expect(nameFarsiInput).toHaveValue('testuser');
+ })
+ });
+ it('should return permissions_list', async () => {
+ render(
+
+
+
+ )
+ const checkboxElement = await screen.findAllByTestId("PermissionList-checkbox")
+ await waitFor(()=>{
+ expect(checkboxElement).toHaveLength(2)
+ })
+ });
+ it('should see the value of the name_en that pass to input value', async () => {
+ render(
+
+
+
+ )
+ const name_enElement = screen.getByLabelText("نام انگلیسی")
+ await waitFor(()=>{
+ expect(name_enElement).toHaveValue("manage_passenger_office_navgan")
+ })
+ });
+ it('should see the value of the name_fa that pass to input value', async () => {
+ render(
+
+
+
+ )
+ const name_enElement = screen.getByLabelText("نام فارسی")
+ await waitFor(()=>{
+ expect(name_enElement).toHaveValue("مدیریت کارتابل رییس اداره مسافری استان")
+ })
+ });
+ it('should get checked if the id exists in permission lists', async () => {
+ render(
+
+
+
+ )
+ const checkboxElement = screen.getByLabelText("مدیریت کارتابل رییس اداره مسافری استان")
+ await waitFor(()=>{
+ expect(checkboxElement).toBeChecked();
+ })
+
+ });
+ })
+ describe("Validation", ()=>{
+ it('should see error text when name_fa input is empty', async () => {
+ render(
+
+
+
+ )
+ const name_faInput = screen.getByLabelText("نام فارسی")
+ expect(screen.queryByText("وارد کردن نام فارسی الزامیست")).not.toBeInTheDocument()
+ fireEvent.change(name_faInput, {target : {value : ''}});
+
+ fireEvent.blur(name_faInput)
+
+ await waitFor(()=>{
+ expect(screen.queryByText("وارد کردن نام فارسی الزامیست")).toBeInTheDocument()
+ })
+ fireEvent.change(name_faInput, {target : {value : "نام فارسی"}})
+
+ await waitFor(()=>{
+ expect(screen.queryByText("وارد کردن نام فارسی الزامیست")).not.toBeInTheDocument()
+ })
+ })
+ it('should see error text when name_en input is empty', async () => {
+ render(
+
+
+
+ )
+ const name_enInput = screen.getByLabelText("نام انگلیسی")
+ expect(screen.queryByText("وارد کردن نام انگلیسی الزامیست")).not.toBeInTheDocument()
+
+ fireEvent.change(name_enInput, {target : {value : null}});
+ fireEvent.blur(name_enInput)
+
+ await waitFor(()=>{
+
+ expect(screen.queryByText("وارد کردن نام انگلیسی الزامیست")).toBeInTheDocument()
+ })
+ fireEvent.change(name_enInput, {target : {value : "english name"}})
+
+ await waitFor(()=>{
+ expect(screen.queryByText("وارد کردن نام انگلیسی الزامیست")).not.toBeInTheDocument()
+ })
+ })
+ })
+})
\ No newline at end of file
diff --git a/src/components/dashboard/role-management/Form/UpdateForm/__test__/index.test.js b/src/components/dashboard/role-management/Form/UpdateForm/__test__/index.test.js
new file mode 100644
index 0000000..2a0ee82
--- /dev/null
+++ b/src/components/dashboard/role-management/Form/UpdateForm/__test__/index.test.js
@@ -0,0 +1,22 @@
+import {fireEvent, render, screen, waitFor} from "@testing-library/react";
+import MockAppWithProviders from "../../../../../../../mocks/AppWithProvider";
+import UpdateContent from "@/components/dashboard/role-management/Form/UpdateForm/UpdateContent";
+import UpdateForm from "@/components/dashboard/role-management/Form/UpdateForm";
+import userEvent from "@testing-library/user-event";
+
+describe("Create Form Component from Role Management Component",()=>{
+ describe("Rendering",()=>{
+ it('should see Tooltip text when mouse over', async () => {
+ render(
+
+
+
+ )
+ const buttonElement = screen.getByTestId("dialog_tooltip");
+ fireEvent.mouseOver(buttonElement);
+ await waitFor(()=>{
+ expect(screen.queryByText("ویرایش")).toBeVisible();
+ })
+ });
+ })
+})
\ No newline at end of file
diff --git a/src/components/dashboard/role-management/Form/UpdateForm/index.jsx b/src/components/dashboard/role-management/Form/UpdateForm/index.jsx
new file mode 100644
index 0000000..018fe32
--- /dev/null
+++ b/src/components/dashboard/role-management/Form/UpdateForm/index.jsx
@@ -0,0 +1,31 @@
+import {Dialog, IconButton, Stack, Tooltip} from "@mui/material";
+import {useTranslations} from "next-intl";
+import {useState} from "react";
+import UpdateContent from "@/components/dashboard/role-management/Form/UpdateForm/UpdateContent";
+import EditIcon from "@mui/icons-material/Edit";
+
+const UpdateForm = ({mutate, fetchUrl, row}) => {
+ const t = useTranslations();
+ const [openConfirmDialog, setOpenConfirmDialog] = useState(false);
+
+ return (
+
+
+ {
+ setOpenConfirmDialog(true);
+ }}
+ >
+
+
+
+
+
+ )
+}
+export default UpdateForm
\ No newline at end of file
diff --git a/src/components/dashboard/role-management/RoleManagementComponent.jsx b/src/components/dashboard/role-management/RoleManagementComponent.jsx
new file mode 100644
index 0000000..0121c00
--- /dev/null
+++ b/src/components/dashboard/role-management/RoleManagementComponent.jsx
@@ -0,0 +1,94 @@
+import DataTable from "@/core/components/DataTable";
+import {GET_ROLE_MANAGEMENT} from "@/core/data/apiRoutes";
+import TableRowActions from "@/components/dashboard/role-management/TableRowActions";
+import {Box, Typography} from "@mui/material";
+import {useTranslations} from "next-intl";
+import {useMemo} from "react";
+import moment from "jalali-moment";
+import MuiDatePicker from "@/core/components/MuiDatePicker";
+import TableToolbar from "@/components/dashboard/role-management/TableToolbar";
+
+const RoleManagementComponent = () => {
+ const t = useTranslations();
+
+ const columns = useMemo(() => [{
+ accessorFn: (row) => row.id,
+ id: "id",
+ sortDescFirst: true,
+ header: t("RoleManagement.id"),
+ enableColumnFilter: true,
+ datatype: "numeric",
+ filterFn: "equals",
+ columnFilterModeOptions: ["equals", "notEquals", "contains", "lessThan", "greaterThan", "between",],
+ Cell: ({renderedCellValue}) => ({renderedCellValue}),
+ },
+ {
+ accessorFn: (row) => row.name_fa,
+ id: "name_fa",
+ header: t("RoleManagement.name_fa"),
+ enableColumnFilter: true,
+ datatype: "text",
+ filterFn: "contains",
+ columnFilterModeOptions: ["contains", "equals", "notEquals"],
+ Cell: ({renderedCellValue}) => ({renderedCellValue}),
+ },
+ {
+ accessorFn: (row) => row.name,
+ id: "name",
+ header: t("RoleManagement.name"),
+ enableColumnFilter: true,
+ datatype: "text",
+ filterFn: "contains",
+ columnFilterModeOptions: ["contains"],
+ Cell: ({renderedCellValue}) => ({renderedCellValue}),
+ },
+ {
+ accessorFn: (row) => moment(row.created_at).locale("fa").format("HH:mm | yyyy/MM/DD"),
+ id: "created_at",
+ header: t("RoleManagement.created_at"),
+ enableColumnFilter: true,
+ datatype: "date",
+ filterFn: "lessThan",
+ columnFilterModeOptions: ["lessThan", "greaterThan"],
+ Cell: ({renderedCellValue}) => {
+ return {renderedCellValue};
+ },
+ Header: ({column}) => <>{column.columnDef.header}>,
+ Filter: ({column}) => {
+ return ();
+ },
+ }, {
+ accessorFn: (row) => moment(row.updated_at).locale("fa").format("HH:mm | yyyy/MM/DD"),
+ id: "updated_at",
+ header: t("RoleManagement.updated_at"),
+ enableColumnFilter: false,
+ datatype: "numeric",
+ Cell: ({renderedCellValue}) => ({renderedCellValue}),
+ }], []);
+ return(
+
+
+
+ )
+}
+export default RoleManagementComponent
\ No newline at end of file
diff --git a/src/components/dashboard/role-management/TableRowActions.jsx b/src/components/dashboard/role-management/TableRowActions.jsx
new file mode 100644
index 0000000..500aeca
--- /dev/null
+++ b/src/components/dashboard/role-management/TableRowActions.jsx
@@ -0,0 +1,24 @@
+import {Box} from "@mui/material";
+import UpdateForm from "@/components/dashboard/role-management/Form/UpdateForm";
+import DeleteForm from "@/components/dashboard/role-management/Form/DeleteForm";
+
+
+const TableRowActions = ({row, mutate, fetchUrl}) => {
+
+ return (
+
+
+
+
+ );
+};
+
+export default TableRowActions;
diff --git a/src/components/dashboard/role-management/TableToolbar.jsx b/src/components/dashboard/role-management/TableToolbar.jsx
new file mode 100644
index 0000000..14dfef1
--- /dev/null
+++ b/src/components/dashboard/role-management/TableToolbar.jsx
@@ -0,0 +1,11 @@
+import {useTranslations} from "next-intl";
+import CreateForm from "@/components/dashboard/role-management/Form/CreateForm";
+
+function TableToolbar({mutate, fetchUrl}) {
+ const t = useTranslations();
+
+ return
+
+}
+
+export default TableToolbar;
diff --git a/src/components/dashboard/role-management/__test__/RoleManagementComponent.test.js b/src/components/dashboard/role-management/__test__/RoleManagementComponent.test.js
new file mode 100644
index 0000000..a60dc01
--- /dev/null
+++ b/src/components/dashboard/role-management/__test__/RoleManagementComponent.test.js
@@ -0,0 +1,63 @@
+import {render, screen, waitFor} from "@testing-library/react";
+import MockAppWithProviders from "../../../../../mocks/AppWithProvider";
+import RoleManagementComponent from "@/components/dashboard/role-management/RoleManagementComponent";
+
+describe("Role Management", ()=>{
+ describe("Rendering", ()=>{
+ it('should see id title of data table', async () => {
+ render(
+
+
+
+ )
+ const idEelement = screen.queryByText("کد یکتا")
+ await waitFor(()=>{
+ expect(idEelement).toBeInTheDocument()
+ })
+ });
+ it('should see name title of data table', async () => {
+ render(
+
+
+
+ )
+ const nameEelement = screen.queryByText("نام انگلیسی")
+ await waitFor(()=>{
+ expect(nameEelement).toBeInTheDocument()
+ })
+ });
+ it('should see name_fa title of data table', async () => {
+ render(
+
+
+
+ )
+ const name_faEelement = screen.queryByText("نام فارسی")
+ await waitFor(()=>{
+ expect(name_faEelement).toBeInTheDocument()
+ })
+ });
+ it('should see created at title of data table', async () => {
+ render(
+
+
+
+ )
+ const createdEelement = screen.queryByText("تاریخ درخواست")
+ await waitFor(()=>{
+ expect(createdEelement).toBeInTheDocument()
+ })
+ });
+ it('should see updated at title of data table', async () => {
+ render(
+
+
+
+ )
+ const updateEelement = screen.queryByText("تاریخ بروزرسانی")
+ await waitFor(()=>{
+ expect(updateEelement).toBeInTheDocument()
+ })
+ });
+ })
+})
\ No newline at end of file
diff --git a/src/components/dashboard/role-management/index.jsx b/src/components/dashboard/role-management/index.jsx
new file mode 100644
index 0000000..5251c8b
--- /dev/null
+++ b/src/components/dashboard/role-management/index.jsx
@@ -0,0 +1,13 @@
+import RoleManagementComponent from "@/components/dashboard/role-management/RoleManagementComponent";
+import DashboardLayout from "@/layouts/DashboardLayout";
+
+function DashboardRoleManagementComponent() {
+
+ return (
+
+
+
+ );
+}
+
+export default DashboardRoleManagementComponent;
diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetButton.jsx b/src/components/layouts/Dashboard/CallWidget/CallWidgetButton.jsx
deleted file mode 100644
index 416e5c8..0000000
--- a/src/components/layouts/Dashboard/CallWidget/CallWidgetButton.jsx
+++ /dev/null
@@ -1,13 +0,0 @@
-import PermPhoneMsgIcon from '@mui/icons-material/PermPhoneMsg';
-import StyledFab from "@/core/components/StyledFab";
-
-const CallWidgetButton = ({setOpen}) => {
- return (
- setOpen(true)}>
-
-
- )
-}
-
-export default CallWidgetButton
\ No newline at end of file
diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallActions/index.jsx b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallActions/index.jsx
new file mode 100644
index 0000000..d1a68f7
--- /dev/null
+++ b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallActions/index.jsx
@@ -0,0 +1,7 @@
+const CallActions = ({tab}) => {
+ return (
+ <>CallActions - {tab.id}>
+ )
+}
+
+export default CallActions
diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallHistory/index.jsx b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallHistory/index.jsx
new file mode 100644
index 0000000..048c091
--- /dev/null
+++ b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallHistory/index.jsx
@@ -0,0 +1,7 @@
+const CallHistory = ({tab}) => {
+ return (
+ <>CallHistory - {tab.id}>
+ )
+}
+
+export default CallHistory
diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/index.jsx b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/index.jsx
new file mode 100644
index 0000000..79dc736
--- /dev/null
+++ b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/index.jsx
@@ -0,0 +1,19 @@
+import {Grid} from "@mui/material";
+import CallActions from "@/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallActions";
+import CallHistory from "@/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallHistory";
+
+const CallTabPanel = ({tab}) => {
+ return (
+
+
+
+
+
+
+
+
+ )
+}
+
+export default CallTabPanel
\ No newline at end of file
diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabsList/CallTabLabel.jsx b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabsList/CallTabLabel.jsx
new file mode 100644
index 0000000..0137265
--- /dev/null
+++ b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabsList/CallTabLabel.jsx
@@ -0,0 +1,23 @@
+import {IconButton, Stack, Typography, Zoom} from "@mui/material";
+import CloseIcon from '@mui/icons-material/Close';
+import useAnswers from "@/lib/callWidget/hooks/useAnswers";
+
+const CallTabLabel = ({tab}) => {
+ const {closeAnswerTab} = useAnswers()
+ const handleCloseClick = (id) => {
+ closeAnswerTab(id)
+ };
+
+ return (
+
+ {tab.phone_number} - {tab.id}
+
+ handleCloseClick(tab.id)}>
+
+
+
+
+ )
+}
+
+export default CallTabLabel
diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabsList/index.jsx b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabsList/index.jsx
new file mode 100644
index 0000000..249d005
--- /dev/null
+++ b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabsList/index.jsx
@@ -0,0 +1,27 @@
+import {Box, Tab, Tabs} from "@mui/material";
+import CallTabLabel
+ from "@/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabsList/CallTabLabel";
+import useAnswers from "@/lib/callWidget/hooks/useAnswers";
+
+const CallTabsList = () => {
+ const {answersList, changeActiveTabAnswers, activeTab} = useAnswers()
+ const handleChange = (event, newValue) => {
+ changeActiveTabAnswers(newValue)
+ };
+
+ return (
+
+
+ {answersList.map((answer) => (
+ } key={answer.id}/>
+ ))}
+
+
+ )
+}
+
+export default CallTabsList
diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/index.jsx b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/index.jsx
new file mode 100644
index 0000000..8a96506
--- /dev/null
+++ b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/index.jsx
@@ -0,0 +1,17 @@
+import CallTabPanel from "@/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel";
+import useAnswers from "@/lib/callWidget/hooks/useAnswers";
+import CallTabsList from "src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabsList";
+
+const CallTabs = () => {
+ const {answersList} = useAnswers()
+
+ return (
+ <>
+
+ {answersList.map((answer) => (
+
+ ))}
+ >
+ )
+}
+export default CallTabs
\ No newline at end of file
diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/index.jsx b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/index.jsx
index 77ed57d..728f68d 100644
--- a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/index.jsx
+++ b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/index.jsx
@@ -1,23 +1,48 @@
import {Dialog} from "@mui/material";
import {DialogTransition} from "@/core/components/DialogTransition";
+import {useEffect} from "react";
+import useCallDialog from "@/lib/callWidget/hooks/useCallDialog";
+import useAnswers from "@/lib/callWidget/hooks/useAnswers";
+import useSocket from "@/lib/app/hooks/useSocket";
+import CallTabs from "@/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs";
-const CallWidgetDialog = ({open, setOpen}) => {
+const CallWidgetDialog = () => {
+ const {openCallDialog, setOpenCallDialog} = useCallDialog()
+ const {answersList, newAnswerTab} = useAnswers()
+ const socket = useSocket()
- const handleClose = () => {
- setOpen(false);
- };
+ useEffect(() => {
+ const answerCall = (_data, act) => {
+ const data = JSON.parse(_data)
+ newAnswerTab({
+ id: data.call_id,
+ phone_number: data.phone_number
+ })
+ act()
+ }
+ socket.on('answer', answerCall)
+
+ return () => {
+ socket.off('answer', answerCall)
+ }
+ });
+
+ useEffect(() => {
+ if (answersList.length === 0) {
+ setOpenCallDialog(false)
+ return
+ }
+ setOpenCallDialog(true)
+ }, [answersList.length]);
return (
- <>
-
- >
+
)
}
diff --git a/src/components/layouts/Dashboard/CallWidget/index.jsx b/src/components/layouts/Dashboard/CallWidget/index.jsx
index 4d871b2..1480498 100644
--- a/src/components/layouts/Dashboard/CallWidget/index.jsx
+++ b/src/components/layouts/Dashboard/CallWidget/index.jsx
@@ -1,14 +1,25 @@
-import CallWidgetButton from "@/components/layouts/Dashboard/CallWidget/CallWidgetButton";
import CallWidgetDialog from "@/components/layouts/Dashboard/CallWidget/CallWidgetDialog";
-import {useState} from "react";
+import {AnswersProvider} from "@/lib/callWidget/contexts/answers";
+import useSocket from "@/lib/app/hooks/useSocket";
+import {useEffect} from "react";
const CallWidget = () => {
- const [open, setOpen] = useState(false)
+ const socket = useSocket()
+
+ useEffect(() => {
+ if (socket.connected) return
+
+ socket.connect()
+
+ return () => {
+ socket.disconnect()
+ }
+ }, []);
+
return (
- <>
-
-
- >
+
+
+
)
}
diff --git a/src/components/layouts/Dashboard/Header/index.jsx b/src/components/layouts/Dashboard/Header/index.jsx
index 8760960..e8af065 100644
--- a/src/components/layouts/Dashboard/Header/index.jsx
+++ b/src/components/layouts/Dashboard/Header/index.jsx
@@ -1,6 +1,8 @@
import MenuIcon from "@mui/icons-material/Menu";
-import {AppBar, Box, Container, CssBaseline, IconButton, Stack, Toolbar, useTheme} from "@mui/material";
import ProfileMenu from "./ProfileMenu";
+import {useTheme} from "@mui/material/styles";
+import CssBaseline from "@mui/material/CssBaseline";
+import {AppBar, Box, Container, IconButton, Stack, Toolbar} from "@mui/material";
function Header({drawerWidth, handleDrawerToggle}) {
const theme = useTheme();
diff --git a/src/components/login-expert/__test__/LoginExpertComponent.test.js b/src/components/login-expert/__test__/LoginExpertComponent.test.js
index d76e49d..695dbcb 100644
--- a/src/components/login-expert/__test__/LoginExpertComponent.test.js
+++ b/src/components/login-expert/__test__/LoginExpertComponent.test.js
@@ -124,6 +124,7 @@ describe('Login expert component from login page', () => {
})
describe("validation", ()=>{
it("Disabled submit button until fields completed", async ()=> {
+
render(
diff --git a/src/core/components/DataTable.jsx b/src/core/components/DataTable.jsx
index 10fdffa..adedf3d 100644
--- a/src/core/components/DataTable.jsx
+++ b/src/core/components/DataTable.jsx
@@ -9,54 +9,45 @@ import useRequest from "@/lib/app/hooks/useRequest";
function DataTable(props) {
const requestServer = useRequest({auth: true})
- const fetcher = (...args) => {
- return requestServer(args, 'get', {
- pending: false,
- success: {notification: {show: false}}
- }).then((response) => {
- setRowCount(response.data.meta.totalRowCount);
- return response.data.data;
- }).catch(() => {
- })
- };
const t = useTranslations();
const {languageApp, languageList} = useLanguage();
const [columnFilters, setColumnFilters] = useState([]);
const [sorting, setSorting] = useState([]);
const [pagination, setPagination] = useState({pageIndex: 0, pageSize: 10});
- const [rowCount, setRowCount] = useState(0);
const [columnFilterFns, setColumnFilterFns] = useState(() => {
let output = {};
const list = props.columns.map((item) =>
item.enableColumnFilter ? {[item.id]: item.filterFn} : {[item.id]: ""}
);
- for (var key in list) {
- var nestedObj = list[key];
- for (var nestedKey in nestedObj) {
+ for (const key in list) {
+ const nestedObj = list[key];
+ for (const nestedKey in nestedObj) {
output[nestedKey] = nestedObj[nestedKey];
}
}
return output;
});
- const [updateTime, setupdateTime] = useState(
+ const [updateTime, setUpdateTime] = useState(
moment().format("HH:mm | jYYYY/jM/jD")
);
+
const tableLocalization = useMemo(
() =>
- languageList.find((item) => item.key == languageApp).tableLocalization,
+ languageList.find((item) => item.key === languageApp).tableLocalization,
[languageApp, languageList]
);
+
const fetchUrl = useMemo(() => {
- const url = new URL(props.tableUrl);
- url.searchParams.set(
+ const params = new URLSearchParams();
+ params.set(
"start",
`${pagination.pageIndex * pagination.pageSize}`
);
const filters = columnFilters.map((filter) => {
let datatype;
for (const i in props.columns) {
- if (props.columns[i].id == filter.id) {
+ if (props.columns[i].id === filter.id) {
datatype = props.columns[i].datatype;
}
}
@@ -66,32 +57,40 @@ function DataTable(props) {
datatype: datatype,
};
});
- url.searchParams.set("size", pagination.pageSize);
- url.searchParams.set("filters", JSON.stringify(filters ?? []));
- url.searchParams.set("sorting", JSON.stringify(sorting ?? []));
- return url;
+ params.set("size", pagination.pageSize);
+ params.set("filters", JSON.stringify(filters ?? []));
+ params.set("sorting", JSON.stringify(sorting ?? []));
+ return `${props.tableUrl}?${params}`;
}, [
+ props.tableUrl,
columnFilters,
columnFilterFns,
pagination,
sorting,
+ props.columns,
]);
- const {data, isValidating, mutate} = useSWR(fetchUrl, fetcher, {
- revalidateIfStale: false,
- revalidateOnFocus: false,
- revalidateOnReconnect: false
- });
+ const {data, isValidating, mutate} = useSWR(fetchUrl, (...args) =>
+ requestServer(args, 'get', {
+ pending: false,
+ success: {notification: {show: false}}
+ }).then((response) => response.data).catch(() => {
+ })
+ , {
+ revalidateIfStale: false,
+ revalidateOnFocus: false,
+ revalidateOnReconnect: true,
+ keepPreviousData: true
+ });
useEffect(() => {
- setupdateTime(moment().format("HH:mm | jYYYY/jM/jD"));
+ setUpdateTime(moment().format("HH:mm | jYYYY/jM/jD"));
}, [isValidating, languageApp]);
-
return (
(
<>
{props.enableCustomToolbar /* send condition */
- ? () /* send component */
- : }
+ ? /* send component */
+ : ""}
>
)}
renderBottomToolbarCustomActions={({table}) => (
@@ -156,6 +155,7 @@ function DataTable(props) {
>
)}
state={{
+ showProgressBars: isValidating,
columnFilters,
columnFilterFns,
pagination,
diff --git a/src/core/components/LoadingHardPage.jsx b/src/core/components/LoadingHardPage.jsx
index c3b24d6..5d00cd0 100644
--- a/src/core/components/LoadingHardPage.jsx
+++ b/src/core/components/LoadingHardPage.jsx
@@ -1,5 +1,6 @@
-import {Backdrop, Box, styled} from "@mui/material";
import SvgLoading from "@/core/components/svgs/SvgLoading";
+import {Backdrop, Box} from "@mui/material";
+import {styled} from "@mui/material/styles";
const LoadingImage = styled(Box)({
"@keyframes load": {
@@ -23,7 +24,7 @@ const LoadingHardPage = ({children, loading}) => {
return (
<>
theme.zIndex.drawer + 1}}
+ sx={{bgcolor: "#fff", zIndex: (theme) => theme.zIndex.modal + 1}}
open={loading}
>
{
+ const t = useTranslations();
+
+ return (
+
+ {t("Permission.typography_you_dont_have_access")}
+
+ }
+ actions={
+ <>
+
+ >
+ }
+ />
+ );
+};
+
+export default RolePermissionComponent;
diff --git a/src/core/components/Middleware/WithAuthComponent.jsx b/src/core/components/Middleware/WithAuthComponent.jsx
new file mode 100644
index 0000000..9a6ff5d
--- /dev/null
+++ b/src/core/components/Middleware/WithAuthComponent.jsx
@@ -0,0 +1,36 @@
+import { Button, Typography } from "@mui/material";
+import { useRouter } from "next/router";
+import Message from "@/core/components/Messages";
+import { NextLinkComposed } from "@/core/components/LinkRouting";
+import {useTranslations} from "next-intl";
+
+const WithAuthComponent = () => {
+ const router = useRouter();
+ const t = useTranslations();
+
+ return (
+
+ {t("Authorization.typography_your_access_to_this_page_has_expired_Please_login_again")}
+
+ }
+ actions={
+ <>
+
+ >
+ }
+ />
+ );
+};
+
+export default WithAuthComponent;
diff --git a/src/core/components/Middleware/WithoutAuthComponent.jsx b/src/core/components/Middleware/WithoutAuthComponent.jsx
new file mode 100644
index 0000000..7dbb38d
--- /dev/null
+++ b/src/core/components/Middleware/WithoutAuthComponent.jsx
@@ -0,0 +1,25 @@
+import {Button, Stack, Typography} from "@mui/material";
+import Message from "@/core/components/Messages";
+import {useTranslations} from "next-intl";
+const WithoutAuthComponent = ({ backUrlDecodedPath }) => {
+ const t = useTranslations();
+
+ return (
+
+
+ {t("Authorization.typography_your_login_is_valid_and_you_do_not_need_to_login_again")}
+
+
+ {t("Authorization.typography_redirect_to")}{" "}
+ {backUrlDecodedPath
+ ? t("Authorization.typography_routing_previuos_page")
+ : t("Authorization.typography_routing_dashbaord_page")}
+
+
+ }
+ />
+ );
+};
+export default WithoutAuthComponent;
diff --git a/src/core/components/Middleware/__test__/RolePermissionComponent.test.js b/src/core/components/Middleware/__test__/RolePermissionComponent.test.js
new file mode 100644
index 0000000..44a583d
--- /dev/null
+++ b/src/core/components/Middleware/__test__/RolePermissionComponent.test.js
@@ -0,0 +1,23 @@
+import { render, screen } from '@testing-library/react';
+import MockAppWithProviders from "../../../../../mocks/AppWithProvider";
+import RolePermissionComponent from "@/core/components/Middleware/RolePermissionComponent";
+
+describe('RolePermissionComponent From RolePermissionComponent page', () => {
+ describe('Rendering', () => {
+ it('should render the error message when user dont have permission', () => {
+ render();
+
+ expect(screen.queryByText('شما دسترسی لازم برای مشاهده این صفحه را ندارید!')).toBeInTheDocument();
+
+ });
+
+ it('should render the back dashboard button when user dont have permission', () => {
+ render();
+
+ const backButton = screen.queryByText('بازگشت به صفحه اصلی');
+
+ expect(backButton).toBeInTheDocument();
+
+ });
+ });
+});
diff --git a/src/core/components/Middleware/__test__/WithAuthComponent.test.js b/src/core/components/Middleware/__test__/WithAuthComponent.test.js
new file mode 100644
index 0000000..a43e20f
--- /dev/null
+++ b/src/core/components/Middleware/__test__/WithAuthComponent.test.js
@@ -0,0 +1,39 @@
+import { render, screen, fireEvent, waitFor } from '@testing-library/react';
+import MockAppWithProviders from "../../../../../mocks/AppWithProvider";
+import WithAuthComponent from "@/core/components/Middleware/WithAuthComponent";
+import mockRouter from 'next-router-mock';
+
+describe('WithAuthComponent From WithAuthMiddleware', () => {
+ describe('Rendering', () => {
+ it('should see expired text in render component', () => {
+ render();
+
+ const authText = screen.queryByText('دسترسی شما منقضی شده است لطفا دوباره ورود نمایید.');
+
+ expect(authText).toBeInTheDocument();
+ });
+
+ it('should see login button in render component', () => {
+ render();
+
+ const loginButton = screen.queryByText('ورود');
+
+ expect(loginButton).toBeInTheDocument();
+ });
+ });
+ describe('behavior', () => {
+ it('should see if router updated in login button', () => {
+
+ mockRouter.query.back_url = 'back_url'
+ render();
+
+ const loginButton = screen.queryByText('ورود');
+
+ fireEvent.click(loginButton);
+
+ expect(mockRouter).toMatchObject({
+ query: { back_url: "back_url" },
+ });
+ });
+ });
+});
diff --git a/src/core/components/Middleware/__test__/WithoutAuthComponent.test.js b/src/core/components/Middleware/__test__/WithoutAuthComponent.test.js
new file mode 100644
index 0000000..520d7bd
--- /dev/null
+++ b/src/core/components/Middleware/__test__/WithoutAuthComponent.test.js
@@ -0,0 +1,37 @@
+import {render, screen, waitFor} from '@testing-library/react';
+import WithoutAuthComponent from "@/core/components/Middleware/WithoutAuthComponent";
+import MockAppWithProviders from "../../../../../mocks/AppWithProvider";
+import WithoutAuthMiddleware from "@/middlewares/WithoutAuth";
+
+describe('WithoutAuthComponent From WithoutAuthMiddleware', () => {
+ describe('Rendering', () => {
+ it('should render the message with correct text when backUrlDecodedPath is not provided', async () => {
+
+ localStorage.setItem("_token", 'token');
+
+ render();
+
+ await waitFor(()=>{
+ expect(screen.queryByText('شما دسترسی لازم به این صفحه را دارید نیاز به ورود مجدد نیست.')).toBeInTheDocument();
+
+ expect(screen.queryByText('درحال رفتن به صفحه داشبورد...')).toBeInTheDocument();
+ })
+
+ });
+
+ it('should render the message with a different text when backUrlDecodedPath is provided', async() => {
+ render();
+
+ localStorage.setItem("_token", 'token');
+
+ render();
+
+ await waitFor(()=>{
+
+ expect(screen.queryByText('درحال رفتن به صفحه قبل...')).toBeInTheDocument();
+ })
+
+
+ });
+ });
+});
diff --git a/src/core/components/NetworkComponent.jsx b/src/core/components/NetworkComponent.jsx
index 8ebbb68..22d4620 100644
--- a/src/core/components/NetworkComponent.jsx
+++ b/src/core/components/NetworkComponent.jsx
@@ -6,13 +6,13 @@ import WifiOffIcon from '@mui/icons-material/WifiOff';
import {useTranslations} from "next-intl";
const NetworkComponent = () => {
- const toastId = useRef(null);
+ const networkToastId = useRef(null);
const network = useNetwork()
const t = useTranslations()
useEffect(() => {
if (network.online) {
- toast.update(toastId.current, {
+ toast.update(networkToastId.current, {
type: toast.TYPE.SUCCESS,
render: t('online_message'),
autoClose: 2000,
@@ -22,8 +22,7 @@ const NetworkComponent = () => {
});
return
}
- toast.dismiss()
- toastId.current = toast.warn(t('offline_message'), {
+ networkToastId.current = toast.warn(t('offline_message'), {
autoClose: false, closeButton: false, closeOnClick: false, icon:
})
}, [network.online]);
diff --git a/src/core/components/StyledFab.jsx b/src/core/components/StyledFab.jsx
index dd935cc..95c5ff7 100644
--- a/src/core/components/StyledFab.jsx
+++ b/src/core/components/StyledFab.jsx
@@ -5,7 +5,6 @@ const StyledFab = styled(Fab)`
bottom: 16px;
/* @noflip */
left: 16px;
- z-index: 1500;
`;
export default StyledFab;
diff --git a/src/core/components/StyledForm.jsx b/src/core/components/StyledForm.jsx
index 41eeabc..253fc12 100644
--- a/src/core/components/StyledForm.jsx
+++ b/src/core/components/StyledForm.jsx
@@ -1,5 +1,5 @@
-import {styled} from "@mui/material";
import {Form} from "formik";
+import {styled} from "@mui/material/styles";
const StyledForm = styled(Form)``;
diff --git a/src/core/components/svgs/Svg403.jsx b/src/core/components/svgs/Svg403.jsx
index 720828c..d706ed0 100644
--- a/src/core/components/svgs/Svg403.jsx
+++ b/src/core/components/svgs/Svg403.jsx
@@ -1,4 +1,4 @@
-import {useTheme} from "@mui/material";
+import {useTheme} from "@mui/material/styles";
const Svg403 = ({width, height}) => {
const theme = useTheme()
diff --git a/src/core/components/svgs/Svg404.jsx b/src/core/components/svgs/Svg404.jsx
index e46ab12..159d7c6 100644
--- a/src/core/components/svgs/Svg404.jsx
+++ b/src/core/components/svgs/Svg404.jsx
@@ -1,4 +1,4 @@
-import {useTheme} from "@mui/material";
+import {useTheme} from "@mui/material/styles";
const Svg404 = ({width, height}) => {
const theme = useTheme()
diff --git a/src/core/components/svgs/SvgChangePassword.jsx b/src/core/components/svgs/SvgChangePassword.jsx
index a7ee82f..1221d28 100644
--- a/src/core/components/svgs/SvgChangePassword.jsx
+++ b/src/core/components/svgs/SvgChangePassword.jsx
@@ -1,4 +1,4 @@
-import {useTheme} from "@mui/material";
+import {useTheme} from "@mui/material/styles";
const SvgChangePassword = ({width, height}) => {
const theme = useTheme()
diff --git a/src/core/components/svgs/SvgDashboard.jsx b/src/core/components/svgs/SvgDashboard.jsx
index a5b40e0..a636bf4 100644
--- a/src/core/components/svgs/SvgDashboard.jsx
+++ b/src/core/components/svgs/SvgDashboard.jsx
@@ -1,9 +1,9 @@
-import {useTheme} from "@mui/material";
+import {useTheme} from "@mui/material/styles";
const SvgDashboard = ({width, height}) => {
const theme = useTheme()
const fillColor = theme.palette.primary.main
-
+
return (