diff --git a/.env.local.example b/.env.local.example index 7ea3878..94a14f9 100644 --- a/.env.local.example +++ b/.env.local.example @@ -1,4 +1,4 @@ HOST="rms.witel.ir" -NEXT_PUBLIC_VERSION="0.15.2" +NEXT_PUBLIC_VERSION="0.17.0" NEXT_PUBLIC_API_URL="https://rms.witel.ir" NEXT_PUBLIC_MAPTILE_ENDPOINT="https://rmsmap.rmto.ir/141map" \ No newline at end of file diff --git a/package.json b/package.json index c5b2139..45edef7 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,6 @@ "date-fns-jalali": "^2.13.0-0", "dayjs": "^1.11.10", "file-saver": "^2.0.5", - "formik": "^2.4.5", "jalali-moment": "^3.3.11", "lz-string": "^1.5.0", "material-react-table": "^2.11.2", @@ -36,6 +35,7 @@ "react-hook-form": "^7.52.1", "react-leaflet": "^4.2.1", "react-leaflet-draw": "^0.20.4", + "react-leaflet-markercluster": "^4.2.1", "react-toastify": "^10.0.5", "sass": "^1.75.0", "sharp": "^0.33.4", @@ -45,7 +45,6 @@ "yup": "^1.4.0" }, "devDependencies": { - "@types/node": "22.10.1", "eslint": "^8", "eslint-config-next": "14.1.0", "eslint-config-prettier": "^9.1.0", diff --git a/src/app/(withAuth)/dashboard/damage-items/page.js b/src/app/(withAuth)/dashboard/damage-items/page.js new file mode 100644 index 0000000..1a222ae --- /dev/null +++ b/src/app/(withAuth)/dashboard/damage-items/page.js @@ -0,0 +1,11 @@ +import DamagesPage from "@/components/dashboard/damageItems"; +import WithPermission from "@/core/middlewares/withPermission"; + +const Page = () => { + return ( + + + + ); +}; +export default Page; diff --git a/src/app/(withAuth)/dashboard/damages/operator/page.js b/src/app/(withAuth)/dashboard/damages/operator/page.js new file mode 100644 index 0000000..8715b11 --- /dev/null +++ b/src/app/(withAuth)/dashboard/damages/operator/page.js @@ -0,0 +1,13 @@ +import WithPermission from "@/core/middlewares/withPermission"; +import ActivityCodeLog from "@/core/components/ActivityCodeLog"; +import OperatorPage from "@/components/dashboard/damages/operator"; +const Page = () => { + return ( + + + {/* change permission */} + + ); +}; + +export default Page; diff --git a/src/app/(withAuth)/dashboard/damages/report/page.js b/src/app/(withAuth)/dashboard/damages/report/page.js new file mode 100644 index 0000000..d6bfd78 --- /dev/null +++ b/src/app/(withAuth)/dashboard/damages/report/page.js @@ -0,0 +1,11 @@ +import DamagesReportPage from "@/components/dashboard/damages/report"; +import WithPermission from "@/core/middlewares/withPermission"; + +const Page = () => { + return ( + + + + ); +}; +export default Page; diff --git a/src/app/(withAuth)/dashboard/fast-react/operator/page.js b/src/app/(withAuth)/dashboard/fast-react/operator/page.js new file mode 100644 index 0000000..89fe922 --- /dev/null +++ b/src/app/(withAuth)/dashboard/fast-react/operator/page.js @@ -0,0 +1,12 @@ +import OperatorPage from "@/components/dashboard/fastReact/operator"; +import WithPermission from "@/core/middlewares/withPermission"; +const Page = () => { + return ( + + + {/* */} + + ); +}; + +export default Page; diff --git a/src/app/(withAuth)/dashboard/fast-react/supervisor/page.js b/src/app/(withAuth)/dashboard/fast-react/supervisor/page.js new file mode 100644 index 0000000..820c1af --- /dev/null +++ b/src/app/(withAuth)/dashboard/fast-react/supervisor/page.js @@ -0,0 +1,13 @@ +import WithPermission from "@/core/middlewares/withPermission"; +import SupervisorPage from "@/components/dashboard/fastReact/supervisor"; +import ActivityCodeLog from "@/core/components/ActivityCodeLog"; +const Page = () => { + return ( + + + + + ); +}; + +export default Page; diff --git a/src/app/(withAuth)/dashboard/road-items/reports/map/page.js b/src/app/(withAuth)/dashboard/road-items/reports/map/page.js new file mode 100644 index 0000000..00f000d --- /dev/null +++ b/src/app/(withAuth)/dashboard/road-items/reports/map/page.js @@ -0,0 +1,6 @@ +import RoadItemsReportMap from "@/components/dashboard/roadItems/reports/Map"; + +const Page = () => { + return ; +}; +export default Page; diff --git a/src/assets/scss/global.scss b/src/assets/scss/global.scss index 7b73e7e..1fc9f82 100644 --- a/src/assets/scss/global.scss +++ b/src/assets/scss/global.scss @@ -1,6 +1,7 @@ @import "fontiran"; @import "leaflet/dist/leaflet.css"; @import "react-toastify/dist/ReactToastify.css"; +@import "./map-styles.scss"; .filter-toast { box-shadow: diff --git a/src/assets/scss/map-styles.scss b/src/assets/scss/map-styles.scss index 44e6651..595f664 100644 --- a/src/assets/scss/map-styles.scss +++ b/src/assets/scss/map-styles.scss @@ -1,27 +1,9 @@ -.leaflet-draw-tooltip { - background-color: #4f4f4f; - font-family: IRANSans; +.custom-marker-cluster { + font-family: IRANSansFaNum; + background-color: rgba(51, 181, 229, 0.6); color: #fff; - font-size: 10px; - padding: 2px 8px; - margin-right: 15px; - border-radius: 4px; - box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.1); -} - -.leaflet-draw-tooltip:before { - content: ""; - position: absolute; - bottom: 100%; - left: 103%; - transform: rotate(90deg); - margin-left: -6px; - border-width: 6px; - border-style: solid; - border-color: transparent transparent #333 transparent; -} - -.leaflet-draw-tooltip-subtext { - font-size: 12px; - color: #ffeb3b; + border-radius: 50%; + text-align: center; + font-size: 14px; + line-height: 40px; } diff --git a/src/components/dashboard/azmayesh/Forms/CandUAzmayesh/index.jsx b/src/components/dashboard/azmayesh/Forms/CandUAzmayesh/index.jsx index 960946d..8ce5b88 100644 --- a/src/components/dashboard/azmayesh/Forms/CandUAzmayesh/index.jsx +++ b/src/components/dashboard/azmayesh/Forms/CandUAzmayesh/index.jsx @@ -1,13 +1,27 @@ "use client"; -import { Dialog } from "@mui/material"; +import { Dialog, IconButton } from "@mui/material"; import FormAndRequest from "./FormAndRequest"; import DialogLoading from "@/core/components/DialogLoading"; +import CloseIcon from "@mui/icons-material/Close"; const CandUAzmayesh = ({ open, setOpen, mutate, updateInfo, rowId, loadingOpen, fromUpdate = false }) => { const isUpdateReady = fromUpdate ? updateInfo !== null : true; return ( + setOpen(false)} + sx={(theme) => ({ + position: "absolute", + right: 8, + top: 8, + zIndex: 50, + color: theme.palette.grey[500], + })} + > + + {isUpdateReady ? ( { const isUpdateReady = fromUpdate ? updateInfo !== null : true; return ( + setOpen(false)} + sx={(theme) => ({ + position: "absolute", + right: 8, + top: 8, + zIndex: 50, + color: theme.palette.grey[500], + })} + > + + {isUpdateReady ? ( { + const requestServer = useRequest({ notificationSuccess: true }); + const defaultValues = { + title: "", + unit: "", + base_price: null, + }; + + const { + control, + handleSubmit, + formState: { isSubmitting }, + } = useForm({ + defaultValues, + resolver: yupResolver(validationSchema), + mode: "onBlur", + }); + + const onSubmit = async (data) => { + const formData = new FormData(); + formData.append("title", data.title); + formData.append("unit", data.unit); + formData.append("base_price", data.base_price); + + try { + await requestServer(CREATE_RECEIPT_DAMAGE_ITEMS, "post", { + data: formData, + }); + mutate(); + setOpen(false); + } catch (error) {} + }; + + return ( + <> + + ثبت آیتم جدید + + + + + ( + + )} + /> + ( + + )} + /> + ( + { + if (!isNaN(event.target.value)) { + onChange(event.target.value); + } else { + onChange(value); + } + }} + helperText={error ? error.message : null} + variant="outlined" + fullWidth + /> + )} + /> + + + + + + + + + ); +}; +export default CreateFormContent; diff --git a/src/components/dashboard/damageItems/Actions/Create/Form/index.jsx b/src/components/dashboard/damageItems/Actions/Create/Form/index.jsx new file mode 100644 index 0000000..4d04509 --- /dev/null +++ b/src/components/dashboard/damageItems/Actions/Create/Form/index.jsx @@ -0,0 +1,25 @@ +import { Dialog, IconButton } from "@mui/material"; +import CreateFormContent from "./CreateFormContent"; +import CloseIcon from "@mui/icons-material/Close"; + +const CreateForm = ({ open, setOpen, mutate }) => { + return ( + + setOpen(false)} + sx={(theme) => ({ + position: "absolute", + right: 8, + top: 8, + zIndex: 50, + color: theme.palette.grey[500], + })} + > + + + {open && } + + ); +}; +export default CreateForm; diff --git a/src/components/dashboard/damageItems/Actions/Create/index.jsx b/src/components/dashboard/damageItems/Actions/Create/index.jsx new file mode 100644 index 0000000..a720b22 --- /dev/null +++ b/src/components/dashboard/damageItems/Actions/Create/index.jsx @@ -0,0 +1,36 @@ +import { AddCircle } from "@mui/icons-material"; +import { Button, IconButton, useMediaQuery, useTheme } from "@mui/material"; +import CreateForm from "./Form"; +import { useState } from "react"; + +const CreateDamage = ({ mutate }) => { + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("sm")); + const [open, setOpen] = useState(false); + + const handleOpen = () => { + setOpen(true); + }; + + return ( + <> + {isMobile ? ( + + + + ) : ( + + )} + + + ); +}; +export default CreateDamage; diff --git a/src/components/dashboard/damageItems/DamagesList.jsx b/src/components/dashboard/damageItems/DamagesList.jsx new file mode 100644 index 0000000..e93d216 --- /dev/null +++ b/src/components/dashboard/damageItems/DamagesList.jsx @@ -0,0 +1,84 @@ +"use client"; +import DataTableWithAuth from "@/core/components/DataTableWithAuth"; +import { GET_RECEIPT_DAMAGE_ITEMS_LIST } from "@/core/utils/routes"; +import { Box } from "@mui/material"; +import { useMemo } from "react"; +import Toolbar from "./Toolbar"; +import RowActions from "./RowActions"; + +const DamagesList = () => { + const columns = useMemo( + () => [ + { + accessorKey: "id", + header: "کد یکتا", + id: "id", + enableColumnFilter: true, + datatype: "text", + filterMode: "equals", + sortDescFirst: false, + columnFilterModeOptions: ["equals", "contains"], + grow: false, + size: 100, + }, + { + accessorKey: "title", + header: "عنوان", + id: "title", + enableColumnFilter: true, + datatype: "text", + filterMode: "equals", + columnFilterModeOptions: ["equals", "contains"], + grow: false, + size: 100, + }, + { + accessorKey: "unit", + header: "واحد", + id: "unit", + enableColumnFilter: false, + grow: false, + size: 100, + }, + { + accessorKey: "base_price", + header: "فی (ریال)", + id: "base_price", + enableColumnFilter: false, + grow: false, + size: 100, + Cell: ({ renderedCellValue }) => (renderedCellValue / 1).toLocaleString(), + }, + { + accessorKey: "status", + header: "وضعیت", + id: "status", + enableColumnFilter: false, + grow: false, + size: 100, + Cell: ({ renderedCellValue }) => (renderedCellValue == 1 ? "فعال" : "غیرفعال"), + }, + ], + [] + ); + + return ( + <> + + + + + ); +}; +export default DamagesList; diff --git a/src/components/dashboard/damageItems/RowActions/Activity/index.jsx b/src/components/dashboard/damageItems/RowActions/Activity/index.jsx new file mode 100644 index 0000000..3683899 --- /dev/null +++ b/src/components/dashboard/damageItems/RowActions/Activity/index.jsx @@ -0,0 +1,31 @@ +import { ATIVITY_RECEIPT_DAMAGE_ITEMS } from "@/core/utils/routes"; +import useRequest from "@/lib/hooks/useRequest"; +import { Switch } from "@mui/material"; +import { useState } from "react"; + +const Activity = ({ rowId, mutate, status }) => { + const [disabled, setDisabled] = useState(false); + const [checked, setChecked] = useState(status == 1); + const requestServer = useRequest(); + + const handleSwitch = (event) => { + const newChecked = event.target.checked; + setChecked(newChecked); + setDisabled(true); + + requestServer(`${ATIVITY_RECEIPT_DAMAGE_ITEMS}/${rowId}`, "post") + .then(() => { + mutate(); + }) + .catch(() => { + setChecked(!newChecked); + }) + .finally(() => { + setDisabled(false); + }); + }; + + return ; +}; + +export default Activity; diff --git a/src/components/dashboard/damageItems/RowActions/Edit/Form/index.jsx b/src/components/dashboard/damageItems/RowActions/Edit/Form/index.jsx new file mode 100644 index 0000000..30965fe --- /dev/null +++ b/src/components/dashboard/damageItems/RowActions/Edit/Form/index.jsx @@ -0,0 +1,145 @@ +import NumberField from "@/core/components/NumberField"; +import StyledForm from "@/core/components/StyledForm"; +import { UPDATE_RECEIPT_DAMAGE_ITEMS } from "@/core/utils/routes"; +import useRequest from "@/lib/hooks/useRequest"; +import { yupResolver } from "@hookform/resolvers/yup"; +import { Beenhere, ExitToApp } from "@mui/icons-material"; +import { Button, DialogActions, DialogContent, DialogTitle, Stack, TextField } from "@mui/material"; +import { DialogHeader } from "next/dist/client/components/react-dev-overlay/internal/components/Dialog"; +import { Controller, useForm } from "react-hook-form"; +import { object, string } from "yup"; + +const validationSchema = object({ + title: string().required("وارد کردن عنوان الزامیست!"), + unit: string().required("وارد کردن واحد الزامیست!"), + base_price: string().required("وارد کردن فی الزامیست!"), +}); + +const EditFormContent = ({ row, rowId, mutate, setOpen }) => { + const requestServer = useRequest({ notificationSuccess: true }); + const defaultValues = { + title: row.original.title, + unit: row.original.unit, + base_price: row.original.base_price, + }; + + const { + control, + handleSubmit, + formState: { isSubmitting }, + } = useForm({ + defaultValues, + resolver: yupResolver(validationSchema), + mode: "onBlur", + }); + + const onSubmit = async (data) => { + const formData = new FormData(); + formData.append("title", data.title); + formData.append("unit", data.unit); + formData.append("base_price", data.base_price); + + try { + await requestServer(`${UPDATE_RECEIPT_DAMAGE_ITEMS}/${rowId}`, "post", { + data: formData, + }); + mutate(); + setOpen(false); + } catch (error) {} + }; + + return ( + <> + + ویرایش آیتم + + + + + ( + + )} + /> + ( + + )} + /> + ( + { + if (!isNaN(event.target.value)) { + onChange(event.target.value); + } else { + onChange(value); + } + }} + helperText={error ? error.message : null} + variant="outlined" + fullWidth + /> + )} + /> + + + + + + + + + ); +}; +export default EditFormContent; diff --git a/src/components/dashboard/damageItems/RowActions/Edit/index.jsx b/src/components/dashboard/damageItems/RowActions/Edit/index.jsx new file mode 100644 index 0000000..39447a8 --- /dev/null +++ b/src/components/dashboard/damageItems/RowActions/Edit/index.jsx @@ -0,0 +1,44 @@ +import BorderColorIcon from "@mui/icons-material/BorderColor"; +import { Dialog, IconButton, Tooltip } from "@mui/material"; +import EditFormContent from "./Form"; +import { useState } from "react"; +import CloseIcon from "@mui/icons-material/Close"; + +const Edit = ({ row, mutate, rowId }) => { + const [openEditDialog, setOpenEditDialog] = useState(false); + + return ( + <> + + { + setOpenEditDialog(true); + }} + > + + + + + setOpenEditDialog(false)} + sx={(theme) => ({ + position: "absolute", + right: 8, + top: 8, + zIndex: 50, + color: theme.palette.grey[500], + })} + > + + + {openEditDialog && ( + + )} + + + ); +}; +export default Edit; diff --git a/src/components/dashboard/damageItems/RowActions/index.jsx b/src/components/dashboard/damageItems/RowActions/index.jsx new file mode 100644 index 0000000..8fbaea1 --- /dev/null +++ b/src/components/dashboard/damageItems/RowActions/index.jsx @@ -0,0 +1,13 @@ +import { Box } from "@mui/material"; +import Activity from "./Activity"; +import Edit from "./Edit"; + +const RowActions = ({ row, mutate }) => { + return ( + + + + + ); +}; +export default RowActions; diff --git a/src/components/dashboard/damageItems/Toolbar.jsx b/src/components/dashboard/damageItems/Toolbar.jsx new file mode 100644 index 0000000..b639318 --- /dev/null +++ b/src/components/dashboard/damageItems/Toolbar.jsx @@ -0,0 +1,10 @@ +import CreateDamage from "./Actions/Create"; + +const Toolbar = ({ mutate }) => { + return ( + <> + + + ); +}; +export default Toolbar; diff --git a/src/components/dashboard/damageItems/index.jsx b/src/components/dashboard/damageItems/index.jsx new file mode 100644 index 0000000..ac37d7d --- /dev/null +++ b/src/components/dashboard/damageItems/index.jsx @@ -0,0 +1,13 @@ +import PageTitle from "@/core/components/PageTitle"; +import { Stack } from "@mui/material"; +import DamagesList from "./DamagesList"; + +const DamagesPage = () => { + return ( + + + + + ); +}; +export default DamagesPage; diff --git a/src/components/dashboard/damages/operator/Actions/create/Forms/CityContent.jsx b/src/components/dashboard/damages/operator/Actions/create/Forms/CityContent.jsx new file mode 100644 index 0000000..08da4e5 --- /dev/null +++ b/src/components/dashboard/damages/operator/Actions/create/Forms/CityContent.jsx @@ -0,0 +1,39 @@ +import SelectBox from "@/core/components/SelectBox"; +import useEdaratLists from "@/lib/hooks/useEdaratLists"; +import { useEffect, useState } from "react"; + +const CityContent = ({ provinceID, field, fieldState: { error } }) => { + const { edaratList, loadingEdaratList, errorEdaratList } = useEdaratLists(provinceID); + const [prevDependency, setPrevDependency] = useState(provinceID); + + useEffect(() => { + if (prevDependency === provinceID) return; + field.onChange(null); + setPrevDependency(provinceID); + }, [provinceID]); + + return ( + + ); +}; +export default CityContent; diff --git a/src/components/dashboard/damages/operator/Actions/create/Forms/CityController.jsx b/src/components/dashboard/damages/operator/Actions/create/Forms/CityController.jsx new file mode 100644 index 0000000..f5b46d4 --- /dev/null +++ b/src/components/dashboard/damages/operator/Actions/create/Forms/CityController.jsx @@ -0,0 +1,16 @@ +import SelectBox from "@/core/components/SelectBox"; +import useEdaratLists from "@/lib/hooks/useEdaratLists"; +import { Controller, useWatch } from "react-hook-form"; +import CityContent from "./CityContent"; + +const CityController = ({ control }) => { + const provinceID = useWatch({ control, name: "province_id" }); + return ( + } + /> + ); +}; +export default CityController; diff --git a/src/components/dashboard/damages/operator/Actions/create/Forms/CreateFormContent.jsx b/src/components/dashboard/damages/operator/Actions/create/Forms/CreateFormContent.jsx new file mode 100644 index 0000000..11ec3b4 --- /dev/null +++ b/src/components/dashboard/damages/operator/Actions/create/Forms/CreateFormContent.jsx @@ -0,0 +1,239 @@ +import React, { useState } from "react"; +import { Controller, useForm } from "react-hook-form"; +import { yupResolver } from "@hookform/resolvers/yup"; +import { array, mixed, number, object, string } from "yup"; +import { Box, Button, DialogActions, DialogContent, Tab, Tabs } from "@mui/material"; +import StyledForm from "@/core/components/StyledForm"; +import InfoIcon from "@mui/icons-material/Info"; +import ExitToAppIcon from "@mui/icons-material/ExitToApp"; +import KeyboardDoubleArrowRightIcon from "@mui/icons-material/KeyboardDoubleArrowRight"; +import BeenhereIcon from "@mui/icons-material/Beenhere"; +import MinorCrashIcon from "@mui/icons-material/MinorCrash"; +import DamageInfo from "./DamageInfo"; +import KeyboardDoubleArrowLeftIcon from "@mui/icons-material/KeyboardDoubleArrowLeft"; +import DamageReport from "./DamageReport"; +import DamageItem from "./DamageItem"; +import validateNationalCode from "@/core/utils/nationalCodeValidation"; +import AssessmentIcon from "@mui/icons-material/Assessment"; + +function TabPanel(props) { + const { children, value, index } = props; + return ( + + ); +} + +const validationSchema = object({ + accident_date: string().required("لطفا تاریخ تصادف را وارد کنید!!!"), + accident_time: string().required("لطفا زمان تصادف را وارد کنید!!!"), + accident_type: string().required("لطفا نوع تصادف را وارد کنید!!!"), + province_id: number().required("لطفا استان را وارد کنید!!!"), + city_id: number().required("لطفا شهرستان را وارد کنید!!!"), + axis_name: string().required("لطفا نام محور را وارد کنید!!!"), + driver_name: string().required("لطفا نام و نام خانوادگی را وارد کنید!!!"), + phone_number: string() + .matches(/^09\d{9}$/, "شماره موبایل باید با 09 شروع شده و 11 رقم باشد") + .required("لطفا تلفن همراه را وارد کنید!!!"), + national_code: mixed() + .test("max", "کد ملی باید شامل 10 رقم باشد", (value) => value.toString().length === 10) + .test("validation", "کد ملی صحیح نمی باشد", (value) => validateNationalCode(value)) + .required("لطفا کد ملی را وارد کنید!!!"), + start_point: mixed() + .test("start-point-required", "لطفاً نقطه تصادف را مشخص کنید!", function (value) { + return !!value; + }) + .required("لطفاً نقطه تصادف را مشخص کنید!"), + plate_part1: mixed() + .test("max", "2رقم", (value) => value.toString().length === 2) + .required("plate_part1 الزامیست"), + plate_part2: mixed().required("plate_part2 الزامیست"), + plate_part3: mixed() + .test("max", "3رقم", (value) => value.toString().length === 3) + .required("plate_part3الزامیست"), + plate_part4: mixed() + .test("max", "2رقم", (value) => value.toString().length === 2) + .required("plate_part4الزامیست"), + damage_picture1: mixed().nullable().required("لطفا عکس تصادف را بارگذاری کنید!"), + damage_picture2: mixed().nullable().required("لطفا عکس تصادف را بارگذاری کنید!"), + police_serial: string().when("radio_button", { + is: "police_file_checkbox", // زمانی که گزینه "police_file_checkbox" انتخاب شده باشد + then: (schema) => schema.required("شماره کروکی یا نامه پلیس راه الزامی است"), + }), + police_file_date: string().when("radio_button", { + is: "police_file_checkbox", // زمانی که گزینه "police_file_checkbox" انتخاب شده باشد + then: (schema) => schema.required("تاریخ کروکی یا نامه پلیس راه الزامی است"), + }), + police_file: mixed().when("radio_button", { + is: "police_file_checkbox", // زمانی که گزینه "police_file_checkbox" انتخاب شده باشد + then: (schema) => schema.nullable().required("تصویر کروکی یا نامه پلیس راه الزامی است"), + otherwise: (schema) => schema.notRequired(), + }), + items_damage: array().min(1, "حداقل یک آیتم خسارت ضروریست!!!"), +}); + +const CreateFormContent = ({ setOpen, SubmitDamage, defaultData }) => { + const defaultValues = { + accident_date: defaultData.accident_date, + accident_time: defaultData.accident_time, + accident_type: defaultData.accident_type, + province_id: defaultData.province_id, + city_id: defaultData.city_id, + axis_name: defaultData.axis_name, + driver_name: defaultData.driver_name, + phone_number: defaultData.phone_number, + national_code: defaultData.national_code, + plate_part1: defaultData.plate_part1, + plate_part2: defaultData.plate_part2, + plate_part3: defaultData.plate_part3, + plate_part4: defaultData.plate_part4, + radio_button: defaultData.radio_button, + damage_picture1: defaultData.damage_picture1, + damage_picture2: defaultData.damage_picture2, + report_base: defaultData.report_base, + police_file_checkbox: defaultData.police_file_checkbox, + police_file: defaultData.police_file, + police_file_date: defaultData.police_file_date, + police_serial: defaultData.police_serial, + start_point: defaultData.start_point, + items_damage: defaultData.items_damage, + }; + + const [tabState, setTabState] = useState(0); + const handleClose = () => setOpen(false); + const handleChangeTab = (event, newValue) => setTabState(newValue); + const handlePrev = () => { + if (tabState === 0) { + handleClose(); + } else { + setTabState(tabState - 1); + } + }; + const { + control, + handleSubmit, + setValue, + trigger, + formState: { errors, isSubmitting, isValid }, + } = useForm({ + defaultValues, + resolver: yupResolver(validationSchema), + mode: "all", + }); + const onSubmitBase = async (data) => { + await SubmitDamage(data); + }; + const handleNext = async () => { + let fieldsToValidate = []; + if (tabState === 0) { + fieldsToValidate = [ + "phone_number", + "start_point", + "plate_part1", + "plate_part2", + "plate_part3", + "plate_part4", + "accident_date", + "accident_type", + "national_code", + "accident_time", + "province_id", + "city_id", + "axis_name", + "driver_name", + "damage_picture1", + "damage_picture2", + ]; + } else if (tabState === 1) { + fieldsToValidate = ["report_base", "police_file", "police_file_date", "police_serial"]; + } else if (tabState === 2) { + fieldsToValidate = ["items_damage"]; + } + const isValid = await trigger(fieldsToValidate); + if (isValid) { + setTabState(tabState + 1); + } + }; + return ( + + + } label="اطلاعات تصادف" /> + } label="گزارش خسارت" /> + } label="آیتم خسارت" /> + + + + + + + + + + + { + return ( + + ); + }} + /> + + + + + + {tabState !== 2 ? ( + + ) : ( + + )} + + + ); +}; +export default CreateFormContent; diff --git a/src/components/dashboard/damages/operator/Actions/create/Forms/DamageAmount.jsx b/src/components/dashboard/damages/operator/Actions/create/Forms/DamageAmount.jsx new file mode 100644 index 0000000..35eb93f --- /dev/null +++ b/src/components/dashboard/damages/operator/Actions/create/Forms/DamageAmount.jsx @@ -0,0 +1,56 @@ +import { Controller, useWatch } from "react-hook-form"; +import { Grid } from "@mui/material"; +import NumberField from "@/core/components/NumberField"; +import { useEffect } from "react"; + +const DamageAmount = ({ control, damageItemList, setValue }) => { + const itemDamage = useWatch({ control, name: "id" }); + const itemValue = useWatch({ control, name: "value" }); + const itemDamageId = damageItemList.find((damageItem) => damageItem.id === itemDamage); + const isDisabled = itemDamageId?.base_price ? itemDamageId?.base_price !== 0 : false; + const calculatedValue = itemValue * (itemDamageId?.base_price ? itemDamageId?.base_price : 0); + useEffect(() => { + if (isDisabled) { + setValue("amount", calculatedValue); + } + }, [itemValue, itemDamageId, isDisabled, setValue]); + return ( + + { + return ( + { + const inputValue = event.target.value; + if (isNaN(Number(inputValue))) { + return; + } + if (!isDisabled) { + field.onChange(inputValue); + } + }} + label={`هزینه خسارت (ریال)`} + placeholder="هزینه خسارت را وارد کنید" + fullWidth + size="small" + disabled={isDisabled || itemDamage === null} + error={error} + helperText={error?.message} + /> + ); + }} + /> + + ); +}; +export default DamageAmount; diff --git a/src/components/dashboard/damages/operator/Actions/create/Forms/DamageInfo.jsx b/src/components/dashboard/damages/operator/Actions/create/Forms/DamageInfo.jsx new file mode 100644 index 0000000..797610c --- /dev/null +++ b/src/components/dashboard/damages/operator/Actions/create/Forms/DamageInfo.jsx @@ -0,0 +1,280 @@ +import { Controller, useWatch } from "react-hook-form"; +import { Grid, Stack, TextField } from "@mui/material"; +import MuiDatePicker from "@/core/components/MuiDatePicker"; +import MuiTimePicker from "@/core/components/MuiTimePicker"; +import SelectBox from "@/core/components/SelectBox"; +import useProvinces from "@/lib/hooks/useProvince"; +import PlateNumber from "@/core/components/PlateNumber"; +import ImageUpload from "./ImageUpload"; +import MapInfoOneMarker from "@/core/components/MapInfoOneMarker"; +import CityController from "@/components/dashboard/damages/operator/Actions/create/Forms/CityController"; +import PersianTextField from "@/core/components/PersianTextField"; + +const DamageInfo = ({ control, setValue, errors }) => { + const StartPoint = useWatch({ control, name: "start_point" }); + const { provinces, errorProvinces, loadingProvinces } = useProvinces(); + + return ( + + + + { + return ( + + ); + }} + /> + + + + + + ( + + )} + /> + + + + + ( + + )} + /> + + + ( + { + const inputValue = event.target.value; + if (isNaN(Number(inputValue))) { + return; + } + field.onChange(inputValue); + }} + label="کد ملی راننده" + placeholder={"کد ملی راننده را وارد کنید"} + fullWidth + size="small" + error={error} + helperText={error?.message} + InputLabelProps={{ shrink: true }} + /> + )} + /> + + + + + ( + { + const inputValue = event.target.value; + if (isNaN(Number(inputValue))) { + return; + } + field.onChange(inputValue); + }} + label="شماره موبایل راننده" + placeholder={"شماره موبایل راننده را وارد کنید"} + fullWidth + size="small" + error={error} + helperText={error?.message} + InputLabelProps={{ shrink: true }} + /> + )} + /> + + + + + + + + { + return ( + field.onChange(value || [])} + helperText={error ? error.message : null} + /> + ); + }} + name={"accident_date"} + /> + + + { + return ( + field.onChange(value || null)} + helperText={error ? error.message : null} + /> + ); + }} + name={"accident_time"} + /> + + + { + return ( + + ); + }} + /> + + + + + ( + + )} + /> + + + ( + + )} + /> + + + + + + + ); +}; +export default DamageInfo; diff --git a/src/components/dashboard/damages/operator/Actions/create/Forms/DamageItem.jsx b/src/components/dashboard/damages/operator/Actions/create/Forms/DamageItem.jsx new file mode 100644 index 0000000..e52c280 --- /dev/null +++ b/src/components/dashboard/damages/operator/Actions/create/Forms/DamageItem.jsx @@ -0,0 +1,167 @@ +import { + Autocomplete, + Box, + Button, + Chip, + Divider, + Fade, + Grid, + Skeleton, + Stack, + TextField, + Typography, +} from "@mui/material"; +import { Controller, useForm, useWatch } from "react-hook-form"; +import LinearProgress from "@mui/material/LinearProgress"; +import { useEffect, useState } from "react"; +import DamageItemInfo from "./DamageItemInfo"; +import { number, object } from "yup"; +import useDamageItemList from "@/lib/hooks/useDamageItemList"; +import DamagePrice from "./DamagePrice"; +import { yupResolver } from "@hookform/resolvers/yup"; + +const schema = object({ + id: number().required("لطفا نوع خسارت را وارد کنید!!!"), + value: number().required("لطفا میزان خسارت را وارد کنید!!!"), + amount: number().required("لطفا هزینه خسارت را وارد کنید!!!"), +}); +const defaultValues = { + id: null, + value: null, + amount: null, +}; +const DamageItem = ({ baseOnChange, baseDamageItems }) => { + const flattenBaseDamageItems = (items) => { + return items.map((item) => ({ + id: item.id, + title: item.title, + unit: item.unit, + base_price: item.base_price, + status: item.status, + until_27_shahrivar: item.until_27_shahrivar, + ...item.pivot, // اضافه کردن مقادیر موجود در pivot به سطح بالا + })); + }; + const [selectedDamageItemList, setSelectedDamageItemList] = useState( + baseDamageItems.length !== 0 ? flattenBaseDamageItems(baseDamageItems) : [] + ); + const { damageItemList, loadingDamageItemList, errorDamageItemList } = useDamageItemList(); + const { control, handleSubmit, reset, setValue } = useForm({ + defaultValues, + resolver: yupResolver(schema), + mode: "onBlur", + }); + const deleteDamageItem = (id) => { + setSelectedDamageItemList((prev) => prev.filter((DamageItem) => DamageItem.id !== id)); + }; + useEffect(() => { + baseOnChange(selectedDamageItemList); + }, [selectedDamageItemList]); + const handleCreateDamage = (data) => { + setSelectedDamageItemList((prev) => { + const isDuplicate = prev.some((item) => item.id === data.id); + if (!isDuplicate) { + return [...prev, data]; + } else { + return prev; + } + }); + reset(); + }; + + return ( + <> + + {loadingDamageItemList ? ( + + + + ) : ( + <> + + ( + option.title} + isOptionEqualToValue={(option, value) => option.id === value.id} + loading={loadingDamageItemList} + renderInput={(params) => ( + + )} + onChange={(_, value) => field.onChange(value?.id)} + value={damageItemList.find((item) => item.id === field.value) || null} + /> + )} + /> + + + + + + + + + + + )} + + + + + {selectedDamageItemList.length === 0 && ( + + + ابتدا اطلاعات بالا را تکمیل نمایید + + + )} + + + {loadingDamageItemList ? ( + + + + ) : ( + selectedDamageItemList.length !== 0 && + selectedDamageItemList.map((selectedDamageItem, index) => ( + + + + )) + )} + + + + ); +}; +export default DamageItem; diff --git a/src/components/dashboard/damages/operator/Actions/create/Forms/DamageItemInfo.jsx b/src/components/dashboard/damages/operator/Actions/create/Forms/DamageItemInfo.jsx new file mode 100644 index 0000000..ca95574 --- /dev/null +++ b/src/components/dashboard/damages/operator/Actions/create/Forms/DamageItemInfo.jsx @@ -0,0 +1,60 @@ +import { Box, Card, IconButton, Stack, Typography } from "@mui/material"; +import DeleteIcon from "@mui/icons-material/Delete"; +import React from "react"; +import MinorCrashIcon from "@mui/icons-material/MinorCrash"; + +const DamageItemInfo = ({ deleteDamageItem, selectedDamageItem, damageItemList }) => { + const itemDamageId = damageItemList.find((item) => item.id == selectedDamageItem.id); + return ( + + + + + + {itemDamageId.title} + + + + میزان خسارت ({itemDamageId.unit}) :{" "} + + {(selectedDamageItem.value / 1).toLocaleString()} + + + + + هزینه خسارت (ریال) :{" "} + + {(selectedDamageItem.amount / 1).toLocaleString()} + + + + + + + deleteDamageItem(selectedDamageItem.id)}> + + + + + ); +}; +export default DamageItemInfo; diff --git a/src/components/dashboard/damages/operator/Actions/create/Forms/DamagePrice.jsx b/src/components/dashboard/damages/operator/Actions/create/Forms/DamagePrice.jsx new file mode 100644 index 0000000..96ff80a --- /dev/null +++ b/src/components/dashboard/damages/operator/Actions/create/Forms/DamagePrice.jsx @@ -0,0 +1,48 @@ +import { Controller, useWatch } from "react-hook-form"; +import { Grid, TextField } from "@mui/material"; +import DamageAmount from "./DamageAmount"; + +const DamagePrice = ({ damageItemList, control, setValue }) => { + const itemDamage = useWatch({ control, name: "id" }); + const itemDamageId = damageItemList.find((damageItem) => damageItem.id === itemDamage); + + return ( + <> + + { + return ( + { + const inputValue = event.target.value; + if (isNaN(Number(inputValue))) { + return; + } + field.onChange(inputValue); + }} + label={`میزان خسارت ${itemDamageId?.unit ? `(${itemDamageId?.unit})` : ""}`} + fullWidth + size="small" + error={error} + helperText={error?.message} + /> + ); + }} + /> + + + + ); +}; +export default DamagePrice; diff --git a/src/components/dashboard/damages/operator/Actions/create/Forms/DamageReport.jsx b/src/components/dashboard/damages/operator/Actions/create/Forms/DamageReport.jsx new file mode 100644 index 0000000..d994aff --- /dev/null +++ b/src/components/dashboard/damages/operator/Actions/create/Forms/DamageReport.jsx @@ -0,0 +1,51 @@ +import { FormControlLabel, RadioGroup, Stack } from "@mui/material"; +import { Controller, useWatch } from "react-hook-form"; +import { useState } from "react"; +import PoliceFileReport from "./PoliceFileReport"; +import Radio from "@mui/material/Radio"; + +const DamageReport = ({ control }) => { + const watchedStatus = useWatch({ control, name: "radio_button" }); + const [selectedOption, setSelectedOption] = useState(watchedStatus || null); + // const [selectedOption, setSelectedOption] = useState("report_base"); + + const handleOptionChange = (e) => { + setSelectedOption(e.target.value); + }; + + return ( + <> + + + } + /> + } + label="بازدید عوامل و کارشناسان راهداری" + /> + } + /> + } + label="کروکی یا نامه پلیس راه (معرفی فرد به اداره)" + /> + + + {selectedOption === "police_file_checkbox" && } + + ); +}; +export default DamageReport; diff --git a/src/components/dashboard/damages/operator/Actions/create/Forms/ImageUpload.jsx b/src/components/dashboard/damages/operator/Actions/create/Forms/ImageUpload.jsx new file mode 100644 index 0000000..5186a4b --- /dev/null +++ b/src/components/dashboard/damages/operator/Actions/create/Forms/ImageUpload.jsx @@ -0,0 +1,77 @@ +import { FormControl, FormHelperText } from "@mui/material"; +import UploadSystem from "@/core/components/UploadSystem"; +import React, { useEffect, useState } from "react"; + +const ImageUpload = ({ name, value, onChange, error, title }) => { + const [beforeImg, setBeforeImg] = useState(value ? value : null); + const [beforeFileType, setBeforeFileType] = useState(value ? "image/" : null); + const [beforeFileName, setBeforeFileName] = useState(null); + const [showBeforeImage, setShowBeforeImage] = useState(!value); + + useEffect(() => { + if (value) { + setShowBeforeImage(false); + if (typeof value === "string") { + setBeforeImg(value); + setBeforeFileType("image/"); + } else if (value instanceof File) { + setBeforeImg(URL.createObjectURL(value)); + setBeforeFileType(value.type); + } + } + }, [value]); + + const handleFileChange = (event) => { + const uploadedFile = event.target?.files?.[0]; + if (uploadedFile) { + const fileType = event.target?.files?.[0].type; + const fileName = event.target?.files?.[0].name; + setBeforeImg(URL.createObjectURL(uploadedFile)); + setBeforeFileType(fileType); + setBeforeFileName(fileName); + onChange(uploadedFile); + setShowBeforeImage(false); + } + }; + + return ( + + + {title} + + + {error ? error.message : null} + + ); +}; +export default ImageUpload; diff --git a/src/components/dashboard/damages/operator/Actions/create/Forms/PoliceFileReport.jsx b/src/components/dashboard/damages/operator/Actions/create/Forms/PoliceFileReport.jsx new file mode 100644 index 0000000..64f1067 --- /dev/null +++ b/src/components/dashboard/damages/operator/Actions/create/Forms/PoliceFileReport.jsx @@ -0,0 +1,67 @@ +import { Controller } from "react-hook-form"; +import { Grid, Stack, TextField } from "@mui/material"; +import MuiDatePicker from "@/core/components/MuiDatePicker"; +import ImageUpload from "./ImageUpload"; + +const PoliceFileReport = ({ control }) => { + return ( + + + + + ( + + )} + /> + + + { + return ( + field.onChange(value || [])} + helperText={error ? error.message : null} + /> + ); + }} + name={"police_file_date"} + /> + + + + { + return ( + + ); + }} + /> + + ); +}; +export default PoliceFileReport; diff --git a/src/components/dashboard/damages/operator/Actions/create/Forms/index.jsx b/src/components/dashboard/damages/operator/Actions/create/Forms/index.jsx new file mode 100644 index 0000000..ab50184 --- /dev/null +++ b/src/components/dashboard/damages/operator/Actions/create/Forms/index.jsx @@ -0,0 +1,102 @@ +"use client"; +import { Dialog, IconButton } from "@mui/material"; +import CreateFormContent from "./CreateFormContent"; +import { format } from "date-fns"; +import { CREATE_DAMAGE } from "@/core/utils/routes"; +import useRequest from "@/lib/hooks/useRequest"; +import CloseIcon from "@mui/icons-material/Close"; + +const OperatorCreateForm = ({ open, setOpen, mutate }) => { + const requestServer = useRequest({ notificationSuccess: true }); + const SubmitCreateDamage = async (result) => { + const PlateNumber = `${result.plate_part1}-${result.plate_part2}-${result.plate_part3}-${result.plate_part4}`; + const formData = new FormData(); + formData.append("accident_date", result.accident_date); + formData.append("accident_time", format(new Date(result.accident_time), "HH:mm")); + formData.append("accident_type", result.accident_type); + formData.append("province_id", result.province_id); + formData.append("city_id", result.city_id); + formData.append("axis_name", result.axis_name); + formData.append("driver_name", result.driver_name); + formData.append("driver_phone_number", result.phone_number); + formData.append("driver_national_code", result.national_code); + formData.append("plaque", PlateNumber); + formData.append("damage_picture1", result.damage_picture1); + formData.append("damage_picture2", result.damage_picture2); + formData.append("lat", result.start_point.lat); + formData.append("lng", result.start_point.lng); + result.items_damage.map((item, index) => { + formData.append(`damage_items[${index}][id]`, item.id); + formData.append(`damage_items[${index}][value]`, item.value); + formData.append(`damage_items[${index}][amount]`, item.amount); + }); + if (result.radio_button === "report_base") { + formData.append("report_base", 1); + } + if (result.radio_button === "police_file_checkbox") { + formData.append("report_base", 0); + formData.append("police_file", result.police_file); + formData.append("police_file_date", result.police_file_date); + formData.append("police_serial", result.police_serial); + } + await requestServer(`${CREATE_DAMAGE}`, "post", { + data: formData, + }) + .then(() => { + mutate(); + setOpen(false); + }) + .catch(() => {}); + }; + const defaultData = { + accident_date: "", + accident_time: "", + accident_type: "", + province_id: null, + city_id: null, + axis_name: "", + driver_name: "", + phone_number: "", + national_code: "", + plate_part1: "", + plate_part2: "الف", + plate_part3: "", + plate_part4: "", + radio_button: "report_base", + damage_picture1: null, + damage_picture2: null, + report_base: "", + police_file_checkbox: "", + police_file: null, + police_file_date: "", + police_serial: "", + start_point: "", + items_damage: [], + }; + return ( + + setOpen(false)} + sx={(theme) => ({ + position: "absolute", + right: 8, + top: 8, + zIndex: 50, + color: theme.palette.grey[500], + })} + > + + + {open && ( + + )} + + ); +}; +export default OperatorCreateForm; diff --git a/src/components/dashboard/damages/operator/Actions/create/index.jsx b/src/components/dashboard/damages/operator/Actions/create/index.jsx new file mode 100644 index 0000000..5816c12 --- /dev/null +++ b/src/components/dashboard/damages/operator/Actions/create/index.jsx @@ -0,0 +1,38 @@ +"use client"; +import { Button, IconButton, useMediaQuery } from "@mui/material"; +import { useTheme } from "@emotion/react"; +import { useState } from "react"; +import { AddCircle } from "@mui/icons-material"; +import OperatorCreateForm from "./Forms"; + +const OperatorCreate = ({ mutate }) => { + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("sm")); + const [open, setOpen] = useState(false); + + const handleOpen = () => { + setOpen(true); + }; + + return ( + <> + {isMobile ? ( + + + + ) : ( + + )} + + + ); +}; +export default OperatorCreate; diff --git a/src/components/dashboard/damages/operator/ExcelPrint/index.jsx b/src/components/dashboard/damages/operator/ExcelPrint/index.jsx new file mode 100644 index 0000000..f38d451 --- /dev/null +++ b/src/components/dashboard/damages/operator/ExcelPrint/index.jsx @@ -0,0 +1,75 @@ +"use client"; +import { Button, CircularProgress, IconButton, useMediaQuery } from "@mui/material"; +import { useMemo, useState } from "react"; +import moment from "jalali-moment"; +import FileSaver from "file-saver"; +import DescriptionIcon from "@mui/icons-material/Description"; +import useRequest from "@/lib/hooks/useRequest"; +import { useTheme } from "@emotion/react"; +import DataTableFilterDataStructure from "@/core/utils/DataTableFilterDataStructure"; +import isArrayEmpty from "@/core/utils/isArrayEmpty"; +import { EXPORT_DAMAGES_OPERATOR_LIST } from "@/core/utils/routes"; + +const PrintExcel = ({ table, filterData }) => { + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("sm")); + const [loading, setLoading] = useState(false); + const requestServer = useRequest({ notificationSuccess: true }); + const activeFilters = Object.entries(filterData).reduce((acc, [key, filter]) => { + if (filter.value && (!Array.isArray(filter.value) || filter.value.some((item) => item.trim() !== ""))) { + acc.push({ id: key, value: filter.value }); + } + return acc; + }, []); + + const filterParams = useMemo(() => { + const params = new URLSearchParams(); + if (activeFilters.length > 0) { + activeFilters.map((filter, index) => { + const filteredFilterData = DataTableFilterDataStructure(filterData, isArrayEmpty); + params.set("filters", JSON.stringify(filteredFilterData.length === 0 ? [] : filteredFilterData)); + }); + } else { + params.set("filters", JSON.stringify([])); + } + return params; + }, [activeFilters]); + + const clickHandler = () => { + setLoading(true); + requestServer(`${EXPORT_DAMAGES_OPERATOR_LIST}?${filterParams}`, "get", { + requestOptions: { responseType: "blob" }, + }) + .then((response) => { + const filename = `خروجی کارتابل عملیات فعالیت روزانه تاریخ_${moment().format("jYYYY_jMM_jDD")}.xlsx`; + FileSaver.saveAs(response.data, filename); + }) + .catch(() => {}) + .finally(() => { + setLoading(false); + }); + }; + + return ( + <> + {isMobile ? ( + + + + ) : ( + + )} + + ); +}; +export default PrintExcel; diff --git a/src/components/dashboard/damages/operator/Form/CreateFactor/CreateFactorContent.jsx b/src/components/dashboard/damages/operator/Form/CreateFactor/CreateFactorContent.jsx new file mode 100644 index 0000000..961f59c --- /dev/null +++ b/src/components/dashboard/damages/operator/Form/CreateFactor/CreateFactorContent.jsx @@ -0,0 +1,215 @@ +import { CHECK_PAYMENT_STATUS, CREATE_FACTOR_DAMAGE, SEND_SMS_AGAIN } from "@/core/utils/routes"; +import useRequest from "@/lib/hooks/useRequest"; +import PaymentIcon from "@mui/icons-material/Payment"; +import ReceiptLongIcon from "@mui/icons-material/ReceiptLong"; +import RequestQuoteIcon from "@mui/icons-material/RequestQuote"; +import WebAssetIcon from "@mui/icons-material/WebAsset"; +import { + Alert, + Box, + Button, + Chip, + DialogActions, + DialogContent, + Divider, + Grid, + Stack, + Typography, +} from "@mui/material"; +import { useReducer, useState } from "react"; + +const initialState = { + submittingCreateFactor: false, + submittingCheckFactor: false, + submittingSms: false, +}; + +const reducer = (state, action) => { + switch (action.type) { + case "SET_SUBMITTING_CREATE_FACTOR": + return { ...state, submittingCreateFactor: action.payload }; + case "SET_SUBMITTING_CHECK_FACTOR": + return { ...state, submittingCheckFactor: action.payload }; + case "SET_SUBMITTING_SMS": + return { ...state, submittingSms: action.payload }; + default: + return state; + } +}; +const CreateFactorContent = ({ row, mutate, rowId, setOpenCreateFactorDialog }) => { + const [state, dispatch] = useReducer(reducer, initialState); + const requestServer = useRequest({ notificationSuccess: true }); + const [factorCreated, setFactorCreated] = useState(row.original?.status === 3); + const handleCreateFactor = () => { + dispatch({ type: "SET_SUBMITTING_CREATE_FACTOR", payload: true }); + requestServer(`${CREATE_FACTOR_DAMAGE}/${rowId}`, "get") + .then(() => { + mutate(); + dispatch({ type: "SET_SUBMITTING_CREATE_FACTOR", payload: false }); + setFactorCreated(true); + }) + .catch(() => { + dispatch({ type: "SET_SUBMITTING_CREATE_FACTOR", payload: false }); + }); + }; + const handleCheckPaymentStatus = () => { + dispatch({ type: "SET_SUBMITTING_CHECK_FACTOR", payload: true }); + requestServer(`${CHECK_PAYMENT_STATUS}/${rowId}`, "get") + .then(() => { + setOpenCreateFactorDialog(false); + mutate(); + dispatch({ type: "SET_SUBMITTING_CHECK_FACTOR", payload: false }); + }) + .catch(() => { + dispatch({ type: "SET_SUBMITTING_CHECK_FACTOR", payload: false }); + }); + }; + const handleSendSMS = () => { + dispatch({ type: "SET_SUBMITTING_SMS", payload: true }); + requestServer(`${SEND_SMS_AGAIN}/${rowId}`, "get") + .then(() => { + dispatch({ type: "SET_SUBMITTING_SMS", payload: false }); + }) + .catch(() => { + dispatch({ type: "SET_SUBMITTING_SMS", payload: false }); + }); + }; + + return ( + <> + + + + + } + label={ + + مبلغ کسری بیمه + + } + /> + + + + {(row.original?.deposit_insurance_amount / 1).toLocaleString() || "0"} ریال + + + + + } + label={ + + مبلغ کسری داغی + + } + /> + + + + {(row.original?.deposit_daghi_amount / 1).toLocaleString() || "0"} ریال + + + + + } + label={ + + مبلغ کل خسارت برآورد شده + + } + /> + + + + {(row.original?.sum / 1).toLocaleString() || "0"} ریال + + + + + } + label={ + + مبلغ قابل پرداخت + + } + /> + + + + {(row.original?.sum / 1).toLocaleString() || "0"} ریال + + + {!factorCreated && ( + + + + بعد از صدور فاکتور، ویرایش اطلاعات کاربر و مبالغ امکان‌پذیر نخواهد بود. + + + لینک پرداخت فاکتور به شماره {row.original?.driver_phone_number} ارسال می شود. + + + + )} + + + + {factorCreated ? ( + <> + + + + + ) : ( + <> + + + + )} + + + ); +}; +export default CreateFactorContent; diff --git a/src/components/dashboard/damages/operator/Form/CreateFactor/index.jsx b/src/components/dashboard/damages/operator/Form/CreateFactor/index.jsx new file mode 100644 index 0000000..86804a7 --- /dev/null +++ b/src/components/dashboard/damages/operator/Form/CreateFactor/index.jsx @@ -0,0 +1,43 @@ +import { Dialog, DialogTitle, IconButton, Tooltip } from "@mui/material"; +import { useState } from "react"; +import ReceiptLongIcon from "@mui/icons-material/ReceiptLong"; +import CreateFactorContent from "./CreateFactorContent"; + +const CreateFactor = ({ row, rowId, mutate }) => { + const [openCreateFactorDialog, setOpenCreateFactorDialog] = useState(false); + + return ( + <> + + setOpenCreateFactorDialog(true)}> + + + + setOpenCreateFactorDialog(false)} + PaperProps={{ + sx: { + boxShadow: "rgba(60, 64, 67, 0.3) 0px 1px 16px 0px, rgba(60, 64, 67, 0.15) 0px 1px 10px 0px", + }, + }} + maxWidth={"sm"} + scroll="paper" + > + + {row.original?.status == 2 ? "صدور فاکتور" : "استعلام پرداخت"} + + {openCreateFactorDialog && ( + + )} + + + ); +}; +export default CreateFactor; diff --git a/src/components/dashboard/damages/operator/Form/EditForm/EditController.jsx b/src/components/dashboard/damages/operator/Form/EditForm/EditController.jsx new file mode 100644 index 0000000..f545cc6 --- /dev/null +++ b/src/components/dashboard/damages/operator/Form/EditForm/EditController.jsx @@ -0,0 +1,113 @@ +import useRequest from "@/lib/hooks/useRequest"; +import CreateFormContent from "@/components/dashboard/damages/operator/Actions/create/Forms/CreateFormContent"; +import { useEffect, useState } from "react"; +import { GET_DAMAGE_ITEM_DETAILS, UPDATE_DAMAGE_ITEM } from "@/core/utils/routes"; +import DialogLoading from "@/core/components/DialogLoading"; +import { format } from "date-fns"; +import moment from "jalali-moment"; + +const EditController = ({ rowId, mutate, setOpenEditDialog }) => { + const requestServer = useRequest(); + const [damageItemDetails, setDamageItemDetails] = useState(null); + const [damageItemDetailsLoading, setDamageItemDetailsLoading] = useState(false); + + useEffect(() => { + setDamageItemDetailsLoading(true); + requestServer(`${GET_DAMAGE_ITEM_DETAILS}/${rowId}`, "get") + .then((response) => { + setDamageItemDetails(response.data.data); + setDamageItemDetailsLoading(false); + }) + .catch((e) => { + setDamageItemDetailsLoading(false); + }); + }, [rowId]); + + const today = new Date().toISOString().split("T")[0]; + const plaqueNumber = damageItemDetails?.plaque.split("-") || []; + const defaultData = { + damage_picture1: damageItemDetails?.damage_picture1 || null, + damage_picture2: damageItemDetails?.damage_picture2 || null, + accident_date: new Date(damageItemDetails?.accident_date) || "", + accident_time: new Date(`${today} ${damageItemDetails?.accident_time || ""}`), + accident_type: damageItemDetails?.accident_type || "", + axis_name: damageItemDetails?.axis_name || "", + driver_name: damageItemDetails?.driver_name || "", + police_file: damageItemDetails?.police_file || null, + police_file_date: damageItemDetails?.police_file_date || "", + police_serial: damageItemDetails?.police_serial || "", + items_damage: damageItemDetails?.damages || [], + phone_number: damageItemDetails?.driver_phone_number || "", + national_code: damageItemDetails?.driver_national_code || "", + plate_part1: plaqueNumber[0] || "", + plate_part2: plaqueNumber[1] || "", + plate_part3: plaqueNumber[2] || "", + plate_part4: plaqueNumber[3] || "", + radio_button: damageItemDetails?.report_base === 1 ? "report_base" : "police_file_checkbox", + report_base: damageItemDetails?.report_base, + province_id: damageItemDetails?.province_id || null, + city_id: damageItemDetails?.city_id || null, + start_point: { lat: damageItemDetails?.lat || "", lng: damageItemDetails?.lng || "" }, + }; + const HandleSubmit = async (result) => { + const PlateNumber = `${result.plate_part1}-${result.plate_part2}-${result.plate_part3}-${result.plate_part4}`; + const formData = new FormData(); + const accidentDate = + result.accident_date == defaultData.accident_date + ? moment(result.accident_date).locale("en").format("YYYY-MM-DD") + : result.accident_date; + formData.append("accident_date", accidentDate); + formData.append("accident_time", format(new Date(result.accident_time), "HH:mm")); + formData.append("accident_type", result.accident_type); + formData.append("province_id", result.province_id); + formData.append("city_id", result.city_id); + formData.append("axis_name", result.axis_name); + formData.append("driver_name", result.driver_name); + formData.append("driver_phone_number", result.phone_number); + formData.append("driver_national_code", result.national_code); + formData.append("plaque", PlateNumber); + result.damage_picture1 !== defaultData.damage_picture1 && + formData.append("damage_picture1", result.damage_picture1); + result.damage_picture2 !== defaultData.damage_picture2 && + formData.append("damage_picture2", result.damage_picture2); + formData.append("lat", result.start_point.lat); + formData.append("lng", result.start_point.lng); + result.items_damage.map((item, index) => { + formData.append(`damage_items[${index}][id]`, item.id); + formData.append(`damage_items[${index}][value]`, item.value); + formData.append(`damage_items[${index}][amount]`, item.amount); + }); + if (result.radio_button === "report_base") { + formData.append("report_base", 1); + } + if (result.radio_button === "police_file_checkbox") { + formData.append("report_base", 0); + result.police_file !== defaultData.police_file && formData.append("police_file", result.police_file); + formData.append("police_file_date", result.police_file_date); + formData.append("police_serial", result.police_serial); + } + await requestServer(`${UPDATE_DAMAGE_ITEM}/${rowId}`, "post", { + data: formData, + }) + .then(() => { + mutate(); + setOpenEditDialog(false); + }) + .catch(() => {}); + }; + return ( + <> + {damageItemDetailsLoading ? ( + + ) : ( + + )} + + ); +}; +export default EditController; diff --git a/src/components/dashboard/damages/operator/Form/EditForm/index.jsx b/src/components/dashboard/damages/operator/Form/EditForm/index.jsx new file mode 100644 index 0000000..e608b32 --- /dev/null +++ b/src/components/dashboard/damages/operator/Form/EditForm/index.jsx @@ -0,0 +1,59 @@ +import { Dialog, DialogTitle, IconButton, Tooltip } from "@mui/material"; +import { useState } from "react"; +import BorderColorIcon from "@mui/icons-material/BorderColor"; +import EditController from "./EditController"; +import CloseIcon from "@mui/icons-material/Close"; + +const EditForm = ({ row, mutate, rowId }) => { + const [openEditDialog, setOpenEditDialog] = useState(false); + + return ( + <> + + { + setOpenEditDialog(true); + }} + > + + + + + setOpenEditDialog(false)} + sx={(theme) => ({ + position: "absolute", + right: 8, + top: 8, + zIndex: 50, + color: theme.palette.grey[500], + })} + > + + + {openEditDialog && ( + + )} + + + ); +}; +export default EditForm; diff --git a/src/components/dashboard/damages/operator/Form/RegisterInsurance/DaghiFile.jsx b/src/components/dashboard/damages/operator/Form/RegisterInsurance/DaghiFile.jsx new file mode 100644 index 0000000..9f9670a --- /dev/null +++ b/src/components/dashboard/damages/operator/Form/RegisterInsurance/DaghiFile.jsx @@ -0,0 +1,48 @@ +import { Grid, Stack, TextField } from "@mui/material"; +import { Controller } from "react-hook-form"; +import ImageUpload from "./ImageUpload"; +import NumberField from "@/core/components/NumberField"; + +const DaghiFile = ({ control }) => { + return ( + + + + + ( + + )} + /> + + + + ( + + )} + /> + + ); +}; +export default DaghiFile; diff --git a/src/components/dashboard/damages/operator/Form/RegisterInsurance/DaghiPayment.jsx b/src/components/dashboard/damages/operator/Form/RegisterInsurance/DaghiPayment.jsx new file mode 100644 index 0000000..91c4430 --- /dev/null +++ b/src/components/dashboard/damages/operator/Form/RegisterInsurance/DaghiPayment.jsx @@ -0,0 +1,53 @@ +import { useState } from "react"; +import { FormControlLabel, RadioGroup, Stack } from "@mui/material"; +import { Controller, useWatch } from "react-hook-form"; +import Radio from "@mui/material/Radio"; +import DaghiFile from "./DaghiFile"; + +const DaghiPayment = ({ control }) => { + const watchedStatus = useWatch({ control, name: "deposit_daghi_status" }); + const [selectedOption, setSelectedOption] = useState(watchedStatus || null); + + const handleOptionChange = (e) => { + setSelectedOption(e.target.value); + }; + + return ( + <> + + + } + /> + } + label="صورتجلسه تحویل داغی به انبار ندارد" + /> + } + /> + } + label="صورتجلسه تحویل داغی به انبار دارد" + /> + + + + {selectedOption === "has_daghi" && } + + ); +}; +export default DaghiPayment; diff --git a/src/components/dashboard/damages/operator/Form/RegisterInsurance/ImageUpload.jsx b/src/components/dashboard/damages/operator/Form/RegisterInsurance/ImageUpload.jsx new file mode 100644 index 0000000..107d640 --- /dev/null +++ b/src/components/dashboard/damages/operator/Form/RegisterInsurance/ImageUpload.jsx @@ -0,0 +1,77 @@ +import { FormControl, FormHelperText } from "@mui/material"; +import UploadSystem from "@/core/components/UploadSystem"; +import React, { useEffect, useState } from "react"; + +const ImageUpload = ({ name, value, onChange, error, beforeImage = null, title }) => { + const [beforeImg, setBeforeImg] = useState(beforeImage ? beforeImage : null); + const [beforeFileType, setBeforeFileType] = useState(beforeImage ? "image/" : null); + const [beforeFileName, setBeforeFileName] = useState(null); + const [showBeforeImage, setShowBeforeImage] = useState(!beforeImage); + + useEffect(() => { + if (value) { + setShowBeforeImage(false); + if (typeof value === "string") { + setBeforeImg(value); + setBeforeFileType("image/"); + } else if (value instanceof File) { + setBeforeImg(URL.createObjectURL(value)); + setBeforeFileType(value.type); + } + } + }, [value]); + + const handleFileChange = (event) => { + const uploadedFile = event.target?.files?.[0]; + if (uploadedFile) { + const fileType = event.target?.files?.[0].type; + const fileName = event.target?.files?.[0].name; + setBeforeImg(URL.createObjectURL(uploadedFile)); + setBeforeFileType(fileType); + setBeforeFileName(fileName); + onChange(uploadedFile); + setShowBeforeImage(false); + } + }; + + return ( + + + {title} + + + {error ? error.message : null} + + ); +}; +export default ImageUpload; diff --git a/src/components/dashboard/damages/operator/Form/RegisterInsurance/InsuranceFile.jsx b/src/components/dashboard/damages/operator/Form/RegisterInsurance/InsuranceFile.jsx new file mode 100644 index 0000000..da88acd --- /dev/null +++ b/src/components/dashboard/damages/operator/Form/RegisterInsurance/InsuranceFile.jsx @@ -0,0 +1,48 @@ +import NumberField from "@/core/components/NumberField"; +import { Grid, Stack } from "@mui/material"; +import { Controller } from "react-hook-form"; +import ImageUpload from "./ImageUpload"; + +const InsuranceFile = ({ control }) => { + return ( + + + + + ( + + )} + /> + + + + ( + + )} + /> + + ); +}; +export default InsuranceFile; diff --git a/src/components/dashboard/damages/operator/Form/RegisterInsurance/InsurancePayment.jsx b/src/components/dashboard/damages/operator/Form/RegisterInsurance/InsurancePayment.jsx new file mode 100644 index 0000000..50ee912 --- /dev/null +++ b/src/components/dashboard/damages/operator/Form/RegisterInsurance/InsurancePayment.jsx @@ -0,0 +1,52 @@ +import { FormControlLabel, FormHelperText, RadioGroup, Stack } from "@mui/material"; +import { Controller, useWatch } from "react-hook-form"; +import Radio from "@mui/material/Radio"; +import { useState } from "react"; +import InsuranceFile from "./InsuranceFile"; + +const InsurancePayment = ({ control }) => { + const watchedStatus = useWatch({ control, name: "deposit_insurance_status" }); + const [selectedOption, setSelectedOption] = useState(watchedStatus || null); + + const handleOptionChange = (e) => { + setSelectedOption(e.target.value); + }; + + return ( + <> + + + } + /> + } + label="فیش واریزی (بیمه) ندارد" + /> + } + /> + } + label="فیش واریزی (بیمه) دارد" + /> + + + {selectedOption === "has_insurance" && } + + ); +}; +export default InsurancePayment; diff --git a/src/components/dashboard/damages/operator/Form/RegisterInsurance/RegisterInsuranceContent.jsx b/src/components/dashboard/damages/operator/Form/RegisterInsurance/RegisterInsuranceContent.jsx new file mode 100644 index 0000000..b33b433 --- /dev/null +++ b/src/components/dashboard/damages/operator/Form/RegisterInsurance/RegisterInsuranceContent.jsx @@ -0,0 +1,203 @@ +import { Box, Button, DialogActions, DialogContent, Tab, Tabs, Typography } from "@mui/material"; +import ExitToAppIcon from "@mui/icons-material/ExitToApp"; +import KeyboardDoubleArrowRightIcon from "@mui/icons-material/KeyboardDoubleArrowRight"; +import KeyboardDoubleArrowLeftIcon from "@mui/icons-material/KeyboardDoubleArrowLeft"; +import LocalPostOfficeIcon from "@mui/icons-material/LocalPostOffice"; +import BeenhereIcon from "@mui/icons-material/Beenhere"; +import StyledForm from "@/core/components/StyledForm"; +import PaymentIcon from "@mui/icons-material/Payment"; +import React, { useState } from "react"; +import { useForm } from "react-hook-form"; +import { yupResolver } from "@hookform/resolvers/yup"; +import { mixed, object, string } from "yup"; +import InsurancePayment from "./InsurancePayment"; +import DaghiPayment from "./DaghiPayment"; +import { CONFIRM_PAYMENT_INFO } from "@/core/utils/routes"; +import useRequest from "@/lib/hooks/useRequest"; +function TabPanel(props) { + const { children, value, index } = props; + return ( + + ); +} +const validationSchema = object({ + deposit_insurance_status: string().required("انتخاب یک گزینه الزامیست."), + deposit_daghi_status: string().required("انتخاب یک گزینه الزامیست."), + deposit_insurance_amount: string().when("deposit_insurance_status", { + is: "has_insurance", + then: (schema) => schema.required("مبلغ بیمه الزامی است."), + otherwise: (schema) => schema.notRequired(), + }), + deposit_insurance_image: mixed().when("deposit_insurance_status", { + is: "has_insurance", + then: (schema) => schema.required("تصویر فیش واریزی بیمه الزامی است."), + otherwise: (schema) => schema.notRequired(), + }), + deposit_daghi_amount: string() + .when("deposit_daghi_status", { + is: "has_daghi", + then: (schema) => schema.required("مبلغ داغی الزامی است."), + otherwise: (schema) => schema.notRequired(), + }) + .test("max-percentage", "مبلغ داغی نباید بیشتر از ۳۰ درصد مبلغ کل خسارت باشد.", function (value) { + const sum = this.options.context.sum || 0; + if (!value || isNaN(value) || !sum) return true; + return parseFloat(value) <= sum * 0.3; + }), + deposit_daghi_image: mixed().when("deposit_daghi_status", { + is: "has_daghi", + then: (schema) => schema.required("تصویر صورتجلسه تحویل داغی به انبار الزامی است."), + otherwise: (schema) => schema.notRequired(), + }), +}).test("total-amount-limit", "مجموع مبلغ بیمه و داغی نباید از کل مبلغ خسارت بیشتر باشد.", function (values) { + const sum = this.options.context.sum || 0; + const depositInsuranceAmount = parseFloat(values.deposit_insurance_amount) || 0; + const depositDaghiAmount = parseFloat(values.deposit_daghi_amount) || 0; + return depositInsuranceAmount + depositDaghiAmount <= sum; +}); + +const RegisterInsuranceContent = ({ setOpenRegisterInsuranceDialog, row, mutate, rowId }) => { + const [tabState, setTabState] = useState(0); + const requestServer = useRequest({ notificationSuccess: true }); + const handleClose = () => setOpenRegisterInsuranceDialog(false); + const handleChangeTab = (event, newValue) => setTabState(newValue); + + const defaultValues = { + deposit_insurance_status: "no_insurance", + deposit_insurance_amount: "", + deposit_insurance_image: null, + deposit_daghi_status: "no_daghi", + deposit_daghi_amount: "", + deposit_daghi_image: null, + }; + const handlePrev = () => { + if (tabState === 0) { + handleClose(); + } else { + setTabState(tabState - 1); + } + }; + const handleNext = async () => { + let fieldsToValidate = []; + if (tabState === 0) { + fieldsToValidate = ["deposit_insurance_status", "deposit_insurance_amount", "deposit_insurance_image"]; + } + if (tabState === 1) { + fieldsToValidate = ["deposit_daghi_status", "deposit_daghi_amount", "deposit_daghi_image"]; + } + + const isValid = await trigger(fieldsToValidate); + if (isValid) { + setTabState(tabState + 1); + } + }; + + const { + control, + handleSubmit, + trigger, + formState: { isSubmitting, errors }, + } = useForm({ + defaultValues, + resolver: yupResolver(validationSchema), + mode: "all", + context: { sum: row?.original?.sum }, + }); + + const onSubmitBase = async (data) => { + const formData = new FormData(); + const depositDaghiStatus = data.deposit_daghi_status === "no_daghi" ? 0 : 1; + const depositInsuranceStatus = data.deposit_insurance_status === "no_insurance" ? 0 : 1; + + if (data.deposit_daghi_status === "has_daghi") { + formData.append("deposit_daghi_status", depositDaghiStatus); + formData.append("deposit_daghi_amount", data.deposit_daghi_amount); + formData.append("deposit_daghi_image", data.deposit_daghi_image); + } + if (data.deposit_insurance_status === "has_insurance") { + formData.append("deposit_insurance_status", depositInsuranceStatus); + formData.append("deposit_insurance_amount", data.deposit_insurance_amount); + formData.append("deposit_insurance_image", data.deposit_insurance_image); + } + try { + await requestServer(`${CONFIRM_PAYMENT_INFO}/${rowId}`, "post", { + data: formData, + }); + mutate(); + setOpenRegisterInsuranceDialog(false); + } catch (error) {} + }; + return ( + + + } label="فیش واریزی بیمه" /> + } label="صورتجلسه تحویل داغی به انبار" /> + + + + + + + + + + + + {errors[""]?.message && ( + + {errors[""].message} + + )} + + + {tabState !== 1 && ( + + )} + {tabState === 1 && ( + + )} + + + ); +}; +export default RegisterInsuranceContent; diff --git a/src/components/dashboard/damages/operator/Form/RegisterInsurance/index.jsx b/src/components/dashboard/damages/operator/Form/RegisterInsurance/index.jsx new file mode 100644 index 0000000..9a4e05e --- /dev/null +++ b/src/components/dashboard/damages/operator/Form/RegisterInsurance/index.jsx @@ -0,0 +1,53 @@ +import { Dialog, IconButton, Tooltip } from "@mui/material"; +import { useState } from "react"; +import ReceiptIcon from "@mui/icons-material/Receipt"; +import RegisterInsuranceContent from "./RegisterInsuranceContent"; +import CloseIcon from "@mui/icons-material/Close"; + +const RegisterInsurance = ({ row, mutate, rowId }) => { + const [openRegisterInsuranceDialog, setOpenRegisterInsuranceDialog] = useState(false); + + return ( + <> + + setOpenRegisterInsuranceDialog(true)}> + + + + + setOpenRegisterInsuranceDialog(false)} + sx={(theme) => ({ + position: "absolute", + right: 8, + top: 8, + zIndex: 50, + color: theme.palette.grey[500], + })} + > + + + {openRegisterInsuranceDialog && ( + + )} + + + ); +}; +export default RegisterInsurance; diff --git a/src/components/dashboard/damages/operator/OperatorList.jsx b/src/components/dashboard/damages/operator/OperatorList.jsx new file mode 100644 index 0000000..15907c0 --- /dev/null +++ b/src/components/dashboard/damages/operator/OperatorList.jsx @@ -0,0 +1,466 @@ +"use client"; +import { useEffect, useMemo, useState } from "react"; +import { Box, Stack, Typography } from "@mui/material"; +import DataTableWithAuth from "@/core/components/DataTableWithAuth"; +import Toolbar from "./Toolbar"; +import { GET_TECHNICAL_DAMAGE_OPERATOR_LIST } from "@/core/utils/routes"; +import moment from "jalali-moment"; +import RowActions from "./RowActions"; +import useProvinces from "@/lib/hooks/useProvince"; +import CustomSelectByDependency from "@/core/components/DataTable/filter/fieldsType/CustomSelectByDependency"; +import useEdaratLists from "@/lib/hooks/useEdaratLists"; +import ImageDialog from "./RowActions/ImageDialog"; +import LocationForm from "./RowActions/LocationForm"; +import ShowPlate from "./RowActions/ShowPlate"; +import DamageItemDialog from "./RowActions/DamageItemDialog"; +import DamageReportDialog from "./RowActions/DamageReport"; + +const OperatorList = () => { + const statusOptions = [ + { value: "", label: "همه وضعیت ها" }, + { value: 0, label: "بدون اقدام" }, + { value: 1, label: "صدور نامه بیمه و کارشناسی داغی" }, + { value: 2, label: "فیش ها ثبت شده است" }, + { value: 3, label: "فاکتور صادر شده است" }, + { value: 4, label: "فاکتور پرداخت شده است" }, + { value: 5, label: "نامه پلیس راه صادر شده است" }, + ]; + const columns = useMemo(() => { + return [ + { + accessorKey: "id", + header: "کد یکتا", // Unique Code + id: "id", + enableColumnFilter: true, + datatype: "text", + filterMode: "equals", + sortDescFirst: false, + columnFilterModeOptions: ["equals", "contains"], + grow: false, + size: 100, + }, + { + header: "اطلاعات تصادف", + id: "accidentInfo", + enableColumnFilter: false, + enableSorting: false, + grow: false, + size: 50, + columns: [ + { + accessorKey: "province_id", + header: "استان", + id: "province_id", + enableColumnFilter: true, + datatype: "numeric", + filterMode: "equals", + grow: false, + size: 130, + ColumnSelectComponent: (props) => { + const { provinces, errorProvinces, loadingProvinces } = useProvinces(); + const getColumnSelectOptions = useMemo(() => { + if (loadingProvinces) { + return [{ value: "loading", label: "در حال بارگذاری..." }]; + } + if (errorProvinces) { + return [{ value: "error", label: "خطا در بارگذاری" }]; + } + return [ + { value: "", label: "کل کشور" }, + ...provinces.map((province) => ({ + value: province.id, + label: province.name_fa, + })), + ]; + }, [provinces, errorProvinces, loadingProvinces]); + return ( + + ); + }, + Cell: ({ row }) => <>{row.original.province_fa}, + }, + { + accessorKey: "city_id", + header: "شهرستان", // Office + id: "city_id", + enableColumnFilter: true, + datatype: "numeric", + filterMode: "equals", + dependencyId: "province_id", + grow: false, + size: 120, + ColumnSelectComponent: (props) => { + const { edaratList, loadingEdaratList, errorEdaratList } = useEdaratLists( + props.dependencyFieldValue.value + ); + const [prevDependency, setPrevDependency] = useState(props.dependencyFieldValue.value); + + const getColumnSelectOptions = useMemo(() => { + if (props.dependencyFieldValue.value === "") { + return [{ value: "empty", label: "ابتدا استان را انتخاب کنید" }]; + } + if (loadingEdaratList) { + return [{ value: "loading", label: "در حال بارگذاری..." }]; + } + if (errorEdaratList) { + return [{ value: "error", label: "خطا در بارگذاری" }]; + } + return [ + { value: "", label: "کل شهر ها" }, + ...edaratList.map((edare) => ({ + value: edare.id, + label: edare.name_fa, + })), + ]; + }, [edaratList, loadingEdaratList, errorEdaratList]); + useEffect(() => { + if (prevDependency === props.dependencyFieldValue?.value) return; + props.handleChange({ ...props.filterParameters, value: "" }); + setPrevDependency(props.dependencyFieldValue?.value); + }, [props.dependencyFieldValue?.value]); + return ( + + ); + }, + Cell: ({ row }) => <>{row.original.city_fa}, + }, + { + accessorKey: "axis_name", + header: "نام محور", + id: "axis_name", + enableColumnFilter: true, + enableSorting: true, + datatype: "text", + filterMode: "equals", + columnFilterModeOptions: ["equals", "contains"], + grow: false, + size: 120, + }, + { + accessorKey: "driver_name", + header: "نام راننده", + id: "driver_name", + enableColumnFilter: true, + enableSorting: true, + datatype: "text", + filterMode: "equals", + columnFilterModeOptions: ["equals", "contains"], + grow: false, + size: 120, + }, + { + accessorKey: "driver_national_code", + header: "کد ملی راننده", + id: "driver_national_code", + enableColumnFilter: true, + enableSorting: true, + datatype: "text", + filterMode: "equals", + columnFilterModeOptions: ["equals", "contains"], + grow: false, + size: 120, + }, + { + accessorKey: "driver_phone_number", + header: "شماره موبایل راننده", + id: "driver_phone_number", + enableColumnFilter: true, + enableSorting: true, + datatype: "text", + filterMode: "equals", + columnFilterModeOptions: ["equals", "contains"], + grow: false, + size: 120, + }, + { + accessorKey: "plaque", + header: "پلاک", + id: "plaque", + enableColumnFilter: false, + datatype: "text", + filterMode: "equals", + enableSorting: false, + grow: false, + size: 100, + Cell: ({ renderedCellValue }) => , + }, + { + accessorKey: "accident_type", + header: "نوع تصادف", // Value + id: "accident_type", + enableColumnFilter: true, + enableSorting: true, + datatype: "text", + filterMode: "equals", + columnFilterModeOptions: ["equals", "contains"], + grow: false, + size: 120, + Cell: ({ row }) => <>{row.original.accident_type_fa}, + }, + { + accessorFn: (row) => { + const formattedDate = moment(row.accident_date, "YYYY-MM-DD"); // تنظیم فرمت صحیح تاریخ + const formattedTime = row.accident_time || "00:00:00"; // در صورت خالی بودن مقدار، مقدار پیش‌فرض داده شود + return moment(`${formattedDate.format("YYYY-MM-DD")}T${formattedTime}`) + .locale("fa") + .format("HH:mm | YYYY/MM/DD"); + }, + header: "تاریخ تصادف", + id: "accident_date", + enableColumnFilter: true, + datatype: "date", + filterMode: "between", + grow: false, + size: 100, + }, + { + accessorKey: "files", + header: "تصاویر", + id: "files", + enableColumnFilter: false, + enableSorting: false, + datatype: "array", + grow: false, + size: 100, + muiTableBodyCellProps: { + sx: { + borderLeft: "1px solid #e1e1e1", + py: 0, + "&:first-of-type": { + borderLeft: "unset", + }, + }, + }, + Cell: ({ row }) => { + if (row.original.damage_picture1) { + const imagesList = [row.original.damage_picture1, row.original.damage_picture2]; + return ( + + + + ); + } + return ( + + بدون تصویر + + ); + }, + }, + { + accessorKey: "location", + header: "موقعیت", + id: "location", + enableColumnFilter: false, + enableSorting: false, + datatype: "array", + grow: false, + size: 100, + muiTableBodyCellProps: { + sx: { + borderLeft: "1px solid #e1e1e1", + py: 0, + "&:first-of-type": { + borderLeft: "unset", + }, + }, + }, + Cell: ({ row }) => { + return ( + + + + ); + }, + }, + ], + }, + { + header: "اطلاعات خسارت", + id: "damagesInfo", + enableColumnFilter: false, + enableSorting: false, + grow: false, + size: 50, + columns: [ + { + header: "گزارش خسارت", + id: "damageReport", + enableColumnFilter: false, + enableSorting: false, + datatype: "array", + grow: false, + size: 100, + muiTableBodyCellProps: { + sx: { + borderLeft: "1px solid #e1e1e1", + py: 0, + "&:first-of-type": { + borderLeft: "unset", + }, + }, + }, + Cell: ({ row }) => { + return ( + + + + ); + }, + }, + { + header: "آیتم های خسارت", + id: "damageItems", + enableColumnFilter: false, + enableSorting: false, + datatype: "array", + grow: false, + size: 100, + muiTableBodyCellProps: { + sx: { + borderLeft: "1px solid #e1e1e1", + py: 0, + "&:first-of-type": { + borderLeft: "unset", + }, + }, + }, + Cell: ({ row }) => { + return ( + + + + ); + }, + }, + { + accessorKey: "sum", + header: "مبلغ کل خسارت (ریال)", + id: "sum", + enableColumnFilter: true, + datatype: "text", + filterMode: "equals", + columnFilterModeOptions: ["equals", "contains"], + sortDescFirst: true, + grow: false, + size: 100, + Cell: ({ row }) => ( + + {(row.original.sum / 1).toLocaleString()} + + ), + }, + ], + }, + { + header: "اطلاعات واریز", + id: "items", + enableColumnFilter: false, + enableSorting: false, + grow: false, + size: 50, + columns: [ + { + header: "مبلغ فیش (ریال)", + id: "final_amount", + enableColumnFilter: false, + grow: false, + size: 100, + Cell: ({ row }) => ( + + {(row.original.final_amount / 1).toLocaleString()} + + ), + }, + { + header: "مبلغ بیمه (ریال)", + id: "deposit_insurance_amount", + enableColumnFilter: false, + grow: false, + size: 100, + Cell: ({ row }) => ( + + {(row.original.deposit_insurance_amount / 1).toLocaleString()} + + ), + }, + { + header: "مبلغ داغی (ریال)", + id: "deposit_daghi_amount", + enableColumnFilter: false, + grow: false, + size: 100, + Cell: ({ row }) => ( + + {(row.original.deposit_daghi_amount / 1).toLocaleString()} + + ), + }, + ], + }, + { + accessorFn: (row) => moment(row.created_at).locale("fa").format("HH:mm | yyyy/MM/DD"), + header: "تاریخ ثبت", + id: "created_at", + enableColumnFilter: true, + datatype: "date", + filterMode: "between", + grow: false, + size: 100, + }, + { + accessorKey: "status", + header: "وضعیت", + id: "status", + enableColumnFilter: true, + datatype: "numeric", + filterMode: "equals", + grow: false, + size: 100, + columnSelectOption: () => { + return statusOptions.map((status) => ({ + value: status.value, + label: status.label, + })); + }, + Cell: ({ row }) => { + return {row.original.status_fa}; + }, + }, + ]; + }, []); + + return ( + <> + + + + + ); +}; +export default OperatorList; diff --git a/src/components/dashboard/damages/operator/RowActions/DamageItemDialog/DamageItemContent.jsx b/src/components/dashboard/damages/operator/RowActions/DamageItemDialog/DamageItemContent.jsx new file mode 100644 index 0000000..e9a76e5 --- /dev/null +++ b/src/components/dashboard/damages/operator/RowActions/DamageItemDialog/DamageItemContent.jsx @@ -0,0 +1,76 @@ +import DialogLoading from "@/core/components/DialogLoading"; +import { GET_DAMAGE_ITEM_DETAILS, GET_RAHDARAN_ROAD_ITEM } from "@/core/utils/routes"; +import useRequest from "@/lib/hooks/useRequest"; +import { + DialogContent, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Typography, +} from "@mui/material"; +import { useEffect, useState } from "react"; + +const DamageItemContent = ({ rowId }) => { + const [loading, setLoading] = useState(true); + const [data, setData] = useState(null); + const request = useRequest(); + + useEffect(() => { + const fetchData = async () => { + try { + setLoading(true); + const response = await request(`${GET_DAMAGE_ITEM_DETAILS}/${rowId}`); + setData(response.data.data.damages); + } catch (error) { + } finally { + setLoading(false); + } + }; + + fetchData(); + }, [rowId]); + + return ( + <> + + {loading ? ( + + ) : data ? ( + + + + + نوع خسارت + میزان خسارت + هزینه خسارت (ریال) + + + + {data.map((item) => { + return ( + + {item?.title} + + {(item?.pivot?.value / 1).toLocaleString()} ({item?.pivot?.unit}) + + {(item?.pivot?.amount / 1).toLocaleString()} + + ); + })} + +
+
+ ) : ( + اطلاعات یافت نشد + )} +
+ + ); +}; +export default DamageItemContent; diff --git a/src/components/dashboard/damages/operator/RowActions/DamageItemDialog/index.jsx b/src/components/dashboard/damages/operator/RowActions/DamageItemDialog/index.jsx new file mode 100644 index 0000000..6164de2 --- /dev/null +++ b/src/components/dashboard/damages/operator/RowActions/DamageItemDialog/index.jsx @@ -0,0 +1,43 @@ +import { Button, Dialog, DialogActions, DialogTitle, IconButton, Tooltip } from "@mui/material"; +import MinorCrashIcon from "@mui/icons-material/MinorCrash"; +import { useState } from "react"; +import DamageItemContent from "./DamageItemContent"; + +const DamageItemDialog = ({ rowId }) => { + const [openDamageItemDialog, setOpenDamageItemDialog] = useState(false); + return ( + <> + + setOpenDamageItemDialog(true)}> + + + + setOpenDamageItemDialog(false)} + PaperProps={{ + sx: { + boxShadow: "rgba(60, 64, 67, 0.3) 0px 1px 16px 0px, rgba(60, 64, 67, 0.15) 0px 1px 10px 0px", + }, + }} + dir="rtl" + maxWidth={"md"} + > + آیتم های خسارت + {openDamageItemDialog && } + + + + + + ); +}; +export default DamageItemDialog; diff --git a/src/components/dashboard/damages/operator/RowActions/DamageReport/DamageReportContent.jsx b/src/components/dashboard/damages/operator/RowActions/DamageReport/DamageReportContent.jsx new file mode 100644 index 0000000..3876c57 --- /dev/null +++ b/src/components/dashboard/damages/operator/RowActions/DamageReport/DamageReportContent.jsx @@ -0,0 +1,103 @@ +import { Box, Button, Chip, DialogContent, Divider, Grid, Typography } from "@mui/material"; +import PaymentIcon from "@mui/icons-material/Payment"; +import moment from "jalali-moment"; +import DownloadIcon from "@mui/icons-material/Download"; +import FormatListNumberedIcon from "@mui/icons-material/FormatListNumbered"; +import EventNoteIcon from "@mui/icons-material/EventNote"; +import ImageIcon from "@mui/icons-material/Image"; + +const DamageReportContent = ({ damageReportDetails }) => { + return ( + <> + + + + + } + label={ + + نوع گزارش + + } + /> + + + + {damageReportDetails?.report_base === 1 + ? "بازدید عوامل و کارشناسان راهداری" + : "کروکی یا نامه پلیس راه"} + + + {damageReportDetails?.report_base === 0 && ( + <> + + + } + label={ + + شماره کروکی یا نامه پلیس راه + + } + /> + + + + {damageReportDetails?.police_serial} + + + + + } + label={ + + تاریخ کروکی یا نامه پلیس راه + + } + /> + + + + {moment(damageReportDetails?.police_file_date, "YYYY-MM-DD HH:mm:ss") + .locale("fa") + .format("YYYY/MM/DD")} + + + + + } + label={ + + تصویر کروکی یا نامه پلیس راه + + } + /> + + + + + + )} + + + + ); +}; +export default DamageReportContent; diff --git a/src/components/dashboard/damages/operator/RowActions/DamageReport/DamageReportController.jsx b/src/components/dashboard/damages/operator/RowActions/DamageReport/DamageReportController.jsx new file mode 100644 index 0000000..402911f --- /dev/null +++ b/src/components/dashboard/damages/operator/RowActions/DamageReport/DamageReportController.jsx @@ -0,0 +1,34 @@ +import useRequest from "@/lib/hooks/useRequest"; +import { useEffect, useState } from "react"; +import { GET_DAMAGE_ITEM_DETAILS } from "@/core/utils/routes"; +import DialogLoading from "@/core/components/DialogLoading"; +import DamageReportContent from "./DamageReportContent"; + +const DamageReportController = ({ rowId }) => { + const requestServer = useRequest(); + const [damageReportDetails, setDamageReportDetails] = useState(null); + const [damageItemDetailsLoading, setDamageItemDetailsLoading] = useState(false); + + useEffect(() => { + setDamageItemDetailsLoading(true); + requestServer(`${GET_DAMAGE_ITEM_DETAILS}/${rowId}`, "get") + .then((response) => { + setDamageReportDetails(response.data.data); + setDamageItemDetailsLoading(false); + }) + .catch((e) => { + setDamageItemDetailsLoading(false); + }); + }, [rowId]); + + return ( + <> + {damageItemDetailsLoading ? ( + + ) : ( + + )} + + ); +}; +export default DamageReportController; diff --git a/src/components/dashboard/damages/operator/RowActions/DamageReport/index.jsx b/src/components/dashboard/damages/operator/RowActions/DamageReport/index.jsx new file mode 100644 index 0000000..be283ab --- /dev/null +++ b/src/components/dashboard/damages/operator/RowActions/DamageReport/index.jsx @@ -0,0 +1,38 @@ +import React, { useState } from "react"; +import { Tooltip, IconButton, Dialog, DialogTitle, DialogActions, Button } from "@mui/material"; +import AssessmentIcon from "@mui/icons-material/Assessment"; +import DamageReportController from "./DamageReportController"; +const DamageReportDialog = ({ rowId }) => { + const [openDamageReportDialog, setOpenDamageReportDialog] = useState(false); + + return ( + <> + + setOpenDamageReportDialog(true)}> + + + + setOpenDamageReportDialog(false)} + PaperProps={{ + sx: { + boxShadow: "rgba(60, 64, 67, 0.3) 0px 1px 16px 0px, rgba(60, 64, 67, 0.15) 0px 1px 10px 0px", + }, + }} + dir="rtl" + maxWidth={"sm"} + fullWidth + > + گزارش خسارت + {openDamageReportDialog && } + + + + + + ); +}; +export default DamageReportDialog; diff --git a/src/components/dashboard/damages/operator/RowActions/DeleteDialog/DeleteContent.jsx b/src/components/dashboard/damages/operator/RowActions/DeleteDialog/DeleteContent.jsx new file mode 100644 index 0000000..28791c7 --- /dev/null +++ b/src/components/dashboard/damages/operator/RowActions/DeleteDialog/DeleteContent.jsx @@ -0,0 +1,39 @@ +import { Button, DialogActions, DialogContent, Stack, Typography } from "@mui/material"; +import React, { useState } from "react"; +import useRequest from "@/lib/hooks/useRequest"; +import { DELETE_DAMAGE_ITEM } from "@/core/utils/routes"; + +const DeleteContent = ({ rowId, mutate, setOpenDeleteDialog }) => { + const [submitting, setSubmitting] = useState(false); + const requestServer = useRequest({ notificationSuccess: true }); + const handleClick = () => { + setSubmitting(true); + requestServer(`${DELETE_DAMAGE_ITEM}/${rowId}`, "delete") + .then(() => { + mutate(); + setOpenDeleteDialog(false); + setSubmitting(false); + }) + .catch(() => { + setSubmitting(false); + }); + }; + return ( + <> + + + آیا از حذف فعالیت اطمینان دارید؟ + + + + + + + + ); +}; +export default DeleteContent; diff --git a/src/components/dashboard/damages/operator/RowActions/DeleteDialog/index.jsx b/src/components/dashboard/damages/operator/RowActions/DeleteDialog/index.jsx new file mode 100644 index 0000000..145e323 --- /dev/null +++ b/src/components/dashboard/damages/operator/RowActions/DeleteDialog/index.jsx @@ -0,0 +1,34 @@ +import DeleteIcon from "@mui/icons-material/Delete"; +import { Dialog, DialogTitle, IconButton, Tooltip } from "@mui/material"; +import { useState } from "react"; +import DeleteContent from "./DeleteContent"; +const DeleteDialog = ({ rowId, mutate }) => { + const [openDeleteDialog, setOpenDeleteDialog] = useState(false); + + return ( + <> + + setOpenDeleteDialog(true)}> + + + + setOpenDeleteDialog(false)} + PaperProps={{ + sx: { + boxShadow: "rgba(60, 64, 67, 0.3) 0px 1px 16px 0px, rgba(60, 64, 67, 0.15) 0px 1px 10px 0px", + }, + }} + dir="rtl" + maxWidth={"md"} + > + حذف فعالیت + {openDeleteDialog && ( + + )} + + + ); +}; +export default DeleteDialog; diff --git a/src/components/dashboard/damages/operator/RowActions/ImageDialog/ImageFormContent.jsx b/src/components/dashboard/damages/operator/RowActions/ImageDialog/ImageFormContent.jsx new file mode 100644 index 0000000..c8f22d0 --- /dev/null +++ b/src/components/dashboard/damages/operator/RowActions/ImageDialog/ImageFormContent.jsx @@ -0,0 +1,45 @@ +import { Box, Stack, Typography, useTheme } from "@mui/material"; +import Image from "next/image"; + +const ImageFormContent = ({ image, title }) => { + const theme = useTheme(); + return ( + + + {title} + + + Image + + + ); +}; +export default ImageFormContent; diff --git a/src/components/dashboard/damages/operator/RowActions/ImageDialog/index.jsx b/src/components/dashboard/damages/operator/RowActions/ImageDialog/index.jsx new file mode 100644 index 0000000..74ade1d --- /dev/null +++ b/src/components/dashboard/damages/operator/RowActions/ImageDialog/index.jsx @@ -0,0 +1,44 @@ +import { Button, Dialog, DialogActions, DialogContent, DialogTitle, IconButton, Paper, Tooltip } from "@mui/material"; +import { useState } from "react"; +import PhotoLibraryIcon from "@mui/icons-material/PhotoLibrary"; +import ImageFormContent from "./ImageFormContent"; + +const ImageDialog = ({ images }) => { + const [openImageDialog, setOpenImageDialog] = useState(false); + + return ( + <> + + setOpenImageDialog(true)}> + + + + setOpenImageDialog(false)} + PaperProps={{ + sx: { + boxShadow: "rgba(60, 64, 67, 0.3) 0px 1px 16px 0px, rgba(60, 64, 67, 0.15) 0px 1px 10px 0px", + }, + }} + maxWidth={"sm"} + scroll="paper" + > + تصاویر + {openImageDialog && ( + + + + + )} + + + + + + ); +}; +export default ImageDialog; diff --git a/src/components/dashboard/damages/operator/RowActions/LocationForm/LocationFormContent.jsx b/src/components/dashboard/damages/operator/RowActions/LocationForm/LocationFormContent.jsx new file mode 100644 index 0000000..14b3db3 --- /dev/null +++ b/src/components/dashboard/damages/operator/RowActions/LocationForm/LocationFormContent.jsx @@ -0,0 +1,35 @@ +"use client"; +import { Box, Button, DialogActions, DialogContent, Stack } from "@mui/material"; +import React from "react"; +import dynamic from "next/dynamic"; +import MapLoading from "@/core/components/MapLayer/Loading"; +import ShowLocationMarker from "@/core/components/ShowLocationMarker"; +const MapLayer = dynamic(() => import("@/core/components/MapLayer"), { + loading: () => , + ssr: false, +}); + +const LocationFormContent = ({ setOpenLocationDialog, start_lat, start_lng, end_lat, end_lng }) => { + return ( + <> + + + + + + + + + + + + ); +}; +export default LocationFormContent; diff --git a/src/components/dashboard/damages/operator/RowActions/LocationForm/index.jsx b/src/components/dashboard/damages/operator/RowActions/LocationForm/index.jsx new file mode 100644 index 0000000..d614215 --- /dev/null +++ b/src/components/dashboard/damages/operator/RowActions/LocationForm/index.jsx @@ -0,0 +1,40 @@ +import React, { useState } from "react"; +import { Tooltip, IconButton, Dialog, DialogTitle } from "@mui/material"; +import ExploreIcon from "@mui/icons-material/Explore"; +import LocationFormContent from "./LocationFormContent"; +const LocationForm = ({ start_lat, start_lng, end_lat, end_lng }) => { + const [openLocationDialog, setOpenLocationDialog] = useState(false); + + return ( + <> + + setOpenLocationDialog(true)}> + + + + setOpenLocationDialog(false)} + PaperProps={{ + sx: { + boxShadow: "rgba(60, 64, 67, 0.3) 0px 1px 16px 0px, rgba(60, 64, 67, 0.15) 0px 1px 10px 0px", + }, + }} + dir="rtl" + maxWidth={"md"} + > + موقعیت + {openLocationDialog && ( + + )} + + + ); +}; +export default LocationForm; diff --git a/src/components/dashboard/damages/operator/RowActions/PoliceRahLetter/PoliceRahLetterContent.jsx b/src/components/dashboard/damages/operator/RowActions/PoliceRahLetter/PoliceRahLetterContent.jsx new file mode 100644 index 0000000..a4d4b63 --- /dev/null +++ b/src/components/dashboard/damages/operator/RowActions/PoliceRahLetter/PoliceRahLetterContent.jsx @@ -0,0 +1,40 @@ +import { Button, DialogActions, DialogContent, Stack, Typography } from "@mui/material"; +import React, { useState } from "react"; +import useRequest from "@/lib/hooks/useRequest"; +import { GET_POLICERAH, GET_POLICERAH_PAGE } from "@/core/utils/routes"; + +const PoliceRahLetterContent = ({ setOpenPoliceRahDialog, rowId, mutate }) => { + const [submitting, setSubmitting] = useState(false); + const requestServer = useRequest(); + const handleClick = () => { + setSubmitting(true); + requestServer(`${GET_POLICERAH}/${rowId}`, "get") + .then(() => { + mutate(); + window.open(`${GET_POLICERAH_PAGE}/${rowId}`, "_blank"); + setSubmitting(false); + setOpenPoliceRahDialog(false); + }) + .catch(() => { + setSubmitting(false); + }); + }; + return ( + <> + + + آیا از صدور نامه به پلیس راه اطمینان دارید؟ + + + + + + + + ); +}; +export default PoliceRahLetterContent; diff --git a/src/components/dashboard/damages/operator/RowActions/PoliceRahLetter/index.jsx b/src/components/dashboard/damages/operator/RowActions/PoliceRahLetter/index.jsx new file mode 100644 index 0000000..ab064d9 --- /dev/null +++ b/src/components/dashboard/damages/operator/RowActions/PoliceRahLetter/index.jsx @@ -0,0 +1,38 @@ +import { Dialog, DialogTitle, IconButton, Tooltip } from "@mui/material"; +import GarageIcon from "@mui/icons-material/Garage"; +import React, { useState } from "react"; +import PoliceRahLetterContent from "./PoliceRahLetterContent"; + +const PoliceRahLetter = ({ rowId, mutate }) => { + const [openPoliceRahDialog, setOpenPoliceRahDialog] = useState(false); + return ( + <> + + setOpenPoliceRahDialog(true)}> + + + + setOpenPoliceRahDialog(false)} + PaperProps={{ + sx: { + boxShadow: "rgba(60, 64, 67, 0.3) 0px 1px 16px 0px, rgba(60, 64, 67, 0.15) 0px 1px 10px 0px", + }, + }} + maxWidth={"xs"} + scroll="paper" + > + صدور نامه پلیس راه + {openPoliceRahDialog && ( + + )} + + + ); +}; +export default PoliceRahLetter; diff --git a/src/components/dashboard/damages/operator/RowActions/SendToInsurance/SendToInsuranceContent.jsx b/src/components/dashboard/damages/operator/RowActions/SendToInsurance/SendToInsuranceContent.jsx new file mode 100644 index 0000000..620049a --- /dev/null +++ b/src/components/dashboard/damages/operator/RowActions/SendToInsurance/SendToInsuranceContent.jsx @@ -0,0 +1,40 @@ +import { Button, DialogActions, DialogContent, Stack, Typography } from "@mui/material"; +import React, { useState } from "react"; +import useRequest from "@/lib/hooks/useRequest"; +import { GET_INSURANCE, GET_INSURANCE_PAGE } from "@/core/utils/routes"; + +const SendToInsuranceContent = ({ setOpenSendInsuranceDialog, rowId, mutate }) => { + const [submitting, setSubmitting] = useState(false); + const requestServer = useRequest(); + const handleClick = () => { + setSubmitting(true); + requestServer(`${GET_INSURANCE}/${rowId}`, "get") + .then(() => { + mutate(); + window.open(`${GET_INSURANCE_PAGE}/${rowId}`, "_blank"); + setSubmitting(false); + setOpenSendInsuranceDialog(false); + }) + .catch(() => { + setSubmitting(false); + }); + }; + return ( + <> + + + آیا از صدور نامه بیمه اطمینان دارید؟ + + + + + + + + ); +}; +export default SendToInsuranceContent; diff --git a/src/components/dashboard/damages/operator/RowActions/SendToInsurance/index.jsx b/src/components/dashboard/damages/operator/RowActions/SendToInsurance/index.jsx new file mode 100644 index 0000000..d1d578a --- /dev/null +++ b/src/components/dashboard/damages/operator/RowActions/SendToInsurance/index.jsx @@ -0,0 +1,38 @@ +import { Dialog, DialogTitle, IconButton, Tooltip } from "@mui/material"; +import EmailIcon from "@mui/icons-material/Email"; +import React, { useState } from "react"; +import SendToInsuranceContent from "./SendToInsuranceContent"; + +const SendToInsurance = ({ rowId, mutate }) => { + const [openSendInsuranceDialog, setOpenSendInsuranceDialog] = useState(false); + return ( + <> + + setOpenSendInsuranceDialog(true)}> + + + + setOpenSendInsuranceDialog(false)} + PaperProps={{ + sx: { + boxShadow: "rgba(60, 64, 67, 0.3) 0px 1px 16px 0px, rgba(60, 64, 67, 0.15) 0px 1px 10px 0px", + }, + }} + maxWidth={"xs"} + scroll="paper" + > + نامه بیمه + {openSendInsuranceDialog && ( + + )} + + + ); +}; +export default SendToInsurance; diff --git a/src/components/dashboard/damages/operator/RowActions/ShowPlate/index.jsx b/src/components/dashboard/damages/operator/RowActions/ShowPlate/index.jsx new file mode 100644 index 0000000..af1f495 --- /dev/null +++ b/src/components/dashboard/damages/operator/RowActions/ShowPlate/index.jsx @@ -0,0 +1,38 @@ +import { Stack } from "@mui/material"; + +const ShowPlate = ({ plate }) => { + return ( + + + {plate.split("-")[3]} + + + + {plate.split("-")[2]} + + + {plate.split("-")[1]} + + + {plate.split("-")[0]} + + + + ); +}; +export default ShowPlate; diff --git a/src/components/dashboard/damages/operator/RowActions/index.jsx b/src/components/dashboard/damages/operator/RowActions/index.jsx new file mode 100644 index 0000000..f146c12 --- /dev/null +++ b/src/components/dashboard/damages/operator/RowActions/index.jsx @@ -0,0 +1,33 @@ +import { Box } from "@mui/material"; +import RegisterInsurance from "../Form/RegisterInsurance"; +import CreateFactor from "../Form/CreateFactor"; +import DeleteDialog from "./DeleteDialog"; +import SendToInsurance from "./SendToInsurance"; +import PoliceRahLetter from "./PoliceRahLetter"; +import EditForm from "../Form/EditForm"; +import { usePermissions } from "@/lib/hooks/usePermissions"; + +const RowActions = ({ row, mutate }) => { + const { data: userPermissions } = usePermissions(); + const hasDeletePermission = userPermissions.some((item) => + ["delete-receipt", "delete-receipt-province"].includes(item) + ); + const hasEditPermission = userPermissions.some((item) => ["edit-receipt", "edit-receipt-province"].includes(item)); + return ( + + {[4, 5].includes(row.original?.status) && } + {[2, 3].includes(row.original?.status) && ( + + )} + {[1, 2].includes(row.original?.status) && ( + + )} + {[0, 1, 2].includes(row.original?.status) && } + {hasEditPermission && [0, 1, 2].includes(row.original?.status) && ( + + )} + {hasDeletePermission && } + + ); +}; +export default RowActions; diff --git a/src/components/dashboard/damages/operator/Toolbar.jsx b/src/components/dashboard/damages/operator/Toolbar.jsx new file mode 100644 index 0000000..3024621 --- /dev/null +++ b/src/components/dashboard/damages/operator/Toolbar.jsx @@ -0,0 +1,16 @@ +import { Box } from "@mui/material"; +import OperatorCreate from "./Actions/create"; +import PrintExcel from "./ExcelPrint"; +import { usePermissions } from "@/lib/hooks/usePermissions"; + +const Toolbar = ({ table, filterData, mutate }) => { + const { data: userPermissions } = usePermissions(); + const hasAddPermission = userPermissions.some((item) => ["add-receipt", "add-receipt-province"].includes(item)); + return ( + + + {hasAddPermission && } + + ); +}; +export default Toolbar; diff --git a/src/components/dashboard/damages/operator/index.jsx b/src/components/dashboard/damages/operator/index.jsx new file mode 100644 index 0000000..4936664 --- /dev/null +++ b/src/components/dashboard/damages/operator/index.jsx @@ -0,0 +1,14 @@ +"use client"; +import PageTitle from "@/core/components/PageTitle"; +import { Stack } from "@mui/material"; +import OperatorList from "./OperatorList"; + +const OperatorPage = () => { + return ( + + + + + ); +}; +export default OperatorPage; diff --git a/src/components/dashboard/roadItems/reports/ExcelPrint/index.jsx b/src/components/dashboard/damages/report/ExcelPrint/index.jsx similarity index 80% rename from src/components/dashboard/roadItems/reports/ExcelPrint/index.jsx rename to src/components/dashboard/damages/report/ExcelPrint/index.jsx index eb86d27..2922a18 100644 --- a/src/components/dashboard/roadItems/reports/ExcelPrint/index.jsx +++ b/src/components/dashboard/damages/report/ExcelPrint/index.jsx @@ -1,12 +1,12 @@ "use client"; -import { Button, CircularProgress, IconButton, useMediaQuery } from "@mui/material"; -import { useMemo, useState } from "react"; -import moment from "jalali-moment"; -import FileSaver from "file-saver"; -import DescriptionIcon from "@mui/icons-material/Description"; +import { EXPORT_COUNTRY_RECEIPT_REPORT, EXPORT_PROVINCE_RECEIPT_REPORT } from "@/core/utils/routes"; import useRequest from "@/lib/hooks/useRequest"; -import { EXPORT_ROAD_ITEMS_OPERATOR_LIST } from "@/core/utils/routes"; import { useTheme } from "@emotion/react"; +import DescriptionIcon from "@mui/icons-material/Description"; +import { Button, CircularProgress, IconButton, useMediaQuery } from "@mui/material"; +import FileSaver from "file-saver"; +import moment from "jalali-moment"; +import { useMemo, useState } from "react"; const PrintExcel = ({ table, filterData }) => { const theme = useTheme(); @@ -15,8 +15,7 @@ const PrintExcel = ({ table, filterData }) => { const requestServer = useRequest({ notificationSuccess: true }); const activeFilters = Object.entries(filterData).reduce((acc, [key, filter]) => { if (filter.value) { - // Check if there's an active filter - acc.push({ id: key, value: filter.value }); + acc.push({ id: filter.id, value: filter.value }); } return acc; }, []); @@ -32,11 +31,14 @@ const PrintExcel = ({ table, filterData }) => { const clickHandler = () => { setLoading(true); - requestServer(`${EXPORT_ROAD_ITEMS_OPERATOR_LIST}?${filterParams}`, "get", { + const CountryOrProvince = activeFilters.find((item) => item.id === "province_id"); + const requestUrl = + CountryOrProvince.value === "-1" ? EXPORT_COUNTRY_RECEIPT_REPORT : EXPORT_PROVINCE_RECEIPT_REPORT; + requestServer(`${requestUrl}?${filterParams}`, "get", { requestOptions: { responseType: "blob" }, }) .then((response) => { - const filename = `خروجی کارتابل گزارشات فعالیت روزانه تاریخ_${moment().format("jYYYY_jMM_jDD")}.xlsx`; + const filename = `خروجی کارتابل گزارشات خسارات وارده بر ابنیه فنی تاریخ_${moment().format("jYYYY_jMM_jDD")}.xlsx`; FileSaver.saveAs(response.data, filename); }) .catch(() => {}) diff --git a/src/components/dashboard/damages/report/ReportLists.jsx b/src/components/dashboard/damages/report/ReportLists.jsx new file mode 100644 index 0000000..c43c9bb --- /dev/null +++ b/src/components/dashboard/damages/report/ReportLists.jsx @@ -0,0 +1,241 @@ +"use client"; +import DataTableWithAuth from "@/core/components/DataTableWithAuth"; +import { Box } from "@mui/material"; +import { useMemo } from "react"; +import Toolbar from "./Toolbar"; + +const ReportLists = ({ data, specialFilter }) => { + const columns = useMemo(() => { + return [ + { + accessorKey: "edare_name", + header: "اداره", + id: "edare_name", + enableColumnFilter: false, + enableSorting: false, + datatype: "text", + grow: false, + size: 50, + }, + { + accessorKey: "tedade", + header: "تعداد کل", + id: "tedade", + enableColumnFilter: false, + enableSorting: false, + datatype: "text", + grow: false, + size: 50, + muiTableBodyCellProps: { + sx: { + textAlign: "center", + borderLeft: "1px solid #e1e1e1", + py: 0, + "&:first-of-type": { + borderLeft: "unset", + }, + }, + }, + Cell: ({ renderedCellValue }) => { + return (renderedCellValue / 1).toLocaleString(); + }, + }, + { + header: "تعداد نحوه شناسایی خسارت", + enableColumnFilter: false, + enableSorting: false, + datatype: "text", + grow: false, + size: 50, + columns: [ + { + accessorKey: "police_rah", + header: "گزارش پلیس راه", + id: "police_rah", + enableColumnFilter: false, + enableSorting: false, + datatype: "text", + grow: false, + size: 50, + muiTableBodyCellProps: { + sx: { + textAlign: "center", + borderLeft: "1px solid #e1e1e1", + py: 0, + "&:first-of-type": { + borderLeft: "unset", + }, + }, + }, + Cell: ({ renderedCellValue }) => { + return (renderedCellValue / 1).toLocaleString(); + }, + }, + { + accessorKey: "gozaresh_gasht", + header: "گزارش گشت راهداری", + id: "gozaresh_gasht", + enableColumnFilter: false, + enableSorting: false, + datatype: "text", + grow: false, + size: 50, + muiTableBodyCellProps: { + sx: { + textAlign: "center", + borderLeft: "1px solid #e1e1e1", + py: 0, + "&:first-of-type": { + borderLeft: "unset", + }, + }, + }, + Cell: ({ renderedCellValue }) => { + return (renderedCellValue / 1).toLocaleString(); + }, + }, + ], + }, + { + accessorKey: "kol_sabt_shode", + header: "مبلغ کل خسارات ثبت شده (ریال)", + id: "kol_sabt_shode", + enableColumnFilter: false, + enableSorting: false, + datatype: "text", + grow: false, + size: 50, + muiTableBodyCellProps: { + sx: { + textAlign: "center", + borderLeft: "1px solid #e1e1e1", + py: 0, + "&:first-of-type": { + borderLeft: "unset", + }, + }, + }, + Cell: ({ renderedCellValue }) => { + return (renderedCellValue / 1).toLocaleString(); + }, + }, + { + accessorKey: "vasel_shode", + header: "کل مبلغ وصول شده (ریال)", + id: "vasel_shode", + enableColumnFilter: false, + enableSorting: false, + datatype: "text", + grow: false, + size: 50, + muiTableBodyCellProps: { + sx: { + textAlign: "center", + borderLeft: "1px solid #e1e1e1", + py: 0, + "&:first-of-type": { + borderLeft: "unset", + }, + }, + }, + Cell: ({ renderedCellValue }) => { + return (renderedCellValue / 1).toLocaleString(); + }, + }, + { + header: "مبلغ وصول شده به تفکیک نوع پرداخت (ریال)", + enableColumnFilter: false, + enableSorting: false, + datatype: "text", + grow: false, + size: 50, + columns: [ + { + accessorKey: "vasel_shode_final", + header: "فیش", + id: "vasel_shode_final", + enableColumnFilter: false, + enableSorting: false, + datatype: "text", + grow: false, + size: 50, + muiTableBodyCellProps: { + sx: { + textAlign: "center", + borderLeft: "1px solid #e1e1e1", + py: 0, + "&:first-of-type": { + borderLeft: "unset", + }, + }, + }, + Cell: ({ renderedCellValue }) => { + return (renderedCellValue / 1).toLocaleString(); + }, + }, + { + accessorKey: "vasel_shode_bimeh", + header: "بیمه", + id: "vasel_shode_bimeh", + enableColumnFilter: false, + enableSorting: false, + datatype: "text", + grow: false, + size: 50, + muiTableBodyCellProps: { + sx: { + textAlign: "center", + borderLeft: "1px solid #e1e1e1", + py: 0, + "&:first-of-type": { + borderLeft: "unset", + }, + }, + }, + Cell: ({ renderedCellValue }) => { + return (renderedCellValue / 1).toLocaleString(); + }, + }, + { + accessorKey: "vasel_shode_daghi", + header: "داغی", + id: "vasel_shode_daghi", + enableColumnFilter: false, + enableSorting: false, + datatype: "text", + grow: false, + size: 50, + muiTableBodyCellProps: { + sx: { + textAlign: "center", + borderLeft: "1px solid #e1e1e1", + py: 0, + "&:first-of-type": { + borderLeft: "unset", + }, + }, + }, + Cell: ({ renderedCellValue }) => { + return (renderedCellValue / 1).toLocaleString(); + }, + }, + ], + }, + ]; + }, []); + + return ( + + + + ); +}; + +export default ReportLists; diff --git a/src/components/dashboard/damages/report/Search/FromDateController.jsx b/src/components/dashboard/damages/report/Search/FromDateController.jsx new file mode 100644 index 0000000..02d92bd --- /dev/null +++ b/src/components/dashboard/damages/report/Search/FromDateController.jsx @@ -0,0 +1,28 @@ +import { Controller } from "react-hook-form"; +import MuiDatePicker from "@/core/components/MuiDatePicker"; +import React from "react"; +import moment from "jalali-moment"; + +const FromDateController = ({ control }) => { + return ( + ( + { + const formattedDate = moment(new Date(newValue)).locale("en").format("YYYY-MM-DD"); + onChange(formattedDate); // Update the field value in react-hook-form + }} + helperText={error ? error.message : null} + /> + )} + /> + ); +}; +export default FromDateController; diff --git a/src/components/dashboard/damages/report/Search/SearchReportField.jsx b/src/components/dashboard/damages/report/Search/SearchReportField.jsx new file mode 100644 index 0000000..ab8442c --- /dev/null +++ b/src/components/dashboard/damages/report/Search/SearchReportField.jsx @@ -0,0 +1,45 @@ +import { Button, Grid, LinearProgress, Typography } from "@mui/material"; +import SearchIcon from "@mui/icons-material/Search"; +import React from "react"; +import FromDateController from "./FromDateController"; +import ToDateController from "./ToDateController"; +import SelectProvince from "./SelectProvince"; +import { Controller } from "react-hook-form"; + +const SearchReportField = ({ control, hasProvincesPermission }) => { + return ( + + + + + + + + {hasProvincesPermission && ( + + + + )} + + ( + + )} + /> + + + ); +}; + +export default SearchReportField; diff --git a/src/components/dashboard/damages/report/Search/SelectProvince.jsx b/src/components/dashboard/damages/report/Search/SelectProvince.jsx new file mode 100644 index 0000000..b1d8a39 --- /dev/null +++ b/src/components/dashboard/damages/report/Search/SelectProvince.jsx @@ -0,0 +1,47 @@ +import { Controller } from "react-hook-form"; +import MuiDatePicker from "@/core/components/MuiDatePicker"; +import React from "react"; +import { FormControl, FormHelperText, InputLabel, LinearProgress, MenuItem, Select, Typography } from "@mui/material"; +import useProvinces from "@/lib/hooks/useProvince"; + +const SelectProvince = ({ control }) => { + const { provinces, loadingProvinces, errorProvinces } = useProvinces(); + return ( + ( + + استان + + {error && {error.message}} + + )} + /> + ); +}; +export default SelectProvince; diff --git a/src/components/dashboard/damages/report/Search/ToDateController.jsx b/src/components/dashboard/damages/report/Search/ToDateController.jsx new file mode 100644 index 0000000..4e41124 --- /dev/null +++ b/src/components/dashboard/damages/report/Search/ToDateController.jsx @@ -0,0 +1,30 @@ +import { Controller, useWatch } from "react-hook-form"; +import MuiDatePicker from "@/core/components/MuiDatePicker"; +import React from "react"; +import moment from "jalali-moment"; + +const ToDateController = ({ control }) => { + const minDate = useWatch({ control, name: "from_date" }); + return ( + ( + { + const formattedDate = moment(new Date(newValue)).locale("en").format("YYYY-MM-DD"); + onChange(formattedDate); // Update the field value in react-hook-form + }} + helperText={error ? error.message : null} + /> + )} + /> + ); +}; +export default ToDateController; diff --git a/src/components/dashboard/damages/report/Search/index.jsx b/src/components/dashboard/damages/report/Search/index.jsx new file mode 100644 index 0000000..f29d302 --- /dev/null +++ b/src/components/dashboard/damages/report/Search/index.jsx @@ -0,0 +1,13 @@ +import StyledForm from "@/core/components/StyledForm"; +import SearchReportField from "./SearchReportField"; + +const SearchReportList = ({ handleSubmit, onSearchSubmit, control, hasProvincesPermission }) => { + return ( + <> + + + + + ); +}; +export default SearchReportList; diff --git a/src/components/dashboard/damages/report/Toolbar.jsx b/src/components/dashboard/damages/report/Toolbar.jsx new file mode 100644 index 0000000..7dd930f --- /dev/null +++ b/src/components/dashboard/damages/report/Toolbar.jsx @@ -0,0 +1,11 @@ +import PrintExcel from "./ExcelPrint"; +import { Box } from "@mui/material"; + +const Toolbar = ({ table, filterData, mutate }) => { + return ( + + + + ); +}; +export default Toolbar; diff --git a/src/components/dashboard/damages/report/index.jsx b/src/components/dashboard/damages/report/index.jsx new file mode 100644 index 0000000..6e2092d --- /dev/null +++ b/src/components/dashboard/damages/report/index.jsx @@ -0,0 +1,137 @@ +"use client"; +import PageTitle from "@/core/components/PageTitle"; +import { GET_PROVINCE_RECEIPT_REPORT, GET_COUNTRY_RECEIPT_REPORT } from "@/core/utils/routes"; +import { useAuth } from "@/lib/contexts/auth"; +import { usePermissions } from "@/lib/hooks/usePermissions"; +import useRequest from "@/lib/hooks/useRequest"; +import { yupResolver } from "@hookform/resolvers/yup"; +import { LinearProgress, Stack } from "@mui/material"; +import moment from "jalali-moment"; +import { useEffect, useState } from "react"; +import { useForm } from "react-hook-form"; +import * as yup from "yup"; +import SearchReportList from "./Search"; +import ReportLists from "./ReportLists"; + +const DamagesReportPage = () => { + const [data, setData] = useState(null); + const { data: userPermissions } = usePermissions(); + const { user } = useAuth(); + const requestServer = useRequest(); + const hasProvincesPermission = userPermissions?.includes("show-receipt"); + + const defaultValues = { + from_date: moment(new Date()).format("YYYY-MM-DD"), + date_to: moment(new Date()).format("YYYY-MM-DD"), + province_id: hasProvincesPermission ? "-1" : user.province_id, + }; + const defaultFilter = [ + { value: `${defaultValues.province_id}`, datatype: "numeric", id: "province_id", fn: "equals" }, + { value: `${defaultValues.from_date}`, datatype: "date", id: "from_date", fn: "between" }, + { value: `${defaultValues.date_to}`, datatype: "date", id: "date_to", fn: "between" }, + ]; + const [specialFilter, setSpecialFilter] = useState(defaultFilter); + + const onSearchSubmit = async (data) => { + const params = new URLSearchParams(); + params.set("from_date", `${data.from_date}`); + params.set("date_to", `${data.date_to}`); + setSpecialFilter([ + { value: `${data.province_id}`, datatype: "numeric", id: "province_id", fn: "equals" }, + { value: `${data.from_date}`, datatype: "date", id: "from_date", fn: "between" }, + { value: `${data.date_to}`, datatype: "date", id: "date_to", fn: "between" }, + ]); + if (data.province_id === "-1") { + try { + const response = await requestServer(`${GET_COUNTRY_RECEIPT_REPORT}?${params}`); + const result = response.data.data; + const nationalReportForItem = result.activities.find((report) => report.province_id == -1); + const nationalReport = { + edare_name: "کل کشور", + ...nationalReportForItem, + }; + + setData({ + data: [ + nationalReport, + ...result.provinces.map((province) => { + const filteredReports = result.activities.find( + (report) => report.province_id == province.id + ); + return { + ...filteredReports, + edare_name: province.name_fa, + }; + }), + ], + filters: data, + }); + } catch (e) { + console.log(e); + } + } else { + try { + params.set("province_id", `${data.province_id}`); + const response = await requestServer(`${GET_PROVINCE_RECEIPT_REPORT}?${params}`); + const result = response.data.data; + const nationalReportForItem = result.activities.find((report) => report.city_id == "-1"); + const nationalReport = { + ...nationalReportForItem, + edare_name: "کل استان", + }; + setData({ + data: [ + nationalReport, + ...result.cities.map((edare) => { + const filteredReports = result.activities.find((report) => report.city_id == edare.id); + return { + edare_name: edare.name_fa, + ...filteredReports, + }; + }), + ], + filters: data, + }); + } catch (e) {} + } + }; + + useEffect(() => { + if (!userPermissions) return; + onSearchSubmit(defaultValues); + }, [userPermissions]); + + const validationSchema = yup.object().shape({ + from_date: yup + .string() + .required("تاریخ شروع الزامی است!") + .matches(/^\d{4}-\d{2}-\d{2}$/, "تاریخ شروع الزامی است!") + .test("is-valid-date", "تاریخ شروع معتبر نیست!", (value) => moment(value, "YYYY-MM-DD", true).isValid()), + date_to: yup + .string() + .required("تاریخ پایان الزامی است!") + .matches(/^\d{4}-\d{2}-\d{2}$/, "تاریخ پایان الزامی است!") + .test("is-valid-date", "تاریخ پایان معتبر نیست!", (value) => moment(value, "YYYY-MM-DD", true).isValid()), + province_id: yup.string().nullable(), + }); + + const { control, handleSubmit } = useForm({ + defaultValues, + resolver: yupResolver(validationSchema), + mode: "onBlur", + }); + + return ( + + + + {!data ? : } + + ); +}; +export default DamagesReportPage; diff --git a/src/components/dashboard/fastReact/complaintList/ComplaintListTable.jsx b/src/components/dashboard/fastReact/complaintList/ComplaintListTable.jsx new file mode 100644 index 0000000..90102fc --- /dev/null +++ b/src/components/dashboard/fastReact/complaintList/ComplaintListTable.jsx @@ -0,0 +1,281 @@ +import { useEffect, useMemo, useState } from "react"; +import { Box, Button, Dialog, DialogActions, Stack, Typography } from "@mui/material"; +import DataTableWithAuth from "@/core/components/DataTableWithAuth"; +import Toolbar from "./Toolbar"; +import RowActions from "./RowActions"; +import { GET_FAST_REACT_COMPLAINTS } from "@/core/utils/routes"; +import useProvinces from "@/lib/hooks/useProvince"; +import CustomSelectByDependency from "@/core/components/DataTable/filter/fieldsType/CustomSelectByDependency"; +import { usePermissions } from "@/lib/hooks/usePermissions"; +import { useAuth } from "@/lib/contexts/auth"; +import useEdaratLists from "@/lib/hooks/useEdaratLists"; +import DescriptionForm from "./RowActions/DescriptionDialog"; +import LocationForm from "./RowActions/LocationDialog"; +import moment from "jalali-moment"; + +const ComplaintListTable = ({ open, setOpen, mutate }) => { + const { data: userPermissions } = usePermissions(); + const hasCountryPermission = userPermissions?.includes("show-fast-react"); + const { user } = useAuth(); + const columns = useMemo(() => { + const dynamicColumns = { + header: "استان", + id: "road_observeds__province_id", + enableColumnFilter: true, + datatype: "numeric", + filterMode: "equals", + grow: false, + size: 130, + ColumnSelectComponent: (props) => { + const { provinces, errorProvinces, loadingProvinces } = useProvinces(); + const getColumnSelectOptions = useMemo(() => { + if (loadingProvinces) { + return [{ value: "loading", label: "در حال بارگذاری..." }]; + } + if (errorProvinces) { + return [{ value: "error", label: "خطا در بارگذاری" }]; + } + return [ + { value: "", label: "کل کشور" }, + ...provinces.map((province) => ({ + value: province.id, + label: province.name_fa, + })), + ]; + }, [provinces, errorProvinces, loadingProvinces]); + return ( + + ); + }, + Cell: ({ row }) => <>{row.original.province_fa}, + }; + return [ + { + accessorKey: "id", + header: "کد یکتا", + id: "road_observeds__id", + enableColumnFilter: true, + datatype: "text", + filterMode: "equals", + sortDescFirst: false, + columnFilterModeOptions: ["equals", "contains"], + grow: false, + size: 100, + }, + ...(hasCountryPermission ? [dynamicColumns] : []), + { + header: "اداره", + id: "road_observeds__edarate_shahri_id", + enableColumnFilter: true, + datatype: "numeric", + filterMode: "equals", + dependencyId: hasCountryPermission ? "road_observeds__province_id" : null, + grow: false, + size: 120, + ColumnSelectComponent: (props) => { + const { edaratList, loadingEdaratList, errorEdaratList } = useEdaratLists( + hasCountryPermission ? props.dependencyFieldValue.value : user.province_id + ); + const [prevDependency, setPrevDependency] = useState( + hasCountryPermission ? props.dependencyFieldValue.value : user.province_id + ); + + const getColumnSelectOptions = useMemo(() => { + if (hasCountryPermission && props.dependencyFieldValue.value === "") { + return [{ value: "empty", label: "ابتدا استان را انتخاب کنید" }]; + } + if (loadingEdaratList) { + return [{ value: "loading", label: "در حال بارگذاری..." }]; + } + if (errorEdaratList) { + return [{ value: "error", label: "خطا در بارگذاری" }]; + } + return [ + { value: "", label: "کل ادارات" }, + ...edaratList.map((edare) => ({ + value: edare.id, + label: edare.name_fa, + })), + ]; + }, [edaratList, loadingEdaratList, errorEdaratList]); + useEffect(() => { + if (hasCountryPermission) return; + if (prevDependency === props.dependencyFieldValue?.value) return; + props.handleChange({ ...props.filterParameters, value: "" }); + setPrevDependency(props.dependencyFieldValue?.value); + }, [props.dependencyFieldValue?.value, hasCountryPermission]); + return ( + + ); + }, + Cell: ({ row }) => <>{row.original.edarate_shahri_name_fa}, + }, + { + header: "اطلاعات ثبت‌ شده در سامانه سوانح", + id: "fkInfo", + enableColumnFilter: false, + enableSorting: false, + grow: false, + size: 50, + columns: [ + { + accessorKey: "fk_RegisteredEventMessage", + header: "کد سوانح", + id: "fk_RegisteredEventMessage", + enableColumnFilter: true, + datatype: "text", + filterMode: "equals", + sortDescFirst: false, + columnFilterModeOptions: ["equals", "contains"], + grow: false, + size: 100, + }, + { + accessorKey: "Title", + header: "موضوع گزارش", + id: "Title", + enableColumnFilter: false, + datatype: "text", + filterMode: "equals", + sortDescFirst: false, + columnFilterModeOptions: ["equals", "contains"], + grow: false, + size: 100, + }, + { + accessorKey: "FeatureTypeTitle", + header: "نوع گزارش", + id: "FeatureTypeTitle", + enableColumnFilter: false, + datatype: "text", + filterMode: "equals", + sortDescFirst: false, + columnFilterModeOptions: ["equals", "contains"], + grow: false, + size: 100, + }, + { + accessorKey: "Description", + header: "توضیح گزارش", + id: "Description", + enableColumnFilter: false, + datatype: "text", + filterMode: "equals", + sortDescFirst: false, + columnFilterModeOptions: ["equals", "contains"], + grow: false, + size: 100, + muiTableBodyCellProps: { + sx: { + borderLeft: "1px solid #e1e1e1", + py: 0, + "&:first-of-type": { + borderLeft: "unset", + }, + }, + }, + Cell: ({ renderedCellValue }) => { + if (renderedCellValue) { + return ( + + + + ); + } + return بدون توضیحات; + }, + }, + { + header: "موقعیت", + id: "location", + enableColumnFilter: false, + enableSorting: false, + datatype: "array", + grow: false, + size: 100, + muiTableBodyCellProps: { + sx: { + borderLeft: "1px solid #e1e1e1", + py: 0, + "&:first-of-type": { + borderLeft: "unset", + }, + }, + }, + Cell: ({ row }) => { + return ( + + + + ); + }, + }, + { + accessorKey: "MobileForSendEventSms", + header: "شماره تماس گیرنده", + id: "MobileForSendEventSms", + enableColumnFilter: false, + datatype: "text", + filterMode: "equals", + sortDescFirst: false, + columnFilterModeOptions: ["equals", "contains"], + grow: false, + size: 100, + }, + { + accessorFn: (row) => moment(row.StartTime_DateTime, "M/D/YYYY h:mm:ss A").locale("fa").format("HH:mm | yyyy/MM/DD"), + header: "تاریخ ثبت", + id: "StartTime_DateTime", + enableColumnFilter: false, + datatype: "date", + filterMode: "between", + grow: false, + size: 100, + }, + ] + } + ]; + }, []); + + return ( + setOpen(false)} open={open} fullWidth maxWidth={"xl"}> + {open && ( + + + + )} + + + + + ); +}; +export default ComplaintListTable; diff --git a/src/components/dashboard/fastReact/complaintList/ExcelPrint/index.jsx b/src/components/dashboard/fastReact/complaintList/ExcelPrint/index.jsx new file mode 100644 index 0000000..05f3362 --- /dev/null +++ b/src/components/dashboard/fastReact/complaintList/ExcelPrint/index.jsx @@ -0,0 +1,74 @@ +import { Button, CircularProgress, IconButton, useMediaQuery } from "@mui/material"; +import { useMemo, useState } from "react"; +import moment from "jalali-moment"; +import FileSaver from "file-saver"; +import DescriptionIcon from "@mui/icons-material/Description"; +import useRequest from "@/lib/hooks/useRequest"; +import { EXPORT_FAST_REACT_COMPLAINT_LIST } from "@/core/utils/routes"; +import { useTheme } from "@emotion/react"; +import DataTableFilterDataStructure from "@/core/utils/DataTableFilterDataStructure"; +import isArrayEmpty from "@/core/utils/isArrayEmpty"; + +const PrintExcel = ({ table, filterData }) => { + const [loading, setLoading] = useState(false); + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("sm")); + const requestServer = useRequest(); + const activeFilters = Object.entries(filterData).reduce((acc, [key, filter]) => { + if (filter.value && (!Array.isArray(filter.value) || filter.value.some((item) => item.trim() !== ""))) { + acc.push({ id: key, value: filter.value }); + } + return acc; + }, []); + + const filterParams = useMemo(() => { + const params = new URLSearchParams(); + if (activeFilters.length > 0) { + activeFilters.map((filter, index) => { + const filteredFilterData = DataTableFilterDataStructure(filterData, isArrayEmpty); + params.set("filters", JSON.stringify(filteredFilterData.length === 0 ? [] : filteredFilterData)); + }); + } else { + params.set("filters", JSON.stringify([])); + } + return params; + }, [activeFilters]); + + const clickHandler = () => { + setLoading(true); + requestServer(`${EXPORT_FAST_REACT_COMPLAINT_LIST}?${filterParams}`, "get", { + requestOptions: { responseType: "blob" }, + }) + .then((response) => { + const filename = `خروجی لیست شکایات واکنش سریع تاریخ_${moment().format("jYYYY_jMM_jDD")}.xlsx`; + FileSaver.saveAs(response.data, filename); + }) + .catch(() => {}) + .finally(() => { + setLoading(false); + }); + }; + + return ( + <> + {isMobile ? ( + + + + ) : ( + + )} + + ); +}; +export default PrintExcel; diff --git a/src/components/dashboard/fastReact/complaintList/Form/registerAction/ImageUpload.jsx b/src/components/dashboard/fastReact/complaintList/Form/registerAction/ImageUpload.jsx new file mode 100644 index 0000000..5186a4b --- /dev/null +++ b/src/components/dashboard/fastReact/complaintList/Form/registerAction/ImageUpload.jsx @@ -0,0 +1,77 @@ +import { FormControl, FormHelperText } from "@mui/material"; +import UploadSystem from "@/core/components/UploadSystem"; +import React, { useEffect, useState } from "react"; + +const ImageUpload = ({ name, value, onChange, error, title }) => { + const [beforeImg, setBeforeImg] = useState(value ? value : null); + const [beforeFileType, setBeforeFileType] = useState(value ? "image/" : null); + const [beforeFileName, setBeforeFileName] = useState(null); + const [showBeforeImage, setShowBeforeImage] = useState(!value); + + useEffect(() => { + if (value) { + setShowBeforeImage(false); + if (typeof value === "string") { + setBeforeImg(value); + setBeforeFileType("image/"); + } else if (value instanceof File) { + setBeforeImg(URL.createObjectURL(value)); + setBeforeFileType(value.type); + } + } + }, [value]); + + const handleFileChange = (event) => { + const uploadedFile = event.target?.files?.[0]; + if (uploadedFile) { + const fileType = event.target?.files?.[0].type; + const fileName = event.target?.files?.[0].name; + setBeforeImg(URL.createObjectURL(uploadedFile)); + setBeforeFileType(fileType); + setBeforeFileName(fileName); + onChange(uploadedFile); + setShowBeforeImage(false); + } + }; + + return ( + + + {title} + + + {error ? error.message : null} + + ); +}; +export default ImageUpload; diff --git a/src/components/dashboard/fastReact/complaintList/Form/registerAction/RegisterActionContent.jsx b/src/components/dashboard/fastReact/complaintList/Form/registerAction/RegisterActionContent.jsx new file mode 100644 index 0000000..34d4130 --- /dev/null +++ b/src/components/dashboard/fastReact/complaintList/Form/registerAction/RegisterActionContent.jsx @@ -0,0 +1,106 @@ +import { Button, DialogActions, DialogContent, FormControlLabel, RadioGroup, Stack } from "@mui/material"; +import { Controller, useForm } from "react-hook-form"; +import Radio from "@mui/material/Radio"; +import StyledForm from "@/core/components/StyledForm"; +import { useState } from "react"; +import RegisterActionDone from "./RegisterActionDone"; +import RegisterActionUndone from "./RegisterActionUndone"; +import { yupResolver } from "@hookform/resolvers/yup"; +import { mixed, object, string } from "yup"; +import BeenhereIcon from "@mui/icons-material/Beenhere"; + +const RegisterActionContent = ({ setOpen, defaultValues, onBaseSubmit }) => { + const [selectedOption, setSelectedOption] = useState(defaultValues?.rms_status || "1"); + + const handleOptionChange = (e) => { + setSelectedOption(e.target.value); + }; + const validationSchema = object({ + description: string().required("توضیحات الزامی است"), + start_point: mixed().when("rms_status", { + is: "1", + then: (schema) => schema.required("مکان اقدام الزامی است"), + otherwise: (schema) => schema.notRequired(), + }), + image_before_1: mixed().when("rms_status", { + is: "1", + then: (schema) => schema.required("تصویر قبل از اقدام الزامی است"), + otherwise: (schema) => schema.notRequired(), + }), + image_after_1: mixed().when("rms_status", { + is: "1", + then: (schema) => schema.required("تصویر بعد از اقدام الزامی است"), + otherwise: (schema) => schema.notRequired(), + }), + }); + + const { + control, + handleSubmit, + setValue, + formState: { isSubmitting, errors }, + } = useForm({ + defaultValues, + resolver: yupResolver(validationSchema), + mode: "onChange", + }); + + const onSubmit = async (data) => { + await onBaseSubmit(data); + }; + return ( + + + + + } + /> + } + label="اقدام انجام شد" + /> + } + /> + } + label="اقدام انجام نشد" + /> + + + {selectedOption === "1" ? ( + + ) : ( + + )} + + + + + + + ); +}; +export default RegisterActionContent; diff --git a/src/components/dashboard/fastReact/complaintList/Form/registerAction/RegisterActionDone.jsx b/src/components/dashboard/fastReact/complaintList/Form/registerAction/RegisterActionDone.jsx new file mode 100644 index 0000000..567832d --- /dev/null +++ b/src/components/dashboard/fastReact/complaintList/Form/registerAction/RegisterActionDone.jsx @@ -0,0 +1,86 @@ +import { Controller, useWatch } from "react-hook-form"; +import { Grid, Stack, TextField } from "@mui/material"; +import ImageUpload from "./ImageUpload"; +import MapInfoOneMarker from "@/core/components/MapInfoOneMarker"; + +const RegisterActionDone = ({ control, setValue, errors }) => { + const StartPoint = useWatch({ control, name: "start_point" }); + return ( + + + + + { + return ( + + ); + }} + /> + + + + + + + ( + + )} + /> + + + ( + + )} + /> + + + + + + + + + + + + ); +}; +export default RegisterActionDone; diff --git a/src/components/dashboard/fastReact/complaintList/Form/registerAction/RegisterActionUndone.jsx b/src/components/dashboard/fastReact/complaintList/Form/registerAction/RegisterActionUndone.jsx new file mode 100644 index 0000000..dd2b43b --- /dev/null +++ b/src/components/dashboard/fastReact/complaintList/Form/registerAction/RegisterActionUndone.jsx @@ -0,0 +1,37 @@ +import { Grid, Stack, TextField } from "@mui/material"; +import { Controller } from "react-hook-form"; + +const RegisterActionUndone = ({ control }) => { + return ( + + + + + { + return ( + + ); + }} + /> + + + + + ); +}; +export default RegisterActionUndone; diff --git a/src/components/dashboard/fastReact/complaintList/Form/registerAction/index.jsx b/src/components/dashboard/fastReact/complaintList/Form/registerAction/index.jsx new file mode 100644 index 0000000..7362f4b --- /dev/null +++ b/src/components/dashboard/fastReact/complaintList/Form/registerAction/index.jsx @@ -0,0 +1,66 @@ +import { Dialog, DialogTitle, IconButton, Tooltip } from "@mui/material"; +import { useState } from "react"; +import RegisterActionContent from "./RegisterActionContent"; +import AppRegistrationIcon from "@mui/icons-material/AppRegistration"; +import useRequest from "@/lib/hooks/useRequest"; +import { REGISTER_COMPLAINTS_LIST } from "@/core/utils/routes"; +const RegisterAction = ({ rowId, mutate }) => { + const requestServer = useRequest({ notificationSuccess: true }); + const [openRegisterActionDialog, setOpenRegisterActionDialog] = useState(false); + const defaultValues = { + rms_status: "1", + description: "", + start_point: "", + image_before_1: null, + image_after_1: null, + }; + const onBaseSubmit = async (result) => { + console.log(result); + const formData = new FormData(); + const rmsLatLng = `${result.start_point.lat},${result.start_point.lng}`; + if (result.rms_status === "1") { + formData.append("rms_description", result.description); + formData.append("rms_status", 1); + formData.append("start_point", rmsLatLng); + formData.append("image_before", result.image_before_1); + formData.append("image_after", result.image_after_1); + } else { + formData.append("rms_description", result.description); + formData.append("rms_status", 2); + } + await requestServer(`${REGISTER_COMPLAINTS_LIST}/${rowId}`, "post", { + data: formData, + }) + .then((res) => { + mutate(); + setOpenRegisterActionDialog(false); + }) + .catch(() => {}); + }; + return ( + <> + + setOpenRegisterActionDialog(true)}> + + + + + ثبت اقدام + + + + ); +}; +export default RegisterAction; diff --git a/src/components/dashboard/fastReact/complaintList/RowActions/DescriptionDialog/index.jsx b/src/components/dashboard/fastReact/complaintList/RowActions/DescriptionDialog/index.jsx new file mode 100644 index 0000000..4d80ac4 --- /dev/null +++ b/src/components/dashboard/fastReact/complaintList/RowActions/DescriptionDialog/index.jsx @@ -0,0 +1,50 @@ +import { Dialog, DialogContent, DialogTitle, IconButton, Tooltip, Typography, Card, CardContent } from "@mui/material"; +import { useState } from "react"; +import RemoveRedEyeIcon from "@mui/icons-material/RemoveRedEye"; +import CloseIcon from "@mui/icons-material/Close"; + +const DescriptionForm = ({ description, title, icon: IconComponent = RemoveRedEyeIcon }) => { + const [openOfficerDescriptionDialog, setOpenOfficerDescriptionDialog] = useState(false); + + return ( + <> + + setOpenOfficerDescriptionDialog(true)}> + + + + setOpenOfficerDescriptionDialog(false)} + PaperProps={{ + sx: { + transition: "all .3s", + boxShadow: "rgba(60, 64, 67, 0.3) 0px 1px 16px 0px, rgba(60, 64, 67, 0.15) 0px 1px 10px 0px", + borderRadius: 2, + padding: 1, + }, + }} + maxWidth="sm" + fullWidth + dir="rtl" + > + + + {title} + + setOpenOfficerDescriptionDialog(false)} size="small"> + + + + + + + {description || "هیچ توضیحی موجود نیست"} + + + + + + ); +}; +export default DescriptionForm; diff --git a/src/components/dashboard/fastReact/complaintList/RowActions/LocationDialog/LocationFormContent.jsx b/src/components/dashboard/fastReact/complaintList/RowActions/LocationDialog/LocationFormContent.jsx new file mode 100644 index 0000000..94f091d --- /dev/null +++ b/src/components/dashboard/fastReact/complaintList/RowActions/LocationDialog/LocationFormContent.jsx @@ -0,0 +1,30 @@ +"use client"; +import { Box, Button, DialogActions, DialogContent, Stack } from "@mui/material"; +import React from "react"; +import dynamic from "next/dynamic"; +import MapLoading from "@/core/components/MapLayer/Loading"; +import ShowLocationMarker from "@/core/components/ShowLocationMarker"; +const MapLayer = dynamic(() => import("@/core/components/MapLayer"), { + loading: () => , + ssr: false, +}); + +const LocationFormContent = ({ setOpenLocationDialog, start_lat, start_lng }) => { + return ( + <> + + + + + + + + + + + + ); +}; +export default LocationFormContent; diff --git a/src/components/dashboard/fastReact/complaintList/RowActions/LocationDialog/index.jsx b/src/components/dashboard/fastReact/complaintList/RowActions/LocationDialog/index.jsx new file mode 100644 index 0000000..09df6ad --- /dev/null +++ b/src/components/dashboard/fastReact/complaintList/RowActions/LocationDialog/index.jsx @@ -0,0 +1,36 @@ +import React, { useState } from "react"; +import { Tooltip, IconButton, Dialog, DialogTitle } from "@mui/material"; +import ExploreIcon from "@mui/icons-material/Explore"; +import LocationFormContent from "./LocationFormContent"; +const LocationForm = ({ start_lat, start_lng }) => { + const [openLocationDialog, setOpenLocationDialog] = useState(false); + + return ( + <> + + setOpenLocationDialog(true)}> + + + + setOpenLocationDialog(false)} + PaperProps={{ + sx: { + boxShadow: "rgba(60, 64, 67, 0.3) 0px 1px 16px 0px, rgba(60, 64, 67, 0.15) 0px 1px 10px 0px", + }, + }} + dir="rtl" + maxWidth={"md"} + > + موقعیت + + + + ); +}; +export default LocationForm; diff --git a/src/components/dashboard/fastReact/complaintList/RowActions/Refer/EdarateShahriField.jsx b/src/components/dashboard/fastReact/complaintList/RowActions/Refer/EdarateShahriField.jsx new file mode 100644 index 0000000..f44d8e1 --- /dev/null +++ b/src/components/dashboard/fastReact/complaintList/RowActions/Refer/EdarateShahriField.jsx @@ -0,0 +1,70 @@ +"use client"; +import useEdaratLists from "@/lib/hooks/useEdaratLists"; +import { FormControl, FormHelperText, InputLabel, MenuItem, OutlinedInput, Select } from "@mui/material"; +import { useEffect, useMemo, useState } from "react"; +import { useWatch } from "react-hook-form"; + +const EdarateShahriField = ({ control, field, fieldState: { error } }) => { + const dependencyField = useWatch({ control, name: "province_id" }); + return ( + + ); +}; + +const EdarehShahriController = ({ value, onChange, dependencyField, error }) => { + const { edaratList, loadingEdaratList, errorEdaratList } = useEdaratLists(dependencyField); + const [prevDependency, setPrevDependency] = useState(dependencyField); + + const columnSelectOption = useMemo(() => { + if (dependencyField === "") { + return [{ value: "empty", label: "ابتدا استان را انتخاب کنید" }]; + } + if (loadingEdaratList) { + return [{ value: "loading", label: "در حال بارگذاری..." }]; + } + if (errorEdaratList) { + return [{ value: "error", label: "خطا در بارگذاری" }]; + } + return edaratList.map((edare) => ({ + value: edare.id, + label: edare.name_fa, + })); + }, [edaratList, loadingEdaratList, errorEdaratList]); + + useEffect(() => { + if (prevDependency === dependencyField) return; + onChange(""); + setPrevDependency(dependencyField); + }, [dependencyField]); + + return ( + + + اداره + + + {error && {error.message}} + + ); +}; +export default EdarateShahriField; diff --git a/src/components/dashboard/fastReact/complaintList/RowActions/Refer/ProvinceField.jsx b/src/components/dashboard/fastReact/complaintList/RowActions/Refer/ProvinceField.jsx new file mode 100644 index 0000000..455339e --- /dev/null +++ b/src/components/dashboard/fastReact/complaintList/RowActions/Refer/ProvinceField.jsx @@ -0,0 +1,33 @@ +import useProvinces from "@/lib/hooks/useProvince"; +import { FormControl, FormHelperText, InputLabel, MenuItem, Select } from "@mui/material"; + +const ProvinceField = ({ field, fieldState: { error } }) => { + const { provinces, loadingProvinces, errorProvinces } = useProvinces(); + return ( + + استان + + {error && {error.message}} + + ); +}; +export default ProvinceField; diff --git a/src/components/dashboard/fastReact/complaintList/RowActions/Refer/ReferContent.jsx b/src/components/dashboard/fastReact/complaintList/RowActions/Refer/ReferContent.jsx new file mode 100644 index 0000000..59774e6 --- /dev/null +++ b/src/components/dashboard/fastReact/complaintList/RowActions/Refer/ReferContent.jsx @@ -0,0 +1,101 @@ +import { REFER_FAST_REACT } from "@/core/utils/routes"; +import useRequest from "@/lib/hooks/useRequest"; +import { yupResolver } from "@hookform/resolvers/yup"; +import { Button, DialogActions, DialogContent, Stack, TextField } from "@mui/material"; +import { Controller, useForm } from "react-hook-form"; +import * as Yup from "yup"; +import EdarateShahriField from "./EdarateShahriField"; +import ProvinceField from "./ProvinceField"; + +const ReferContent = ({ rowId, mutate, setOpenReferDialog }) => { + const requestServer = useRequest({ notificationSuccess: true }); + + const validationSchema = Yup.object().shape({ + province_id: Yup.string().required("لطفاً استان را وارد کنید."), + edarat_shahri_id: Yup.string().required("لطفاً اداره را وارد کنید."), + description: Yup.string().required("لطفاً توضیحات را وارد کنید."), + }); + + const { + control, + register, + handleSubmit, + formState: { errors, isSubmitting }, + } = useForm({ + defaultValues: { + province_id: "", + edarat_shahri_id: "", + description: "", + }, + resolver: yupResolver(validationSchema), + mode: "onBlur", + }); + + const onSubmit = async (data) => { + const formData = new FormData(); + formData.append("province_id", data.province_id); + formData.append("edarate_shahri_id", data.edarat_shahri_id); + formData.append("refer_description", data.description); + await requestServer(`${REFER_FAST_REACT}/${rowId}`, "post", { + data: formData, + }) + .then(() => { + mutate(); + setOpenReferDialog(false); + }) + .catch(() => {}); + }; + + return ( + <> + + + { + return ; + }} + /> + { + return ; + }} + /> + + + + + + + + + + + ); +}; +export default ReferContent; diff --git a/src/components/dashboard/fastReact/complaintList/RowActions/Refer/index.jsx b/src/components/dashboard/fastReact/complaintList/RowActions/Refer/index.jsx new file mode 100644 index 0000000..8dc9875 --- /dev/null +++ b/src/components/dashboard/fastReact/complaintList/RowActions/Refer/index.jsx @@ -0,0 +1,33 @@ +import SettingsBackupRestoreIcon from "@mui/icons-material/SettingsBackupRestore"; +import { Dialog, DialogTitle, IconButton, Tooltip } from "@mui/material"; +import { useState } from "react"; +import ReferContent from "./ReferContent"; + +const Refer = ({ rowId, mutate }) => { + const [openReferDialog, setOpenReferDialog] = useState(false); + return ( + <> + + setOpenReferDialog(true)}> + + + + + ارجاع + {openReferDialog && ( + + )} + + + ); +}; +export default Refer; diff --git a/src/components/dashboard/fastReact/complaintList/RowActions/ReferList/ReferListContent.jsx b/src/components/dashboard/fastReact/complaintList/RowActions/ReferList/ReferListContent.jsx new file mode 100644 index 0000000..31a1e4c --- /dev/null +++ b/src/components/dashboard/fastReact/complaintList/RowActions/ReferList/ReferListContent.jsx @@ -0,0 +1,81 @@ +import DialogLoading from "@/core/components/DialogLoading"; +import { GET_FAST_REACT_REFER_LIST } from "@/core/utils/routes"; +import useRequest from "@/lib/hooks/useRequest"; +import { + DialogContent, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Typography, +} from "@mui/material"; +import moment from "jalali-moment"; +import { useEffect, useState } from "react"; + +const ReferListContent = ({ rowId }) => { + const [loading, setLoading] = useState(true); + const [data, setData] = useState(null); + const request = useRequest(); + + useEffect(() => { + const fetchData = async () => { + try { + setLoading(true); + const response = await request(`${GET_FAST_REACT_REFER_LIST}/${rowId}`); + setData(response.data.data); + } catch (error) { + } finally { + setLoading(false); + } + }; + + fetchData(); + }, [rowId]); + + return ( + <> + + {loading ? ( + + ) : data.length !== 0 ? ( + + + + + کاربر + از اداره + از اداره + تاریخ + توضیحات + + + + {data.map((refer, index) => { + return ( + + {refer.user} + {`${refer.from_province} | ${refer.from_edareh}`} + {`${refer.to_province} | ${refer.to_edareh}`} + + {moment(refer.created_at).locale("fa").format("HH:mm | yyyy/MM/DD")} + + {refer.description} + + ); + })} + +
+
+ ) : ( + ارجاعی در سامانه یافت نشد + )} +
+ + ); +}; +export default ReferListContent; diff --git a/src/components/dashboard/fastReact/complaintList/RowActions/ReferList/index.jsx b/src/components/dashboard/fastReact/complaintList/RowActions/ReferList/index.jsx new file mode 100644 index 0000000..619bd98 --- /dev/null +++ b/src/components/dashboard/fastReact/complaintList/RowActions/ReferList/index.jsx @@ -0,0 +1,43 @@ +import { Button, Dialog, DialogActions, DialogTitle, IconButton, Tooltip } from "@mui/material"; +import ListAltIcon from "@mui/icons-material/ListAlt"; +import ReferListContent from "./ReferListContent"; +import { useState } from "react"; + +const ReferList = ({ rowId }) => { + const [openReferListDialog, setOpenReferListDialog] = useState(false); + return ( + <> + + setOpenReferListDialog(true)}> + + + + setOpenReferListDialog(false)} + PaperProps={{ + sx: { + boxShadow: "rgba(60, 64, 67, 0.3) 0px 1px 16px 0px, rgba(60, 64, 67, 0.15) 0px 1px 10px 0px", + }, + }} + dir="rtl" + maxWidth={"md"} + > + لیست ارجاعات + {openReferListDialog && } + + + + + + ); +}; +export default ReferList; diff --git a/src/components/dashboard/fastReact/complaintList/RowActions/index.jsx b/src/components/dashboard/fastReact/complaintList/RowActions/index.jsx new file mode 100644 index 0000000..e9972aa --- /dev/null +++ b/src/components/dashboard/fastReact/complaintList/RowActions/index.jsx @@ -0,0 +1,20 @@ +import { Box } from "@mui/material"; +import RegisterAction from "../Form/registerAction"; +import ReferList from "./ReferList"; +import Refer from "./Refer"; +import { usePermissions } from "@/lib/hooks/usePermissions"; + +const RowActions = ({ row, mutate }) => { + const { data: userPermissions } = usePermissions(); + const hasActionPermission = userPermissions.includes("show-fast-react-edarate-shahri") + return ( + + {hasActionPermission && ( + + )} + + + + ); +}; +export default RowActions; diff --git a/src/components/dashboard/fastReact/complaintList/Toolbar.jsx b/src/components/dashboard/fastReact/complaintList/Toolbar.jsx new file mode 100644 index 0000000..22d8bd3 --- /dev/null +++ b/src/components/dashboard/fastReact/complaintList/Toolbar.jsx @@ -0,0 +1,11 @@ +import { Stack } from "@mui/material"; +import PrintExcel from "./ExcelPrint"; + +const Toolbar = ({ table, filterData, mutate }) => { + return ( + + + + ); +}; +export default Toolbar; diff --git a/src/components/dashboard/fastReact/complaintList/index.jsx b/src/components/dashboard/fastReact/complaintList/index.jsx new file mode 100644 index 0000000..5c3edc6 --- /dev/null +++ b/src/components/dashboard/fastReact/complaintList/index.jsx @@ -0,0 +1,36 @@ +import { Button, IconButton, useMediaQuery } from "@mui/material"; +import ChecklistIcon from "@mui/icons-material/Checklist"; +import { useTheme } from "@emotion/react"; +import { useState } from "react"; +import ComplaintListTable from "./ComplaintListTable"; + +const ComplaintList = ({ mutate }) => { + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("sm")); + const [open, setOpen] = useState(false); + + const handleOpen = () => { + setOpen(true); + }; + return ( + <> + {isMobile ? ( + + + + ) : ( + + )} + + + ); +}; +export default ComplaintList; diff --git a/src/components/dashboard/fastReact/operator/ExcelPrint/index.jsx b/src/components/dashboard/fastReact/operator/ExcelPrint/index.jsx new file mode 100644 index 0000000..c1e2bea --- /dev/null +++ b/src/components/dashboard/fastReact/operator/ExcelPrint/index.jsx @@ -0,0 +1,75 @@ +import { Button, CircularProgress, IconButton, useMediaQuery } from "@mui/material"; +import { useMemo, useState } from "react"; +import moment from "jalali-moment"; +import FileSaver from "file-saver"; +import DescriptionIcon from "@mui/icons-material/Description"; +import useRequest from "@/lib/hooks/useRequest"; +import { EXPORT_FAST_REACT_OPERATOR_LIST } from "@/core/utils/routes"; +import { useTheme } from "@emotion/react"; +import DataTableFilterDataStructure from "@/core/utils/DataTableFilterDataStructure"; +import isArrayEmpty from "@/core/utils/isArrayEmpty"; + +const PrintExcel = ({ table, filterData }) => { + const [loading, setLoading] = useState(false); + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("sm")); + const requestServer = useRequest(); + const activeFilters = Object.entries(filterData).reduce((acc, [key, filter]) => { + if (filter.value && (!Array.isArray(filter.value) || filter.value.some((item) => item.trim() !== ""))) { + acc.push({ id: key, value: filter.value }); + } + return acc; + }, []); + + const filterParams = useMemo(() => { + const params = new URLSearchParams(); + if (activeFilters.length > 0) { + activeFilters.map((filter, index) => { + const filteredFilterData = DataTableFilterDataStructure(filterData, isArrayEmpty); + params.set("filters", JSON.stringify(filteredFilterData.length === 0 ? [] : filteredFilterData)); + }); + } else { + params.set("filters", JSON.stringify([])); + } + return params; + }, [activeFilters]); + + const clickHandler = () => { + setLoading(true); + requestServer(`${EXPORT_FAST_REACT_OPERATOR_LIST}?${filterParams}`, "get", { + requestOptions: { responseType: "blob" }, + }) + .then((response) => { + const filename = `خروجی کارتابل عملیات واکنش سریع تاریخ_${moment().format("jYYYY_jMM_jDD")}.xlsx`; + FileSaver.saveAs(response.data, filename); + }) + .catch(() => {}) + .finally(() => { + setLoading(false); + }); + }; + + return ( + <> + {isMobile ? ( + + + + ) : ( + + )} + + ); +}; + +export default PrintExcel; diff --git a/src/components/dashboard/fastReact/operator/Form/EditForm/EditController.jsx b/src/components/dashboard/fastReact/operator/Form/EditForm/EditController.jsx new file mode 100644 index 0000000..5025d54 --- /dev/null +++ b/src/components/dashboard/fastReact/operator/Form/EditForm/EditController.jsx @@ -0,0 +1,72 @@ +import useRequest from "@/lib/hooks/useRequest"; +import { useEffect, useState } from "react"; +import { GET_FAST_REACT_ITEM_DETAIL, UPDATE_FAST_REACT } from "@/core/utils/routes"; +import DialogLoading from "@/core/components/DialogLoading"; +import RegisterActionContent from "@/components/dashboard/fastReact/complaintList/Form/registerAction/RegisterActionContent"; + +const EditController = ({ rowId, mutate, setOpenEditDialog }) => { + const requestServer = useRequest(); + const [fastReactItemDetails, setFastReactItemDetails] = useState(null); + const [fastReactItemDetailsLoading, setFastReactItemDetailsLoading] = useState(false); + + useEffect(() => { + setFastReactItemDetailsLoading(true); + requestServer(`${GET_FAST_REACT_ITEM_DETAIL}/${rowId}`, "get") + .then((response) => { + setFastReactItemDetails(response.data.data); + setFastReactItemDetailsLoading(false); + }) + .catch((e) => { + setFastReactItemDetailsLoading(false); + }); + }, [rowId]); + const startPoint = fastReactItemDetails?.rms_start_latlng + ? { lat: fastReactItemDetails?.rms_start_latlng[0], lng: fastReactItemDetails?.rms_start_latlng[1] } + : { lat: "", lng: "" }; + const defaultData = { + rms_status: `${fastReactItemDetails?.rms_status || "1"}`, + description: fastReactItemDetails?.rms_description || "", + start_point: startPoint, + image_before_1: fastReactItemDetails?.image_before || null, + image_after_1: fastReactItemDetails?.image_after || null, + }; + const HandleSubmit = async (result) => { + console.log("result", result); + const formData = new FormData(); + const rmsLatLng = `${result.start_point.lat},${result.start_point.lng}`; + if (result.rms_status === "1") { + formData.append("rms_description", result.description); + formData.append("rms_status", 1); + formData.append("start_point", rmsLatLng); + result.image_before_1 !== defaultData.image_before_1 && + formData.append("image_before", result.image_before_1); + result.image_after_1 !== defaultData.image_after_1 && formData.append("image_after", result.image_after_1); + } else { + formData.append("rms_description", result.description); + formData.append("rms_status", 2); + } + + await requestServer(`${UPDATE_FAST_REACT}/${rowId}`, "post", { + data: formData, + }) + .then(() => { + mutate(); + setOpenEditDialog(false); + }) + .catch(() => {}); + }; + return ( + <> + {fastReactItemDetailsLoading ? ( + + ) : ( + + )} + + ); +}; +export default EditController; diff --git a/src/components/dashboard/fastReact/operator/Form/EditForm/index.jsx b/src/components/dashboard/fastReact/operator/Form/EditForm/index.jsx new file mode 100644 index 0000000..0af8244 --- /dev/null +++ b/src/components/dashboard/fastReact/operator/Form/EditForm/index.jsx @@ -0,0 +1,58 @@ +import BorderColorIcon from "@mui/icons-material/BorderColor"; +import CloseIcon from "@mui/icons-material/Close"; +import { Dialog, IconButton, Tooltip } from "@mui/material"; +import { useState } from "react"; +import EditController from "./EditController"; + +const EditForm = ({ row, mutate, rowId }) => { + const [openEditDialog, setOpenEditDialog] = useState(false); + + return ( + <> + + { + setOpenEditDialog(true); + }} + > + + + + + setOpenEditDialog(false)} + sx={(theme) => ({ + position: "absolute", + right: 8, + top: 8, + zIndex: 50, + color: theme.palette.grey[500], + })} + > + + + {openEditDialog && ( + + )} + + + ); +}; +export default EditForm; diff --git a/src/components/dashboard/fastReact/operator/OperatorList.jsx b/src/components/dashboard/fastReact/operator/OperatorList.jsx new file mode 100644 index 0000000..3ff010b --- /dev/null +++ b/src/components/dashboard/fastReact/operator/OperatorList.jsx @@ -0,0 +1,323 @@ +import DataTableWithAuth from "@/core/components/DataTableWithAuth"; +import { GET_FAST_REACT_OPERATOR } from "@/core/utils/routes"; +import EngineeringIcon from "@mui/icons-material/Engineering"; +import ThreePIcon from "@mui/icons-material/ThreeP"; +import { Box, Stack, Typography } from "@mui/material"; +import moment from "jalali-moment"; +import { useMemo } from "react"; +import RowActions from "./RowActions"; +import DescriptionForm from "./RowActions/DescriptionDialog"; +import ImageDialog from "./RowActions/ImageDialog"; +import LocationForm from "./RowActions/LocationDialog"; +import Toolbar from "./Toolbar"; + +const OperatorList = () => { + const statusOptions = [ + { value: "", label: "همه وضعیت ها" }, + { value: 0, label: "درحال بررسی" }, + { value: 1, label: "تایید شده" }, + { value: 2, label: "عدم تایید" }, + ]; + + const columns = useMemo( + () => [ + { + accessorKey: "id", + header: "کد یکتا", + id: "road_observeds__id", + enableColumnFilter: true, + datatype: "text", + filterMode: "equals", + sortDescFirst: false, + columnFilterModeOptions: ["equals", "contains"], + grow: false, + size: 100, + }, + { + header: "اطلاعات ثبت‌ شده در سامانه سوانح", + id: "fkInfo", + enableColumnFilter: false, + enableSorting: false, + grow: false, + size: 50, + columns: [ + { + accessorKey: "fk_RegisteredEventMessage", + header: "کد سوانح", + id: "fk_RegisteredEventMessage", + enableColumnFilter: true, + datatype: "text", + filterMode: "equals", + sortDescFirst: false, + columnFilterModeOptions: ["equals", "contains"], + grow: false, + size: 100, + }, + { + accessorKey: "Title", + header: "موضوع گزارش", + id: "Title", + enableColumnFilter: false, + datatype: "text", + filterMode: "equals", + sortDescFirst: false, + columnFilterModeOptions: ["equals", "contains"], + grow: false, + size: 100, + }, + { + accessorKey: "FeatureTypeTitle", + header: "نوع گزارش", + id: "FeatureTypeTitle", + enableColumnFilter: false, + datatype: "text", + filterMode: "equals", + sortDescFirst: false, + columnFilterModeOptions: ["equals", "contains"], + grow: false, + size: 100, + }, + { + accessorKey: "Description", + header: "توضیح گزارش", + id: "Description", + enableColumnFilter: false, + datatype: "text", + filterMode: "equals", + sortDescFirst: false, + columnFilterModeOptions: ["equals", "contains"], + grow: false, + size: 100, + muiTableBodyCellProps: { + sx: { + borderLeft: "1px solid #e1e1e1", + py: 0, + "&:first-of-type": { + borderLeft: "unset", + }, + }, + }, + Cell: ({ renderedCellValue }) => { + if (renderedCellValue) { + return ( + + + + ); + } + return بدون توضیحات; + }, + }, + { + header: "موقعیت", + id: "location", + enableColumnFilter: false, + enableSorting: false, + datatype: "array", + grow: false, + size: 100, + muiTableBodyCellProps: { + sx: { + borderLeft: "1px solid #e1e1e1", + py: 0, + "&:first-of-type": { + borderLeft: "unset", + }, + }, + }, + Cell: ({ row }) => { + if (!row.original.rms_start_latlng) return <>بدون موقعیت; + return ( + + + + ); + }, + }, + { + header: "تصاویر", + id: "images", + enableColumnFilter: false, + enableSorting: false, + datatype: "array", + grow: false, + size: 100, + muiTableBodyCellProps: { + sx: { + borderLeft: "1px solid #e1e1e1", + py: 0, + "&:first-of-type": { + borderLeft: "unset", + }, + }, + }, + Cell: ({ row }) => { + if (!row.original?.image_before && !row.original?.image_after) return <>بدون تصویر; + return ( + + {row.original?.image_before && row.original?.image_after && ( + + )} + + ); + }, + }, + { + accessorKey: "MobileForSendEventSms", + header: "شماره تماس گیرنده", + id: "MobileForSendEventSms", + enableColumnFilter: false, + datatype: "text", + filterMode: "equals", + sortDescFirst: false, + columnFilterModeOptions: ["equals", "contains"], + grow: false, + size: 100, + }, + { + accessorFn: (row) => moment(row.StartTime_DateTime, "M/D/YYYY h:mm:ss A").locale("fa").format("HH:mm | yyyy/MM/DD"), + header: "تاریخ ثبت", + id: "StartTime_DateTime", + enableColumnFilter: false, + datatype: "date", + filterMode: "between", + grow: false, + size: 100, + }, + ] + }, + { + accessorFn: (row) => { + return row.rms_last_activity + ? moment(row?.rms_last_activity).locale("fa").format("HH:mm | yyyy/MM/DD") + : "بدون تاریخ"; + }, + header: "تاریخ اقدام", + id: "rms_last_activity", + enableColumnFilter: true, + datatype: "date", + filterMode: "between", + grow: false, + size: 100, + }, + { + accessorKey: "rms_description", + header: "توضیح مامور", + id: "rms_description", + enableColumnFilter: false, + datatype: "text", + filterMode: "equals", + sortDescFirst: false, + columnFilterModeOptions: ["equals", "contains"], + grow: false, + size: 100, + muiTableBodyCellProps: { + sx: { + borderLeft: "1px solid #e1e1e1", + py: 0, + "&:first-of-type": { + borderLeft: "unset", + }, + }, + }, + Cell: ({ renderedCellValue }) => { + if (renderedCellValue) { + return ( + + + + ); + } + return بدون توضیحات; + }, + }, + { + accessorKey: "supervisor_description", + header: "توضیح کارشناس", + id: "supervisor_description", + enableColumnFilter: false, + datatype: "text", + filterMode: "equals", + sortDescFirst: false, + columnFilterModeOptions: ["equals", "contains"], + grow: false, + size: 100, + muiTableBodyCellProps: { + sx: { + borderLeft: "1px solid #e1e1e1", + py: 0, + "&:first-of-type": { + borderLeft: "unset", + }, + }, + }, + Cell: ({ renderedCellValue }) => { + if (renderedCellValue) { + return ( + + + + ); + } + return بدون توضیحات; + }, + }, + { + accessorKey: "status", + header: "وضعیت", + id: "status", + enableColumnFilter: true, + datatype: "numeric", + filterMode: "equals", + sortDescFirst: true, + grow: false, + size: 100, + columnSelectOption: () => { + return statusOptions.map((status) => ({ + value: status.value, + label: status.label, + })); + }, + Cell: ({ row }) => <>{row.original.status_fa}, + }, + ], + [] + ); + + return ( + <> + + + + + ); +}; +export default OperatorList; diff --git a/src/components/dashboard/fastReact/operator/RowActions/DescriptionDialog/index.jsx b/src/components/dashboard/fastReact/operator/RowActions/DescriptionDialog/index.jsx new file mode 100644 index 0000000..4d80ac4 --- /dev/null +++ b/src/components/dashboard/fastReact/operator/RowActions/DescriptionDialog/index.jsx @@ -0,0 +1,50 @@ +import { Dialog, DialogContent, DialogTitle, IconButton, Tooltip, Typography, Card, CardContent } from "@mui/material"; +import { useState } from "react"; +import RemoveRedEyeIcon from "@mui/icons-material/RemoveRedEye"; +import CloseIcon from "@mui/icons-material/Close"; + +const DescriptionForm = ({ description, title, icon: IconComponent = RemoveRedEyeIcon }) => { + const [openOfficerDescriptionDialog, setOpenOfficerDescriptionDialog] = useState(false); + + return ( + <> + + setOpenOfficerDescriptionDialog(true)}> + + + + setOpenOfficerDescriptionDialog(false)} + PaperProps={{ + sx: { + transition: "all .3s", + boxShadow: "rgba(60, 64, 67, 0.3) 0px 1px 16px 0px, rgba(60, 64, 67, 0.15) 0px 1px 10px 0px", + borderRadius: 2, + padding: 1, + }, + }} + maxWidth="sm" + fullWidth + dir="rtl" + > + + + {title} + + setOpenOfficerDescriptionDialog(false)} size="small"> + + + + + + + {description || "هیچ توضیحی موجود نیست"} + + + + + + ); +}; +export default DescriptionForm; diff --git a/src/components/dashboard/fastReact/operator/RowActions/ImageDialog/ImageFormContent.jsx b/src/components/dashboard/fastReact/operator/RowActions/ImageDialog/ImageFormContent.jsx new file mode 100644 index 0000000..c8f22d0 --- /dev/null +++ b/src/components/dashboard/fastReact/operator/RowActions/ImageDialog/ImageFormContent.jsx @@ -0,0 +1,45 @@ +import { Box, Stack, Typography, useTheme } from "@mui/material"; +import Image from "next/image"; + +const ImageFormContent = ({ image, title }) => { + const theme = useTheme(); + return ( + + + {title} + + + Image + + + ); +}; +export default ImageFormContent; diff --git a/src/components/dashboard/fastReact/operator/RowActions/ImageDialog/index.jsx b/src/components/dashboard/fastReact/operator/RowActions/ImageDialog/index.jsx new file mode 100644 index 0000000..de3b76a --- /dev/null +++ b/src/components/dashboard/fastReact/operator/RowActions/ImageDialog/index.jsx @@ -0,0 +1,46 @@ +import PhotoLibraryIcon from "@mui/icons-material/PhotoLibrary"; +import { Button, Dialog, DialogActions, DialogContent, DialogTitle, IconButton, Tooltip } from "@mui/material"; +import { useState } from "react"; +import ImageFormContent from "./ImageFormContent"; + +const ImageDialog = ({ image_before, image_after }) => { + const [openImageDialog, setOpenImageDialog] = useState(false); + + return ( + <> + + setOpenImageDialog(true)}> + + + + setOpenImageDialog(false)} + PaperProps={{ + sx: { + boxShadow: "rgba(60, 64, 67, 0.3) 0px 1px 16px 0px, rgba(60, 64, 67, 0.15) 0px 1px 10px 0px", + }, + }} + maxWidth={"sm"} + scroll="paper" + > + تصاویر + + {openImageDialog && ( + <> + + + + )} + + + + + + + ); +}; +export default ImageDialog; diff --git a/src/components/dashboard/fastReact/operator/RowActions/LocationDialog/LocationFormContent.jsx b/src/components/dashboard/fastReact/operator/RowActions/LocationDialog/LocationFormContent.jsx new file mode 100644 index 0000000..94f091d --- /dev/null +++ b/src/components/dashboard/fastReact/operator/RowActions/LocationDialog/LocationFormContent.jsx @@ -0,0 +1,30 @@ +"use client"; +import { Box, Button, DialogActions, DialogContent, Stack } from "@mui/material"; +import React from "react"; +import dynamic from "next/dynamic"; +import MapLoading from "@/core/components/MapLayer/Loading"; +import ShowLocationMarker from "@/core/components/ShowLocationMarker"; +const MapLayer = dynamic(() => import("@/core/components/MapLayer"), { + loading: () => , + ssr: false, +}); + +const LocationFormContent = ({ setOpenLocationDialog, start_lat, start_lng }) => { + return ( + <> + + + + + + + + + + + + ); +}; +export default LocationFormContent; diff --git a/src/components/dashboard/fastReact/operator/RowActions/LocationDialog/index.jsx b/src/components/dashboard/fastReact/operator/RowActions/LocationDialog/index.jsx new file mode 100644 index 0000000..09df6ad --- /dev/null +++ b/src/components/dashboard/fastReact/operator/RowActions/LocationDialog/index.jsx @@ -0,0 +1,36 @@ +import React, { useState } from "react"; +import { Tooltip, IconButton, Dialog, DialogTitle } from "@mui/material"; +import ExploreIcon from "@mui/icons-material/Explore"; +import LocationFormContent from "./LocationFormContent"; +const LocationForm = ({ start_lat, start_lng }) => { + const [openLocationDialog, setOpenLocationDialog] = useState(false); + + return ( + <> + + setOpenLocationDialog(true)}> + + + + setOpenLocationDialog(false)} + PaperProps={{ + sx: { + boxShadow: "rgba(60, 64, 67, 0.3) 0px 1px 16px 0px, rgba(60, 64, 67, 0.15) 0px 1px 10px 0px", + }, + }} + dir="rtl" + maxWidth={"md"} + > + موقعیت + + + + ); +}; +export default LocationForm; diff --git a/src/components/dashboard/fastReact/operator/RowActions/index.jsx b/src/components/dashboard/fastReact/operator/RowActions/index.jsx new file mode 100644 index 0000000..5b632d6 --- /dev/null +++ b/src/components/dashboard/fastReact/operator/RowActions/index.jsx @@ -0,0 +1,13 @@ +import { Box } from "@mui/material"; +import EditForm from "../Form/EditForm"; + +const RowActions = ({ row, mutate }) => { + return ( + + {row.original.status === 2 && ( + + )} + + ); +}; +export default RowActions; diff --git a/src/components/dashboard/fastReact/operator/Toolbar.jsx b/src/components/dashboard/fastReact/operator/Toolbar.jsx new file mode 100644 index 0000000..479ae4e --- /dev/null +++ b/src/components/dashboard/fastReact/operator/Toolbar.jsx @@ -0,0 +1,13 @@ +import PrintExcel from "./ExcelPrint"; +import ComplaintList from "../complaintList"; +import { Stack } from "@mui/material"; + +const Toolbar = ({ table, filterData, mutate }) => { + return ( + + + + + ); +}; +export default Toolbar; diff --git a/src/components/dashboard/fastReact/operator/index.jsx b/src/components/dashboard/fastReact/operator/index.jsx new file mode 100644 index 0000000..a2b328c --- /dev/null +++ b/src/components/dashboard/fastReact/operator/index.jsx @@ -0,0 +1,14 @@ +"use client"; +import PageTitle from "@/core/components/PageTitle"; +import { Stack } from "@mui/material"; +import OperatorList from "./OperatorList"; + +const OperatorPage = () => { + return ( + + + + + ); +}; +export default OperatorPage; diff --git a/src/components/dashboard/fastReact/supervisor/ExcelPrint/index.jsx b/src/components/dashboard/fastReact/supervisor/ExcelPrint/index.jsx new file mode 100644 index 0000000..120a8f0 --- /dev/null +++ b/src/components/dashboard/fastReact/supervisor/ExcelPrint/index.jsx @@ -0,0 +1,75 @@ +import { Button, CircularProgress, IconButton, useMediaQuery } from "@mui/material"; +import { useMemo, useState } from "react"; +import moment from "jalali-moment"; +import FileSaver from "file-saver"; +import DescriptionIcon from "@mui/icons-material/Description"; +import useRequest from "@/lib/hooks/useRequest"; +import { EXPORT_FAST_REACT_SUPERVISOR_LIST } from "@/core/utils/routes"; +import { useTheme } from "@emotion/react"; +import DataTableFilterDataStructure from "@/core/utils/DataTableFilterDataStructure"; +import isArrayEmpty from "@/core/utils/isArrayEmpty"; + +const PrintExcel = ({ table, filterData }) => { + const [loading, setLoading] = useState(false); + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("sm")); + const requestServer = useRequest(); + const activeFilters = Object.entries(filterData).reduce((acc, [key, filter]) => { + if (filter.value && (!Array.isArray(filter.value) || filter.value.some((item) => item.trim() !== ""))) { + acc.push({ id: key, value: filter.value }); + } + return acc; + }, []); + + const filterParams = useMemo(() => { + const params = new URLSearchParams(); + if (activeFilters.length > 0) { + activeFilters.map((filter, index) => { + const filteredFilterData = DataTableFilterDataStructure(filterData, isArrayEmpty); + params.set("filters", JSON.stringify(filteredFilterData.length === 0 ? [] : filteredFilterData)); + }); + } else { + params.set("filters", JSON.stringify([])); + } + return params; + }, [activeFilters]); + + const clickHandler = () => { + setLoading(true); + requestServer(`${EXPORT_FAST_REACT_SUPERVISOR_LIST}?${filterParams}`, "get", { + requestOptions: { responseType: "blob" }, + }) + .then((response) => { + const filename = `خروجی کارتابل ارزیابی واکنش سریع تاریخ_${moment().format("jYYYY_jMM_jDD")}.xlsx`; + FileSaver.saveAs(response.data, filename); + }) + .catch(() => {}) + .finally(() => { + setLoading(false); + }); + }; + + return ( + <> + {isMobile ? ( + + + + ) : ( + + )} + + ); +}; + +export default PrintExcel; diff --git a/src/components/dashboard/fastReact/supervisor/RowActions/ConfirmDialog/ConfirmContent.jsx b/src/components/dashboard/fastReact/supervisor/RowActions/ConfirmDialog/ConfirmContent.jsx new file mode 100644 index 0000000..5944db3 --- /dev/null +++ b/src/components/dashboard/fastReact/supervisor/RowActions/ConfirmDialog/ConfirmContent.jsx @@ -0,0 +1,69 @@ +import { Button, DialogActions, DialogContent, Stack, TextField } from "@mui/material"; +import { useForm } from "react-hook-form"; +import useRequest from "@/lib/hooks/useRequest"; +import { VERIFY_BY_FAST_REACT_SUPERVISOR } from "@/core/utils/routes"; + +const ConfirmContent = ({ rowId, mutate, setOpenConfirmDialog }) => { + const requestServer = useRequest({ notificationSuccess: true }); + + const { + register, + handleSubmit, + formState: { isSubmitting }, + } = useForm({ + defaultValues: { + description: "", + }, + }); + + const onSubmit = async (data) => { + const formData = new FormData(); + data.description !== "" && formData.append("description", data.description); + formData.append("verify", 1); + requestServer(`${VERIFY_BY_FAST_REACT_SUPERVISOR}/${rowId}`, "post", { + data: formData, + }) + .then(() => { + mutate(); + setOpenConfirmDialog(false); + }) + .catch(() => {}); + }; + + return ( + <> + + + + + + + + + + + + + ); +}; +export default ConfirmContent; diff --git a/src/components/dashboard/fastReact/supervisor/RowActions/ConfirmDialog/index.jsx b/src/components/dashboard/fastReact/supervisor/RowActions/ConfirmDialog/index.jsx new file mode 100644 index 0000000..10e6048 --- /dev/null +++ b/src/components/dashboard/fastReact/supervisor/RowActions/ConfirmDialog/index.jsx @@ -0,0 +1,29 @@ +import { Dialog, DialogTitle, IconButton, Tooltip } from "@mui/material"; +import { useState } from "react"; +import ConfirmContent from "./ConfirmContent"; +import DoneIcon from "@mui/icons-material/Done"; +const ConfirmForm = ({ rowId, mutate }) => { + const [openConfirmDialog, setOpenConfirmDialog] = useState(false); + return ( + <> + + setOpenConfirmDialog(true)}> + + + + + تایید اقدام + + + + ); +}; +export default ConfirmForm; diff --git a/src/components/dashboard/fastReact/supervisor/RowActions/DescriptionDialog/index.jsx b/src/components/dashboard/fastReact/supervisor/RowActions/DescriptionDialog/index.jsx new file mode 100644 index 0000000..4d80ac4 --- /dev/null +++ b/src/components/dashboard/fastReact/supervisor/RowActions/DescriptionDialog/index.jsx @@ -0,0 +1,50 @@ +import { Dialog, DialogContent, DialogTitle, IconButton, Tooltip, Typography, Card, CardContent } from "@mui/material"; +import { useState } from "react"; +import RemoveRedEyeIcon from "@mui/icons-material/RemoveRedEye"; +import CloseIcon from "@mui/icons-material/Close"; + +const DescriptionForm = ({ description, title, icon: IconComponent = RemoveRedEyeIcon }) => { + const [openOfficerDescriptionDialog, setOpenOfficerDescriptionDialog] = useState(false); + + return ( + <> + + setOpenOfficerDescriptionDialog(true)}> + + + + setOpenOfficerDescriptionDialog(false)} + PaperProps={{ + sx: { + transition: "all .3s", + boxShadow: "rgba(60, 64, 67, 0.3) 0px 1px 16px 0px, rgba(60, 64, 67, 0.15) 0px 1px 10px 0px", + borderRadius: 2, + padding: 1, + }, + }} + maxWidth="sm" + fullWidth + dir="rtl" + > + + + {title} + + setOpenOfficerDescriptionDialog(false)} size="small"> + + + + + + + {description || "هیچ توضیحی موجود نیست"} + + + + + + ); +}; +export default DescriptionForm; diff --git a/src/components/dashboard/fastReact/supervisor/RowActions/ImageDialog/ImageFormContent.jsx b/src/components/dashboard/fastReact/supervisor/RowActions/ImageDialog/ImageFormContent.jsx new file mode 100644 index 0000000..c8f22d0 --- /dev/null +++ b/src/components/dashboard/fastReact/supervisor/RowActions/ImageDialog/ImageFormContent.jsx @@ -0,0 +1,45 @@ +import { Box, Stack, Typography, useTheme } from "@mui/material"; +import Image from "next/image"; + +const ImageFormContent = ({ image, title }) => { + const theme = useTheme(); + return ( + + + {title} + + + Image + + + ); +}; +export default ImageFormContent; diff --git a/src/components/dashboard/fastReact/supervisor/RowActions/ImageDialog/index.jsx b/src/components/dashboard/fastReact/supervisor/RowActions/ImageDialog/index.jsx new file mode 100644 index 0000000..de3b76a --- /dev/null +++ b/src/components/dashboard/fastReact/supervisor/RowActions/ImageDialog/index.jsx @@ -0,0 +1,46 @@ +import PhotoLibraryIcon from "@mui/icons-material/PhotoLibrary"; +import { Button, Dialog, DialogActions, DialogContent, DialogTitle, IconButton, Tooltip } from "@mui/material"; +import { useState } from "react"; +import ImageFormContent from "./ImageFormContent"; + +const ImageDialog = ({ image_before, image_after }) => { + const [openImageDialog, setOpenImageDialog] = useState(false); + + return ( + <> + + setOpenImageDialog(true)}> + + + + setOpenImageDialog(false)} + PaperProps={{ + sx: { + boxShadow: "rgba(60, 64, 67, 0.3) 0px 1px 16px 0px, rgba(60, 64, 67, 0.15) 0px 1px 10px 0px", + }, + }} + maxWidth={"sm"} + scroll="paper" + > + تصاویر + + {openImageDialog && ( + <> + + + + )} + + + + + + + ); +}; +export default ImageDialog; diff --git a/src/components/dashboard/fastReact/supervisor/RowActions/LocationDialog/LocationFormContent.jsx b/src/components/dashboard/fastReact/supervisor/RowActions/LocationDialog/LocationFormContent.jsx new file mode 100644 index 0000000..94f091d --- /dev/null +++ b/src/components/dashboard/fastReact/supervisor/RowActions/LocationDialog/LocationFormContent.jsx @@ -0,0 +1,30 @@ +"use client"; +import { Box, Button, DialogActions, DialogContent, Stack } from "@mui/material"; +import React from "react"; +import dynamic from "next/dynamic"; +import MapLoading from "@/core/components/MapLayer/Loading"; +import ShowLocationMarker from "@/core/components/ShowLocationMarker"; +const MapLayer = dynamic(() => import("@/core/components/MapLayer"), { + loading: () => , + ssr: false, +}); + +const LocationFormContent = ({ setOpenLocationDialog, start_lat, start_lng }) => { + return ( + <> + + + + + + + + + + + + ); +}; +export default LocationFormContent; diff --git a/src/components/dashboard/fastReact/supervisor/RowActions/LocationDialog/index.jsx b/src/components/dashboard/fastReact/supervisor/RowActions/LocationDialog/index.jsx new file mode 100644 index 0000000..09df6ad --- /dev/null +++ b/src/components/dashboard/fastReact/supervisor/RowActions/LocationDialog/index.jsx @@ -0,0 +1,36 @@ +import React, { useState } from "react"; +import { Tooltip, IconButton, Dialog, DialogTitle } from "@mui/material"; +import ExploreIcon from "@mui/icons-material/Explore"; +import LocationFormContent from "./LocationFormContent"; +const LocationForm = ({ start_lat, start_lng }) => { + const [openLocationDialog, setOpenLocationDialog] = useState(false); + + return ( + <> + + setOpenLocationDialog(true)}> + + + + setOpenLocationDialog(false)} + PaperProps={{ + sx: { + boxShadow: "rgba(60, 64, 67, 0.3) 0px 1px 16px 0px, rgba(60, 64, 67, 0.15) 0px 1px 10px 0px", + }, + }} + dir="rtl" + maxWidth={"md"} + > + موقعیت + + + + ); +}; +export default LocationForm; diff --git a/src/components/dashboard/fastReact/supervisor/RowActions/RejectDialog/RejectFormContent.jsx b/src/components/dashboard/fastReact/supervisor/RowActions/RejectDialog/RejectFormContent.jsx new file mode 100644 index 0000000..5c51886 --- /dev/null +++ b/src/components/dashboard/fastReact/supervisor/RowActions/RejectDialog/RejectFormContent.jsx @@ -0,0 +1,83 @@ +import { Button, DialogActions, DialogContent, Stack, TextField } from "@mui/material"; +import { useForm } from "react-hook-form"; +import { yupResolver } from "@hookform/resolvers/yup"; +import * as Yup from "yup"; +import useRequest from "@/lib/hooks/useRequest"; +import { REJECT_BY_FAST_REACT_SUPERVISOR } from "@/core/utils/routes"; + +const RejectContent = ({ rowId, mutate, setOpenRejectDialog }) => { + const requestServer = useRequest({ notificationSuccess: true }); + + const validationSchema = Yup.object().shape({ + description: Yup.string().required("لطفاً توضیحات را وارد کنید."), + }); + + const { + register, + handleSubmit, + formState: { errors, isSubmitting }, + reset, + } = useForm({ + defaultValues: { + description: "", + }, + resolver: yupResolver(validationSchema), + mode: "onBlur", + }); + + const onSubmit = async (data) => { + const formData = new FormData(); + formData.append("description", data.description); + formData.append("verify", 2); + requestServer(`${REJECT_BY_FAST_REACT_SUPERVISOR}/${rowId}`, "post", { + data: formData, + }) + .then(() => { + mutate(); + setOpenRejectDialog(false); + }) + .catch(() => {}) + .finally(() => { + reset(); + }); + }; + + return ( + <> + + + + + + + + + + + + + ); +}; +export default RejectContent; diff --git a/src/components/dashboard/fastReact/supervisor/RowActions/RejectDialog/index.jsx b/src/components/dashboard/fastReact/supervisor/RowActions/RejectDialog/index.jsx new file mode 100644 index 0000000..5d754a5 --- /dev/null +++ b/src/components/dashboard/fastReact/supervisor/RowActions/RejectDialog/index.jsx @@ -0,0 +1,29 @@ +import { Dialog, DialogTitle, IconButton, Tooltip } from "@mui/material"; +import { useState } from "react"; +import ClearIcon from "@mui/icons-material/Clear"; +import RejectContent from "./RejectFormContent"; +const RejectForm = ({ rowId, mutate }) => { + const [openRejectDialog, setOpenRejectDialog] = useState(false); + return ( + <> + + setOpenRejectDialog(true)}> + + + + + عدم تایید اقدام + + + + ); +}; +export default RejectForm; diff --git a/src/components/dashboard/fastReact/supervisor/RowActions/RestoreForm/RestoreContent.jsx b/src/components/dashboard/fastReact/supervisor/RowActions/RestoreForm/RestoreContent.jsx new file mode 100644 index 0000000..06c86a6 --- /dev/null +++ b/src/components/dashboard/fastReact/supervisor/RowActions/RestoreForm/RestoreContent.jsx @@ -0,0 +1,42 @@ +import { RESTORE_FAST_REACT } from "@/core/utils/routes"; +import useRequest from "@/lib/hooks/useRequest"; +import { Button, DialogActions, DialogContent, Stack, Typography } from "@mui/material"; +import { useState } from "react"; + +const RestoreContent = ({ rowId, mutate, setOpenRestoreDialog }) => { + const [submitting, setSubmitting] = useState(false); + const requestServer = useRequest({ notificationSuccess: true }); + const handleClick = () => { + setSubmitting(true); + requestServer(`${RESTORE_FAST_REACT}/${rowId}`, "post") + .then(() => { + mutate(); + setOpenRestoreDialog(false); + setSubmitting(false); + }) + .catch(() => { + setSubmitting(false); + }); + }; + return ( + <> + + + + آیا از بازگردانی فعالیت اطمینان دارید؟ + + + + + + + + + ); +}; + +export default RestoreContent; diff --git a/src/components/dashboard/fastReact/supervisor/RowActions/RestoreForm/index.jsx b/src/components/dashboard/fastReact/supervisor/RowActions/RestoreForm/index.jsx new file mode 100644 index 0000000..2632c20 --- /dev/null +++ b/src/components/dashboard/fastReact/supervisor/RowActions/RestoreForm/index.jsx @@ -0,0 +1,32 @@ +import React, { useState } from "react"; +import { Tooltip, IconButton, Dialog, DialogTitle } from "@mui/material"; +import ReplyIcon from "@mui/icons-material/Reply"; +import RestoreContent from "./RestoreContent"; +const RestoreForm = ({ rowId, mutate }) => { + const [openRestoreDialog, setOpenRestoreDialog] = useState(false); + + return ( + <> + + setOpenRestoreDialog(true)}> + + + + setOpenRestoreDialog(false)} + PaperProps={{ + sx: { + boxShadow: "rgba(60, 64, 67, 0.3) 0px 1px 16px 0px, rgba(60, 64, 67, 0.15) 0px 1px 10px 0px", + }, + }} + dir="rtl" + maxWidth={"md"} + > + بازگردانی فعالیت + + + + ); +}; +export default RestoreForm; diff --git a/src/components/dashboard/fastReact/supervisor/RowActions/index.jsx b/src/components/dashboard/fastReact/supervisor/RowActions/index.jsx new file mode 100644 index 0000000..f9947ab --- /dev/null +++ b/src/components/dashboard/fastReact/supervisor/RowActions/index.jsx @@ -0,0 +1,25 @@ +import { Box } from "@mui/material"; +import ConfirmForm from "./ConfirmDialog"; +import RejectForm from "./RejectDialog"; +import { usePermissions } from "@/lib/hooks/usePermissions"; +import RestoreForm from "./RestoreForm"; + +const RowActions = ({ row, mutate }) => { + const { data: userPermissions } = usePermissions(); + const hasSupervisePermission = userPermissions.some((item) => + ["supervise-fast-react", "supervise-fast-react-province"].includes(item) + ); + const hasRestorePermission = userPermissions.includes("restore-fast-react"); + return ( + + {hasSupervisePermission && row.original?.status === 0 && ( + + )} + {hasSupervisePermission && row.original?.status === 0 && ( + + )} + {hasRestorePermission && row.original?.status !== 0 && } + + ); +}; +export default RowActions; diff --git a/src/components/dashboard/fastReact/supervisor/SupervisorList.jsx b/src/components/dashboard/fastReact/supervisor/SupervisorList.jsx new file mode 100644 index 0000000..89eb53c --- /dev/null +++ b/src/components/dashboard/fastReact/supervisor/SupervisorList.jsx @@ -0,0 +1,423 @@ +import { useEffect, useMemo, useState } from "react"; +import { Box, Stack, Typography } from "@mui/material"; +import DataTableWithAuth from "@/core/components/DataTableWithAuth"; +import Toolbar from "./Toolbar"; +import moment from "jalali-moment"; +import RowActions from "./RowActions"; +import { GET_FAST_REACT_SUPERVISOR } from "@/core/utils/routes"; +import useProvinces from "@/lib/hooks/useProvince"; +import CustomSelectByDependency from "@/core/components/DataTable/filter/fieldsType/CustomSelectByDependency"; +import { usePermissions } from "@/lib/hooks/usePermissions"; +import { useAuth } from "@/lib/contexts/auth"; +import useEdaratLists from "@/lib/hooks/useEdaratLists"; +import DescriptionForm from "./RowActions/DescriptionDialog"; +import LocationForm from "./RowActions/LocationDialog"; +import EngineeringIcon from "@mui/icons-material/Engineering"; +import ThreePIcon from "@mui/icons-material/ThreeP"; +import ImageDialog from "./RowActions/ImageDialog"; + +const SupervisorList = () => { + const { data: userPermissions } = usePermissions(); + const hasCountryPermission = userPermissions?.includes("show-fast-react"); + const { user } = useAuth(); + const statusOptions = [ + { value: "", label: "همه وضعیت ها" }, + { value: 0, label: "درحال بررسی" }, + { value: 1, label: "تایید شده" }, + { value: 2, label: "عدم تایید" }, + ]; + + const columns = useMemo(() => { + const dynamicColumns = { + header: "استان", + id: "road_observeds__province_id", + enableColumnFilter: true, + datatype: "numeric", + filterMode: "equals", + grow: false, + size: 130, + ColumnSelectComponent: (props) => { + const { provinces, errorProvinces, loadingProvinces } = useProvinces(); + const getColumnSelectOptions = useMemo(() => { + if (loadingProvinces) { + return [{ value: "loading", label: "در حال بارگذاری..." }]; + } + if (errorProvinces) { + return [{ value: "error", label: "خطا در بارگذاری" }]; + } + return [ + { value: "", label: "کل کشور" }, + ...provinces.map((province) => ({ + value: province.id, + label: province.name_fa, + })), + ]; + }, [provinces, errorProvinces, loadingProvinces]); + return ( + + ); + }, + Cell: ({ row }) => <>{row.original.province_fa}, + }; + return [ + { + accessorKey: "id", + header: "کد یکتا", + id: "road_observeds__id", + enableColumnFilter: true, + datatype: "text", + filterMode: "equals", + sortDescFirst: false, + columnFilterModeOptions: ["equals", "contains"], + grow: false, + size: 100, + }, + ...(hasCountryPermission ? [dynamicColumns] : []), + { + header: "اداره", + id: "edarate_shahri_id", + enableColumnFilter: true, + datatype: "numeric", + filterMode: "equals", + dependencyId: hasCountryPermission ? "road_observeds__province_id" : null, + grow: false, + size: 120, + ColumnSelectComponent: (props) => { + const { edaratList, loadingEdaratList, errorEdaratList } = useEdaratLists( + hasCountryPermission ? props.dependencyFieldValue.value : user.province_id + ); + const [prevDependency, setPrevDependency] = useState( + hasCountryPermission ? props.dependencyFieldValue.value : user.province_id + ); + + const getColumnSelectOptions = useMemo(() => { + if (hasCountryPermission && props.dependencyFieldValue.value === "") { + return [{ value: "empty", label: "ابتدا استان را انتخاب کنید" }]; + } + if (loadingEdaratList) { + return [{ value: "loading", label: "در حال بارگذاری..." }]; + } + if (errorEdaratList) { + return [{ value: "error", label: "خطا در بارگذاری" }]; + } + return [ + { value: "", label: "کل ادارات" }, + ...edaratList.map((edare) => ({ + value: edare.id, + label: edare.name_fa, + })), + ]; + }, [edaratList, loadingEdaratList, errorEdaratList]); + useEffect(() => { + if (hasCountryPermission) return; + if (prevDependency === props.dependencyFieldValue?.value) return; + props.handleChange({ ...props.filterParameters, value: "" }); + setPrevDependency(props.dependencyFieldValue?.value); + }, [props.dependencyFieldValue?.value, hasCountryPermission]); + return ( + + ); + }, + Cell: ({ renderedCellValue, row }) => <>{row.original.name_fa}, + }, + { + header: "اطلاعات ثبت‌ شده در سامانه سوانح", + id: "fkInfo", + enableColumnFilter: false, + enableSorting: false, + grow: false, + size: 50, + columns: [ + { + accessorKey: "fk_RegisteredEventMessage", + header: "کد سوانح", + id: "fk_RegisteredEventMessage", + enableColumnFilter: true, + datatype: "text", + filterMode: "equals", + sortDescFirst: false, + columnFilterModeOptions: ["equals", "contains"], + grow: false, + size: 100, + }, + { + accessorKey: "Title", + header: "موضوع گزارش", + id: "Title", + enableColumnFilter: false, + datatype: "text", + filterMode: "equals", + sortDescFirst: false, + columnFilterModeOptions: ["equals", "contains"], + grow: false, + size: 100, + }, + { + accessorKey: "FeatureTypeTitle", + header: "نوع گزارش", + id: "FeatureTypeTitle", + enableColumnFilter: false, + datatype: "text", + filterMode: "equals", + sortDescFirst: false, + columnFilterModeOptions: ["equals", "contains"], + grow: false, + size: 100, + }, + { + accessorKey: "Description", + header: "توضیح گزارش", + id: "Description", + enableColumnFilter: false, + datatype: "text", + filterMode: "equals", + sortDescFirst: false, + columnFilterModeOptions: ["equals", "contains"], + grow: false, + size: 100, + muiTableBodyCellProps: { + sx: { + borderLeft: "1px solid #e1e1e1", + py: 0, + "&:first-of-type": { + borderLeft: "unset", + }, + }, + }, + Cell: ({ renderedCellValue }) => { + if (renderedCellValue) { + return ( + + + + ); + } + return بدون توضیحات; + }, + }, + { + header: "موقعیت", + id: "location", + enableColumnFilter: false, + enableSorting: false, + datatype: "array", + grow: false, + size: 100, + muiTableBodyCellProps: { + sx: { + borderLeft: "1px solid #e1e1e1", + py: 0, + "&:first-of-type": { + borderLeft: "unset", + }, + }, + }, + Cell: ({ row }) => { + if (!row.original.rms_start_latlng) return <>بدون موقعیت; + return ( + + + + ); + }, + }, + { + header: "تصاویر", + id: "images", + enableColumnFilter: false, + enableSorting: false, + datatype: "array", + grow: false, + size: 100, + muiTableBodyCellProps: { + sx: { + borderLeft: "1px solid #e1e1e1", + py: 0, + "&:first-of-type": { + borderLeft: "unset", + }, + }, + }, + Cell: ({ row }) => { + if (!row.original?.image_before && !row.original?.image_after) return <>بدون تصویر; + return ( + + {row.original?.image_before && row.original?.image_after && ( + + )} + + ); + }, + }, + { + accessorKey: "MobileForSendEventSms", + header: "شماره تماس گیرنده", + id: "MobileForSendEventSms", + enableColumnFilter: false, + datatype: "text", + filterMode: "equals", + sortDescFirst: false, + columnFilterModeOptions: ["equals", "contains"], + grow: false, + size: 100, + }, + { + accessorFn: (row) => moment(row.StartTime_DateTime, "M/D/YYYY h:mm:ss A").locale("fa").format("HH:mm | yyyy/MM/DD"), + header: "تاریخ ثبت", + id: "StartTime_DateTime", + enableColumnFilter: false, + datatype: "date", + filterMode: "between", + grow: false, + size: 100, + }, + ] + }, + { + accessorFn: (row) => { + return row.rms_last_activity + ? moment(row?.rms_last_activity).locale("fa").format("HH:mm | yyyy/MM/DD") + : "بدون تاریخ"; + }, + header: "تاریخ اقدام", + id: "rms_last_activity", + enableColumnFilter: true, + datatype: "date", + filterMode: "between", + grow: false, + size: 100, + }, + { + accessorKey: "rms_description", + header: "توضیح مامور", + id: "rms_description", + enableColumnFilter: false, + datatype: "text", + filterMode: "equals", + sortDescFirst: false, + columnFilterModeOptions: ["equals", "contains"], + grow: false, + size: 100, + muiTableBodyCellProps: { + sx: { + borderLeft: "1px solid #e1e1e1", + py: 0, + "&:first-of-type": { + borderLeft: "unset", + }, + }, + }, + Cell: ({ renderedCellValue }) => { + if (renderedCellValue) { + return ( + + + + ); + } + return بدون توضیحات; + }, + }, + { + accessorKey: "supervisor_description", + header: "توضیح کارشناس", + id: "supervisor_description", + enableColumnFilter: false, + datatype: "text", + filterMode: "equals", + sortDescFirst: false, + columnFilterModeOptions: ["equals", "contains"], + grow: false, + size: 100, + muiTableBodyCellProps: { + sx: { + borderLeft: "1px solid #e1e1e1", + py: 0, + "&:first-of-type": { + borderLeft: "unset", + }, + }, + }, + Cell: ({ renderedCellValue }) => { + if (renderedCellValue) { + return ( + + + + ); + } + return بدون توضیحات; + }, + }, + { + accessorKey: "status", + header: "وضعیت", + id: "status", + enableColumnFilter: true, + datatype: "numeric", + filterMode: "equals", + sortDescFirst: true, + grow: false, + size: 100, + columnSelectOption: () => { + return statusOptions.map((status) => ({ + value: status.value, + label: status.label, + })); + }, + Cell: ({ row }) => <>{row.original.status_fa}, + }, + ]; + }, []); + + return ( + <> + + + + + ); +}; +export default SupervisorList; diff --git a/src/components/dashboard/fastReact/supervisor/Toolbar.jsx b/src/components/dashboard/fastReact/supervisor/Toolbar.jsx new file mode 100644 index 0000000..479ae4e --- /dev/null +++ b/src/components/dashboard/fastReact/supervisor/Toolbar.jsx @@ -0,0 +1,13 @@ +import PrintExcel from "./ExcelPrint"; +import ComplaintList from "../complaintList"; +import { Stack } from "@mui/material"; + +const Toolbar = ({ table, filterData, mutate }) => { + return ( + + + + + ); +}; +export default Toolbar; diff --git a/src/components/dashboard/fastReact/supervisor/index.jsx b/src/components/dashboard/fastReact/supervisor/index.jsx new file mode 100644 index 0000000..3fa490b --- /dev/null +++ b/src/components/dashboard/fastReact/supervisor/index.jsx @@ -0,0 +1,14 @@ +"use client"; +import PageTitle from "@/core/components/PageTitle"; +import { Stack } from "@mui/material"; +import SupervisorList from "./SupervisorList"; + +const SupervisorPage = () => { + return ( + + + + + ); +}; +export default SupervisorPage; diff --git a/src/components/dashboard/inquiryPrivacy/provinceAdmin/zaminGov/RowActions/MapModify/PolygonForDesign.jsx b/src/components/dashboard/inquiryPrivacy/provinceAdmin/zaminGov/RowActions/MapModify/PolygonForDesign.jsx index cd75f8d..4e1bb43 100644 --- a/src/components/dashboard/inquiryPrivacy/provinceAdmin/zaminGov/RowActions/MapModify/PolygonForDesign.jsx +++ b/src/components/dashboard/inquiryPrivacy/provinceAdmin/zaminGov/RowActions/MapModify/PolygonForDesign.jsx @@ -1,7 +1,5 @@ "use client"; -import "leaflet-draw/dist/leaflet.draw.css"; -import "@/assets/scss/map-styles.scss"; import { FeatureGroup, useMap } from "react-leaflet"; import { EditControl } from "react-leaflet-draw"; import { useEffect, useRef, useState } from "react"; diff --git a/src/components/dashboard/roadItems/operator/Actions/Create/Forms/CreateFormContent.jsx b/src/components/dashboard/roadItems/operator/Actions/Create/Forms/CreateFormContent.jsx index cfe6c09..62bc6a8 100644 --- a/src/components/dashboard/roadItems/operator/Actions/Create/Forms/CreateFormContent.jsx +++ b/src/components/dashboard/roadItems/operator/Actions/Create/Forms/CreateFormContent.jsx @@ -249,7 +249,6 @@ const CreateFormContent = ({ setOpen, onSubmit, is_gasht = false }) => { diff --git a/src/components/dashboard/roadItems/operator/Actions/Create/Forms/index.jsx b/src/components/dashboard/roadItems/operator/Actions/Create/Forms/index.jsx index c9921bf..edcc098 100644 --- a/src/components/dashboard/roadItems/operator/Actions/Create/Forms/index.jsx +++ b/src/components/dashboard/roadItems/operator/Actions/Create/Forms/index.jsx @@ -1,10 +1,11 @@ "use client"; -import { Dialog } from "@mui/material"; +import { Dialog, IconButton } from "@mui/material"; import CreateFormContent from "@/components/dashboard/roadItems/operator/Actions/Create/Forms/CreateFormContent"; import { CREATE_ROAD_ITEMS } from "@/core/utils/routes"; import useRequest from "@/lib/hooks/useRequest"; +import CloseIcon from "@mui/icons-material/Close"; -const OperatorCreateForm = ({ open, setOpen, mutate, rowId }) => { +const OperatorCreateForm = ({ open, setOpen, mutate }) => { const requestServer = useRequest({ notificationSuccess: true }); const HandleSubmit = async ({ result }) => { const formData = new FormData(); @@ -22,6 +23,19 @@ const OperatorCreateForm = ({ open, setOpen, mutate, rowId }) => { }; return ( + setOpen(false)} + sx={(theme) => ({ + position: "absolute", + right: 8, + top: 8, + zIndex: 50, + color: theme.palette.grey[500], + })} + > + + {open && } ); diff --git a/src/components/dashboard/roadItems/operator/Actions/ObservedGashtCreate/GashtCreate.jsx b/src/components/dashboard/roadItems/operator/Actions/ObservedGashtCreate/GashtCreate.jsx index 968d907..4784f26 100644 --- a/src/components/dashboard/roadItems/operator/Actions/ObservedGashtCreate/GashtCreate.jsx +++ b/src/components/dashboard/roadItems/operator/Actions/ObservedGashtCreate/GashtCreate.jsx @@ -1,11 +1,25 @@ -import { Dialog } from "@mui/material"; +import { Dialog, IconButton } from "@mui/material"; import GashtCreateContent from "./GashtCreateContent"; import { useState } from "react"; +import CloseIcon from "@mui/icons-material/Close"; const GashtCreate = ({ open, setOpen, mutate, rowId }) => { const [tabState, setTabState] = useState(0); return ( + setOpen(false)} + sx={(theme) => ({ + position: "absolute", + right: 8, + top: 8, + zIndex: 50, + color: theme.palette.grey[500], + })} + > + + {open && ( )} diff --git a/src/components/dashboard/roadItems/operator/Actions/ObservedGashtCreate/TableInfo.jsx b/src/components/dashboard/roadItems/operator/Actions/ObservedGashtCreate/TableInfo.jsx index 73f9cdd..ca60b84 100644 --- a/src/components/dashboard/roadItems/operator/Actions/ObservedGashtCreate/TableInfo.jsx +++ b/src/components/dashboard/roadItems/operator/Actions/ObservedGashtCreate/TableInfo.jsx @@ -43,7 +43,7 @@ const TableInfo = ({ specialFilter, setTabState, setItemInfo }) => { }, { accessorKey: "item_name", - header: "نام ایتم", + header: "نام آیتم", id: "item_name", enableColumnFilter: false, datatype: "text", diff --git a/src/components/dashboard/roadItems/operator/ExcelPrint/index.jsx b/src/components/dashboard/roadItems/operator/ExcelPrint/index.jsx index 4cf871f..8862da8 100644 --- a/src/components/dashboard/roadItems/operator/ExcelPrint/index.jsx +++ b/src/components/dashboard/roadItems/operator/ExcelPrint/index.jsx @@ -7,6 +7,8 @@ import DescriptionIcon from "@mui/icons-material/Description"; import useRequest from "@/lib/hooks/useRequest"; import { EXPORT_ROAD_ITEMS_OPERATOR_LIST } from "@/core/utils/routes"; import { useTheme } from "@emotion/react"; +import DataTableFilterDataStructure from "@/core/utils/DataTableFilterDataStructure"; +import isArrayEmpty from "@/core/utils/isArrayEmpty"; const PrintExcel = ({ table, filterData }) => { const theme = useTheme(); @@ -14,8 +16,7 @@ const PrintExcel = ({ table, filterData }) => { const [loading, setLoading] = useState(false); const requestServer = useRequest({ notificationSuccess: true }); const activeFilters = Object.entries(filterData).reduce((acc, [key, filter]) => { - if (filter.value) { - // Check if there's an active filter + if (filter.value && (!Array.isArray(filter.value) || filter.value.some((item) => item.trim() !== ""))) { acc.push({ id: key, value: filter.value }); } return acc; @@ -23,10 +24,14 @@ const PrintExcel = ({ table, filterData }) => { const filterParams = useMemo(() => { const params = new URLSearchParams(); - activeFilters.length > 0 && + if (activeFilters.length > 0) { activeFilters.map((filter, index) => { - params.set(`${filter.id}`, filter.value); + const filteredFilterData = DataTableFilterDataStructure(filterData, isArrayEmpty); + params.set("filters", JSON.stringify(filteredFilterData.length === 0 ? [] : filteredFilterData)); }); + } else { + params.set("filters", JSON.stringify([])); + } return params; }, [activeFilters]); @@ -36,7 +41,7 @@ const PrintExcel = ({ table, filterData }) => { requestOptions: { responseType: "blob" }, }) .then((response) => { - const filename = `خروجی کارتابل فعالیت روزانه تاریخ_${moment().format("jYYYY_jMM_jDD")}.xlsx`; + const filename = `خروجی کارتابل عملیات فعالیت روزانه تاریخ_${moment().format("jYYYY_jMM_jDD")}.xlsx`; FileSaver.saveAs(response.data, filename); }) .catch(() => {}) diff --git a/src/components/dashboard/roadItems/operator/OperatorList.jsx b/src/components/dashboard/roadItems/operator/OperatorList.jsx index 52913cc..793b1e6 100644 --- a/src/components/dashboard/roadItems/operator/OperatorList.jsx +++ b/src/components/dashboard/roadItems/operator/OperatorList.jsx @@ -156,9 +156,8 @@ const OperatorList = () => { }, }, { - accessorKey: "files", - header: "تصاویر", // Images - id: "files", + header: "تصاویر", + id: "images", enableColumnFilter: false, enableSorting: false, datatype: "array", @@ -173,23 +172,15 @@ const OperatorList = () => { }, }, }, - Cell: ({ renderedCellValue }) => { - if (renderedCellValue.length > 0) { - return ( - - - - ); - } + Cell: ({ renderedCellValue, row }) => { return ( - - بدون تصویر - + + + ); }, }, { - accessorKey: "location", header: "موقعیت", // Location id: "location", enableColumnFilter: false, @@ -220,8 +211,7 @@ const OperatorList = () => { }, }, { - accessorKey: "cmms_machines", - header: "خودرو ها", // Car ID + header: "خودرو ها", id: "cmmsMachines__machine_code", enableColumnFilter: true, datatype: "text", @@ -239,19 +229,15 @@ const OperatorList = () => { }, }, }, - Cell: ({ renderedCellValue }) => { - if (renderedCellValue) { - return ( - - - - ); - } - return خودرویی وجود ندارد; + Cell: ({ renderedCellValue, row }) => { + return ( + + + + ); }, }, { - accessorKey: "rahdaran", header: "راهداران", id: "rahdaran__code", enableColumnFilter: true, @@ -270,10 +256,10 @@ const OperatorList = () => { }, }, }, - Cell: ({ renderedCellValue }) => { + Cell: ({ renderedCellValue, row }) => { return ( - + ); }, @@ -300,7 +286,7 @@ const OperatorList = () => { }, { accessorKey: "status", - header: "وضعیت", // Status + header: "وضعیت نظارت", id: "status", enableColumnFilter: true, datatype: "numeric", @@ -318,7 +304,7 @@ const OperatorList = () => { }, { accessorKey: "supervisor_description", - header: "توضیحات کارشناس", // Description + header: "توضیحات ناظر", id: "supervisor_description", enableColumnFilter: false, enableSorting: false, diff --git a/src/components/dashboard/roadItems/operator/RowActions/DescriptionForm/index.jsx b/src/components/dashboard/roadItems/operator/RowActions/DescriptionForm/index.jsx index 481e2d6..91b135a 100644 --- a/src/components/dashboard/roadItems/operator/RowActions/DescriptionForm/index.jsx +++ b/src/components/dashboard/roadItems/operator/RowActions/DescriptionForm/index.jsx @@ -8,7 +8,7 @@ const DescriptionForm = ({ description }) => { return ( <> - + setOpenOfficerDescriptionDialog(true)}> @@ -30,7 +30,7 @@ const DescriptionForm = ({ description }) => { > - توضیحات کارشناس + توضیحات ناظر setOpenOfficerDescriptionDialog(false)} size="small"> diff --git a/src/components/dashboard/roadItems/operator/RowActions/EditForm/EditController.jsx b/src/components/dashboard/roadItems/operator/RowActions/EditForm/EditController.jsx index 164fc70..4c4fcc9 100644 --- a/src/components/dashboard/roadItems/operator/RowActions/EditForm/EditController.jsx +++ b/src/components/dashboard/roadItems/operator/RowActions/EditForm/EditController.jsx @@ -1,19 +1,82 @@ -import { Dialog, DialogTitle } from "@mui/material"; -import EditFormContent from "./EditFormContent"; -import { UPDATE_ROAD_ITEMS } from "@/core/utils/routes"; +import DialogLoading from "@/core/components/DialogLoading"; +import { + GET_CMMS_MACHINE_ROAD_ITEM, + GET_IMAGES_ROAD_ITEM, + GET_RAHDARAN_ROAD_ITEM, + UPDATE_ROAD_ITEMS, +} from "@/core/utils/routes"; import useRequest from "@/lib/hooks/useRequest"; +import { Typography } from "@mui/material"; +import { useEffect, useState } from "react"; +import EditFormContent from "./EditFormContent"; const EditController = ({ rowId, mutate, setOpenEditDialog, openEditDialog, row }) => { - const requestServer = useRequest({ notificationSuccess: true }); + const requestServer = useRequest(); + const [loadingImages, setLoadingImages] = useState(true); + const [images, setImages] = useState(null); + const [loadingCmmsMachine, setLoadingCmmsMachine] = useState(true); + const [cmmsMachine, setCmmsMachine] = useState(null); + const [loadingRahdaran, setLoadingRahdaran] = useState(true); + const [rahdaran, setRahdaran] = useState(null); + + useEffect(() => { + const fetchData = async () => { + try { + setLoadingImages(true); + const response = await requestServer(`${GET_IMAGES_ROAD_ITEM}/${rowId}`); + setImages(response.data); + } catch (error) { + } finally { + setLoadingImages(false); + } + }; + + fetchData(); + }, [rowId]); + + useEffect(() => { + const fetchData = async () => { + try { + setLoadingCmmsMachine(true); + const response = await requestServer(`${GET_CMMS_MACHINE_ROAD_ITEM}/${rowId}`); + setCmmsMachine(response.data); + } catch (error) { + } finally { + setLoadingCmmsMachine(false); + } + }; + + fetchData(); + }, [rowId]); + + useEffect(() => { + const fetchData = async () => { + try { + setLoadingRahdaran(true); + const response = await requestServer(`${GET_RAHDARAN_ROAD_ITEM}/${rowId}`); + setRahdaran(response.data); + } catch (error) { + } finally { + setLoadingRahdaran(false); + } + }; + + fetchData(); + }, [rowId]); const HandleSubmit = async ({ result }) => { const formData = new FormData(); for (const [key, value] of Object.entries(result)) { formData.append(key, value); } - await requestServer(`${UPDATE_ROAD_ITEMS}/${rowId}`, "post", { - data: formData, - }) + await requestServer( + `${UPDATE_ROAD_ITEMS}/${rowId}`, + "post", + { + data: formData, + }, + { notificationSuccess: true } + ) .then((response) => { mutate(); setOpenEditDialog(false); @@ -21,34 +84,28 @@ const EditController = ({ rowId, mutate, setOpenEditDialog, openEditDialog, row .catch((error) => {}); }; - return ( - + ) : images && cmmsMachine && rahdaran ? ( + - ویرایش اطلاعات - - + /> + ) : ( + + تمامی اطلاعات یافت نشد! + ); }; export default EditController; diff --git a/src/components/dashboard/roadItems/operator/RowActions/EditForm/EditFormContent.jsx b/src/components/dashboard/roadItems/operator/RowActions/EditForm/EditFormContent.jsx index d8b8dc3..c49d50d 100644 --- a/src/components/dashboard/roadItems/operator/RowActions/EditForm/EditFormContent.jsx +++ b/src/components/dashboard/roadItems/operator/RowActions/EditForm/EditFormContent.jsx @@ -1,7 +1,6 @@ -import { LinearProgress, Typography } from "@mui/material"; -import React from "react"; -import useRoadItemGetISubtems from "@/lib/hooks/useRoadItemGetISubtems"; import EditFormCreate from "@/components/dashboard/roadItems/operator/RowActions/EditForm/EditFormCreate"; +import useRoadItemGetISubtems from "@/lib/hooks/useRoadItemGetISubtems"; +import { LinearProgress, Typography } from "@mui/material"; const EditFormContent = ({ setOpenEditDialog, onSubmit, defaultData, is_gasht }) => { const { subItemsList, loadingSubItemsList, errorSubItemsList } = useRoadItemGetISubtems(defaultData?.item_id); diff --git a/src/components/dashboard/roadItems/operator/RowActions/EditForm/index.jsx b/src/components/dashboard/roadItems/operator/RowActions/EditForm/index.jsx index 9302918..4548f15 100644 --- a/src/components/dashboard/roadItems/operator/RowActions/EditForm/index.jsx +++ b/src/components/dashboard/roadItems/operator/RowActions/EditForm/index.jsx @@ -2,6 +2,7 @@ import { Dialog, DialogTitle, IconButton, Tooltip } from "@mui/material"; import { useState } from "react"; import BorderColorIcon from "@mui/icons-material/BorderColor"; import EditController from "./EditController"; +import CloseIcon from "@mui/icons-material/Close"; const EditForm = ({ row, mutate, rowId }) => { const [openEditDialog, setOpenEditDialog] = useState(false); @@ -19,13 +20,40 @@ const EditForm = ({ row, mutate, rowId }) => { - + + ویرایش اطلاعات + setOpenEditDialog(false)} + sx={(theme) => ({ + position: "absolute", + right: 8, + top: 8, + zIndex: 50, + color: theme.palette.grey[500], + })} + > + + + {openEditDialog && ( + + )} + ); }; diff --git a/src/components/dashboard/roadItems/operator/RowActions/ImageForm/ImageFormContent.jsx b/src/components/dashboard/roadItems/operator/RowActions/ImageForm/ImageFormContent.jsx index 9aaa92e..c8f22d0 100644 --- a/src/components/dashboard/roadItems/operator/RowActions/ImageForm/ImageFormContent.jsx +++ b/src/components/dashboard/roadItems/operator/RowActions/ImageForm/ImageFormContent.jsx @@ -1,4 +1,4 @@ -import { Box, DialogContent, Paper, Stack, Typography, useTheme } from "@mui/material"; +import { Box, Stack, Typography, useTheme } from "@mui/material"; import Image from "next/image"; const ImageFormContent = ({ image, title }) => { @@ -29,6 +29,7 @@ const ImageFormContent = ({ image, title }) => { src={image} alt="Image" fill={true} + sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw" loading="lazy" style={{ objectFit: "contain", diff --git a/src/components/dashboard/roadItems/operator/RowActions/ImageForm/ImagesContent.jsx b/src/components/dashboard/roadItems/operator/RowActions/ImageForm/ImagesContent.jsx new file mode 100644 index 0000000..6414e24 --- /dev/null +++ b/src/components/dashboard/roadItems/operator/RowActions/ImageForm/ImagesContent.jsx @@ -0,0 +1,43 @@ +import { useEffect, useState } from "react"; +import ImageFormContent from "./ImageFormContent"; +import DialogLoading from "@/core/components/DialogLoading"; +import useRequest from "@/lib/hooks/useRequest"; +import { GET_IMAGES_ROAD_ITEM } from "@/core/utils/routes"; +import { Typography } from "@mui/material"; + +const ImagesContent = ({ rowId }) => { + const [loading, setLoading] = useState(true); + const [data, setData] = useState(null); + const request = useRequest(); + + useEffect(() => { + const fetchData = async () => { + try { + setLoading(true); + const response = await request(`${GET_IMAGES_ROAD_ITEM}/${rowId}`); + setData(response.data.data); + } catch (error) { + } finally { + setLoading(false); + } + }; + + fetchData(); + }, [rowId]); + + return ( + <> + {loading ? ( + + ) : data ? ( + <> + + + + ) : ( + تصویری در سامانه یافت نشد + )} + + ); +}; +export default ImagesContent; diff --git a/src/components/dashboard/roadItems/operator/RowActions/ImageForm/index.jsx b/src/components/dashboard/roadItems/operator/RowActions/ImageForm/index.jsx index 130f443..08e9cd9 100644 --- a/src/components/dashboard/roadItems/operator/RowActions/ImageForm/index.jsx +++ b/src/components/dashboard/roadItems/operator/RowActions/ImageForm/index.jsx @@ -1,9 +1,9 @@ -import { Button, Dialog, DialogActions, DialogContent, DialogTitle, IconButton, Paper, Tooltip } from "@mui/material"; -import { useState } from "react"; import PhotoLibraryIcon from "@mui/icons-material/PhotoLibrary"; -import ImageFormContent from "./ImageFormContent"; +import { Button, Dialog, DialogActions, DialogContent, DialogTitle, IconButton, Tooltip } from "@mui/material"; +import { useState } from "react"; +import ImagesContent from "./ImagesContent"; -const ImageDialog = ({ images }) => { +const ImageDialog = ({ rowId }) => { const [openImageDialog, setOpenImageDialog] = useState(false); return ( @@ -26,12 +26,7 @@ const ImageDialog = ({ images }) => { scroll="paper" > تصاویر - - - - - - + {openImageDialog && } + )} + + ); +}; +export default PrintExcel; diff --git a/src/components/dashboard/roadItems/reports/ItemsReports/ReportLists.jsx b/src/components/dashboard/roadItems/reports/ItemsReports/ReportLists.jsx index 8646e22..b739780 100644 --- a/src/components/dashboard/roadItems/reports/ItemsReports/ReportLists.jsx +++ b/src/components/dashboard/roadItems/reports/ItemsReports/ReportLists.jsx @@ -2,8 +2,9 @@ import DataTableWithAuth from "@/core/components/DataTableWithAuth"; import { Box } from "@mui/material"; import { useMemo } from "react"; +import Toolbar from "./TableToolbar"; -const ReportLists = ({ itemsList, data }) => { +const ReportLists = ({ itemsList, data, specialFilter }) => { const columns = useMemo(() => { const dynamicColumns = itemsList?.map((item) => ({ @@ -65,7 +66,7 @@ const ReportLists = ({ itemsList, data }) => { }, }, { - header: "ایتم های فعالیت روزانه", + header: "آیتم های فعالیت روزانه", id: "items", enableColumnFilter: false, enableSorting: false, @@ -85,6 +86,8 @@ const ReportLists = ({ itemsList, data }) => { page_name={"roadItemReportByItems"} table_name={"roadItemReportByItemsList"} enablePagination={false} + TableToolbar={Toolbar} + specialFilter={specialFilter} /> ); diff --git a/src/components/dashboard/roadItems/reports/ItemsReports/Search/FromDateController.jsx b/src/components/dashboard/roadItems/reports/ItemsReports/Search/FromDateController.jsx index 2cfa8af..2e23def 100644 --- a/src/components/dashboard/roadItems/reports/ItemsReports/Search/FromDateController.jsx +++ b/src/components/dashboard/roadItems/reports/ItemsReports/Search/FromDateController.jsx @@ -13,6 +13,7 @@ const FromDateController = ({ control }) => { name="from_date" error={!!error} value={value} + label={"تاریخ فعالیت (از تاریخ)"} placeholder={"از تاریخ"} setFieldValue={(name, newValue) => { const formattedDate = moment(new Date(newValue)).locale("en").format("YYYY-MM-DD"); diff --git a/src/components/dashboard/roadItems/reports/ItemsReports/Search/ToDateController.jsx b/src/components/dashboard/roadItems/reports/ItemsReports/Search/ToDateController.jsx index 003ac20..2429410 100644 --- a/src/components/dashboard/roadItems/reports/ItemsReports/Search/ToDateController.jsx +++ b/src/components/dashboard/roadItems/reports/ItemsReports/Search/ToDateController.jsx @@ -15,6 +15,7 @@ const ToDateController = ({ control }) => { minDate={minDate} error={!!error} value={value} + label={"تاریخ فعالیت (تا تاریخ)"} placeholder={"تا تاریخ"} setFieldValue={(name, newValue) => { const formattedDate = moment(new Date(newValue)).locale("en").format("YYYY-MM-DD"); diff --git a/src/components/dashboard/roadItems/reports/ItemsReports/TableToolbar.jsx b/src/components/dashboard/roadItems/reports/ItemsReports/TableToolbar.jsx new file mode 100644 index 0000000..32c4395 --- /dev/null +++ b/src/components/dashboard/roadItems/reports/ItemsReports/TableToolbar.jsx @@ -0,0 +1,10 @@ +import PrintExcel from "./ExcelPrint"; + +const Toolbar = ({ table, filterData }) => { + return ( + <> + + + ); +}; +export default Toolbar; diff --git a/src/components/dashboard/roadItems/reports/ItemsReports/index.jsx b/src/components/dashboard/roadItems/reports/ItemsReports/index.jsx index 137ed83..b9eb0da 100644 --- a/src/components/dashboard/roadItems/reports/ItemsReports/index.jsx +++ b/src/components/dashboard/roadItems/reports/ItemsReports/index.jsx @@ -18,20 +18,32 @@ const ReportPage = () => { const [data, setData] = useState(null); const { itemsList, loadingItemsList, errorItemsList } = useRoadItemGetItems(); const { data: userPermissions } = usePermissions(); + const { user } = useAuth(); const requestServer = useRequest(); - const hasProvincesPermission = userPermissions.includes("show-road-item-supervise-cartable"); + const hasProvincesPermission = userPermissions?.includes("show-road-item-supervise-cartable"); const defaultValues = { from_date: moment(new Date()).format("YYYY-MM-DD"), date_to: moment(new Date()).format("YYYY-MM-DD"), province_id: hasProvincesPermission ? "-1" : user.province_id, }; + const defaultFilter = [ + { value: `${defaultValues.province_id}`, datatype: "numeric", id: "province_id", fn: "equals" }, + { value: `${defaultValues.from_date}`, datatype: "date", id: "from_date", fn: "between" }, + { value: `${defaultValues.date_to}`, datatype: "date", id: "date_to", fn: "between" }, + ]; + const [specialFilter, setSpecialFilter] = useState(defaultFilter); const onSearchSubmit = async (data) => { const params = new URLSearchParams(); params.set("from_date", `${data.from_date}`); params.set("date_to", `${data.date_to}`); + setSpecialFilter([ + { value: `${data.province_id}`, datatype: "numeric", id: "province_id", fn: "equals" }, + { value: `${data.from_date}`, datatype: "date", id: "from_date", fn: "between" }, + { value: `${data.date_to}`, datatype: "date", id: "date_to", fn: "between" }, + ]); if (data.province_id === "-1") { try { const response = await requestServer(`${GET_PROVINCE_ACTIVITY_PER_ITEM}?${params}`); @@ -119,9 +131,10 @@ const ReportPage = () => { }; useEffect(() => { + if (!userPermissions) return; if (itemsList.length === 0) return; onSearchSubmit(defaultValues); - }, [itemsList]); + }, [itemsList, userPermissions]); const validationSchema = yup.object().shape({ from_date: yup @@ -144,7 +157,7 @@ const ReportPage = () => { }); return ( - + { خطا در دریافت اطلاعات!!! ) : ( - + )} ); diff --git a/src/components/dashboard/roadItems/reports/Map/ClusterSwitch/index.jsx b/src/components/dashboard/roadItems/reports/Map/ClusterSwitch/index.jsx new file mode 100644 index 0000000..e8fac87 --- /dev/null +++ b/src/components/dashboard/roadItems/reports/Map/ClusterSwitch/index.jsx @@ -0,0 +1,25 @@ +import DoneIcon from "@mui/icons-material/Done"; +import { Chip, Tooltip, Zoom } from "@mui/material"; + +const ClusterSwitch = ({ isCluster, setCluster, disabled, max }) => { + return ( + + + setCluster((c) => !c)} + sx={{ borderRadius: 1 }} + icon={ + + + + } + /> + + + ); +}; +export default ClusterSwitch; diff --git a/src/components/dashboard/roadItems/reports/Map/Filter/Drawer/ActivityDateTimeFilter.jsx b/src/components/dashboard/roadItems/reports/Map/Filter/Drawer/ActivityDateTimeFilter.jsx new file mode 100644 index 0000000..cab7ddc --- /dev/null +++ b/src/components/dashboard/roadItems/reports/Map/Filter/Drawer/ActivityDateTimeFilter.jsx @@ -0,0 +1,32 @@ +import MuiDatePicker from "@/core/components/DataTable/filter/fieldsType/CustomDate/MuiDatePicker"; +import { Box } from "@mui/material"; + +const ActivityDateTimeFilter = ({ value, onChange, error }) => { + return ( + + { + onChange([formattedDate, value[1]]); + }} + maxDate={value[1]} + placeholder={`از تاریخ`} + /> + { + onChange([value[0], formattedDate]); + }} + minDate={value[0]} + placeholder={`تا تاریخ`} + helperText={error ? error.message : null} + error={Boolean(error)} + /> + + ); +}; +export default ActivityDateTimeFilter; diff --git a/src/components/dashboard/roadItems/reports/Map/Filter/Drawer/EdarehShahriFilter.jsx b/src/components/dashboard/roadItems/reports/Map/Filter/Drawer/EdarehShahriFilter.jsx new file mode 100644 index 0000000..eb08e27 --- /dev/null +++ b/src/components/dashboard/roadItems/reports/Map/Filter/Drawer/EdarehShahriFilter.jsx @@ -0,0 +1,65 @@ +"use client"; +import useEdaratLists from "@/lib/hooks/useEdaratLists"; +import { FormControl, InputLabel, MenuItem, OutlinedInput, Select } from "@mui/material"; +import { useEffect, useMemo, useState } from "react"; +import { useWatch } from "react-hook-form"; + +const EdarehShahriFilter = ({ control, value, onChange }) => { + const dependencyField = useWatch({ control, name: "province_id" }); + return ; +}; + +const EdarehShahriController = ({ value, onChange, dependencyField }) => { + const { edaratList, loadingEdaratList, errorEdaratList } = useEdaratLists(dependencyField); + const [prevDependency, setPrevDependency] = useState(dependencyField); + + const columnSelectOption = useMemo(() => { + if (dependencyField === "") { + return [{ value: "empty", label: "ابتدا استان را انتخاب کنید" }]; + } + if (loadingEdaratList) { + return [{ value: "loading", label: "در حال بارگذاری..." }]; + } + if (errorEdaratList) { + return [{ value: "error", label: "خطا در بارگذاری" }]; + } + return [ + { value: "", label: "کل ادارات" }, + ...edaratList.map((edare) => ({ + value: edare.id, + label: edare.name_fa, + })), + ]; + }, [edaratList, loadingEdaratList, errorEdaratList]); + + useEffect(() => { + if (prevDependency === dependencyField) return; + onChange(""); + setPrevDependency(dependencyField); + }, [dependencyField]); + + return ( + + + اداره + + + + ); +}; +export default EdarehShahriFilter; diff --git a/src/components/dashboard/roadItems/reports/Map/Filter/Drawer/ItemFilter.jsx b/src/components/dashboard/roadItems/reports/Map/Filter/Drawer/ItemFilter.jsx new file mode 100644 index 0000000..de27d11 --- /dev/null +++ b/src/components/dashboard/roadItems/reports/Map/Filter/Drawer/ItemFilter.jsx @@ -0,0 +1,47 @@ +import useRoadItemGetItems from "@/lib/hooks/useRoadItemGetItems"; +import { FormControl, InputLabel, MenuItem, OutlinedInput, Select } from "@mui/material"; +import { useMemo } from "react"; + +const ItemFilter = ({ value, onChange }) => { + const { itemsList, loadingItemsList, errorItemsList } = useRoadItemGetItems(); + const columnSelectOption = useMemo(() => { + if (loadingItemsList) { + return [{ value: "loading", label: "در حال بارگذاری..." }]; + } + if (errorItemsList) { + return [{ value: "error", label: "خطا در بارگذاری" }]; + } + return [ + { value: "", label: "همه آیتم ها" }, + ...itemsList.map((item) => ({ + value: item.id, + label: item.name, + })), + ]; + }, [itemsList, loadingItemsList, errorItemsList]); + + return ( + + + آیتم فعالیت + + + + ); +}; +export default ItemFilter; diff --git a/src/components/dashboard/roadItems/reports/Map/Filter/Drawer/ProvinceFilter.jsx b/src/components/dashboard/roadItems/reports/Map/Filter/Drawer/ProvinceFilter.jsx new file mode 100644 index 0000000..4de3f9e --- /dev/null +++ b/src/components/dashboard/roadItems/reports/Map/Filter/Drawer/ProvinceFilter.jsx @@ -0,0 +1,47 @@ +import useProvinces from "@/lib/hooks/useProvince"; +import { FormControl, InputLabel, MenuItem, OutlinedInput, Select } from "@mui/material"; +import { useMemo } from "react"; + +const ProvinceFilter = ({ value, onChange }) => { + const { provinces, errorProvinces, loadingProvinces } = useProvinces(); + const columnSelectOption = useMemo(() => { + if (loadingProvinces) { + return [{ value: "loading", label: "در حال بارگذاری..." }]; + } + if (errorProvinces) { + return [{ value: "error", label: "خطا در بارگذاری" }]; + } + return [ + { value: "", label: "کل کشور" }, + ...provinces.map((province) => ({ + value: province.id, + label: province.name_fa, + })), + ]; + }, [provinces, errorProvinces, loadingProvinces]); + + return ( + + + استان + + + + ); +}; +export default ProvinceFilter; diff --git a/src/components/dashboard/roadItems/reports/Map/Filter/Drawer/StatusFilter.jsx b/src/components/dashboard/roadItems/reports/Map/Filter/Drawer/StatusFilter.jsx new file mode 100644 index 0000000..dd52ea5 --- /dev/null +++ b/src/components/dashboard/roadItems/reports/Map/Filter/Drawer/StatusFilter.jsx @@ -0,0 +1,35 @@ +import { FormControl, InputLabel, MenuItem, OutlinedInput, Select } from "@mui/material"; + +const StatusFilter = ({ value, onChange }) => { + const statusOptions = [ + { value: "", label: "همه وضعیت ها" }, + { value: 0, label: "درحال بررسی" }, + { value: 1, label: "تایید" }, + { value: 2, label: "عدم تایید" }, + ]; + + return ( + + + وضعیت نظارت + + + + ); +}; +export default StatusFilter; diff --git a/src/components/dashboard/roadItems/reports/Map/Filter/Drawer/SubItemFilter.jsx b/src/components/dashboard/roadItems/reports/Map/Filter/Drawer/SubItemFilter.jsx new file mode 100644 index 0000000..e9f973a --- /dev/null +++ b/src/components/dashboard/roadItems/reports/Map/Filter/Drawer/SubItemFilter.jsx @@ -0,0 +1,65 @@ +"use client"; +import useRoadItemGetSubItems from "@/lib/hooks/useRoadItemGetISubtems"; +import { FormControl, InputLabel, MenuItem, OutlinedInput, Select } from "@mui/material"; +import { useEffect, useMemo, useState } from "react"; +import { useWatch } from "react-hook-form"; + +const SubItemFilter = ({ control, value, onChange }) => { + const dependencyField = useWatch({ control, name: "item_id" }); + return ; +}; + +const SubItemController = ({ value, onChange, dependencyField }) => { + const { subItemsList, loadingSubItemsList, errorSubItemsList } = useRoadItemGetSubItems(dependencyField); + const [prevDependency, setPrevDependency] = useState(dependencyField); + + const columnSelectOption = useMemo(() => { + if (dependencyField === "") { + return [{ value: "empty", label: "ابتدا آیتم فعالیت را انتخاب کنید" }]; + } + if (loadingSubItemsList) { + return [{ value: "loading", label: "در حال بارگذاری..." }]; + } + if (errorSubItemsList) { + return [{ value: "error", label: "خطا در بارگذاری" }]; + } + return [ + { value: "", label: "همه اقدام ها" }, + ...subItemsList.map((item) => ({ + value: item.sub_item, + label: item.name, + })), + ]; + }, [subItemsList, loadingSubItemsList, errorSubItemsList]); + + useEffect(() => { + if (prevDependency === dependencyField) return; + onChange(""); + setPrevDependency(dependencyField); + }, [dependencyField]); + + return ( + + + اقدام انجام شده + + + + ); +}; +export default SubItemFilter; diff --git a/src/components/dashboard/roadItems/reports/Map/Filter/Drawer/index.jsx b/src/components/dashboard/roadItems/reports/Map/Filter/Drawer/index.jsx new file mode 100644 index 0000000..9700220 --- /dev/null +++ b/src/components/dashboard/roadItems/reports/Map/Filter/Drawer/index.jsx @@ -0,0 +1,141 @@ +import ScrollBox from "@/core/components/ScrollBox"; +import { Box, Button, IconButton, Typography } from "@mui/material"; +import CancelIcon from "@mui/icons-material/Cancel"; +import FilterAltIcon from "@mui/icons-material/FilterAlt"; +import { Controller, useForm } from "react-hook-form"; +import ProvinceFilter from "./ProvinceFilter"; +import EdarehShahriFilter from "./EdarehShahriFilter"; +import ItemFilter from "./ItemFilter"; +import SubItemFilter from "./SubItemFilter"; +import StatusFilter from "./StatusFilter"; +import ActivityDateTimeFilter from "./ActivityDateTimeFilter"; +import { yupResolver } from "@hookform/resolvers/yup"; +import * as Yup from "yup"; + +const validationSchema = Yup.object({ + activity_date_time: Yup.array() + .of(Yup.string().nullable()) + .test({ + test(value, ctx) { + const [start, end] = value || ["", ""]; + if ((start && !end) || (!start && end)) { + return ctx.createError({ message: "این بخش را تکمیل نمایید" }); + } + return true; + }, + }), +}); + +const headerSx = { + display: "flex", + alignItems: "center", + justifyContent: "space-between", + px: 2, + py: 1, + backgroundColor: "#155175", + boxShadow: "rgba(0, 0, 0, 0.19) 0px 10px 20px, rgba(0, 0, 0, 0.23) 0px 6px 6px", + maxWidth: "450px", +}; + +const headerTitleSx = { display: "flex", alignItems: "center" }; +const headerIconSx = { color: "#fff", mr: 1 }; +const iconButtonSx = { color: "#fff" }; + +const formContainerSx = { px: 2, py: 3 }; +const footerSx = { display: "flex", justifyContent: "center", alignItems: "center", pb: 2 }; + +const submitButtonSx = { + px: 8, + boxShadow: "rgba(0, 0, 0, 0.23) 0px 6px 6px, rgba(0, 0, 0, 0.19) 10px 0px 20px", + backgroundColor: "primary2", + ":hover": { backgroundColor: "primary2" }, +}; + +const FilterDrawer = ({ defaultValues, setFilterData, closeDrawer }) => { + const { + control, + handleSubmit, + formState: { isDirty }, + } = useForm({ + defaultValues, + resolver: yupResolver(validationSchema), + mode: "onBlur", + }); + + const onSubmit = (data) => { + setFilterData(data); + closeDrawer(); + }; + + return ( + <> + + + + + فیلتر + + + + + + + + +
+ + ( + + )} + /> + ( + + )} + /> + ( + + )} + /> + ( + + )} + /> + ( + + )} + /> + ( + + )} + /> + + + + + +
+
+ + ); +}; +export default FilterDrawer; diff --git a/src/components/dashboard/roadItems/reports/Map/Filter/index.jsx b/src/components/dashboard/roadItems/reports/Map/Filter/index.jsx new file mode 100644 index 0000000..c12a511 --- /dev/null +++ b/src/components/dashboard/roadItems/reports/Map/Filter/index.jsx @@ -0,0 +1,44 @@ +"use client"; +import { Box, Button, Drawer } from "@mui/material"; +import FilterListIcon from "@mui/icons-material/FilterList"; +import { useCallback, useState } from "react"; +import FilterDrawer from "./Drawer"; + +const drawerSx = { + overflowY: "hidden", + display: "flex", + flexDirection: "column", + height: "100%", + zIndex: "1300", +}; + +const boxSx = { + width: { xs: 300, sm: 450 }, +}; + +const Filter = ({ filterData, setFilterData }) => { + const [open, setOpen] = useState(false); + + const openDrawer = useCallback(() => setOpen(true), []); + const closeDrawer = useCallback(() => setOpen(false), []); + + return ( + <> + + + + {open && ( + + )} + + + + ); +}; +export default Filter; diff --git a/src/components/dashboard/roadItems/reports/Map/Legend/index.jsx b/src/components/dashboard/roadItems/reports/Map/Legend/index.jsx new file mode 100644 index 0000000..20707aa --- /dev/null +++ b/src/components/dashboard/roadItems/reports/Map/Legend/index.jsx @@ -0,0 +1,48 @@ +import { Box, Stack, Typography } from "@mui/material"; + +const Legend = () => { + return ( + + + theme.palette.primary.main, + background: (theme) => theme.palette.primary.light, + }} + /> + در حال بررسی + + + theme.palette.success.main, + background: (theme) => theme.palette.success.light, + }} + /> + تایید شده + + + theme.palette.warning.main, + background: (theme) => theme.palette.warning.light, + }} + /> + عدم تایید + + + ); +}; +export default Legend; diff --git a/src/components/dashboard/roadItems/reports/Map/PointsOnMap/DialogInfoItem/ContentInfoItem.jsx b/src/components/dashboard/roadItems/reports/Map/PointsOnMap/DialogInfoItem/ContentInfoItem.jsx new file mode 100644 index 0000000..a10a1aa --- /dev/null +++ b/src/components/dashboard/roadItems/reports/Map/PointsOnMap/DialogInfoItem/ContentInfoItem.jsx @@ -0,0 +1,104 @@ +import { Box, Chip, Divider, Stack, Typography } from "@mui/material"; +import moment from "jalali-moment"; +import Image from "next/image"; + +const ContentInfoItem = ({ data }) => { + return ( + + + + + {data.id !== "" && data.id} + + + + + + {data.province_fa !== "" && data.province_fa} + + + + + + + {data.edarat_name !== "" && data.edarat_name} + + + + + + + {data.item_fa !== "" && data.item_fa} + + + + + + + {data.sub_item_fa !== "" && data.sub_item_fa} + + + + + + + + {(data.sub_item_data / 1).toLocaleString()} + + + {`(${data.unit_fa})`} + + + + + + + + {data.activity_date_time && + moment(data.activity_date_time).locale("fa").format("HH:mm | yyyy/MM/DD")} + + + + + + + {data.created_at && moment(data.created_at).locale("fa").format("HH:mm | yyyy/MM/DD")} + + + + + + + {data.status_fa !== "" && data.status_fa} + + + + + + + before_image + + + + + + before_image + + + + + ); +}; +export default ContentInfoItem; diff --git a/src/components/dashboard/roadItems/reports/Map/PointsOnMap/DialogInfoItem/index.jsx b/src/components/dashboard/roadItems/reports/Map/PointsOnMap/DialogInfoItem/index.jsx new file mode 100644 index 0000000..a3c9b23 --- /dev/null +++ b/src/components/dashboard/roadItems/reports/Map/PointsOnMap/DialogInfoItem/index.jsx @@ -0,0 +1,51 @@ +import DialogLoading from "@/core/components/DialogLoading"; +import { GET_ROAD_ITEM } from "@/core/utils/routes"; +import useRequest from "@/lib/hooks/useRequest"; +import { Button, DialogActions, DialogContent, DialogTitle } from "@mui/material"; +import { useEffect, useState } from "react"; +import ContentInfoItem from "./ContentInfoItem"; + +const DialogInfoItem = ({ selectedId, closeDialog }) => { + const request = useRequest(); + const [loading, setLoading] = useState(true); + const [data, setData] = useState(null); + + useEffect(() => { + const controller = new AbortController(); + + const fetchItem = async () => { + setLoading(true); + setData(null); + try { + const response = await request(`${GET_ROAD_ITEM}/${selectedId}`, "get", { + signal: controller.signal, + }); + setData(response.data.data); + } catch (error) { + } finally { + setLoading(false); + } + }; + + fetchItem(); + + return () => { + controller.abort(); + }; + }, [selectedId]); + + return ( + <> + جزئیات فعالیت + + {loading ? : data && } + + + + + + ); +}; +export default DialogInfoItem; diff --git a/src/components/dashboard/roadItems/reports/Map/PointsOnMap/index.jsx b/src/components/dashboard/roadItems/reports/Map/PointsOnMap/index.jsx new file mode 100644 index 0000000..8599f3f --- /dev/null +++ b/src/components/dashboard/roadItems/reports/Map/PointsOnMap/index.jsx @@ -0,0 +1,71 @@ +import { Dialog, useTheme } from "@mui/material"; +import { useCallback, useEffect, useMemo, useState } from "react"; +import { CircleMarker, useMap } from "react-leaflet"; +import MarkerClusterGroup from "react-leaflet-markercluster"; +import DialogInfoItem from "./DialogInfoItem"; + +const PointsOnMap = ({ data, isCluster }) => { + const map = useMap(); + const theme = useTheme(); + const [selectedId, setSelectedId] = useState(null); + const [open, setOpen] = useState(false); + + const openDialog = useCallback(() => setOpen(true), []); + const closeDialog = useCallback(() => setOpen(false), []); + + const getMarkerColor = (status) => { + if (status === 0) return theme.palette.info.main; + if (status === 1) return theme.palette.success.main; + return theme.palette.warning.main; + }; + + const markers = useMemo(() => { + return data.map(({ i, l, g, status }) => ( + { + openDialog(); + setSelectedId(i); + }, + }} + key={i} + center={L.latLng(l, g)} + radius={8} + color={getMarkerColor(status)} + /> + )); + }, [data, theme]); + + useEffect(() => { + if (data.length === 0) return; + const bounds = L.latLngBounds(data.map(({ l, g }) => L.latLng(l, g))); + map.flyToBounds(bounds, { duration: 0.7 }); + }, [data]); + + const createClusterCustomIcon = (cluster) => { + const count = cluster.getChildCount(); + return L.divIcon({ + html: `
${count}
`, + className: "custom-marker-cluster", + iconSize: L.point(40, 40, true), + }); + }; + + if (data.length === 0) return null; + + return ( + <> + {isCluster ? ( + + {markers} + + ) : ( + markers + )} + + {open && } + + + ); +}; +export default PointsOnMap; diff --git a/src/components/dashboard/roadItems/reports/Map/index.jsx b/src/components/dashboard/roadItems/reports/Map/index.jsx new file mode 100644 index 0000000..1daacfb --- /dev/null +++ b/src/components/dashboard/roadItems/reports/Map/index.jsx @@ -0,0 +1,127 @@ +"use client"; +import LoadingHardPage from "@/core/components/LoadingHardPage"; +import MapLoading from "@/core/components/MapLayer/Loading"; +import { GET_ROAD_ITEMS_REPORT_MAP } from "@/core/utils/routes"; +import useRequest from "@/lib/hooks/useRequest"; +import { Box, Stack } from "@mui/material"; +import dynamic from "next/dynamic"; +import { useCallback, useEffect, useState } from "react"; +import Filter from "./Filter"; +import PointsOnMap from "./PointsOnMap"; +import moment from "jalali-moment"; +import ClusterSwitch from "./ClusterSwitch"; +import Legend from "./Legend"; +const MapLayer = dynamic(() => import("@/core/components/MapLayer"), { + loading: () => , + ssr: false, +}); + +const defaultValues = { + province_id: "", + edarat_id: "", + item_id: "", + sub_item_id: "", + status: "", + activity_date_time: [moment().locale("en").format("YYYY-MM-DD"), moment().locale("en").format("YYYY-MM-DD")], +}; + +const MAX_POINTS = 1000; + +const containerStyles = { + width: "100%", + height: "100%", + p: 1, + border: "1px dashed", + borderColor: "divider", + borderRadius: 1, +}; + +const controlBarStyles = { + p: 1, + background: "#fff", +}; + +const RoadItemsReportMap = () => { + const requestServer = useRequest(); + const [filterData, setFilterData] = useState(defaultValues); + const [isCluster, setCluster] = useState(false); + const [data, setData] = useState([]); + const [isLoading, setLoading] = useState(true); + + const buildQueryParams = useCallback(() => { + const params = new URLSearchParams(); + const { province_id, edarat_id, item_id, sub_item_id, activity_date_time, status } = filterData; + + if (province_id) params.set("province_id", province_id); + if (edarat_id) params.set("edarat_id", edarat_id); + if (item_id) params.set("item_id", item_id); + if (sub_item_id) params.set("sub_item_id", sub_item_id); + if (activity_date_time[0]) { + params.set("date_from", activity_date_time[0]); + params.set("date_to", activity_date_time[1]); + } + if (status) params.set("status", status); + + return params.toString(); + }, [filterData]); + + useEffect(() => { + const fetchData = async () => { + try { + setLoading(true); + const query = buildQueryParams(); + const response = await requestServer(`${GET_ROAD_ITEMS_REPORT_MAP}?${query}`); + const fetchedData = response?.data?.data || []; + + if (fetchedData.length > MAX_POINTS) { + setCluster(true); + } + setData(fetchedData); + } catch (error) { + } finally { + setLoading(false); + } + }; + + fetchData(); + }, [filterData, buildQueryParams]); + + return ( + + + + + MAX_POINTS} + max={MAX_POINTS} + /> + + + + + + + + + + + {isLoading && ( + + + + )} + + + + + ); +}; +export default RoadItemsReportMap; diff --git a/src/components/dashboard/roadItems/reports/SubItemsReports/ExcelPrint/index.jsx b/src/components/dashboard/roadItems/reports/SubItemsReports/ExcelPrint/index.jsx new file mode 100644 index 0000000..443135b --- /dev/null +++ b/src/components/dashboard/roadItems/reports/SubItemsReports/ExcelPrint/index.jsx @@ -0,0 +1,77 @@ +"use client"; +import { Button, CircularProgress, IconButton, useMediaQuery } from "@mui/material"; +import { useMemo, useState } from "react"; +import moment from "jalali-moment"; +import FileSaver from "file-saver"; +import DescriptionIcon from "@mui/icons-material/Description"; +import useRequest from "@/lib/hooks/useRequest"; +import { useTheme } from "@emotion/react"; +import { + EXPORT_COUNTRY_ROAD_ITEM_REPORTS_PER_SUB_ITEM, + EXPORT_PROVINCE_ROAD_ITEM_REPORTS_PER_SUB_ITEM, +} from "@/core/utils/routes"; + +const PrintExcel = ({ table, filterData }) => { + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("sm")); + const [loading, setLoading] = useState(false); + const requestServer = useRequest({ notificationSuccess: true }); + const activeFilters = Object.entries(filterData).reduce((acc, [key, filter]) => { + if (filter.value) { + acc.push({ id: filter.id, value: filter.value }); + } + return acc; + }, []); + + const filterParams = useMemo(() => { + const params = new URLSearchParams(); + activeFilters.length > 0 && + activeFilters.map((filter, index) => { + params.set(`${filter.id}`, filter.value); + }); + return params; + }, [activeFilters]); + + const clickHandler = () => { + setLoading(true); + const CountryOrProvince = activeFilters.find((item) => item.id === "province_id"); + const requestUrl = + CountryOrProvince.value === "-1" + ? EXPORT_COUNTRY_ROAD_ITEM_REPORTS_PER_SUB_ITEM + : EXPORT_PROVINCE_ROAD_ITEM_REPORTS_PER_SUB_ITEM; + requestServer(`${requestUrl}?${filterParams}`, "get", { + requestOptions: { responseType: "blob" }, + }) + .then((response) => { + const filename = `خروجی کارتابل گزارشات فعالیت روزانه براساس موارد اقدام شده تاریخ_${moment().format("jYYYY_jMM_jDD")}.xlsx`; + FileSaver.saveAs(response.data, filename); + }) + .catch(() => {}) + .finally(() => { + setLoading(false); + }); + }; + + return ( + <> + {isMobile ? ( + + + + ) : ( + + )} + + ); +}; +export default PrintExcel; diff --git a/src/components/dashboard/roadItems/reports/SubItemsReports/Search/FromDateController.jsx b/src/components/dashboard/roadItems/reports/SubItemsReports/Search/FromDateController.jsx index 2cfa8af..2e23def 100644 --- a/src/components/dashboard/roadItems/reports/SubItemsReports/Search/FromDateController.jsx +++ b/src/components/dashboard/roadItems/reports/SubItemsReports/Search/FromDateController.jsx @@ -13,6 +13,7 @@ const FromDateController = ({ control }) => { name="from_date" error={!!error} value={value} + label={"تاریخ فعالیت (از تاریخ)"} placeholder={"از تاریخ"} setFieldValue={(name, newValue) => { const formattedDate = moment(new Date(newValue)).locale("en").format("YYYY-MM-DD"); diff --git a/src/components/dashboard/roadItems/reports/SubItemsReports/Search/SelectItems.jsx b/src/components/dashboard/roadItems/reports/SubItemsReports/Search/SelectItems.jsx index 57bcc86..db71a8a 100644 --- a/src/components/dashboard/roadItems/reports/SubItemsReports/Search/SelectItems.jsx +++ b/src/components/dashboard/roadItems/reports/SubItemsReports/Search/SelectItems.jsx @@ -12,13 +12,13 @@ const SelectProvince = ({ control }) => { control={control} render={({ field, fieldState: { error } }) => ( - ایتم + آیتم } + size="small" + displayEmpty + > + عادی + فوری + آنی + + + + + {subItemsList?.needs_end_point === 1 ? ( + + ) : ( + + )} + + + ); +}; +export default GetObservedItemInfo; diff --git a/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/ModalActionsDuringPatrol.jsx b/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/ModalActionsDuringPatrol.jsx index 073fd9e..97be471 100644 --- a/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/ModalActionsDuringPatrol.jsx +++ b/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/ModalActionsDuringPatrol.jsx @@ -3,7 +3,7 @@ import { Dialog } from "@mui/material"; const ModalActionsDuringPatrol = ({ open, setOpen, setActionsList }) => { const HandleSubmit = async (data) => { - setActionsList((prev) => [...prev, data]); + setActionsList((prev) => [...prev, { ...data, instant_action: 1 }]); setOpen(false); }; diff --git a/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/ModalObservedItemPatrol.jsx b/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/ModalObservedItemPatrol.jsx new file mode 100644 index 0000000..9a6c0fc --- /dev/null +++ b/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/ModalObservedItemPatrol.jsx @@ -0,0 +1,16 @@ +import { Dialog } from "@mui/material"; +import CreateFormContentObservedItem from "./CreateFormContentObservedItem"; + +const ModalObservedItemPatrol = ({ open, setOpen, setActionsList }) => { + const HandleSubmit = async (data) => { + setActionsList((prev) => [...prev, { ...data, instant_action: 0 }]); + setOpen(false); + }; + + return ( + + + + ); +}; +export default ModalObservedItemPatrol; diff --git a/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/ObservedInfo.jsx b/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/ObservedInfo.jsx new file mode 100644 index 0000000..16b9953 --- /dev/null +++ b/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/ObservedInfo.jsx @@ -0,0 +1,37 @@ +"use client"; + +import DeleteIcon from "@mui/icons-material/Delete"; +import VisibilityIcon from "@mui/icons-material/Visibility"; +import { Box, Card, CardActions, CardHeader, Chip, IconButton, Typography } from "@mui/material"; + +const ObservedInfo = ({ action, deleteAction }) => { + return ( + + } + title={ + + {action.data.item_name} + + } + subheader={action.data.sub_item_name} + /> + + + + + + + + + ); +}; + +export default ObservedInfo; diff --git a/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/PatrolDetailInfo.jsx b/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/PatrolDetailInfo.jsx index 91cc822..14fd172 100644 --- a/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/PatrolDetailInfo.jsx +++ b/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/PatrolDetailInfo.jsx @@ -4,7 +4,7 @@ import MapLoading from "@/core/components/MapLayer/Loading"; import ShowPlak from "@/core/components/ShowPlak"; import { formatSecondsToHHMMSS } from "@/core/utils/formatSecondsToHHMMSS"; import AddRoadIcon from "@mui/icons-material/AddRoad"; -import BadgeIcon from '@mui/icons-material/Badge'; +import BadgeIcon from "@mui/icons-material/Badge"; import DirectionsCarFilledIcon from "@mui/icons-material/DirectionsCarFilled"; import ElectricCarIcon from "@mui/icons-material/ElectricCar"; import LocalGasStationIcon from "@mui/icons-material/LocalGasStation"; @@ -12,16 +12,7 @@ import QrCode2Icon from "@mui/icons-material/QrCode2"; import QueryBuilderIcon from "@mui/icons-material/QueryBuilder"; import ShareLocationIcon from "@mui/icons-material/ShareLocation"; import WatchLaterIcon from "@mui/icons-material/WatchLater"; -import { - Box, - Card, - CardContent, - Chip, - Divider, - Fade, - Stack, - Typography -} from "@mui/material"; +import { Box, Card, CardContent, Chip, Divider, Fade, Stack, Typography } from "@mui/material"; import moment from "jalali-moment"; import dynamic from "next/dynamic"; import React, { useEffect } from "react"; diff --git a/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/PreviousStatesInfoInObservedItem.jsx b/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/PreviousStatesInfoInObservedItem.jsx new file mode 100644 index 0000000..7476631 --- /dev/null +++ b/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/PreviousStatesInfoInObservedItem.jsx @@ -0,0 +1,43 @@ +import { Box, Chip, Divider, Grid, Typography } from "@mui/material"; +import InsertDriveFileIcon from "@mui/icons-material/InsertDriveFile"; +import FileCopyIcon from "@mui/icons-material/FileCopy"; + +const PreviousStatesInfoInObservedItem = ({ itemsList, subItemsList }) => { + return ( + + + + } + label={ + + آیتم انتخاب ‌شده + + } + /> + + + + {itemsList.name || "هیچ آیتمی انتخاب نشده است"} + + + + + } + label={ + + اقدام مشاهده شده + + } + /> + + + + {subItemsList?.name || "هیچ اقدامی انتخاب نشده است"} + + + + ); +}; +export default PreviousStatesInfoInObservedItem; diff --git a/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/index.jsx b/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/index.jsx index 7769896..c19d1e1 100644 --- a/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/index.jsx +++ b/src/components/dashboard/roadPatrols/operator/Forms/CreatePatrol/index.jsx @@ -1,11 +1,25 @@ "use client"; -import { Dialog } from "@mui/material"; +import { Dialog, IconButton } from "@mui/material"; import PatrolForms from "./PatrolForms"; +import CloseIcon from "@mui/icons-material/Close"; const CreatePatrol = ({ open, setOpen, mutate }) => { return ( + setOpen(false)} + sx={(theme) => ({ + position: "absolute", + right: 8, + top: 8, + zIndex: 50, + color: theme.palette.grey[500], + })} + > + + {open && } ); diff --git a/src/components/dashboard/roadPatrols/operator/OperatorList.jsx b/src/components/dashboard/roadPatrols/operator/OperatorList.jsx index f9e144d..52bb4b4 100644 --- a/src/components/dashboard/roadPatrols/operator/OperatorList.jsx +++ b/src/components/dashboard/roadPatrols/operator/OperatorList.jsx @@ -7,9 +7,10 @@ import { GET_ROAD_PATROL_OPERATOR_LIST } from "@/core/utils/routes"; import moment from "jalali-moment"; import RowActions from "./RowActions"; import ReportForm from "./RowActions/ReportForm"; -import RahdaranDialog from "./RowActions/RahdaranForm"; +import RahdaranDialog from "./RowActions/RahdaranForm" import MachinePerformanceForm from "./RowActions/MachinePerformanceForm"; + const OperatorList = () => { const columns = useMemo( () => [ diff --git a/src/components/dashboard/roadPatrols/operator/RowActions/MachinePerformanceForm/MachinePerformanceContent.jsx b/src/components/dashboard/roadPatrols/operator/RowActions/MachinePerformanceForm/MachinePerformanceContent.jsx index c16b10d..ead48ff 100644 --- a/src/components/dashboard/roadPatrols/operator/RowActions/MachinePerformanceForm/MachinePerformanceContent.jsx +++ b/src/components/dashboard/roadPatrols/operator/RowActions/MachinePerformanceForm/MachinePerformanceContent.jsx @@ -2,11 +2,11 @@ import { Box, Chip, DialogContent, Divider, Grid, Stack, Typography } from "@mui import React, { useEffect, useState, useRef } from "react"; import { Marker, useMap } from "react-leaflet"; import AzmayeshActiveIcon from "@/assets/images/examine_marker_active.png"; -import StopsInfo from "@/components/dashboard/roadPatrols/operator/RowActions/MachinePerformanceForm/StopsInfo"; import MapLayer from "@/core/components/MapLayer"; import PinDropIcon from "@mui/icons-material/PinDrop"; import AccessAlarmsIcon from "@mui/icons-material/AccessAlarms"; import { formatSecondsToHHMMSS } from "@/core/utils/formatSecondsToHHMMSS"; +import StopsInfo from "./StopsInfo"; const defaultIconSize = [35, 35]; const MachinePerformanceContent = ({ machinesLists }) => { const [selectedPoint, setSelectedPoint] = useState(null); @@ -44,7 +44,7 @@ const MachinePerformanceContent = ({ machinesLists }) => { return ( <> - + { - + { + const [loading, setLoading] = useState(true); + const [data, setData] = useState(null); + const request = useRequest(); + + useEffect(() => { + const fetchData = async () => { + try { + setLoading(true); + const response = await request(`${GET_CMMS_MACHINE_ROAD_PATROL}/${row.getValue('id')}`); + setData(response.data.data); + } catch (error) { + } finally { + setLoading(false); + } + }; + + fetchData(); + }, []); + + return ( + <> + + {loading ? ( + + ) : data && row.original?.stop_points?.length > 0 ? ( + + ) : ( + عملکرد خودرویی در سامانه یافت نشد + )} + + + ) +} +export default MachinePerformanceFetch \ No newline at end of file diff --git a/src/components/dashboard/roadPatrols/operator/RowActions/MachinePerformanceForm/StopsInfo.jsx b/src/components/dashboard/roadPatrols/operator/RowActions/MachinePerformanceForm/StopsInfo.jsx index 115a79d..d8e44cb 100644 --- a/src/components/dashboard/roadPatrols/operator/RowActions/MachinePerformanceForm/StopsInfo.jsx +++ b/src/components/dashboard/roadPatrols/operator/RowActions/MachinePerformanceForm/StopsInfo.jsx @@ -5,13 +5,18 @@ import LocalGasStationIcon from "@mui/icons-material/LocalGasStation"; import QrCode2Icon from "@mui/icons-material/QrCode2"; import AddRoadIcon from "@mui/icons-material/AddRoad"; import DirectionsCarFilledIcon from "@mui/icons-material/DirectionsCarFilled"; -import BadgeIcon from '@mui/icons-material/Badge'; +import BadgeIcon from "@mui/icons-material/Badge"; import { formatSecondsToHHMMSS } from "@/core/utils/formatSecondsToHHMMSS"; import ShowPlak from "@/core/components/ShowPlak"; const StopsInfo = ({ staticData }) => { return ( - + { const [openMachinesCodeDialog, setOpenMachinesCodeDialog] = useState(false); - const machineData = { - cmms_machine_code: row.original?.cmms_machines[0], - resultCode: 0, - mileage: row.original?.distance, - accOnDuration: row.original?.vehicle_runtime, - fuelConsumption: row.original?.fuel_consumption, - stopPoints: row.original?.stop_points, - }; return ( <> @@ -33,7 +25,9 @@ const MachinePerformanceForm = ({ row }) => { maxWidth={"md"} > عملکرد خودرو - + {openMachinesCodeDialog && ( + + )} + <> + {isMobile ? ( + + + + ) : ( + + )} + ); }; export default PrintExcel; diff --git a/src/components/dashboard/roadPatrols/reports/ReportLists.jsx b/src/components/dashboard/roadPatrols/reports/ReportLists.jsx index 4a3d8e5..76f6cef 100644 --- a/src/components/dashboard/roadPatrols/reports/ReportLists.jsx +++ b/src/components/dashboard/roadPatrols/reports/ReportLists.jsx @@ -4,7 +4,7 @@ import { Box } from "@mui/material"; import { useMemo } from "react"; import Toolbar from "./Toolbar"; -const ReportLists = ({ data }) => { +const ReportLists = ({ data, specialFilter }) => { const columns = useMemo(() => { return [ { @@ -195,12 +195,12 @@ const ReportLists = ({ data }) => { return ( ); diff --git a/src/components/dashboard/roadPatrols/reports/Search/FromDateController.jsx b/src/components/dashboard/roadPatrols/reports/Search/FromDateController.jsx index 2cfa8af..73b095f 100644 --- a/src/components/dashboard/roadPatrols/reports/Search/FromDateController.jsx +++ b/src/components/dashboard/roadPatrols/reports/Search/FromDateController.jsx @@ -13,6 +13,7 @@ const FromDateController = ({ control }) => { name="from_date" error={!!error} value={value} + label={"تاریخ شروع (از تاریخ)"} placeholder={"از تاریخ"} setFieldValue={(name, newValue) => { const formattedDate = moment(new Date(newValue)).locale("en").format("YYYY-MM-DD"); diff --git a/src/components/dashboard/roadPatrols/reports/Search/ToDateController.jsx b/src/components/dashboard/roadPatrols/reports/Search/ToDateController.jsx index 003ac20..d102c13 100644 --- a/src/components/dashboard/roadPatrols/reports/Search/ToDateController.jsx +++ b/src/components/dashboard/roadPatrols/reports/Search/ToDateController.jsx @@ -15,6 +15,7 @@ const ToDateController = ({ control }) => { minDate={minDate} error={!!error} value={value} + label={"تاریخ شروع (تا تاریخ)"} placeholder={"تا تاریخ"} setFieldValue={(name, newValue) => { const formattedDate = moment(new Date(newValue)).locale("en").format("YYYY-MM-DD"); diff --git a/src/components/dashboard/roadPatrols/reports/Toolbar.jsx b/src/components/dashboard/roadPatrols/reports/Toolbar.jsx index 5dc6e42..7dd930f 100644 --- a/src/components/dashboard/roadPatrols/reports/Toolbar.jsx +++ b/src/components/dashboard/roadPatrols/reports/Toolbar.jsx @@ -2,6 +2,10 @@ import PrintExcel from "./ExcelPrint"; import { Box } from "@mui/material"; const Toolbar = ({ table, filterData, mutate }) => { - return {/**/}; + return ( + + + + ); }; export default Toolbar; diff --git a/src/components/dashboard/roadPatrols/reports/index.jsx b/src/components/dashboard/roadPatrols/reports/index.jsx index 12743aa..ae2ec32 100644 --- a/src/components/dashboard/roadPatrols/reports/index.jsx +++ b/src/components/dashboard/roadPatrols/reports/index.jsx @@ -18,18 +18,29 @@ const ReportPage = () => { const { data: userPermissions } = usePermissions(); const { user } = useAuth(); const requestServer = useRequest(); - const hasProvincesPermission = userPermissions.includes("show-road-patrol-supervise-cartable"); + const hasProvincesPermission = userPermissions?.includes("show-road-patrol-supervise-cartable"); const defaultValues = { from_date: moment(new Date()).format("YYYY-MM-DD"), date_to: moment(new Date()).format("YYYY-MM-DD"), province_id: hasProvincesPermission ? "-1" : user.province_id, }; + const defaultFilter = [ + { value: `${defaultValues.province_id}`, datatype: "numeric", id: "province_id", fn: "equals" }, + { value: `${defaultValues.from_date}`, datatype: "date", id: "from_date", fn: "between" }, + { value: `${defaultValues.date_to}`, datatype: "date", id: "date_to", fn: "between" }, + ]; + const [specialFilter, setSpecialFilter] = useState(defaultFilter); const onSearchSubmit = async (data) => { const params = new URLSearchParams(); params.set("from_date", `${data.from_date}`); params.set("date_to", `${data.date_to}`); + setSpecialFilter([ + { value: `${data.province_id}`, datatype: "numeric", id: "province_id", fn: "equals" }, + { value: `${data.from_date}`, datatype: "date", id: "from_date", fn: "between" }, + { value: `${data.date_to}`, datatype: "date", id: "date_to", fn: "between" }, + ]); if (data.province_id === "-1") { try { const response = await requestServer(`${GET_PROVINCE_ACTIVITY_REPORT}?${params}`); @@ -84,8 +95,9 @@ const ReportPage = () => { }; useEffect(() => { + if (!userPermissions) return; onSearchSubmit(defaultValues); - }, []); + }, [userPermissions]); const validationSchema = yup.object().shape({ from_date: yup @@ -108,14 +120,14 @@ const ReportPage = () => { }); return ( - + - {!data ? : } + {!data ? : } ); }; diff --git a/src/components/dashboard/roadPatrols/supervisor/ExcelPrint/index.jsx b/src/components/dashboard/roadPatrols/supervisor/ExcelPrint/index.jsx index e5ba95a..ba176bb 100644 --- a/src/components/dashboard/roadPatrols/supervisor/ExcelPrint/index.jsx +++ b/src/components/dashboard/roadPatrols/supervisor/ExcelPrint/index.jsx @@ -1,17 +1,21 @@ -import { Button, CircularProgress } from "@mui/material"; +import { Button, CircularProgress, IconButton, useMediaQuery } from "@mui/material"; import { useMemo, useState } from "react"; import moment from "jalali-moment"; import FileSaver from "file-saver"; import DescriptionIcon from "@mui/icons-material/Description"; import useRequest from "@/lib/hooks/useRequest"; import { EXPORT_ROAD_PATROL_SUPERVISOR_LIST } from "@/core/utils/routes"; +import DataTableFilterDataStructure from "@/core/utils/DataTableFilterDataStructure"; +import isArrayEmpty from "@/core/utils/isArrayEmpty"; +import { useTheme } from "@emotion/react"; const PrintExcel = ({ table, filterData }) => { + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("sm")); const [loading, setLoading] = useState(false); const requestServer = useRequest(); const activeFilters = Object.entries(filterData).reduce((acc, [key, filter]) => { - if (filter.value) { - // Check if there's an active filter + if (filter.value && (!Array.isArray(filter.value) || filter.value.some((item) => item.trim() !== ""))) { acc.push({ id: key, value: filter.value }); } return acc; @@ -19,10 +23,14 @@ const PrintExcel = ({ table, filterData }) => { const filterParams = useMemo(() => { const params = new URLSearchParams(); - activeFilters.length > 0 && + if (activeFilters.length > 0) { activeFilters.map((filter, index) => { - params.set(`${filter.id}`, filter.value); + const filteredFilterData = DataTableFilterDataStructure(filterData, isArrayEmpty); + params.set("filters", JSON.stringify(filteredFilterData.length === 0 ? [] : filteredFilterData)); }); + } else { + params.set("filters", JSON.stringify([])); + } return params; }, [activeFilters]); @@ -32,7 +40,7 @@ const PrintExcel = ({ table, filterData }) => { requestOptions: { responseType: "blob" }, }) .then((response) => { - const filename = `خروجی کارتابل ارزیابی تاریخ_${moment().format("jYYYY_jMM_jDD")}.xlsx`; + const filename = `خروجی کارتابل ارزیابی گشت راهداری تاریخ_${moment().format("jYYYY_jMM_jDD")}.xlsx`; FileSaver.saveAs(response.data, filename); }) .catch(() => {}) @@ -42,17 +50,25 @@ const PrintExcel = ({ table, filterData }) => { }; return ( - + <> + {isMobile ? ( + + + + ) : ( + + )} + ); }; diff --git a/src/components/dashboard/roadPatrols/supervisor/RowActions/MachinePerformanceForm/MachinePerformanceContent.jsx b/src/components/dashboard/roadPatrols/supervisor/RowActions/MachinePerformanceForm/MachinePerformanceContent.jsx index 93c0874..ead48ff 100644 --- a/src/components/dashboard/roadPatrols/supervisor/RowActions/MachinePerformanceForm/MachinePerformanceContent.jsx +++ b/src/components/dashboard/roadPatrols/supervisor/RowActions/MachinePerformanceForm/MachinePerformanceContent.jsx @@ -2,11 +2,11 @@ import { Box, Chip, DialogContent, Divider, Grid, Stack, Typography } from "@mui import React, { useEffect, useState, useRef } from "react"; import { Marker, useMap } from "react-leaflet"; import AzmayeshActiveIcon from "@/assets/images/examine_marker_active.png"; -import StopsInfo from "@/components/dashboard/roadPatrols/operator/RowActions/MachinePerformanceForm/StopsInfo"; import MapLayer from "@/core/components/MapLayer"; import PinDropIcon from "@mui/icons-material/PinDrop"; import AccessAlarmsIcon from "@mui/icons-material/AccessAlarms"; import { formatSecondsToHHMMSS } from "@/core/utils/formatSecondsToHHMMSS"; +import StopsInfo from "./StopsInfo"; const defaultIconSize = [35, 35]; const MachinePerformanceContent = ({ machinesLists }) => { const [selectedPoint, setSelectedPoint] = useState(null); @@ -44,7 +44,7 @@ const MachinePerformanceContent = ({ machinesLists }) => { return ( <> - + { - + { ); }; -export default MachinePerformanceContent; \ No newline at end of file +export default MachinePerformanceContent; diff --git a/src/components/dashboard/roadPatrols/supervisor/RowActions/MachinePerformanceForm/MachinesCodeFetch.jsx b/src/components/dashboard/roadPatrols/supervisor/RowActions/MachinePerformanceForm/MachinesCodeFetch.jsx new file mode 100644 index 0000000..0eb47d2 --- /dev/null +++ b/src/components/dashboard/roadPatrols/supervisor/RowActions/MachinePerformanceForm/MachinesCodeFetch.jsx @@ -0,0 +1,49 @@ +import DialogLoading from "@/core/components/DialogLoading"; +import { GET_CMMS_MACHINE_ROAD_PATROL } from "@/core/utils/routes"; +import useRequest from "@/lib/hooks/useRequest"; +import { DialogContent, Typography } from "@mui/material"; +import { useEffect, useState } from "react"; +import MachinePerformanceContent from "./MachinePerformanceContent"; + +const MachinePerformanceFetch = ({ row }) => { + const [loading, setLoading] = useState(true); + const [data, setData] = useState(null); + const request = useRequest(); + + useEffect(() => { + const fetchData = async () => { + try { + setLoading(true); + const response = await request(`${GET_CMMS_MACHINE_ROAD_PATROL}/${row.getValue('id')}`); + setData(response.data.data); + } catch (error) { + } finally { + setLoading(false); + } + }; + + fetchData(); + }, []); + + return ( + <> + + {loading ? ( + + ) : data && row.original?.stop_points?.length > 0 ? ( + + ) : ( + عملکرد خودرویی در سامانه یافت نشد + )} + + + ) +} +export default MachinePerformanceFetch \ No newline at end of file diff --git a/src/components/dashboard/roadPatrols/supervisor/RowActions/MachinePerformanceForm/StopsInfo.jsx b/src/components/dashboard/roadPatrols/supervisor/RowActions/MachinePerformanceForm/StopsInfo.jsx index 0415b7c..d8e44cb 100644 --- a/src/components/dashboard/roadPatrols/supervisor/RowActions/MachinePerformanceForm/StopsInfo.jsx +++ b/src/components/dashboard/roadPatrols/supervisor/RowActions/MachinePerformanceForm/StopsInfo.jsx @@ -1,17 +1,22 @@ import { Box, Chip, Divider, Grid, Typography } from "@mui/material"; import AccessAlarmsIcon from "@mui/icons-material/AccessAlarms"; -import PinDropIcon from "@mui/icons-material/PinDrop"; + import LocalGasStationIcon from "@mui/icons-material/LocalGasStation"; import QrCode2Icon from "@mui/icons-material/QrCode2"; import AddRoadIcon from "@mui/icons-material/AddRoad"; import DirectionsCarFilledIcon from "@mui/icons-material/DirectionsCarFilled"; -import BadgeIcon from '@mui/icons-material/Badge'; +import BadgeIcon from "@mui/icons-material/Badge"; import { formatSecondsToHHMMSS } from "@/core/utils/formatSecondsToHHMMSS"; import ShowPlak from "@/core/components/ShowPlak"; -const StopsInfo = ({ selectedPoint, staticData }) => { +const StopsInfo = ({ staticData }) => { return ( - + { {formatSecondsToHHMMSS(staticData?.accOnDuration)} - - - } - label={ - - طول جغرافیایی - - } - /> - - - - {selectedPoint ? selectedPoint.longitude : "نقطه مورد نظر را انتخاب کنید"} - - - - - } - label={ - - عرض جغرافیایی - - } - /> - - - - {selectedPoint ? selectedPoint.latitude : "نقطه مورد نظر را انتخاب کنید"} - - - - - } - label={ - - مدت زمان توقف - - } - /> - - - - {selectedPoint - ? `${formatSecondsToHHMMSS(selectedPoint.duration)} ثانیه` - : "نقطه مورد نظر را انتخاب کنید"} - - ); }; -export default StopsInfo; \ No newline at end of file +export default StopsInfo; diff --git a/src/components/dashboard/roadPatrols/supervisor/RowActions/MachinePerformanceForm/index.jsx b/src/components/dashboard/roadPatrols/supervisor/RowActions/MachinePerformanceForm/index.jsx index 3eb7b87..c8dcb9e 100644 --- a/src/components/dashboard/roadPatrols/supervisor/RowActions/MachinePerformanceForm/index.jsx +++ b/src/components/dashboard/roadPatrols/supervisor/RowActions/MachinePerformanceForm/index.jsx @@ -1,18 +1,10 @@ -import { Button, Dialog, DialogActions, DialogTitle, IconButton, Tooltip } from "@mui/material"; import DirectionsCarIcon from "@mui/icons-material/DirectionsCar"; -import MachinePerformanceContent from "./MachinePerformanceContent"; +import { Button, Dialog, DialogActions, DialogTitle, IconButton, Tooltip } from "@mui/material"; import { useState } from "react"; +import MachinePerformanceFetch from "./MachinesCodeFetch"; const MachinePerformanceForm = ({ row }) => { const [openMachinesCodeDialog, setOpenMachinesCodeDialog] = useState(false); - const machineData = { - cmms_machine_code: row.original?.cmms_machines[0], - resultCode: 0, - mileage: row.original?.distance, - accOnDuration: row.original?.vehicle_runtime, - fuelConsumption: row.original?.fuel_consumption, - stopPoints: row.original?.stop_points, - }; return ( <> @@ -33,7 +25,9 @@ const MachinePerformanceForm = ({ row }) => { maxWidth={"md"} > عملکرد خودرو - + {openMachinesCodeDialog && ( + + )} + + setPlateDrawer(false)} + > + + {plate_words.map((item) => ( + ( + + )} + /> + ))} + + + ( + { + const inputValue = event.target.value; + if (isNaN(Number(inputValue))) { + return; + } + inputValue.length <= 2 ? field.onChange(inputValue) : null; + }} + /> + )} + /> + + + + + + + {plateErrors && ( + + پلاک نامعتبر است!!! + + )} + + ); +}; +export default PlateNumber; diff --git a/src/core/components/ScrollBox.jsx b/src/core/components/ScrollBox.jsx new file mode 100644 index 0000000..11f8404 --- /dev/null +++ b/src/core/components/ScrollBox.jsx @@ -0,0 +1,19 @@ +import { Box, styled } from "@mui/material"; + +const ScrollBox = styled(Box)({ + flexGrow: 1, + overflowY: "auto", + maxWidth: "450px", + "::-webkit-scrollbar": { + width: "5px", + }, + "::-webkit-scrollbar-track": { + boxShadow: "inset 0 0 5px #fff", + borderRadius: "5px", + }, + "::-webkit-scrollbar-thumb": { + background: "#155175", + borderRadius: "0px", + }, +}); +export default ScrollBox; diff --git a/src/core/components/SelectBox.jsx b/src/core/components/SelectBox.jsx new file mode 100644 index 0000000..765acaa --- /dev/null +++ b/src/core/components/SelectBox.jsx @@ -0,0 +1,59 @@ +import { FormControl, FormHelperText, InputLabel, MenuItem, Select } from "@mui/material"; + +function SelectBox({ + name, + value, + onChange, + isDependency = false, + dependencyLabel = "", + onBlur, + selectors, + label, + error, + schema, + disabled, + helperText, + isLoading, + errorEcured, + variant = "outlined", +}) { + return ( + + {label} + + {helperText || error?.message} + + ); +} +export default SelectBox; diff --git a/src/core/components/ShowPlak.jsx b/src/core/components/ShowPlak.jsx index 5d51455..6e02934 100644 --- a/src/core/components/ShowPlak.jsx +++ b/src/core/components/ShowPlak.jsx @@ -14,7 +14,6 @@ const ShowPlak = ({ plak_number }) => { }; }; const plakParts = processPlak(plak_number); - return ( !isValueEmpty(filter.value)) + .map(({ filterMode, id, ...rest }) => ({ + ...rest, + id: id.replace(/__/g, "."), + fn: filterMode, + })); +} diff --git a/src/core/utils/isArrayEmpty.js b/src/core/utils/isArrayEmpty.js new file mode 100644 index 0000000..81624fb --- /dev/null +++ b/src/core/utils/isArrayEmpty.js @@ -0,0 +1,6 @@ +export default function isArrayEmpty(value) { + if (Array.isArray(value)) { + return value.length === 0 || value.every((v) => v === ""); + } + return value === "" || value === null || value === undefined; +} diff --git a/src/core/utils/nationalCodeValidation.js b/src/core/utils/nationalCodeValidation.js new file mode 100644 index 0000000..747f4cd --- /dev/null +++ b/src/core/utils/nationalCodeValidation.js @@ -0,0 +1,30 @@ +function validateNationalCode(code) { + const invalidCodes = [ + "1111111111", + "2222222222", + "3333333333", + "4444444444", + "5555555555", + "6666666666", + "7777777777", + "8888888888", + "9999999999", + ]; + if (invalidCodes.includes(code)) return false; + + const L = code.length; + if (L < 8 || parseInt(code, 10) === 0) return false; + + code = code.padStart(10, "0"); + if (parseInt(code.substr(3, 6), 10) === 0) return false; + + const c = parseInt(code.charAt(9), 10); + let s = 0; + for (let i = 0; i < 9; i++) { + s += parseInt(code.charAt(i), 10) * (10 - i); + } + s = s % 11; + return (s < 2 && c === s) || (s >= 2 && c === 11 - s); +} + +export default validateNationalCode; diff --git a/src/core/utils/pageMenu.js b/src/core/utils/pageMenu.js index f312523..1811e60 100644 --- a/src/core/utils/pageMenu.js +++ b/src/core/utils/pageMenu.js @@ -137,7 +137,7 @@ export const pageMenu = [ id: "roadItemManagmentMap", label: "پراکندگی بر روی نقشه", type: "page", - route: process.env.NEXT_PUBLIC_API_URL + "/v2/map?type=road_items", + route: "/dashboard/road-items/reports/map", icon: , permissions: [ "show-road-item-supervise-cartable", @@ -154,7 +154,7 @@ export const pageMenu = [ Subitems: [ { id: "roadItemManagmentReportProvince", - label: "تعداد فعالیت ها به تفکیک ایتم", + label: "تعداد فعالیت ها به تفکیک آیتم", type: "page", route: "/dashboard/road-items/reports/items-report", permissions: [ @@ -369,19 +369,11 @@ export const pageMenu = [ id: "roadObservationsManagmentSupervisorCartable", label: "کارتابل", type: "page", - route: process.env.NEXT_PUBLIC_API_URL + "/v2/road_observations/supervisor/cartable", - permissions: ["supervise-fast-react", "supervise-fast-react-province"], + route: "/dashboard/fast-react/supervisor", + permissions: ["show-fast-react", "show-fast-react-province"], }, ], }, - { - id: "roadObservationsManagmentList", - label: "رسیدگی به شکایات", - type: "page", - route: process.env.NEXT_PUBLIC_API_URL + "/v2/road_observations", - icon: , - permissions: ["show-fast-react", "show-fast-react-province", "show-fast-react-edarate-shahri"], - }, { id: "roadObservationsManagmentOparation", label: "عملیات", @@ -394,8 +386,8 @@ export const pageMenu = [ id: "roadObservationsManagmentOparationCartable", label: "کارتابل", type: "page", - route: process.env.NEXT_PUBLIC_API_URL + "/v2/road_observations/operator/cartable", - permissions: ["show-fast-react", "show-fast-react-province", "show-fast-react-edarate-shahri"], + route: "/dashboard/fast-react/operator", + permissions: ["show-fast-react-edarate-shahri"], }, ], }, @@ -468,18 +460,26 @@ export const pageMenu = [ id: "receiptManagmentOparationCartable", label: "کارتابل", type: "page", - route: process.env.NEXT_PUBLIC_API_URL + "/v2/receipt", + route: "/dashboard/damages/operator", permissions: ["all"], }, ], }, { - id: "receiptManagmentReport", + id: "damageItems", + label: "آیتم خسارات", + type: "page", + route: "/dashboard/damage-items", + icon: , + permissions: ["manage-damage"], + }, + { + id: "damagesManagmentReport", label: "گزارش ها", type: "page", - route: process.env.NEXT_PUBLIC_API_URL + "/v2/receipt/report", + route: "/dashboard/damages/report", icon: , - permissions: ["all"], + permissions: ["show-receipt", "show-receipt-province"], }, ], }, diff --git a/src/core/utils/routes.js b/src/core/utils/routes.js index a217a6e..6014c93 100644 --- a/src/core/utils/routes.js +++ b/src/core/utils/routes.js @@ -30,23 +30,34 @@ export const UPDATE_AZMAYESH_TYPE = api + "/api/v3/azmayesh_types/update"; //road patrol export const GET_ROAD_PATROL_OPERATOR_LIST = api + "/api/v3/road_patrols/operator_index"; -export const EXPORT_ROAD_PATROL_OPERATOR_LIST = api + "/v2/road_patrols/operator/cartable/report"; +export const EXPORT_ROAD_PATROL_OPERATOR_LIST = api + "/api/v3/road_patrols/operator_report"; export const GET_ROAD_PATROL_SUPERVISOR_LIST = api + "/api/v3/road_patrols/supervisor_index"; -export const EXPORT_ROAD_PATROL_SUPERVISOR_LIST = api + "/v2/road_patrols/supervisor/cartable/report"; +export const EXPORT_ROAD_PATROL_SUPERVISOR_LIST = api + "/api/v3/road_patrols/supervisor_report"; +export const EXPORT_PROVINCE_ROAD_PATROL_REPORTS = api + "/api/v3/road_patrol_reports/province_activity_excel"; +export const EXPORT_COUNTRY_ROAD_PATROL_REPORTS = api + "/api/v3/road_patrol_reports/country_activity_excel"; export const DELETE_ROAD_PATROL_SUPERVISOR = api + "/api/v3/road_patrols/delete"; +export const GET_CMMS_MACHINE_ROAD_PATROL = api + "/api/v3/road_patrols/machines"; export const GET_ROAD_PATROL_OPERATOR_REPORT = api + "/v2/road_patrols/operator/report"; -export const GET_ROAD_PATROL_SUPERVISOR_REPORT = api + "/v2/road_patrols/supervisor/report"; +export const GET_ROAD_PATROL_SUPERVISOR_REPORT = api + "/v2/road_patrols/operator/report"; export const CREATE_PATROL = api + "/api/v3/road_patrols/store"; export const GET_FMS_DATA = api + "/api/v3/fms_vehicle/get_activity"; export const GET_OTP_TOKEN = api + "/v2/get_otp_token"; -export const GET_PROVINCE_ACTIVITY_REPORT = api + "/api/v3/road_patrol_reports/province_activity"; -export const GET_CITY_ACTIVITY_REPORT = api + "/api/v3/road_patrol_reports/city_activity"; +export const GET_PROVINCE_ACTIVITY_REPORT = api + "/api/v3/road_patrol_reports/country_activity"; +export const GET_CITY_ACTIVITY_REPORT = api + "/api/v3/road_patrol_reports/province_activity"; // road items export const GET_ROAD_ITEMS_SUPERVISOR_LIST = api + "/api/v3/road_items/supervisor_index"; export const EXPORT_ROAD_ITEMS_SUPERVISOR_LIST = api + "/api/v3/road_items/supervisor_report"; export const GET_ROAD_ITEMS_OPERATOR_LIST = api + "/api/v3/road_items/operator_index"; export const EXPORT_ROAD_ITEMS_OPERATOR_LIST = api + "/api/v3/road_items/operator_report"; +export const EXPORT_PROVINCE_ROAD_ITEM_REPORTS_PER_ITEM = + api + "/api/v3/road_item_reports/province_activity_excel_per_item"; +export const EXPORT_COUNTRY_ROAD_ITEM_REPORTS_PER_ITEM = + api + "/api/v3/road_item_reports/country_activity_excel_per_item"; +export const EXPORT_PROVINCE_ROAD_ITEM_REPORTS_PER_SUB_ITEM = + api + "/api/v3/road_item_reports/province_activity_excel_per_sub_item"; +export const EXPORT_COUNTRY_ROAD_ITEM_REPORTS_PER_SUB_ITEM = + api + "/api/v3/road_item_reports/country_activity_excel_per_sub_item"; export const GET_ROAD_ITEMS_ITEM = api + "/v2/items"; export const GET_ROAD_ITEMS_SUB_ITEM = api + "/v2/sub_items"; export const GET_EDARAT_LISTS = api + "/public/contents/edarate_shahri_by_province"; @@ -59,10 +70,60 @@ export const RESTORE_BY_SUPERVISOR = api + "/api/v3/road_items/restore"; export const GET_CAR_LIST_SEARCH = api + "/api/v3/cmms_machines/search"; export const GET_RAHDARANS_LIST_SEARCH = api + "/api/v3/rahdaran/search"; export const GET_OBSERVED_GASHT_LIST = api + "/api/v3/observed_items/filter"; -export const GET_PROVINCE_ACTIVITY_PER_ITEM = api + "/api/v3/road_item_reports/province_activity_per_item"; -export const GET_CITY_ACTIVITY_PER_ITEM = api + "/api/v3/road_item_reports/city_activity_per_item"; -export const GET_PROVINCE_ACTIVITY_PER_SUB_ITEM = api + "/api/v3/road_item_reports/province_activity_per_sub_item"; -export const GET_CITY_ACTIVITY_PER_SUB_ITEM = api + "/api/v3/road_item_reports/city_activity_per_sub_item"; +export const GET_ROAD_ITEMS_REPORT_MAP = api + "/api/v3/road_item_reports/activities_on_map"; +export const GET_ROAD_ITEM = api + "/api/v3/road_item_reports/show_on_map"; +export const GET_PROVINCE_ACTIVITY_PER_ITEM = api + "/api/v3/road_item_reports/country_activity_per_item"; +export const GET_CITY_ACTIVITY_PER_ITEM = api + "/api/v3/road_item_reports/province_activity_per_item"; +export const GET_PROVINCE_ACTIVITY_PER_SUB_ITEM = api + "/api/v3/road_item_reports/country_activity_per_sub_item"; +export const GET_CITY_ACTIVITY_PER_SUB_ITEM = api + "/api/v3/road_item_reports/province_activity_per_sub_item"; +export const GET_IMAGES_ROAD_ITEM = api + "/api/v3/road_items/files"; +export const GET_CMMS_MACHINE_ROAD_ITEM = api + "/api/v3/road_items/machines"; +export const GET_RAHDARAN_ROAD_ITEM = api + "/api/v3/road_items/rahdaran"; + +// damage items +export const GET_RECEIPT_DAMAGE_ITEMS_LIST = api + "/api/v3/damages"; +export const CREATE_RECEIPT_DAMAGE_ITEMS = api + "/api/v3/damages"; +export const UPDATE_RECEIPT_DAMAGE_ITEMS = api + "/api/v3/damages"; +export const ATIVITY_RECEIPT_DAMAGE_ITEMS = api + "/api/v3/damages/activate"; + +// damage receipt +export const GET_TECHNICAL_DAMAGE_OPERATOR_LIST = api + "/api/v3/receipts"; +export const CREATE_DAMAGE = api + "/api/v3/receipts"; +export const GET_DAMAGE_ITEM_LIST = api + "/api/v3/damages/list"; +export const DELETE_DAMAGE_ITEM = api + "/api/v3/receipts"; +export const CREATE_FACTOR_DAMAGE = api + "/api/v3/receipts/submit_invoice"; +export const CONFIRM_PAYMENT_INFO = api + "/api/v3/receipts/confirm_payment_info"; +export const CHECK_PAYMENT_STATUS = api + "/api/v3/receipts/check_payment_status"; +export const SEND_SMS_AGAIN = api + "/api/v3/receipts/send_sms_again"; +export const EXPORT_DAMAGES_OPERATOR_LIST = api + "/api/v3/receipts/excel_report"; +export const GET_DAMAGE_ITEM_DETAILS = api + "/api/v3/receipts"; +export const UPDATE_DAMAGE_ITEM = api + "/api/v3/receipts"; +export const GET_INSURANCE = api + "/api/v3/receipts/generate_insurance_letter"; +export const GET_INSURANCE_PAGE = api + "/v2/receipt/send-to-insurance"; +export const GET_POLICERAH = api + "/api/v3/receipts/generate_insurance_letter"; +export const GET_POLICERAH_PAGE = api + "/v2/receipt/document-release"; + +// recept report +export const GET_COUNTRY_RECEIPT_REPORT = api + "/api/v3/receipt_reports/country_report"; +export const GET_PROVINCE_RECEIPT_REPORT = api + "/api/v3/receipt_reports/province_report"; +export const EXPORT_COUNTRY_RECEIPT_REPORT = api + "/api/v3/receipt_reports/country_excel_report"; +export const EXPORT_PROVINCE_RECEIPT_REPORT = api + "/api/v3/receipt_reports/province_excel_report"; // activity code log export const ACTIVITY_LOG = api + "/api/v3/profile/add_activity"; + +// fast react +export const GET_FAST_REACT_SUPERVISOR = api + "/api/v3/road_observations/supervisor_index"; +export const GET_FAST_REACT_COMPLAINTS = api + "/api/v3/road_observations/complaints_index"; +export const VERIFY_BY_FAST_REACT_SUPERVISOR = api + "/api/v3/road_observations/verify"; +export const REJECT_BY_FAST_REACT_SUPERVISOR = api + "/api/v3/road_observations/verify"; +export const REGISTER_COMPLAINTS_LIST = api + "/api/v3/road_observations/register"; +export const GET_FAST_REACT_OPERATOR = api + "/api/v3/road_observations/operator_index"; +export const GET_FAST_REACT_REFER_LIST = api + "/api/v3/road_observations/refer_list"; +export const REFER_FAST_REACT = api + "/api/v3/road_observations/refer"; +export const RESTORE_FAST_REACT = api + "/api/v3/road_observations/restore"; +export const GET_FAST_REACT_ITEM_DETAIL = api + "/api/v3/road_observations"; +export const UPDATE_FAST_REACT = api + "/api/v3/road_observations/modify_registration"; +export const EXPORT_FAST_REACT_SUPERVISOR_LIST = api + "/api/v3/road_observations/supervisor_report"; +export const EXPORT_FAST_REACT_OPERATOR_LIST = api + "/api/v3/road_observations/operator_report"; +export const EXPORT_FAST_REACT_COMPLAINT_LIST = api + "/api/v3/road_observations/complaints_report"; diff --git a/src/lib/contexts/DataTable.js b/src/lib/contexts/DataTable.js index 5c9752a..76f0f13 100644 --- a/src/lib/contexts/DataTable.js +++ b/src/lib/contexts/DataTable.js @@ -38,8 +38,22 @@ const DataTableProvider = ({ children, user_id, page_name, table_name, columns, return innerObject.value === ""; } }); - const CheckHideValues = Object.values(hideData).every((value) => value === true); + const flattenObject = (obj) => { + let result = {}; + for (const key in obj) { + if (typeof obj[key] === "object" && obj[key] !== null) { + Object.assign(result, flattenObject(obj[key])); // باز کردن اشیای تودرتو + } else { + result[key] = obj[key]; + } + } + return result; + }; + // تبدیل آبجکت به آبجکت فلت شده + const flatHideData = flattenObject(hideData); + + const CheckHideValues = Object.values(flatHideData).every((value) => value === true); hasAnyConflict = !CheckHideValues || JSON.stringify(sortData) !== JSON.stringify(initialSort) || !CheckFilterValues; diff --git a/src/lib/hooks/useDamageItemList.js b/src/lib/hooks/useDamageItemList.js new file mode 100644 index 0000000..a353627 --- /dev/null +++ b/src/lib/hooks/useDamageItemList.js @@ -0,0 +1,29 @@ +import { useEffect, useState } from "react"; +import { GET_DAMAGE_ITEM_LIST, GET_PROVINCE_LISTS } from "@/core/utils/routes"; +import useRequest from "@/lib/hooks/useRequest"; + +const useDamageItemList = () => { + const requestServer = useRequest(); + const [damageItemList, setDamageItemList] = useState([]); + const [loadingDamageItemList, setLoadingDamageItemList] = useState(true); + const [errorDamageItemList, setErrorDamageItemList] = useState(null); + + useEffect(() => { + const fetchCities = async () => { + try { + const response = await requestServer(`${GET_DAMAGE_ITEM_LIST}`); + setDamageItemList(response.data.data); + setLoadingDamageItemList(false); + } catch (e) { + setErrorDamageItemList(e); + setLoadingDamageItemList(false); + } + }; + + fetchCities(); + }, []); + + return { damageItemList, loadingDamageItemList, errorDamageItemList }; +}; + +export default useDamageItemList; diff --git a/src/lib/hooks/usePersianInput.js b/src/lib/hooks/usePersianInput.js new file mode 100644 index 0000000..641c27d --- /dev/null +++ b/src/lib/hooks/usePersianInput.js @@ -0,0 +1,49 @@ +import { useEffect } from "react"; +function usePersianInput(inputRef, onChange) { + useEffect(() => { + const input = inputRef.current; + if (!input) return; + + const handleInput = (event) => { + const originalValue = event.target.value; + const filteredValue = originalValue.replace(/[A-Za-z]/g, ""); + if (filteredValue !== originalValue) { + event.target.value = filteredValue; + if (onChange) { + onChange(filteredValue); + } + } else if (onChange) { + onChange(originalValue); + } + }; + + const handleKeyDown = (event) => { + const controlKeys = [ + "Backspace", + "Delete", + "Tab", + "Escape", + "Enter", + "ArrowLeft", + "ArrowRight", + "ArrowUp", + "ArrowDown", + ]; + if (controlKeys.includes(event.key)) return; + + if (/[A-Za-z]/.test(event.key)) { + event.preventDefault(); + } + }; + + input.addEventListener("input", handleInput); + input.addEventListener("keydown", handleKeyDown); + + return () => { + input.removeEventListener("input", handleInput); + input.removeEventListener("keydown", handleKeyDown); + }; + }, [inputRef, onChange]); +} + +export default usePersianInput; diff --git a/src/lib/hooks/useProvince.js b/src/lib/hooks/useProvince.js index f6b98b8..a36df7d 100644 --- a/src/lib/hooks/useProvince.js +++ b/src/lib/hooks/useProvince.js @@ -15,7 +15,6 @@ const useProvinces = () => { setProvinces(response.data.data); setLoadingProvinces(false); } catch (e) { - console.error("Error fetching provinces:", e); setErrorProvinces(e); setLoadingProvinces(false); } diff --git a/src/lib/hooks/useRoadItemGetISubtems.js b/src/lib/hooks/useRoadItemGetISubtems.js index b0e3264..368f72d 100644 --- a/src/lib/hooks/useRoadItemGetISubtems.js +++ b/src/lib/hooks/useRoadItemGetISubtems.js @@ -30,7 +30,7 @@ function reducer(state, action) { } } -const useRoadItemGetItems = (id) => { +const useRoadItemGetSubItems = (id) => { const requestServer = useRequest({ notificationShow: false }); const [state, dispatch] = useReducer(reducer, initialState); useEffect(() => { @@ -59,4 +59,4 @@ const useRoadItemGetItems = (id) => { }; }; -export default useRoadItemGetItems; +export default useRoadItemGetSubItems;