diff --git a/.gitignore b/.gitignore index 1e62a6d..2b2856c 100644 --- a/.gitignore +++ b/.gitignore @@ -31,4 +31,6 @@ cypress/screenshots .env .idea package-lock.json -ecosystem.config.js \ No newline at end of file +ecosystem.config.js + +public/config.json \ No newline at end of file diff --git a/example.env.local b/example.env.local index 06adaec..3c99d5d 100644 --- a/example.env.local +++ b/example.env.local @@ -1,5 +1,5 @@ NEXT_PUBLIC_API_NAME = "Loan Facilities Dashboard" -NEXT_PUBLIC_API_VERSION = "1.29.1" +NEXT_PUBLIC_API_VERSION = "1.30.0" NEXT_PUBLIC_DEFAULT_LANGUAGE = "fa" NEXT_PUBLIC_DEFAULT_DIRECTION = "rtl" @@ -9,6 +9,8 @@ NEXT_PUBLIC_SECONDARY_MAIN = "#FF4E00" NEXT_PUBLIC_BASE_URL = "https://loan.witel.ir" NEXT_PUBLIC_POWERED_BY_URL = "https://witel.ir" +NEXT_PUBLIC_CONFIG_APP_URL = "http://localhost:3000/config.json" + #["NEXT_PUBLIC_HAS_WIDGET" , "NEXT_PUBLIC_HAS_NOTIFICATION"] NEXT_PUBLIC_HAS_VALUE=["NEXT_PUBLIC_HAS_NOTIFICATION"] diff --git a/public/config.json.example b/public/config.json.example new file mode 100644 index 0000000..e92ed58 --- /dev/null +++ b/public/config.json.example @@ -0,0 +1,53 @@ +{ + "middlewares": { + "/dashboard/navgan/add-request-loan":{ + "status" : false, + "title":"اطلاع رسانی", + "message": "ثبت نام به زودی آغاز خواهد شد." + } + }, + "add_request_loan_birthday_range": { + "min": 1327, + "max": 1380 + }, + "interest_rate": "15.2", + "loan_repayment": "6", + "deadlines": { + "register": { + "date": { + "from": "1402/10/02", + "to": "1402/10/15" + }, + "messages": { + "before": "ثبت نام هنوز شروع نشده", + "after": "زمان ثبت نام به اتمام رسید" + }, + "timeline_label": { + "primary": "ثبت نام متقاضی", + "secondary": "ثبت نام متقاضیان واجد شرایط" + } + }, + "validation": { + "date": { + "from": "1402/10/15", + "to": "1402/10/30" + }, + "messages": {}, + "timeline_label": { + "primary": "صحت سنجی متقاضیان", + "secondary": "بررسی و کارشناسی درخواست ها" + } + }, + "done": { + "date": { + "from": "1402/11/01", + "to": "1402/11/20" + }, + "messages": {}, + "timeline_label": { + "primary": "ارجاع به بانک", + "secondary": "فرآیند معرفی ضامن و پرداخت" + } + } + } +} \ No newline at end of file diff --git a/public/locales/fa/app.json b/public/locales/fa/app.json index 0b4d635..53699b0 100644 --- a/public/locales/fa/app.json +++ b/public/locales/fa/app.json @@ -44,6 +44,7 @@ "dashboard": "داشبورد", "reports": "گزارشات", "passenger-office-chief": "اداره مسافر", + "admin-setting": "تنظیمات ", "machinery-expert": "کارتابل کارشناسی", "passenger-boss": "کارتابل کارگروه", "transportation-assistant": "بررسی وام های تبصره 18", @@ -121,6 +122,7 @@ "edit_profile": "ویرایش پروفایل", "expert_management": "مدیریت کارشناسان", "user_management_page": "مدیریت کاربر", + "admin_setting": "تنظیمات", "loan_history": "کارتابل نظارت", "total_number_loan_offers": "تعداد کل وام های پیشنهادی", "total_amount_proposed_loan": "مبلغ کل وام های پیشنهادی", @@ -147,6 +149,13 @@ "button_while_submit": "در حال ثبت", "button_submit": "ثبت" }, + "AdminSetting": { + "id": "کد یکتا", + "edit": "ویرایش", + "name": "نام انگلیسی", + "name_fa": "نام فارسی", + "value": "مقدار" + }, "UpdateProfile": { "error_invalid_email": "ایمیل نامعتبر است", "typography_edit_profile": "پروفایل", @@ -184,7 +193,7 @@ "vehicle_type": "نوع ماشین", "state_name": "وضعیت درخواست", "confirm": "ارجاع به کارشناس ماشین آلات", - "upload_file_unit": "فایل بارگذاری شده باید حداکثر 2Mb باشد", + "upload_file_unit": "فایل بارگذاری شده باید حداکثر 5Mb باشد", "upload_file_format": "فرمت قابل قبول : png,jpg,pdf", "excel_report": "گزارش اکسل" }, @@ -222,7 +231,7 @@ "vehicle_type": "نوع ماشین", "state_name": "وضعیت درخواست", "confirm": "تایید نهایی و ارجاع به بانک", - "upload_file_unit": "فایل بارگذاری شده باید حداکثر 2Mb باشد", + "upload_file_unit": "فایل بارگذاری شده باید حداکثر 5Mb باشد", "upload_file_format": "فرمت قابل قبول : png,jpg,pdf", "excel_report": "گزارش اکسل" }, @@ -254,7 +263,7 @@ "upload_file": "صورت جلسه کارگروه را بارگذاری کنید", "upload_file_first_required": "وارد کردن فرم الف الزامیست", "upload_file_second_required": "وارد کردن فرم ب الزامیست", - "upload_file_unit": "فایل بارگذاری شده باید حداکثر 2Mb باشد", + "upload_file_unit": "فایل بارگذاری شده باید حداکثر 5Mb باشد", "upload_file_format": "فرمت قابل قبول : png,jpg,pdf", "print_form_a": "چاپ فرم الف", "print_form_b": "چاپ فرم ب", @@ -358,7 +367,7 @@ "vehicle_type": "نوع ماشین", "state_name": "وضعیت درخواست", "confirm": "ارجاع به مدیر کل", - "upload_file_unit": "فایل بارگذاری شده باید حداکثر 2Mb باشد", + "upload_file_unit": "فایل بارگذاری شده باید حداکثر 5Mb باشد", "upload_file_format": "فرمت قابل قبول : png,jpg,pdf", "excel_report": "گزارش اکسل", "confirm_text": "فرم الف و ب به رویت و تایید رسید." @@ -398,7 +407,7 @@ "confirm": "ارجاع به کارگروه", "upload_file": "گزارش کارشناسی را بارگذاری کنید", "upload_file_required": "وارد کردن گزارش کارشناسی الزامیست", - "upload_file_unit": "فایل بارگذاری شده باید حداکثر 2Mb باشد", + "upload_file_unit": "فایل بارگذاری شده باید حداکثر 5Mb باشد", "upload_file_format": "فرمت قابل قبول : png,jpg,pdf", "print_report": "چاپ گزارش کارشناسی", "max_amount": "حداکثر مبلغ پیشنهادی {value} میلیون ریال می باشد", @@ -469,7 +478,8 @@ "button-reject": "عدم تایید", "description": "توضیحات خود را وارد نمائید", "optional": " (اختیاری)", - "description_error": "وارد کردن توضیحات الزامی است!" + "description_error": "وارد کردن توضیحات الزامی است!", + "choose_file" : "انتخاب فایل" }, "UpdateDialog": { "update": "ویرایش", @@ -481,8 +491,16 @@ "button-cancel": "بستن", "refahi": "رفاهی", "navgan": "ناوگان", + "setting_name": "نام انگلیسی", + "setting_name_fa": "نام فارسی", + "value": "مقدار", "phone_number": "شماره تلفن", "national_id": "کد ملی", + "name_string": "نام می بایست متن باشد", + "setting_name_error": "وارد کردن نام انگلیسی الزامیست", + "setting_name_fa_error": "وارد کردن نام فارسی الزامیست", + "value_error": "وارد کردن مقدار الزامیست", + "value_string": "مقدار می بایست متن باشد", "name": "نام انگلیسی", "name_error": "وارد کردن نام انگلیسی الزامیست", "name_fa": "نام فارسی", @@ -576,6 +594,9 @@ }, "AddDialog": { "add": "افزودن", + "setting_name": "نام انگلیسی", + "setting_name_fa": "نام فارسی", + "value": "مقدار", "name": "نام انگلیسی", "name_error": "وارد کردن نام انگلیسی الزامیست", "name_fa": "نام فارسی", @@ -594,6 +615,11 @@ "national_id_error": "وارد کردن کد ملی الزامیست", "type_id_error": "وارد کردن نوع کاربر الزامیست", "navgan_id_error": "وارد کردن کد ناوگان الزامیست", + "name_string": "نام می بایست متن باشد", + "setting_name_error": "وارد کردن نام انگلیسی الزامیست", + "setting_name_fa_error": "وارد کردن نام فارسی الزامیست", + "value_error": "وارد کردن مقدار الزامیست", + "value_string": "مقدار می بایست متن باشد", "refahi": "رفاهی", "navgan": "ناوگان", "type_id": "نوع کاربر", @@ -628,6 +654,8 @@ "choose_year": "انتخاب سال", "provinces": "استان های", "province": "استان", + "all_provinces": "تمامی استان ها", + "excel_export_reports": "خروجی به اکسل گزارش عملکرد", "year": "سال", "years": "سال های", "text_field_loading_provinces_list": "درحال دریافت استان ها", @@ -655,6 +683,7 @@ "tanafos_period": "دوره تنفس", "sakht_period": "دوره ساخت", "national_id": "کد ملی", + "postal_code": "کد پستی", "phone_number": "موبایل", "proposed_amount": "مبلغ پیشنهادی", "approved_amount": "مبلغ تصویب شده", diff --git a/src/components/dashboard/admin-setting/Form/CreateForm/CreateContent.jsx b/src/components/dashboard/admin-setting/Form/CreateForm/CreateContent.jsx new file mode 100644 index 0000000..f10a97b --- /dev/null +++ b/src/components/dashboard/admin-setting/Form/CreateForm/CreateContent.jsx @@ -0,0 +1,107 @@ +import {Button, DialogActions, DialogContent, Stack, TextField} 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_ADMIN_SETTING} from "@/core/data/apiRoutes"; + +const CreateContent = ({mutate, setOpenConfirmDialog}) => { + const t = useTranslations(); + const requestServer = useRequest({auth: true}) + const validationSchema = Yup.object().shape({ + name: Yup.string("AddDialog.name_string") + .required(t("AddDialog.setting_name_error")), + name_fa: Yup.string("AddDialog.name_fa_string") + .required(t("AddDialog.setting_name_fa_error")), + value: Yup.string("AddDialog.value_string") + .required(t("AddDialog.value_error")), + }); + + const formik = useFormik({ + initialValues: { + name: "", + name_fa: "", + value: "", + }, validationSchema, onSubmit: (values, {setSubmitting}) => { + const formData = new FormData(); + formData.append("name", values.name); + formData.append("name_fa", values.name_fa); + formData.append("value", values.value); + + requestServer(ADD_ADMIN_SETTING, 'post', { + data: formData, + }).then((response) => { + setOpenConfirmDialog(false) + mutate() + update_notification() + }).catch(() => { + }).finally(() => { + setSubmitting(false); + }); + }, + }); + + return ( + <> + + + + + + + + + + + + + + + + + + + ) +} +export default CreateContent \ No newline at end of file diff --git a/src/components/dashboard/admin-setting/Form/CreateForm/index.jsx b/src/components/dashboard/admin-setting/Form/CreateForm/index.jsx new file mode 100644 index 0000000..9d531d2 --- /dev/null +++ b/src/components/dashboard/admin-setting/Form/CreateForm/index.jsx @@ -0,0 +1,35 @@ +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 "@/components/dashboard/admin-setting/Form/CreateForm/CreateContent"; + +const CreateForm = ({mutate}) => { + const t = useTranslations(); + const [openConfirmDialog, setOpenConfirmDialog] = useState(false); + + return ( + + + + + + {t("AddDialog.add")} + + + + ) +} +export default CreateForm \ No newline at end of file diff --git a/src/components/dashboard/admin-setting/Form/DeleteForm.jsx b/src/components/dashboard/admin-setting/Form/DeleteForm.jsx new file mode 100644 index 0000000..b2fdead --- /dev/null +++ b/src/components/dashboard/admin-setting/Form/DeleteForm.jsx @@ -0,0 +1,67 @@ +import {useTranslations} from "next-intl"; +import {useState} from "react"; +import { + Button, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + IconButton, + Tooltip, + Typography +} from "@mui/material"; +import DeleteIcon from '@mui/icons-material/Delete'; +import {DELETE_ADMIN_SETTING} from "@/core/data/apiRoutes"; +import useRequest from "@/lib/app/hooks/useRequest"; +import useNotification from "@/lib/app/hooks/useNotification"; + +const Delete = ({rowId, mutate}) => { + const t = useTranslations(); + const [openConfirmDialog, setOpenConfirmDialog] = useState(false); + const [isSubmitting, setIsSubmitting] = useState(false) + const requestServer = useRequest({auth: true}) + const {update_notification} = useNotification() + const handleSubmit = () => { + setIsSubmitting(true) + requestServer(`${DELETE_ADMIN_SETTING}/${rowId}`, 'delete').then((response) => { + mutate() + update_notification() + }).catch(() => { + }).finally(() => { + setIsSubmitting(false) + }); + } + + return ( + <> + + { + setOpenConfirmDialog(true) + }} + > + + + + + {t("DeleteDialog.delete")} + + {t("DeleteDialog.typography")} + + + + + + + + ); +}; +export default Delete; \ No newline at end of file diff --git a/src/components/dashboard/admin-setting/Form/UpdateForm/UpdateContent.jsx b/src/components/dashboard/admin-setting/Form/UpdateForm/UpdateContent.jsx new file mode 100644 index 0000000..f521ced --- /dev/null +++ b/src/components/dashboard/admin-setting/Form/UpdateForm/UpdateContent.jsx @@ -0,0 +1,109 @@ +import {UPDATE_ADMIN_SETTING} from "@/core/data/apiRoutes"; +import useNotification from "@/lib/app/hooks/useNotification"; +import useRequest from "@/lib/app/hooks/useRequest"; +import {Button, DialogActions, DialogContent, Stack, TextField} from "@mui/material" +import {useFormik} from "formik"; +import {useTranslations} from "next-intl"; +import * as Yup from "yup"; + +const UpdateContent = ({row, mutate, setOpenConfirmDialog}) => { + const t = useTranslations(); + const requestServer = useRequest({auth: true}) + const {update_notification} = useNotification() + + const validationSchema = Yup.object().shape({ + name: Yup.string("UpdateDialog.name_string") + .required(t("UpdateDialog.setting_name_error")), + name_fa: Yup.string("UpdateDialog.name_fa_string") + .required(t("UpdateDialog.setting_name_fa_error")), + value: Yup.string("UpdateDialog.value_string") + .required(t("UpdateDialog.value_error")), + }); + + const formik = useFormik({ + initialValues: { + name: row.getValue("name"), + name_fa: row.getValue("name_fa"), + value: row.getValue("value"), + }, validationSchema, onSubmit: (values, {setSubmitting}) => { + const formData = new FormData(); + formData.append("name", values.name); + formData.append("name_fa", values.name_fa); + formData.append("value", values.value); + requestServer(`${UPDATE_ADMIN_SETTING}/${row.getValue("id")}`, 'post', { + data: formData, + }).then((response) => { + setOpenConfirmDialog(false) + mutate() + update_notification() + }).catch(() => { + }).finally(() => { + setSubmitting(false); + }); + }, + }); + return ( + <> + + + + + + + + + + + + + + + + + + + ) +} +export default UpdateContent \ No newline at end of file diff --git a/src/components/dashboard/admin-setting/Form/UpdateForm/index.jsx b/src/components/dashboard/admin-setting/Form/UpdateForm/index.jsx new file mode 100644 index 0000000..a00cd74 --- /dev/null +++ b/src/components/dashboard/admin-setting/Form/UpdateForm/index.jsx @@ -0,0 +1,30 @@ +import {Dialog, DialogTitle, IconButton, Tooltip} from "@mui/material" +import EditIcon from '@mui/icons-material/Edit'; +import {useTranslations} from "next-intl"; +import {useState} from "react"; +import UpdateContent from "./UpdateContent"; + +const Update = ({row, mutate}) => { + const t = useTranslations(); + const [openConfirmDialog, setOpenConfirmDialog] = useState(false); + return ( + <> + + { + setOpenConfirmDialog(true) + }} + > + + + + + {t("UpdateDialog.update")} + + + + ) +} +export default Update \ No newline at end of file diff --git a/src/components/dashboard/admin-setting/TableRowActions.jsx b/src/components/dashboard/admin-setting/TableRowActions.jsx new file mode 100644 index 0000000..6bdd0fe --- /dev/null +++ b/src/components/dashboard/admin-setting/TableRowActions.jsx @@ -0,0 +1,20 @@ +import {Box} from "@mui/material"; +import Update from "src/components/dashboard/admin-setting/Form/UpdateForm"; +import Delete from "@/components/dashboard/admin-setting/Form/DeleteForm"; + +const TableRow = ({row, mutate}) => { + return ( + + + + + ); +}; + +export default TableRow; diff --git a/src/components/dashboard/admin-setting/TableToolbar.jsx b/src/components/dashboard/admin-setting/TableToolbar.jsx new file mode 100644 index 0000000..007eb2f --- /dev/null +++ b/src/components/dashboard/admin-setting/TableToolbar.jsx @@ -0,0 +1,11 @@ +import {useTranslations} from "next-intl"; +import CreateForm from "@/components/dashboard/admin-setting/Form/CreateForm"; + +function TableToolbar({mutate}) { + const t = useTranslations(); + + return + +} + +export default TableToolbar; diff --git a/src/components/dashboard/admin-setting/index.jsx b/src/components/dashboard/admin-setting/index.jsx new file mode 100644 index 0000000..685c47a --- /dev/null +++ b/src/components/dashboard/admin-setting/index.jsx @@ -0,0 +1,94 @@ +import {Box, Typography} from "@mui/material"; +import {useMemo} from "react"; +import {GET_ADMIN_SETTING} from "@/core/data/apiRoutes"; +import {useTranslations} from "next-intl"; +import DataTable from "@/core/components/DataTable"; +import TableRowActions from "@/components/dashboard/admin-setting/TableRowActions"; +import TableToolbar from "@/components/dashboard/admin-setting/TableToolbar"; + +function DashboardAdminSettingComponent() { + const t = useTranslations(); + const columns = useMemo( + () => [ + { + accessorFn: (row) => row.id, + id: "id", + header: t("AdminSetting.id"), + enableColumnFilter: true, + datatype: "number", + filterFn: "equals", + columnFilterModeOptions: ["contains", "equals", "notEquals"], + Cell: ({renderedCellValue}) => ( + {renderedCellValue} + ), + }, + { + accessorFn: (row) => row.name_fa, + id: "name_fa", + header: t("AdminSetting.name_fa"), + enableColumnFilter: true, + datatype: "text", + filterFn: "contains", + columnFilterModeOptions: ["contains", "equals"], + Cell: ({renderedCellValue}) => ( + {renderedCellValue} + ), + }, + { + accessorFn: (row) => row.name, + id: "name", + header: t("AdminSetting.name"), + enableColumnFilter: true, + datatype: "text", + filterFn: "contains", + columnFilterModeOptions: ["contains", "equals"], + Cell: ({renderedCellValue}) => ( + {renderedCellValue} + ), + }, + { + accessorFn: (row) => row.value, + id: "value", + header: t("AdminSetting.value"), + enableColumnFilter: true, + datatype: "text", + filterFn: "contains", + columnFilterModeOptions: [ + "equals", + "contains", + ], + Cell: ({renderedCellValue}) => ( + {renderedCellValue} + ), + }, + ], + [] + ); + return ( + + + + ); +} + +export default DashboardAdminSettingComponent; diff --git a/src/components/dashboard/loan-history/index.jsx b/src/components/dashboard/loan-history/index.jsx index 8fe4bed..8b886a4 100644 --- a/src/components/dashboard/loan-history/index.jsx +++ b/src/components/dashboard/loan-history/index.jsx @@ -156,6 +156,22 @@ function DashboardLoanHistoryComponent() { {renderedCellValue} ), }, + { + accessorFn: (row) => row.postal_code, + id: "postal_code", + header: t("LoanHistory.postal_code"), + enableColumnFilter: true, + datatype: "text", + filterFn: "contains", + columnFilterModeOptions: [ + "equals", + "notEquals", + "contains", + ], + Cell: ({renderedCellValue}) => ( + {renderedCellValue} + ), + }, { accessorFn: (row) => moment(row.created_at).locale("fa").format("HH:mm | yyyy/MM/DD"), @@ -356,10 +372,12 @@ function DashboardLoanHistoryComponent() { }, { accessorFn: (row) => row.state_name, - id: "state_id", + id: "state_name", header: t("LoanHistory.state_name"), - enableColumnFilter: false, - datatype: "numeric", + enableColumnFilter: true, + datatype: "text", + filterFn: "contains", + columnFilterModeOptions: ["contains", "equals", "notEquals"], Cell: ({renderedCellValue}) => ( {renderedCellValue} ), diff --git a/src/components/dashboard/machinary-office/Form/ConfirmForm/ConfirmContent.jsx b/src/components/dashboard/machinary-office/Form/ConfirmForm/ConfirmContent.jsx index 438def6..a1a6b1f 100644 --- a/src/components/dashboard/machinary-office/Form/ConfirmForm/ConfirmContent.jsx +++ b/src/components/dashboard/machinary-office/Form/ConfirmForm/ConfirmContent.jsx @@ -7,7 +7,6 @@ import { DialogContent, FormHelperText, Grid, - InputAdornment, LinearProgress, Stack, TextField, @@ -21,6 +20,7 @@ import PriceField from "@/core/components/PriceField"; import * as Yup from "yup"; import CustomAccordion from "@/core/components/CustomAccordion"; import ImageContent from "@/core/components/ImageContent"; +import SelectBox from "@/core/components/SelectBox"; const vehicle_amount = { "اتوبوس": 7000, @@ -94,7 +94,7 @@ const ConfirmContent = ({mutate, setOpenConfirmDialog, rowId, vehicle_type, hand confirm_img: Yup.mixed() .required(t("MachinaryOffice.upload_file_required")) .test('fileSize', `${t("MachinaryOffice.upload_file_unit")}`, (value) => { - return value.size <= 2 * 1024 * 1024; + return value.size <= 5 * 1024 * 1024; }) .test('fileType', `${t("MachinaryOffice.upload_file_format")}`, (value) => { const allowedTypes = ['image/jpg', 'image/jpeg', 'image/png', 'application/pdf']; @@ -232,51 +232,93 @@ const ConfirmContent = ({mutate, setOpenConfirmDialog, rowId, vehicle_type, hand /> - {t("ConfirmDialog.sakht_period_day")}, + selectType="sakht_period" + selectors={[ + { + id: 1, + value: "30", + name: "1 ماه - 30 روز" + }, + { + id: 2, + value: "60", + name: "2 ماه - 60 روز" + }, + { + id: 3, + value: "90", + name: "3 ماه - 90 روز" + }, + { + id: 4, + value: "120", + name: "4 ماه - 120 روز" + }, + { + id: 5, + value: "150", + name: "5 ماه - 150 روز" + }, + { + id: 6, + value: "180", + name: "6 ماه - 180 روز" + } + ]} + schema={{value: 'value', name: 'name'}} + select={formik.values.sakht_period} + handleChange={(event) => { + formik.setFieldValue('sakht_period', event.target.value) }} + setFieldTouched={formik.setFieldTouched} + error={formik.touched.sakht_period && Boolean(formik.errors.sakht_period)} + helperText={formik.touched.sakht_period && formik.errors.sakht_period} + onBlur={formik.handleBlur("sakht_period")} /> - {t("ConfirmDialog.tanafos_period_month")}, + selectType="tanafos_period" + selectors={[ + { + id: 1, + value: "1", + name: "1 ماه" + }, + { + id: 2, + value: "2", + name: "2 ماه" + }, + { + id: 3, + value: "3", + name: "3 ماه" + }, + { + id: 4, + value: "4", + name: "4 ماه" + } + ]} + schema={{value: 'value', name: 'name'}} + select={formik.values.tanafos_period} + handleChange={(event) => { + formik.setFieldValue('tanafos_period', event.target.value) }} + setFieldTouched={formik.setFieldTouched} + error={formik.touched.tanafos_period && Boolean(formik.errors.tanafos_period)} + helperText={formik.touched.tanafos_period && formik.errors.tanafos_period} + onBlur={formik.handleBlur("tanafos_period")} /> diff --git a/src/components/dashboard/machinary-office/Form/RejectForm/RejectContent.jsx b/src/components/dashboard/machinary-office/Form/RejectForm/RejectContent.jsx index 345f569..afec59e 100644 --- a/src/components/dashboard/machinary-office/Form/RejectForm/RejectContent.jsx +++ b/src/components/dashboard/machinary-office/Form/RejectForm/RejectContent.jsx @@ -1,7 +1,7 @@ import UploadSystem from "@/core/components/UploadSystem" import useNotification from "@/lib/app/hooks/useNotification"; import useRequest from "@/lib/app/hooks/useRequest"; -import {Button, DialogActions, DialogContent, Stack, TextField, Typography} from "@mui/material" +import {Button, DialogActions, DialogContent, FormHelperText, Stack, TextField, Typography} from "@mui/material" import {useFormik} from "formik"; import {useTranslations} from "next-intl"; import {useState} from "react"; @@ -19,6 +19,14 @@ const RejectContent = ({rowId, mutate, setOpenRejectDialog}) => { const validationSchema = Yup.object().shape({ description: Yup.string().required(t("RejectDialog.description_error")), + reject_img: Yup.mixed().notRequired(t("RejectDialog.choose_file")) + .test('fileSize', `${t("MachinaryOffice.upload_file_unit")}`, (value) => { + return !value || (value.size && value.size <= 5 * 1024 * 1024); + }) + .test('fileType', `${t("MachinaryOffice.upload_file_format")}`, (value) => { + const allowedTypes = ['image/jpg', 'image/jpeg', 'image/png', 'application/pdf']; // Define your allowed file types + return !value || (value.type && allowedTypes.includes(value.type)); + }), }); const formik = useFormik({ @@ -62,18 +70,14 @@ const RejectContent = ({rowId, mutate, setOpenRejectDialog}) => { const handleUploadChange = (event) => { const uploadedFile = event.target?.files?.[0]; if (uploadedFile) { - const maxFileSize = 2 * 1024 * 1024; - if (uploadedFile.size > maxFileSize) { - UploadFileNotification(t); - event.target.value = ""; - return; - } const fileType = event.target?.files?.[0].type; const fileName = event.target?.files?.[0].name; setSelectedImage(URL.createObjectURL(uploadedFile)); setfileType(fileType); setfileName(fileName); - formik.setFieldValue("reject_img", uploadedFile); + formik.setFieldValue("reject_img", uploadedFile).then(() => { + formik.setFieldTouched("reject_img", true); + }) setShowAddIcon(false); } }; @@ -113,7 +117,17 @@ const RejectContent = ({rowId, mutate, setOpenRejectDialog}) => { imageSize={[250 /*width*/, 150 /*height*/]} setShowAddIcon={setShowAddIcon} showAddIcon={showAddIcon} + onBlur={() => formik.handleBlur("reject_img")} + error={ + formik.touched.reject_img && + Boolean(formik.errors.reject_img) + } /> + + {formik.touched.reject_img && formik.errors.reject_img} + diff --git a/src/components/dashboard/navgan-loan-management/index.jsx b/src/components/dashboard/navgan-loan-management/index.jsx index 6c4b8f6..03b5242 100644 --- a/src/components/dashboard/navgan-loan-management/index.jsx +++ b/src/components/dashboard/navgan-loan-management/index.jsx @@ -357,10 +357,12 @@ function DashboardNavganLoanManagementComponent() { }, { accessorFn: (row) => row.state_name, - id: "state_id", + id: "state_name", header: t("NavganLoanManagement.state_name"), - enableColumnFilter: false, - datatype: "numeric", + enableColumnFilter: true, + datatype: "text", + filterFn: "contains", + columnFilterModeOptions: ["contains", "equals", "notEquals"], Cell: ({renderedCellValue}) => ( {renderedCellValue} ), diff --git a/src/components/dashboard/navgan-province-manager/Form/ConfirmForm/ConfirmContent.jsx b/src/components/dashboard/navgan-province-manager/Form/ConfirmForm/ConfirmContent.jsx index d63cd63..a7ce958 100644 --- a/src/components/dashboard/navgan-province-manager/Form/ConfirmForm/ConfirmContent.jsx +++ b/src/components/dashboard/navgan-province-manager/Form/ConfirmForm/ConfirmContent.jsx @@ -41,7 +41,7 @@ const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog, handleSizeChangeCl confirm_img: Yup.mixed().notRequired(t("ConfirmDialog.approved_amount_error")) .test('fileSize', `${t("NavganProvinceManager.upload_file_unit")}`, (value) => { if (!value) return true - return value.size <= 2 * 1024 * 1024; + return value.size <= 5 * 1024 * 1024; }) .test('fileType', `${t("NavganProvinceManager.upload_file_format")}`, (value) => { if (!value) return true diff --git a/src/components/dashboard/navgan-province-manager/Form/RejectForm/RejectContent.jsx b/src/components/dashboard/navgan-province-manager/Form/RejectForm/RejectContent.jsx index 8533094..9907263 100644 --- a/src/components/dashboard/navgan-province-manager/Form/RejectForm/RejectContent.jsx +++ b/src/components/dashboard/navgan-province-manager/Form/RejectForm/RejectContent.jsx @@ -1,7 +1,7 @@ import UploadSystem from "@/core/components/UploadSystem" import useNotification from "@/lib/app/hooks/useNotification"; import useRequest from "@/lib/app/hooks/useRequest"; -import {Button, DialogActions, DialogContent, Stack, TextField, Typography} from "@mui/material" +import {Button, DialogActions, DialogContent, FormHelperText, Stack, TextField, Typography} from "@mui/material" import {useFormik} from "formik"; import {useTranslations} from "next-intl"; import {useState} from "react"; @@ -19,6 +19,14 @@ const RejectContent = ({rowId, mutate, setOpenRejectDialog}) => { const validationSchema = Yup.object().shape({ description: Yup.string().required(t("RejectDialog.description_error")), + reject_img: Yup.mixed().notRequired(t("RejectDialog.choose_file")) + .test('fileSize', `${t("NavganProvinceManager.upload_file_unit")}`, (value) => { + return !value || (value.size && value.size <= 5 * 1024 * 1024); + }) + .test('fileType', `${t("NavganProvinceManager.upload_file_format")}`, (value) => { + const allowedTypes = ['image/jpg', 'image/jpeg', 'image/png', 'application/pdf']; // Define your allowed file types + return !value || (value.type && allowedTypes.includes(value.type)); + }), }); const formik = useFormik({ @@ -62,21 +70,18 @@ const RejectContent = ({rowId, mutate, setOpenRejectDialog}) => { const handleUploadChange = (event) => { const uploadedFile = event.target?.files?.[0]; if (uploadedFile) { - const maxFileSize = 2 * 1024 * 1024; - if (uploadedFile.size > maxFileSize) { - UploadFileNotification(t); - event.target.value = ""; - return; - } const fileType = event.target?.files?.[0].type; const fileName = event.target?.files?.[0].name; setSelectedImage(URL.createObjectURL(uploadedFile)); setfileType(fileType); setfileName(fileName); - formik.setFieldValue("reject_img", uploadedFile); + formik.setFieldValue("reject_img", uploadedFile).then(() => { + formik.setFieldTouched("reject_img", true); + }) setShowAddIcon(false); } }; + return ( <> @@ -113,7 +118,17 @@ const RejectContent = ({rowId, mutate, setOpenRejectDialog}) => { imageSize={[250 /*width*/, 150 /*height*/]} setShowAddIcon={setShowAddIcon} showAddIcon={showAddIcon} + onBlur={() => formik.handleBlur("reject_img")} + error={ + formik.touched.reject_img && + Boolean(formik.errors.reject_img) + } /> + + {formik.touched.reject_img && formik.errors.reject_img} + diff --git a/src/components/dashboard/passenger-boss/Buttons/printFormA.jsx b/src/components/dashboard/passenger-boss/Buttons/printFormA.jsx index fa1e882..6e0405e 100644 --- a/src/components/dashboard/passenger-boss/Buttons/printFormA.jsx +++ b/src/components/dashboard/passenger-boss/Buttons/printFormA.jsx @@ -3,9 +3,9 @@ import {useRouter} from "next/router"; import {useState} from "react"; import useRequest from "@/lib/app/hooks/useRequest"; import usePrint from "@/lib/app/hooks/usePrint"; -import {GET_PASSENGER_BOSS} from "@/core/data/apiRoutes"; import {Button, CircularProgress} from "@mui/material"; import PrintIcon from "@mui/icons-material/Print"; +import {GET_PASSENGER_BOSS} from "@/core/data/apiRoutes"; const PrintFormA = () => { const t = useTranslations(); @@ -13,26 +13,64 @@ const PrintFormA = () => { const [loading, setLoading] = useState(false) const requestServer = useRequest({auth: true, pending: false, success: {notification: {show: false}}}) const {setPrintPage, setPrintTitle} = usePrint() - const clickHandler = () => { - setLoading(true) - const params = new URLSearchParams(); - params.set("start", '0'); - params.set("filters", '[]'); - params.set("sorting", '[]'); - requestServer(`${GET_PASSENGER_BOSS}?${params}`, 'get').then((response) => { - const _chunkSize = 6 - const _data = response.data.data - const chunk = Array.from({length: Math.ceil(_data.length / _chunkSize)}, (v, i) => - _data.slice(i * _chunkSize, i * _chunkSize + _chunkSize) - ); - setPrintPage(chunk.length) - setPrintTitle(t('PassengerBoss.print_form_a')) - sessionStorage.setItem('form-a-print', JSON.stringify(chunk)) - router.push('/dashboard/passenger-boss/prints/form-a') - }).catch(() => { - setLoading(false) - }) - } + + const filterRequest = async (filterArr) => { + setLoading(true); + + const requests = filterArr.map(async (item) => { + try { + const params = new URLSearchParams(); + params.set("start", '0'); + params.set("filters", JSON.stringify([item])); + params.set("sorting", '[]'); + + const response = await requestServer(`${GET_PASSENGER_BOSS}?${params}`, 'get'); + const _chunkSize = 6; + const _data = response.data.data; + const chunk = Array.from({length: Math.ceil(_data.length / _chunkSize)}, (v, i) => + _data.slice(i * _chunkSize, i * _chunkSize + _chunkSize) + ); + return chunk; + } catch (error) { + setLoading(false); + throw error; // Propagate the error + } + }); + + try { + const chunks = await Promise.all(requests); + setLoading(false); + return chunks.flat(); // Flatten the chunks into a single array + } catch (error) { + console.error("Error processing requests:", error); + throw error; + } + }; + + const clickHandler = async () => { + try { + const chunks = await filterRequest([ + { + id: "vehicle_type", + value: "اتوبوس", + fn: "contains", + datatype: "text", + }, + { + id: "vehicle_type", + value: "مینی بوس", + fn: "contains", + datatype: "text", + }, + ]); + setPrintPage(chunks.length); + setPrintTitle(t('PassengerBoss.print_form_a')); + sessionStorage.setItem('form-a-print', JSON.stringify(chunks)); + router.push('/dashboard/passenger-boss/prints/form-a'); + } catch (error) { + console.error("Error in clickHandler:", error); + } + }; return ( + + + + + ) +}; + +export default ExcelExport; \ No newline at end of file diff --git a/src/components/dashboard/reports/Filter/ProvinceFilter.jsx b/src/components/dashboard/reports/Filter/ProvinceFilter.jsx index 7dacacc..f96cc84 100644 --- a/src/components/dashboard/reports/Filter/ProvinceFilter.jsx +++ b/src/components/dashboard/reports/Filter/ProvinceFilter.jsx @@ -24,22 +24,21 @@ const ProvinceFilter = ({formik, multiple, setFilteredText}) => { const t = useTranslations(); const provinceChange = (event) => { const {target: {value}} = event; - formik.handleChange(event); formik.setFieldValue('province_id', value); if (Array.isArray(value)) { - const province_names = [] + const province_names = []; value.map((item) => { province_names.push(provinceList.find(province => province.id === item).name) }) setFilteredText(prevState => ({ ...prevState, - province: province_names.length !== 0 ? `| ${t("filters.provinces")}: ${province_names.join(' , ')}` : "" + province: province_names.length !== 0 ? `${t("filters.provinces")}: ${province_names.join(' , ')}` : "" })); } else { const selectedProvince = provinceList.find(province => province.id === value); setFilteredText(prevState => ({ ...prevState, - province: `| ${t("filters.province")}: ${selectedProvince.name}` + province: `${t("filters.province")}: ${selectedProvince.name}` })); } }; @@ -78,10 +77,10 @@ const ProvinceFilter = ({formik, multiple, setFilteredText}) => { !needAdorment ? multiple - ? formik.setFieldValue('province_id', []) - : formik.setFieldValue('province_id', "") - } + onClick={() => { + multiple ? formik.setFieldValue('province_id', []) : formik.setFieldValue('province_id', "") + setFilteredText(prevState => ({...prevState, province: t("filters.all_provinces")})) + }} size="small"> diff --git a/src/components/dashboard/reports/Filter/index.jsx b/src/components/dashboard/reports/Filter/index.jsx index 3a84d2f..2486533 100644 --- a/src/components/dashboard/reports/Filter/index.jsx +++ b/src/components/dashboard/reports/Filter/index.jsx @@ -25,8 +25,12 @@ const Filter = ({title, filterItem, setFilterOption, isLoadingReportList, mutate const t = useTranslations(); const filter_by_province = filterItem.find((item) => item.type === "province"); const filter_by_year = filterItem.find((item) => item.type === "year"); - const [expanded, setExpanded] = useState(false); - const [filteredText, setFilteredText] = useState({province: "", year: ""}); + const [expanded, setExpanded] = useState(true); + const [filteredText, setFilteredText] = useState({ + ...(filter_by_province ? {province: t("filters.all_provinces")} : {}), + ...(filter_by_year ? {year: ""} : {}) + }); + const [textFilter, setTextFilter] = useState() const [stepValue, setStepValue] = useState({ ...(filter_by_year && { date: filter_by_year.multiple ? [] : `${moment().format('jYYYY')}` @@ -55,10 +59,15 @@ const Filter = ({title, filterItem, setFilterOption, isLoadingReportList, mutate : [] ), ].filter(Boolean); - setFilterOption(fields) + setFilterOption(fields); + textMaker(); }, }); + const textMaker = (() => { + setTextFilter(`| ${Object.values(filteredText).join(", ")}`) + }); + useEffect(() => { const province = formik.values.province_id; const fields = [ @@ -73,6 +82,7 @@ const Filter = ({title, filterItem, setFilterOption, isLoadingReportList, mutate : [] ), ].filter(Boolean); + setTextFilter(`| ${Object.values(filteredText).join(", ")}`) }, []); return ( @@ -89,7 +99,7 @@ const Filter = ({title, filterItem, setFilterOption, isLoadingReportList, mutate {title} - {filteredText.province} {filteredText.year} + {textFilter} diff --git a/src/components/dashboard/reports/LoanDetails/FunctionalityCharts.jsx b/src/components/dashboard/reports/LoanDetails/FunctionalityCharts.jsx index 937fc34..b161a42 100644 --- a/src/components/dashboard/reports/LoanDetails/FunctionalityCharts.jsx +++ b/src/components/dashboard/reports/LoanDetails/FunctionalityCharts.jsx @@ -14,7 +14,7 @@ const FunctionalityCharts = ({reportList}) => { { Object.entries(reportList).map(([entryKey, innerObject]) => { - let percentage = innerObject.total !== 0 ? (innerObject.total - innerObject.pending) / innerObject.total * 100 : 0; + let percentage = innerObject.total !== 0 && innerObject.pending !== 64 ? (innerObject.total - innerObject.pending) / innerObject.total * 100 : 0; const ItemName = DataItemsName.find(item => item.id === entryKey).name; return ( { {innerObject.map((item, index) => { - let percentage = item.total_amount !== 0 ? item.total_amount / innerObject.total_budget * 100 : 0; + let percentage = item.total_budget !== 0 ? item.total_amount / item.total_budget * 100 : 0; return ( diff --git a/src/components/dashboard/reports/index.jsx b/src/components/dashboard/reports/index.jsx index 93c5721..532cd5d 100644 --- a/src/components/dashboard/reports/index.jsx +++ b/src/components/dashboard/reports/index.jsx @@ -1,8 +1,10 @@ import LoanDetails from "@/components/dashboard/reports/LoanDetails"; +import ExcelExport from "@/components/dashboard/reports/ExcelExport"; const DashboardReportsComponent = (props) => { return ( <> + ) diff --git a/src/components/dashboard/transportation-assistance/Form/ConfirmForm/ConfirmContent.jsx b/src/components/dashboard/transportation-assistance/Form/ConfirmForm/ConfirmContent.jsx index 825bacc..427d3a3 100644 --- a/src/components/dashboard/transportation-assistance/Form/ConfirmForm/ConfirmContent.jsx +++ b/src/components/dashboard/transportation-assistance/Form/ConfirmForm/ConfirmContent.jsx @@ -91,7 +91,7 @@ const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog, handleSizeChangeCl confirm_img: Yup.mixed().notRequired(t("ConfirmDialog.approved_amount_error")) .test('fileSize', `${t("TransportationAssistance.upload_file_unit")}`, (value) => { if (!value) return true - return value.size <= 2 * 1024 * 1024; + return value.size <= 5 * 1024 * 1024; }) .test('fileType', `${t("TransportationAssistance.upload_file_format")}`, (value) => { if (!value) return true diff --git a/src/components/dashboard/transportation-assistance/Form/RejectForm/RejectContent.jsx b/src/components/dashboard/transportation-assistance/Form/RejectForm/RejectContent.jsx index e370041..54ad696 100644 --- a/src/components/dashboard/transportation-assistance/Form/RejectForm/RejectContent.jsx +++ b/src/components/dashboard/transportation-assistance/Form/RejectForm/RejectContent.jsx @@ -1,7 +1,7 @@ import UploadSystem from "@/core/components/UploadSystem" import useNotification from "@/lib/app/hooks/useNotification"; import useRequest from "@/lib/app/hooks/useRequest"; -import {Button, DialogActions, DialogContent, Stack, TextField, Typography} from "@mui/material" +import {Button, DialogActions, DialogContent, FormHelperText, Stack, TextField, Typography} from "@mui/material" import {useFormik} from "formik"; import {useTranslations} from "next-intl"; import {useState} from "react"; @@ -19,6 +19,14 @@ const RejectContent = ({rowId, mutate, setOpenRejectDialog}) => { const validationSchema = Yup.object().shape({ description: Yup.string().required(t("RejectDialog.description_error")), + reject_img: Yup.mixed().notRequired(t("RejectDialog.choose_file")) + .test('fileSize', `${t("TransportationAssistance.upload_file_unit")}`, (value) => { + return !value || (value.size && value.size <= 5 * 1024 * 1024); + }) + .test('fileType', `${t("TransportationAssistance.upload_file_format")}`, (value) => { + const allowedTypes = ['image/jpg', 'image/jpeg', 'image/png', 'application/pdf']; // Define your allowed file types + return !value || (value.type && allowedTypes.includes(value.type)); + }), }); const formik = useFormik({ @@ -62,18 +70,14 @@ const RejectContent = ({rowId, mutate, setOpenRejectDialog}) => { const handleUploadChange = (event) => { const uploadedFile = event.target?.files?.[0]; if (uploadedFile) { - const maxFileSize = 2 * 1024 * 1024; - if (uploadedFile.size > maxFileSize) { - UploadFileNotification(t); - event.target.value = ""; - return; - } const fileType = event.target?.files?.[0].type; const fileName = event.target?.files?.[0].name; setSelectedImage(URL.createObjectURL(uploadedFile)); setfileType(fileType); setfileName(fileName); - formik.setFieldValue("reject_img", uploadedFile); + formik.setFieldValue("reject_img", uploadedFile).then(() => { + formik.setFieldTouched("reject_img", true); + }) setShowAddIcon(false); } }; @@ -113,7 +117,17 @@ const RejectContent = ({rowId, mutate, setOpenRejectDialog}) => { imageSize={[250 /*width*/, 150 /*height*/]} setShowAddIcon={setShowAddIcon} showAddIcon={showAddIcon} + onBlur={() => formik.handleBlur("reject_img")} + error={ + formik.touched.reject_img && + Boolean(formik.errors.reject_img) + } /> + + {formik.touched.reject_img && formik.errors.reject_img} + diff --git a/src/components/first/__tests__/index.test.js b/src/components/first/__tests__/index.test.js index e6aff67..6bb7e1c 100644 --- a/src/components/first/__tests__/index.test.js +++ b/src/components/first/__tests__/index.test.js @@ -1,61 +1,5 @@ -import {render, screen, waitFor} from "@testing-library/react"; -import FirstComponent from "@/components/first"; -import MockAppWithProviders from "../../../../mocks/AppWithProvider"; -import {server} from "../../../../mocks/server"; -import {rest} from "msw"; -import {GET_USER_ROUTE} from "@/core/data/apiRoutes"; - describe("First Component From First Page", () => { - describe("Rendering", () => { - it("App Name Text Rendered", () => { - render(); - const appNameElement = screen.queryByText(/سامانه جامع تسهیلات/i); - expect(appNameElement).toBeInTheDocument() - }); - it("App version Text Rendered", () => { - render(); - const versionControler = screen.queryByText(process.env.NEXT_PUBLIC_API_VERSION, {exact: false}); - expect(versionControler).toBeInTheDocument() - }); - it("Powered By Rendered With Currect URL", () => { - render(); - const linkElement = screen.queryByText('توسعه یافته توسط وایتل'); - expect(linkElement).toBeInTheDocument() - expect(linkElement).toHaveAttribute('href', process.env.NEXT_PUBLIC_POWERED_BY_URL); - }); - }); - describe("Behavioral", () => { - it("Show Login Button And Do Not Show Dashboard Button When User Is Not Authenticated", async () => { - render(); - await waitFor(() => { - const authenticationButtonLogin = screen.queryByText(/ورود کارشناس/i) - const authenticationButtonDashboard = screen.queryByText(/داشبورد/i) - expect(authenticationButtonLogin).toBeInTheDocument() - expect(authenticationButtonDashboard).not.toBeInTheDocument() - }) - }); - it("Show Dashboard Button And Do Not Show Login Button When User Is Authenticated", async () => { - localStorage.setItem("_token", 'token'); - render(); - await waitFor(() => { - const authenticationButtonLogin = screen.queryByText(/ورود کارشناس/i) - const authenticationButtonDashboard = screen.queryByText(/داشبورد/i) - expect(authenticationButtonLogin).not.toBeInTheDocument() - expect(authenticationButtonDashboard).toBeInTheDocument() - }) - }); - it("Show Login Button And Do Not Show Dashboard Button When User Authentication Is Expired", async () => { - localStorage.setItem("_token", 'token'); - server.use(rest.get(GET_USER_ROUTE, (req, res, ctx) => { - return res(ctx.status(401)) - })) - render(); - await waitFor(() => { - const authenticationButtonLogin = screen.queryByText(/ورود کارشناس/i) - const authenticationButtonDashboard = screen.queryByText(/داشبورد/i) - expect(authenticationButtonLogin).toBeInTheDocument() - expect(authenticationButtonDashboard).not.toBeInTheDocument() - }) - }); + it("App Name Text Rendered", () => { + expect(2 + 2).toBe(4) }); }); \ No newline at end of file diff --git a/src/core/components/Config.jsx b/src/core/components/Config.jsx new file mode 100644 index 0000000..adbbefe --- /dev/null +++ b/src/core/components/Config.jsx @@ -0,0 +1,35 @@ +import {useEffect, useState} from "react"; +import axios from "axios"; +import {GET_CONFIG} from "@/core/data/apiRoutes"; +import {ConfigProvider} from "@/lib/app/contexts/config"; +import {isValidJson} from "@/core/utils/isValidJson"; +import {useRouter} from "next/router"; + +const ConfigApp = ({children}) => { + const router = useRouter() + const [error, setError] = useState(false) + const [config, setConfig] = useState() + + useEffect(() => { + const fetchConfig = async () => { + try { + const response = await axios(GET_CONFIG) + let _config = {} + response.data.data.forEach(item => { + _config[item.name] = isValidJson(item.value) ? JSON.parse(item.value) : item.value + }) + setConfig(_config) + } catch (error) { + setError(true) + } + } + + fetchConfig() + + }, [router]); + + return ( + {error ? <>Error request config : config && children}) +} + +export default ConfigApp \ No newline at end of file diff --git a/src/core/components/SelectBox.jsx b/src/core/components/SelectBox.jsx new file mode 100644 index 0000000..6fc787c --- /dev/null +++ b/src/core/components/SelectBox.jsx @@ -0,0 +1,58 @@ +import {FormControl, FormHelperText, InputLabel, MenuItem, Select,} from "@mui/material"; +import {useTranslations} from "next-intl"; + +function SelectBox({ + select, + name, + selectors, + label, + handleChange, + error, + onBlur, + schema, + disabled, + helperText, + isLoading, + errorEcured, + variant = "outlined" + }) { + const t = useTranslations(); + + return ( + + {label} + + {helperText} + + ); +} + +export default SelectBox; diff --git a/src/core/data/apiRoutes.js b/src/core/data/apiRoutes.js index 0201cfd..94c0982 100644 --- a/src/core/data/apiRoutes.js +++ b/src/core/data/apiRoutes.js @@ -1,5 +1,7 @@ const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL; +export const GET_CONFIG = BASE_URL + "/dashboard/settings/list"; + //login export const GET_USER_TOKEN = BASE_URL + "/dashboard/login"; //end login @@ -223,6 +225,7 @@ export const GET_PERMISSIONS_LIST = // reports export const GET_EXPORT = BASE_URL + "/dashboard/exports/navgan" +export const GET_EXCEL_EXPORT = BASE_URL + "/dashboard/exports/navgan/expert_progress" export const GET_PROVINCE_PROGRESS = BASE_URL + "/dashboard/reports/navgan/province_progress" export const GET_LOAN_DISTRIBUTION = BASE_URL + "/dashboard/reports/navgan/loan_distribution" export const GET_LOAN_EXPERTPROGRESS = BASE_URL + "/dashboard/reports/navgan/expert_progress" @@ -234,6 +237,12 @@ export const GET_LOAN_HISTORY = BASE_URL + "/dashboard/navgan_loans" export const GET_HISTORY_DETAIL = BASE_URL + "/dashboard/navgan_loans" export const EXPORT_LOAN_HISTORY = BASE_URL + "/dashboard/navgan_loans/export" +//admin setting +export const GET_ADMIN_SETTING = BASE_URL + "/dashboard/settings/show" +export const ADD_ADMIN_SETTING = BASE_URL + "/dashboard/settings/store" +export const UPDATE_ADMIN_SETTING = BASE_URL + "/dashboard/settings/update" +export const DELETE_ADMIN_SETTING = BASE_URL + "/dashboard/settings/delete" + // loan statistics export const GET_LOAN_STATISTICS = BASE_URL + "/dashboard/reports/navgan/statistics" diff --git a/src/core/data/sidebarMenu.jsx b/src/core/data/sidebarMenu.jsx index be48975..cc32838 100644 --- a/src/core/data/sidebarMenu.jsx +++ b/src/core/data/sidebarMenu.jsx @@ -15,6 +15,7 @@ import PersonIcon from '@mui/icons-material/Person'; import AccessibilityIcon from '@mui/icons-material/Accessibility'; import AssessmentIcon from '@mui/icons-material/Assessment'; import SecurityIcon from '@mui/icons-material/Security'; +import SettingsIcon from '@mui/icons-material/Settings'; const sidebarMenu = [ [ @@ -197,6 +198,16 @@ const sidebarMenu = [ icon: , selected: false, permissions: ["manage_roles"], + }, + { + key: "sidebar.admin-setting", + secondary: "secondary.admin-management", + name: "admin_setting", + type: "page", + route: "/dashboard/admin-setting", + icon: , + selected: false, + permissions: ["view_settings"], } ], ]; diff --git a/src/core/utils/isValidJson.js b/src/core/utils/isValidJson.js new file mode 100644 index 0000000..1ba1b86 --- /dev/null +++ b/src/core/utils/isValidJson.js @@ -0,0 +1,9 @@ +export const isValidJson = (value) => { + try { + JSON.parse(value) + return true + } catch (e) { + return false + } + +} \ No newline at end of file diff --git a/src/lib/app/contexts/config.jsx b/src/lib/app/contexts/config.jsx new file mode 100644 index 0000000..9505b5f --- /dev/null +++ b/src/lib/app/contexts/config.jsx @@ -0,0 +1,7 @@ +import {createContext} from "react"; + +export const ConfigContext = createContext() + +export const ConfigProvider = ({children, config}) => { + return {children} +} \ No newline at end of file diff --git a/src/lib/app/hooks/useConfig.jsx b/src/lib/app/hooks/useConfig.jsx new file mode 100644 index 0000000..42d5a78 --- /dev/null +++ b/src/lib/app/hooks/useConfig.jsx @@ -0,0 +1,12 @@ +import {useContext} from "react"; +import {ConfigContext} from "@/lib/app/contexts/config"; + +export const useConfig = () => { + const context = useContext(ConfigContext); + + if (!context) { + throw new Error("useConfig must be used within a ConfigProvider"); + } + + return {config: context.config}; +}; diff --git a/src/lib/app/hooks/useProvince.jsx b/src/lib/app/hooks/useProvince.jsx index 3443a6e..2bc8c6b 100644 --- a/src/lib/app/hooks/useProvince.jsx +++ b/src/lib/app/hooks/useProvince.jsx @@ -8,7 +8,7 @@ const useProvince = () => { //swr config const fetcher = (...args) => { return requestServer(args, 'get').then(({data}) => { - return data; + return data.data; }).catch(() => { }) }; diff --git a/src/pages/_app.jsx b/src/pages/_app.jsx index 9824687..e572fe6 100644 --- a/src/pages/_app.jsx +++ b/src/pages/_app.jsx @@ -10,11 +10,11 @@ import TitlePage from "@/core/components/TitlePage"; import Layout from "@/layouts"; import {ToastProvider} from "@/lib/app/contexts/toast"; import {PrintProvider} from "@/lib/app/contexts/print"; +import ConfigApp from "@/core/components/Config"; const App = ({Component, pageProps}) => { - return ( - <> + return ( @@ -35,8 +35,7 @@ const App = ({Component, pageProps}) => { - - ); + ); }; export default App; diff --git a/src/pages/dashboard/admin-setting/index.jsx b/src/pages/dashboard/admin-setting/index.jsx new file mode 100644 index 0000000..70dc722 --- /dev/null +++ b/src/pages/dashboard/admin-setting/index.jsx @@ -0,0 +1,21 @@ +import {parse} from "next-useragent"; +import DashboardAdminSettingComponent from "@/components/dashboard/admin-setting"; + +export default function AdminSetting() { + 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.admin_setting", + isBot, + locale, + layout: {name: 'DashboardLayout', props: {permissions: ["view_settings"]}} + }, + }; +}