diff --git a/public/locales/fa/app.json b/public/locales/fa/app.json index 0e83399..3194638 100644 --- a/public/locales/fa/app.json +++ b/public/locales/fa/app.json @@ -32,6 +32,7 @@ "passenger-boss": "رئیس مسافر ", "transportation-assistant": "معاونت حمل و نقل", "province-manager": "مدیر کل استانی", + "inspector-expert": "بازدید کارشناس", "change-password": "تغییر رمز عبور", "edit-profile": "ویرایش پروفایل" }, @@ -77,6 +78,7 @@ "province_manager_page": "مدیر کل استانی", "passenger_office_page": "اداره مسافر", "machinary_office_page": "ماشین آلات", + "inspector_expert_page": "بازدید کارشناس", "edit_profile": "ویرایش پروفایل" }, "MuiDatePicker": { @@ -162,6 +164,17 @@ "vehicle_type": "نوع ماشین", "state_name": "وضعیت درخواست" }, + "InspectorExpert": { + "name": "نام", + "id": "کد یکتا", + "national_id": "کد ملی", + "phone_number": "موبایل", + "created_at": "تاریخ درخواست", + "updated_at": "تاریخ بروزرسانی", + "navgan_id": "کد ناوگان", + "vehicle_type": "نوع ماشین", + "state_name": "وضعیت درخواست" + }, "MachinaryOffice": { "name": "نام", "id": "کد یکتا", diff --git a/src/components/dashboard/inspector-expert/Form/ConfirmForm.jsx b/src/components/dashboard/inspector-expert/Form/ConfirmForm.jsx new file mode 100644 index 0000000..7343ea5 --- /dev/null +++ b/src/components/dashboard/inspector-expert/Form/ConfirmForm.jsx @@ -0,0 +1,137 @@ +import {useTranslations} from "next-intl"; +import {useState} from "react"; +import { + Button, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + IconButton, + Stack, + TextField, + Tooltip, + Typography +} from "@mui/material"; +import ThumbUpAltIcon from "@mui/icons-material/ThumbUpAlt"; +import UploadSystem from "@/core/components/UploadSystem"; +import {useFormik} from "formik"; +import UploadFileNotification from "@/core/components/notifications/UploadFileNotification"; +import {CONFIRM_INSPECTOR_EXPERT} from "@/core/data/apiRoutes"; +import useRequest from "@/lib/app/hooks/useRequest"; + +const Confirm = ({rowId, fetchUrl, mutate}) => { + const t = useTranslations(); + const [selectedImage, setSelectedImage] = useState(""); + const [fileType, setfileType] = useState(null); + const [fileName, setfileName] = useState(null); + const [showAddIcon, setShowAddIcon] = useState(true); + const [openConfirmDialog, setOpenConfirmDialog] = useState(false); + const requestServer = useRequest({auth: true}) + + const formik = useFormik({ + initialValues: { + description: "", + confirm_img: null + }, + onSubmit: (values, {setSubmitting}) => { + const formData = new FormData(); + if (values.description != "") formData.append("expert_description", values.description); + if (values.confirm_img != null) formData.append("attachment", values.confirm_img); + + requestServer(`${CONFIRM_INSPECTOR_EXPERT}/${rowId}`, 'post', { + data: formData, + }).then((response) => { + mutate(fetchUrl) + }).catch(() => { + }).finally(() => { + setSubmitting(false); + }); + }, + }); + + const handleDescriptionChange = (event) => { + formik.setFieldValue("description", event.target.value) + }; + 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("confirm_img", uploadedFile); + setShowAddIcon(false); + } + }; + return ( + <> + + { + setOpenConfirmDialog(true) + }} + > + + + + + {t("ConfirmDialog.confirm")} + + + + + {t("ConfirmDialog.description")}{t("ConfirmDialog.optional")}} + value={formik.values.description} + onChange={handleDescriptionChange} + fullWidth + variant="outlined" + sx={{mt: 1}} + /> + + + {t("UploadSystem.upload_file")}{t("UploadSystem.optional")} + + + + + + + + + + + ); +}; +export default Confirm; \ No newline at end of file diff --git a/src/components/dashboard/inspector-expert/Form/RejectForm.jsx b/src/components/dashboard/inspector-expert/Form/RejectForm.jsx new file mode 100644 index 0000000..8719749 --- /dev/null +++ b/src/components/dashboard/inspector-expert/Form/RejectForm.jsx @@ -0,0 +1,144 @@ +import {REJECT_INSPECTOR_EXPERT} from "@/core/data/apiRoutes"; +import { + Button, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + IconButton, + Stack, + TextField, + Tooltip, + Typography, +} from "@mui/material"; +import {useTranslations} from "next-intl"; +import {useFormik} from "formik"; +import * as Yup from "yup"; +import {useState} from "react"; +import UploadSystem from "@/core/components/UploadSystem"; +import UploadFileNotification from "@/core/components/notifications/UploadFileNotification"; +import useDirection from "@/lib/app/hooks/useDirection"; +import ThumbDownIcon from "@mui/icons-material/ThumbDown"; +import useRequest from "@/lib/app/hooks/useRequest"; + +const Reject = ({rowId, fetchUrl, mutate}) => { + const [selectedImage, setSelectedImage] = useState(""); + const [fileType, setfileType] = useState(null); + const [fileName, setfileName] = useState(null); + const [showAddIcon, setShowAddIcon] = useState(true); + const t = useTranslations(); + const {directionApp} = useDirection(); + const [openRejectDialog, setOpenRejectDialog] = useState(false); + const requestServer = useRequest({auth: true}) + + const validationSchema = Yup.object().shape({ + description: Yup.string().required(t("RejectDialog.description_error")), + }); + + const formik = useFormik({ + initialValues: { + description: "", + reject_img: null + }, + validationSchema, + onSubmit: (values, {setSubmitting}) => { + const formData = new FormData(); + formData.append("expert_description", values.description); + if (values.reject_img != null) formData.append("attachment", values.reject_img); + + requestServer(`${REJECT_INSPECTOR_EXPERT}/${rowId}`, 'post', { + data: formData, + }).then((response) => { + mutate(fetchUrl) + }).catch(() => { + }).finally(() => { + setSubmitting(false); + }); + }, + }); + + const handleDescriptionChange = (event) => { + formik.setFieldValue("description", event.target.value); + formik.handleChange(event); + }; + 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); + setShowAddIcon(false); + } + }; + return ( + <> + + setOpenRejectDialog(true)}> + + + + + {t("RejectDialog.reject")} + + + + + + + {t("UploadSystem.upload_file")}{t("UploadSystem.optional")} + + + + + + + + + + + ); +}; +export default Reject; \ No newline at end of file diff --git a/src/components/dashboard/inspector-expert/Form/ReviseForm.jsx b/src/components/dashboard/inspector-expert/Form/ReviseForm.jsx new file mode 100644 index 0000000..3093a5e --- /dev/null +++ b/src/components/dashboard/inspector-expert/Form/ReviseForm.jsx @@ -0,0 +1,142 @@ +import {REVISE_INSPECTOR_EXPERT} from "@/core/data/apiRoutes"; +import { + Button, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + IconButton, + Stack, + TextField, + Tooltip, + Typography, +} from "@mui/material"; +import {useTranslations} from "next-intl"; +import {useFormik} from "formik"; +import * as Yup from "yup"; +import {useState} from "react"; +import UploadSystem from "@/core/components/UploadSystem"; +import UploadFileNotification from "@/core/components/notifications/UploadFileNotification"; +import ThumbDownIcon from "@mui/icons-material/ThumbDown"; +import useRequest from "@/lib/app/hooks/useRequest"; + +const Revise = ({rowId, fetchUrl, mutate}) => { + const [selectedImage, setSelectedImage] = useState(""); + const [fileType, setfileType] = useState(null); + const [fileName, setfileName] = useState(null); + const [showAddIcon, setShowAddIcon] = useState(true); + const t = useTranslations(); + const [openRejectDialog, setOpenRejectDialog] = useState(false); + const requestServer = useRequest({auth: true}) + + const validationSchema = Yup.object().shape({ + description: Yup.string().required(t("RejectDialog.description_error")), + }); + + const formik = useFormik({ + initialValues: { + description: "", + reject_img: null + }, + validationSchema, + onSubmit: (values, {setSubmitting}) => { + const formData = new FormData(); + formData.append("expert_description", values.description); + if (values.reject_img != null) formData.append("attachment", values.reject_img); + + requestServer(`${REVISE_INSPECTOR_EXPERT}/${rowId}`, 'post', { + data: formData, + }).then((response) => { + mutate(fetchUrl) + }).catch(() => { + }).finally(() => { + setSubmitting(false); + }); + }, + }); + + const handleDescriptionChange = (event) => { + formik.setFieldValue("description", event.target.value); + formik.handleChange(event); + }; + 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); + setShowAddIcon(false); + } + }; + return ( + <> + + setOpenRejectDialog(true)}> + + + + + {t("RejectDialog.reject")} + + + + + + + {t("UploadSystem.upload_file")}{t("UploadSystem.optional")} + + + + + + + + + + + ); +}; +export default Revise; \ No newline at end of file diff --git a/src/components/dashboard/inspector-expert/TableRowActions.jsx b/src/components/dashboard/inspector-expert/TableRowActions.jsx new file mode 100644 index 0000000..f76c2d2 --- /dev/null +++ b/src/components/dashboard/inspector-expert/TableRowActions.jsx @@ -0,0 +1,29 @@ +import {Box} from "@mui/material"; +import ConfirmForm from "./Form/ConfirmForm" +import RejectForm from "./Form/RejectForm" +import ReviseForm from "./Form/ReviseForm" + +const TableRow = ({row, mutate, fetchUrl}) => { + + return ( + + + + + + ); +}; + +export default TableRow; diff --git a/src/components/dashboard/inspector-expert/TableTollbar.jsx b/src/components/dashboard/inspector-expert/TableTollbar.jsx new file mode 100644 index 0000000..e69de29 diff --git a/src/components/dashboard/inspector-expert/index.jsx b/src/components/dashboard/inspector-expert/index.jsx new file mode 100644 index 0000000..3a1027b --- /dev/null +++ b/src/components/dashboard/inspector-expert/index.jsx @@ -0,0 +1,236 @@ +import DashboardLayouts from "@/layouts/dashboardLayouts"; +import {Box, IconButton, Typography} from "@mui/material"; +import ClearIcon from "@mui/icons-material/Clear"; +import {useMemo} from "react"; +import {GET_INSPECTOR_EXPERT} from "@/core/data/apiRoutes"; +import {useTranslations} from "next-intl"; +import TableRowActions from "./TableRowActions"; +import {LocalizationProvider, MobileDateTimePicker,} from "@mui/x-date-pickers"; +import {AdapterDateFnsJalali} from "@mui/x-date-pickers/AdapterDateFnsJalali"; +import moment from "jalali-moment"; +import {faIR} from "@mui/x-date-pickers/locales"; +import DataTable from "@/core/components/DataTable"; + +function DashboardInspectorExpertComponent() { + const t = useTranslations(); + const columns = useMemo( + () => [ + { + accessorFn: (row) => row.id, + id: "id", + header: t("InspectorExpert.id"), + enableColumnFilter: true, + datatype: "numeric", + filterFn: "equals", + columnFilterModeOptions: [ + "equals", + "notEquals", + "contains", + "lessThan", + "greaterThan", + "between", + ], + Cell: ({renderedCellValue}) => ( + {renderedCellValue} + ), + }, + { + accessorFn: (row) => row.name, + id: "name", + header: t("InspectorExpert.name"), + enableColumnFilter: true, + datatype: "text", + filterFn: "contains", + columnFilterModeOptions: ["contains", "equals", "notEquals"], + Cell: ({renderedCellValue}) => ( + {renderedCellValue} + ), + }, + { + accessorFn: (row) => row.national_id, + id: "national_id", + header: t("InspectorExpert.national_id"), + enableColumnFilter: true, + datatype: "numeric", + filterFn: "equals", + columnFilterModeOptions: [ + "equals", + "notEquals", + "contains", + "lessThan", + "greaterThan", + "between", + ], + Cell: ({renderedCellValue}) => ( + {renderedCellValue} + ), + }, + { + accessorFn: (row) => row.phone_number, + id: "phone_number", + header: t("InspectorExpert.phone_number"), + enableColumnFilter: true, + datatype: "numeric", + filterFn: "equals", + columnFilterModeOptions: [ + "equals", + "notEquals", + "contains", + "lessThan", + "greaterThan", + "between", + ], + Cell: ({renderedCellValue}) => ( + {renderedCellValue} + ), + }, + { + accessorFn: (row) => + moment(row.created_at).locale("fa").format("HH:mm | yyyy/MM/DD"), + id: "created_at", + header: t("InspectorExpert.created_at"), + enableColumnFilter: true, + datatype: "date", + filterFn: "lessThan", + columnFilterModeOptions: ["lessThan", "greaterThan"], + Cell: ({renderedCellValue}) => { + return {renderedCellValue}; + }, + Header: ({column}) => {column.columnDef.header}, + Filter: ({column}) => { + const filterFnValue = column.columnDef._filterFn; + return ( + + + { + const date = new Date(newValue); + const formattedDate = moment(date) + .locale("en") + .format("YYYY-MM-DD HH:mm"); + column.setFilterValue(formattedDate); + }} + slotProps={{ + textField: { + placeholder: "تاریخ خود را وارد کنید", + helperText: `${t("filter_mode")}: ${t(filterFnValue)}`, + sx: {minWidth: "120px"}, + variant: "standard", + }, + }} + value={ + column.getFilterValue() + ? new Date(column.getFilterValue()) + : null + } + /> + + { + column.setFilterValue(null); + }} + sx={{ + color: column.getFilterValue() + ? "rgba(0, 0, 0, 0.54)" + : "#bfbfbf", + }} + > + + + + ); + }, + }, + { + accessorFn: (row) => + moment(row.updated_at).locale("fa").format("HH:mm | yyyy/MM/DD"), + id: "updated_at", + header: t("InspectorExpert.updated_at"), + enableColumnFilter: false, + datatype: "numeric", + Cell: ({renderedCellValue}) => ( + {renderedCellValue} + ), + }, + // { + // accessorFn: (row) => row.navgan_id, + // id: "navgan_id", + // header: t("InspectorExpert.navgan_id"), + // enableColumnFilter: true, + // datatype: "numeric", + // filterFn: "equals", + // columnFilterModeOptions: [ + // "equals", + // "notEquals", + // "contains", + // "lessThan", + // "greaterThan", + // "between", + // ], + // Cell: ({renderedCellValue}) => ( + // {renderedCellValue} + // ), + // }, + // { + // accessorFn: (row) => row.vehicle_type, + // id: "vehicle_type", + // header: t("InspectorExpert.vehicle_type"), + // enableColumnFilter: true, + // datatype: "text", + // filterFn: "contains", + // columnFilterModeOptions: ["contains", "equals", "notEquals"], + // Cell: ({renderedCellValue}) => ( + // {renderedCellValue} + // ), + // }, + { + accessorFn: (row) => row.state_name, + id: "state_id", + header: t("InspectorExpert.state_name"), + enableColumnFilter: false, + datatype: "numeric", + // filterFn: "equals", + // filterSelectOptions: [ + // ], + // filterVariant: "select", + Cell: ({renderedCellValue}) => ( + {renderedCellValue} + ), + }, + ], + [] + ); + return ( + + + + + + ); +} + +export default DashboardInspectorExpertComponent; diff --git a/src/core/data/apiRoutes.js b/src/core/data/apiRoutes.js index 36bff5e..089f62f 100644 --- a/src/core/data/apiRoutes.js +++ b/src/core/data/apiRoutes.js @@ -66,3 +66,17 @@ export const CONFIRM_PROVINCE_MANAGER = export const REJECT_PROVINCE_MANAGER = BASE_URL + "/dashboard/province_manager/reject"; //province manager + +// inspector expert +export const GET_INSPECTOR_EXPERT = + BASE_URL + "/dashboard/inspector_expert/show"; + +export const CONFIRM_INSPECTOR_EXPERT = + BASE_URL + "/dashboard/inspector_expert/confirm"; + +export const REJECT_INSPECTOR_EXPERT = + BASE_URL + "/dashboard/inspector_expert/reject"; + +export const REVISE_INSPECTOR_EXPERT = + BASE_URL + "/dashboard/inspector_expert/revise" +//inspector expert diff --git a/src/core/data/sidebarMenu.jsx b/src/core/data/sidebarMenu.jsx index f380085..5a9094e 100644 --- a/src/core/data/sidebarMenu.jsx +++ b/src/core/data/sidebarMenu.jsx @@ -57,6 +57,14 @@ const sidebarMenu = [ selected: false, role: "province_manager", }, + { + key: "sidebar.inspector-expert", + type: "page", + route: "/dashboard/inspector-expert", + icon: , + selected: false, + role: "inspector_expert", + }, ], ]; diff --git a/src/pages/dashboard/inspector-expert/index.jsx b/src/pages/dashboard/inspector-expert/index.jsx new file mode 100644 index 0000000..e47e596 --- /dev/null +++ b/src/pages/dashboard/inspector-expert/index.jsx @@ -0,0 +1,26 @@ +import RolePermissionMiddleware from "@/middlewares/RolePermission"; +import WithAuthMiddleware from "@/middlewares/WithAuth"; +import {parse} from "next-useragent"; +import DashboardInspectorExpertComponent from "@/components/dashboard/inspector-expert"; + +const requiredPermissions = ["inspector_expert"]; +export default function PassengerOffice() { + 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.inspector_expert_page", + isBot, + }, + }; +}