resolve conflicts

This commit is contained in:
2024-12-23 13:09:03 +03:30
106 changed files with 4074 additions and 582 deletions

View File

@@ -1,28 +1,26 @@
"use client";
import {Autocomplete, CircularProgress, TextField} from "@mui/material";
import {useEffect, useState} from "react";
import { Autocomplete, CircularProgress, TextField } from "@mui/material";
import { useEffect, useState } from "react";
import useRequest from "@/lib/hooks/useRequest";
import {debounce} from "@mui/material/utils";
import { debounce } from "@mui/material/utils";
import { GET_CAR_LIST_SEARCH } from "@/core/utils/routes";
const CarCode = ({carCode, setCarCode}) => {
const [inputValue, setInputValue] = useState('');
const CarCode = ({ carCode, setCarCode, inputValueDefault = "", error }) => {
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.length < 2) {
if (!query || query?.length < 3) {
setOptions([]);
return;
}
setLoading(true);
requestServer(`car-code/api?${query}`, "get", {
requestOptions: {signal: controller.signal}
requestServer(`${GET_CAR_LIST_SEARCH}?machine_code=${query}`, "get", {
requestOptions: { signal: controller.signal },
})
.then((response) => {
setOptions(response || []);
setOptions(response.data.data || []);
})
.catch(() => {
setOptions([]);
@@ -30,9 +28,9 @@ const CarCode = ({carCode, setCarCode}) => {
.finally(() => {
setLoading(false);
});
}, 500)
debouncer(inputValue)
}
}, 500);
debouncer(inputValue);
};
useEffect(() => {
const controller = new AbortController();
@@ -45,34 +43,35 @@ const CarCode = ({carCode, setCarCode}) => {
value={carCode}
size="small"
onChange={(event, newValue) => {
setCarCode(newValue);
setCarCode(newValue || null); // Set an empty string if no value is selected
}}
inputValue={inputValue}
onInputChange={(event, newInputValue) => {
setInputValue(newInputValue);
setInputValue(newInputValue || ""); // Prevent inputValue from being null or undefined
}}
options={options}
getOptionLabel={(option) =>
typeof option === 'string' ? option : option.name || option.code
}
getOptionLabel={(option) => (typeof option === "string" ? option : option.machine_code)}
isOptionEqualToValue={(option, value) => {
if (!value) return false; // Handle null/undefined values
if (value === "") return option.machine_code === ""; // Handle empty string case
return option.machine_code === (value?.machine_code || value);
}}
loading={loading}
loadingText="درحال جستجوی کد خودرو"
noOptionsText={
inputValue.length < 2
? "حداقل دو رقم از کد خودرو را وارد کنید"
: "کد خودرویی یافت نشد"
}
sx={{width: 300}}
noOptionsText={inputValue?.length < 3 ? "حداقل سه رقم از کد خودرو را وارد کنید" : "کد خودرویی یافت نشد"}
fullWidth
renderInput={(params) => (
<TextField
{...params}
label="کد خودرو"
fullWidth
error={!!error} // نمایش خطا
helperText={error?.message} // پیام خطا
InputProps={{
...params.InputProps,
endAdornment: (
<>
{loading ? <CircularProgress size="25px" color="inherit"/> : null}
{loading ? <CircularProgress size="25px" color="inherit" /> : null}
{params.InputProps.endAdornment}
</>
),
@@ -82,5 +81,4 @@ const CarCode = ({carCode, setCarCode}) => {
/>
);
};
export default CarCode;
export default CarCode;

View File

@@ -39,7 +39,13 @@ const DataTable_Main = (props) => {
}
return value === "" || value === null || value === undefined;
};
const filteredFilterData = Object.values(filterData).filter((filter) => !isValueEmpty(filter.value));
const filteredFilterData = Object.values(filterData)
.filter((filter) => !isValueEmpty(filter.value))
.map(({ filterMode, ...rest }) => ({
...rest,
fn: filterMode,
}));
const params = new URLSearchParams();
params.set("start", `${pagination.pageIndex * pagination.pageSize}`);

View File

@@ -1,13 +1,14 @@
"use client";
import { Box, Button, Drawer, styled } from "@mui/material";
import { Form, Formik, getIn } from "formik";
import useTableSetting from "@/lib/hooks/useTableSetting";
import { useEffect, useState } from "react";
import FilterHeader from "@/core/components/DataTable/filter/FilterHeader";
import * as Yup from "yup";
import FilterBodyField from "@/core/components/DataTable/filter/FilterBodyField";
import FilterHeader from "@/core/components/DataTable/filter/FilterHeader";
import useDataTable from "@/lib/hooks/useDataTable";
import { yupResolver } from "@hookform/resolvers/yup";
import { Box, Button, Drawer, styled } from "@mui/material";
import { useEffect, useState } from "react";
import { Controller, useForm, useWatch } from "react-hook-form";
import * as Yup from "yup";
import FilterBodyController from "./FilterBodyController";
const ScrollBox = styled(Box)({
flexGrow: 1,
@@ -26,21 +27,15 @@ const ScrollBox = styled(Box)({
},
});
function FilterBody({ columns, drawerState, setDrawerState, user_id, page_name, table_name }) {
const { settingStore } = useTableSetting();
const [initialValues, setInitialValues] = useState({});
const [validationSchema, setValidationSchema] = useState({});
function FilterBody({ columns, drawerState, setDrawerState }) {
const { filterData, setFilterData } = useDataTable();
const [validationSchema, setValidationSchema] = useState(Yup.object({}));
useEffect(() => {
setInitialValues(filterData);
}, [columns, settingStore, user_id, page_name, table_name, filterData]);
useEffect(() => {
const generatedSchema = Yup.object().shape(
Object.keys(initialValues).reduce((acc, key) => {
const initialValue = initialValues[key];
if (initialValue.filterFn === "between") {
const schema = Yup.object(
Object.keys(filterData).reduce((acc, key) => {
const initialValue = filterData[key];
if (initialValue.filterMode === "between") {
acc[key] = Yup.object().shape({
value: Yup.array()
.of(Yup.string().nullable())
@@ -68,13 +63,23 @@ function FilterBody({ columns, drawerState, setDrawerState, user_id, page_name,
}, {})
);
setValidationSchema(generatedSchema);
}, [initialValues]);
setValidationSchema(schema);
}, [filterData]);
const handleSubmit = (values, { setSubmitting }) => {
const {
handleSubmit,
control,
reset,
formState: { errors, isDirty, isValid },
} = useForm({
defaultValues: filterData,
resolver: yupResolver(validationSchema),
mode: "onChange",
});
const onSubmit = (data) => {
setDrawerState(false);
setFilterData(values);
setSubmitting(false);
setFilterData(data);
};
return (
@@ -84,72 +89,49 @@ function FilterBody({ columns, drawerState, setDrawerState, user_id, page_name,
sx={{ overflowY: "hidden", display: "flex", flexDirection: "column", height: "100%", zIndex: "1300" }}
>
<FilterHeader setDrawerState={setDrawerState} />
{Object.keys(initialValues).length > 0 && (
{Object.keys(filterData).length > 0 && (
<ScrollBox>
<Formik initialValues={initialValues} onSubmit={handleSubmit} validationSchema={validationSchema}>
{({
values,
handleChange,
handleBlur,
setFieldValue,
errors,
touched,
isSubmitting,
isValid,
dirty,
resetForm,
}) => (
<Form>
<Box sx={{ px: 1, py: 2 }}>
{columns.map(
(column) =>
column.enableColumnFilter && (
<FilterBodyField
key={column.id}
column={column}
filterParameters={values[column.id]}
handleChange={handleChange}
handleBlur={handleBlur}
dependencyFieldValue={
column.dependencyId ? getIn(values, column.dependencyId) : null
}
setFieldValue={setFieldValue}
resetForm={resetForm}
errors={errors}
touched={touched}
/>
)
)}
</Box>
<Box
sx={{
display: "flex",
justifyContent: "center",
alignItems: "center",
pb: 2,
}}
>
<Button
type="submit"
variant="contained"
size="large"
disabled={isSubmitting || !isValid || !dirty}
sx={{
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",
},
}}
>
اعمال فیلتر
</Button>
</Box>
</Form>
)}
</Formik>
<form onSubmit={handleSubmit(onSubmit)}>
<Box sx={{ px: 1, py: 2 }}>
{columns.map(
(column) =>
column.enableColumnFilter && (
<FilterBodyController
key={column.id}
column={column}
control={control}
errors={errors}
reset={reset}
/>
)
)}
</Box>
<Box
sx={{
display: "flex",
justifyContent: "center",
alignItems: "center",
pb: 2,
}}
>
<Button
type="submit"
variant="contained"
size="large"
disabled={!isDirty || !isValid}
sx={{
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",
},
}}
>
اعمال فیلتر
</Button>
</Box>
</form>
</ScrollBox>
)}
</Drawer>

View File

@@ -0,0 +1,25 @@
import { Controller, useWatch } from "react-hook-form";
import FilterBodyField from "./FilterBodyField";
const FilterBodyController = ({ column, control, reset, errors }) => {
const dependencyField = useWatch({ control, name: column?.dependencyId });
return (
<Controller
name={`${column.id}`}
control={control}
render={({ field: { value, onBlur, onChange } }) => (
<FilterBodyField
column={column}
filterParameters={value}
handleChange={onChange}
handleBlur={onBlur}
dependencyFieldValue={dependencyField || null}
resetForm={() => reset()}
errors={errors}
/>
)}
/>
);
};
export default FilterBodyController;

View File

@@ -1,11 +1,11 @@
import { useState } from "react";
import FilterOptionList from "@/core/components/DataTable/filter/FilterOptionList";
import CustomDatePicker from "@/core/components/DataTable/filter/fieldsType/CustomDate/CustomDatePicker";
import CustomDatePickerRange from "@/core/components/DataTable/filter/fieldsType/CustomDate/CustomDatePickerRange";
import CustomSelect from "@/core/components/DataTable/filter/fieldsType/CustomSelect";
import CustomSelectMultiple from "@/core/components/DataTable/filter/fieldsType/CustomSelectMultiple";
import CustomTextField from "@/core/components/DataTable/filter/fieldsType/CustomTextField";
import CustomTextFieldRange from "@/core/components/DataTable/filter/fieldsType/CustomTextFieldRange";
import CustomDatePickerRange from "@/core/components/DataTable/filter/fieldsType/CustomDate/CustomDatePickerRange";
import CustomDatePicker from "@/core/components/DataTable/filter/fieldsType/CustomDate/CustomDatePicker";
import CustomSelectMultiple from "@/core/components/DataTable/filter/fieldsType/CustomSelectMultiple";
import { useState } from "react";
const columnFilterModeOptionFa = {
equals: "برابر",
@@ -24,82 +24,71 @@ function FilterBodyField({
handleBlur,
dependencyFieldValue,
setFieldValue,
errors,
touched,
resetForm,
errors,
}) {
const [anchorEl, setAnchorEl] = useState(null);
const defaultFilterTranslation = columnFilterModeOptionFa[filterParameters.filterFn] || filterParameters.filterFn;
const defaultFilterTranslation =
columnFilterModeOptionFa[filterParameters.filterMode] || filterParameters.filterMode;
const handleOpenFilterBox = (event) => {
setAnchorEl(event.currentTarget);
};
const renderField = () => {
const commonProps = {
column,
filterParameters,
defaultFilterTranslation,
handleOpenFilterBox,
dependencyFieldValue,
setFieldValue,
handleChange,
handleBlur,
errors,
};
switch (filterParameters.datatype) {
case "numeric":
if (filterParameters.filterMode === "between") {
return <CustomTextFieldRange {...commonProps} />;
}
if (filterParameters.filterMode === "equals") {
return <CustomSelect {...commonProps} />;
}
break;
case "date":
if (filterParameters.filterMode === "equals") {
return <CustomDatePicker {...commonProps} />;
}
if (filterParameters.filterMode === "between") {
return <CustomDatePickerRange {...commonProps} />;
}
break;
case "array":
if (filterParameters.filterMode === "equals") {
return <CustomSelectMultiple {...commonProps} />;
}
break;
default:
return <CustomTextField {...commonProps} />;
}
};
return (
<>
{filterParameters.datatype === "numeric" && filterParameters.filterFn === "between" ? (
<CustomTextFieldRange
column={column}
filterParameters={filterParameters}
defaultFilterTranslation={defaultFilterTranslation}
handleOpenFilterBox={handleOpenFilterBox}
handleBlur={handleBlur}
handleChange={handleChange}
errors={errors}
touched={touched}
/>
) : filterParameters.datatype === "numeric" && filterParameters.filterFn === "equals" ? (
<CustomSelect
column={column}
filterParameters={filterParameters}
dependencyFieldValue={dependencyFieldValue}
setFieldValue={setFieldValue}
defaultFilterTranslation={defaultFilterTranslation}
handleOpenFilterBox={handleOpenFilterBox}
/>
) : filterParameters.datatype === "date" && filterParameters.filterFn === "equals" ? (
<CustomDatePicker
column={column}
filterParameters={filterParameters}
setFieldValue={setFieldValue}
defaultFilterTranslation={defaultFilterTranslation}
handleOpenFilterBox={handleOpenFilterBox}
/>
) : filterParameters.datatype === "date" && filterParameters.filterFn === "between" ? (
<CustomDatePickerRange
column={column}
filterParameters={filterParameters}
setFieldValue={setFieldValue}
defaultFilterTranslation={defaultFilterTranslation}
handleOpenFilterBox={handleOpenFilterBox}
errors={errors}
touched={touched}
/>
) : filterParameters.datatype === "array" && filterParameters.filterFn === "equals" ? (
<CustomSelectMultiple
column={column}
filterParameters={filterParameters}
setFieldValue={setFieldValue}
defaultFilterTranslation={defaultFilterTranslation}
handleOpenFilterBox={handleOpenFilterBox}
/>
) : (
<CustomTextField
column={column}
filterParameters={filterParameters}
defaultFilterTranslation={defaultFilterTranslation}
handleOpenFilterBox={handleOpenFilterBox}
handleBlur={handleBlur}
handleChange={(e) => setFieldValue(`${column.id}.value`, e.target.value)}
/>
)}
{renderField()}
{Array.isArray(column.columnFilterModeOptions) && (
<FilterOptionList
column={column}
anchorEl={anchorEl}
setAnchorEl={setAnchorEl}
filterParameters={filterParameters}
filterType={filterParameters.filterFn}
setFieldValue={setFieldValue}
filterType={filterParameters.filterMode}
handleChange={handleChange}
resetForm={resetForm}
filterOption={column.columnFilterModeOptions}
columnFilterModeOptionFa={columnFilterModeOptionFa}

View File

@@ -4,19 +4,22 @@ import { ListItem, Menu } from "@mui/material";
import { useState } from "react";
function FilterOptionList({
column,
filterType,
filterOption,
filterParameters,
anchorEl,
columnFilterModeOptionFa,
setAnchorEl,
setFieldValue,
handleChange,
}) {
const [selectedFilter, setSelectedFilter] = useState(filterType);
const handleChangeItem = (event, index) => {
setFieldValue(`${column.id}.value`, filterOption[index] === "between" ? ["", ""] : "");
setFieldValue(`${column.id}.filterFn`, filterOption[index]);
handleChange({
...filterParameters,
value: filterOption[index] === "between" ? ["", ""] : "",
filterMode: filterOption[index],
});
setSelectedFilter(filterOption[index]);
setAnchorEl(null);
};

View File

@@ -2,12 +2,14 @@ import React from "react";
import MuiDatePicker from "@/core/components/DataTable/filter/fieldsType/CustomDate/MuiDatePicker";
import { Typography } from "@mui/material";
function CustomDatePicker({ column, filterParameters, defaultFilterTranslation, setFieldValue }) {
function CustomDatePicker({ column, filterParameters, defaultFilterTranslation, handleChange }) {
return (
<MuiDatePicker
name={`${column.id}.value`}
value={filterParameters.value}
setFieldValue={setFieldValue}
setFieldValue={(name, formattedDate) => {
handleChange({ ...filterParameters, value: formattedDate });
}}
placeholder={column.header}
helperText={
<Typography variant="button" sx={{ color: "#155175" }}>

View File

@@ -4,13 +4,15 @@ import React from "react";
import { Box, Typography } from "@mui/material";
import MuiDatePicker from "@/core/components/DataTable/filter/fieldsType/CustomDate/MuiDatePicker";
function CustomDatePickerRange({ column, filterParameters, defaultFilterTranslation, setFieldValue, errors, touched }) {
function CustomDatePickerRange({ column, filterParameters, defaultFilterTranslation, handleChange, errors }) {
return (
<Box sx={{ display: "flex", gap: 1 }}>
<MuiDatePicker
name={`${column.id}.value[0]`}
value={filterParameters.value[0]}
setFieldValue={setFieldValue}
setFieldValue={(name, formattedDate) => {
handleChange({ ...filterParameters, value: [formattedDate, filterParameters.value[1]] });
}}
maxDate={filterParameters.value[1]}
placeholder={`از تاریخ`}
helperText={
@@ -22,10 +24,12 @@ function CustomDatePickerRange({ column, filterParameters, defaultFilterTranslat
<MuiDatePicker
name={`${column.id}.value[1]`}
value={filterParameters.value[1]}
setFieldValue={setFieldValue}
setFieldValue={(name, formattedDate) => {
handleChange({ ...filterParameters, value: [filterParameters.value[0], formattedDate] });
}}
minDate={filterParameters.value[0]}
placeholder={`تا تاریخ`}
helperText={errors?.[`${column.id}`]?.value ? errors?.[`${column.id}`]?.value : null}
helperText={errors?.[`${column.id}`]?.value ? errors?.[`${column.id}`]?.value.message : null}
error={Boolean(errors?.[`${column.id}`]?.value)}
/>
</Box>

View File

@@ -18,6 +18,7 @@ function MuiDatePicker({ value, setFieldValue, name, minDate, maxDate, helperTex
sx={{ width: "100%" }}
id={name}
name={name}
closeOnSelect={true}
aria-describedby="component-helper-text"
onChange={(value) => {
const date = new Date(value);

View File

@@ -1,9 +1,22 @@
"use client";
import { FormControl, FormHelperText, InputLabel, MenuItem, OutlinedInput, Select, Typography } from "@mui/material";
import { useEffect, useMemo } from "react";
function CustomSelect({ column, dependencyFieldValue, filterParameters, defaultFilterTranslation, setFieldValue }) {
const columnSelectOption = column.columnSelectOption(dependencyFieldValue);
function CustomSelect({ column, dependencyFieldValue, filterParameters, defaultFilterTranslation, handleChange }) {
const columnSelectOption = useMemo(() => {
return column?.columnSelectOption?.(dependencyFieldValue) || [];
}, [dependencyFieldValue]);
useEffect(() => {
const isValidValue = columnSelectOption.some((option) => option.value === filterParameters.value);
if (!isValidValue) {
handleChange({ ...filterParameters, value: "" });
}
}, [columnSelectOption, filterParameters.value, handleChange]);
const isValidValue = columnSelectOption.some((option) => option.value === filterParameters.value);
const selectedValue = isValidValue ? filterParameters.value : "";
return (
<FormControl fullWidth sx={{ marginY: 1 }}>
@@ -12,11 +25,9 @@ function CustomSelect({ column, dependencyFieldValue, filterParameters, defaultF
labelId={`label${column.id}`}
id={column.id}
name={`${column.id}.value`}
value={filterParameters.value}
value={selectedValue}
input={<OutlinedInput label={column.header} />}
onChange={(e) => {
setFieldValue(`${column.id}.value`, e.target.value);
}}
onChange={(e) => handleChange({ ...filterParameters, value: e.target.value })}
>
<MenuItem value="">{column.header}</MenuItem>
{columnSelectOption.map((option) => (

View File

@@ -12,7 +12,7 @@ import {
Typography,
} from "@mui/material";
function CustomSelectMultiple({ column, filterParameters, defaultFilterTranslation, setFieldValue }) {
function CustomSelectMultiple({ column, filterParameters, defaultFilterTranslation, handleChange }) {
const columnSelectOption = column.columnSelectOption;
const getLabelForValue = (value) => {
@@ -30,9 +30,7 @@ function CustomSelectMultiple({ column, filterParameters, defaultFilterTranslati
id={column.id}
value={filterParameters.value}
multiple
onChange={(e) => {
setFieldValue(`${column.id}.value`, e.target.value);
}}
onChange={(e) => handleChange({ ...filterParameters, value: e.target.value })}
renderValue={(selected) => (
<Box sx={{ display: "flex", flexWrap: "wrap", gap: 0.5 }}>
{selected.map((value) => (

View File

@@ -3,11 +3,11 @@ import FilterListIcon from "@mui/icons-material/FilterList";
function CustomTextField({
column,
filterParameters,
defaultFilterTranslation,
filterParameters,
handleOpenFilterBox,
handleChange,
handleBlur,
handleChange,
}) {
return (
<TextField
@@ -15,7 +15,7 @@ function CustomTextField({
name={column.id}
label={column.header}
value={filterParameters.value}
onChange={handleChange}
onChange={(e) => handleChange({ ...filterParameters, value: e.target.value })}
onBlur={handleBlur}
fullWidth
helperText={

View File

@@ -3,20 +3,21 @@ import FilterListIcon from "@mui/icons-material/FilterList";
function CustomTextFieldRange({
column,
filterParameters,
defaultFilterTranslation,
handleOpenFilterBox,
handleChange,
filterParameters,
handleBlur,
errors,
touched,
}) {
return (
<Box sx={{ display: "flex" }}>
<TextField
id={`${column.id}.value[0]`}
name={`${column.id}.value[0]`}
onChange={handleChange}
onChange={(e) =>
handleChange({ ...filterParameters, value: [e.target.value, filterParameters.value[1]] })
}
onBlur={handleBlur}
label={<Typography>از {column.header}</Typography>}
value={filterParameters.value[0]}
@@ -34,10 +35,12 @@ function CustomTextFieldRange({
<TextField
id={`${column.id}.value[1]`}
name={`${column.id}.value[1]`}
onChange={handleChange}
onChange={(e) =>
handleChange({ ...filterParameters, value: [filterParameters.value[0], e.target.value] })
}
onBlur={handleBlur}
error={touched?.[`${column.id}`]?.value && Boolean(errors?.[`${column.id}`]?.value)}
helperText={touched?.[`${column.id}`]?.value ? errors?.[`${column.id}`]?.value : null}
error={Boolean(errors?.[`${column.id}`]?.value)}
helperText={errors?.[`${column.id}`]?.value ? errors?.[`${column.id}`]?.value.message : null}
label={<Typography>تا {column.header}</Typography>}
value={filterParameters.value[1]}
fullWidth

View File

@@ -1,11 +1,12 @@
"use client";
import { IconButton, Tooltip } from "@mui/material";
import { IconButton, Tooltip, Badge } from "@mui/material";
import AutorenewIcon from "@mui/icons-material/Autorenew";
import useTableSetting from "@/lib/hooks/useTableSetting";
import useDataTable from "@/lib/hooks/useDataTable";
function ResetStorage({ user_id, page_name, table_name }) {
const { resetAction } = useTableSetting();
const { isDirty } = useDataTable();
const reset = () => {
resetAction(user_id, page_name, table_name);
};
@@ -13,7 +14,20 @@ function ResetStorage({ user_id, page_name, table_name }) {
return (
<Tooltip title="بازنشانی اطلاعات">
<IconButton onClick={reset} aria-label="reset table data">
<AutorenewIcon />
{isDirty ? (
<Badge
anchorOrigin={{
vertical: "top",
horizontal: "left",
}}
color="primary"
variant="dot"
>
<AutorenewIcon />
</Badge>
) : (
<AutorenewIcon />
)}
</IconButton>
</Tooltip>
);

View File

@@ -1,4 +1,4 @@
'use client'
"use client";
import { Backdrop, Box, Stack, styled, Typography, useTheme } from "@mui/material";
import SvgLoading from "@/core/components/svgs/SvgLoading";
import SvgError from "@/core/components/svgs/SvgError";

View File

@@ -0,0 +1,137 @@
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 dynamic from "next/dynamic";
import MapLoading from "@/core/components/MapLayer/Loading";
import HereIcon from "@/assets/images/examine_marker_active.png";
const MapLayer = dynamic(() => import("@/core/components/MapLayer"), {
loading: () => <MapLoading />,
ssr: false,
});
const createCustomIcon = (size, iconUrl, labelText) => {
if (labelText) {
return L.divIcon({
html: `
<div style="position: relative; display: inline-block; text-align: center;">
<img
src="${iconUrl}"
style="width: ${size[0]}px; height: ${size[1]}px;"
alt="icon"
/>
<div style="position: absolute; top: 100%; left: 50%; transform: translate(-50%, 0); color: black; font-size: 12px;">
${labelText}
</div>
</div>
`,
iconSize: size,
iconAnchor: [size[0] / 2, size[1]],
popupAnchor: [0, -size[1]],
});
}
return L.icon({
iconUrl: iconUrl,
iconSize: size,
iconAnchor: [size[0] / 2, size[1]],
popupAnchor: [0, -size[1]],
});
};
const MapInteraction = ({ setValue, startLat, startLng }) => {
const [isMarkerLocked, setIsMarkerLocked] = useState(!!(startLat && startLng)); // وضعیت قفل مارکر
const [markerPosition, setMarkerPosition] = useState(
startLat && startLng ? { lat: startLat, lng: startLng } : null
);
const markerRef = useRef();
const map = useMapEvents({
move(e) {
if (!isMarkerLocked && markerRef.current) {
markerRef.current.setLatLng(e.target.getCenter());
}
},
zoom(e) {
if (!isMarkerLocked && markerRef.current) {
markerRef.current.setLatLng(e.target.getCenter());
}
},
});
useEffect(() => {
if (startLat && startLng) {
const position = { lat: startLat, lng: startLng };
map.setView(position, 15);
}
}, [startLat, startLng, map]);
const handleMarkerClick = () => {
if (!isMarkerLocked) {
const center = map.getCenter();
setValue("start_point", { lat: center.lat.toString(), lng: center.lng.toString() });
setMarkerPosition({ lat: center.lat, lng: center.lng }); // به‌روزرسانی موقعیت مارکر
setIsMarkerLocked(true);
}
};
const handleUnlockMarker = () => {
setValue("start_point", null); // حذف مقدار قبلی
setIsMarkerLocked(false); // باز کردن قفل مارکر
setMarkerPosition(null); // تنظیم مارکر به مرکز نقشه
};
return (
<>
<Marker
position={markerPosition || map.getCenter()} // استفاده از موقعیت
ref={markerRef}
icon={createCustomIcon([35, 35], HereIcon.src, "اینجا")}
eventHandlers={{
click: handleMarkerClick,
}}
/>
{isMarkerLocked && (
<Box
sx={{
position: "absolute",
bottom: 10,
left: 10,
zIndex: 1000,
}}
>
<Button variant="contained" onClick={handleUnlockMarker}>
ویرایش
</Button>
</Box>
)}
</>
);
};
const MapInfoOneMarker = ({ setValue, errors, StartPoint = null }) => {
return (
<Box>
<Box sx={{ width: "100%", height: "400px", marginBottom: "16px" }}>
<MapLayer style={{ border: "1px solid #0009", borderRadius: "4px" }}>
<MapInteraction
setValue={setValue}
startLat={StartPoint ? StartPoint.lat : null}
startLng={StartPoint ? StartPoint.lng : null}
/>
</MapLayer>
</Box>
<Box display="flex" gap={2}>
{errors.start_point && (
<Typography color="error" variant="body2">
{errors.start_point.message}
</Typography>
)}
</Box>
</Box>
);
};
export default MapInfoOneMarker;

View File

@@ -0,0 +1,191 @@
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 dynamic from "next/dynamic";
import MapLoading from "@/core/components/MapLayer/Loading";
import EndIcon from "@/assets/images/examine_marker_active.png";
import StartIcon from "@/assets/images/examine_marker.png";
const MapLayer = dynamic(() => import("@/core/components/MapLayer"), {
loading: () => <MapLoading />,
ssr: false,
});
const createCustomIcon = (size, iconUrl, labelText) => {
if (labelText) {
return L.divIcon({
html: `
<div style="position: relative; display: inline-block; text-align: center;">
<img
src="${iconUrl}"
style="width: ${size[0]}px; height: ${size[1]}px;"
alt="icon"
/>
<div style="position: absolute; top: 100%; left: 50%; transform: translate(-50%, 0); color: black; font-size: 12px;">
${labelText}
</div>
</div>
`,
iconSize: size,
iconAnchor: [size[0] / 2, size[1]],
popupAnchor: [0, -size[1]],
});
}
return L.icon({
iconUrl: iconUrl,
iconSize: size,
iconAnchor: [size[0] / 2, size[1]],
popupAnchor: [0, -size[1]],
});
};
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 startRef = useRef();
const endRef = useRef();
const map = useMapEvents({
move(e) {
if (!isStartLocked && startRef.current) {
startRef.current.setLatLng(e.target.getCenter());
} else if (isStartLocked && !isEndLocked && endRef.current) {
endRef.current.setLatLng(e.target.getCenter());
}
},
zoom(e) {
if (!isStartLocked && startRef.current) {
startRef.current.setLatLng(e.target.getCenter());
} else if (isStartLocked && !isEndLocked && endRef.current) {
endRef.current.setLatLng(e.target.getCenter());
}
},
});
useEffect(() => {
if (startLat && endLng) {
map.fitBounds(
[
{ lat: startLat, lng: startLng },
{ lat: endLat, lng: endLng },
],
{ paddingTopLeft: [16, 16], paddingBottomRight: [16, 130] }
);
}
}, [startLat, map, endLng]);
const handleUnlockStart = () => {
setIsStartLocked(false);
setStartPosition(null);
setValue("start_point", null);
};
const handleUnlockEnd = () => {
setIsEndLocked(false);
setIsStartLocked(false);
setEndPosition(null);
setStartPosition(null);
setValue("end_point", "");
setValue("start_point", null);
};
return (
<>
<Marker
position={startPosition || map.getCenter()}
ref={startRef}
icon={createCustomIcon([35, 35], StartIcon.src, "نقطه شروع")}
eventHandlers={{
click: () => {
if (!isStartLocked) {
const center = map.getCenter();
setValue("start_point", { lat: center.lat.toString(), lng: center.lng.toString() });
setStartPosition({ lat: center.lat, lng: center.lng });
setIsStartLocked(true);
}
},
}}
/>
{isStartLocked && !isEndLocked && (
<Box
sx={{
position: "absolute",
bottom: 10,
left: 10,
zIndex: 1000,
}}
>
<Button variant={"contained"} onClick={handleUnlockStart}>
ویرایش نقطه شروع
</Button>
</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);
}
},
}}
/>
)}
{isEndLocked && (
<Box
sx={{
position: "absolute",
bottom: 10,
left: 10,
zIndex: 1000,
}}
>
<Button variant={"contained"} onClick={handleUnlockEnd}>
ویرایش نقاط
</Button>
</Box>
)}
</>
);
};
const MapInfoTwoMarker = ({ setValue, errors, StartPoint = null, EndPoint = null }) => {
return (
<Box>
<Box sx={{ width: "100%", height: "400px", marginBottom: "16px" }}>
<MapLayer style={{ border: "1px solid #0009", borderRadius: "4px" }}>
<MapInteraction
setValue={setValue}
startLat={StartPoint ? StartPoint.lat : null}
startLng={StartPoint ? StartPoint.lng : null}
endLat={EndPoint ? EndPoint.lat : null}
endLng={EndPoint ? EndPoint.lng : null}
/>
</MapLayer>
</Box>
<Box display="flex" gap={2}>
{errors.start_point && (
<Typography color="error" variant="body2">
{errors.start_point.message}
</Typography>
)}
{errors.end_point && (
<Typography color="error" variant="body2">
{errors.end_point.message}
</Typography>
)}
</Box>
</Box>
);
};
export default MapInfoTwoMarker;

View File

@@ -1,53 +1,70 @@
import {LocalizationProvider, MobileDateTimePicker} from "@mui/x-date-pickers";
import {AdapterDateFnsJalali} from "@mui/x-date-pickers/AdapterDateFnsJalali";
import moment from "jalali-moment";
import {faIR} from "@mui/x-date-pickers/locales";
import {FormControl, IconButton, InputAdornment, Stack} from "@mui/material";
import React from "react";
import { LocalizationProvider, MobileDatePicker } from "@mui/x-date-pickers";
import { AdapterDateFnsJalali } from "@mui/x-date-pickers/AdapterDateFnsJalali";
import { faIR } from "@mui/x-date-pickers/locales";
import { Box, FormHelperText, IconButton, InputAdornment } from "@mui/material";
import ClearIcon from "@mui/icons-material/Clear";
import moment from "jalali-moment";
export default function PickerWithButtonField({formik, name, value, error, touched, disabled}) {
function MuiDatePicker({ value, setFieldValue, name, minDate, maxDate, helperText, placeholder, error }) {
return (
<FormControl variant="outlined" fullWidth size="small" error={!!error}>
<Stack sx={{width: "100%"}} direction={"row"}>
<LocalizationProvider
dateAdapter={AdapterDateFnsJalali}
localeText={faIR.components.MuiLocalizationProvider.defaultProps.localeText}
>
<MobileDateTimePicker
ampm={false}
disableFuture
value={value ? new Date(value) : null}
onChange={(newValue) => {
const formattedDate = moment(newValue).locale("en").format("YYYY-MM-DD HH:mm");
formik.setFieldValue(name, formattedDate);
}}
slotProps={{
textField: {
placeholder:
name === "start_date" ? "تاریخ شروع را وارد کنید" : "تاریخ اتمام را وارد کنید",
variant: "outlined",
disabled,
fullWidth: true,
error: !!error,
helperText: touched && error,
size: "small",
InputProps: {
endAdornment: (
<InputAdornment position="end">
<IconButton
size="small"
onClick={() => formik.setFieldValue(name, null)}
>
<ClearIcon/>
</IconButton>
</InputAdornment>
),
},
<Box sx={{ display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center" }}>
<LocalizationProvider
dateAdapter={AdapterDateFnsJalali}
localeText={faIR.components.MuiLocalizationProvider.defaultProps.localeText}
>
<MobileDatePicker
value={value ? new Date(value) : null}
sx={{ width: "100%" }}
id={name}
name={name}
closeOnSelect
disableFuture
label={helperText}
aria-describedby="component-helper-text"
onChange={(value) => {
const date = new Date(value);
const formattedDate = moment(date).locale("en").format("YYYY-MM-DD");
setFieldValue(name, formattedDate);
}}
minDate={minDate ? new Date(minDate) : null}
maxDate={maxDate ? new Date(maxDate) : null}
slotProps={{
textField: {
error: error,
size: "small",
placeholder: placeholder,
InputProps: {
endAdornment: (
<InputAdornment position="end">
<IconButton
size="small"
onClick={(event) => {
event.stopPropagation();
setFieldValue(name, "");
}}
sx={{
color: "#bfbfbf",
"&:hover": {
backgroundColor: "rgba(189, 189, 189, 0.1)",
color: "#363434",
},
}}
>
<ClearIcon />
</IconButton>
</InputAdornment>
),
},
}}
/>
</LocalizationProvider>
</Stack>
</FormControl>
},
}}
/>
<FormHelperText id="component-helper-text" sx={{ color: error ? "#d32f2f" : "unset" }}>
{helperText ? helperText : ""}
</FormHelperText>
</LocalizationProvider>
</Box>
);
}
export default MuiDatePicker;

View File

@@ -0,0 +1,75 @@
import React from "react";
import { LocalizationProvider, MobileTimePicker } from "@mui/x-date-pickers";
import { AdapterDateFnsJalali } from "@mui/x-date-pickers/AdapterDateFnsJalali";
import { faIR } from "@mui/x-date-pickers/locales";
import { Box, FormHelperText, IconButton, InputAdornment } from "@mui/material";
import ClearIcon from "@mui/icons-material/Clear";
import { parseISO, format } from "date-fns";
function MuiTimePicker({ value, setFieldValue, name, minTime, maxTime, helperText, placeholder, error }) {
// Ensure value, minTime, maxTime are Date objects
const parsedValue = value ? (typeof value === "string" ? parseISO(value) : value) : null;
const parsedMinTime = minTime ? (typeof minTime === "string" ? parseISO(minTime) : minTime) : null;
const parsedMaxTime = maxTime && (typeof maxTime === "string" ? parseISO(maxTime) : null);
return (
<Box sx={{ display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center" }}>
<LocalizationProvider
dateAdapter={AdapterDateFnsJalali}
localeText={faIR.components.MuiLocalizationProvider.defaultProps.localeText}
>
<MobileTimePicker
value={parsedValue}
sx={{ width: "100%" }}
id={name}
closeOnSelect
label={helperText ? helperText : placeholder}
ampm={false}
timeSteps={{ hours: 1, minutes: 5 }}
views={["hours", "minutes"]}
name={name}
onChange={(newValue) => {
const formattedTime = newValue ? format(new Date(newValue), "HH:mm") : "";
setFieldValue(name, formattedTime);
}}
minTime={parsedMinTime}
maxTime={parsedMaxTime}
slotProps={{
textField: {
placeholder: placeholder,
size: "small",
error: error,
InputProps: {
endAdornment: (
<InputAdornment position="end">
<IconButton
size="small"
onClick={(event) => {
event.stopPropagation();
setFieldValue(name, "");
}}
sx={{
color: "#bfbfbf",
"&:hover": {
backgroundColor: "rgba(189, 189, 189, 0.1)",
color: "#363434",
},
}}
>
<ClearIcon />
</IconButton>
</InputAdornment>
),
},
},
}}
/>
<FormHelperText id="component-helper-text" sx={{ color: error ? "#d32f2f" : "unset" }}>
{helperText ? helperText : ""}
</FormHelperText>
</LocalizationProvider>
</Box>
);
}
export default MuiTimePicker;

View File

@@ -1,10 +1,12 @@
'use client'
import { Paper } from "@mui/material"
import LoadingHardPage from "./LoadingHardPage"
"use client";
import { Paper } from "@mui/material";
import LoadingHardPage from "./LoadingHardPage";
const PageLoading = () => {
return <Paper elevation={0} sx={{ position: "relative", overflowX: "hidden", width: '100%', height: '100%' }}>
<LoadingHardPage width={100} height={100} loading={true} sx={{ position: 'absolute' }} />
</Paper>
}
export default PageLoading
return (
<Paper elevation={0} sx={{ position: "relative", overflowX: "hidden", width: "100%", height: "100%" }}>
<LoadingHardPage width={100} height={100} loading={true} sx={{ position: "absolute" }} />
</Paper>
);
};
export default PageLoading;

View File

@@ -0,0 +1,90 @@
"use client";
import { Autocomplete, CircularProgress, TextField } from "@mui/material";
import { useEffect, useState } from "react";
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 [inputValue, setInputValue] = useState(""); // مدیریت مقدار تایپ‌شده
const [options, setOptions] = useState([]);
const [loading, setLoading] = useState(false);
const requestServer = useRequest();
const fetchRahdarCodes = (inputValue, controller) => {
const debouncer = debounce((query) => {
if ((query || "").length < 3) {
setOptions([]);
return;
}
setLoading(true);
requestServer(`${GET_RAHDARANS_LIST_SEARCH}?code=${query}`, "get", {
requestOptions: { signal: controller.signal },
})
.then((response) => {
setOptions(response.data.data || []);
})
.catch(() => {
setOptions([]);
})
.finally(() => {
setLoading(false);
});
}, 500);
debouncer(inputValue);
};
useEffect(() => {
const controller = new AbortController();
fetchRahdarCodes(inputValue, controller);
return () => controller.abort();
}, [inputValue]);
return (
<Autocomplete
multiple // قابلیت انتخاب چندگانه
value={rahdarsCode} // آرایه موارد انتخاب‌شده
size="small"
onChange={(event, newValue) => {
setRahdarsCode(newValue || []); // به‌روزرسانی موارد انتخاب‌شده
}}
inputValue={inputValue} // مقدار تایپ‌شده
onInputChange={(event, newInputValue) => {
setInputValue(newInputValue || ""); // به‌روزرسانی مقدار تایپ‌شده
}}
options={options} // گزینه‌های موجود
getOptionLabel={(option) => (typeof option === "string" ? option : `${option.code} - ${option.name}`)}
isOptionEqualToValue={(option, value) => {
if (!value) return false;
if (value === "") return option.code === "";
return option.code === (value?.code || value);
}}
loading={loading}
loadingText="درحال جستجوی کد راهدار"
noOptionsText={
(inputValue || "").length < 3 ? "حداقل سه رقم از کد راهدار را وارد کنید" : "کد راهداری یافت نشد"
}
fullWidth
renderInput={(params) => (
<TextField
{...params}
label="کد راهداران"
fullWidth
error={!!error}
helperText={error?.message}
InputProps={{
...params.InputProps,
endAdornment: (
<>
{loading ? <CircularProgress size="25px" color="inherit" /> : null}
{params.InputProps.endAdornment}
</>
),
}}
/>
)}
/>
);
};
export default RahdarCode;

View File

@@ -0,0 +1,181 @@
"use client";
import { Marker, useMap } from "react-leaflet";
import { useEffect } from "react";
import L from "leaflet";
import AzmayeshActiveIcon from "@/assets/images/examine_marker_active.png";
import { Box, FormControl, InputAdornment, InputLabel, OutlinedInput, Stack, useMediaQuery } from "@mui/material";
import VerifiedIcon from "@mui/icons-material/Verified";
import { useTheme } from "@emotion/react";
const ShowLocationMarker = ({ start_lat, start_lng, end_lat, end_lng }) => {
const map = useMap();
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
const defaultIconSize = [35, 35];
const createCustomIcon = (size, iconUrl) => {
return L.icon({
iconUrl: iconUrl,
iconSize: size,
iconAnchor: [size[0] / 2, size[1]],
popupAnchor: [0, -size[1]],
});
};
useEffect(() => {
if (start_lat && end_lng) {
map.fitBounds(
[
{ lat: start_lat, lng: start_lng },
{ lat: end_lat, lng: end_lng },
],
{ paddingTopLeft: [16, 16], paddingBottomRight: [16, 130] }
);
} else if (start_lat) {
map.setView({ lat: start_lat, lng: start_lng }, 15);
}
}, [start_lat, start_lng, map, end_lng, end_lat]);
return (
<>
{start_lat && start_lng && (
<Marker
position={{ lat: start_lat, lng: start_lng }}
icon={createCustomIcon(defaultIconSize, AzmayeshActiveIcon.src)}
/>
)}
{end_lat && end_lng && (
<Marker
position={{ lat: end_lat, lng: end_lng }}
icon={createCustomIcon(defaultIconSize, AzmayeshActiveIcon.src)}
/>
)}
<Box
sx={{
zIndex: "400",
position: "absolute",
background: "#ffffff94",
p: 2,
borderRadius: "10px",
boxShadow: "rgba(0, 0, 0, 0.24) 0px 3px 8px",
bottom: "5px",
left: "5px",
right: "5px",
}}
>
<Stack direction={end_lat && end_lng ? "row" : "column"} spacing={end_lat && end_lng ? 0.5 : 0}>
{start_lat && start_lng && (
<Stack spacing={1}>
<FormControl size="small" variant="outlined">
<InputLabel sx={{ color: "success.main" }} htmlFor="start_lat">
{end_lat && end_lng ? "طول جغرافیایی شروع" : "طول جغرافیایی"}
</InputLabel>
<OutlinedInput
id="start_lat"
type="text"
size="small"
readOnly
fullWidth
sx={{
"& .MuiOutlinedInput-notchedOutline": {
borderColor: "success.main",
},
color: "success.main",
}}
value={start_lat}
endAdornment={
<InputAdornment position="end">
<VerifiedIcon color="success" />
</InputAdornment>
}
label={end_lat && end_lng ? "طول جغرافیایی شروع" : "طول جغرافیایی"}
/>
</FormControl>
<FormControl size="small" variant="outlined">
<InputLabel sx={{ color: "success.main" }} htmlFor="start_lng">
{end_lat && end_lng ? "عرض جغرافیایی شروع" : "عرض جغرافیایی"}
</InputLabel>
<OutlinedInput
id="start_lng"
type="text"
size="small"
readOnly
fullWidth
sx={{
"& .MuiOutlinedInput-notchedOutline": {
borderColor: "success.main",
},
color: "success.main",
}}
value={start_lng}
endAdornment={
<InputAdornment position="end">
<VerifiedIcon color="success" />
</InputAdornment>
}
label={end_lat && end_lng ? "عرض جغرافیایی شروع" : "عرض جغرافیایی"}
/>
</FormControl>
</Stack>
)}
{end_lat && end_lng && (
<Stack spacing={1}>
<FormControl size="small" variant="outlined">
<InputLabel sx={{ color: "success.main" }} htmlFor="end_lat">
طول جغرافیایی پایان
</InputLabel>
<OutlinedInput
id="end_lat"
type="text"
size="small"
readOnly
sx={{
"& .MuiOutlinedInput-notchedOutline": {
borderColor: "success.main",
},
color: "success.main",
}}
value={end_lat}
endAdornment={
<InputAdornment position="end">
<VerifiedIcon color="success" />
</InputAdornment>
}
label="طول جغرافیایی پایان"
/>
</FormControl>
<FormControl size="small" variant="outlined">
<InputLabel sx={{ color: "success.main" }} htmlFor="end_lng">
عرض جغرافیایی پایان
</InputLabel>
<OutlinedInput
id="end_lng"
type="text"
size="small"
readOnly
sx={{
"& .MuiOutlinedInput-notchedOutline": {
borderColor: "success.main",
},
color: "success.main",
}}
value={end_lng}
endAdornment={
<InputAdornment position="end">
<VerifiedIcon color="success" />
</InputAdornment>
}
label="عرض جغرافیایی پایان"
/>
</FormControl>
</Stack>
)}
</Stack>
</Box>
</>
);
};
export default ShowLocationMarker;

View File

@@ -61,6 +61,35 @@ export const errorUnauthorizedToast = (toastContainer) =>
draggable: true,
}
);
export const errorAccessDeniedToast = (message, toastContainer) =>
toast.error(
() => (
<Box
sx={{
display: "flex",
flexDirection: "column",
alignItems: "start",
justifyContent: "start",
}}
>
<Box sx={{ display: "flex", alignItems: "center" }}>
<Dangerous color="error" sx={{ mr: 1.6 }} />
<Box sx={{ display: "flex", flexDirection: "column" }}>
<Typography variant="caption">{message || "Access Denied"}</Typography>
</Box>
</Box>
</Box>
),
{
icon: false,
containerId: toastContainer,
autoClose: 3000,
hideProgressBar: true,
pauseOnHover: true,
closeOnClick: false,
draggable: true,
}
);
export const errorLogicToast = (message, toastContainer) =>
toast.error(

View File

@@ -0,0 +1,94 @@
import { Box, Button, Paper, Typography } from "@mui/material";
import AddIcon from "@mui/icons-material/Add";
import DeleteForeverIcon from "@mui/icons-material/DeleteForever";
import { useRef } from "react";
const UploadSystem = ({ selectedImage, handleUploadChange, imageSize, fileType, showAddIcon }) => {
const fileInputRef = useRef(null);
const handleClick = () => {
fileInputRef.current.click();
};
return (
<Box sx={{ width: "100%", my: 1 }}>
{showAddIcon ? (
<Box
sx={{
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
border: "1px solid #b3b3b3",
borderRadius: "10px",
cursor: "pointer",
padding: "5px",
height: imageSize[1],
}}
onClick={handleClick}
>
<AddIcon sx={{ fontSize: "2rem", color: "#a19d9d" }} />
<Typography
variant="subtitle2"
sx={{
fontWeight: 600,
fontSize: "0.8rem",
color: "#a19d9d",
mt: 1,
}}
textAlign="center"
>
فرمت قابل قبول : png, jpg
<br />
حداکثر 3Mb
</Typography>
</Box>
) : (
<>
{fileType && fileType.startsWith("image/") && (
<Box
width="100%"
height={imageSize[1]}
sx={{
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
cursor: "pointer",
objectFit: "contain",
border: "1px solid #b3b3b3",
borderRadius: "10px",
padding: "5px",
overflow: "hidden",
}}
onClick={handleClick}
>
<Paper
elevation={0}
sx={{
width: "100%",
height: "100%",
display: "flex",
alignItems: "center",
justifyContent: "center",
backgroundSize: "contain",
backgroundRepeat: "no-repeat",
backgroundPosition: "center",
backgroundImage: `url(${selectedImage})`,
}}
/>
</Box>
)}
</>
)}
<input
type="file"
accept="image/*"
style={{ display: "none" }}
onChange={handleUploadChange}
ref={fileInputRef}
/>
</Box>
);
};
export default UploadSystem;

View File

@@ -1,6 +1,7 @@
"use client";
import { toast } from "react-toastify";
import {
errorAccessDeniedToast,
errorClientToast,
errorLogicToast,
errorServerToast,
@@ -22,6 +23,9 @@ const errorClient = (response, notification, toastContainer, logout) => {
logout();
if (notification) errorUnauthorizedToast(toastContainer);
break;
case 403:
if (notification) errorAccessDeniedToast(response.data.message, toastContainer);
break;
case 422:
if ("type" in response.data) {
if (Array.isArray(response.data.message)) {

View File

@@ -108,7 +108,7 @@ export const pageMenu = [
id: "roadItemManagmentSupervisorCartable",
label: "کارتابل",
type: "page",
route: process.env.NEXT_PUBLIC_API_URL + "/v2/road_items/supervisor/cartable",
route: "/dashboard/road-items/supervisor",
permissions: [
"show-road-item-supervise-cartable",
"show-road-item-supervise-cartable-province",
@@ -135,7 +135,7 @@ export const pageMenu = [
id: "roadItemManagmentOparationCartable",
label: "کارتابل",
type: "page",
route: process.env.NEXT_PUBLIC_API_URL + "/v2/road_items/operator/cartable",
route: "/dashboard/road-items/operator",
permissions: ["create-road-item"],
},
],

View File

@@ -27,11 +27,25 @@ export const DELETE_SAMPLE_LIST = api + "/api/v3/azmayesh_samples/delete";
export const DELETE_AZMAYESH_TYPE_LIST = api + "/api/v3/azmayesh_types/delete";
export const ADD_AZMAYESH_TYPE = api + "/api/v3/azmayesh_types/store";
export const UPDATE_AZMAYESH_TYPE = api + "/api/v3/azmayesh_types/update";
export const GET_OPERATOR_LIST = "/v3/api/fake-operator-list";
export const EXPORT_OPERATOR_LIST = "https://rms.rmto.ir/v2/road_patrols/operator/cartable/report";
export const GET_SUPERVISOR_LIST = "/v3/api/fake-supervisor-list";
export const EXPORT_SUPERVISOR_LIST = "https://rms.rmto.ir/v2/road_patrols/supervisor/cartable/report";
////**** mohammad check here ****////
export const GET_OTP_TOKEN = "https://rms.rmto.ir/v2/get_otp_token";
export const VERIFY_OTP = "https://rms.rmto.ir/v2/verify_otp";
//road patrol
export const GET_ROAD_PATROL_OPERATOR_LIST = "/v3/api/fake-road-patrol/operator";
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 EXPORT_ROAD_PATROL_SUPERVISOR_LIST = "https://rms.rmto.ir/v2/road_patrols/supervisor/cartable/report";
// 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 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 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";
export const REJECT_BY_SUPERVISOR = api + "/api/v3/road_items/verify_by_supervisor";
export const DELETE_BY_SUPERVISOR = api + "/api/v3/road_items/delete";
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";