working on faaliat information

This commit is contained in:
2025-01-01 14:37:11 +03:30
70 changed files with 1738 additions and 517 deletions

View File

@@ -4,11 +4,12 @@ import useRequest from "@/lib/hooks/useRequest";
import { debounce } from "@mui/material/utils";
import { GET_CAR_LIST_SEARCH } from "@/core/utils/routes";
const CarCode = ({ carCode, setCarCode, inputValueDefault = "", error }) => {
const CarCode = ({ carCode, setCarCode, inputValueDefault = "", error, multiple = false }) => {
const [inputValue, setInputValue] = useState(inputValueDefault);
const [options, setOptions] = useState([]);
const [loading, setLoading] = useState(false);
const requestServer = useRequest();
const fetchCarCodes = (inputValue, controller) => {
const debouncer = debounce((query) => {
if (!query || query?.length < 3) {
@@ -40,20 +41,32 @@ const CarCode = ({ carCode, setCarCode, inputValueDefault = "", error }) => {
return (
<Autocomplete
multiple={multiple}
value={carCode}
size="small"
onChange={(event, newValue) => {
setCarCode(newValue || null); // Set an empty string if no value is selected
setCarCode(multiple ? newValue || [] : newValue || null);
}}
inputValue={inputValue}
onInputChange={(event, newInputValue) => {
setInputValue(newInputValue || ""); // Prevent inputValue from being null or undefined
}}
options={options}
getOptionLabel={(option) => (typeof option === "string" ? option : option.machine_code)}
getOptionLabel={(option) => {
if (typeof option === "string") return option;
const { machine_code, car_name, plak_number } = option;
let label = `${machine_code || ""}`;
if (car_name) {
label += ` | ${car_name}`;
}
if (plak_number) {
label += ` | ${plak_number}`;
}
return label;
}}
isOptionEqualToValue={(option, value) => {
if (!value) return false; // Handle null/undefined values
if (value === "") return option.machine_code === ""; // Handle empty string case
if (!value) return false;
if (value === "") return option.machine_code === "";
return option.machine_code === (value?.machine_code || value);
}}
loading={loading}
@@ -65,8 +78,8 @@ const CarCode = ({ carCode, setCarCode, inputValueDefault = "", error }) => {
{...params}
label="کد خودرو"
fullWidth
error={!!error} // نمایش خطا
helperText={error?.message} // پیام خطا
error={!!error}
helperText={error?.message}
InputProps={{
...params.InputProps,
endAdornment: (

View File

@@ -42,8 +42,9 @@ const DataTable_Main = (props) => {
const filteredFilterData = Object.values(filterData)
.filter((filter) => !isValueEmpty(filter.value))
.map(({ filterMode, ...rest }) => ({
.map(({ filterMode, id, ...rest }) => ({
...rest,
id: id.replace(/__/g, "."),
fn: filterMode,
}));

View File

@@ -2,7 +2,7 @@ import { Controller, useWatch } from "react-hook-form";
import FilterBodyField from "./FilterBodyField";
const FilterBodyController = ({ column, control, reset, errors }) => {
const dependencyField = useWatch({ control, name: column?.dependencyId });
const dependencyField = useWatch({ control, name: column.dependencyId });
return (
<Controller
@@ -14,7 +14,7 @@ const FilterBodyController = ({ column, control, reset, errors }) => {
filterParameters={value}
handleChange={onChange}
handleBlur={onBlur}
dependencyFieldValue={dependencyField || null}
dependencyFieldValue={column.dependencyId ? dependencyField : null}
resetForm={() => reset()}
errors={errors}
/>

View File

@@ -54,10 +54,13 @@ function FilterBodyField({
return <CustomTextFieldRange {...commonProps} />;
}
if (filterParameters.filterMode === "equals") {
return <CustomSelect {...commonProps} />;
return column.ColumnSelectComponent ? (
<column.ColumnSelectComponent {...commonProps} />
) : (
<CustomSelect {...commonProps} />
);
}
break;
case "date":
if (filterParameters.filterMode === "equals") {
return <CustomDatePicker {...commonProps} />;

View File

@@ -0,0 +1,53 @@
"use client";
import { FormControl, FormHelperText, InputLabel, MenuItem, OutlinedInput, Select, Typography } from "@mui/material";
import { useCallback, useEffect } from "react";
function CustomSelectByDependency({
column,
filterParameters,
defaultFilterTranslation,
handleChange,
columnSelectOption,
}) {
const isValidValue = columnSelectOption.some((option) => option.value === filterParameters.value);
const handleInvalidValue = useCallback(() => {
if (!isValidValue && filterParameters.value !== "") {
handleChange({ ...filterParameters, value: columnSelectOption[0]?.value || "" });
}
}, [isValidValue, filterParameters, handleChange, columnSelectOption]);
useEffect(() => {
handleInvalidValue();
}, [handleInvalidValue]);
const selectedValue = isValidValue ? filterParameters.value : columnSelectOption[0]?.value || "";
return (
<FormControl fullWidth sx={{ marginY: 1 }}>
<InputLabel id={`label${column.id}`}>{column.header}</InputLabel>
<Select
labelId={`label${column.id}`}
id={column.id}
name={`${column.id}.value`}
value={selectedValue}
input={<OutlinedInput label={column.header} />}
onChange={(e) => handleChange({ ...filterParameters, value: e.target.value })}
>
{columnSelectOption.map((option) => (
<MenuItem key={option.value} value={option.value}>
{option.label}
</MenuItem>
))}
</Select>
<FormHelperText>
<Typography variant="button" sx={{ color: "#155175" }}>
نوع فیلتر: {defaultFilterTranslation}
</Typography>
</FormHelperText>
</FormControl>
);
}
export default CustomSelectByDependency;

View File

@@ -9,14 +9,16 @@ function FilterColumn({ columns, user_id, page_name, table_name }) {
return (
<>
<FilterButton drawerState={open} setDrawerState={setOpen} />
<FilterBody
columns={columns}
drawerState={open}
setDrawerState={setOpen}
user_id={user_id}
page_name={page_name}
table_name={table_name}
/>
{open && (
<FilterBody
columns={columns}
drawerState={open}
setDrawerState={setOpen}
user_id={user_id}
page_name={page_name}
table_name={table_name}
/>
)}
</>
);
}

View File

@@ -6,7 +6,7 @@ import useDataTable from "@/lib/hooks/useDataTable";
function ResetStorage({ user_id, page_name, table_name }) {
const { resetAction } = useTableSetting();
const { isDirty } = useDataTable();
const { isAnyDirty } = useDataTable();
const reset = () => {
resetAction(user_id, page_name, table_name);
};
@@ -14,7 +14,7 @@ function ResetStorage({ user_id, page_name, table_name }) {
return (
<Tooltip title="بازنشانی اطلاعات">
<IconButton onClick={reset} aria-label="reset table data">
{isDirty ? (
{isAnyDirty ? (
<Badge
anchorOrigin={{
vertical: "top",

View File

@@ -9,13 +9,14 @@ const InputType = ({ itemInfo, defaultValues, setDefaultValues }) => {
[itemInfo.id]: value,
}));
};
const label = itemInfo.unit ? `${itemInfo.name} (${itemInfo.unit})` : itemInfo.name;
return (
<FormControl size="small" fullWidth variant="outlined">
<InputLabel htmlFor={itemInfo.id}>{itemInfo.name}</InputLabel>
<InputLabel htmlFor={itemInfo.id}>{label}</InputLabel>
<OutlinedInput
id={itemInfo.id}
label={itemInfo.name}
label={label}
autoComplete="off"
size="small"
fullWidth

View File

@@ -9,15 +9,15 @@ const SelectType = ({ itemInfo, defaultValues, setDefaultValues }) => {
[itemInfo.id]: newValue,
}));
};
const label = itemInfo.unit ? `${itemInfo.name} (${itemInfo.unit})` : itemInfo.name;
return (
<FormControl size="small" fullWidth variant="outlined">
<InputLabel id={itemInfo.id}>{itemInfo.name}</InputLabel>
<InputLabel id={itemInfo.id}>{label}</InputLabel>
<Select
labelId={itemInfo.id}
id={itemInfo.id}
value={defaultValues[itemInfo.id] || ""}
label={itemInfo.name}
label={label}
onChange={handleChange}
>
<MenuItem value="">{itemInfo.name}</MenuItem>

View File

@@ -2,7 +2,7 @@ import React, { useEffect, useRef, useState } from "react";
import { Marker, useMapEvents } from "react-leaflet";
import "leaflet/dist/leaflet.css";
import L from "leaflet";
import { Box, Button, Typography } from "@mui/material";
import { Alert, Box, Button, Stack, Typography, Zoom } from "@mui/material";
import dynamic from "next/dynamic";
import MapLoading from "@/core/components/MapLayer/Loading";
import HereIcon from "@/assets/images/examine_marker_active.png";
@@ -40,9 +40,11 @@ const createCustomIcon = (size, iconUrl, labelText) => {
popupAnchor: [0, -size[1]],
});
};
const MAX_ZOOM_FOR_MARKER = 13;
const MapInteraction = ({ setValue, startLat, startLng }) => {
const [isMarkerLocked, setIsMarkerLocked] = useState(!!(startLat && startLng)); // وضعیت قفل مارکر
const [enableSend, setEnableSend] = useState(false);
const [markerPosition, setMarkerPosition] = useState(
startLat && startLng ? { lat: startLat, lng: startLng } : null
);
@@ -50,11 +52,13 @@ const MapInteraction = ({ setValue, startLat, startLng }) => {
const map = useMapEvents({
move(e) {
setEnableSend(e.target.getZoom() > MAX_ZOOM_FOR_MARKER);
if (!isMarkerLocked && markerRef.current) {
markerRef.current.setLatLng(e.target.getCenter());
}
},
zoom(e) {
setEnableSend(e.target.getZoom() > MAX_ZOOM_FOR_MARKER);
if (!isMarkerLocked && markerRef.current) {
markerRef.current.setLatLng(e.target.getCenter());
}
@@ -69,6 +73,7 @@ const MapInteraction = ({ setValue, startLat, startLng }) => {
}, [startLat, startLng, map]);
const handleMarkerClick = () => {
if (!enableSend) return;
if (!isMarkerLocked) {
const center = map.getCenter();
setValue("start_point", { lat: center.lat.toString(), lng: center.lng.toString() });
@@ -93,6 +98,23 @@ const MapInteraction = ({ setValue, startLat, startLng }) => {
click: handleMarkerClick,
}}
/>
<Stack
direction={"row"}
justifyContent={"center"}
sx={{
zIndex: "400",
width: "100%",
position: "absolute",
top: 0,
left: 0,
}}
>
<Zoom in={!enableSend}>
<Alert sx={{ mt: 1, py: 0.5, px: 2, borderRadius: 2, border: 1 }} icon={false} color={"warning"}>
برای ثبت محل فعالیت، لطفاً نقشه را بیشتر زوم کنید.
</Alert>
</Zoom>
</Stack>
{isMarkerLocked && (
<Box
sx={{

View File

@@ -2,7 +2,7 @@ import React, { useEffect, useRef, useState } from "react";
import { Marker, useMapEvents } from "react-leaflet";
import "leaflet/dist/leaflet.css";
import L from "leaflet";
import { Box, Button, Typography } from "@mui/material";
import { Alert, Box, Button, Stack, Typography, Zoom } from "@mui/material";
import dynamic from "next/dynamic";
import MapLoading from "@/core/components/MapLayer/Loading";
import EndIcon from "@/assets/images/examine_marker_active.png";
@@ -40,17 +40,20 @@ const createCustomIcon = (size, iconUrl, labelText) => {
popupAnchor: [0, -size[1]],
});
};
const MAX_ZOOM_FOR_MARKER = 13;
const MapInteraction = ({ setValue, startLat, startLng, endLat, endLng }) => {
const [isStartLocked, setIsStartLocked] = useState(!!(startLat && startLng)); // وضعیت قفل نقطه آغاز
const [isEndLocked, setIsEndLocked] = useState(!!(endLat && endLng)); // وضعیت قفل نقطه پایان
const [startPosition, setStartPosition] = useState(startLat && startLng ? { lat: startLat, lng: startLng } : null);
const [endPosition, setEndPosition] = useState(endLat && endLng ? { lat: endLat, lng: endLng } : null);
const [enableSend, setEnableSend] = useState(false);
const startRef = useRef();
const endRef = useRef();
const map = useMapEvents({
move(e) {
setEnableSend(e.target.getZoom() > MAX_ZOOM_FOR_MARKER);
if (!isStartLocked && startRef.current) {
startRef.current.setLatLng(e.target.getCenter());
} else if (isStartLocked && !isEndLocked && endRef.current) {
@@ -58,6 +61,7 @@ const MapInteraction = ({ setValue, startLat, startLng, endLat, endLng }) => {
}
},
zoom(e) {
setEnableSend(e.target.getZoom() > MAX_ZOOM_FOR_MARKER);
if (!isStartLocked && startRef.current) {
startRef.current.setLatLng(e.target.getCenter());
} else if (isStartLocked && !isEndLocked && endRef.current) {
@@ -101,6 +105,7 @@ const MapInteraction = ({ setValue, startLat, startLng, endLat, endLng }) => {
icon={createCustomIcon([35, 35], StartIcon.src, "نقطه شروع")}
eventHandlers={{
click: () => {
if (!enableSend) return;
if (!isStartLocked) {
const center = map.getCenter();
setValue("start_point", { lat: center.lat.toString(), lng: center.lng.toString() });
@@ -110,6 +115,23 @@ const MapInteraction = ({ setValue, startLat, startLng, endLat, endLng }) => {
},
}}
/>
<Stack
direction={"row"}
justifyContent={"center"}
sx={{
zIndex: "400",
width: "100%",
position: "absolute",
top: 0,
left: 0,
}}
>
<Zoom in={!enableSend}>
<Alert sx={{ mt: 1, py: 0.5, px: 2, borderRadius: 2, border: 1 }} icon={false} color={"warning"}>
برای ثبت محل فعالیت، لطفاً نقشه را بیشتر زوم کنید.
</Alert>
</Zoom>
</Stack>
{isStartLocked && !isEndLocked && (
<Box
sx={{
@@ -125,21 +147,45 @@ const MapInteraction = ({ setValue, startLat, startLng, endLat, endLng }) => {
</Box>
)}
{isStartLocked && (
<Marker
position={endPosition || map.getCenter()}
ref={endRef}
icon={createCustomIcon([35, 35], EndIcon.src, "نقطه پایان")}
eventHandlers={{
click: () => {
if (!isEndLocked) {
const center = map.getCenter();
setValue("end_point", { lat: center.lat.toString(), lng: center.lng.toString() });
setEndPosition({ lat: center.lat, lng: center.lng });
setIsEndLocked(true);
}
},
}}
/>
<>
<Stack
direction={"row"}
justifyContent={"center"}
sx={{
zIndex: "400",
width: "100%",
position: "absolute",
top: 0,
left: 0,
}}
>
<Zoom in={!enableSend}>
<Alert
sx={{ mt: 1, py: 0.5, px: 2, borderRadius: 2, border: 1 }}
icon={false}
color={"warning"}
>
برای ثبت محل فعالیت، لطفاً نقشه را بیشتر زوم کنید.
</Alert>
</Zoom>
</Stack>
<Marker
position={endPosition || map.getCenter()}
ref={endRef}
icon={createCustomIcon([35, 35], EndIcon.src, "نقطه پایان")}
eventHandlers={{
click: () => {
if (!enableSend) return;
if (!isEndLocked) {
const center = map.getCenter();
setValue("end_point", { lat: center.lat.toString(), lng: center.lng.toString() });
setEndPosition({ lat: center.lat, lng: center.lng });
setIsEndLocked(true);
}
},
}}
/>
</>
)}
{isEndLocked && (
<Box

View File

@@ -0,0 +1,29 @@
import React from "react";
import { Stack, Typography } from "@mui/material";
import LtrTextField from "@/core/components/LtrTextField";
import InputAdornment from "@mui/material/InputAdornment";
const NumberField = React.forwardRef((props, ref) => {
return (
<LtrTextField
{...props}
ref={ref}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<Stack direction="row" alignItems="center" spacing={0.5}>
<Typography sx={{ margin: 1 }} component="span">
{(props.value / 1).toLocaleString()}
</Typography>
<Typography component="span" variant="caption">
{props.unit}
</Typography>
</Stack>
</InputAdornment>
),
}}
/>
);
});
NumberField.displayName = "NumberField";
export default NumberField;

View File

@@ -5,7 +5,7 @@ import useRequest from "@/lib/hooks/useRequest";
import { debounce } from "@mui/material/utils";
import { GET_RAHDARANS_LIST_SEARCH } from "@/core/utils/routes";
const RahdarCode = ({ rahdarsCode, setRahdarsCode, error }) => {
const RahdarCode = ({ rahdarsCode, setRahdarsCode, error, multiple = true }) => {
const [inputValue, setInputValue] = useState(""); // مدیریت مقدار تایپ‌شده
const [options, setOptions] = useState([]);
const [loading, setLoading] = useState(false);
@@ -42,11 +42,11 @@ const RahdarCode = ({ rahdarsCode, setRahdarsCode, error }) => {
return (
<Autocomplete
multiple // قابلیت انتخاب چندگانه
multiple={multiple} // قابلیت انتخاب چندگانه داینامیک
value={rahdarsCode} // آرایه موارد انتخاب‌شده
size="small"
onChange={(event, newValue) => {
setRahdarsCode(newValue || []); // به‌روزرسانی موارد انتخاب‌شده
setRahdarsCode(multiple ? newValue || [] : newValue || null); // مدیریت حالت چندگانه و تک‌گانه
}}
inputValue={inputValue} // مقدار تایپ‌شده
onInputChange={(event, newInputValue) => {
@@ -86,5 +86,4 @@ const RahdarCode = ({ rahdarsCode, setRahdarsCode, error }) => {
/>
);
};
export default RahdarCode;

View File

@@ -0,0 +1,62 @@
import { Stack } from "@mui/material";
const ShowPlak = ({ plak_number }) => {
const processPlak = (plak_number) => {
const parts = plak_number.match(/^([\u0600-\u06FF]+)(\d+)-(\d+)([^\d]*)(\d+)$/);
if (!parts) return {};
return {
region: parts[1], // "ايران"
mainNumber: parts[2], // "13"
serial: parts[3], // "189"
letter: parts[4], // "الف"
number: parts[5], // "15"
};
};
const plakParts = processPlak(plak_number);
return (
<Stack sx={{ border: 1, borderColor: "divider", borderRadius: 1, maxWidth: "150px" }} direction={"row"}>
<Stack
sx={{
borderRight: 1,
borderColor: "divider",
textAlign: "center",
width: "100%",
}}
>
{plakParts.region} {plakParts.mainNumber}
</Stack>
<Stack spacing={1} direction={"row"} sx={{ width: "100%" }}>
<Stack
sx={{
textAlign: "center",
width: "100%",
px: 1,
}}
>
{plakParts.serial}
</Stack>
<Stack
sx={{
textAlign: "center",
width: "100%",
pl: 1,
}}
>
{plakParts.letter}
</Stack>
<Stack
sx={{
textAlign: "center",
width: "100%",
px: 1,
}}
>
{plakParts.number}
</Stack>
</Stack>
</Stack>
);
};
export default ShowPlak;

View File

@@ -124,13 +124,6 @@ export const pageMenu = [
hasSubitems: true,
badges: ["road_items.operation_cnt"],
Subitems: [
{
id: "roadItemManagmentOparationCreate",
label: "ثبت فعالیت",
type: "page",
route: process.env.NEXT_PUBLIC_API_URL + "/v2/road_items/operator/create",
permissions: ["create-road-item"],
},
{
id: "roadItemManagmentOparationCartable",
label: "کارتابل",
@@ -186,7 +179,7 @@ export const pageMenu = [
id: "roadPatrolManagmentSupervisorCartable",
label: "کارتابل",
type: "page",
route: process.env.NEXT_PUBLIC_API_URL + "/v2/road_patrols/supervisor/cartable",
route: "/dashboard/road-patrols/supervisor",
permissions: [
"show-road-patrol-supervise-cartable",
"show-road-patrol-supervise-cartable-province",

View File

@@ -29,9 +29,9 @@ export const ADD_AZMAYESH_TYPE = api + "/api/v3/azmayesh_types/store";
export const UPDATE_AZMAYESH_TYPE = api + "/api/v3/azmayesh_types/update";
//road patrol
export const GET_ROAD_PATROL_OPERATOR_LIST = "/v3/api/fake-road-patrol/operator";
export const GET_ROAD_PATROL_OPERATOR_LIST = api + "/api/v3/road_patrols/operator_index";
export const EXPORT_ROAD_PATROL_OPERATOR_LIST = "https://rms.rmto.ir/v2/road_patrols/operator/cartable/report";
export const GET_ROAD_PATROL_SUPERVISOR_LIST = "/v3/api/fake-road-patrol/supervisor";
export const GET_ROAD_PATROL_SUPERVISOR_LIST = api + "/api/v3/road_patrols/supervisor_index";
export const EXPORT_ROAD_PATROL_SUPERVISOR_LIST = "https://rms.rmto.ir/v2/road_patrols/supervisor/cartable/report";
export const GET_OTP_TOKEN = "fake/api";
export const VERIFY_OTP = "fake/api";
@@ -43,6 +43,7 @@ export const GET_ROAD_ITEMS_OPERATOR_LIST = api + "/api/v3/road_items/operator_i
export const EXPORT_ROAD_ITEMS_OPERATOR_LIST = api + "/api/v3/road_items/operator_report";
export const GET_ROAD_ITEMS_ITEM = "https://rms.witel.ir/v2/items";
export const GET_ROAD_ITEMS_SUB_ITEM = "https://rms.witel.ir/v2/sub_items";
export const GET_EDARAT_LISTS = "https://rms.witel.ir/public/contents/edarate_shahri_by_province";
export const CREATE_ROAD_ITEMS = api + "/api/v3/road_items/store";
export const UPDATE_ROAD_ITEMS = api + "/api/v3/road_items/update";
export const VERIFY_BY_SUPERVISOR = api + "/api/v3/road_items/verify_by_supervisor";