Feature/amiriis missions
This commit is contained in:
@@ -1,39 +1,39 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState, memo } from "react";
|
||||
import useRequest from "@/lib/hooks/useRequest";
|
||||
import { ACTIVITY_LOG } from "@/core/utils/routes";
|
||||
import NumberField from "@/core/components/NumberField";
|
||||
|
||||
const ActivityCodeLog = memo(({ activity_code }) => {
|
||||
const requestServer = useRequest({ notificationShow: false });
|
||||
const [hasLogged, setHasLogged] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!activity_code || hasLogged) return;
|
||||
|
||||
const controller = new AbortController();
|
||||
|
||||
const fetchActivityLog = async () => {
|
||||
try {
|
||||
await requestServer(ACTIVITY_LOG, "post", {
|
||||
data: { activityCode: activity_code },
|
||||
signal: controller.signal,
|
||||
});
|
||||
setHasLogged(true);
|
||||
} catch (error) {}
|
||||
};
|
||||
|
||||
fetchActivityLog();
|
||||
|
||||
return () => {
|
||||
controller.abort();
|
||||
};
|
||||
}, [activity_code, hasLogged, requestServer]);
|
||||
|
||||
return null;
|
||||
});
|
||||
ActivityCodeLog.displayName = "ActivityCodeLog";
|
||||
|
||||
ActivityCodeLog.displayName = "ActivityCodeLog";
|
||||
export default ActivityCodeLog;
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState, memo } from "react";
|
||||
import useRequest from "@/lib/hooks/useRequest";
|
||||
import { ACTIVITY_LOG } from "@/core/utils/routes";
|
||||
import NumberField from "@/core/components/NumberField";
|
||||
|
||||
const ActivityCodeLog = memo(({ activity_code }) => {
|
||||
const requestServer = useRequest({ notificationShow: false });
|
||||
const [hasLogged, setHasLogged] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!activity_code || hasLogged) return;
|
||||
|
||||
const controller = new AbortController();
|
||||
|
||||
const fetchActivityLog = async () => {
|
||||
try {
|
||||
await requestServer(ACTIVITY_LOG, "post", {
|
||||
data: { activityCode: activity_code },
|
||||
signal: controller.signal,
|
||||
});
|
||||
setHasLogged(true);
|
||||
} catch (error) {}
|
||||
};
|
||||
|
||||
fetchActivityLog();
|
||||
|
||||
return () => {
|
||||
controller.abort();
|
||||
};
|
||||
}, [activity_code, hasLogged, requestServer]);
|
||||
|
||||
return null;
|
||||
});
|
||||
ActivityCodeLog.displayName = "ActivityCodeLog";
|
||||
|
||||
ActivityCodeLog.displayName = "ActivityCodeLog";
|
||||
export default ActivityCodeLog;
|
||||
|
||||
@@ -1,52 +1,52 @@
|
||||
"use client";
|
||||
|
||||
import { Box, Chip, Divider, Grid, Typography } from "@mui/material";
|
||||
import QrCode2Icon from "@mui/icons-material/QrCode2";
|
||||
import HomeWorkIcon from "@mui/icons-material/HomeWork";
|
||||
import DomainIcon from "@mui/icons-material/Domain";
|
||||
import CottageIcon from "@mui/icons-material/Cottage";
|
||||
import AccountTreeIcon from "@mui/icons-material/AccountTree";
|
||||
import HolidayVillageIcon from "@mui/icons-material/HolidayVillage";
|
||||
import CalendarMonthIcon from "@mui/icons-material/CalendarMonth";
|
||||
import FaxIcon from "@mui/icons-material/Fax";
|
||||
import FullscreenIcon from "@mui/icons-material/Fullscreen";
|
||||
import ArchitectureIcon from "@mui/icons-material/Architecture";
|
||||
import Diversity2Icon from "@mui/icons-material/Diversity2";
|
||||
import SubtitlesIcon from "@mui/icons-material/Subtitles";
|
||||
import FingerprintIcon from "@mui/icons-material/Fingerprint";
|
||||
|
||||
const findItem = {
|
||||
id: { title: "کد یکتا", icon: <FingerprintIcon /> },
|
||||
shomareh_darkhast: { title: "شماره درخواست", icon: <QrCode2Icon /> },
|
||||
ostan: { title: "استان", icon: <HomeWorkIcon /> },
|
||||
shahr: { title: "شهر", icon: <DomainIcon /> },
|
||||
shahrestan: { title: "شهرستان", icon: <CottageIcon /> },
|
||||
bakhsh: { title: "بخش", icon: <AccountTreeIcon /> },
|
||||
roosta: { title: "روستا", icon: <HolidayVillageIcon /> },
|
||||
tarikh_darkhast: { title: "تاریخ درخواست", icon: <CalendarMonthIcon /> },
|
||||
sazman: { title: "سازمان", icon: <FaxIcon /> },
|
||||
masahat_zamin: { title: "مساحت زمین", icon: <FullscreenIcon /> },
|
||||
masahat_tarh: { title: "مساحت طرح", icon: <ArchitectureIcon /> },
|
||||
gorooh_tarh: { title: "گروه طرح", icon: <Diversity2Icon /> },
|
||||
onvane_tarh: { title: "عنوان طرح", icon: <SubtitlesIcon /> },
|
||||
};
|
||||
|
||||
const DetailItem = ({ item }) => {
|
||||
const currentItem = findItem[item.key];
|
||||
if (!currentItem) return null;
|
||||
|
||||
return (
|
||||
<Grid item xs={12} md={6}>
|
||||
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||
<Chip
|
||||
icon={currentItem?.icon}
|
||||
sx={{ fontSize: "12px", fontWeight: 500, pl: "5px" }}
|
||||
label={currentItem?.title}
|
||||
/>
|
||||
<Divider sx={{ flex: 1, mx: 2 }} />
|
||||
<Typography>{item.value}</Typography>
|
||||
</Box>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
export default DetailItem;
|
||||
"use client";
|
||||
|
||||
import { Box, Chip, Divider, Grid, Typography } from "@mui/material";
|
||||
import QrCode2Icon from "@mui/icons-material/QrCode2";
|
||||
import HomeWorkIcon from "@mui/icons-material/HomeWork";
|
||||
import DomainIcon from "@mui/icons-material/Domain";
|
||||
import CottageIcon from "@mui/icons-material/Cottage";
|
||||
import AccountTreeIcon from "@mui/icons-material/AccountTree";
|
||||
import HolidayVillageIcon from "@mui/icons-material/HolidayVillage";
|
||||
import CalendarMonthIcon from "@mui/icons-material/CalendarMonth";
|
||||
import FaxIcon from "@mui/icons-material/Fax";
|
||||
import FullscreenIcon from "@mui/icons-material/Fullscreen";
|
||||
import ArchitectureIcon from "@mui/icons-material/Architecture";
|
||||
import Diversity2Icon from "@mui/icons-material/Diversity2";
|
||||
import SubtitlesIcon from "@mui/icons-material/Subtitles";
|
||||
import FingerprintIcon from "@mui/icons-material/Fingerprint";
|
||||
|
||||
const findItem = {
|
||||
id: { title: "کد یکتا", icon: <FingerprintIcon /> },
|
||||
shomareh_darkhast: { title: "شماره درخواست", icon: <QrCode2Icon /> },
|
||||
ostan: { title: "استان", icon: <HomeWorkIcon /> },
|
||||
shahr: { title: "شهر", icon: <DomainIcon /> },
|
||||
shahrestan: { title: "شهرستان", icon: <CottageIcon /> },
|
||||
bakhsh: { title: "بخش", icon: <AccountTreeIcon /> },
|
||||
roosta: { title: "روستا", icon: <HolidayVillageIcon /> },
|
||||
tarikh_darkhast: { title: "تاریخ درخواست", icon: <CalendarMonthIcon /> },
|
||||
sazman: { title: "سازمان", icon: <FaxIcon /> },
|
||||
masahat_zamin: { title: "مساحت زمین", icon: <FullscreenIcon /> },
|
||||
masahat_tarh: { title: "مساحت طرح", icon: <ArchitectureIcon /> },
|
||||
gorooh_tarh: { title: "گروه طرح", icon: <Diversity2Icon /> },
|
||||
onvane_tarh: { title: "عنوان طرح", icon: <SubtitlesIcon /> },
|
||||
};
|
||||
|
||||
const DetailItem = ({ item }) => {
|
||||
const currentItem = findItem[item.key];
|
||||
if (!currentItem) return null;
|
||||
|
||||
return (
|
||||
<Grid item xs={12} md={6}>
|
||||
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||
<Chip
|
||||
icon={currentItem?.icon}
|
||||
sx={{ fontSize: "12px", fontWeight: 500, pl: "5px" }}
|
||||
label={currentItem?.title}
|
||||
/>
|
||||
<Divider sx={{ flex: 1, mx: 2 }} />
|
||||
<Typography>{item.value}</Typography>
|
||||
</Box>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
export default DetailItem;
|
||||
|
||||
@@ -1,39 +1,39 @@
|
||||
"use client";
|
||||
|
||||
import { Box } from "@mui/material";
|
||||
import dynamic from "next/dynamic";
|
||||
import MapLoading from "@/core/components/MapLayer/Loading";
|
||||
import FlyToPolyGon from "./FlyToPolygon";
|
||||
|
||||
const MapLayer = dynamic(() => import("@/core/components/MapLayer"), {
|
||||
loading: () => <MapLoading />,
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
const DetailMap = ({ data }) => {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
height: "200px",
|
||||
border: "1px solid #e1e1e1",
|
||||
borderRadius: 2,
|
||||
my: 3,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
height: "100%",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<MapLayer>
|
||||
<FlyToPolyGon polygon={data.polygon} />
|
||||
</MapLayer>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
export default DetailMap;
|
||||
"use client";
|
||||
|
||||
import { Box } from "@mui/material";
|
||||
import dynamic from "next/dynamic";
|
||||
import MapLoading from "@/core/components/MapLayer/Loading";
|
||||
import FlyToPolyGon from "./FlyToPolygon";
|
||||
|
||||
const MapLayer = dynamic(() => import("@/core/components/MapLayer"), {
|
||||
loading: () => <MapLoading />,
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
const DetailMap = ({ data }) => {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
height: "200px",
|
||||
border: "1px solid #e1e1e1",
|
||||
borderRadius: 2,
|
||||
my: 3,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
height: "100%",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<MapLayer>
|
||||
<FlyToPolyGon polygon={data.polygon} />
|
||||
</MapLayer>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
export default DetailMap;
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
"use client";
|
||||
|
||||
import { Polygon, useMap } from "react-leaflet";
|
||||
import { useEffect } from "react";
|
||||
|
||||
const purpleOptions = { color: "purple" };
|
||||
|
||||
const FlyToPolygon = ({ polygon }) => {
|
||||
const map = useMap();
|
||||
|
||||
useEffect(() => {
|
||||
if (map) {
|
||||
const leafletPolygon = L.polygon(polygon);
|
||||
const bounds = leafletPolygon.getBounds();
|
||||
map.flyToBounds(bounds, { padding: [50, 50] });
|
||||
}
|
||||
}, [map, polygon]);
|
||||
|
||||
return <Polygon pathOptions={purpleOptions} positions={polygon} />;
|
||||
};
|
||||
|
||||
export default FlyToPolygon;
|
||||
"use client";
|
||||
|
||||
import { Polygon, useMap } from "react-leaflet";
|
||||
import { useEffect } from "react";
|
||||
|
||||
const purpleOptions = { color: "purple" };
|
||||
|
||||
const FlyToPolygon = ({ polygon }) => {
|
||||
const map = useMap();
|
||||
|
||||
useEffect(() => {
|
||||
if (map) {
|
||||
const leafletPolygon = L.polygon(polygon);
|
||||
const bounds = leafletPolygon.getBounds();
|
||||
map.flyToBounds(bounds, { padding: [50, 50] });
|
||||
}
|
||||
}, [map, polygon]);
|
||||
|
||||
return <Polygon pathOptions={purpleOptions} positions={polygon} />;
|
||||
};
|
||||
|
||||
export default FlyToPolygon;
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
"use client";
|
||||
|
||||
import { Polyline, useMap } from "react-leaflet";
|
||||
import { useEffect } from "react";
|
||||
const purpleOptions = { color: "purple" };
|
||||
|
||||
const FlyToPolyline = ({ polyline }) => {
|
||||
const map = useMap();
|
||||
|
||||
useEffect(() => {
|
||||
if (map) {
|
||||
const leafletPolyline = L.polyline(polyline); // Create a Leaflet polyline
|
||||
const bounds = leafletPolyline.getBounds(); // Get bounds from the polyline
|
||||
map.flyToBounds(bounds, { padding: [50, 50] }); // Fly to the polyline bounds
|
||||
}
|
||||
}, [map, polyline]);
|
||||
|
||||
return <Polyline pathOptions={purpleOptions} positions={polyline} />; // Render the polyline
|
||||
};
|
||||
|
||||
export default FlyToPolyline;
|
||||
"use client";
|
||||
|
||||
import { Polyline, useMap } from "react-leaflet";
|
||||
import { useEffect } from "react";
|
||||
const purpleOptions = { color: "purple" };
|
||||
|
||||
const FlyToPolyline = ({ polyline }) => {
|
||||
const map = useMap();
|
||||
|
||||
useEffect(() => {
|
||||
if (map) {
|
||||
const leafletPolyline = L.polyline(polyline); // Create a Leaflet polyline
|
||||
const bounds = leafletPolyline.getBounds(); // Get bounds from the polyline
|
||||
map.flyToBounds(bounds, { padding: [50, 50] }); // Fly to the polyline bounds
|
||||
}
|
||||
}, [map, polyline]);
|
||||
|
||||
return <Polyline pathOptions={purpleOptions} positions={polyline} />; // Render the polyline
|
||||
};
|
||||
|
||||
export default FlyToPolyline;
|
||||
|
||||
@@ -1,57 +1,57 @@
|
||||
"use client";
|
||||
|
||||
import { Box, Button, Chip, Divider, Grid, Typography } from "@mui/material";
|
||||
import AttachFileIcon from "@mui/icons-material/AttachFile";
|
||||
import DownloadingIcon from "@mui/icons-material/Downloading";
|
||||
import DetailItem from "./DetailItem";
|
||||
import DetailMap from "./DetailMap";
|
||||
import ContactMailIcon from "@mui/icons-material/ContactMail";
|
||||
|
||||
const ApplicantRequestDetail = ({ data }) => {
|
||||
return (
|
||||
<>
|
||||
<Divider sx={{ my: 3 }}>
|
||||
<Chip color="primary" sx={{ mx: 1, fontSize: "13px", fontWeight: 500 }} label="اطلاعات طرح متقاضی" />
|
||||
</Divider>
|
||||
<Grid container columnSpacing={3} rowSpacing={1.2}>
|
||||
{Object.entries(data).map(([key, value]) => (
|
||||
<DetailItem key={key} item={{ key: key, value: value }} />
|
||||
))}
|
||||
<Grid item xs={12} md={6}>
|
||||
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||
<Chip
|
||||
icon={<AttachFileIcon />}
|
||||
sx={{ fontSize: "12px", fontWeight: 500, pl: "5px" }}
|
||||
label="فایل مصوب"
|
||||
/>
|
||||
<Divider sx={{ flex: 1, mx: 2 }} />
|
||||
<Button
|
||||
variant="contained"
|
||||
color="success"
|
||||
startIcon={<DownloadingIcon />}
|
||||
sx={{ borderRadius: 5 }}
|
||||
>
|
||||
دریافت
|
||||
</Button>
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid container columnSpacing={2} sx={{ mt: 2, justifyContent: "center" }}>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Divider>
|
||||
<Chip
|
||||
sx={{ fontSize: "12px", fontWeight: 500, pl: "5px" }}
|
||||
icon={<ContactMailIcon />}
|
||||
label="آدرس"
|
||||
/>
|
||||
</Divider>
|
||||
<Box sx={{ display: "flex", justifyContent: "center", my: 1 }}>
|
||||
<Typography>{data.address}</Typography>
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<DetailMap data={data} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default ApplicantRequestDetail;
|
||||
"use client";
|
||||
|
||||
import { Box, Button, Chip, Divider, Grid, Typography } from "@mui/material";
|
||||
import AttachFileIcon from "@mui/icons-material/AttachFile";
|
||||
import DownloadingIcon from "@mui/icons-material/Downloading";
|
||||
import DetailItem from "./DetailItem";
|
||||
import DetailMap from "./DetailMap";
|
||||
import ContactMailIcon from "@mui/icons-material/ContactMail";
|
||||
|
||||
const ApplicantRequestDetail = ({ data }) => {
|
||||
return (
|
||||
<>
|
||||
<Divider sx={{ my: 3 }}>
|
||||
<Chip color="primary" sx={{ mx: 1, fontSize: "13px", fontWeight: 500 }} label="اطلاعات طرح متقاضی" />
|
||||
</Divider>
|
||||
<Grid container columnSpacing={3} rowSpacing={1.2}>
|
||||
{Object.entries(data).map(([key, value]) => (
|
||||
<DetailItem key={key} item={{ key: key, value: value }} />
|
||||
))}
|
||||
<Grid item xs={12} md={6}>
|
||||
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||
<Chip
|
||||
icon={<AttachFileIcon />}
|
||||
sx={{ fontSize: "12px", fontWeight: 500, pl: "5px" }}
|
||||
label="فایل مصوب"
|
||||
/>
|
||||
<Divider sx={{ flex: 1, mx: 2 }} />
|
||||
<Button
|
||||
variant="contained"
|
||||
color="success"
|
||||
startIcon={<DownloadingIcon />}
|
||||
sx={{ borderRadius: 5 }}
|
||||
>
|
||||
دریافت
|
||||
</Button>
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid container columnSpacing={2} sx={{ mt: 2, justifyContent: "center" }}>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Divider>
|
||||
<Chip
|
||||
sx={{ fontSize: "12px", fontWeight: 500, pl: "5px" }}
|
||||
icon={<ContactMailIcon />}
|
||||
label="آدرس"
|
||||
/>
|
||||
</Divider>
|
||||
<Box sx={{ display: "flex", justifyContent: "center", my: 1 }}>
|
||||
<Typography>{data.address}</Typography>
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<DetailMap data={data} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default ApplicantRequestDetail;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { Box, Stack } from "@mui/material";
|
||||
import BlinkingCircle from "./BlinkingCircle";
|
||||
|
||||
const BlinkingCell = ({ visible = false, value }) => {
|
||||
return (
|
||||
<Stack direction={"row"} alignItems={"center"} spacing={0.5}>
|
||||
<Box>{value}</Box>
|
||||
{visible && <BlinkingCircle />}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
export default BlinkingCell;
|
||||
import { Box, Stack } from "@mui/material";
|
||||
import BlinkingCircle from "./BlinkingCircle";
|
||||
|
||||
const BlinkingCell = ({ visible = false, value }) => {
|
||||
return (
|
||||
<Stack direction={"row"} alignItems={"center"} spacing={0.5}>
|
||||
<Box>{value}</Box>
|
||||
{visible && <BlinkingCircle />}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
export default BlinkingCell;
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
import { keyframes } from "@emotion/react";
|
||||
import { Box } from "@mui/material";
|
||||
|
||||
const blink = keyframes`
|
||||
0% { opacity: 1; }
|
||||
50% { opacity: 0.3; }
|
||||
100% { opacity: 1; }
|
||||
`;
|
||||
|
||||
const BlinkingCircle = () => {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
width: 6,
|
||||
height: 6,
|
||||
borderRadius: "50%",
|
||||
backgroundColor: (theme) => theme.palette.warning.main,
|
||||
animation: `${blink} 1.3s infinite ease-in-out`,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default BlinkingCircle;
|
||||
import { keyframes } from "@emotion/react";
|
||||
import { Box } from "@mui/material";
|
||||
|
||||
const blink = keyframes`
|
||||
0% { opacity: 1; }
|
||||
50% { opacity: 0.3; }
|
||||
100% { opacity: 1; }
|
||||
`;
|
||||
|
||||
const BlinkingCircle = () => {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
width: 6,
|
||||
height: 6,
|
||||
borderRadius: "50%",
|
||||
backgroundColor: (theme) => theme.palette.warning.main,
|
||||
animation: `${blink} 1.3s infinite ease-in-out`,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default BlinkingCircle;
|
||||
|
||||
@@ -1,103 +1,103 @@
|
||||
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_CAR_LIST_SEARCH } from "@/core/utils/routes";
|
||||
|
||||
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();
|
||||
|
||||
useEffect(() => {
|
||||
const fetchCarCodes = (inputValue, controller) => {
|
||||
const debouncer = debounce((query) => {
|
||||
if (!query || query?.length < 3) {
|
||||
setOptions([]);
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
requestServer(`${GET_CAR_LIST_SEARCH}?machine_code=${query}`, "get", {
|
||||
requestOptions: { signal: controller.signal },
|
||||
})
|
||||
.then((response) => {
|
||||
const combinedArray = carCode ? [...carCode, ...response.data.data] : [...response.data.data];
|
||||
const uniqueArray = Array.from(new Map(combinedArray.map((item) => [item.id, item])).values());
|
||||
setOptions(uniqueArray || []);
|
||||
})
|
||||
.catch(() => {
|
||||
setOptions([]);
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
}, 500);
|
||||
debouncer(inputValue);
|
||||
};
|
||||
|
||||
const controller = new AbortController();
|
||||
fetchCarCodes(inputValue, controller);
|
||||
return () => controller.abort();
|
||||
}, [inputValue]);
|
||||
|
||||
return (
|
||||
<Autocomplete
|
||||
multiple={multiple}
|
||||
value={carCode}
|
||||
size="small"
|
||||
onChange={(event, newValue) => {
|
||||
setCarCode(multiple ? newValue || [] : newValue || null);
|
||||
}}
|
||||
inputValue={inputValue}
|
||||
onInputChange={(event, newInputValue) => {
|
||||
setInputValue(newInputValue || "");
|
||||
}}
|
||||
options={options}
|
||||
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;
|
||||
if (value === "") return option.machine_code === "";
|
||||
return option.machine_code === (value?.machine_code || value);
|
||||
}}
|
||||
loading={loading}
|
||||
loadingText="درحال جستجوی کد خودرو"
|
||||
noOptionsText={inputValue?.length < 3 ? "حداقل سه رقم از کد خودرو را وارد کنید" : "کد خودرویی یافت نشد"}
|
||||
fullWidth
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
label={multiple ? "خودرو ها" : "خودرو"}
|
||||
fullWidth
|
||||
placeholder={multiple ? "کد خودرو ها را وارد کنید" : "کد خودرو را وارد کنید"}
|
||||
error={!!error}
|
||||
helperText={error?.message}
|
||||
InputLabelProps={{
|
||||
shrink: true,
|
||||
}}
|
||||
InputProps={{
|
||||
...params.InputProps,
|
||||
endAdornment: (
|
||||
<>
|
||||
{loading ? <CircularProgress size="25px" color="inherit" /> : null}
|
||||
{params.InputProps.endAdornment}
|
||||
</>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export default CarCode;
|
||||
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_CAR_LIST_SEARCH } from "@/core/utils/routes";
|
||||
|
||||
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();
|
||||
|
||||
useEffect(() => {
|
||||
const fetchCarCodes = (inputValue, controller) => {
|
||||
const debouncer = debounce((query) => {
|
||||
if (!query || query?.length < 3) {
|
||||
setOptions([]);
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
requestServer(`${GET_CAR_LIST_SEARCH}?machine_code=${query}`, "get", {
|
||||
requestOptions: { signal: controller.signal },
|
||||
})
|
||||
.then((response) => {
|
||||
const combinedArray = carCode ? [...carCode, ...response.data.data] : [...response.data.data];
|
||||
const uniqueArray = Array.from(new Map(combinedArray.map((item) => [item.id, item])).values());
|
||||
setOptions(uniqueArray || []);
|
||||
})
|
||||
.catch(() => {
|
||||
setOptions([]);
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
}, 500);
|
||||
debouncer(inputValue);
|
||||
};
|
||||
|
||||
const controller = new AbortController();
|
||||
fetchCarCodes(inputValue, controller);
|
||||
return () => controller.abort();
|
||||
}, [inputValue]);
|
||||
|
||||
return (
|
||||
<Autocomplete
|
||||
multiple={multiple}
|
||||
value={carCode}
|
||||
size="small"
|
||||
onChange={(event, newValue) => {
|
||||
setCarCode(multiple ? newValue || [] : newValue || null);
|
||||
}}
|
||||
inputValue={inputValue}
|
||||
onInputChange={(event, newInputValue) => {
|
||||
setInputValue(newInputValue || "");
|
||||
}}
|
||||
options={options}
|
||||
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;
|
||||
if (value === "") return option.machine_code === "";
|
||||
return option.machine_code === (value?.machine_code || value);
|
||||
}}
|
||||
loading={loading}
|
||||
loadingText="درحال جستجوی کد خودرو"
|
||||
noOptionsText={inputValue?.length < 3 ? "حداقل سه رقم از کد خودرو را وارد کنید" : "کد خودرویی یافت نشد"}
|
||||
fullWidth
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
label={multiple ? "خودرو ها" : "خودرو"}
|
||||
fullWidth
|
||||
placeholder={multiple ? "کد خودرو ها را وارد کنید" : "کد خودرو را وارد کنید"}
|
||||
error={!!error}
|
||||
helperText={error?.message}
|
||||
InputLabelProps={{
|
||||
shrink: true,
|
||||
}}
|
||||
InputProps={{
|
||||
...params.InputProps,
|
||||
endAdornment: (
|
||||
<>
|
||||
{loading ? <CircularProgress size="25px" color="inherit" /> : null}
|
||||
{params.InputProps.endAdornment}
|
||||
</>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export default CarCode;
|
||||
|
||||
@@ -1,53 +1,53 @@
|
||||
import React from "react";
|
||||
import { AdapterDateFnsJalali } from "@mui/x-date-pickers/AdapterDateFnsJalali";
|
||||
import { faIR } from "@mui/x-date-pickers/locales";
|
||||
import { LocalizationProvider, MobileDatePicker } from "@mui/x-date-pickers";
|
||||
import { IconButton, InputAdornment } from "@mui/material";
|
||||
import ClearIcon from "@mui/icons-material/Clear";
|
||||
|
||||
const CustomDatePicker = ({ dateValue, setDateValue, placeholder = "انتخاب تاریخ", size = "small" }) => {
|
||||
const handleDateChange = (newValue) => {
|
||||
setDateValue(newValue);
|
||||
};
|
||||
|
||||
return (
|
||||
<LocalizationProvider
|
||||
dateAdapter={AdapterDateFnsJalali}
|
||||
localeText={faIR.components.MuiLocalizationProvider.defaultProps.localeText}
|
||||
>
|
||||
<MobileDatePicker
|
||||
value={dateValue || null}
|
||||
onChange={handleDateChange}
|
||||
slotProps={{
|
||||
textField: {
|
||||
size: size,
|
||||
placeholder: placeholder,
|
||||
InputProps: {
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<IconButton
|
||||
size={size}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
setDateValue(null);
|
||||
}}
|
||||
sx={{
|
||||
color: "#bfbfbf",
|
||||
"&:hover": {
|
||||
backgroundColor: "rgba(189, 189, 189, 0.1)",
|
||||
color: "#363434",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<ClearIcon />
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
),
|
||||
},
|
||||
},
|
||||
}}
|
||||
></MobileDatePicker>
|
||||
</LocalizationProvider>
|
||||
);
|
||||
};
|
||||
export default CustomDatePicker;
|
||||
import React from "react";
|
||||
import { AdapterDateFnsJalali } from "@mui/x-date-pickers/AdapterDateFnsJalali";
|
||||
import { faIR } from "@mui/x-date-pickers/locales";
|
||||
import { LocalizationProvider, MobileDatePicker } from "@mui/x-date-pickers";
|
||||
import { IconButton, InputAdornment } from "@mui/material";
|
||||
import ClearIcon from "@mui/icons-material/Clear";
|
||||
|
||||
const CustomDatePicker = ({ dateValue, setDateValue, placeholder = "انتخاب تاریخ", size = "small" }) => {
|
||||
const handleDateChange = (newValue) => {
|
||||
setDateValue(newValue);
|
||||
};
|
||||
|
||||
return (
|
||||
<LocalizationProvider
|
||||
dateAdapter={AdapterDateFnsJalali}
|
||||
localeText={faIR.components.MuiLocalizationProvider.defaultProps.localeText}
|
||||
>
|
||||
<MobileDatePicker
|
||||
value={dateValue || null}
|
||||
onChange={handleDateChange}
|
||||
slotProps={{
|
||||
textField: {
|
||||
size: size,
|
||||
placeholder: placeholder,
|
||||
InputProps: {
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<IconButton
|
||||
size={size}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
setDateValue(null);
|
||||
}}
|
||||
sx={{
|
||||
color: "#bfbfbf",
|
||||
"&:hover": {
|
||||
backgroundColor: "rgba(189, 189, 189, 0.1)",
|
||||
color: "#363434",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<ClearIcon />
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
),
|
||||
},
|
||||
},
|
||||
}}
|
||||
></MobileDatePicker>
|
||||
</LocalizationProvider>
|
||||
);
|
||||
};
|
||||
export default CustomDatePicker;
|
||||
|
||||
@@ -1,179 +1,179 @@
|
||||
import DataTableFilterDataStructure from "@/core/utils/DataTableFilterDataStructure";
|
||||
import { flattenArrayOfObjects } from "@/core/utils/flattenArrayOfObjects";
|
||||
import { flattenObjectOfObjects } from "@/core/utils/flattenObjectOfObjects";
|
||||
import isArrayEmpty from "@/core/utils/isArrayEmpty";
|
||||
import useDataTable from "@/lib/hooks/useDataTable";
|
||||
import useRequest from "@/lib/hooks/useRequest";
|
||||
import { useMaterialReactTable } from "material-react-table";
|
||||
import { useCallback, useEffect, useReducer, useState } from "react";
|
||||
import useSWR from "swr";
|
||||
import { FA_DATATABLE_LOCALIZATION } from "./localization/fa/datatable";
|
||||
import DataTable_Paper from "./table/Paper";
|
||||
|
||||
const rowSelectionReducer = (state, action) => {
|
||||
switch (action.type) {
|
||||
case "TOGGLE_ROW":
|
||||
return { [action.payload]: true };
|
||||
case "RESET":
|
||||
return {};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
const DataTable_Main = (props) => {
|
||||
const request = useRequest();
|
||||
const [rowSelection, dispatchRowSelection] = useReducer(rowSelectionReducer, {});
|
||||
const { filterData, sortData, setSortData, hideData } = useDataTable();
|
||||
const {
|
||||
need_filter,
|
||||
table_url,
|
||||
user_id,
|
||||
page_name,
|
||||
table_name,
|
||||
columns,
|
||||
initialStateProps,
|
||||
TableToolbar,
|
||||
table_title,
|
||||
RowActions,
|
||||
specific_data,
|
||||
specialFilter,
|
||||
} = props;
|
||||
const flatColumns = flattenArrayOfObjects(columns, "columns");
|
||||
const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 10 });
|
||||
const [totalRowCount, setTotalRowCount] = useState(0);
|
||||
const flattenHideData = flattenObjectOfObjects(hideData);
|
||||
const onSortingChange = (event) => {
|
||||
setSortData(event);
|
||||
};
|
||||
|
||||
const fetchUrl = useCallback(() => {
|
||||
const params = new URLSearchParams();
|
||||
params.set("start", `${pagination.pageIndex * pagination.pageSize}`);
|
||||
params.set("size", pagination.pageSize);
|
||||
if (specialFilter) {
|
||||
const filteredSpecialFilterData = Object.values(specialFilter).filter(
|
||||
(filter) => !isArrayEmpty(filter.value)
|
||||
);
|
||||
params.set("filters", JSON.stringify(filteredSpecialFilterData || []));
|
||||
} else {
|
||||
const filteredFilterData = DataTableFilterDataStructure(filterData, isArrayEmpty);
|
||||
params.set("filters", JSON.stringify(filteredFilterData.length === 0 ? [] : filteredFilterData));
|
||||
}
|
||||
params.set(
|
||||
"sorting",
|
||||
JSON.stringify(
|
||||
Object.values(sortData).map(({ id, ...rest }) => ({
|
||||
...rest,
|
||||
id: id.replace(/__/g, "."),
|
||||
}))
|
||||
)
|
||||
);
|
||||
return `${table_url}?${params}`;
|
||||
}, [table_url, filterData, pagination, sortData, specialFilter]);
|
||||
|
||||
const fetcher = async (url) => {
|
||||
try {
|
||||
const response = await request(url);
|
||||
setTotalRowCount(response.data?.meta?.totalRowCount);
|
||||
if (specific_data) {
|
||||
return specific_data(response.data);
|
||||
} else {
|
||||
return response.data?.data;
|
||||
}
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const { data, isValidating, mutate } = useSWR(props.data ? "" : fetchUrl(), fetcher, {
|
||||
revalidateIfStale: true,
|
||||
revalidateOnFocus: false,
|
||||
revalidateOnReconnect: true,
|
||||
keepPreviousData: true,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!isValidating) return;
|
||||
dispatchRowSelection({ type: "RESET" });
|
||||
}, [isValidating]);
|
||||
|
||||
const table = useMaterialReactTable({
|
||||
localization: FA_DATATABLE_LOCALIZATION,
|
||||
columns,
|
||||
data: data ?? [],
|
||||
muiTableBodyRowProps: ({ row }) => ({
|
||||
onClick: () => dispatchRowSelection({ type: "TOGGLE_ROW", payload: row.id }),
|
||||
selected: !!rowSelection[row.id],
|
||||
}),
|
||||
initialState: {
|
||||
density: "compact",
|
||||
columnVisibility: flattenHideData,
|
||||
sorting: sortData,
|
||||
...initialStateProps,
|
||||
},
|
||||
state: {
|
||||
showProgressBars: isValidating || props.loading,
|
||||
columnVisibility: flattenHideData,
|
||||
sorting: sortData,
|
||||
pagination,
|
||||
rowSelection,
|
||||
},
|
||||
muiTableHeadProps: {
|
||||
sx: {
|
||||
borderTop: "1px solid #e1e1e1",
|
||||
borderBottom: "2px solid #e1e1e1",
|
||||
},
|
||||
},
|
||||
muiTableHeadCellProps: {
|
||||
sx: {
|
||||
color: "primary.main",
|
||||
borderLeft: "1px solid #e1e1e1",
|
||||
"&:first-of-type": {
|
||||
borderLeft: "unset",
|
||||
},
|
||||
},
|
||||
},
|
||||
muiTableBodyCellProps: {
|
||||
sx: {
|
||||
borderLeft: "1px solid #e1e1e1",
|
||||
"&:first-of-type": {
|
||||
borderLeft: "unset",
|
||||
},
|
||||
},
|
||||
},
|
||||
manualFiltering: true,
|
||||
manualPagination: true,
|
||||
manualSorting: true,
|
||||
onPaginationChange: setPagination,
|
||||
onSortingChange: onSortingChange,
|
||||
rowCount: totalRowCount,
|
||||
renderTopToolbarCustomActions: ({ table }) => (
|
||||
<>
|
||||
{TableToolbar && (
|
||||
<TableToolbar table={table} mutate={mutate} filterData={specialFilter || filterData} />
|
||||
)}
|
||||
</>
|
||||
),
|
||||
renderRowActions: ({ row }) => <RowActions row={row} mutate={mutate} />,
|
||||
...props,
|
||||
});
|
||||
|
||||
return (
|
||||
<DataTable_Paper
|
||||
table={table}
|
||||
table_title={table_title}
|
||||
need_filter={need_filter}
|
||||
mutate={mutate}
|
||||
table_url={table_url}
|
||||
columns={flatColumns}
|
||||
user_id={user_id}
|
||||
page_name={page_name}
|
||||
table_name={table_name}
|
||||
special_data={props.data}
|
||||
special_filter={specialFilter}
|
||||
setFilterData={props.setFilterData}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export default DataTable_Main;
|
||||
import DataTableFilterDataStructure from "@/core/utils/DataTableFilterDataStructure";
|
||||
import { flattenArrayOfObjects } from "@/core/utils/flattenArrayOfObjects";
|
||||
import { flattenObjectOfObjects } from "@/core/utils/flattenObjectOfObjects";
|
||||
import isArrayEmpty from "@/core/utils/isArrayEmpty";
|
||||
import useDataTable from "@/lib/hooks/useDataTable";
|
||||
import useRequest from "@/lib/hooks/useRequest";
|
||||
import { useMaterialReactTable } from "material-react-table";
|
||||
import { useCallback, useEffect, useReducer, useState } from "react";
|
||||
import useSWR from "swr";
|
||||
import { FA_DATATABLE_LOCALIZATION } from "./localization/fa/datatable";
|
||||
import DataTable_Paper from "./table/Paper";
|
||||
|
||||
const rowSelectionReducer = (state, action) => {
|
||||
switch (action.type) {
|
||||
case "TOGGLE_ROW":
|
||||
return { [action.payload]: true };
|
||||
case "RESET":
|
||||
return {};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
const DataTable_Main = (props) => {
|
||||
const request = useRequest();
|
||||
const [rowSelection, dispatchRowSelection] = useReducer(rowSelectionReducer, {});
|
||||
const { filterData, sortData, setSortData, hideData } = useDataTable();
|
||||
const {
|
||||
need_filter,
|
||||
table_url,
|
||||
user_id,
|
||||
page_name,
|
||||
table_name,
|
||||
columns,
|
||||
initialStateProps,
|
||||
TableToolbar,
|
||||
table_title,
|
||||
RowActions,
|
||||
specific_data,
|
||||
specialFilter,
|
||||
} = props;
|
||||
const flatColumns = flattenArrayOfObjects(columns, "columns");
|
||||
const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 10 });
|
||||
const [totalRowCount, setTotalRowCount] = useState(0);
|
||||
const flattenHideData = flattenObjectOfObjects(hideData);
|
||||
const onSortingChange = (event) => {
|
||||
setSortData(event);
|
||||
};
|
||||
|
||||
const fetchUrl = useCallback(() => {
|
||||
const params = new URLSearchParams();
|
||||
params.set("start", `${pagination.pageIndex * pagination.pageSize}`);
|
||||
params.set("size", pagination.pageSize);
|
||||
if (specialFilter) {
|
||||
const filteredSpecialFilterData = Object.values(specialFilter).filter(
|
||||
(filter) => !isArrayEmpty(filter.value)
|
||||
);
|
||||
params.set("filters", JSON.stringify(filteredSpecialFilterData || []));
|
||||
} else {
|
||||
const filteredFilterData = DataTableFilterDataStructure(filterData, isArrayEmpty);
|
||||
params.set("filters", JSON.stringify(filteredFilterData.length === 0 ? [] : filteredFilterData));
|
||||
}
|
||||
params.set(
|
||||
"sorting",
|
||||
JSON.stringify(
|
||||
Object.values(sortData).map(({ id, ...rest }) => ({
|
||||
...rest,
|
||||
id: id.replace(/__/g, "."),
|
||||
}))
|
||||
)
|
||||
);
|
||||
return `${table_url}?${params}`;
|
||||
}, [table_url, filterData, pagination, sortData, specialFilter]);
|
||||
|
||||
const fetcher = async (url) => {
|
||||
try {
|
||||
const response = await request(url);
|
||||
setTotalRowCount(response.data?.meta?.totalRowCount);
|
||||
if (specific_data) {
|
||||
return specific_data(response.data);
|
||||
} else {
|
||||
return response.data?.data;
|
||||
}
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const { data, isValidating, mutate } = useSWR(props.data ? "" : fetchUrl(), fetcher, {
|
||||
revalidateIfStale: true,
|
||||
revalidateOnFocus: false,
|
||||
revalidateOnReconnect: true,
|
||||
keepPreviousData: true,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!isValidating) return;
|
||||
dispatchRowSelection({ type: "RESET" });
|
||||
}, [isValidating]);
|
||||
|
||||
const table = useMaterialReactTable({
|
||||
localization: FA_DATATABLE_LOCALIZATION,
|
||||
columns,
|
||||
data: data ?? [],
|
||||
muiTableBodyRowProps: ({ row }) => ({
|
||||
onClick: () => dispatchRowSelection({ type: "TOGGLE_ROW", payload: row.id }),
|
||||
selected: !!rowSelection[row.id],
|
||||
}),
|
||||
initialState: {
|
||||
density: "compact",
|
||||
columnVisibility: flattenHideData,
|
||||
sorting: sortData,
|
||||
...initialStateProps,
|
||||
},
|
||||
state: {
|
||||
showProgressBars: isValidating || props.loading,
|
||||
columnVisibility: flattenHideData,
|
||||
sorting: sortData,
|
||||
pagination,
|
||||
rowSelection,
|
||||
},
|
||||
muiTableHeadProps: {
|
||||
sx: {
|
||||
borderTop: "1px solid #e1e1e1",
|
||||
borderBottom: "2px solid #e1e1e1",
|
||||
},
|
||||
},
|
||||
muiTableHeadCellProps: {
|
||||
sx: {
|
||||
color: "primary.main",
|
||||
borderLeft: "1px solid #e1e1e1",
|
||||
"&:first-of-type": {
|
||||
borderLeft: "unset",
|
||||
},
|
||||
},
|
||||
},
|
||||
muiTableBodyCellProps: {
|
||||
sx: {
|
||||
borderLeft: "1px solid #e1e1e1",
|
||||
"&:first-of-type": {
|
||||
borderLeft: "unset",
|
||||
},
|
||||
},
|
||||
},
|
||||
manualFiltering: true,
|
||||
manualPagination: true,
|
||||
manualSorting: true,
|
||||
onPaginationChange: setPagination,
|
||||
onSortingChange: onSortingChange,
|
||||
rowCount: totalRowCount,
|
||||
renderTopToolbarCustomActions: ({ table }) => (
|
||||
<>
|
||||
{TableToolbar && (
|
||||
<TableToolbar table={table} mutate={mutate} filterData={specialFilter || filterData} />
|
||||
)}
|
||||
</>
|
||||
),
|
||||
renderRowActions: ({ row }) => <RowActions row={row} mutate={mutate} />,
|
||||
...props,
|
||||
});
|
||||
|
||||
return (
|
||||
<DataTable_Paper
|
||||
table={table}
|
||||
table_title={table_title}
|
||||
need_filter={need_filter}
|
||||
mutate={mutate}
|
||||
table_url={table_url}
|
||||
columns={flatColumns}
|
||||
user_id={user_id}
|
||||
page_name={page_name}
|
||||
table_name={table_name}
|
||||
special_data={props.data}
|
||||
special_filter={specialFilter}
|
||||
setFilterData={props.setFilterData}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export default DataTable_Main;
|
||||
|
||||
@@ -1,191 +1,191 @@
|
||||
import { parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||
import { TableBody, Typography } from "@mui/material";
|
||||
import { useMRT_RowVirtualizer, useMRT_Rows } from "material-react-table";
|
||||
import { memo, useMemo } from "react";
|
||||
import DataTable_TableBodyRow, { Memo_DataTable_TableBodyRow } from "./TableBodyRow";
|
||||
|
||||
const DataTable_TableBody = ({ columnVirtualizer, table, ...rest }) => {
|
||||
const {
|
||||
getBottomRows,
|
||||
getIsSomeRowsPinned,
|
||||
getRowModel,
|
||||
getState,
|
||||
getTopRows,
|
||||
options: {
|
||||
enableStickyFooter,
|
||||
enableStickyHeader,
|
||||
layoutMode,
|
||||
localization,
|
||||
memoMode,
|
||||
muiTableBodyProps,
|
||||
renderDetailPanel,
|
||||
renderEmptyRowsFallback,
|
||||
rowPinningDisplayMode,
|
||||
},
|
||||
refs: { tableFooterRef, tableHeadRef, tablePaperRef },
|
||||
} = table;
|
||||
const { columnFilters, globalFilter, isFullScreen, rowPinning } = getState();
|
||||
|
||||
const tableBodyProps = {
|
||||
...parseFromValuesOrFunc(muiTableBodyProps, { table }),
|
||||
...rest,
|
||||
};
|
||||
|
||||
const tableHeadHeight = ((enableStickyHeader || isFullScreen) && tableHeadRef.current?.clientHeight) || 0;
|
||||
const tableFooterHeight = (enableStickyFooter && tableFooterRef.current?.clientHeight) || 0;
|
||||
|
||||
const pinnedRowIds = useMemo(() => {
|
||||
if (!rowPinning.bottom?.length && !rowPinning.top?.length) return [];
|
||||
return getRowModel()
|
||||
.rows.filter((row) => row.getIsPinned())
|
||||
.map((r) => r.id);
|
||||
}, [rowPinning, getRowModel().rows]);
|
||||
|
||||
const rows = useMRT_Rows(table);
|
||||
|
||||
const rowVirtualizer = useMRT_RowVirtualizer(table, rows);
|
||||
|
||||
const { virtualRows } = rowVirtualizer ?? {};
|
||||
|
||||
const commonRowProps = {
|
||||
columnVirtualizer,
|
||||
numRows: rows.length,
|
||||
table,
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{!rowPinningDisplayMode?.includes("sticky") && getIsSomeRowsPinned("top") && (
|
||||
<TableBody
|
||||
{...tableBodyProps}
|
||||
sx={(theme) => ({
|
||||
display: layoutMode?.startsWith("grid") ? "grid" : undefined,
|
||||
position: "sticky",
|
||||
top: tableHeadHeight - 1,
|
||||
zIndex: 1,
|
||||
...parseFromValuesOrFunc(tableBodyProps?.sx, theme),
|
||||
})}
|
||||
>
|
||||
{getTopRows().map((row, staticRowIndex) => {
|
||||
const props = {
|
||||
...commonRowProps,
|
||||
row,
|
||||
staticRowIndex,
|
||||
};
|
||||
return memoMode === "rows" ? (
|
||||
<Memo_DataTable_TableBodyRow key={row.id} {...props} />
|
||||
) : (
|
||||
<DataTable_TableBodyRow key={row.id} {...props} />
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
)}
|
||||
<TableBody
|
||||
{...tableBodyProps}
|
||||
sx={(theme) => ({
|
||||
display: layoutMode?.startsWith("grid") ? "grid" : undefined,
|
||||
height: rowVirtualizer ? `${rowVirtualizer.getTotalSize()}px` : undefined,
|
||||
minHeight: !rows.length ? "100px" : undefined,
|
||||
position: "relative",
|
||||
...parseFromValuesOrFunc(tableBodyProps?.sx, theme),
|
||||
})}
|
||||
>
|
||||
{tableBodyProps?.children ??
|
||||
(!rows.length ? (
|
||||
<tr
|
||||
style={{
|
||||
display: layoutMode?.startsWith("grid") ? "grid" : undefined,
|
||||
}}
|
||||
>
|
||||
<td
|
||||
colSpan={table.getVisibleLeafColumns().length}
|
||||
style={{
|
||||
display: layoutMode?.startsWith("grid") ? "grid" : undefined,
|
||||
}}
|
||||
>
|
||||
{renderEmptyRowsFallback?.({ table }) ?? (
|
||||
<Typography
|
||||
sx={{
|
||||
color: "text.secondary",
|
||||
fontStyle: "italic",
|
||||
maxWidth: `min(100vw, ${tablePaperRef.current?.clientWidth ?? 360}px)`,
|
||||
py: "2rem",
|
||||
textAlign: "center",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
{globalFilter || columnFilters.length
|
||||
? localization.noResultsFound
|
||||
: localization.noRecordsToDisplay}
|
||||
</Typography>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
) : (
|
||||
<>
|
||||
{(virtualRows ?? rows).map((rowOrVirtualRow, staticRowIndex) => {
|
||||
let row = rowOrVirtualRow;
|
||||
if (rowVirtualizer) {
|
||||
if (renderDetailPanel) {
|
||||
if (rowOrVirtualRow.index % 2 === 1) {
|
||||
return null;
|
||||
} else {
|
||||
staticRowIndex = rowOrVirtualRow.index / 2;
|
||||
}
|
||||
} else {
|
||||
staticRowIndex = rowOrVirtualRow.index;
|
||||
}
|
||||
row = rows[staticRowIndex];
|
||||
}
|
||||
const props = {
|
||||
...commonRowProps,
|
||||
pinnedRowIds,
|
||||
row,
|
||||
rowVirtualizer,
|
||||
staticRowIndex,
|
||||
virtualRow: rowVirtualizer ? rowOrVirtualRow : undefined,
|
||||
};
|
||||
const key = `${row.id}-${row.index}`;
|
||||
return memoMode === "rows" ? (
|
||||
<Memo_DataTable_TableBodyRow key={key} {...props} />
|
||||
) : (
|
||||
<DataTable_TableBodyRow key={key} {...props} />
|
||||
);
|
||||
})}
|
||||
</>
|
||||
))}
|
||||
</TableBody>
|
||||
{!rowPinningDisplayMode?.includes("sticky") && getIsSomeRowsPinned("bottom") && (
|
||||
<TableBody
|
||||
{...tableBodyProps}
|
||||
sx={(theme) => ({
|
||||
bottom: tableFooterHeight - 1,
|
||||
display: layoutMode?.startsWith("grid") ? "grid" : undefined,
|
||||
position: "sticky",
|
||||
zIndex: 1,
|
||||
...parseFromValuesOrFunc(tableBodyProps?.sx, theme),
|
||||
})}
|
||||
>
|
||||
{getBottomRows().map((row, staticRowIndex) => {
|
||||
const props = {
|
||||
...commonRowProps,
|
||||
row,
|
||||
staticRowIndex,
|
||||
};
|
||||
return memoMode === "rows" ? (
|
||||
<Memo_DataTable_TableBodyRow key={row.id} {...props} />
|
||||
) : (
|
||||
<DataTable_TableBodyRow key={row.id} {...props} />
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default DataTable_TableBody;
|
||||
|
||||
export const Memo_DataTable_TableBody = memo(
|
||||
DataTable_TableBody,
|
||||
(prev, next) => prev.table.options.data === next.table.options.data
|
||||
);
|
||||
import { parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||
import { TableBody, Typography } from "@mui/material";
|
||||
import { useMRT_RowVirtualizer, useMRT_Rows } from "material-react-table";
|
||||
import { memo, useMemo } from "react";
|
||||
import DataTable_TableBodyRow, { Memo_DataTable_TableBodyRow } from "./TableBodyRow";
|
||||
|
||||
const DataTable_TableBody = ({ columnVirtualizer, table, ...rest }) => {
|
||||
const {
|
||||
getBottomRows,
|
||||
getIsSomeRowsPinned,
|
||||
getRowModel,
|
||||
getState,
|
||||
getTopRows,
|
||||
options: {
|
||||
enableStickyFooter,
|
||||
enableStickyHeader,
|
||||
layoutMode,
|
||||
localization,
|
||||
memoMode,
|
||||
muiTableBodyProps,
|
||||
renderDetailPanel,
|
||||
renderEmptyRowsFallback,
|
||||
rowPinningDisplayMode,
|
||||
},
|
||||
refs: { tableFooterRef, tableHeadRef, tablePaperRef },
|
||||
} = table;
|
||||
const { columnFilters, globalFilter, isFullScreen, rowPinning } = getState();
|
||||
|
||||
const tableBodyProps = {
|
||||
...parseFromValuesOrFunc(muiTableBodyProps, { table }),
|
||||
...rest,
|
||||
};
|
||||
|
||||
const tableHeadHeight = ((enableStickyHeader || isFullScreen) && tableHeadRef.current?.clientHeight) || 0;
|
||||
const tableFooterHeight = (enableStickyFooter && tableFooterRef.current?.clientHeight) || 0;
|
||||
|
||||
const pinnedRowIds = useMemo(() => {
|
||||
if (!rowPinning.bottom?.length && !rowPinning.top?.length) return [];
|
||||
return getRowModel()
|
||||
.rows.filter((row) => row.getIsPinned())
|
||||
.map((r) => r.id);
|
||||
}, [rowPinning, getRowModel().rows]);
|
||||
|
||||
const rows = useMRT_Rows(table);
|
||||
|
||||
const rowVirtualizer = useMRT_RowVirtualizer(table, rows);
|
||||
|
||||
const { virtualRows } = rowVirtualizer ?? {};
|
||||
|
||||
const commonRowProps = {
|
||||
columnVirtualizer,
|
||||
numRows: rows.length,
|
||||
table,
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{!rowPinningDisplayMode?.includes("sticky") && getIsSomeRowsPinned("top") && (
|
||||
<TableBody
|
||||
{...tableBodyProps}
|
||||
sx={(theme) => ({
|
||||
display: layoutMode?.startsWith("grid") ? "grid" : undefined,
|
||||
position: "sticky",
|
||||
top: tableHeadHeight - 1,
|
||||
zIndex: 1,
|
||||
...parseFromValuesOrFunc(tableBodyProps?.sx, theme),
|
||||
})}
|
||||
>
|
||||
{getTopRows().map((row, staticRowIndex) => {
|
||||
const props = {
|
||||
...commonRowProps,
|
||||
row,
|
||||
staticRowIndex,
|
||||
};
|
||||
return memoMode === "rows" ? (
|
||||
<Memo_DataTable_TableBodyRow key={row.id} {...props} />
|
||||
) : (
|
||||
<DataTable_TableBodyRow key={row.id} {...props} />
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
)}
|
||||
<TableBody
|
||||
{...tableBodyProps}
|
||||
sx={(theme) => ({
|
||||
display: layoutMode?.startsWith("grid") ? "grid" : undefined,
|
||||
height: rowVirtualizer ? `${rowVirtualizer.getTotalSize()}px` : undefined,
|
||||
minHeight: !rows.length ? "100px" : undefined,
|
||||
position: "relative",
|
||||
...parseFromValuesOrFunc(tableBodyProps?.sx, theme),
|
||||
})}
|
||||
>
|
||||
{tableBodyProps?.children ??
|
||||
(!rows.length ? (
|
||||
<tr
|
||||
style={{
|
||||
display: layoutMode?.startsWith("grid") ? "grid" : undefined,
|
||||
}}
|
||||
>
|
||||
<td
|
||||
colSpan={table.getVisibleLeafColumns().length}
|
||||
style={{
|
||||
display: layoutMode?.startsWith("grid") ? "grid" : undefined,
|
||||
}}
|
||||
>
|
||||
{renderEmptyRowsFallback?.({ table }) ?? (
|
||||
<Typography
|
||||
sx={{
|
||||
color: "text.secondary",
|
||||
fontStyle: "italic",
|
||||
maxWidth: `min(100vw, ${tablePaperRef.current?.clientWidth ?? 360}px)`,
|
||||
py: "2rem",
|
||||
textAlign: "center",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
{globalFilter || columnFilters.length
|
||||
? localization.noResultsFound
|
||||
: localization.noRecordsToDisplay}
|
||||
</Typography>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
) : (
|
||||
<>
|
||||
{(virtualRows ?? rows).map((rowOrVirtualRow, staticRowIndex) => {
|
||||
let row = rowOrVirtualRow;
|
||||
if (rowVirtualizer) {
|
||||
if (renderDetailPanel) {
|
||||
if (rowOrVirtualRow.index % 2 === 1) {
|
||||
return null;
|
||||
} else {
|
||||
staticRowIndex = rowOrVirtualRow.index / 2;
|
||||
}
|
||||
} else {
|
||||
staticRowIndex = rowOrVirtualRow.index;
|
||||
}
|
||||
row = rows[staticRowIndex];
|
||||
}
|
||||
const props = {
|
||||
...commonRowProps,
|
||||
pinnedRowIds,
|
||||
row,
|
||||
rowVirtualizer,
|
||||
staticRowIndex,
|
||||
virtualRow: rowVirtualizer ? rowOrVirtualRow : undefined,
|
||||
};
|
||||
const key = `${row.id}-${row.index}`;
|
||||
return memoMode === "rows" ? (
|
||||
<Memo_DataTable_TableBodyRow key={key} {...props} />
|
||||
) : (
|
||||
<DataTable_TableBodyRow key={key} {...props} />
|
||||
);
|
||||
})}
|
||||
</>
|
||||
))}
|
||||
</TableBody>
|
||||
{!rowPinningDisplayMode?.includes("sticky") && getIsSomeRowsPinned("bottom") && (
|
||||
<TableBody
|
||||
{...tableBodyProps}
|
||||
sx={(theme) => ({
|
||||
bottom: tableFooterHeight - 1,
|
||||
display: layoutMode?.startsWith("grid") ? "grid" : undefined,
|
||||
position: "sticky",
|
||||
zIndex: 1,
|
||||
...parseFromValuesOrFunc(tableBodyProps?.sx, theme),
|
||||
})}
|
||||
>
|
||||
{getBottomRows().map((row, staticRowIndex) => {
|
||||
const props = {
|
||||
...commonRowProps,
|
||||
row,
|
||||
staticRowIndex,
|
||||
};
|
||||
return memoMode === "rows" ? (
|
||||
<Memo_DataTable_TableBodyRow key={row.id} {...props} />
|
||||
) : (
|
||||
<DataTable_TableBodyRow key={row.id} {...props} />
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default DataTable_TableBody;
|
||||
|
||||
export const Memo_DataTable_TableBody = memo(
|
||||
DataTable_TableBody,
|
||||
(prev, next) => prev.table.options.data === next.table.options.data
|
||||
);
|
||||
|
||||
@@ -1,255 +1,255 @@
|
||||
import { getCommonMRTCellStyles, parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { Skeleton, TableCell } from "@mui/material";
|
||||
import { isCellEditable, openEditingCell } from "material-react-table";
|
||||
import { memo, useEffect, useMemo, useState } from "react";
|
||||
import DataTable_CopyButton from "../buttons/CopyButton";
|
||||
import DataTable_TableBodyCellValue from "./TableBodyCellValue";
|
||||
|
||||
const DataTable_TableBodyCell = ({ cell, numRows, rowRef, staticColumnIndex, staticRowIndex, table, ...rest }) => {
|
||||
const theme = useTheme();
|
||||
const {
|
||||
getState,
|
||||
options: {
|
||||
columnResizeDirection,
|
||||
columnResizeMode,
|
||||
createDisplayMode,
|
||||
editDisplayMode,
|
||||
enableCellActions,
|
||||
enableClickToCopy,
|
||||
enableColumnOrdering,
|
||||
enableColumnPinning,
|
||||
enableGrouping,
|
||||
layoutMode,
|
||||
mrtTheme: { draggingBorderColor, baseBackgroundColor },
|
||||
muiSkeletonProps,
|
||||
muiTableBodyCellProps,
|
||||
},
|
||||
setHoveredColumn,
|
||||
} = table;
|
||||
const {
|
||||
actionCell,
|
||||
columnSizingInfo,
|
||||
creatingRow,
|
||||
density,
|
||||
draggingColumn,
|
||||
draggingRow,
|
||||
editingCell,
|
||||
editingRow,
|
||||
hoveredColumn,
|
||||
hoveredRow,
|
||||
isLoading,
|
||||
showSkeletons,
|
||||
} = getState();
|
||||
const { column, row } = cell;
|
||||
const { columnDef } = column;
|
||||
const { columnDefType } = columnDef;
|
||||
|
||||
const args = { cell, column, row, table };
|
||||
const tableCellProps = {
|
||||
...parseFromValuesOrFunc(muiTableBodyCellProps, args),
|
||||
...parseFromValuesOrFunc(columnDef.muiTableBodyCellProps, args),
|
||||
...rest,
|
||||
};
|
||||
|
||||
const skeletonProps = parseFromValuesOrFunc(muiSkeletonProps, {
|
||||
cell,
|
||||
column,
|
||||
row,
|
||||
table,
|
||||
});
|
||||
|
||||
const [skeletonWidth, setSkeletonWidth] = useState(100);
|
||||
useEffect(() => {
|
||||
if ((!isLoading && !showSkeletons) || skeletonWidth !== 100) return;
|
||||
const size = column.getSize();
|
||||
setSkeletonWidth(
|
||||
columnDefType === "display" ? size / 2 : Math.round(Math.random() * (size - size / 3) + size / 3)
|
||||
);
|
||||
}, [isLoading, showSkeletons]);
|
||||
|
||||
const draggingBorders = useMemo(() => {
|
||||
const isDraggingColumn = draggingColumn?.id === column.id;
|
||||
const isHoveredColumn = hoveredColumn?.id === column.id;
|
||||
const isDraggingRow = draggingRow?.id === row.id;
|
||||
const isHoveredRow = hoveredRow?.id === row.id;
|
||||
const isFirstColumn = column.getIsFirstColumn();
|
||||
const isLastColumn = column.getIsLastColumn();
|
||||
const isLastRow = numRows && staticRowIndex === numRows - 1;
|
||||
const isResizingColumn = columnSizingInfo.isResizingColumn === column.id;
|
||||
const showResizeBorder = isResizingColumn && columnResizeMode === "onChange";
|
||||
|
||||
const borderStyle = showResizeBorder
|
||||
? `2px solid ${draggingBorderColor} !important`
|
||||
: isDraggingColumn || isDraggingRow
|
||||
? `1px dashed ${theme.palette.grey[500]} !important`
|
||||
: isHoveredColumn || isHoveredRow || isResizingColumn
|
||||
? `2px dashed ${draggingBorderColor} !important`
|
||||
: undefined;
|
||||
|
||||
if (showResizeBorder) {
|
||||
return columnResizeDirection === "ltr" ? { borderRight: borderStyle } : { borderLeft: borderStyle };
|
||||
}
|
||||
|
||||
return borderStyle
|
||||
? {
|
||||
borderBottom:
|
||||
isDraggingRow || isHoveredRow || (isLastRow && !isResizingColumn) ? borderStyle : undefined,
|
||||
borderLeft:
|
||||
isDraggingColumn || isHoveredColumn || ((isDraggingRow || isHoveredRow) && isFirstColumn)
|
||||
? borderStyle
|
||||
: undefined,
|
||||
borderRight:
|
||||
isDraggingColumn || isHoveredColumn || ((isDraggingRow || isHoveredRow) && isLastColumn)
|
||||
? borderStyle
|
||||
: undefined,
|
||||
borderTop: isDraggingRow || isHoveredRow ? borderStyle : undefined,
|
||||
}
|
||||
: undefined;
|
||||
}, [columnSizingInfo.isResizingColumn, draggingColumn, draggingRow, hoveredColumn, hoveredRow, staticRowIndex]);
|
||||
|
||||
const isColumnPinned = enableColumnPinning && columnDef.columnDefType !== "group" && column.getIsPinned();
|
||||
|
||||
const isEditable = isCellEditable({ cell, table });
|
||||
|
||||
const isEditing =
|
||||
isEditable &&
|
||||
!["custom", "modal"].includes(editDisplayMode) &&
|
||||
(editDisplayMode === "table" || editingRow?.id === row.id || editingCell?.id === cell.id) &&
|
||||
!row.getIsGrouped();
|
||||
|
||||
const isCreating = isEditable && createDisplayMode === "row" && creatingRow?.id === row.id;
|
||||
|
||||
const showClickToCopyButton =
|
||||
(parseFromValuesOrFunc(enableClickToCopy, cell) === true ||
|
||||
parseFromValuesOrFunc(columnDef.enableClickToCopy, cell) === true) &&
|
||||
!["context-menu", false].includes(
|
||||
// @ts-ignore
|
||||
parseFromValuesOrFunc(columnDef.enableClickToCopy, cell)
|
||||
);
|
||||
|
||||
const isRightClickable = parseFromValuesOrFunc(enableCellActions, cell);
|
||||
|
||||
const cellValueProps = {
|
||||
cell,
|
||||
table,
|
||||
};
|
||||
|
||||
const handleDoubleClick = (event) => {
|
||||
tableCellProps?.onDoubleClick?.(event);
|
||||
openEditingCell({ cell, table });
|
||||
};
|
||||
|
||||
const handleDragEnter = (e) => {
|
||||
tableCellProps?.onDragEnter?.(e);
|
||||
if (enableGrouping && hoveredColumn?.id === "drop-zone") {
|
||||
setHoveredColumn(null);
|
||||
}
|
||||
if (enableColumnOrdering && draggingColumn) {
|
||||
setHoveredColumn(columnDef.enableColumnOrdering !== false ? column : null);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDragOver = (e) => {
|
||||
if (columnDef.enableColumnOrdering !== false) {
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
const handleContextMenu = (e) => {
|
||||
tableCellProps?.onContextMenu?.(e);
|
||||
if (isRightClickable) {
|
||||
e.preventDefault();
|
||||
table.setActionCell(cell);
|
||||
table.refs.actionCellRef.current = e.currentTarget;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<TableCell
|
||||
data-index={staticColumnIndex}
|
||||
data-pinned={!!isColumnPinned || undefined}
|
||||
{...tableCellProps}
|
||||
onContextMenu={handleContextMenu}
|
||||
onDoubleClick={handleDoubleClick}
|
||||
onDragEnter={handleDragEnter}
|
||||
onDragOver={handleDragOver}
|
||||
sx={(theme) => ({
|
||||
"&:hover": {
|
||||
outline:
|
||||
actionCell?.id === cell.id ||
|
||||
(editDisplayMode === "cell" && isEditable) ||
|
||||
(editDisplayMode === "table" && (isCreating || isEditing))
|
||||
? `1px solid ${theme.palette.grey[500]}`
|
||||
: undefined,
|
||||
textOverflow: "clip",
|
||||
},
|
||||
alignItems: layoutMode?.startsWith("grid") ? "center" : undefined,
|
||||
cursor: isRightClickable
|
||||
? "context-menu"
|
||||
: isEditable && editDisplayMode === "cell"
|
||||
? "pointer"
|
||||
: "inherit",
|
||||
outline: actionCell?.id === cell.id ? `1px solid ${theme.palette.grey[500]}` : undefined,
|
||||
outlineOffset: "-1px",
|
||||
overflow: "hidden",
|
||||
p:
|
||||
density === "compact"
|
||||
? columnDefType === "display"
|
||||
? "0 0.5rem"
|
||||
: "0.5rem"
|
||||
: density === "comfortable"
|
||||
? columnDefType === "display"
|
||||
? "0.5rem 0.75rem"
|
||||
: "1rem"
|
||||
: columnDefType === "display"
|
||||
? "1rem 1.25rem"
|
||||
: "1.5rem",
|
||||
|
||||
textOverflow: columnDefType !== "display" ? "ellipsis" : undefined,
|
||||
whiteSpace: row.getIsPinned() || density === "compact" ? "nowrap" : "normal",
|
||||
...getCommonMRTCellStyles({
|
||||
column,
|
||||
table,
|
||||
tableCellProps,
|
||||
theme,
|
||||
}),
|
||||
...draggingBorders,
|
||||
backgroundColor: baseBackgroundColor,
|
||||
})}
|
||||
>
|
||||
{tableCellProps.children ?? (
|
||||
<>
|
||||
{cell.getIsPlaceholder() ? (
|
||||
(columnDef.PlaceholderCell?.({ cell, column, row, table }) ?? null)
|
||||
) : showSkeletons !== false && (isLoading || showSkeletons) ? (
|
||||
<Skeleton animation="wave" height={20} width={skeletonWidth} {...skeletonProps} />
|
||||
) : columnDefType === "display" &&
|
||||
(["mrt-row-expand", "mrt-row-numbers", "mrt-row-select"].includes(column.id) ||
|
||||
!row.getIsGrouped()) ? (
|
||||
columnDef.Cell?.({
|
||||
cell,
|
||||
column,
|
||||
renderedCellValue: cell.renderValue(),
|
||||
row,
|
||||
rowRef,
|
||||
staticColumnIndex,
|
||||
staticRowIndex,
|
||||
table,
|
||||
})
|
||||
) : showClickToCopyButton && columnDef.enableClickToCopy !== false ? (
|
||||
<DataTable_CopyButton cell={cell} table={table}>
|
||||
<DataTable_TableBodyCellValue {...cellValueProps} />
|
||||
</DataTable_CopyButton>
|
||||
) : (
|
||||
<DataTable_TableBodyCellValue {...cellValueProps} />
|
||||
)}
|
||||
{cell.getIsGrouped() && !columnDef.GroupedCell && <> ({row.subRows?.length})</>}
|
||||
</>
|
||||
)}
|
||||
</TableCell>
|
||||
);
|
||||
};
|
||||
export default DataTable_TableBodyCell;
|
||||
|
||||
export const Memo_DataTable_TableBodyCell = memo(DataTable_TableBodyCell, (prev, next) => next.cell === prev.cell);
|
||||
import { getCommonMRTCellStyles, parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { Skeleton, TableCell } from "@mui/material";
|
||||
import { isCellEditable, openEditingCell } from "material-react-table";
|
||||
import { memo, useEffect, useMemo, useState } from "react";
|
||||
import DataTable_CopyButton from "../buttons/CopyButton";
|
||||
import DataTable_TableBodyCellValue from "./TableBodyCellValue";
|
||||
|
||||
const DataTable_TableBodyCell = ({ cell, numRows, rowRef, staticColumnIndex, staticRowIndex, table, ...rest }) => {
|
||||
const theme = useTheme();
|
||||
const {
|
||||
getState,
|
||||
options: {
|
||||
columnResizeDirection,
|
||||
columnResizeMode,
|
||||
createDisplayMode,
|
||||
editDisplayMode,
|
||||
enableCellActions,
|
||||
enableClickToCopy,
|
||||
enableColumnOrdering,
|
||||
enableColumnPinning,
|
||||
enableGrouping,
|
||||
layoutMode,
|
||||
mrtTheme: { draggingBorderColor, baseBackgroundColor },
|
||||
muiSkeletonProps,
|
||||
muiTableBodyCellProps,
|
||||
},
|
||||
setHoveredColumn,
|
||||
} = table;
|
||||
const {
|
||||
actionCell,
|
||||
columnSizingInfo,
|
||||
creatingRow,
|
||||
density,
|
||||
draggingColumn,
|
||||
draggingRow,
|
||||
editingCell,
|
||||
editingRow,
|
||||
hoveredColumn,
|
||||
hoveredRow,
|
||||
isLoading,
|
||||
showSkeletons,
|
||||
} = getState();
|
||||
const { column, row } = cell;
|
||||
const { columnDef } = column;
|
||||
const { columnDefType } = columnDef;
|
||||
|
||||
const args = { cell, column, row, table };
|
||||
const tableCellProps = {
|
||||
...parseFromValuesOrFunc(muiTableBodyCellProps, args),
|
||||
...parseFromValuesOrFunc(columnDef.muiTableBodyCellProps, args),
|
||||
...rest,
|
||||
};
|
||||
|
||||
const skeletonProps = parseFromValuesOrFunc(muiSkeletonProps, {
|
||||
cell,
|
||||
column,
|
||||
row,
|
||||
table,
|
||||
});
|
||||
|
||||
const [skeletonWidth, setSkeletonWidth] = useState(100);
|
||||
useEffect(() => {
|
||||
if ((!isLoading && !showSkeletons) || skeletonWidth !== 100) return;
|
||||
const size = column.getSize();
|
||||
setSkeletonWidth(
|
||||
columnDefType === "display" ? size / 2 : Math.round(Math.random() * (size - size / 3) + size / 3)
|
||||
);
|
||||
}, [isLoading, showSkeletons]);
|
||||
|
||||
const draggingBorders = useMemo(() => {
|
||||
const isDraggingColumn = draggingColumn?.id === column.id;
|
||||
const isHoveredColumn = hoveredColumn?.id === column.id;
|
||||
const isDraggingRow = draggingRow?.id === row.id;
|
||||
const isHoveredRow = hoveredRow?.id === row.id;
|
||||
const isFirstColumn = column.getIsFirstColumn();
|
||||
const isLastColumn = column.getIsLastColumn();
|
||||
const isLastRow = numRows && staticRowIndex === numRows - 1;
|
||||
const isResizingColumn = columnSizingInfo.isResizingColumn === column.id;
|
||||
const showResizeBorder = isResizingColumn && columnResizeMode === "onChange";
|
||||
|
||||
const borderStyle = showResizeBorder
|
||||
? `2px solid ${draggingBorderColor} !important`
|
||||
: isDraggingColumn || isDraggingRow
|
||||
? `1px dashed ${theme.palette.grey[500]} !important`
|
||||
: isHoveredColumn || isHoveredRow || isResizingColumn
|
||||
? `2px dashed ${draggingBorderColor} !important`
|
||||
: undefined;
|
||||
|
||||
if (showResizeBorder) {
|
||||
return columnResizeDirection === "ltr" ? { borderRight: borderStyle } : { borderLeft: borderStyle };
|
||||
}
|
||||
|
||||
return borderStyle
|
||||
? {
|
||||
borderBottom:
|
||||
isDraggingRow || isHoveredRow || (isLastRow && !isResizingColumn) ? borderStyle : undefined,
|
||||
borderLeft:
|
||||
isDraggingColumn || isHoveredColumn || ((isDraggingRow || isHoveredRow) && isFirstColumn)
|
||||
? borderStyle
|
||||
: undefined,
|
||||
borderRight:
|
||||
isDraggingColumn || isHoveredColumn || ((isDraggingRow || isHoveredRow) && isLastColumn)
|
||||
? borderStyle
|
||||
: undefined,
|
||||
borderTop: isDraggingRow || isHoveredRow ? borderStyle : undefined,
|
||||
}
|
||||
: undefined;
|
||||
}, [columnSizingInfo.isResizingColumn, draggingColumn, draggingRow, hoveredColumn, hoveredRow, staticRowIndex]);
|
||||
|
||||
const isColumnPinned = enableColumnPinning && columnDef.columnDefType !== "group" && column.getIsPinned();
|
||||
|
||||
const isEditable = isCellEditable({ cell, table });
|
||||
|
||||
const isEditing =
|
||||
isEditable &&
|
||||
!["custom", "modal"].includes(editDisplayMode) &&
|
||||
(editDisplayMode === "table" || editingRow?.id === row.id || editingCell?.id === cell.id) &&
|
||||
!row.getIsGrouped();
|
||||
|
||||
const isCreating = isEditable && createDisplayMode === "row" && creatingRow?.id === row.id;
|
||||
|
||||
const showClickToCopyButton =
|
||||
(parseFromValuesOrFunc(enableClickToCopy, cell) === true ||
|
||||
parseFromValuesOrFunc(columnDef.enableClickToCopy, cell) === true) &&
|
||||
!["context-menu", false].includes(
|
||||
// @ts-ignore
|
||||
parseFromValuesOrFunc(columnDef.enableClickToCopy, cell)
|
||||
);
|
||||
|
||||
const isRightClickable = parseFromValuesOrFunc(enableCellActions, cell);
|
||||
|
||||
const cellValueProps = {
|
||||
cell,
|
||||
table,
|
||||
};
|
||||
|
||||
const handleDoubleClick = (event) => {
|
||||
tableCellProps?.onDoubleClick?.(event);
|
||||
openEditingCell({ cell, table });
|
||||
};
|
||||
|
||||
const handleDragEnter = (e) => {
|
||||
tableCellProps?.onDragEnter?.(e);
|
||||
if (enableGrouping && hoveredColumn?.id === "drop-zone") {
|
||||
setHoveredColumn(null);
|
||||
}
|
||||
if (enableColumnOrdering && draggingColumn) {
|
||||
setHoveredColumn(columnDef.enableColumnOrdering !== false ? column : null);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDragOver = (e) => {
|
||||
if (columnDef.enableColumnOrdering !== false) {
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
const handleContextMenu = (e) => {
|
||||
tableCellProps?.onContextMenu?.(e);
|
||||
if (isRightClickable) {
|
||||
e.preventDefault();
|
||||
table.setActionCell(cell);
|
||||
table.refs.actionCellRef.current = e.currentTarget;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<TableCell
|
||||
data-index={staticColumnIndex}
|
||||
data-pinned={!!isColumnPinned || undefined}
|
||||
{...tableCellProps}
|
||||
onContextMenu={handleContextMenu}
|
||||
onDoubleClick={handleDoubleClick}
|
||||
onDragEnter={handleDragEnter}
|
||||
onDragOver={handleDragOver}
|
||||
sx={(theme) => ({
|
||||
"&:hover": {
|
||||
outline:
|
||||
actionCell?.id === cell.id ||
|
||||
(editDisplayMode === "cell" && isEditable) ||
|
||||
(editDisplayMode === "table" && (isCreating || isEditing))
|
||||
? `1px solid ${theme.palette.grey[500]}`
|
||||
: undefined,
|
||||
textOverflow: "clip",
|
||||
},
|
||||
alignItems: layoutMode?.startsWith("grid") ? "center" : undefined,
|
||||
cursor: isRightClickable
|
||||
? "context-menu"
|
||||
: isEditable && editDisplayMode === "cell"
|
||||
? "pointer"
|
||||
: "inherit",
|
||||
outline: actionCell?.id === cell.id ? `1px solid ${theme.palette.grey[500]}` : undefined,
|
||||
outlineOffset: "-1px",
|
||||
overflow: "hidden",
|
||||
p:
|
||||
density === "compact"
|
||||
? columnDefType === "display"
|
||||
? "0 0.5rem"
|
||||
: "0.5rem"
|
||||
: density === "comfortable"
|
||||
? columnDefType === "display"
|
||||
? "0.5rem 0.75rem"
|
||||
: "1rem"
|
||||
: columnDefType === "display"
|
||||
? "1rem 1.25rem"
|
||||
: "1.5rem",
|
||||
|
||||
textOverflow: columnDefType !== "display" ? "ellipsis" : undefined,
|
||||
whiteSpace: row.getIsPinned() || density === "compact" ? "nowrap" : "normal",
|
||||
...getCommonMRTCellStyles({
|
||||
column,
|
||||
table,
|
||||
tableCellProps,
|
||||
theme,
|
||||
}),
|
||||
...draggingBorders,
|
||||
backgroundColor: baseBackgroundColor,
|
||||
})}
|
||||
>
|
||||
{tableCellProps.children ?? (
|
||||
<>
|
||||
{cell.getIsPlaceholder() ? (
|
||||
(columnDef.PlaceholderCell?.({ cell, column, row, table }) ?? null)
|
||||
) : showSkeletons !== false && (isLoading || showSkeletons) ? (
|
||||
<Skeleton animation="wave" height={20} width={skeletonWidth} {...skeletonProps} />
|
||||
) : columnDefType === "display" &&
|
||||
(["mrt-row-expand", "mrt-row-numbers", "mrt-row-select"].includes(column.id) ||
|
||||
!row.getIsGrouped()) ? (
|
||||
columnDef.Cell?.({
|
||||
cell,
|
||||
column,
|
||||
renderedCellValue: cell.renderValue(),
|
||||
row,
|
||||
rowRef,
|
||||
staticColumnIndex,
|
||||
staticRowIndex,
|
||||
table,
|
||||
})
|
||||
) : showClickToCopyButton && columnDef.enableClickToCopy !== false ? (
|
||||
<DataTable_CopyButton cell={cell} table={table}>
|
||||
<DataTable_TableBodyCellValue {...cellValueProps} />
|
||||
</DataTable_CopyButton>
|
||||
) : (
|
||||
<DataTable_TableBodyCellValue {...cellValueProps} />
|
||||
)}
|
||||
{cell.getIsGrouped() && !columnDef.GroupedCell && <> ({row.subRows?.length})</>}
|
||||
</>
|
||||
)}
|
||||
</TableCell>
|
||||
);
|
||||
};
|
||||
export default DataTable_TableBodyCell;
|
||||
|
||||
export const Memo_DataTable_TableBodyCell = memo(DataTable_TableBodyCell, (prev, next) => next.cell === prev.cell);
|
||||
|
||||
@@ -1,103 +1,103 @@
|
||||
import { Box } from "@mui/material";
|
||||
|
||||
const allowedTypes = ["string", "number"];
|
||||
|
||||
const DataTable_TableBodyCellValue = ({ cell, rowRef, staticColumnIndex, staticRowIndex, table }) => {
|
||||
const {
|
||||
getState,
|
||||
options: {
|
||||
enableFilterMatchHighlighting,
|
||||
mrtTheme: { matchHighlightColor },
|
||||
},
|
||||
} = table;
|
||||
const { column, row } = cell;
|
||||
const { columnDef } = column;
|
||||
const { globalFilter, globalFilterFn } = getState();
|
||||
const filterValue = column.getFilterValue();
|
||||
|
||||
let renderedCellValue =
|
||||
cell.getIsAggregated() && columnDef.AggregatedCell
|
||||
? columnDef.AggregatedCell({
|
||||
cell,
|
||||
column,
|
||||
row,
|
||||
table,
|
||||
})
|
||||
: row.getIsGrouped() && !cell.getIsGrouped()
|
||||
? null
|
||||
: cell.getIsGrouped() && columnDef.GroupedCell
|
||||
? columnDef.GroupedCell({
|
||||
cell,
|
||||
column,
|
||||
row,
|
||||
table,
|
||||
})
|
||||
: undefined;
|
||||
|
||||
const isGroupedValue = renderedCellValue !== undefined;
|
||||
|
||||
if (!isGroupedValue) {
|
||||
renderedCellValue = cell.renderValue();
|
||||
}
|
||||
|
||||
if (
|
||||
enableFilterMatchHighlighting &&
|
||||
columnDef.enableFilterMatchHighlighting !== false &&
|
||||
String(renderedCellValue) &&
|
||||
allowedTypes.includes(typeof renderedCellValue) &&
|
||||
((filterValue &&
|
||||
allowedTypes.includes(typeof filterValue) &&
|
||||
["autocomplete", "text"].includes(columnDef.filterVariant)) ||
|
||||
(globalFilter && allowedTypes.includes(typeof globalFilter) && column.getCanGlobalFilter()))
|
||||
) {
|
||||
const chunks = highlightWords?.({
|
||||
matchExactly: (filterValue ? columnDef._filterFn : globalFilterFn) !== "fuzzy",
|
||||
query: (filterValue ?? globalFilter ?? "").toString(),
|
||||
text: renderedCellValue?.toString(),
|
||||
});
|
||||
if (chunks?.length > 1 || chunks?.[0]?.match) {
|
||||
renderedCellValue = (
|
||||
<span aria-label={renderedCellValue} role="note">
|
||||
{chunks?.map(({ key, match, text }) => (
|
||||
<Box
|
||||
aria-hidden="true"
|
||||
component="span"
|
||||
key={key}
|
||||
sx={
|
||||
match
|
||||
? {
|
||||
backgroundColor: matchHighlightColor,
|
||||
borderRadius: "2px",
|
||||
color: (theme) =>
|
||||
theme.palette.mode === "dark"
|
||||
? theme.palette.common.white
|
||||
: theme.palette.common.black,
|
||||
padding: "2px 1px",
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{text}
|
||||
</Box>
|
||||
)) ?? renderedCellValue}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (columnDef.Cell && !isGroupedValue) {
|
||||
renderedCellValue = columnDef.Cell({
|
||||
cell,
|
||||
column,
|
||||
renderedCellValue,
|
||||
row,
|
||||
rowRef,
|
||||
staticColumnIndex,
|
||||
staticRowIndex,
|
||||
table,
|
||||
});
|
||||
}
|
||||
|
||||
return renderedCellValue;
|
||||
};
|
||||
export default DataTable_TableBodyCellValue;
|
||||
import { Box } from "@mui/material";
|
||||
|
||||
const allowedTypes = ["string", "number"];
|
||||
|
||||
const DataTable_TableBodyCellValue = ({ cell, rowRef, staticColumnIndex, staticRowIndex, table }) => {
|
||||
const {
|
||||
getState,
|
||||
options: {
|
||||
enableFilterMatchHighlighting,
|
||||
mrtTheme: { matchHighlightColor },
|
||||
},
|
||||
} = table;
|
||||
const { column, row } = cell;
|
||||
const { columnDef } = column;
|
||||
const { globalFilter, globalFilterFn } = getState();
|
||||
const filterValue = column.getFilterValue();
|
||||
|
||||
let renderedCellValue =
|
||||
cell.getIsAggregated() && columnDef.AggregatedCell
|
||||
? columnDef.AggregatedCell({
|
||||
cell,
|
||||
column,
|
||||
row,
|
||||
table,
|
||||
})
|
||||
: row.getIsGrouped() && !cell.getIsGrouped()
|
||||
? null
|
||||
: cell.getIsGrouped() && columnDef.GroupedCell
|
||||
? columnDef.GroupedCell({
|
||||
cell,
|
||||
column,
|
||||
row,
|
||||
table,
|
||||
})
|
||||
: undefined;
|
||||
|
||||
const isGroupedValue = renderedCellValue !== undefined;
|
||||
|
||||
if (!isGroupedValue) {
|
||||
renderedCellValue = cell.renderValue();
|
||||
}
|
||||
|
||||
if (
|
||||
enableFilterMatchHighlighting &&
|
||||
columnDef.enableFilterMatchHighlighting !== false &&
|
||||
String(renderedCellValue) &&
|
||||
allowedTypes.includes(typeof renderedCellValue) &&
|
||||
((filterValue &&
|
||||
allowedTypes.includes(typeof filterValue) &&
|
||||
["autocomplete", "text"].includes(columnDef.filterVariant)) ||
|
||||
(globalFilter && allowedTypes.includes(typeof globalFilter) && column.getCanGlobalFilter()))
|
||||
) {
|
||||
const chunks = highlightWords?.({
|
||||
matchExactly: (filterValue ? columnDef._filterFn : globalFilterFn) !== "fuzzy",
|
||||
query: (filterValue ?? globalFilter ?? "").toString(),
|
||||
text: renderedCellValue?.toString(),
|
||||
});
|
||||
if (chunks?.length > 1 || chunks?.[0]?.match) {
|
||||
renderedCellValue = (
|
||||
<span aria-label={renderedCellValue} role="note">
|
||||
{chunks?.map(({ key, match, text }) => (
|
||||
<Box
|
||||
aria-hidden="true"
|
||||
component="span"
|
||||
key={key}
|
||||
sx={
|
||||
match
|
||||
? {
|
||||
backgroundColor: matchHighlightColor,
|
||||
borderRadius: "2px",
|
||||
color: (theme) =>
|
||||
theme.palette.mode === "dark"
|
||||
? theme.palette.common.white
|
||||
: theme.palette.common.black,
|
||||
padding: "2px 1px",
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{text}
|
||||
</Box>
|
||||
)) ?? renderedCellValue}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (columnDef.Cell && !isGroupedValue) {
|
||||
renderedCellValue = columnDef.Cell({
|
||||
cell,
|
||||
column,
|
||||
renderedCellValue,
|
||||
row,
|
||||
rowRef,
|
||||
staticColumnIndex,
|
||||
staticRowIndex,
|
||||
table,
|
||||
});
|
||||
}
|
||||
|
||||
return renderedCellValue;
|
||||
};
|
||||
export default DataTable_TableBodyCellValue;
|
||||
|
||||
@@ -1,216 +1,216 @@
|
||||
import { commonCellBeforeAfterStyles, getCommonPinnedCellStyles, parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { TableRow, alpha, darken, lighten } from "@mui/material";
|
||||
import { getIsRowSelected } from "material-react-table";
|
||||
import { memo, useMemo, useRef } from "react";
|
||||
import DataTable_TableBodyCell, { Memo_DataTable_TableBodyCell } from "./TableBodyCell";
|
||||
import DataTable_TableDetailPanel from "./TableDetailPanel";
|
||||
|
||||
const DataTable_TableBodyRow = ({
|
||||
columnVirtualizer,
|
||||
numRows,
|
||||
pinnedRowIds,
|
||||
row,
|
||||
rowVirtualizer,
|
||||
staticRowIndex,
|
||||
table,
|
||||
virtualRow,
|
||||
...rest
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const {
|
||||
getState,
|
||||
options: {
|
||||
enableRowOrdering,
|
||||
enableRowPinning,
|
||||
enableStickyFooter,
|
||||
enableStickyHeader,
|
||||
layoutMode,
|
||||
memoMode,
|
||||
mrtTheme: { baseBackgroundColor, pinnedRowBackgroundColor, selectedRowBackgroundColor },
|
||||
muiTableBodyRowProps,
|
||||
renderDetailPanel,
|
||||
rowPinningDisplayMode,
|
||||
},
|
||||
refs: { tableFooterRef, tableHeadRef },
|
||||
setHoveredRow,
|
||||
} = table;
|
||||
const { density, draggingColumn, draggingRow, editingCell, editingRow, hoveredRow, isFullScreen, rowPinning } =
|
||||
getState();
|
||||
|
||||
const visibleCells = row.getVisibleCells();
|
||||
|
||||
const { virtualColumns, virtualPaddingLeft, virtualPaddingRight } = columnVirtualizer ?? {};
|
||||
|
||||
const isRowSelected = getIsRowSelected({ row, table });
|
||||
const isRowPinned = enableRowPinning && row.getIsPinned();
|
||||
const isDraggingRow = draggingRow?.id === row.id;
|
||||
const isHoveredRow = hoveredRow?.id === row.id;
|
||||
|
||||
const tableRowProps = {
|
||||
...parseFromValuesOrFunc(muiTableBodyRowProps, {
|
||||
row,
|
||||
staticRowIndex,
|
||||
table,
|
||||
}),
|
||||
...rest,
|
||||
};
|
||||
|
||||
const [bottomPinnedIndex, topPinnedIndex] = useMemo(() => {
|
||||
if (!enableRowPinning || !rowPinningDisplayMode?.includes("sticky") || !pinnedRowIds || !row.getIsPinned())
|
||||
return [];
|
||||
return [[...pinnedRowIds].reverse().indexOf(row.id), pinnedRowIds.indexOf(row.id)];
|
||||
}, [pinnedRowIds, rowPinning]);
|
||||
|
||||
const tableHeadHeight = ((enableStickyHeader || isFullScreen) && tableHeadRef.current?.clientHeight) || 0;
|
||||
const tableFooterHeight = (enableStickyFooter && tableFooterRef.current?.clientHeight) || 0;
|
||||
|
||||
const sx = parseFromValuesOrFunc(tableRowProps?.sx, theme);
|
||||
|
||||
const defaultRowHeight = density === "compact" ? 37 : density === "comfortable" ? 53 : 69;
|
||||
|
||||
const customRowHeight =
|
||||
// @ts-ignore
|
||||
parseInt(tableRowProps?.style?.height ?? sx?.height, 10) || undefined;
|
||||
|
||||
const rowHeight = customRowHeight || defaultRowHeight;
|
||||
|
||||
const handleDragEnter = (_e) => {
|
||||
if (enableRowOrdering && draggingRow) {
|
||||
setHoveredRow(row);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDragOver = (e) => {
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
const rowRef = useRef(null);
|
||||
|
||||
const cellHighlightColor = isRowSelected
|
||||
? selectedRowBackgroundColor
|
||||
: isRowPinned
|
||||
? pinnedRowBackgroundColor
|
||||
: undefined;
|
||||
|
||||
const cellHighlightColorHover =
|
||||
tableRowProps?.hover !== false
|
||||
? isRowSelected
|
||||
? cellHighlightColor
|
||||
: theme.palette.mode === "dark"
|
||||
? `${lighten(baseBackgroundColor, 0.2)}`
|
||||
: `${darken(baseBackgroundColor, 0.2)}`
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableRow
|
||||
data-index={renderDetailPanel ? staticRowIndex * 2 : staticRowIndex}
|
||||
data-pinned={!!isRowPinned || undefined}
|
||||
data-selected={isRowSelected || undefined}
|
||||
onDragEnter={handleDragEnter}
|
||||
onDragOver={handleDragOver}
|
||||
ref={(node) => {
|
||||
if (node) {
|
||||
rowRef.current = node;
|
||||
rowVirtualizer?.measureElement(node);
|
||||
}
|
||||
}}
|
||||
selected={isRowSelected}
|
||||
{...tableRowProps}
|
||||
style={{
|
||||
transform: virtualRow ? `translateY(${virtualRow.start}px)` : undefined,
|
||||
...tableRowProps?.style,
|
||||
}}
|
||||
sx={(theme) => ({
|
||||
"&:hover td:after": cellHighlightColorHover
|
||||
? {
|
||||
backgroundColor: alpha(cellHighlightColorHover, 0.3),
|
||||
...commonCellBeforeAfterStyles,
|
||||
}
|
||||
: undefined,
|
||||
bottom:
|
||||
!virtualRow && bottomPinnedIndex !== undefined && isRowPinned
|
||||
? `${bottomPinnedIndex * rowHeight + (enableStickyFooter ? tableFooterHeight - 1 : 0)}px`
|
||||
: undefined,
|
||||
boxSizing: "border-box",
|
||||
display: layoutMode?.startsWith("grid") ? "flex" : undefined,
|
||||
opacity: isRowPinned ? 0.97 : isDraggingRow || isHoveredRow ? 0.5 : 1,
|
||||
position: virtualRow
|
||||
? "absolute"
|
||||
: rowPinningDisplayMode?.includes("sticky") && isRowPinned
|
||||
? "sticky"
|
||||
: "relative",
|
||||
td: {
|
||||
...getCommonPinnedCellStyles({ table, theme }),
|
||||
},
|
||||
"td:after": cellHighlightColor
|
||||
? {
|
||||
backgroundColor: cellHighlightColor,
|
||||
...commonCellBeforeAfterStyles,
|
||||
}
|
||||
: undefined,
|
||||
top: virtualRow
|
||||
? 0
|
||||
: topPinnedIndex !== undefined && isRowPinned
|
||||
? `${
|
||||
topPinnedIndex * rowHeight +
|
||||
(enableStickyHeader || isFullScreen ? tableHeadHeight - 1 : 0)
|
||||
}px`
|
||||
: undefined,
|
||||
transition: virtualRow ? "none" : "all 150ms ease-in-out",
|
||||
width: "100%",
|
||||
zIndex: rowPinningDisplayMode?.includes("sticky") && isRowPinned ? 2 : 0,
|
||||
...sx,
|
||||
})}
|
||||
>
|
||||
{virtualPaddingLeft ? <td style={{ display: "flex", width: virtualPaddingLeft }} /> : null}
|
||||
{(virtualColumns ?? visibleCells).map((cellOrVirtualCell, staticColumnIndex) => {
|
||||
let cell = cellOrVirtualCell;
|
||||
if (columnVirtualizer) {
|
||||
staticColumnIndex = cellOrVirtualCell.index;
|
||||
cell = visibleCells[staticColumnIndex];
|
||||
}
|
||||
const props = {
|
||||
cell,
|
||||
numRows,
|
||||
rowRef,
|
||||
staticColumnIndex,
|
||||
staticRowIndex,
|
||||
table,
|
||||
};
|
||||
return cell ? (
|
||||
memoMode === "cells" &&
|
||||
cell.column.columnDef.columnDefType === "data" &&
|
||||
!draggingColumn &&
|
||||
!draggingRow &&
|
||||
editingCell?.id !== cell.id &&
|
||||
editingRow?.id !== row.id ? (
|
||||
<Memo_DataTable_TableBodyCell key={cell.id} {...props} />
|
||||
) : (
|
||||
<DataTable_TableBodyCell key={cell.id} {...props} />
|
||||
)
|
||||
) : null;
|
||||
})}
|
||||
{virtualPaddingRight ? <td style={{ display: "flex", width: virtualPaddingRight }} /> : null}
|
||||
</TableRow>
|
||||
{renderDetailPanel && !row.getIsGrouped() && (
|
||||
<DataTable_TableDetailPanel
|
||||
parentRowRef={rowRef}
|
||||
row={row}
|
||||
rowVirtualizer={rowVirtualizer}
|
||||
staticRowIndex={staticRowIndex}
|
||||
table={table}
|
||||
virtualRow={virtualRow}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default DataTable_TableBodyRow;
|
||||
|
||||
export const Memo_DataTable_TableBodyRow = memo(
|
||||
DataTable_TableBodyRow,
|
||||
(prev, next) => prev.row === next.row && prev.staticRowIndex === next.staticRowIndex
|
||||
);
|
||||
import { commonCellBeforeAfterStyles, getCommonPinnedCellStyles, parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { TableRow, alpha, darken, lighten } from "@mui/material";
|
||||
import { getIsRowSelected } from "material-react-table";
|
||||
import { memo, useMemo, useRef } from "react";
|
||||
import DataTable_TableBodyCell, { Memo_DataTable_TableBodyCell } from "./TableBodyCell";
|
||||
import DataTable_TableDetailPanel from "./TableDetailPanel";
|
||||
|
||||
const DataTable_TableBodyRow = ({
|
||||
columnVirtualizer,
|
||||
numRows,
|
||||
pinnedRowIds,
|
||||
row,
|
||||
rowVirtualizer,
|
||||
staticRowIndex,
|
||||
table,
|
||||
virtualRow,
|
||||
...rest
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const {
|
||||
getState,
|
||||
options: {
|
||||
enableRowOrdering,
|
||||
enableRowPinning,
|
||||
enableStickyFooter,
|
||||
enableStickyHeader,
|
||||
layoutMode,
|
||||
memoMode,
|
||||
mrtTheme: { baseBackgroundColor, pinnedRowBackgroundColor, selectedRowBackgroundColor },
|
||||
muiTableBodyRowProps,
|
||||
renderDetailPanel,
|
||||
rowPinningDisplayMode,
|
||||
},
|
||||
refs: { tableFooterRef, tableHeadRef },
|
||||
setHoveredRow,
|
||||
} = table;
|
||||
const { density, draggingColumn, draggingRow, editingCell, editingRow, hoveredRow, isFullScreen, rowPinning } =
|
||||
getState();
|
||||
|
||||
const visibleCells = row.getVisibleCells();
|
||||
|
||||
const { virtualColumns, virtualPaddingLeft, virtualPaddingRight } = columnVirtualizer ?? {};
|
||||
|
||||
const isRowSelected = getIsRowSelected({ row, table });
|
||||
const isRowPinned = enableRowPinning && row.getIsPinned();
|
||||
const isDraggingRow = draggingRow?.id === row.id;
|
||||
const isHoveredRow = hoveredRow?.id === row.id;
|
||||
|
||||
const tableRowProps = {
|
||||
...parseFromValuesOrFunc(muiTableBodyRowProps, {
|
||||
row,
|
||||
staticRowIndex,
|
||||
table,
|
||||
}),
|
||||
...rest,
|
||||
};
|
||||
|
||||
const [bottomPinnedIndex, topPinnedIndex] = useMemo(() => {
|
||||
if (!enableRowPinning || !rowPinningDisplayMode?.includes("sticky") || !pinnedRowIds || !row.getIsPinned())
|
||||
return [];
|
||||
return [[...pinnedRowIds].reverse().indexOf(row.id), pinnedRowIds.indexOf(row.id)];
|
||||
}, [pinnedRowIds, rowPinning]);
|
||||
|
||||
const tableHeadHeight = ((enableStickyHeader || isFullScreen) && tableHeadRef.current?.clientHeight) || 0;
|
||||
const tableFooterHeight = (enableStickyFooter && tableFooterRef.current?.clientHeight) || 0;
|
||||
|
||||
const sx = parseFromValuesOrFunc(tableRowProps?.sx, theme);
|
||||
|
||||
const defaultRowHeight = density === "compact" ? 37 : density === "comfortable" ? 53 : 69;
|
||||
|
||||
const customRowHeight =
|
||||
// @ts-ignore
|
||||
parseInt(tableRowProps?.style?.height ?? sx?.height, 10) || undefined;
|
||||
|
||||
const rowHeight = customRowHeight || defaultRowHeight;
|
||||
|
||||
const handleDragEnter = (_e) => {
|
||||
if (enableRowOrdering && draggingRow) {
|
||||
setHoveredRow(row);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDragOver = (e) => {
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
const rowRef = useRef(null);
|
||||
|
||||
const cellHighlightColor = isRowSelected
|
||||
? selectedRowBackgroundColor
|
||||
: isRowPinned
|
||||
? pinnedRowBackgroundColor
|
||||
: undefined;
|
||||
|
||||
const cellHighlightColorHover =
|
||||
tableRowProps?.hover !== false
|
||||
? isRowSelected
|
||||
? cellHighlightColor
|
||||
: theme.palette.mode === "dark"
|
||||
? `${lighten(baseBackgroundColor, 0.2)}`
|
||||
: `${darken(baseBackgroundColor, 0.2)}`
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableRow
|
||||
data-index={renderDetailPanel ? staticRowIndex * 2 : staticRowIndex}
|
||||
data-pinned={!!isRowPinned || undefined}
|
||||
data-selected={isRowSelected || undefined}
|
||||
onDragEnter={handleDragEnter}
|
||||
onDragOver={handleDragOver}
|
||||
ref={(node) => {
|
||||
if (node) {
|
||||
rowRef.current = node;
|
||||
rowVirtualizer?.measureElement(node);
|
||||
}
|
||||
}}
|
||||
selected={isRowSelected}
|
||||
{...tableRowProps}
|
||||
style={{
|
||||
transform: virtualRow ? `translateY(${virtualRow.start}px)` : undefined,
|
||||
...tableRowProps?.style,
|
||||
}}
|
||||
sx={(theme) => ({
|
||||
"&:hover td:after": cellHighlightColorHover
|
||||
? {
|
||||
backgroundColor: alpha(cellHighlightColorHover, 0.3),
|
||||
...commonCellBeforeAfterStyles,
|
||||
}
|
||||
: undefined,
|
||||
bottom:
|
||||
!virtualRow && bottomPinnedIndex !== undefined && isRowPinned
|
||||
? `${bottomPinnedIndex * rowHeight + (enableStickyFooter ? tableFooterHeight - 1 : 0)}px`
|
||||
: undefined,
|
||||
boxSizing: "border-box",
|
||||
display: layoutMode?.startsWith("grid") ? "flex" : undefined,
|
||||
opacity: isRowPinned ? 0.97 : isDraggingRow || isHoveredRow ? 0.5 : 1,
|
||||
position: virtualRow
|
||||
? "absolute"
|
||||
: rowPinningDisplayMode?.includes("sticky") && isRowPinned
|
||||
? "sticky"
|
||||
: "relative",
|
||||
td: {
|
||||
...getCommonPinnedCellStyles({ table, theme }),
|
||||
},
|
||||
"td:after": cellHighlightColor
|
||||
? {
|
||||
backgroundColor: cellHighlightColor,
|
||||
...commonCellBeforeAfterStyles,
|
||||
}
|
||||
: undefined,
|
||||
top: virtualRow
|
||||
? 0
|
||||
: topPinnedIndex !== undefined && isRowPinned
|
||||
? `${
|
||||
topPinnedIndex * rowHeight +
|
||||
(enableStickyHeader || isFullScreen ? tableHeadHeight - 1 : 0)
|
||||
}px`
|
||||
: undefined,
|
||||
transition: virtualRow ? "none" : "all 150ms ease-in-out",
|
||||
width: "100%",
|
||||
zIndex: rowPinningDisplayMode?.includes("sticky") && isRowPinned ? 2 : 0,
|
||||
...sx,
|
||||
})}
|
||||
>
|
||||
{virtualPaddingLeft ? <td style={{ display: "flex", width: virtualPaddingLeft }} /> : null}
|
||||
{(virtualColumns ?? visibleCells).map((cellOrVirtualCell, staticColumnIndex) => {
|
||||
let cell = cellOrVirtualCell;
|
||||
if (columnVirtualizer) {
|
||||
staticColumnIndex = cellOrVirtualCell.index;
|
||||
cell = visibleCells[staticColumnIndex];
|
||||
}
|
||||
const props = {
|
||||
cell,
|
||||
numRows,
|
||||
rowRef,
|
||||
staticColumnIndex,
|
||||
staticRowIndex,
|
||||
table,
|
||||
};
|
||||
return cell ? (
|
||||
memoMode === "cells" &&
|
||||
cell.column.columnDef.columnDefType === "data" &&
|
||||
!draggingColumn &&
|
||||
!draggingRow &&
|
||||
editingCell?.id !== cell.id &&
|
||||
editingRow?.id !== row.id ? (
|
||||
<Memo_DataTable_TableBodyCell key={cell.id} {...props} />
|
||||
) : (
|
||||
<DataTable_TableBodyCell key={cell.id} {...props} />
|
||||
)
|
||||
) : null;
|
||||
})}
|
||||
{virtualPaddingRight ? <td style={{ display: "flex", width: virtualPaddingRight }} /> : null}
|
||||
</TableRow>
|
||||
{renderDetailPanel && !row.getIsGrouped() && (
|
||||
<DataTable_TableDetailPanel
|
||||
parentRowRef={rowRef}
|
||||
row={row}
|
||||
rowVirtualizer={rowVirtualizer}
|
||||
staticRowIndex={staticRowIndex}
|
||||
table={table}
|
||||
virtualRow={virtualRow}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default DataTable_TableBodyRow;
|
||||
|
||||
export const Memo_DataTable_TableBodyRow = memo(
|
||||
DataTable_TableBodyRow,
|
||||
(prev, next) => prev.row === next.row && prev.staticRowIndex === next.staticRowIndex
|
||||
);
|
||||
|
||||
@@ -1,87 +1,87 @@
|
||||
import { parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||
import { Collapse, TableCell, TableRow } from "@mui/material";
|
||||
|
||||
const DataTable_TableDetailPanel = ({
|
||||
parentRowRef,
|
||||
row,
|
||||
rowVirtualizer,
|
||||
staticRowIndex,
|
||||
table,
|
||||
virtualRow,
|
||||
...rest
|
||||
}) => {
|
||||
const {
|
||||
getState,
|
||||
getVisibleLeafColumns,
|
||||
options: {
|
||||
layoutMode,
|
||||
mrtTheme: { baseBackgroundColor },
|
||||
muiDetailPanelProps,
|
||||
muiTableBodyRowProps,
|
||||
renderDetailPanel,
|
||||
},
|
||||
} = table;
|
||||
const { isLoading } = getState();
|
||||
|
||||
const tableRowProps = parseFromValuesOrFunc(muiTableBodyRowProps, {
|
||||
isDetailPanel: true,
|
||||
row,
|
||||
staticRowIndex,
|
||||
table,
|
||||
});
|
||||
|
||||
const tableCellProps = {
|
||||
...parseFromValuesOrFunc(muiDetailPanelProps, {
|
||||
row,
|
||||
table,
|
||||
}),
|
||||
...rest,
|
||||
};
|
||||
|
||||
const DetailPanel = !isLoading && renderDetailPanel?.({ row, table });
|
||||
|
||||
return (
|
||||
<TableRow
|
||||
className="Mui-TableBodyCell-DetailPanel"
|
||||
data-index={renderDetailPanel ? staticRowIndex * 2 + 1 : staticRowIndex}
|
||||
ref={(node) => {
|
||||
if (node) {
|
||||
rowVirtualizer?.measureElement?.(node);
|
||||
}
|
||||
}}
|
||||
{...tableRowProps}
|
||||
sx={(theme) => ({
|
||||
display: layoutMode?.startsWith("grid") ? "flex" : undefined,
|
||||
position: virtualRow ? "absolute" : undefined,
|
||||
top: virtualRow ? `${parentRowRef.current?.getBoundingClientRect()?.height}px` : undefined,
|
||||
transform: virtualRow ? `translateY(${virtualRow?.start}px)` : undefined,
|
||||
width: "100%",
|
||||
...parseFromValuesOrFunc(tableRowProps?.sx, theme),
|
||||
})}
|
||||
>
|
||||
<TableCell
|
||||
className="Mui-TableBodyCell-DetailPanel"
|
||||
colSpan={getVisibleLeafColumns().length}
|
||||
{...tableCellProps}
|
||||
sx={(theme) => ({
|
||||
backgroundColor: virtualRow ? baseBackgroundColor : undefined,
|
||||
borderBottom: !row.getIsExpanded() ? "none" : undefined,
|
||||
display: layoutMode?.startsWith("grid") ? "flex" : undefined,
|
||||
py: !!DetailPanel && row.getIsExpanded() ? "1rem" : 0,
|
||||
transition: !virtualRow ? "all 150ms ease-in-out" : undefined,
|
||||
width: `100%`,
|
||||
...parseFromValuesOrFunc(tableCellProps?.sx, theme),
|
||||
})}
|
||||
>
|
||||
{virtualRow ? (
|
||||
row.getIsExpanded() && DetailPanel
|
||||
) : (
|
||||
<Collapse in={row.getIsExpanded()} mountOnEnter unmountOnExit>
|
||||
{DetailPanel}
|
||||
</Collapse>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
};
|
||||
export default DataTable_TableDetailPanel;
|
||||
import { parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||
import { Collapse, TableCell, TableRow } from "@mui/material";
|
||||
|
||||
const DataTable_TableDetailPanel = ({
|
||||
parentRowRef,
|
||||
row,
|
||||
rowVirtualizer,
|
||||
staticRowIndex,
|
||||
table,
|
||||
virtualRow,
|
||||
...rest
|
||||
}) => {
|
||||
const {
|
||||
getState,
|
||||
getVisibleLeafColumns,
|
||||
options: {
|
||||
layoutMode,
|
||||
mrtTheme: { baseBackgroundColor },
|
||||
muiDetailPanelProps,
|
||||
muiTableBodyRowProps,
|
||||
renderDetailPanel,
|
||||
},
|
||||
} = table;
|
||||
const { isLoading } = getState();
|
||||
|
||||
const tableRowProps = parseFromValuesOrFunc(muiTableBodyRowProps, {
|
||||
isDetailPanel: true,
|
||||
row,
|
||||
staticRowIndex,
|
||||
table,
|
||||
});
|
||||
|
||||
const tableCellProps = {
|
||||
...parseFromValuesOrFunc(muiDetailPanelProps, {
|
||||
row,
|
||||
table,
|
||||
}),
|
||||
...rest,
|
||||
};
|
||||
|
||||
const DetailPanel = !isLoading && renderDetailPanel?.({ row, table });
|
||||
|
||||
return (
|
||||
<TableRow
|
||||
className="Mui-TableBodyCell-DetailPanel"
|
||||
data-index={renderDetailPanel ? staticRowIndex * 2 + 1 : staticRowIndex}
|
||||
ref={(node) => {
|
||||
if (node) {
|
||||
rowVirtualizer?.measureElement?.(node);
|
||||
}
|
||||
}}
|
||||
{...tableRowProps}
|
||||
sx={(theme) => ({
|
||||
display: layoutMode?.startsWith("grid") ? "flex" : undefined,
|
||||
position: virtualRow ? "absolute" : undefined,
|
||||
top: virtualRow ? `${parentRowRef.current?.getBoundingClientRect()?.height}px` : undefined,
|
||||
transform: virtualRow ? `translateY(${virtualRow?.start}px)` : undefined,
|
||||
width: "100%",
|
||||
...parseFromValuesOrFunc(tableRowProps?.sx, theme),
|
||||
})}
|
||||
>
|
||||
<TableCell
|
||||
className="Mui-TableBodyCell-DetailPanel"
|
||||
colSpan={getVisibleLeafColumns().length}
|
||||
{...tableCellProps}
|
||||
sx={(theme) => ({
|
||||
backgroundColor: virtualRow ? baseBackgroundColor : undefined,
|
||||
borderBottom: !row.getIsExpanded() ? "none" : undefined,
|
||||
display: layoutMode?.startsWith("grid") ? "flex" : undefined,
|
||||
py: !!DetailPanel && row.getIsExpanded() ? "1rem" : 0,
|
||||
transition: !virtualRow ? "all 150ms ease-in-out" : undefined,
|
||||
width: `100%`,
|
||||
...parseFromValuesOrFunc(tableCellProps?.sx, theme),
|
||||
})}
|
||||
>
|
||||
{virtualRow ? (
|
||||
row.getIsExpanded() && DetailPanel
|
||||
) : (
|
||||
<Collapse in={row.getIsExpanded()} mountOnEnter unmountOnExit>
|
||||
{DetailPanel}
|
||||
</Collapse>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
};
|
||||
export default DataTable_TableDetailPanel;
|
||||
|
||||
@@ -1,68 +1,68 @@
|
||||
import { getCommonTooltipProps, parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||
import { Button, Tooltip } from "@mui/material";
|
||||
import { useState } from "react";
|
||||
|
||||
const DataTable_CopyButton = ({ cell, table, ...rest }) => {
|
||||
const {
|
||||
options: { localization, muiCopyButtonProps },
|
||||
} = table;
|
||||
const { column, row } = cell;
|
||||
const { columnDef } = column;
|
||||
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
const handleCopy = (event, text) => {
|
||||
event.stopPropagation();
|
||||
navigator.clipboard.writeText(text);
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 4000);
|
||||
};
|
||||
|
||||
const buttonProps = {
|
||||
...parseFromValuesOrFunc(muiCopyButtonProps, {
|
||||
cell,
|
||||
column,
|
||||
row,
|
||||
table,
|
||||
}),
|
||||
...parseFromValuesOrFunc(columnDef.muiCopyButtonProps, {
|
||||
cell,
|
||||
column,
|
||||
row,
|
||||
table,
|
||||
}),
|
||||
...rest,
|
||||
};
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
{...getCommonTooltipProps("top")}
|
||||
title={buttonProps?.title ?? (copied ? localization.copiedToClipboard : localization.clickToCopy)}
|
||||
>
|
||||
<Button
|
||||
onClick={(e) => handleCopy(e, cell.getValue())}
|
||||
size="small"
|
||||
type="button"
|
||||
variant="text"
|
||||
{...buttonProps}
|
||||
sx={(theme) => ({
|
||||
backgroundColor: "transparent",
|
||||
border: "none",
|
||||
color: "inherit",
|
||||
cursor: "copy",
|
||||
fontFamily: "inherit",
|
||||
fontSize: "inherit",
|
||||
letterSpacing: "inherit",
|
||||
m: "-0.25rem",
|
||||
minWidth: "unset",
|
||||
py: 0,
|
||||
textAlign: "inherit",
|
||||
textTransform: "inherit",
|
||||
...parseFromValuesOrFunc(buttonProps?.sx, theme),
|
||||
})}
|
||||
title={undefined}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
export default DataTable_CopyButton;
|
||||
import { getCommonTooltipProps, parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||
import { Button, Tooltip } from "@mui/material";
|
||||
import { useState } from "react";
|
||||
|
||||
const DataTable_CopyButton = ({ cell, table, ...rest }) => {
|
||||
const {
|
||||
options: { localization, muiCopyButtonProps },
|
||||
} = table;
|
||||
const { column, row } = cell;
|
||||
const { columnDef } = column;
|
||||
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
const handleCopy = (event, text) => {
|
||||
event.stopPropagation();
|
||||
navigator.clipboard.writeText(text);
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 4000);
|
||||
};
|
||||
|
||||
const buttonProps = {
|
||||
...parseFromValuesOrFunc(muiCopyButtonProps, {
|
||||
cell,
|
||||
column,
|
||||
row,
|
||||
table,
|
||||
}),
|
||||
...parseFromValuesOrFunc(columnDef.muiCopyButtonProps, {
|
||||
cell,
|
||||
column,
|
||||
row,
|
||||
table,
|
||||
}),
|
||||
...rest,
|
||||
};
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
{...getCommonTooltipProps("top")}
|
||||
title={buttonProps?.title ?? (copied ? localization.copiedToClipboard : localization.clickToCopy)}
|
||||
>
|
||||
<Button
|
||||
onClick={(e) => handleCopy(e, cell.getValue())}
|
||||
size="small"
|
||||
type="button"
|
||||
variant="text"
|
||||
{...buttonProps}
|
||||
sx={(theme) => ({
|
||||
backgroundColor: "transparent",
|
||||
border: "none",
|
||||
color: "inherit",
|
||||
cursor: "copy",
|
||||
fontFamily: "inherit",
|
||||
fontSize: "inherit",
|
||||
letterSpacing: "inherit",
|
||||
m: "-0.25rem",
|
||||
minWidth: "unset",
|
||||
py: 0,
|
||||
textAlign: "inherit",
|
||||
textTransform: "inherit",
|
||||
...parseFromValuesOrFunc(buttonProps?.sx, theme),
|
||||
})}
|
||||
title={undefined}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
export default DataTable_CopyButton;
|
||||
|
||||
@@ -1,46 +1,46 @@
|
||||
import { getCommonTooltipProps, parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||
import { IconButton, Tooltip } from "@mui/material";
|
||||
|
||||
const DataTable_GrabHandleButton = ({ location, table, ...rest }) => {
|
||||
const {
|
||||
options: {
|
||||
icons: { DragHandleIcon },
|
||||
localization,
|
||||
},
|
||||
} = table;
|
||||
|
||||
return (
|
||||
<Tooltip {...getCommonTooltipProps("top")} title={rest?.title ?? localization.move}>
|
||||
<IconButton
|
||||
aria-label={rest.title ?? localization.move}
|
||||
disableRipple
|
||||
draggable="true"
|
||||
size="small"
|
||||
{...rest}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
rest?.onClick?.(e);
|
||||
}}
|
||||
sx={(theme) => ({
|
||||
"&:active": {
|
||||
cursor: "grabbing",
|
||||
},
|
||||
"&:hover": {
|
||||
backgroundColor: "transparent",
|
||||
opacity: 1,
|
||||
},
|
||||
cursor: "grab",
|
||||
m: "0 -0.1rem",
|
||||
opacity: location === "row" ? 1 : 0.5,
|
||||
p: "2px",
|
||||
transition: "all 150ms ease-in-out",
|
||||
...parseFromValuesOrFunc(rest?.sx, theme),
|
||||
})}
|
||||
title={undefined}
|
||||
>
|
||||
<DragHandleIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
export default DataTable_GrabHandleButton;
|
||||
import { getCommonTooltipProps, parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||
import { IconButton, Tooltip } from "@mui/material";
|
||||
|
||||
const DataTable_GrabHandleButton = ({ location, table, ...rest }) => {
|
||||
const {
|
||||
options: {
|
||||
icons: { DragHandleIcon },
|
||||
localization,
|
||||
},
|
||||
} = table;
|
||||
|
||||
return (
|
||||
<Tooltip {...getCommonTooltipProps("top")} title={rest?.title ?? localization.move}>
|
||||
<IconButton
|
||||
aria-label={rest.title ?? localization.move}
|
||||
disableRipple
|
||||
draggable="true"
|
||||
size="small"
|
||||
{...rest}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
rest?.onClick?.(e);
|
||||
}}
|
||||
sx={(theme) => ({
|
||||
"&:active": {
|
||||
cursor: "grabbing",
|
||||
},
|
||||
"&:hover": {
|
||||
backgroundColor: "transparent",
|
||||
opacity: 1,
|
||||
},
|
||||
cursor: "grab",
|
||||
m: "0 -0.1rem",
|
||||
opacity: location === "row" ? 1 : 0.5,
|
||||
p: "2px",
|
||||
transition: "all 150ms ease-in-out",
|
||||
...parseFromValuesOrFunc(rest?.sx, theme),
|
||||
})}
|
||||
title={undefined}
|
||||
>
|
||||
<DragHandleIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
export default DataTable_GrabHandleButton;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const DataTable_ToggleFiltersButton = ({ table, ...rest }) => {
|
||||
return <></>;
|
||||
};
|
||||
export default DataTable_ToggleFiltersButton;
|
||||
const DataTable_ToggleFiltersButton = ({ table, ...rest }) => {
|
||||
return <></>;
|
||||
};
|
||||
export default DataTable_ToggleFiltersButton;
|
||||
|
||||
@@ -1,130 +1,130 @@
|
||||
"use client";
|
||||
|
||||
import FilterBodyControllerWithDependency from "@/core/components/DataTable/filter/FilterBodyControllerWithDependency";
|
||||
import FilterHeader from "@/core/components/DataTable/filter/FilterHeader";
|
||||
import useDataTable from "@/lib/hooks/useDataTable";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import { Box, Button } from "@mui/material";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import * as Yup from "yup";
|
||||
import ScrollBox from "../../ScrollBox";
|
||||
import FilterBodyController from "./FilterBodyController";
|
||||
|
||||
function FilterBody({ columns, drawerState, setDrawerState }) {
|
||||
const { filterData, setFilterData } = useDataTable();
|
||||
const [validationSchema, setValidationSchema] = useState(Yup.object({}));
|
||||
|
||||
useEffect(() => {
|
||||
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())
|
||||
.test({
|
||||
test(value, ctx) {
|
||||
const [start, end] = value || ["", ""];
|
||||
if (
|
||||
initialValue.datatype === "numeric" &&
|
||||
parseInt(end, 10) <= parseInt(start, 10)
|
||||
) {
|
||||
return ctx.createError({
|
||||
message: `مقدار وارده باید بیشتر از (${start}) باشد`,
|
||||
});
|
||||
} else if ((start && !end) || (!start && end)) {
|
||||
return ctx.createError({
|
||||
message: "این بخش را تکمیل نمایید",
|
||||
});
|
||||
}
|
||||
return true;
|
||||
},
|
||||
}),
|
||||
});
|
||||
}
|
||||
return acc;
|
||||
}, {})
|
||||
);
|
||||
|
||||
setValidationSchema(schema);
|
||||
}, [filterData]);
|
||||
|
||||
const {
|
||||
handleSubmit,
|
||||
control,
|
||||
reset,
|
||||
formState: { errors, isDirty, isValid },
|
||||
} = useForm({
|
||||
defaultValues: filterData,
|
||||
resolver: yupResolver(validationSchema),
|
||||
mode: "all",
|
||||
});
|
||||
|
||||
const onSubmit = (data) => {
|
||||
setDrawerState(false);
|
||||
setFilterData(data);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<FilterHeader setDrawerState={setDrawerState} />
|
||||
{Object.keys(filterData).length > 0 && (
|
||||
<ScrollBox>
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<Box sx={{ px: 2, py: 3 }}>
|
||||
{columns.map((column) =>
|
||||
column.enableColumnFilter ? (
|
||||
column.dependencyId ? (
|
||||
<FilterBodyControllerWithDependency
|
||||
key={column.id}
|
||||
column={column}
|
||||
control={control}
|
||||
errors={errors}
|
||||
reset={reset}
|
||||
/>
|
||||
) : (
|
||||
<FilterBodyController
|
||||
key={column.id}
|
||||
column={column}
|
||||
control={control}
|
||||
errors={errors}
|
||||
reset={reset}
|
||||
/>
|
||||
)
|
||||
) : null
|
||||
)}
|
||||
</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>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default FilterBody;
|
||||
"use client";
|
||||
|
||||
import FilterBodyControllerWithDependency from "@/core/components/DataTable/filter/FilterBodyControllerWithDependency";
|
||||
import FilterHeader from "@/core/components/DataTable/filter/FilterHeader";
|
||||
import useDataTable from "@/lib/hooks/useDataTable";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import { Box, Button } from "@mui/material";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import * as Yup from "yup";
|
||||
import ScrollBox from "../../ScrollBox";
|
||||
import FilterBodyController from "./FilterBodyController";
|
||||
|
||||
function FilterBody({ columns, drawerState, setDrawerState }) {
|
||||
const { filterData, setFilterData } = useDataTable();
|
||||
const [validationSchema, setValidationSchema] = useState(Yup.object({}));
|
||||
|
||||
useEffect(() => {
|
||||
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())
|
||||
.test({
|
||||
test(value, ctx) {
|
||||
const [start, end] = value || ["", ""];
|
||||
if (
|
||||
initialValue.datatype === "numeric" &&
|
||||
parseInt(end, 10) <= parseInt(start, 10)
|
||||
) {
|
||||
return ctx.createError({
|
||||
message: `مقدار وارده باید بیشتر از (${start}) باشد`,
|
||||
});
|
||||
} else if ((start && !end) || (!start && end)) {
|
||||
return ctx.createError({
|
||||
message: "این بخش را تکمیل نمایید",
|
||||
});
|
||||
}
|
||||
return true;
|
||||
},
|
||||
}),
|
||||
});
|
||||
}
|
||||
return acc;
|
||||
}, {})
|
||||
);
|
||||
|
||||
setValidationSchema(schema);
|
||||
}, [filterData]);
|
||||
|
||||
const {
|
||||
handleSubmit,
|
||||
control,
|
||||
reset,
|
||||
formState: { errors, isDirty, isValid },
|
||||
} = useForm({
|
||||
defaultValues: filterData,
|
||||
resolver: yupResolver(validationSchema),
|
||||
mode: "all",
|
||||
});
|
||||
|
||||
const onSubmit = (data) => {
|
||||
setDrawerState(false);
|
||||
setFilterData(data);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<FilterHeader setDrawerState={setDrawerState} />
|
||||
{Object.keys(filterData).length > 0 && (
|
||||
<ScrollBox>
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<Box sx={{ px: 2, py: 3 }}>
|
||||
{columns.map((column) =>
|
||||
column.enableColumnFilter ? (
|
||||
column.dependencyId ? (
|
||||
<FilterBodyControllerWithDependency
|
||||
key={column.id}
|
||||
column={column}
|
||||
control={control}
|
||||
errors={errors}
|
||||
reset={reset}
|
||||
/>
|
||||
) : (
|
||||
<FilterBodyController
|
||||
key={column.id}
|
||||
column={column}
|
||||
control={control}
|
||||
errors={errors}
|
||||
reset={reset}
|
||||
/>
|
||||
)
|
||||
) : null
|
||||
)}
|
||||
</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>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default FilterBody;
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
import { Controller, useWatch } from "react-hook-form";
|
||||
import FilterBodyField from "./FilterBodyField";
|
||||
|
||||
const FilterBodyController = ({ column, control, reset, errors }) => {
|
||||
return (
|
||||
<Controller
|
||||
name={`${column.id}`}
|
||||
control={control}
|
||||
render={({ field: { value, name, onBlur, onChange } }) => {
|
||||
return (
|
||||
<FilterBodyField
|
||||
column={column}
|
||||
filterParameters={value}
|
||||
handleChange={onChange}
|
||||
handleBlur={onBlur}
|
||||
dependencyFieldValue={null}
|
||||
resetForm={() => reset()}
|
||||
errors={errors}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export default FilterBodyController;
|
||||
import { Controller, useWatch } from "react-hook-form";
|
||||
import FilterBodyField from "./FilterBodyField";
|
||||
|
||||
const FilterBodyController = ({ column, control, reset, errors }) => {
|
||||
return (
|
||||
<Controller
|
||||
name={`${column.id}`}
|
||||
control={control}
|
||||
render={({ field: { value, name, onBlur, onChange } }) => {
|
||||
return (
|
||||
<FilterBodyField
|
||||
column={column}
|
||||
filterParameters={value}
|
||||
handleChange={onChange}
|
||||
handleBlur={onBlur}
|
||||
dependencyFieldValue={null}
|
||||
resetForm={() => reset()}
|
||||
errors={errors}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export default FilterBodyController;
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
import { Controller, useWatch } from "react-hook-form";
|
||||
import FilterBodyField from "./FilterBodyField";
|
||||
|
||||
const FilterBodyControllerWithDependency = ({ column, control, reset, errors }) => {
|
||||
const dependencyField = useWatch({ control, name: column.dependencyId });
|
||||
|
||||
return (
|
||||
<Controller
|
||||
name={`${column.id}`}
|
||||
control={control}
|
||||
render={({ field: { value, name, onBlur, onChange } }) => {
|
||||
return (
|
||||
<FilterBodyField
|
||||
column={column}
|
||||
filterParameters={value}
|
||||
handleChange={onChange}
|
||||
handleBlur={onBlur}
|
||||
dependencyFieldValue={dependencyField}
|
||||
resetForm={() => reset()}
|
||||
errors={errors}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export default FilterBodyControllerWithDependency;
|
||||
import { Controller, useWatch } from "react-hook-form";
|
||||
import FilterBodyField from "./FilterBodyField";
|
||||
|
||||
const FilterBodyControllerWithDependency = ({ column, control, reset, errors }) => {
|
||||
const dependencyField = useWatch({ control, name: column.dependencyId });
|
||||
|
||||
return (
|
||||
<Controller
|
||||
name={`${column.id}`}
|
||||
control={control}
|
||||
render={({ field: { value, name, onBlur, onChange } }) => {
|
||||
return (
|
||||
<FilterBodyField
|
||||
column={column}
|
||||
filterParameters={value}
|
||||
handleChange={onChange}
|
||||
handleBlur={onBlur}
|
||||
dependencyFieldValue={dependencyField}
|
||||
resetForm={() => reset()}
|
||||
errors={errors}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export default FilterBodyControllerWithDependency;
|
||||
|
||||
@@ -1,104 +1,104 @@
|
||||
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 { useState } from "react";
|
||||
|
||||
const columnFilterModeOptionFa = {
|
||||
equals: "برابر",
|
||||
notEquals: "نابرابر",
|
||||
contains: "شامل",
|
||||
lessThan: "کوچکتر",
|
||||
greaterThan: "بزرگتر",
|
||||
fuzzy: "فازی",
|
||||
between: "مابین",
|
||||
};
|
||||
|
||||
function FilterBodyField({
|
||||
column,
|
||||
filterParameters,
|
||||
handleChange,
|
||||
handleBlur,
|
||||
dependencyFieldValue,
|
||||
setFieldValue,
|
||||
resetForm,
|
||||
errors,
|
||||
}) {
|
||||
const [anchorEl, setAnchorEl] = useState(null);
|
||||
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 column.ColumnSelectComponent ? (
|
||||
<column.ColumnSelectComponent {...commonProps} />
|
||||
) : (
|
||||
<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 (
|
||||
<>
|
||||
{renderField()}
|
||||
{Array.isArray(column.columnFilterModeOptions) && (
|
||||
<FilterOptionList
|
||||
column={column}
|
||||
anchorEl={anchorEl}
|
||||
setAnchorEl={setAnchorEl}
|
||||
filterParameters={filterParameters}
|
||||
filterType={filterParameters.filterMode}
|
||||
handleChange={handleChange}
|
||||
resetForm={resetForm}
|
||||
filterOption={column.columnFilterModeOptions}
|
||||
columnFilterModeOptionFa={columnFilterModeOptionFa}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default FilterBodyField;
|
||||
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 { useState } from "react";
|
||||
|
||||
const columnFilterModeOptionFa = {
|
||||
equals: "برابر",
|
||||
notEquals: "نابرابر",
|
||||
contains: "شامل",
|
||||
lessThan: "کوچکتر",
|
||||
greaterThan: "بزرگتر",
|
||||
fuzzy: "فازی",
|
||||
between: "مابین",
|
||||
};
|
||||
|
||||
function FilterBodyField({
|
||||
column,
|
||||
filterParameters,
|
||||
handleChange,
|
||||
handleBlur,
|
||||
dependencyFieldValue,
|
||||
setFieldValue,
|
||||
resetForm,
|
||||
errors,
|
||||
}) {
|
||||
const [anchorEl, setAnchorEl] = useState(null);
|
||||
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 column.ColumnSelectComponent ? (
|
||||
<column.ColumnSelectComponent {...commonProps} />
|
||||
) : (
|
||||
<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 (
|
||||
<>
|
||||
{renderField()}
|
||||
{Array.isArray(column.columnFilterModeOptions) && (
|
||||
<FilterOptionList
|
||||
column={column}
|
||||
anchorEl={anchorEl}
|
||||
setAnchorEl={setAnchorEl}
|
||||
filterParameters={filterParameters}
|
||||
filterType={filterParameters.filterMode}
|
||||
handleChange={handleChange}
|
||||
resetForm={resetForm}
|
||||
filterOption={column.columnFilterModeOptions}
|
||||
columnFilterModeOptionFa={columnFilterModeOptionFa}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default FilterBodyField;
|
||||
|
||||
@@ -1,37 +1,37 @@
|
||||
"use client";
|
||||
import FilterAltIcon from "@mui/icons-material/FilterAlt";
|
||||
import { IconButton, Tooltip, Badge } from "@mui/material";
|
||||
import useDataTable from "@/lib/hooks/useDataTable";
|
||||
|
||||
function FilterButton({ drawerState, setDrawerState }) {
|
||||
const { filterData } = useDataTable();
|
||||
const isValueEmpty = (value) => {
|
||||
if (Array.isArray(value)) {
|
||||
return value.length === 0 || value.every((v) => v === "");
|
||||
}
|
||||
return value === "" || value === null || value === undefined;
|
||||
};
|
||||
const filteredFilterData = Object.values(filterData).filter((filter) => !isValueEmpty(filter.value));
|
||||
return (
|
||||
<Tooltip title="فیلتر">
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
setDrawerState(!drawerState);
|
||||
}}
|
||||
aria-label="filter table"
|
||||
>
|
||||
<Badge
|
||||
badgeContent={filteredFilterData.length}
|
||||
color="primary"
|
||||
anchorOrigin={{
|
||||
vertical: "top",
|
||||
horizontal: "left",
|
||||
}}
|
||||
>
|
||||
<FilterAltIcon />
|
||||
</Badge>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
export default FilterButton;
|
||||
"use client";
|
||||
import FilterAltIcon from "@mui/icons-material/FilterAlt";
|
||||
import { IconButton, Tooltip, Badge } from "@mui/material";
|
||||
import useDataTable from "@/lib/hooks/useDataTable";
|
||||
|
||||
function FilterButton({ drawerState, setDrawerState }) {
|
||||
const { filterData } = useDataTable();
|
||||
const isValueEmpty = (value) => {
|
||||
if (Array.isArray(value)) {
|
||||
return value.length === 0 || value.every((v) => v === "");
|
||||
}
|
||||
return value === "" || value === null || value === undefined;
|
||||
};
|
||||
const filteredFilterData = Object.values(filterData).filter((filter) => !isValueEmpty(filter.value));
|
||||
return (
|
||||
<Tooltip title="فیلتر">
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
setDrawerState(!drawerState);
|
||||
}}
|
||||
aria-label="filter table"
|
||||
>
|
||||
<Badge
|
||||
badgeContent={filteredFilterData.length}
|
||||
color="primary"
|
||||
anchorOrigin={{
|
||||
vertical: "top",
|
||||
horizontal: "left",
|
||||
}}
|
||||
>
|
||||
<FilterAltIcon />
|
||||
</Badge>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
export default FilterButton;
|
||||
|
||||
@@ -1,32 +1,32 @@
|
||||
import { useCallback, useState } from "react";
|
||||
import FilterButton from "./FilterButton";
|
||||
import { Box, Drawer } from "@mui/material";
|
||||
import FilterDrawer from "../../FilterDrawer";
|
||||
|
||||
const FilterCustom = ({ filterData, setFilterData }) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const closeDrawer = useCallback(() => setOpen(false), []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<FilterButton drawerState={open} setDrawerState={setOpen} />
|
||||
<Drawer
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
sx={{ overflowY: "hidden", display: "flex", flexDirection: "column", height: "100%", zIndex: "1300" }}
|
||||
>
|
||||
<Box sx={{ width: { xs: 300, sm: 450 } }}>
|
||||
{open && (
|
||||
<FilterDrawer
|
||||
defaultValues={filterData}
|
||||
setFilterData={setFilterData}
|
||||
closeDrawer={closeDrawer}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Drawer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default FilterCustom;
|
||||
import { useCallback, useState } from "react";
|
||||
import FilterButton from "./FilterButton";
|
||||
import { Box, Drawer } from "@mui/material";
|
||||
import FilterDrawer from "../../FilterDrawer";
|
||||
|
||||
const FilterCustom = ({ filterData, setFilterData }) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const closeDrawer = useCallback(() => setOpen(false), []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<FilterButton drawerState={open} setDrawerState={setOpen} />
|
||||
<Drawer
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
sx={{ overflowY: "hidden", display: "flex", flexDirection: "column", height: "100%", zIndex: "1300" }}
|
||||
>
|
||||
<Box sx={{ width: { xs: 300, sm: 450 } }}>
|
||||
{open && (
|
||||
<FilterDrawer
|
||||
defaultValues={filterData}
|
||||
setFilterData={setFilterData}
|
||||
closeDrawer={closeDrawer}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Drawer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default FilterCustom;
|
||||
|
||||
@@ -1,34 +1,34 @@
|
||||
"use client";
|
||||
|
||||
import CancelIcon from "@mui/icons-material/Cancel";
|
||||
import FilterAltIcon from "@mui/icons-material/FilterAlt";
|
||||
import { Box, IconButton, Typography } from "@mui/material";
|
||||
|
||||
function FilterHeader({ setDrawerState }) {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
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",
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||
<FilterAltIcon sx={{ color: "#fff", mr: 1 }} />
|
||||
<Typography variant="h6" sx={{ color: "#fff" }}>
|
||||
فیلتر
|
||||
</Typography>
|
||||
</Box>
|
||||
<IconButton sx={{ color: "#fff" }} onClick={() => setDrawerState(false)}>
|
||||
<CancelIcon sx={{ fontSize: "1em" }} />
|
||||
</IconButton>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default FilterHeader;
|
||||
"use client";
|
||||
|
||||
import CancelIcon from "@mui/icons-material/Cancel";
|
||||
import FilterAltIcon from "@mui/icons-material/FilterAlt";
|
||||
import { Box, IconButton, Typography } from "@mui/material";
|
||||
|
||||
function FilterHeader({ setDrawerState }) {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
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",
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||
<FilterAltIcon sx={{ color: "#fff", mr: 1 }} />
|
||||
<Typography variant="h6" sx={{ color: "#fff" }}>
|
||||
فیلتر
|
||||
</Typography>
|
||||
</Box>
|
||||
<IconButton sx={{ color: "#fff" }} onClick={() => setDrawerState(false)}>
|
||||
<CancelIcon sx={{ fontSize: "1em" }} />
|
||||
</IconButton>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default FilterHeader;
|
||||
|
||||
@@ -1,47 +1,47 @@
|
||||
"use client";
|
||||
|
||||
import { ListItem, Menu } from "@mui/material";
|
||||
import { useState } from "react";
|
||||
|
||||
function FilterOptionList({
|
||||
filterType,
|
||||
filterOption,
|
||||
filterParameters,
|
||||
anchorEl,
|
||||
columnFilterModeOptionFa,
|
||||
setAnchorEl,
|
||||
handleChange,
|
||||
}) {
|
||||
const [selectedFilter, setSelectedFilter] = useState(filterType);
|
||||
|
||||
const handleChangeItem = (event, index) => {
|
||||
handleChange({
|
||||
...filterParameters,
|
||||
value: filterOption[index] === "between" ? ["", ""] : "",
|
||||
filterMode: filterOption[index],
|
||||
});
|
||||
setSelectedFilter(filterOption[index]);
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
const handleCloseFilterBox = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<Menu id="simple-menu" anchorEl={anchorEl} keepMounted open={Boolean(anchorEl)} onClose={handleCloseFilterBox}>
|
||||
{filterOption.map((option, index) => (
|
||||
<ListItem
|
||||
key={index}
|
||||
onClick={(event) => handleChangeItem(event, index)}
|
||||
selected={option === selectedFilter}
|
||||
sx={{ cursor: "pointer", width: "100px", display: "flex", justifyContent: "center" }}
|
||||
>
|
||||
{columnFilterModeOptionFa[option]}
|
||||
</ListItem>
|
||||
))}
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
|
||||
export default FilterOptionList;
|
||||
"use client";
|
||||
|
||||
import { ListItem, Menu } from "@mui/material";
|
||||
import { useState } from "react";
|
||||
|
||||
function FilterOptionList({
|
||||
filterType,
|
||||
filterOption,
|
||||
filterParameters,
|
||||
anchorEl,
|
||||
columnFilterModeOptionFa,
|
||||
setAnchorEl,
|
||||
handleChange,
|
||||
}) {
|
||||
const [selectedFilter, setSelectedFilter] = useState(filterType);
|
||||
|
||||
const handleChangeItem = (event, index) => {
|
||||
handleChange({
|
||||
...filterParameters,
|
||||
value: filterOption[index] === "between" ? ["", ""] : "",
|
||||
filterMode: filterOption[index],
|
||||
});
|
||||
setSelectedFilter(filterOption[index]);
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
const handleCloseFilterBox = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<Menu id="simple-menu" anchorEl={anchorEl} keepMounted open={Boolean(anchorEl)} onClose={handleCloseFilterBox}>
|
||||
{filterOption.map((option, index) => (
|
||||
<ListItem
|
||||
key={index}
|
||||
onClick={(event) => handleChangeItem(event, index)}
|
||||
selected={option === selectedFilter}
|
||||
sx={{ cursor: "pointer", width: "100px", display: "flex", justifyContent: "center" }}
|
||||
>
|
||||
{columnFilterModeOptionFa[option]}
|
||||
</ListItem>
|
||||
))}
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
|
||||
export default FilterOptionList;
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
import React from "react";
|
||||
import MuiDatePicker from "@/core/components/DataTable/filter/fieldsType/CustomDate/MuiDatePicker";
|
||||
import { Typography } from "@mui/material";
|
||||
|
||||
function CustomDatePicker({ column, filterParameters, defaultFilterTranslation, handleChange }) {
|
||||
return (
|
||||
<MuiDatePicker
|
||||
name={`${column.id}.value`}
|
||||
value={filterParameters.value}
|
||||
setFieldValue={(name, formattedDate) => {
|
||||
handleChange({ ...filterParameters, value: formattedDate });
|
||||
}}
|
||||
placeholder={column.header}
|
||||
helperText={
|
||||
<Typography variant="button" sx={{ color: "#155175" }}>
|
||||
نوع فیلتر: {defaultFilterTranslation} (تاریخ)
|
||||
</Typography>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default CustomDatePicker;
|
||||
import React from "react";
|
||||
import MuiDatePicker from "@/core/components/DataTable/filter/fieldsType/CustomDate/MuiDatePicker";
|
||||
import { Typography } from "@mui/material";
|
||||
|
||||
function CustomDatePicker({ column, filterParameters, defaultFilterTranslation, handleChange }) {
|
||||
return (
|
||||
<MuiDatePicker
|
||||
name={`${column.id}.value`}
|
||||
value={filterParameters.value}
|
||||
setFieldValue={(name, formattedDate) => {
|
||||
handleChange({ ...filterParameters, value: formattedDate });
|
||||
}}
|
||||
placeholder={column.header}
|
||||
helperText={
|
||||
<Typography variant="button" sx={{ color: "#155175" }}>
|
||||
نوع فیلتر: {defaultFilterTranslation} (تاریخ)
|
||||
</Typography>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default CustomDatePicker;
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
"use client";
|
||||
|
||||
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, handleChange, errors }) {
|
||||
return (
|
||||
<Box sx={{ display: "flex", gap: 1 }}>
|
||||
<MuiDatePicker
|
||||
label={column.header}
|
||||
name={`${column.id}.value[0]`}
|
||||
value={filterParameters.value[0]}
|
||||
setFieldValue={(name, formattedDate) => {
|
||||
handleChange({ ...filterParameters, value: [formattedDate, filterParameters.value[1]] });
|
||||
}}
|
||||
maxDate={filterParameters.value[1]}
|
||||
placeholder={`از تاریخ`}
|
||||
helperText={
|
||||
<Typography variant="button" sx={{ color: "#155175" }}>
|
||||
نوع فیلتر: {defaultFilterTranslation} (تاریخ)
|
||||
</Typography>
|
||||
}
|
||||
/>
|
||||
<MuiDatePicker
|
||||
label={column.header}
|
||||
name={`${column.id}.value[1]`}
|
||||
value={filterParameters.value[1]}
|
||||
setFieldValue={(name, formattedDate) => {
|
||||
handleChange({ ...filterParameters, value: [filterParameters.value[0], formattedDate] });
|
||||
}}
|
||||
minDate={filterParameters.value[0]}
|
||||
placeholder={`تا تاریخ`}
|
||||
helperText={errors?.[`${column.id}`]?.value ? errors?.[`${column.id}`]?.value.message : null}
|
||||
error={Boolean(errors?.[`${column.id}`]?.value)}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default CustomDatePickerRange;
|
||||
"use client";
|
||||
|
||||
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, handleChange, errors }) {
|
||||
return (
|
||||
<Box sx={{ display: "flex", gap: 1 }}>
|
||||
<MuiDatePicker
|
||||
label={column.header}
|
||||
name={`${column.id}.value[0]`}
|
||||
value={filterParameters.value[0]}
|
||||
setFieldValue={(name, formattedDate) => {
|
||||
handleChange({ ...filterParameters, value: [formattedDate, filterParameters.value[1]] });
|
||||
}}
|
||||
maxDate={filterParameters.value[1]}
|
||||
placeholder={`از تاریخ`}
|
||||
helperText={
|
||||
<Typography variant="button" sx={{ color: "#155175" }}>
|
||||
نوع فیلتر: {defaultFilterTranslation} (تاریخ)
|
||||
</Typography>
|
||||
}
|
||||
/>
|
||||
<MuiDatePicker
|
||||
label={column.header}
|
||||
name={`${column.id}.value[1]`}
|
||||
value={filterParameters.value[1]}
|
||||
setFieldValue={(name, formattedDate) => {
|
||||
handleChange({ ...filterParameters, value: [filterParameters.value[0], formattedDate] });
|
||||
}}
|
||||
minDate={filterParameters.value[0]}
|
||||
placeholder={`تا تاریخ`}
|
||||
helperText={errors?.[`${column.id}`]?.value ? errors?.[`${column.id}`]?.value.message : null}
|
||||
error={Boolean(errors?.[`${column.id}`]?.value)}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default CustomDatePickerRange;
|
||||
|
||||
@@ -1,72 +1,72 @@
|
||||
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";
|
||||
|
||||
function MuiDatePicker({ label, value, setFieldValue, name, minDate, maxDate, helperText, placeholder, error }) {
|
||||
return (
|
||||
<Box sx={{ display: "flex", flexDirection: "column", my: 1 }}>
|
||||
<LocalizationProvider
|
||||
dateAdapter={AdapterDateFnsJalali}
|
||||
localeText={faIR.components.MuiLocalizationProvider.defaultProps.localeText}
|
||||
>
|
||||
<MobileDatePicker
|
||||
value={value ? new Date(value) : null}
|
||||
sx={{ width: "100%" }}
|
||||
id={name}
|
||||
label={label}
|
||||
name={name}
|
||||
closeOnSelect={true}
|
||||
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: {
|
||||
size: "small",
|
||||
error: error,
|
||||
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>
|
||||
),
|
||||
},
|
||||
InputLabelProps: {
|
||||
shrink: true,
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
{/* <FormHelperText id="component-helper-text" variant="caption" sx={{ ml: 2, color: error ? "#d32f2f" : "unset", fontWeight: 'bold' }}>
|
||||
{helperText ? helperText : ""}
|
||||
</FormHelperText> */}
|
||||
</LocalizationProvider>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default MuiDatePicker;
|
||||
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";
|
||||
|
||||
function MuiDatePicker({ label, value, setFieldValue, name, minDate, maxDate, helperText, placeholder, error }) {
|
||||
return (
|
||||
<Box sx={{ display: "flex", flexDirection: "column", my: 1 }}>
|
||||
<LocalizationProvider
|
||||
dateAdapter={AdapterDateFnsJalali}
|
||||
localeText={faIR.components.MuiLocalizationProvider.defaultProps.localeText}
|
||||
>
|
||||
<MobileDatePicker
|
||||
value={value ? new Date(value) : null}
|
||||
sx={{ width: "100%" }}
|
||||
id={name}
|
||||
label={label}
|
||||
name={name}
|
||||
closeOnSelect={true}
|
||||
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: {
|
||||
size: "small",
|
||||
error: error,
|
||||
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>
|
||||
),
|
||||
},
|
||||
InputLabelProps: {
|
||||
shrink: true,
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
{/* <FormHelperText id="component-helper-text" variant="caption" sx={{ ml: 2, color: error ? "#d32f2f" : "unset", fontWeight: 'bold' }}>
|
||||
{helperText ? helperText : ""}
|
||||
</FormHelperText> */}
|
||||
</LocalizationProvider>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default MuiDatePicker;
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
"use client";
|
||||
|
||||
import { FormControl, InputLabel, MenuItem, OutlinedInput, Select } from "@mui/material";
|
||||
function CustomSelect({ column, filterParameters, handleChange }) {
|
||||
return (
|
||||
<FormControl fullWidth sx={{ marginY: 1 }} size="small">
|
||||
<InputLabel id={`label${column.id}`} shrink>
|
||||
{column.header}
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId={`label${column.id}`}
|
||||
id={column.id}
|
||||
name={`${column.id}.value`}
|
||||
value={filterParameters.value}
|
||||
input={<OutlinedInput label={column.header} />}
|
||||
size="small"
|
||||
onChange={(e) => handleChange({ ...filterParameters, value: e.target.value })}
|
||||
displayEmpty
|
||||
>
|
||||
{column.columnSelectOption().map((option) => (
|
||||
<MenuItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
|
||||
export default CustomSelect;
|
||||
"use client";
|
||||
|
||||
import { FormControl, InputLabel, MenuItem, OutlinedInput, Select } from "@mui/material";
|
||||
function CustomSelect({ column, filterParameters, handleChange }) {
|
||||
return (
|
||||
<FormControl fullWidth sx={{ marginY: 1 }} size="small">
|
||||
<InputLabel id={`label${column.id}`} shrink>
|
||||
{column.header}
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId={`label${column.id}`}
|
||||
id={column.id}
|
||||
name={`${column.id}.value`}
|
||||
value={filterParameters.value}
|
||||
input={<OutlinedInput label={column.header} />}
|
||||
size="small"
|
||||
onChange={(e) => handleChange({ ...filterParameters, value: e.target.value })}
|
||||
displayEmpty
|
||||
>
|
||||
{column.columnSelectOption().map((option) => (
|
||||
<MenuItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
|
||||
export default CustomSelect;
|
||||
|
||||
@@ -1,44 +1,44 @@
|
||||
"use client";
|
||||
|
||||
import { FormControl, FormHelperText, InputLabel, MenuItem, OutlinedInput, Select, Typography } from "@mui/material";
|
||||
import { useCallback, useEffect } from "react";
|
||||
|
||||
function CustomSelectByDependency({
|
||||
column,
|
||||
filterParameters,
|
||||
value,
|
||||
defaultFilterTranslation,
|
||||
handleChange,
|
||||
columnSelectOption,
|
||||
}) {
|
||||
return (
|
||||
<FormControl fullWidth sx={{ my: 1 }} size="small">
|
||||
<InputLabel id={`label${column.id}`} shrink>
|
||||
{column.header}
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId={`label${column.id}`}
|
||||
id={column.id}
|
||||
name={`${column.id}.value`}
|
||||
value={value}
|
||||
size="small"
|
||||
input={<OutlinedInput label={column.header} />}
|
||||
onChange={(e) => handleChange({ ...filterParameters, value: e.target.value })}
|
||||
displayEmpty
|
||||
>
|
||||
{columnSelectOption.map((option) => (
|
||||
<MenuItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
{/* <FormHelperText>
|
||||
<Typography variant="caption" sx={{ color: "#155175", fontWeight: 'bold' }}>
|
||||
نوع فیلتر: {defaultFilterTranslation}
|
||||
</Typography>
|
||||
</FormHelperText> */}
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
|
||||
export default CustomSelectByDependency;
|
||||
"use client";
|
||||
|
||||
import { FormControl, FormHelperText, InputLabel, MenuItem, OutlinedInput, Select, Typography } from "@mui/material";
|
||||
import { useCallback, useEffect } from "react";
|
||||
|
||||
function CustomSelectByDependency({
|
||||
column,
|
||||
filterParameters,
|
||||
value,
|
||||
defaultFilterTranslation,
|
||||
handleChange,
|
||||
columnSelectOption,
|
||||
}) {
|
||||
return (
|
||||
<FormControl fullWidth sx={{ my: 1 }} size="small">
|
||||
<InputLabel id={`label${column.id}`} shrink>
|
||||
{column.header}
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId={`label${column.id}`}
|
||||
id={column.id}
|
||||
name={`${column.id}.value`}
|
||||
value={value}
|
||||
size="small"
|
||||
input={<OutlinedInput label={column.header} />}
|
||||
onChange={(e) => handleChange({ ...filterParameters, value: e.target.value })}
|
||||
displayEmpty
|
||||
>
|
||||
{columnSelectOption.map((option) => (
|
||||
<MenuItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
{/* <FormHelperText>
|
||||
<Typography variant="caption" sx={{ color: "#155175", fontWeight: 'bold' }}>
|
||||
نوع فیلتر: {defaultFilterTranslation}
|
||||
</Typography>
|
||||
</FormHelperText> */}
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
|
||||
export default CustomSelectByDependency;
|
||||
|
||||
@@ -1,60 +1,60 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Box,
|
||||
Chip,
|
||||
FormControl,
|
||||
FormHelperText,
|
||||
InputLabel,
|
||||
MenuItem,
|
||||
OutlinedInput,
|
||||
Select,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
|
||||
function CustomSelectMultiple({ column, filterParameters, defaultFilterTranslation, handleChange }) {
|
||||
const columnSelectOption = column.columnSelectOption;
|
||||
|
||||
const getLabelForValue = (value) => {
|
||||
const option = columnSelectOption.find((opt) => opt.value === value);
|
||||
return option ? option.label : value;
|
||||
};
|
||||
|
||||
return (
|
||||
<FormControl fullWidth sx={{ marginY: 1 }} size="small">
|
||||
<InputLabel htmlFor="uncontrolled-native" id={`label${column.id}`}>
|
||||
{column.header}
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId={`label${column.id}`}
|
||||
id={column.id}
|
||||
value={filterParameters.value}
|
||||
multiple
|
||||
size="small"
|
||||
onChange={(e) => handleChange({ ...filterParameters, value: e.target.value })}
|
||||
renderValue={(selected) => (
|
||||
<Box sx={{ display: "flex", flexWrap: "wrap", gap: 0.5 }}>
|
||||
{selected.map((value) => (
|
||||
<Chip key={value} label={getLabelForValue(value)} />
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
input={<OutlinedInput label={column.header} />}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
>
|
||||
{columnSelectOption.map((option) => (
|
||||
<MenuItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
{/* <FormHelperText>
|
||||
<Typography variant="caption" sx={{ color: "#155175", fontWeight: 'bold' }}>
|
||||
نوع فیلتر: {defaultFilterTranslation}
|
||||
</Typography>
|
||||
</FormHelperText> */}
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
|
||||
export default CustomSelectMultiple;
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Box,
|
||||
Chip,
|
||||
FormControl,
|
||||
FormHelperText,
|
||||
InputLabel,
|
||||
MenuItem,
|
||||
OutlinedInput,
|
||||
Select,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
|
||||
function CustomSelectMultiple({ column, filterParameters, defaultFilterTranslation, handleChange }) {
|
||||
const columnSelectOption = column.columnSelectOption;
|
||||
|
||||
const getLabelForValue = (value) => {
|
||||
const option = columnSelectOption.find((opt) => opt.value === value);
|
||||
return option ? option.label : value;
|
||||
};
|
||||
|
||||
return (
|
||||
<FormControl fullWidth sx={{ marginY: 1 }} size="small">
|
||||
<InputLabel htmlFor="uncontrolled-native" id={`label${column.id}`}>
|
||||
{column.header}
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId={`label${column.id}`}
|
||||
id={column.id}
|
||||
value={filterParameters.value}
|
||||
multiple
|
||||
size="small"
|
||||
onChange={(e) => handleChange({ ...filterParameters, value: e.target.value })}
|
||||
renderValue={(selected) => (
|
||||
<Box sx={{ display: "flex", flexWrap: "wrap", gap: 0.5 }}>
|
||||
{selected.map((value) => (
|
||||
<Chip key={value} label={getLabelForValue(value)} />
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
input={<OutlinedInput label={column.header} />}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
>
|
||||
{columnSelectOption.map((option) => (
|
||||
<MenuItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
{/* <FormHelperText>
|
||||
<Typography variant="caption" sx={{ color: "#155175", fontWeight: 'bold' }}>
|
||||
نوع فیلتر: {defaultFilterTranslation}
|
||||
</Typography>
|
||||
</FormHelperText> */}
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
|
||||
export default CustomSelectMultiple;
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
import { InputAdornment, TextField, Typography } from "@mui/material";
|
||||
import FilterListIcon from "@mui/icons-material/FilterList";
|
||||
|
||||
function CustomTextField({
|
||||
column,
|
||||
defaultFilterTranslation,
|
||||
filterParameters,
|
||||
handleOpenFilterBox,
|
||||
handleBlur,
|
||||
handleChange,
|
||||
}) {
|
||||
return (
|
||||
<TextField
|
||||
id={column.id}
|
||||
name={column.id}
|
||||
label={column.header}
|
||||
value={filterParameters.value}
|
||||
onChange={(e) => handleChange({ ...filterParameters, value: e.target.value })}
|
||||
onBlur={handleBlur}
|
||||
fullWidth
|
||||
// helperText={
|
||||
// <Typography variant="caption" sx={{ color: "#155175", fontWeight: 'bold' }}>
|
||||
// نوع فیلتر: {defaultFilterTranslation}
|
||||
// </Typography>
|
||||
// }
|
||||
variant="outlined"
|
||||
size="small"
|
||||
sx={{ my: 1 }}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position="end" sx={{ cursor: "pointer" }} onClick={handleOpenFilterBox}>
|
||||
<FilterListIcon />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default CustomTextField;
|
||||
import { InputAdornment, TextField, Typography } from "@mui/material";
|
||||
import FilterListIcon from "@mui/icons-material/FilterList";
|
||||
|
||||
function CustomTextField({
|
||||
column,
|
||||
defaultFilterTranslation,
|
||||
filterParameters,
|
||||
handleOpenFilterBox,
|
||||
handleBlur,
|
||||
handleChange,
|
||||
}) {
|
||||
return (
|
||||
<TextField
|
||||
id={column.id}
|
||||
name={column.id}
|
||||
label={column.header}
|
||||
value={filterParameters.value}
|
||||
onChange={(e) => handleChange({ ...filterParameters, value: e.target.value })}
|
||||
onBlur={handleBlur}
|
||||
fullWidth
|
||||
// helperText={
|
||||
// <Typography variant="caption" sx={{ color: "#155175", fontWeight: 'bold' }}>
|
||||
// نوع فیلتر: {defaultFilterTranslation}
|
||||
// </Typography>
|
||||
// }
|
||||
variant="outlined"
|
||||
size="small"
|
||||
sx={{ my: 1 }}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position="end" sx={{ cursor: "pointer" }} onClick={handleOpenFilterBox}>
|
||||
<FilterListIcon />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default CustomTextField;
|
||||
|
||||
@@ -1,62 +1,62 @@
|
||||
import { Box, InputAdornment, TextField, Typography } from "@mui/material";
|
||||
import FilterListIcon from "@mui/icons-material/FilterList";
|
||||
|
||||
function CustomTextFieldRange({
|
||||
column,
|
||||
defaultFilterTranslation,
|
||||
handleOpenFilterBox,
|
||||
handleChange,
|
||||
filterParameters,
|
||||
handleBlur,
|
||||
errors,
|
||||
}) {
|
||||
return (
|
||||
<Box sx={{ display: "flex" }}>
|
||||
<TextField
|
||||
id={`${column.id}.value[0]`}
|
||||
name={`${column.id}.value[0]`}
|
||||
onChange={(e) =>
|
||||
handleChange({ ...filterParameters, value: [e.target.value, filterParameters.value[1]] })
|
||||
}
|
||||
onBlur={handleBlur}
|
||||
label={<Typography>از {column.header}</Typography>}
|
||||
value={filterParameters.value[0]}
|
||||
fullWidth
|
||||
error={touched?.[`${column.id}`]?.value && Boolean(errors?.[`${column.id}`]?.value)}
|
||||
helperText={
|
||||
<Typography variant="caption" sx={{ color: "#155175", fontWeight: "bold" }}>
|
||||
نوع فیلتر: {defaultFilterTranslation}
|
||||
</Typography>
|
||||
}
|
||||
variant="outlined"
|
||||
size="small"
|
||||
sx={{ my: 1, marginRight: 1 }}
|
||||
/>
|
||||
<TextField
|
||||
id={`${column.id}.value[1]`}
|
||||
name={`${column.id}.value[1]`}
|
||||
onChange={(e) =>
|
||||
handleChange({ ...filterParameters, value: [filterParameters.value[0], e.target.value] })
|
||||
}
|
||||
onBlur={handleBlur}
|
||||
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
|
||||
variant="outlined"
|
||||
size="small"
|
||||
sx={{ my: 1 }}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position="end" sx={{ cursor: "pointer" }} onClick={handleOpenFilterBox}>
|
||||
<FilterListIcon />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default CustomTextFieldRange;
|
||||
import { Box, InputAdornment, TextField, Typography } from "@mui/material";
|
||||
import FilterListIcon from "@mui/icons-material/FilterList";
|
||||
|
||||
function CustomTextFieldRange({
|
||||
column,
|
||||
defaultFilterTranslation,
|
||||
handleOpenFilterBox,
|
||||
handleChange,
|
||||
filterParameters,
|
||||
handleBlur,
|
||||
errors,
|
||||
}) {
|
||||
return (
|
||||
<Box sx={{ display: "flex" }}>
|
||||
<TextField
|
||||
id={`${column.id}.value[0]`}
|
||||
name={`${column.id}.value[0]`}
|
||||
onChange={(e) =>
|
||||
handleChange({ ...filterParameters, value: [e.target.value, filterParameters.value[1]] })
|
||||
}
|
||||
onBlur={handleBlur}
|
||||
label={<Typography>از {column.header}</Typography>}
|
||||
value={filterParameters.value[0]}
|
||||
fullWidth
|
||||
error={touched?.[`${column.id}`]?.value && Boolean(errors?.[`${column.id}`]?.value)}
|
||||
helperText={
|
||||
<Typography variant="caption" sx={{ color: "#155175", fontWeight: "bold" }}>
|
||||
نوع فیلتر: {defaultFilterTranslation}
|
||||
</Typography>
|
||||
}
|
||||
variant="outlined"
|
||||
size="small"
|
||||
sx={{ my: 1, marginRight: 1 }}
|
||||
/>
|
||||
<TextField
|
||||
id={`${column.id}.value[1]`}
|
||||
name={`${column.id}.value[1]`}
|
||||
onChange={(e) =>
|
||||
handleChange({ ...filterParameters, value: [filterParameters.value[0], e.target.value] })
|
||||
}
|
||||
onBlur={handleBlur}
|
||||
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
|
||||
variant="outlined"
|
||||
size="small"
|
||||
sx={{ my: 1 }}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position="end" sx={{ cursor: "pointer" }} onClick={handleOpenFilterBox}>
|
||||
<FilterListIcon />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default CustomTextFieldRange;
|
||||
|
||||
@@ -1,35 +1,35 @@
|
||||
"use client";
|
||||
import { useState } from "react";
|
||||
import FilterButton from "@/core/components/DataTable/filter/FilterButton";
|
||||
import FilterBody from "@/core/components/DataTable/filter/FilterBody";
|
||||
import { Box, Drawer } from "@mui/material";
|
||||
|
||||
function FilterColumn({ columns, user_id, page_name, table_name }) {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<FilterButton drawerState={open} setDrawerState={setOpen} />
|
||||
<Drawer
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
sx={{ overflowY: "hidden", display: "flex", flexDirection: "column", height: "100%", zIndex: "1300" }}
|
||||
>
|
||||
<Box sx={{ width: { xs: 300, sm: 450 } }}>
|
||||
{open && (
|
||||
<FilterBody
|
||||
columns={columns}
|
||||
drawerState={open}
|
||||
setDrawerState={setOpen}
|
||||
user_id={user_id}
|
||||
page_name={page_name}
|
||||
table_name={table_name}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Drawer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default FilterColumn;
|
||||
"use client";
|
||||
import { useState } from "react";
|
||||
import FilterButton from "@/core/components/DataTable/filter/FilterButton";
|
||||
import FilterBody from "@/core/components/DataTable/filter/FilterBody";
|
||||
import { Box, Drawer } from "@mui/material";
|
||||
|
||||
function FilterColumn({ columns, user_id, page_name, table_name }) {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<FilterButton drawerState={open} setDrawerState={setOpen} />
|
||||
<Drawer
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
sx={{ overflowY: "hidden", display: "flex", flexDirection: "column", height: "100%", zIndex: "1300" }}
|
||||
>
|
||||
<Box sx={{ width: { xs: 300, sm: 450 } }}>
|
||||
{open && (
|
||||
<FilterBody
|
||||
columns={columns}
|
||||
drawerState={open}
|
||||
setDrawerState={setOpen}
|
||||
user_id={user_id}
|
||||
page_name={page_name}
|
||||
table_name={table_name}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Drawer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default FilterColumn;
|
||||
|
||||
@@ -1,51 +1,51 @@
|
||||
import { parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||
import { TableHead } from "@mui/material";
|
||||
import DataTable_TableHeadRow from "./TableHeadRow";
|
||||
|
||||
const DataTable_TableHead = ({ columnVirtualizer, table, ...rest }) => {
|
||||
const {
|
||||
getState,
|
||||
options: { enableStickyHeader, layoutMode, muiTableHeadProps, positionToolbarAlertBanner },
|
||||
refs: { tableHeadRef },
|
||||
} = table;
|
||||
const { isFullScreen, showAlertBanner } = getState();
|
||||
|
||||
const tableHeadProps = {
|
||||
...parseFromValuesOrFunc(muiTableHeadProps, { table }),
|
||||
...rest,
|
||||
};
|
||||
|
||||
const stickyHeader = enableStickyHeader || isFullScreen;
|
||||
|
||||
return (
|
||||
<TableHead
|
||||
{...tableHeadProps}
|
||||
ref={(ref) => {
|
||||
tableHeadRef.current = ref;
|
||||
if (tableHeadProps?.ref) {
|
||||
// @ts-ignore
|
||||
tableHeadProps.ref.current = ref;
|
||||
}
|
||||
}}
|
||||
sx={(theme) => ({
|
||||
display: layoutMode?.startsWith("grid") ? "grid" : undefined,
|
||||
opacity: 0.97,
|
||||
position: stickyHeader ? "sticky" : "relative",
|
||||
top: stickyHeader && layoutMode?.startsWith("grid") ? 0 : undefined,
|
||||
zIndex: stickyHeader ? 2 : undefined,
|
||||
...parseFromValuesOrFunc(tableHeadProps?.sx, theme),
|
||||
})}
|
||||
>
|
||||
{table.getHeaderGroups().map((headerGroup, index) => (
|
||||
<DataTable_TableHeadRow
|
||||
columnVirtualizer={columnVirtualizer}
|
||||
headerGroup={headerGroup}
|
||||
key={headerGroup.id}
|
||||
table={table}
|
||||
index={index}
|
||||
/>
|
||||
))}
|
||||
</TableHead>
|
||||
);
|
||||
};
|
||||
export default DataTable_TableHead;
|
||||
import { parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||
import { TableHead } from "@mui/material";
|
||||
import DataTable_TableHeadRow from "./TableHeadRow";
|
||||
|
||||
const DataTable_TableHead = ({ columnVirtualizer, table, ...rest }) => {
|
||||
const {
|
||||
getState,
|
||||
options: { enableStickyHeader, layoutMode, muiTableHeadProps, positionToolbarAlertBanner },
|
||||
refs: { tableHeadRef },
|
||||
} = table;
|
||||
const { isFullScreen, showAlertBanner } = getState();
|
||||
|
||||
const tableHeadProps = {
|
||||
...parseFromValuesOrFunc(muiTableHeadProps, { table }),
|
||||
...rest,
|
||||
};
|
||||
|
||||
const stickyHeader = enableStickyHeader || isFullScreen;
|
||||
|
||||
return (
|
||||
<TableHead
|
||||
{...tableHeadProps}
|
||||
ref={(ref) => {
|
||||
tableHeadRef.current = ref;
|
||||
if (tableHeadProps?.ref) {
|
||||
// @ts-ignore
|
||||
tableHeadProps.ref.current = ref;
|
||||
}
|
||||
}}
|
||||
sx={(theme) => ({
|
||||
display: layoutMode?.startsWith("grid") ? "grid" : undefined,
|
||||
opacity: 0.97,
|
||||
position: stickyHeader ? "sticky" : "relative",
|
||||
top: stickyHeader && layoutMode?.startsWith("grid") ? 0 : undefined,
|
||||
zIndex: stickyHeader ? 2 : undefined,
|
||||
...parseFromValuesOrFunc(tableHeadProps?.sx, theme),
|
||||
})}
|
||||
>
|
||||
{table.getHeaderGroups().map((headerGroup, index) => (
|
||||
<DataTable_TableHeadRow
|
||||
columnVirtualizer={columnVirtualizer}
|
||||
headerGroup={headerGroup}
|
||||
key={headerGroup.id}
|
||||
table={table}
|
||||
index={index}
|
||||
/>
|
||||
))}
|
||||
</TableHead>
|
||||
);
|
||||
};
|
||||
export default DataTable_TableHead;
|
||||
|
||||
@@ -1,244 +1,244 @@
|
||||
import { getCommonMRTCellStyles, parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { Box, TableCell } from "@mui/material";
|
||||
import { MRT_TableHeadCellSortLabel } from "material-react-table";
|
||||
import { useMemo } from "react";
|
||||
|
||||
const DataTable_TableHeadCell = ({ columnVirtualizer, header, staticColumnIndex, backgroundColor, table, ...rest }) => {
|
||||
const theme = useTheme();
|
||||
const {
|
||||
getState,
|
||||
options: {
|
||||
columnResizeDirection,
|
||||
columnResizeMode,
|
||||
enableColumnActions,
|
||||
enableColumnDragging,
|
||||
enableColumnOrdering,
|
||||
enableColumnPinning,
|
||||
enableGrouping,
|
||||
enableMultiSort,
|
||||
layoutMode,
|
||||
mrtTheme: { draggingBorderColor },
|
||||
muiTableHeadCellProps,
|
||||
},
|
||||
refs: { tableHeadCellRefs },
|
||||
setHoveredColumn,
|
||||
} = table;
|
||||
const { columnSizingInfo, density, draggingColumn, grouping, hoveredColumn, showColumnFilters } = getState();
|
||||
const { column } = header;
|
||||
const { columnDef } = column;
|
||||
const { columnDefType } = columnDef;
|
||||
|
||||
const tableCellProps = {
|
||||
...parseFromValuesOrFunc(muiTableHeadCellProps, { column, table }),
|
||||
...parseFromValuesOrFunc(columnDef.muiTableHeadCellProps, {
|
||||
column,
|
||||
table,
|
||||
}),
|
||||
...rest,
|
||||
};
|
||||
|
||||
const isColumnPinned = enableColumnPinning && columnDef.columnDefType !== "group" && column.getIsPinned();
|
||||
|
||||
const showColumnActions =
|
||||
(enableColumnActions || columnDef.enableColumnActions) && columnDef.enableColumnActions !== false;
|
||||
|
||||
const showDragHandle =
|
||||
enableColumnDragging !== false &&
|
||||
columnDef.enableColumnDragging !== false &&
|
||||
(enableColumnDragging ||
|
||||
(enableColumnOrdering && columnDef.enableColumnOrdering !== false) ||
|
||||
(enableGrouping && columnDef.enableGrouping !== false && !grouping.includes(column.id)));
|
||||
|
||||
const headerPL = useMemo(() => {
|
||||
let pl = 0;
|
||||
if (column.getCanSort()) pl += 1;
|
||||
if (showColumnActions) pl += 1.75;
|
||||
if (showDragHandle) pl += 1.5;
|
||||
return pl;
|
||||
}, [showColumnActions, showDragHandle]);
|
||||
|
||||
const draggingBorders = useMemo(() => {
|
||||
const showResizeBorder =
|
||||
columnSizingInfo.isResizingColumn === column.id &&
|
||||
columnResizeMode === "onChange" &&
|
||||
!header.subHeaders.length;
|
||||
|
||||
const borderStyle = showResizeBorder
|
||||
? `2px solid ${draggingBorderColor} !important`
|
||||
: draggingColumn?.id === column.id
|
||||
? `1px dashed ${theme.palette.grey[500]}`
|
||||
: hoveredColumn?.id === column.id
|
||||
? `2px dashed ${draggingBorderColor}`
|
||||
: undefined;
|
||||
|
||||
if (showResizeBorder) {
|
||||
return columnResizeDirection === "ltr" ? { borderRight: borderStyle } : { borderLeft: borderStyle };
|
||||
}
|
||||
const draggingBorders = borderStyle
|
||||
? {
|
||||
borderLeft: borderStyle,
|
||||
borderRight: borderStyle,
|
||||
borderTop: borderStyle,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
return draggingBorders;
|
||||
}, [draggingColumn, hoveredColumn, columnSizingInfo.isResizingColumn]);
|
||||
|
||||
const handleDragEnter = (_e) => {
|
||||
if (enableGrouping && hoveredColumn?.id === "drop-zone") {
|
||||
setHoveredColumn(null);
|
||||
}
|
||||
if (enableColumnOrdering && draggingColumn && columnDefType !== "group") {
|
||||
setHoveredColumn(columnDef.enableColumnOrdering !== false ? column : null);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDragOver = (e) => {
|
||||
if (columnDef.enableColumnOrdering !== false) {
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
const HeaderElement =
|
||||
parseFromValuesOrFunc(columnDef.Header, {
|
||||
column,
|
||||
header,
|
||||
table,
|
||||
}) ?? columnDef.header;
|
||||
|
||||
const columnRelativeDepth = header.depth - column.depth;
|
||||
|
||||
if (columnRelativeDepth > 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let rowSpan = 1;
|
||||
if (header.isPlaceholder) {
|
||||
const leafs = header.getLeafHeaders();
|
||||
rowSpan = leafs[leafs.length - 1].depth - header.depth;
|
||||
}
|
||||
|
||||
return (
|
||||
<TableCell
|
||||
align={columnDefType === "group" ? "center" : theme.direction === "rtl" ? "right" : "left"}
|
||||
colSpan={header.colSpan}
|
||||
rowSpan={rowSpan}
|
||||
data-index={staticColumnIndex}
|
||||
data-pinned={!!isColumnPinned || undefined}
|
||||
onDragEnter={handleDragEnter}
|
||||
onDragOver={handleDragOver}
|
||||
ref={(node) => {
|
||||
if (node) {
|
||||
tableHeadCellRefs.current[column.id] = node;
|
||||
if (columnDefType !== "group") {
|
||||
columnVirtualizer?.measureElement?.(node);
|
||||
}
|
||||
}
|
||||
}}
|
||||
{...tableCellProps}
|
||||
sx={(theme) => ({
|
||||
"& :hover": {
|
||||
".MuiButtonBase-root": {
|
||||
opacity: 1,
|
||||
},
|
||||
},
|
||||
flexDirection: layoutMode?.startsWith("grid") ? "column" : undefined,
|
||||
fontWeight: "bold",
|
||||
overflow: "visible",
|
||||
p:
|
||||
density === "compact"
|
||||
? "0.5rem"
|
||||
: density === "comfortable"
|
||||
? columnDefType === "display"
|
||||
? "0.75rem"
|
||||
: "1rem"
|
||||
: columnDefType === "display"
|
||||
? "1rem 1.25rem"
|
||||
: "1.5rem",
|
||||
pb: columnDefType === "display" ? 0 : showColumnFilters || density === "compact" ? "0.4rem" : "0.6rem",
|
||||
pt:
|
||||
columnDefType === "group" || density === "compact"
|
||||
? "0.25rem"
|
||||
: density === "comfortable"
|
||||
? ".75rem"
|
||||
: "1.25rem",
|
||||
userSelect: enableMultiSort && column.getCanSort() ? "none" : undefined,
|
||||
verticalAlign: "middle",
|
||||
...getCommonMRTCellStyles({
|
||||
column,
|
||||
header,
|
||||
table,
|
||||
tableCellProps,
|
||||
theme,
|
||||
}),
|
||||
...draggingBorders,
|
||||
backgroundColor: backgroundColor,
|
||||
})}
|
||||
>
|
||||
{tableCellProps.children ?? (
|
||||
<Box
|
||||
className="Mui-TableHeadCell-Content"
|
||||
sx={{
|
||||
alignItems: "center",
|
||||
display: "flex",
|
||||
flexDirection: tableCellProps?.align === "right" ? "row-reverse" : "row",
|
||||
justifyContent:
|
||||
columnDefType === "group" || tableCellProps?.align === "center"
|
||||
? "center"
|
||||
: column.getCanResize()
|
||||
? "space-between"
|
||||
: "flex-start",
|
||||
position: "relative",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
className="Mui-TableHeadCell-Content-Labels"
|
||||
onClick={column.getToggleSortingHandler()}
|
||||
sx={{
|
||||
alignItems: "center",
|
||||
cursor: column.getCanSort() && columnDefType !== "group" ? "pointer" : undefined,
|
||||
display: "flex",
|
||||
flexDirection: tableCellProps?.align === "right" ? "row-reverse" : "row",
|
||||
overflow: columnDefType === "data" ? "hidden" : undefined,
|
||||
pl: tableCellProps?.align === "center" ? `${headerPL}rem` : undefined,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
className="Mui-TableHeadCell-Content-Wrapper"
|
||||
sx={{
|
||||
"&:hover": {
|
||||
textOverflow: "clip",
|
||||
},
|
||||
minWidth: `${Math.min(columnDef.header?.length ?? 0, 4)}ch`,
|
||||
overflow: columnDefType === "data" ? "hidden" : undefined,
|
||||
textOverflow: "ellipsis",
|
||||
textAlign: "start",
|
||||
color: "#fff",
|
||||
fontWeight: "400",
|
||||
whiteSpace: "nowrap",
|
||||
}}
|
||||
>
|
||||
{HeaderElement}
|
||||
</Box>
|
||||
{column.getCanSort() && columnDefType !== "group" && (
|
||||
<MRT_TableHeadCellSortLabel
|
||||
sx={{
|
||||
width: 20,
|
||||
"& .MuiTableSortLabel-icon": {
|
||||
color: `#fff !important`,
|
||||
},
|
||||
}}
|
||||
header={header}
|
||||
table={table}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</TableCell>
|
||||
);
|
||||
};
|
||||
export default DataTable_TableHeadCell;
|
||||
import { getCommonMRTCellStyles, parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { Box, TableCell } from "@mui/material";
|
||||
import { MRT_TableHeadCellSortLabel } from "material-react-table";
|
||||
import { useMemo } from "react";
|
||||
|
||||
const DataTable_TableHeadCell = ({ columnVirtualizer, header, staticColumnIndex, backgroundColor, table, ...rest }) => {
|
||||
const theme = useTheme();
|
||||
const {
|
||||
getState,
|
||||
options: {
|
||||
columnResizeDirection,
|
||||
columnResizeMode,
|
||||
enableColumnActions,
|
||||
enableColumnDragging,
|
||||
enableColumnOrdering,
|
||||
enableColumnPinning,
|
||||
enableGrouping,
|
||||
enableMultiSort,
|
||||
layoutMode,
|
||||
mrtTheme: { draggingBorderColor },
|
||||
muiTableHeadCellProps,
|
||||
},
|
||||
refs: { tableHeadCellRefs },
|
||||
setHoveredColumn,
|
||||
} = table;
|
||||
const { columnSizingInfo, density, draggingColumn, grouping, hoveredColumn, showColumnFilters } = getState();
|
||||
const { column } = header;
|
||||
const { columnDef } = column;
|
||||
const { columnDefType } = columnDef;
|
||||
|
||||
const tableCellProps = {
|
||||
...parseFromValuesOrFunc(muiTableHeadCellProps, { column, table }),
|
||||
...parseFromValuesOrFunc(columnDef.muiTableHeadCellProps, {
|
||||
column,
|
||||
table,
|
||||
}),
|
||||
...rest,
|
||||
};
|
||||
|
||||
const isColumnPinned = enableColumnPinning && columnDef.columnDefType !== "group" && column.getIsPinned();
|
||||
|
||||
const showColumnActions =
|
||||
(enableColumnActions || columnDef.enableColumnActions) && columnDef.enableColumnActions !== false;
|
||||
|
||||
const showDragHandle =
|
||||
enableColumnDragging !== false &&
|
||||
columnDef.enableColumnDragging !== false &&
|
||||
(enableColumnDragging ||
|
||||
(enableColumnOrdering && columnDef.enableColumnOrdering !== false) ||
|
||||
(enableGrouping && columnDef.enableGrouping !== false && !grouping.includes(column.id)));
|
||||
|
||||
const headerPL = useMemo(() => {
|
||||
let pl = 0;
|
||||
if (column.getCanSort()) pl += 1;
|
||||
if (showColumnActions) pl += 1.75;
|
||||
if (showDragHandle) pl += 1.5;
|
||||
return pl;
|
||||
}, [showColumnActions, showDragHandle]);
|
||||
|
||||
const draggingBorders = useMemo(() => {
|
||||
const showResizeBorder =
|
||||
columnSizingInfo.isResizingColumn === column.id &&
|
||||
columnResizeMode === "onChange" &&
|
||||
!header.subHeaders.length;
|
||||
|
||||
const borderStyle = showResizeBorder
|
||||
? `2px solid ${draggingBorderColor} !important`
|
||||
: draggingColumn?.id === column.id
|
||||
? `1px dashed ${theme.palette.grey[500]}`
|
||||
: hoveredColumn?.id === column.id
|
||||
? `2px dashed ${draggingBorderColor}`
|
||||
: undefined;
|
||||
|
||||
if (showResizeBorder) {
|
||||
return columnResizeDirection === "ltr" ? { borderRight: borderStyle } : { borderLeft: borderStyle };
|
||||
}
|
||||
const draggingBorders = borderStyle
|
||||
? {
|
||||
borderLeft: borderStyle,
|
||||
borderRight: borderStyle,
|
||||
borderTop: borderStyle,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
return draggingBorders;
|
||||
}, [draggingColumn, hoveredColumn, columnSizingInfo.isResizingColumn]);
|
||||
|
||||
const handleDragEnter = (_e) => {
|
||||
if (enableGrouping && hoveredColumn?.id === "drop-zone") {
|
||||
setHoveredColumn(null);
|
||||
}
|
||||
if (enableColumnOrdering && draggingColumn && columnDefType !== "group") {
|
||||
setHoveredColumn(columnDef.enableColumnOrdering !== false ? column : null);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDragOver = (e) => {
|
||||
if (columnDef.enableColumnOrdering !== false) {
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
const HeaderElement =
|
||||
parseFromValuesOrFunc(columnDef.Header, {
|
||||
column,
|
||||
header,
|
||||
table,
|
||||
}) ?? columnDef.header;
|
||||
|
||||
const columnRelativeDepth = header.depth - column.depth;
|
||||
|
||||
if (columnRelativeDepth > 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let rowSpan = 1;
|
||||
if (header.isPlaceholder) {
|
||||
const leafs = header.getLeafHeaders();
|
||||
rowSpan = leafs[leafs.length - 1].depth - header.depth;
|
||||
}
|
||||
|
||||
return (
|
||||
<TableCell
|
||||
align={columnDefType === "group" ? "center" : theme.direction === "rtl" ? "right" : "left"}
|
||||
colSpan={header.colSpan}
|
||||
rowSpan={rowSpan}
|
||||
data-index={staticColumnIndex}
|
||||
data-pinned={!!isColumnPinned || undefined}
|
||||
onDragEnter={handleDragEnter}
|
||||
onDragOver={handleDragOver}
|
||||
ref={(node) => {
|
||||
if (node) {
|
||||
tableHeadCellRefs.current[column.id] = node;
|
||||
if (columnDefType !== "group") {
|
||||
columnVirtualizer?.measureElement?.(node);
|
||||
}
|
||||
}
|
||||
}}
|
||||
{...tableCellProps}
|
||||
sx={(theme) => ({
|
||||
"& :hover": {
|
||||
".MuiButtonBase-root": {
|
||||
opacity: 1,
|
||||
},
|
||||
},
|
||||
flexDirection: layoutMode?.startsWith("grid") ? "column" : undefined,
|
||||
fontWeight: "bold",
|
||||
overflow: "visible",
|
||||
p:
|
||||
density === "compact"
|
||||
? "0.5rem"
|
||||
: density === "comfortable"
|
||||
? columnDefType === "display"
|
||||
? "0.75rem"
|
||||
: "1rem"
|
||||
: columnDefType === "display"
|
||||
? "1rem 1.25rem"
|
||||
: "1.5rem",
|
||||
pb: columnDefType === "display" ? 0 : showColumnFilters || density === "compact" ? "0.4rem" : "0.6rem",
|
||||
pt:
|
||||
columnDefType === "group" || density === "compact"
|
||||
? "0.25rem"
|
||||
: density === "comfortable"
|
||||
? ".75rem"
|
||||
: "1.25rem",
|
||||
userSelect: enableMultiSort && column.getCanSort() ? "none" : undefined,
|
||||
verticalAlign: "middle",
|
||||
...getCommonMRTCellStyles({
|
||||
column,
|
||||
header,
|
||||
table,
|
||||
tableCellProps,
|
||||
theme,
|
||||
}),
|
||||
...draggingBorders,
|
||||
backgroundColor: backgroundColor,
|
||||
})}
|
||||
>
|
||||
{tableCellProps.children ?? (
|
||||
<Box
|
||||
className="Mui-TableHeadCell-Content"
|
||||
sx={{
|
||||
alignItems: "center",
|
||||
display: "flex",
|
||||
flexDirection: tableCellProps?.align === "right" ? "row-reverse" : "row",
|
||||
justifyContent:
|
||||
columnDefType === "group" || tableCellProps?.align === "center"
|
||||
? "center"
|
||||
: column.getCanResize()
|
||||
? "space-between"
|
||||
: "flex-start",
|
||||
position: "relative",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
className="Mui-TableHeadCell-Content-Labels"
|
||||
onClick={column.getToggleSortingHandler()}
|
||||
sx={{
|
||||
alignItems: "center",
|
||||
cursor: column.getCanSort() && columnDefType !== "group" ? "pointer" : undefined,
|
||||
display: "flex",
|
||||
flexDirection: tableCellProps?.align === "right" ? "row-reverse" : "row",
|
||||
overflow: columnDefType === "data" ? "hidden" : undefined,
|
||||
pl: tableCellProps?.align === "center" ? `${headerPL}rem` : undefined,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
className="Mui-TableHeadCell-Content-Wrapper"
|
||||
sx={{
|
||||
"&:hover": {
|
||||
textOverflow: "clip",
|
||||
},
|
||||
minWidth: `${Math.min(columnDef.header?.length ?? 0, 4)}ch`,
|
||||
overflow: columnDefType === "data" ? "hidden" : undefined,
|
||||
textOverflow: "ellipsis",
|
||||
textAlign: "start",
|
||||
color: "#fff",
|
||||
fontWeight: "400",
|
||||
whiteSpace: "nowrap",
|
||||
}}
|
||||
>
|
||||
{HeaderElement}
|
||||
</Box>
|
||||
{column.getCanSort() && columnDefType !== "group" && (
|
||||
<MRT_TableHeadCellSortLabel
|
||||
sx={{
|
||||
width: 20,
|
||||
"& .MuiTableSortLabel-icon": {
|
||||
color: `#fff !important`,
|
||||
},
|
||||
}}
|
||||
header={header}
|
||||
table={table}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</TableCell>
|
||||
);
|
||||
};
|
||||
export default DataTable_TableHeadCell;
|
||||
|
||||
@@ -1,58 +1,58 @@
|
||||
import { parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||
import { TableRow } from "@mui/material";
|
||||
import DataTable_TableHeadCell from "./TableHeadCell";
|
||||
|
||||
const DataTable_TableHeadRow = ({
|
||||
columnVirtualizer,
|
||||
headerGroup,
|
||||
table,
|
||||
index, // Add index prop
|
||||
...rest
|
||||
}) => {
|
||||
const {
|
||||
options: { enableStickyHeader, layoutMode, muiTableHeadRowProps },
|
||||
} = table;
|
||||
const { virtualColumns, virtualPaddingLeft, virtualPaddingRight } = columnVirtualizer ?? {};
|
||||
|
||||
const backgroundColor = index % 2 === 0 ? "#015688" : "#ff5c0f";
|
||||
|
||||
const tableRowProps = {
|
||||
...parseFromValuesOrFunc(muiTableHeadRowProps, {
|
||||
headerGroup,
|
||||
table,
|
||||
}),
|
||||
...rest,
|
||||
sx: (theme) => ({
|
||||
// Access theme from the sx function
|
||||
...(parseFromValuesOrFunc(muiTableHeadRowProps?.sx, theme) || {}),
|
||||
display: layoutMode?.startsWith("grid") ? "flex" : undefined,
|
||||
position: enableStickyHeader && layoutMode === "semantic" ? "sticky" : "relative",
|
||||
top: 0,
|
||||
}),
|
||||
};
|
||||
|
||||
return (
|
||||
<TableRow {...tableRowProps}>
|
||||
{virtualPaddingLeft && <th style={{ display: "flex", width: virtualPaddingLeft }} />}
|
||||
{(virtualColumns ?? headerGroup.headers).map((headerOrVirtualHeader, staticColumnIndex) => {
|
||||
const header = columnVirtualizer
|
||||
? headerGroup.headers[headerOrVirtualHeader.index]
|
||||
: headerOrVirtualHeader;
|
||||
|
||||
return header ? (
|
||||
<DataTable_TableHeadCell
|
||||
columnVirtualizer={columnVirtualizer}
|
||||
header={header}
|
||||
backgroundColor={backgroundColor}
|
||||
key={header.id}
|
||||
staticColumnIndex={staticColumnIndex}
|
||||
table={table}
|
||||
/>
|
||||
) : null;
|
||||
})}
|
||||
{virtualPaddingRight && <th style={{ display: "flex", width: virtualPaddingRight }} />}
|
||||
</TableRow>
|
||||
);
|
||||
};
|
||||
|
||||
export default DataTable_TableHeadRow;
|
||||
import { parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||
import { TableRow } from "@mui/material";
|
||||
import DataTable_TableHeadCell from "./TableHeadCell";
|
||||
|
||||
const DataTable_TableHeadRow = ({
|
||||
columnVirtualizer,
|
||||
headerGroup,
|
||||
table,
|
||||
index, // Add index prop
|
||||
...rest
|
||||
}) => {
|
||||
const {
|
||||
options: { enableStickyHeader, layoutMode, muiTableHeadRowProps },
|
||||
} = table;
|
||||
const { virtualColumns, virtualPaddingLeft, virtualPaddingRight } = columnVirtualizer ?? {};
|
||||
|
||||
const backgroundColor = index % 2 === 0 ? "#015688" : "#ff5c0f";
|
||||
|
||||
const tableRowProps = {
|
||||
...parseFromValuesOrFunc(muiTableHeadRowProps, {
|
||||
headerGroup,
|
||||
table,
|
||||
}),
|
||||
...rest,
|
||||
sx: (theme) => ({
|
||||
// Access theme from the sx function
|
||||
...(parseFromValuesOrFunc(muiTableHeadRowProps?.sx, theme) || {}),
|
||||
display: layoutMode?.startsWith("grid") ? "flex" : undefined,
|
||||
position: enableStickyHeader && layoutMode === "semantic" ? "sticky" : "relative",
|
||||
top: 0,
|
||||
}),
|
||||
};
|
||||
|
||||
return (
|
||||
<TableRow {...tableRowProps}>
|
||||
{virtualPaddingLeft && <th style={{ display: "flex", width: virtualPaddingLeft }} />}
|
||||
{(virtualColumns ?? headerGroup.headers).map((headerOrVirtualHeader, staticColumnIndex) => {
|
||||
const header = columnVirtualizer
|
||||
? headerGroup.headers[headerOrVirtualHeader.index]
|
||||
: headerOrVirtualHeader;
|
||||
|
||||
return header ? (
|
||||
<DataTable_TableHeadCell
|
||||
columnVirtualizer={columnVirtualizer}
|
||||
header={header}
|
||||
backgroundColor={backgroundColor}
|
||||
key={header.id}
|
||||
staticColumnIndex={staticColumnIndex}
|
||||
table={table}
|
||||
/>
|
||||
) : null;
|
||||
})}
|
||||
{virtualPaddingRight && <th style={{ display: "flex", width: virtualPaddingRight }} />}
|
||||
</TableRow>
|
||||
);
|
||||
};
|
||||
|
||||
export default DataTable_TableHeadRow;
|
||||
|
||||
@@ -1,48 +1,48 @@
|
||||
"use client";
|
||||
|
||||
import { Box, Drawer, styled } from "@mui/material";
|
||||
import HideBodyField from "@/core/components/DataTable/hide/HideBodyField";
|
||||
import HideHeader from "@/core/components/DataTable/hide/HideHeader";
|
||||
import useDataTable from "@/lib/hooks/useDataTable";
|
||||
import HideOrShowAll from "@/core/components/DataTable/hide/HideOrShowAll";
|
||||
|
||||
const ScrollBox = styled(Box)({
|
||||
flexGrow: 1,
|
||||
overflowY: "scroll",
|
||||
maxWidth: "450px",
|
||||
"::-webkit-scrollbar": {
|
||||
width: "5px",
|
||||
},
|
||||
"::-webkit-scrollbar-track": {
|
||||
boxShadow: "inset 0 0 5px #fff",
|
||||
borderRadius: "5px",
|
||||
},
|
||||
"::-webkit-scrollbar-thumb": {
|
||||
background: "#155175",
|
||||
borderRadius: "0px",
|
||||
},
|
||||
});
|
||||
|
||||
function HideBody({ columns, drawerState, setDrawerState }) {
|
||||
const { hideData, setHideData } = useDataTable();
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
open={drawerState}
|
||||
onClose={() => setDrawerState(false)}
|
||||
sx={{ overflowY: "hidden", display: "flex", flexDirection: "column", height: "100%", zIndex: "1300" }}
|
||||
>
|
||||
<HideHeader setDrawerState={setDrawerState} />
|
||||
<HideOrShowAll hideData={hideData} setHideData={setHideData} />
|
||||
<ScrollBox>
|
||||
<Box sx={{ px: 1, py: 2, display: "flex", flexDirection: "column" }}>
|
||||
{columns.map((column) => (
|
||||
<HideBodyField key={column.id} column={column} hideData={hideData} setHideData={setHideData} />
|
||||
))}
|
||||
</Box>
|
||||
</ScrollBox>
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
|
||||
export default HideBody;
|
||||
"use client";
|
||||
|
||||
import { Box, Drawer, styled } from "@mui/material";
|
||||
import HideBodyField from "@/core/components/DataTable/hide/HideBodyField";
|
||||
import HideHeader from "@/core/components/DataTable/hide/HideHeader";
|
||||
import useDataTable from "@/lib/hooks/useDataTable";
|
||||
import HideOrShowAll from "@/core/components/DataTable/hide/HideOrShowAll";
|
||||
|
||||
const ScrollBox = styled(Box)({
|
||||
flexGrow: 1,
|
||||
overflowY: "scroll",
|
||||
maxWidth: "450px",
|
||||
"::-webkit-scrollbar": {
|
||||
width: "5px",
|
||||
},
|
||||
"::-webkit-scrollbar-track": {
|
||||
boxShadow: "inset 0 0 5px #fff",
|
||||
borderRadius: "5px",
|
||||
},
|
||||
"::-webkit-scrollbar-thumb": {
|
||||
background: "#155175",
|
||||
borderRadius: "0px",
|
||||
},
|
||||
});
|
||||
|
||||
function HideBody({ columns, drawerState, setDrawerState }) {
|
||||
const { hideData, setHideData } = useDataTable();
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
open={drawerState}
|
||||
onClose={() => setDrawerState(false)}
|
||||
sx={{ overflowY: "hidden", display: "flex", flexDirection: "column", height: "100%", zIndex: "1300" }}
|
||||
>
|
||||
<HideHeader setDrawerState={setDrawerState} />
|
||||
<HideOrShowAll hideData={hideData} setHideData={setHideData} />
|
||||
<ScrollBox>
|
||||
<Box sx={{ px: 1, py: 2, display: "flex", flexDirection: "column" }}>
|
||||
{columns.map((column) => (
|
||||
<HideBodyField key={column.id} column={column} hideData={hideData} setHideData={setHideData} />
|
||||
))}
|
||||
</Box>
|
||||
</ScrollBox>
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
|
||||
export default HideBody;
|
||||
|
||||
@@ -1,76 +1,76 @@
|
||||
import { alpha, Box, Checkbox, styled, Typography } from "@mui/material";
|
||||
import { SimpleTreeView } from "@mui/x-tree-view";
|
||||
import { TreeItem, treeItemClasses } from "@mui/x-tree-view/TreeItem";
|
||||
|
||||
const CustomTreeItem = styled(TreeItem)(({ theme }) => ({
|
||||
[`& .${treeItemClasses.content}`]: {
|
||||
padding: theme.spacing(0.5, 0.5),
|
||||
margin: theme.spacing(0.2, 0),
|
||||
gap: 0,
|
||||
},
|
||||
[`& .${treeItemClasses.iconContainer}`]: {
|
||||
"& .close": {
|
||||
opacity: 0.3,
|
||||
},
|
||||
},
|
||||
[`& .${treeItemClasses.groupTransition}`]: {
|
||||
marginLeft: 16,
|
||||
paddingLeft: 16,
|
||||
borderLeft: `1px dashed ${alpha(theme.palette.text.primary, 0.4)}`,
|
||||
},
|
||||
}));
|
||||
|
||||
function HideBodyField({ column, hideData, setHideData }) {
|
||||
const handleCheckboxChange = () => {
|
||||
setHideData((prevData) => {
|
||||
const updateHideData = (data, id) => {
|
||||
if (data.hasOwnProperty(id)) {
|
||||
return { ...data, [id]: !data[id] };
|
||||
}
|
||||
const updatedData = { ...data };
|
||||
for (const key in data) {
|
||||
if (data[key] && typeof data[key] === "object") {
|
||||
updatedData[key] = updateHideData(data[key], id);
|
||||
}
|
||||
}
|
||||
return updatedData;
|
||||
};
|
||||
|
||||
return updateHideData(prevData, column.id);
|
||||
});
|
||||
};
|
||||
|
||||
const labelContent = (() => {
|
||||
if (typeof hideData[column.id] === "boolean") {
|
||||
return (
|
||||
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||
<Checkbox checked={hideData[column.id]} onChange={handleCheckboxChange} name={column.id} />
|
||||
<Typography variant="subtitle1">{column.header}</Typography>
|
||||
</Box>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Box sx={{ display: "flex", alignItems: "center", p: 1 }}>
|
||||
<Typography variant="subtitle1">{column.header}</Typography>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
})();
|
||||
|
||||
return (
|
||||
<SimpleTreeView disableSelection>
|
||||
<CustomTreeItem itemId={column.id} label={labelContent}>
|
||||
{column.columns?.map((subColumn) => (
|
||||
<HideBodyField
|
||||
key={subColumn.id}
|
||||
column={subColumn}
|
||||
hideData={hideData[column.id]}
|
||||
setHideData={setHideData}
|
||||
/>
|
||||
))}
|
||||
</CustomTreeItem>
|
||||
</SimpleTreeView>
|
||||
);
|
||||
}
|
||||
|
||||
export default HideBodyField;
|
||||
import { alpha, Box, Checkbox, styled, Typography } from "@mui/material";
|
||||
import { SimpleTreeView } from "@mui/x-tree-view";
|
||||
import { TreeItem, treeItemClasses } from "@mui/x-tree-view/TreeItem";
|
||||
|
||||
const CustomTreeItem = styled(TreeItem)(({ theme }) => ({
|
||||
[`& .${treeItemClasses.content}`]: {
|
||||
padding: theme.spacing(0.5, 0.5),
|
||||
margin: theme.spacing(0.2, 0),
|
||||
gap: 0,
|
||||
},
|
||||
[`& .${treeItemClasses.iconContainer}`]: {
|
||||
"& .close": {
|
||||
opacity: 0.3,
|
||||
},
|
||||
},
|
||||
[`& .${treeItemClasses.groupTransition}`]: {
|
||||
marginLeft: 16,
|
||||
paddingLeft: 16,
|
||||
borderLeft: `1px dashed ${alpha(theme.palette.text.primary, 0.4)}`,
|
||||
},
|
||||
}));
|
||||
|
||||
function HideBodyField({ column, hideData, setHideData }) {
|
||||
const handleCheckboxChange = () => {
|
||||
setHideData((prevData) => {
|
||||
const updateHideData = (data, id) => {
|
||||
if (data.hasOwnProperty(id)) {
|
||||
return { ...data, [id]: !data[id] };
|
||||
}
|
||||
const updatedData = { ...data };
|
||||
for (const key in data) {
|
||||
if (data[key] && typeof data[key] === "object") {
|
||||
updatedData[key] = updateHideData(data[key], id);
|
||||
}
|
||||
}
|
||||
return updatedData;
|
||||
};
|
||||
|
||||
return updateHideData(prevData, column.id);
|
||||
});
|
||||
};
|
||||
|
||||
const labelContent = (() => {
|
||||
if (typeof hideData[column.id] === "boolean") {
|
||||
return (
|
||||
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||
<Checkbox checked={hideData[column.id]} onChange={handleCheckboxChange} name={column.id} />
|
||||
<Typography variant="subtitle1">{column.header}</Typography>
|
||||
</Box>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Box sx={{ display: "flex", alignItems: "center", p: 1 }}>
|
||||
<Typography variant="subtitle1">{column.header}</Typography>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
})();
|
||||
|
||||
return (
|
||||
<SimpleTreeView disableSelection>
|
||||
<CustomTreeItem itemId={column.id} label={labelContent}>
|
||||
{column.columns?.map((subColumn) => (
|
||||
<HideBodyField
|
||||
key={subColumn.id}
|
||||
column={subColumn}
|
||||
hideData={hideData[column.id]}
|
||||
setHideData={setHideData}
|
||||
/>
|
||||
))}
|
||||
</CustomTreeItem>
|
||||
</SimpleTreeView>
|
||||
);
|
||||
}
|
||||
|
||||
export default HideBodyField;
|
||||
|
||||
@@ -1,35 +1,35 @@
|
||||
"use client";
|
||||
import ViewColumnIcon from "@mui/icons-material/ViewColumn";
|
||||
import { IconButton, Tooltip, Badge } from "@mui/material";
|
||||
import useDataTable from "@/lib/hooks/useDataTable";
|
||||
import { flattenObjectOfObjects } from "@/core/utils/flattenObjectOfObjects";
|
||||
|
||||
function HideButton({ drawerState, setDrawerState }) {
|
||||
const { hideData } = useDataTable();
|
||||
const flattenHideData = flattenObjectOfObjects(hideData);
|
||||
const falseCount = Object.values(flattenHideData).filter((value) => value === false).length;
|
||||
|
||||
return (
|
||||
<Tooltip title="نمایش/مخفی کردن ستون ها">
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
setDrawerState(!drawerState);
|
||||
}}
|
||||
aria-label="hide table column"
|
||||
>
|
||||
<Badge
|
||||
badgeContent={falseCount}
|
||||
color="primary"
|
||||
anchorOrigin={{
|
||||
vertical: "top",
|
||||
horizontal: "left",
|
||||
}}
|
||||
>
|
||||
<ViewColumnIcon />
|
||||
</Badge>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
export default HideButton;
|
||||
"use client";
|
||||
import ViewColumnIcon from "@mui/icons-material/ViewColumn";
|
||||
import { IconButton, Tooltip, Badge } from "@mui/material";
|
||||
import useDataTable from "@/lib/hooks/useDataTable";
|
||||
import { flattenObjectOfObjects } from "@/core/utils/flattenObjectOfObjects";
|
||||
|
||||
function HideButton({ drawerState, setDrawerState }) {
|
||||
const { hideData } = useDataTable();
|
||||
const flattenHideData = flattenObjectOfObjects(hideData);
|
||||
const falseCount = Object.values(flattenHideData).filter((value) => value === false).length;
|
||||
|
||||
return (
|
||||
<Tooltip title="نمایش/مخفی کردن ستون ها">
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
setDrawerState(!drawerState);
|
||||
}}
|
||||
aria-label="hide table column"
|
||||
>
|
||||
<Badge
|
||||
badgeContent={falseCount}
|
||||
color="primary"
|
||||
anchorOrigin={{
|
||||
vertical: "top",
|
||||
horizontal: "left",
|
||||
}}
|
||||
>
|
||||
<ViewColumnIcon />
|
||||
</Badge>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
export default HideButton;
|
||||
|
||||
@@ -1,34 +1,34 @@
|
||||
"use client";
|
||||
|
||||
import { Box, IconButton, Typography } from "@mui/material";
|
||||
import CancelIcon from "@mui/icons-material/Cancel";
|
||||
import ViewColumnIcon from "@mui/icons-material/ViewColumn";
|
||||
|
||||
function FilterHeader({ setDrawerState }) {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
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",
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||
<ViewColumnIcon sx={{ color: "#fff", mr: 1 }} />
|
||||
<Typography variant="h6" sx={{ color: "#fff" }}>
|
||||
نمایش/مخفی کردن ستون ها
|
||||
</Typography>
|
||||
</Box>
|
||||
<IconButton sx={{ color: "#fff" }} onClick={() => setDrawerState(false)}>
|
||||
<CancelIcon sx={{ fontSize: "1em" }} />
|
||||
</IconButton>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default FilterHeader;
|
||||
"use client";
|
||||
|
||||
import { Box, IconButton, Typography } from "@mui/material";
|
||||
import CancelIcon from "@mui/icons-material/Cancel";
|
||||
import ViewColumnIcon from "@mui/icons-material/ViewColumn";
|
||||
|
||||
function FilterHeader({ setDrawerState }) {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
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",
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||
<ViewColumnIcon sx={{ color: "#fff", mr: 1 }} />
|
||||
<Typography variant="h6" sx={{ color: "#fff" }}>
|
||||
نمایش/مخفی کردن ستون ها
|
||||
</Typography>
|
||||
</Box>
|
||||
<IconButton sx={{ color: "#fff" }} onClick={() => setDrawerState(false)}>
|
||||
<CancelIcon sx={{ fontSize: "1em" }} />
|
||||
</IconButton>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default FilterHeader;
|
||||
|
||||
@@ -1,70 +1,70 @@
|
||||
"use client";
|
||||
|
||||
import { Box, Button } from "@mui/material";
|
||||
import VisibilityIcon from "@mui/icons-material/Visibility";
|
||||
import VisibilityOffIcon from "@mui/icons-material/VisibilityOff";
|
||||
|
||||
const setAllValues = (obj, value) => {
|
||||
const result = {};
|
||||
for (const key in obj) {
|
||||
if (typeof obj[key] === "object" && obj[key] !== null) {
|
||||
result[key] = setAllValues(obj[key], value);
|
||||
} else {
|
||||
result[key] = value;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
const allValuesAre = (obj, value) => {
|
||||
for (const key in obj) {
|
||||
if (typeof obj[key] === "object" && obj[key] !== null) {
|
||||
if (!allValuesAre(obj[key], value)) {
|
||||
return false;
|
||||
}
|
||||
} else if (obj[key] !== value) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
function HideOrShowAll({ hideData, setHideData }) {
|
||||
const handleShowAll = () => {
|
||||
const newHideData = setAllValues(hideData, true);
|
||||
setHideData(newHideData);
|
||||
};
|
||||
|
||||
const handleHideAll = () => {
|
||||
const newHideData = setAllValues(hideData, false);
|
||||
setHideData(newHideData);
|
||||
};
|
||||
|
||||
const allHidden = allValuesAre(hideData, false);
|
||||
const allVisible = allValuesAre(hideData, true);
|
||||
|
||||
return (
|
||||
<Box sx={{ my: 1, display: "flex" }}>
|
||||
<Button
|
||||
sx={{ flex: 1 }}
|
||||
color="info"
|
||||
disabled={allVisible}
|
||||
startIcon={<VisibilityIcon />}
|
||||
onClick={handleShowAll}
|
||||
>
|
||||
نمایش همه
|
||||
</Button>
|
||||
<Button
|
||||
sx={{ flex: 1 }}
|
||||
color="error"
|
||||
disabled={allHidden}
|
||||
startIcon={<VisibilityOffIcon />}
|
||||
onClick={handleHideAll}
|
||||
>
|
||||
مخفی کردن همه
|
||||
</Button>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default HideOrShowAll;
|
||||
"use client";
|
||||
|
||||
import { Box, Button } from "@mui/material";
|
||||
import VisibilityIcon from "@mui/icons-material/Visibility";
|
||||
import VisibilityOffIcon from "@mui/icons-material/VisibilityOff";
|
||||
|
||||
const setAllValues = (obj, value) => {
|
||||
const result = {};
|
||||
for (const key in obj) {
|
||||
if (typeof obj[key] === "object" && obj[key] !== null) {
|
||||
result[key] = setAllValues(obj[key], value);
|
||||
} else {
|
||||
result[key] = value;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
const allValuesAre = (obj, value) => {
|
||||
for (const key in obj) {
|
||||
if (typeof obj[key] === "object" && obj[key] !== null) {
|
||||
if (!allValuesAre(obj[key], value)) {
|
||||
return false;
|
||||
}
|
||||
} else if (obj[key] !== value) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
function HideOrShowAll({ hideData, setHideData }) {
|
||||
const handleShowAll = () => {
|
||||
const newHideData = setAllValues(hideData, true);
|
||||
setHideData(newHideData);
|
||||
};
|
||||
|
||||
const handleHideAll = () => {
|
||||
const newHideData = setAllValues(hideData, false);
|
||||
setHideData(newHideData);
|
||||
};
|
||||
|
||||
const allHidden = allValuesAre(hideData, false);
|
||||
const allVisible = allValuesAre(hideData, true);
|
||||
|
||||
return (
|
||||
<Box sx={{ my: 1, display: "flex" }}>
|
||||
<Button
|
||||
sx={{ flex: 1 }}
|
||||
color="info"
|
||||
disabled={allVisible}
|
||||
startIcon={<VisibilityIcon />}
|
||||
onClick={handleShowAll}
|
||||
>
|
||||
نمایش همه
|
||||
</Button>
|
||||
<Button
|
||||
sx={{ flex: 1 }}
|
||||
color="error"
|
||||
disabled={allHidden}
|
||||
startIcon={<VisibilityOffIcon />}
|
||||
onClick={handleHideAll}
|
||||
>
|
||||
مخفی کردن همه
|
||||
</Button>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default HideOrShowAll;
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import HideButton from "@/core/components/DataTable/hide/HideButton";
|
||||
import HideBody from "@/core/components/DataTable/hide/HideBody";
|
||||
|
||||
function HideColumn({ columns, user_id, page_name, table_name }) {
|
||||
const [open, setOpen] = useState(false);
|
||||
return (
|
||||
<>
|
||||
<HideButton drawerState={open} setDrawerState={setOpen} />
|
||||
<HideBody
|
||||
columns={columns}
|
||||
drawerState={open}
|
||||
setDrawerState={setOpen}
|
||||
user_id={user_id}
|
||||
page_name={page_name}
|
||||
table_name={table_name}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default HideColumn;
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import HideButton from "@/core/components/DataTable/hide/HideButton";
|
||||
import HideBody from "@/core/components/DataTable/hide/HideBody";
|
||||
|
||||
function HideColumn({ columns, user_id, page_name, table_name }) {
|
||||
const [open, setOpen] = useState(false);
|
||||
return (
|
||||
<>
|
||||
<HideButton drawerState={open} setDrawerState={setOpen} />
|
||||
<HideBody
|
||||
columns={columns}
|
||||
drawerState={open}
|
||||
setDrawerState={setOpen}
|
||||
user_id={user_id}
|
||||
page_name={page_name}
|
||||
table_name={table_name}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default HideColumn;
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import DataTable_Main from "./Main";
|
||||
import DataTableProvider from "@/lib/contexts/DataTable";
|
||||
|
||||
const DataTable = (props) => {
|
||||
return (
|
||||
<DataTableProvider
|
||||
user_id={props.user_id}
|
||||
page_name={props.page_name}
|
||||
table_name={props.table_name}
|
||||
columns={props.columns}
|
||||
initialSort={props.sorting}
|
||||
>
|
||||
<DataTable_Main {...props} />
|
||||
</DataTableProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default DataTable;
|
||||
import DataTable_Main from "./Main";
|
||||
import DataTableProvider from "@/lib/contexts/DataTable";
|
||||
|
||||
const DataTable = (props) => {
|
||||
return (
|
||||
<DataTableProvider
|
||||
user_id={props.user_id}
|
||||
page_name={props.page_name}
|
||||
table_name={props.table_name}
|
||||
columns={props.columns}
|
||||
initialSort={props.sorting}
|
||||
>
|
||||
<DataTable_Main {...props} />
|
||||
</DataTableProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default DataTable;
|
||||
|
||||
@@ -1,91 +1,91 @@
|
||||
export const FA_DATATABLE_LOCALIZATION = {
|
||||
actions: "عملیات",
|
||||
and: "و",
|
||||
cancel: "لغو",
|
||||
changeFilterMode: "تغییر حالت فیلتر",
|
||||
changeSearchMode: "تغییر حالت جستجو",
|
||||
clearFilter: "پاک کردن فیلتر",
|
||||
clearSearch: "پاک کردن جستجو",
|
||||
clearSort: "پاک کردن مرتب سازی",
|
||||
clickToCopy: "کلیک برای کپی",
|
||||
collapse: "جمع شدن",
|
||||
collapseAll: "جمع شدن همه",
|
||||
columnActions: "عملیات ستون",
|
||||
copiedToClipboard: "کپی شد",
|
||||
dropToGroupBy: "رها کردن برای گروه بندی بر اساس {column}",
|
||||
edit: "ویرایش",
|
||||
expand: "باز شدن",
|
||||
expandAll: "باز شدن همه",
|
||||
filterArrIncludes: "شامل",
|
||||
filterArrIncludesAll: "شامل همه",
|
||||
filterArrIncludesSome: "شامل",
|
||||
filterBetween: "میان",
|
||||
filterBetweenInclusive: "میان با احتساب هر دو",
|
||||
filterByColumn: "فیلتر بر اساس {column}",
|
||||
filterContains: "شامل",
|
||||
filterEmpty: "خالی",
|
||||
filterEndsWith: "به پایان میرسد با",
|
||||
filterEquals: "برابر",
|
||||
filterEqualsString: "برابر",
|
||||
filterFuzzy: "نزدیک",
|
||||
filterGreaterThan: "بزرگتر از",
|
||||
filterGreaterThanOrEqualTo: "بزرگتر یا مساوی",
|
||||
filterInNumberRange: "میان",
|
||||
filterIncludesString: "شامل",
|
||||
filterIncludesStringSensitive: "شامل",
|
||||
filterLessThan: "کوچکتر از",
|
||||
filterLessThanOrEqualTo: "کوچکتر یا مساوی",
|
||||
filterMode: "حالت فیلتر: {filterType}",
|
||||
filterNotEmpty: "غیر خالی",
|
||||
filterNotEquals: "نا برابر",
|
||||
filterStartsWith: "شروع میشود با",
|
||||
filterWeakEquals: "برابر",
|
||||
filteringByColumn: "فیلتر بر اساس {column} - {filterType} {filterValue}",
|
||||
goToFirstPage: "رفتن به صفحه اول",
|
||||
goToLastPage: "رفتن به صفحه آخر",
|
||||
goToNextPage: "رفتن به صفحه بعدی",
|
||||
goToPreviousPage: "رفتن به صفحه قبلی",
|
||||
grab: "گرفتن",
|
||||
groupByColumn: "گروه بندی بر اساس {column}",
|
||||
groupedBy: "گروه بندی شده بر اساس",
|
||||
hideAll: "پنهان کردن همه",
|
||||
hideColumn: "پنهان کردن ستون {column}",
|
||||
max: "حداکثر",
|
||||
min: "حداقل",
|
||||
move: "انتقال",
|
||||
noRecordsToDisplay: "هیچ رکوردی برای نمایش وجود ندارد",
|
||||
noResultsFound: "نتیجهای یافت نشد",
|
||||
of: "از",
|
||||
or: "یا",
|
||||
pinToLeft: "چسباندن به سمت چپ",
|
||||
pinToRight: "چسباندن به سمت راست",
|
||||
resetColumnSize: "بازنشانی اندازه ستون",
|
||||
resetOrder: "بازنشانی ترتیب",
|
||||
rowActions: "عملیات ردیف",
|
||||
rowNumber: "#",
|
||||
rowNumbers: "شماره ردیف",
|
||||
rowsPerPage: "ردیف در هر صفحه",
|
||||
save: "ذخیره",
|
||||
search: "جستجو",
|
||||
selectedCountOfRowCountRowsSelected: "{selectedCount} از {rowCount} ردیف انتخاب شده",
|
||||
select: "انتخاب",
|
||||
showAll: "نمایش همه",
|
||||
showAllColumns: "نمایش همه ستونها",
|
||||
showHideColumns: "نمایش/مخفی کردن ستونها",
|
||||
showHideFilters: "نمایش/مخفی کردن فیلترها",
|
||||
showHideSearch: "نمایش/مخفی کردن جستجو",
|
||||
sortByColumnAsc: "مرتب سازی بر اساس {column} صعودی",
|
||||
sortByColumnDesc: "مرتب سازی بر اساس {column} نزولی",
|
||||
sortedByColumnAsc: "مرتب شده بر اساس {column} صعودی",
|
||||
sortedByColumnDesc: "مرتب شده بر اساس {column} نزولی",
|
||||
thenBy: "، سپس بر اساس ",
|
||||
toggleDensity: "تغییر تراکم",
|
||||
toggleFullScreen: "تغییر حالت تمام صفحه",
|
||||
toggleSelectAll: "انتخاب/عدم انتخاب همه",
|
||||
toggleSelectRow: "انتخاب/عدم انتخاب ردیف",
|
||||
toggleVisibility: "تغییر پیدا/پنهان",
|
||||
ungroupByColumn: "لغو گروه بندی بر اساس {column}",
|
||||
unpin: "رها کردن",
|
||||
unpinAll: "رها کردن همه",
|
||||
unsorted: "بدون مرتب سازی",
|
||||
};
|
||||
export const FA_DATATABLE_LOCALIZATION = {
|
||||
actions: "عملیات",
|
||||
and: "و",
|
||||
cancel: "لغو",
|
||||
changeFilterMode: "تغییر حالت فیلتر",
|
||||
changeSearchMode: "تغییر حالت جستجو",
|
||||
clearFilter: "پاک کردن فیلتر",
|
||||
clearSearch: "پاک کردن جستجو",
|
||||
clearSort: "پاک کردن مرتب سازی",
|
||||
clickToCopy: "کلیک برای کپی",
|
||||
collapse: "جمع شدن",
|
||||
collapseAll: "جمع شدن همه",
|
||||
columnActions: "عملیات ستون",
|
||||
copiedToClipboard: "کپی شد",
|
||||
dropToGroupBy: "رها کردن برای گروه بندی بر اساس {column}",
|
||||
edit: "ویرایش",
|
||||
expand: "باز شدن",
|
||||
expandAll: "باز شدن همه",
|
||||
filterArrIncludes: "شامل",
|
||||
filterArrIncludesAll: "شامل همه",
|
||||
filterArrIncludesSome: "شامل",
|
||||
filterBetween: "میان",
|
||||
filterBetweenInclusive: "میان با احتساب هر دو",
|
||||
filterByColumn: "فیلتر بر اساس {column}",
|
||||
filterContains: "شامل",
|
||||
filterEmpty: "خالی",
|
||||
filterEndsWith: "به پایان میرسد با",
|
||||
filterEquals: "برابر",
|
||||
filterEqualsString: "برابر",
|
||||
filterFuzzy: "نزدیک",
|
||||
filterGreaterThan: "بزرگتر از",
|
||||
filterGreaterThanOrEqualTo: "بزرگتر یا مساوی",
|
||||
filterInNumberRange: "میان",
|
||||
filterIncludesString: "شامل",
|
||||
filterIncludesStringSensitive: "شامل",
|
||||
filterLessThan: "کوچکتر از",
|
||||
filterLessThanOrEqualTo: "کوچکتر یا مساوی",
|
||||
filterMode: "حالت فیلتر: {filterType}",
|
||||
filterNotEmpty: "غیر خالی",
|
||||
filterNotEquals: "نا برابر",
|
||||
filterStartsWith: "شروع میشود با",
|
||||
filterWeakEquals: "برابر",
|
||||
filteringByColumn: "فیلتر بر اساس {column} - {filterType} {filterValue}",
|
||||
goToFirstPage: "رفتن به صفحه اول",
|
||||
goToLastPage: "رفتن به صفحه آخر",
|
||||
goToNextPage: "رفتن به صفحه بعدی",
|
||||
goToPreviousPage: "رفتن به صفحه قبلی",
|
||||
grab: "گرفتن",
|
||||
groupByColumn: "گروه بندی بر اساس {column}",
|
||||
groupedBy: "گروه بندی شده بر اساس",
|
||||
hideAll: "پنهان کردن همه",
|
||||
hideColumn: "پنهان کردن ستون {column}",
|
||||
max: "حداکثر",
|
||||
min: "حداقل",
|
||||
move: "انتقال",
|
||||
noRecordsToDisplay: "هیچ رکوردی برای نمایش وجود ندارد",
|
||||
noResultsFound: "نتیجهای یافت نشد",
|
||||
of: "از",
|
||||
or: "یا",
|
||||
pinToLeft: "چسباندن به سمت چپ",
|
||||
pinToRight: "چسباندن به سمت راست",
|
||||
resetColumnSize: "بازنشانی اندازه ستون",
|
||||
resetOrder: "بازنشانی ترتیب",
|
||||
rowActions: "عملیات ردیف",
|
||||
rowNumber: "#",
|
||||
rowNumbers: "شماره ردیف",
|
||||
rowsPerPage: "ردیف در هر صفحه",
|
||||
save: "ذخیره",
|
||||
search: "جستجو",
|
||||
selectedCountOfRowCountRowsSelected: "{selectedCount} از {rowCount} ردیف انتخاب شده",
|
||||
select: "انتخاب",
|
||||
showAll: "نمایش همه",
|
||||
showAllColumns: "نمایش همه ستونها",
|
||||
showHideColumns: "نمایش/مخفی کردن ستونها",
|
||||
showHideFilters: "نمایش/مخفی کردن فیلترها",
|
||||
showHideSearch: "نمایش/مخفی کردن جستجو",
|
||||
sortByColumnAsc: "مرتب سازی بر اساس {column} صعودی",
|
||||
sortByColumnDesc: "مرتب سازی بر اساس {column} نزولی",
|
||||
sortedByColumnAsc: "مرتب شده بر اساس {column} صعودی",
|
||||
sortedByColumnDesc: "مرتب شده بر اساس {column} نزولی",
|
||||
thenBy: "، سپس بر اساس ",
|
||||
toggleDensity: "تغییر تراکم",
|
||||
toggleFullScreen: "تغییر حالت تمام صفحه",
|
||||
toggleSelectAll: "انتخاب/عدم انتخاب همه",
|
||||
toggleSelectRow: "انتخاب/عدم انتخاب ردیف",
|
||||
toggleVisibility: "تغییر پیدا/پنهان",
|
||||
ungroupByColumn: "لغو گروه بندی بر اساس {column}",
|
||||
unpin: "رها کردن",
|
||||
unpinAll: "رها کردن همه",
|
||||
unsorted: "بدون مرتب سازی",
|
||||
};
|
||||
|
||||
@@ -1,38 +1,38 @@
|
||||
import { Box, IconButton, ListItemIcon, MenuItem } from "@mui/material";
|
||||
|
||||
const DataTable_ActionMenuItem = ({ icon, label, onOpenSubMenu, table, ...rest }) => {
|
||||
const {
|
||||
options: {
|
||||
icons: { ArrowRightIcon },
|
||||
},
|
||||
} = table;
|
||||
|
||||
return (
|
||||
<MenuItem
|
||||
sx={{
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
minWidth: "120px",
|
||||
my: 0,
|
||||
py: "6px",
|
||||
}}
|
||||
{...rest}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
alignItems: "center",
|
||||
display: "flex",
|
||||
}}
|
||||
>
|
||||
<ListItemIcon>{icon}</ListItemIcon>
|
||||
{label}
|
||||
</Box>
|
||||
{onOpenSubMenu && (
|
||||
<IconButton onClick={onOpenSubMenu} onMouseEnter={onOpenSubMenu} size="small" sx={{ p: 0 }}>
|
||||
<ArrowRightIcon />
|
||||
</IconButton>
|
||||
)}
|
||||
</MenuItem>
|
||||
);
|
||||
};
|
||||
export default DataTable_ActionMenuItem;
|
||||
import { Box, IconButton, ListItemIcon, MenuItem } from "@mui/material";
|
||||
|
||||
const DataTable_ActionMenuItem = ({ icon, label, onOpenSubMenu, table, ...rest }) => {
|
||||
const {
|
||||
options: {
|
||||
icons: { ArrowRightIcon },
|
||||
},
|
||||
} = table;
|
||||
|
||||
return (
|
||||
<MenuItem
|
||||
sx={{
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
minWidth: "120px",
|
||||
my: 0,
|
||||
py: "6px",
|
||||
}}
|
||||
{...rest}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
alignItems: "center",
|
||||
display: "flex",
|
||||
}}
|
||||
>
|
||||
<ListItemIcon>{icon}</ListItemIcon>
|
||||
{label}
|
||||
</Box>
|
||||
{onOpenSubMenu && (
|
||||
<IconButton onClick={onOpenSubMenu} onMouseEnter={onOpenSubMenu} size="small" sx={{ p: 0 }}>
|
||||
<ArrowRightIcon />
|
||||
</IconButton>
|
||||
)}
|
||||
</MenuItem>
|
||||
);
|
||||
};
|
||||
export default DataTable_ActionMenuItem;
|
||||
|
||||
@@ -1,94 +1,94 @@
|
||||
import { parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||
import { Menu } from "@mui/material";
|
||||
import DataTable_ActionMenuItem from "./ActionMenuItem";
|
||||
|
||||
const DataTable_CellActionMenu = ({ table, ...rest }) => {
|
||||
const {
|
||||
getState,
|
||||
options: {
|
||||
editDisplayMode,
|
||||
enableClickToCopy,
|
||||
enableEditing,
|
||||
icons: { ContentCopy, EditIcon },
|
||||
localization,
|
||||
mrtTheme: { menuBackgroundColor },
|
||||
renderCellActionMenuItems,
|
||||
},
|
||||
refs: { actionCellRef },
|
||||
} = table;
|
||||
const { actionCell, density } = getState();
|
||||
const cell = actionCell || null;
|
||||
const { row } = cell;
|
||||
const { column } = cell;
|
||||
const { columnDef } = column;
|
||||
|
||||
const handleClose = (event) => {
|
||||
event?.stopPropagation();
|
||||
table.setActionCell(null);
|
||||
actionCellRef.current = null;
|
||||
};
|
||||
|
||||
const internalMenuItems = [
|
||||
(parseFromValuesOrFunc(enableClickToCopy, cell) === "context-menu" ||
|
||||
parseFromValuesOrFunc(columnDef.enableClickToCopy, cell) === "context-menu") && (
|
||||
<DataTable_ActionMenuItem
|
||||
icon={<ContentCopy />}
|
||||
key={"mrt-copy"}
|
||||
label={localization.copy}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
navigator.clipboard.writeText(cell.getValue());
|
||||
handleClose();
|
||||
}}
|
||||
table={table}
|
||||
/>
|
||||
),
|
||||
parseFromValuesOrFunc(enableEditing, row) && editDisplayMode === "cell" && (
|
||||
<DataTable_ActionMenuItem
|
||||
icon={<EditIcon />}
|
||||
key={"mrt-edit"}
|
||||
label={localization.edit}
|
||||
onClick={() => {
|
||||
openEditingCell({ cell, table });
|
||||
handleClose();
|
||||
}}
|
||||
table={table}
|
||||
/>
|
||||
),
|
||||
].filter(Boolean);
|
||||
|
||||
const renderActionProps = {
|
||||
cell,
|
||||
closeMenu: handleClose,
|
||||
column,
|
||||
internalMenuItems,
|
||||
row,
|
||||
table,
|
||||
};
|
||||
|
||||
const menuItems =
|
||||
columnDef.renderCellActionMenuItems?.(renderActionProps) ?? renderCellActionMenuItems?.(renderActionProps);
|
||||
|
||||
return (
|
||||
(!!menuItems?.length || !!internalMenuItems?.length) && (
|
||||
<Menu
|
||||
MenuListProps={{
|
||||
dense: density === "compact",
|
||||
sx: {
|
||||
backgroundColor: menuBackgroundColor,
|
||||
},
|
||||
}}
|
||||
anchorEl={actionCellRef.current}
|
||||
disableScrollLock
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
onClose={handleClose}
|
||||
open={!!cell}
|
||||
transformOrigin={{ horizontal: -100, vertical: 8 }}
|
||||
{...rest}
|
||||
>
|
||||
{menuItems ?? internalMenuItems}
|
||||
</Menu>
|
||||
)
|
||||
);
|
||||
};
|
||||
export default DataTable_CellActionMenu;
|
||||
import { parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||
import { Menu } from "@mui/material";
|
||||
import DataTable_ActionMenuItem from "./ActionMenuItem";
|
||||
|
||||
const DataTable_CellActionMenu = ({ table, ...rest }) => {
|
||||
const {
|
||||
getState,
|
||||
options: {
|
||||
editDisplayMode,
|
||||
enableClickToCopy,
|
||||
enableEditing,
|
||||
icons: { ContentCopy, EditIcon },
|
||||
localization,
|
||||
mrtTheme: { menuBackgroundColor },
|
||||
renderCellActionMenuItems,
|
||||
},
|
||||
refs: { actionCellRef },
|
||||
} = table;
|
||||
const { actionCell, density } = getState();
|
||||
const cell = actionCell || null;
|
||||
const { row } = cell;
|
||||
const { column } = cell;
|
||||
const { columnDef } = column;
|
||||
|
||||
const handleClose = (event) => {
|
||||
event?.stopPropagation();
|
||||
table.setActionCell(null);
|
||||
actionCellRef.current = null;
|
||||
};
|
||||
|
||||
const internalMenuItems = [
|
||||
(parseFromValuesOrFunc(enableClickToCopy, cell) === "context-menu" ||
|
||||
parseFromValuesOrFunc(columnDef.enableClickToCopy, cell) === "context-menu") && (
|
||||
<DataTable_ActionMenuItem
|
||||
icon={<ContentCopy />}
|
||||
key={"mrt-copy"}
|
||||
label={localization.copy}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
navigator.clipboard.writeText(cell.getValue());
|
||||
handleClose();
|
||||
}}
|
||||
table={table}
|
||||
/>
|
||||
),
|
||||
parseFromValuesOrFunc(enableEditing, row) && editDisplayMode === "cell" && (
|
||||
<DataTable_ActionMenuItem
|
||||
icon={<EditIcon />}
|
||||
key={"mrt-edit"}
|
||||
label={localization.edit}
|
||||
onClick={() => {
|
||||
openEditingCell({ cell, table });
|
||||
handleClose();
|
||||
}}
|
||||
table={table}
|
||||
/>
|
||||
),
|
||||
].filter(Boolean);
|
||||
|
||||
const renderActionProps = {
|
||||
cell,
|
||||
closeMenu: handleClose,
|
||||
column,
|
||||
internalMenuItems,
|
||||
row,
|
||||
table,
|
||||
};
|
||||
|
||||
const menuItems =
|
||||
columnDef.renderCellActionMenuItems?.(renderActionProps) ?? renderCellActionMenuItems?.(renderActionProps);
|
||||
|
||||
return (
|
||||
(!!menuItems?.length || !!internalMenuItems?.length) && (
|
||||
<Menu
|
||||
MenuListProps={{
|
||||
dense: density === "compact",
|
||||
sx: {
|
||||
backgroundColor: menuBackgroundColor,
|
||||
},
|
||||
}}
|
||||
anchorEl={actionCellRef.current}
|
||||
disableScrollLock
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
onClose={handleClose}
|
||||
open={!!cell}
|
||||
transformOrigin={{ horizontal: -100, vertical: 8 }}
|
||||
{...rest}
|
||||
>
|
||||
{menuItems ?? internalMenuItems}
|
||||
</Menu>
|
||||
)
|
||||
);
|
||||
};
|
||||
export default DataTable_CellActionMenu;
|
||||
|
||||
@@ -1,36 +1,36 @@
|
||||
"use client";
|
||||
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 { isAnyDirty } = useDataTable();
|
||||
const reset = () => {
|
||||
resetAction(user_id, page_name, table_name);
|
||||
};
|
||||
|
||||
return (
|
||||
<Tooltip title="بازنشانی اطلاعات">
|
||||
<IconButton onClick={reset} aria-label="reset table data">
|
||||
{isAnyDirty ? (
|
||||
<Badge
|
||||
anchorOrigin={{
|
||||
vertical: "top",
|
||||
horizontal: "left",
|
||||
}}
|
||||
color="primary"
|
||||
variant="dot"
|
||||
>
|
||||
<AutorenewIcon />
|
||||
</Badge>
|
||||
) : (
|
||||
<AutorenewIcon />
|
||||
)}
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
export default ResetStorage;
|
||||
"use client";
|
||||
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 { isAnyDirty } = useDataTable();
|
||||
const reset = () => {
|
||||
resetAction(user_id, page_name, table_name);
|
||||
};
|
||||
|
||||
return (
|
||||
<Tooltip title="بازنشانی اطلاعات">
|
||||
<IconButton onClick={reset} aria-label="reset table data">
|
||||
{isAnyDirty ? (
|
||||
<Badge
|
||||
anchorOrigin={{
|
||||
vertical: "top",
|
||||
horizontal: "left",
|
||||
}}
|
||||
color="primary"
|
||||
variant="dot"
|
||||
>
|
||||
<AutorenewIcon />
|
||||
</Badge>
|
||||
) : (
|
||||
<AutorenewIcon />
|
||||
)}
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
export default ResetStorage;
|
||||
|
||||
@@ -1,100 +1,100 @@
|
||||
import { parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||
import { Paper } from "@mui/material";
|
||||
import DataTable_BottomToolbar from "../toolbar/BottomToolbar";
|
||||
import DataTable_TopToolbar from "../toolbar/TopToolbar";
|
||||
import DataTable_TableContainer from "./TableContainer";
|
||||
|
||||
const DataTable_Paper = ({
|
||||
table,
|
||||
table_name,
|
||||
columns,
|
||||
user_id,
|
||||
page_name,
|
||||
mutate,
|
||||
need_filter,
|
||||
table_url,
|
||||
table_title,
|
||||
setFilterData,
|
||||
...rest
|
||||
}) => {
|
||||
const {
|
||||
getState,
|
||||
options: {
|
||||
enableBottomToolbar,
|
||||
enableTopToolbar,
|
||||
mrtTheme: { baseBackgroundColor },
|
||||
muiTablePaperProps,
|
||||
renderBottomToolbar,
|
||||
renderTopToolbar,
|
||||
},
|
||||
refs: { tablePaperRef },
|
||||
} = table;
|
||||
|
||||
const { isFullScreen } = getState();
|
||||
|
||||
const paperProps = {
|
||||
...parseFromValuesOrFunc(muiTablePaperProps, { table }),
|
||||
...rest,
|
||||
};
|
||||
|
||||
return (
|
||||
<Paper
|
||||
elevation={0}
|
||||
{...paperProps}
|
||||
ref={(ref) => {
|
||||
tablePaperRef.current = ref;
|
||||
if (paperProps?.ref) {
|
||||
//@ts-ignore
|
||||
paperProps.ref.current = ref;
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
...(isFullScreen
|
||||
? {
|
||||
bottom: 0,
|
||||
height: "100dvh",
|
||||
left: 0,
|
||||
margin: 0,
|
||||
maxHeight: "100dvh",
|
||||
maxWidth: "100dvw",
|
||||
padding: 0,
|
||||
position: "fixed",
|
||||
right: 0,
|
||||
top: 0,
|
||||
width: "100dvw",
|
||||
zIndex: 999,
|
||||
}
|
||||
: {}),
|
||||
...paperProps?.style,
|
||||
}}
|
||||
sx={(theme) => ({
|
||||
backgroundColor: baseBackgroundColor,
|
||||
backgroundImage: "unset",
|
||||
overflow: "hidden",
|
||||
transition: "all 100ms ease-in-out",
|
||||
...parseFromValuesOrFunc(paperProps?.sx, theme),
|
||||
})}
|
||||
>
|
||||
{enableTopToolbar &&
|
||||
(parseFromValuesOrFunc(renderTopToolbar, { table }) ?? (
|
||||
<DataTable_TopToolbar
|
||||
need_filter={need_filter}
|
||||
table={table}
|
||||
mutate={mutate}
|
||||
columns={columns}
|
||||
table_url={table_url}
|
||||
user_id={user_id}
|
||||
page_name={page_name}
|
||||
table_name={table_name}
|
||||
table_title={table_title}
|
||||
setFilterData={setFilterData}
|
||||
{...paperProps}
|
||||
/>
|
||||
))}
|
||||
<DataTable_TableContainer table={table} />
|
||||
{enableBottomToolbar &&
|
||||
(parseFromValuesOrFunc(renderBottomToolbar, { table }) ?? <DataTable_BottomToolbar table={table} />)}
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
export default DataTable_Paper;
|
||||
import { parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||
import { Paper } from "@mui/material";
|
||||
import DataTable_BottomToolbar from "../toolbar/BottomToolbar";
|
||||
import DataTable_TopToolbar from "../toolbar/TopToolbar";
|
||||
import DataTable_TableContainer from "./TableContainer";
|
||||
|
||||
const DataTable_Paper = ({
|
||||
table,
|
||||
table_name,
|
||||
columns,
|
||||
user_id,
|
||||
page_name,
|
||||
mutate,
|
||||
need_filter,
|
||||
table_url,
|
||||
table_title,
|
||||
setFilterData,
|
||||
...rest
|
||||
}) => {
|
||||
const {
|
||||
getState,
|
||||
options: {
|
||||
enableBottomToolbar,
|
||||
enableTopToolbar,
|
||||
mrtTheme: { baseBackgroundColor },
|
||||
muiTablePaperProps,
|
||||
renderBottomToolbar,
|
||||
renderTopToolbar,
|
||||
},
|
||||
refs: { tablePaperRef },
|
||||
} = table;
|
||||
|
||||
const { isFullScreen } = getState();
|
||||
|
||||
const paperProps = {
|
||||
...parseFromValuesOrFunc(muiTablePaperProps, { table }),
|
||||
...rest,
|
||||
};
|
||||
|
||||
return (
|
||||
<Paper
|
||||
elevation={0}
|
||||
{...paperProps}
|
||||
ref={(ref) => {
|
||||
tablePaperRef.current = ref;
|
||||
if (paperProps?.ref) {
|
||||
//@ts-ignore
|
||||
paperProps.ref.current = ref;
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
...(isFullScreen
|
||||
? {
|
||||
bottom: 0,
|
||||
height: "100dvh",
|
||||
left: 0,
|
||||
margin: 0,
|
||||
maxHeight: "100dvh",
|
||||
maxWidth: "100dvw",
|
||||
padding: 0,
|
||||
position: "fixed",
|
||||
right: 0,
|
||||
top: 0,
|
||||
width: "100dvw",
|
||||
zIndex: 999,
|
||||
}
|
||||
: {}),
|
||||
...paperProps?.style,
|
||||
}}
|
||||
sx={(theme) => ({
|
||||
backgroundColor: baseBackgroundColor,
|
||||
backgroundImage: "unset",
|
||||
overflow: "hidden",
|
||||
transition: "all 100ms ease-in-out",
|
||||
...parseFromValuesOrFunc(paperProps?.sx, theme),
|
||||
})}
|
||||
>
|
||||
{enableTopToolbar &&
|
||||
(parseFromValuesOrFunc(renderTopToolbar, { table }) ?? (
|
||||
<DataTable_TopToolbar
|
||||
need_filter={need_filter}
|
||||
table={table}
|
||||
mutate={mutate}
|
||||
columns={columns}
|
||||
table_url={table_url}
|
||||
user_id={user_id}
|
||||
page_name={page_name}
|
||||
table_name={table_name}
|
||||
table_title={table_title}
|
||||
setFilterData={setFilterData}
|
||||
{...paperProps}
|
||||
/>
|
||||
))}
|
||||
<DataTable_TableContainer table={table} />
|
||||
{enableBottomToolbar &&
|
||||
(parseFromValuesOrFunc(renderBottomToolbar, { table }) ?? <DataTable_BottomToolbar table={table} />)}
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
export default DataTable_Paper;
|
||||
|
||||
@@ -1,73 +1,73 @@
|
||||
import { parseCSSVarId, parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||
import { Table } from "@mui/material";
|
||||
import { useMRT_ColumnVirtualizer } from "material-react-table";
|
||||
import { useMemo } from "react";
|
||||
import DataTable_TableBody, { Memo_DataTable_TableBody } from "../body/TableBody";
|
||||
import DataTable_TableHead from "../head/TableHead";
|
||||
|
||||
const DataTable_Table = ({ table, ...rest }) => {
|
||||
const {
|
||||
getFlatHeaders,
|
||||
getState,
|
||||
options: {
|
||||
columns,
|
||||
enableStickyHeader,
|
||||
enableTableFooter,
|
||||
enableTableHead,
|
||||
layoutMode,
|
||||
memoMode,
|
||||
muiTableProps,
|
||||
renderCaption,
|
||||
},
|
||||
} = table;
|
||||
const { columnSizing, columnSizingInfo, columnVisibility, isFullScreen } = getState();
|
||||
|
||||
const tableProps = {
|
||||
...parseFromValuesOrFunc(muiTableProps, { table }),
|
||||
...rest,
|
||||
};
|
||||
|
||||
const Caption = parseFromValuesOrFunc(renderCaption, { table });
|
||||
|
||||
const columnSizeVars = useMemo(() => {
|
||||
const headers = getFlatHeaders();
|
||||
const colSizes = {};
|
||||
for (let i = 0; i < headers.length; i++) {
|
||||
const header = headers[i];
|
||||
const colSize = header.getSize();
|
||||
colSizes[`--header-${parseCSSVarId(header.id)}-size`] = colSize;
|
||||
colSizes[`--col-${parseCSSVarId(header.column.id)}-size`] = colSize;
|
||||
}
|
||||
return colSizes;
|
||||
}, [columns, columnSizing, columnSizingInfo, columnVisibility]);
|
||||
|
||||
const columnVirtualizer = useMRT_ColumnVirtualizer(table);
|
||||
|
||||
const commonTableGroupProps = {
|
||||
columnVirtualizer,
|
||||
table,
|
||||
};
|
||||
|
||||
return (
|
||||
<Table
|
||||
stickyHeader={enableStickyHeader || isFullScreen}
|
||||
{...tableProps}
|
||||
style={{ ...columnSizeVars, ...tableProps?.style }}
|
||||
sx={(theme) => ({
|
||||
display: layoutMode?.startsWith("grid") ? "grid" : undefined,
|
||||
position: "relative",
|
||||
...parseFromValuesOrFunc(tableProps?.sx, theme),
|
||||
})}
|
||||
>
|
||||
{!!Caption && <caption>{Caption}</caption>}
|
||||
{enableTableHead && <DataTable_TableHead {...commonTableGroupProps} />}
|
||||
{memoMode === "table-body" || columnSizingInfo.isResizingColumn ? (
|
||||
<Memo_DataTable_TableBody {...commonTableGroupProps} />
|
||||
) : (
|
||||
<DataTable_TableBody {...commonTableGroupProps} />
|
||||
)}
|
||||
{/* {enableTableFooter && <MRT_TableFooter {...commonTableGroupProps} />} */}
|
||||
</Table>
|
||||
);
|
||||
};
|
||||
export default DataTable_Table;
|
||||
import { parseCSSVarId, parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||
import { Table } from "@mui/material";
|
||||
import { useMRT_ColumnVirtualizer } from "material-react-table";
|
||||
import { useMemo } from "react";
|
||||
import DataTable_TableBody, { Memo_DataTable_TableBody } from "../body/TableBody";
|
||||
import DataTable_TableHead from "../head/TableHead";
|
||||
|
||||
const DataTable_Table = ({ table, ...rest }) => {
|
||||
const {
|
||||
getFlatHeaders,
|
||||
getState,
|
||||
options: {
|
||||
columns,
|
||||
enableStickyHeader,
|
||||
enableTableFooter,
|
||||
enableTableHead,
|
||||
layoutMode,
|
||||
memoMode,
|
||||
muiTableProps,
|
||||
renderCaption,
|
||||
},
|
||||
} = table;
|
||||
const { columnSizing, columnSizingInfo, columnVisibility, isFullScreen } = getState();
|
||||
|
||||
const tableProps = {
|
||||
...parseFromValuesOrFunc(muiTableProps, { table }),
|
||||
...rest,
|
||||
};
|
||||
|
||||
const Caption = parseFromValuesOrFunc(renderCaption, { table });
|
||||
|
||||
const columnSizeVars = useMemo(() => {
|
||||
const headers = getFlatHeaders();
|
||||
const colSizes = {};
|
||||
for (let i = 0; i < headers.length; i++) {
|
||||
const header = headers[i];
|
||||
const colSize = header.getSize();
|
||||
colSizes[`--header-${parseCSSVarId(header.id)}-size`] = colSize;
|
||||
colSizes[`--col-${parseCSSVarId(header.column.id)}-size`] = colSize;
|
||||
}
|
||||
return colSizes;
|
||||
}, [columns, columnSizing, columnSizingInfo, columnVisibility]);
|
||||
|
||||
const columnVirtualizer = useMRT_ColumnVirtualizer(table);
|
||||
|
||||
const commonTableGroupProps = {
|
||||
columnVirtualizer,
|
||||
table,
|
||||
};
|
||||
|
||||
return (
|
||||
<Table
|
||||
stickyHeader={enableStickyHeader || isFullScreen}
|
||||
{...tableProps}
|
||||
style={{ ...columnSizeVars, ...tableProps?.style }}
|
||||
sx={(theme) => ({
|
||||
display: layoutMode?.startsWith("grid") ? "grid" : undefined,
|
||||
position: "relative",
|
||||
...parseFromValuesOrFunc(tableProps?.sx, theme),
|
||||
})}
|
||||
>
|
||||
{!!Caption && <caption>{Caption}</caption>}
|
||||
{enableTableHead && <DataTable_TableHead {...commonTableGroupProps} />}
|
||||
{memoMode === "table-body" || columnSizingInfo.isResizingColumn ? (
|
||||
<Memo_DataTable_TableBody {...commonTableGroupProps} />
|
||||
) : (
|
||||
<DataTable_TableBody {...commonTableGroupProps} />
|
||||
)}
|
||||
{/* {enableTableFooter && <MRT_TableFooter {...commonTableGroupProps} />} */}
|
||||
</Table>
|
||||
);
|
||||
};
|
||||
export default DataTable_Table;
|
||||
|
||||
@@ -1,72 +1,72 @@
|
||||
import { parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||
import { TableContainer } from "@mui/material";
|
||||
import { useEffect, useLayoutEffect, useState } from "react";
|
||||
import DataTable_CellActionMenu from "../menus/CellActionMenu";
|
||||
import DataTable_Table from "./Table";
|
||||
import DataTable_TableLoadingOverlay from "./TableLoadingOverlay";
|
||||
|
||||
const useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
|
||||
|
||||
const DataTable_TableContainer = ({ table, ...rest }) => {
|
||||
const {
|
||||
getState,
|
||||
options: { enableCellActions, enableStickyHeader, muiTableContainerProps },
|
||||
refs: { bottomToolbarRef, tableContainerRef, topToolbarRef },
|
||||
} = table;
|
||||
const { actionCell, isFullScreen, isLoading, showLoadingOverlay } = getState();
|
||||
|
||||
const loading = showLoadingOverlay !== false && (isLoading || showLoadingOverlay);
|
||||
|
||||
const [totalToolbarHeight, setTotalToolbarHeight] = useState(0);
|
||||
|
||||
const tableContainerProps = {
|
||||
...parseFromValuesOrFunc(muiTableContainerProps, {
|
||||
table,
|
||||
}),
|
||||
...rest,
|
||||
};
|
||||
|
||||
useIsomorphicLayoutEffect(() => {
|
||||
const topToolbarHeight = typeof document !== "undefined" ? (topToolbarRef.current?.offsetHeight ?? 0) : 0;
|
||||
|
||||
const bottomToolbarHeight =
|
||||
typeof document !== "undefined" ? (bottomToolbarRef?.current?.offsetHeight ?? 0) : 0;
|
||||
|
||||
setTotalToolbarHeight(topToolbarHeight + bottomToolbarHeight);
|
||||
});
|
||||
|
||||
return (
|
||||
<TableContainer
|
||||
aria-busy={loading}
|
||||
aria-describedby={loading ? "mrt-progress" : undefined}
|
||||
{...tableContainerProps}
|
||||
ref={(node) => {
|
||||
if (node) {
|
||||
tableContainerRef.current = node;
|
||||
if (tableContainerProps?.ref) {
|
||||
//@ts-ignore
|
||||
tableContainerProps.ref.current = node;
|
||||
}
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
maxHeight: isFullScreen ? `calc(100vh - ${totalToolbarHeight}px)` : undefined,
|
||||
...tableContainerProps?.style,
|
||||
}}
|
||||
sx={(theme) => ({
|
||||
maxHeight: enableStickyHeader
|
||||
? `clamp(350px, calc(100vh - ${totalToolbarHeight}px), 9999px)`
|
||||
: undefined,
|
||||
maxWidth: "100%",
|
||||
overflow: "auto",
|
||||
position: "relative",
|
||||
...parseFromValuesOrFunc(tableContainerProps?.sx, theme),
|
||||
})}
|
||||
>
|
||||
{loading ? <DataTable_TableLoadingOverlay table={table} /> : null}
|
||||
<DataTable_Table table={table} />
|
||||
{enableCellActions && actionCell && <DataTable_CellActionMenu table={table} />}
|
||||
</TableContainer>
|
||||
);
|
||||
};
|
||||
export default DataTable_TableContainer;
|
||||
import { parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||
import { TableContainer } from "@mui/material";
|
||||
import { useEffect, useLayoutEffect, useState } from "react";
|
||||
import DataTable_CellActionMenu from "../menus/CellActionMenu";
|
||||
import DataTable_Table from "./Table";
|
||||
import DataTable_TableLoadingOverlay from "./TableLoadingOverlay";
|
||||
|
||||
const useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
|
||||
|
||||
const DataTable_TableContainer = ({ table, ...rest }) => {
|
||||
const {
|
||||
getState,
|
||||
options: { enableCellActions, enableStickyHeader, muiTableContainerProps },
|
||||
refs: { bottomToolbarRef, tableContainerRef, topToolbarRef },
|
||||
} = table;
|
||||
const { actionCell, isFullScreen, isLoading, showLoadingOverlay } = getState();
|
||||
|
||||
const loading = showLoadingOverlay !== false && (isLoading || showLoadingOverlay);
|
||||
|
||||
const [totalToolbarHeight, setTotalToolbarHeight] = useState(0);
|
||||
|
||||
const tableContainerProps = {
|
||||
...parseFromValuesOrFunc(muiTableContainerProps, {
|
||||
table,
|
||||
}),
|
||||
...rest,
|
||||
};
|
||||
|
||||
useIsomorphicLayoutEffect(() => {
|
||||
const topToolbarHeight = typeof document !== "undefined" ? (topToolbarRef.current?.offsetHeight ?? 0) : 0;
|
||||
|
||||
const bottomToolbarHeight =
|
||||
typeof document !== "undefined" ? (bottomToolbarRef?.current?.offsetHeight ?? 0) : 0;
|
||||
|
||||
setTotalToolbarHeight(topToolbarHeight + bottomToolbarHeight);
|
||||
});
|
||||
|
||||
return (
|
||||
<TableContainer
|
||||
aria-busy={loading}
|
||||
aria-describedby={loading ? "mrt-progress" : undefined}
|
||||
{...tableContainerProps}
|
||||
ref={(node) => {
|
||||
if (node) {
|
||||
tableContainerRef.current = node;
|
||||
if (tableContainerProps?.ref) {
|
||||
//@ts-ignore
|
||||
tableContainerProps.ref.current = node;
|
||||
}
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
maxHeight: isFullScreen ? `calc(100vh - ${totalToolbarHeight}px)` : undefined,
|
||||
...tableContainerProps?.style,
|
||||
}}
|
||||
sx={(theme) => ({
|
||||
maxHeight: enableStickyHeader
|
||||
? `clamp(350px, calc(100vh - ${totalToolbarHeight}px), 9999px)`
|
||||
: undefined,
|
||||
maxWidth: "100%",
|
||||
overflow: "auto",
|
||||
position: "relative",
|
||||
...parseFromValuesOrFunc(tableContainerProps?.sx, theme),
|
||||
})}
|
||||
>
|
||||
{loading ? <DataTable_TableLoadingOverlay table={table} /> : null}
|
||||
<DataTable_Table table={table} />
|
||||
{enableCellActions && actionCell && <DataTable_CellActionMenu table={table} />}
|
||||
</TableContainer>
|
||||
);
|
||||
};
|
||||
export default DataTable_TableContainer;
|
||||
|
||||
@@ -1,44 +1,44 @@
|
||||
import { Box, CircularProgress } from "@mui/material";
|
||||
|
||||
const DataTable_TableLoadingOverlay = ({ table, ...rest }) => {
|
||||
const {
|
||||
options: {
|
||||
localization,
|
||||
mrtTheme: { baseBackgroundColor },
|
||||
muiCircularProgressProps,
|
||||
},
|
||||
} = table;
|
||||
|
||||
const circularProgressProps = {
|
||||
...parseFromValuesOrFunc(muiCircularProgressProps, { table }),
|
||||
...rest,
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
alignItems: "center",
|
||||
backgroundColor: alpha(baseBackgroundColor, 0.5),
|
||||
bottom: 0,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
left: 0,
|
||||
maxHeight: "100vh",
|
||||
position: "absolute",
|
||||
right: 0,
|
||||
top: 0,
|
||||
width: "100%",
|
||||
zIndex: 3,
|
||||
}}
|
||||
>
|
||||
{circularProgressProps?.Component ?? (
|
||||
<CircularProgress
|
||||
aria-label={localization.noRecordsToDisplay}
|
||||
id="mrt-progress"
|
||||
{...circularProgressProps}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
export default DataTable_TableLoadingOverlay;
|
||||
import { Box, CircularProgress } from "@mui/material";
|
||||
|
||||
const DataTable_TableLoadingOverlay = ({ table, ...rest }) => {
|
||||
const {
|
||||
options: {
|
||||
localization,
|
||||
mrtTheme: { baseBackgroundColor },
|
||||
muiCircularProgressProps,
|
||||
},
|
||||
} = table;
|
||||
|
||||
const circularProgressProps = {
|
||||
...parseFromValuesOrFunc(muiCircularProgressProps, { table }),
|
||||
...rest,
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
alignItems: "center",
|
||||
backgroundColor: alpha(baseBackgroundColor, 0.5),
|
||||
bottom: 0,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
left: 0,
|
||||
maxHeight: "100vh",
|
||||
position: "absolute",
|
||||
right: 0,
|
||||
top: 0,
|
||||
width: "100%",
|
||||
zIndex: 3,
|
||||
}}
|
||||
>
|
||||
{circularProgressProps?.Component ?? (
|
||||
<CircularProgress
|
||||
aria-label={localization.noRecordsToDisplay}
|
||||
id="mrt-progress"
|
||||
{...circularProgressProps}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
export default DataTable_TableLoadingOverlay;
|
||||
|
||||
@@ -1,72 +1,72 @@
|
||||
import { getCommonToolbarStyles, parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||
import { Box, alpha, useMediaQuery } from "@mui/material";
|
||||
import DataTable_LinearProgressBar from "./LinearProgressBar";
|
||||
import DataTable_TablePagination from "./TablePagination";
|
||||
|
||||
const DataTable_BottomToolbar = ({ table, ...rest }) => {
|
||||
const {
|
||||
getState,
|
||||
options: { enablePagination, muiBottomToolbarProps, positionPagination, renderBottomToolbarCustomActions },
|
||||
refs: { bottomToolbarRef },
|
||||
} = table;
|
||||
const { isFullScreen } = getState();
|
||||
|
||||
const isMobile = useMediaQuery("(max-width:720px)");
|
||||
const toolbarProps = {
|
||||
...parseFromValuesOrFunc(muiBottomToolbarProps, { table }),
|
||||
...rest,
|
||||
};
|
||||
|
||||
const stackAlertBanner = isMobile || !!renderBottomToolbarCustomActions;
|
||||
return (
|
||||
<Box
|
||||
{...toolbarProps}
|
||||
ref={(node) => {
|
||||
if (node) {
|
||||
bottomToolbarRef.current = node;
|
||||
if (toolbarProps?.ref) {
|
||||
// @ts-ignore
|
||||
toolbarProps.ref.current = node;
|
||||
}
|
||||
}
|
||||
}}
|
||||
sx={(theme) => ({
|
||||
...getCommonToolbarStyles({ table, theme }),
|
||||
bottom: isFullScreen ? "0" : undefined,
|
||||
boxShadow: `0 1px 2px -1px ${alpha(theme.palette.grey[700], 0.5)} inset`,
|
||||
left: 0,
|
||||
position: isFullScreen ? "fixed" : "relative",
|
||||
right: 0,
|
||||
...parseFromValuesOrFunc(toolbarProps?.sx, theme),
|
||||
})}
|
||||
>
|
||||
<DataTable_LinearProgressBar isTopToolbar={false} table={table} />
|
||||
<Box
|
||||
sx={{
|
||||
alignItems: "center",
|
||||
boxSizing: "border-box",
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
p: "0.5rem",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
{renderBottomToolbarCustomActions ? renderBottomToolbarCustomActions({ table }) : <span />}
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "flex-end",
|
||||
position: stackAlertBanner ? "relative" : "absolute",
|
||||
right: 0,
|
||||
top: 0,
|
||||
}}
|
||||
>
|
||||
{enablePagination && ["both", "bottom"].includes(positionPagination ?? "") && (
|
||||
<DataTable_TablePagination position="bottom" table={table} />
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
export default DataTable_BottomToolbar;
|
||||
import { getCommonToolbarStyles, parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||
import { Box, alpha, useMediaQuery } from "@mui/material";
|
||||
import DataTable_LinearProgressBar from "./LinearProgressBar";
|
||||
import DataTable_TablePagination from "./TablePagination";
|
||||
|
||||
const DataTable_BottomToolbar = ({ table, ...rest }) => {
|
||||
const {
|
||||
getState,
|
||||
options: { enablePagination, muiBottomToolbarProps, positionPagination, renderBottomToolbarCustomActions },
|
||||
refs: { bottomToolbarRef },
|
||||
} = table;
|
||||
const { isFullScreen } = getState();
|
||||
|
||||
const isMobile = useMediaQuery("(max-width:720px)");
|
||||
const toolbarProps = {
|
||||
...parseFromValuesOrFunc(muiBottomToolbarProps, { table }),
|
||||
...rest,
|
||||
};
|
||||
|
||||
const stackAlertBanner = isMobile || !!renderBottomToolbarCustomActions;
|
||||
return (
|
||||
<Box
|
||||
{...toolbarProps}
|
||||
ref={(node) => {
|
||||
if (node) {
|
||||
bottomToolbarRef.current = node;
|
||||
if (toolbarProps?.ref) {
|
||||
// @ts-ignore
|
||||
toolbarProps.ref.current = node;
|
||||
}
|
||||
}
|
||||
}}
|
||||
sx={(theme) => ({
|
||||
...getCommonToolbarStyles({ table, theme }),
|
||||
bottom: isFullScreen ? "0" : undefined,
|
||||
boxShadow: `0 1px 2px -1px ${alpha(theme.palette.grey[700], 0.5)} inset`,
|
||||
left: 0,
|
||||
position: isFullScreen ? "fixed" : "relative",
|
||||
right: 0,
|
||||
...parseFromValuesOrFunc(toolbarProps?.sx, theme),
|
||||
})}
|
||||
>
|
||||
<DataTable_LinearProgressBar isTopToolbar={false} table={table} />
|
||||
<Box
|
||||
sx={{
|
||||
alignItems: "center",
|
||||
boxSizing: "border-box",
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
p: "0.5rem",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
{renderBottomToolbarCustomActions ? renderBottomToolbarCustomActions({ table }) : <span />}
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "flex-end",
|
||||
position: stackAlertBanner ? "relative" : "absolute",
|
||||
right: 0,
|
||||
top: 0,
|
||||
}}
|
||||
>
|
||||
{enablePagination && ["both", "bottom"].includes(positionPagination ?? "") && (
|
||||
<DataTable_TablePagination position="bottom" table={table} />
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
export default DataTable_BottomToolbar;
|
||||
|
||||
@@ -1,40 +1,40 @@
|
||||
import { parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||
import { Collapse, LinearProgress } from "@mui/material";
|
||||
|
||||
const DataTable_LinearProgressBar = ({ isTopToolbar, table, ...rest }) => {
|
||||
const {
|
||||
getState,
|
||||
options: { muiLinearProgressProps },
|
||||
} = table;
|
||||
const { isSaving, showProgressBars } = getState();
|
||||
|
||||
const linearProgressProps = {
|
||||
...parseFromValuesOrFunc(muiLinearProgressProps, {
|
||||
isTopToolbar,
|
||||
table,
|
||||
}),
|
||||
...rest,
|
||||
};
|
||||
|
||||
return (
|
||||
<Collapse
|
||||
in={showProgressBars !== false && (showProgressBars || isSaving)}
|
||||
mountOnEnter
|
||||
sx={{
|
||||
bottom: isTopToolbar ? 0 : undefined,
|
||||
position: "absolute",
|
||||
top: !isTopToolbar ? 0 : undefined,
|
||||
width: "100%",
|
||||
}}
|
||||
unmountOnExit
|
||||
>
|
||||
<LinearProgress
|
||||
aria-busy="true"
|
||||
aria-label="Loading"
|
||||
sx={{ position: "relative" }}
|
||||
{...linearProgressProps}
|
||||
/>
|
||||
</Collapse>
|
||||
);
|
||||
};
|
||||
export default DataTable_LinearProgressBar;
|
||||
import { parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||
import { Collapse, LinearProgress } from "@mui/material";
|
||||
|
||||
const DataTable_LinearProgressBar = ({ isTopToolbar, table, ...rest }) => {
|
||||
const {
|
||||
getState,
|
||||
options: { muiLinearProgressProps },
|
||||
} = table;
|
||||
const { isSaving, showProgressBars } = getState();
|
||||
|
||||
const linearProgressProps = {
|
||||
...parseFromValuesOrFunc(muiLinearProgressProps, {
|
||||
isTopToolbar,
|
||||
table,
|
||||
}),
|
||||
...rest,
|
||||
};
|
||||
|
||||
return (
|
||||
<Collapse
|
||||
in={showProgressBars !== false && (showProgressBars || isSaving)}
|
||||
mountOnEnter
|
||||
sx={{
|
||||
bottom: isTopToolbar ? 0 : undefined,
|
||||
position: "absolute",
|
||||
top: !isTopToolbar ? 0 : undefined,
|
||||
width: "100%",
|
||||
}}
|
||||
unmountOnExit
|
||||
>
|
||||
<LinearProgress
|
||||
aria-busy="true"
|
||||
aria-label="Loading"
|
||||
sx={{ position: "relative" }}
|
||||
{...linearProgressProps}
|
||||
/>
|
||||
</Collapse>
|
||||
);
|
||||
};
|
||||
export default DataTable_LinearProgressBar;
|
||||
|
||||
@@ -1,218 +1,218 @@
|
||||
import { flipIconStyles, getCommonTooltipProps, parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import {
|
||||
Box,
|
||||
IconButton,
|
||||
InputLabel,
|
||||
MenuItem,
|
||||
Pagination,
|
||||
PaginationItem,
|
||||
Select,
|
||||
Tooltip,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
} from "@mui/material";
|
||||
|
||||
const defaultRowsPerPage = [5, 10, 15, 20, 25, 30, 50, 100];
|
||||
|
||||
const DataTable_TablePagination = ({ position = "bottom", table, ...rest }) => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery("(max-width: 720px)");
|
||||
|
||||
const {
|
||||
getState,
|
||||
options: {
|
||||
enableToolbarInternalActions,
|
||||
icons: { ChevronLeftIcon, ChevronRightIcon, FirstPageIcon, LastPageIcon },
|
||||
localization,
|
||||
muiPaginationProps,
|
||||
paginationDisplayMode,
|
||||
},
|
||||
} = table;
|
||||
|
||||
const {
|
||||
pagination: { pageIndex = 0, pageSize = 10 },
|
||||
showGlobalFilter,
|
||||
} = getState();
|
||||
|
||||
const paginationProps = {
|
||||
...parseFromValuesOrFunc(muiPaginationProps, {
|
||||
table,
|
||||
}),
|
||||
...rest,
|
||||
};
|
||||
|
||||
const totalRowCount = table.getRowCount();
|
||||
const numberOfPages = table.getPageCount();
|
||||
const showFirstLastPageButtons = numberOfPages > 2;
|
||||
const firstRowIndex = pageIndex * pageSize;
|
||||
const lastRowIndex = Math.min(pageIndex * pageSize + pageSize, totalRowCount);
|
||||
|
||||
const {
|
||||
SelectProps = {},
|
||||
disabled = false,
|
||||
rowsPerPageOptions = defaultRowsPerPage,
|
||||
showFirstButton = showFirstLastPageButtons,
|
||||
showLastButton = showFirstLastPageButtons,
|
||||
showRowsPerPage = true,
|
||||
...restPaginationProps
|
||||
} = paginationProps ?? {};
|
||||
|
||||
const disableBack = pageIndex <= 0 || disabled;
|
||||
const disableNext = lastRowIndex >= totalRowCount || disabled;
|
||||
|
||||
if (isMobile && SelectProps?.native !== false) {
|
||||
SelectProps.native = true;
|
||||
}
|
||||
|
||||
const tooltipProps = getCommonTooltipProps();
|
||||
|
||||
return (
|
||||
<Box
|
||||
className="MuiTablePagination-root"
|
||||
sx={{
|
||||
alignItems: "center",
|
||||
display: "flex",
|
||||
flexWrap: "wrap",
|
||||
gap: "8px",
|
||||
justifyContent: { md: "space-between", sm: "center" },
|
||||
justifySelf: "flex-end",
|
||||
mt: position === "top" && enableToolbarInternalActions && !showGlobalFilter ? "3rem" : undefined,
|
||||
position: "relative",
|
||||
px: "8px",
|
||||
py: "12px",
|
||||
zIndex: 2,
|
||||
}}
|
||||
>
|
||||
{showRowsPerPage && (
|
||||
<Box sx={{ alignItems: "center", display: "flex", gap: "8px" }}>
|
||||
<InputLabel htmlFor="mrt-rows-per-page" sx={{ mb: 0 }}>
|
||||
{localization.rowsPerPage}
|
||||
</InputLabel>
|
||||
<Select
|
||||
MenuProps={{ disableScrollLock: true }}
|
||||
disableUnderline
|
||||
disabled={disabled}
|
||||
inputProps={{
|
||||
"aria-label": localization.rowsPerPage,
|
||||
id: "mrt-rows-per-page",
|
||||
}}
|
||||
label={localization.rowsPerPage}
|
||||
onChange={(event) => table.setPageSize(+event.target.value)}
|
||||
sx={{ mb: 0 }}
|
||||
value={pageSize}
|
||||
variant="standard"
|
||||
{...SelectProps}
|
||||
>
|
||||
{rowsPerPageOptions.map((option) => {
|
||||
const value = typeof option !== "number" ? option.value : option;
|
||||
const label = typeof option !== "number" ? option.label : `${option}`;
|
||||
return (
|
||||
SelectProps?.children ??
|
||||
(SelectProps?.native ? (
|
||||
<option key={value} value={value}>
|
||||
{label}
|
||||
</option>
|
||||
) : (
|
||||
<MenuItem key={value} sx={{ m: 0 }} value={value}>
|
||||
{label}
|
||||
</MenuItem>
|
||||
))
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
</Box>
|
||||
)}
|
||||
{paginationDisplayMode === "pages" ? (
|
||||
<Pagination
|
||||
count={numberOfPages}
|
||||
disabled={disabled}
|
||||
onChange={(_e, newPageIndex) => table.setPageIndex(newPageIndex - 1)}
|
||||
page={pageIndex + 1}
|
||||
renderItem={(item) => (
|
||||
<PaginationItem
|
||||
slots={{
|
||||
first: FirstPageIcon,
|
||||
last: LastPageIcon,
|
||||
next: ChevronRightIcon,
|
||||
previous: ChevronLeftIcon,
|
||||
}}
|
||||
{...item}
|
||||
/>
|
||||
)}
|
||||
showFirstButton={showFirstButton}
|
||||
showLastButton={showLastButton}
|
||||
{...restPaginationProps}
|
||||
/>
|
||||
) : paginationDisplayMode === "default" ? (
|
||||
<>
|
||||
<Typography
|
||||
align="center"
|
||||
component="span"
|
||||
sx={{ m: "0 4px", minWidth: "8ch" }}
|
||||
variant="body2"
|
||||
>{`${
|
||||
lastRowIndex === 0 ? 0 : (firstRowIndex + 1).toLocaleString()
|
||||
}-${lastRowIndex.toLocaleString()} ${
|
||||
localization.of
|
||||
} ${totalRowCount.toLocaleString()}`}</Typography>
|
||||
<Box gap="xs">
|
||||
{showFirstButton && (
|
||||
<Tooltip {...tooltipProps} title={localization.goToFirstPage}>
|
||||
<span>
|
||||
<IconButton
|
||||
aria-label={localization.goToFirstPage}
|
||||
disabled={disableBack}
|
||||
onClick={() => table.firstPage()}
|
||||
size="small"
|
||||
>
|
||||
<FirstPageIcon {...flipIconStyles(theme)} />
|
||||
</IconButton>
|
||||
</span>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Tooltip {...tooltipProps} title={localization.goToPreviousPage}>
|
||||
<span>
|
||||
<IconButton
|
||||
aria-label={localization.goToPreviousPage}
|
||||
disabled={disableBack}
|
||||
onClick={() => table.previousPage()}
|
||||
size="small"
|
||||
>
|
||||
<ChevronLeftIcon {...flipIconStyles(theme)} />
|
||||
</IconButton>
|
||||
</span>
|
||||
</Tooltip>
|
||||
<Tooltip {...tooltipProps} title={localization.goToNextPage}>
|
||||
<span>
|
||||
<IconButton
|
||||
aria-label={localization.goToNextPage}
|
||||
disabled={disableNext}
|
||||
onClick={() => table.nextPage()}
|
||||
size="small"
|
||||
>
|
||||
<ChevronRightIcon {...flipIconStyles(theme)} />
|
||||
</IconButton>
|
||||
</span>
|
||||
</Tooltip>
|
||||
{showLastButton && (
|
||||
<Tooltip {...tooltipProps} title={localization.goToLastPage}>
|
||||
<span>
|
||||
<IconButton
|
||||
aria-label={localization.goToLastPage}
|
||||
disabled={disableNext}
|
||||
onClick={() => table.lastPage()}
|
||||
size="small"
|
||||
>
|
||||
<LastPageIcon {...flipIconStyles(theme)} />
|
||||
</IconButton>
|
||||
</span>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Box>
|
||||
</>
|
||||
) : null}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
export default DataTable_TablePagination;
|
||||
import { flipIconStyles, getCommonTooltipProps, parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import {
|
||||
Box,
|
||||
IconButton,
|
||||
InputLabel,
|
||||
MenuItem,
|
||||
Pagination,
|
||||
PaginationItem,
|
||||
Select,
|
||||
Tooltip,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
} from "@mui/material";
|
||||
|
||||
const defaultRowsPerPage = [5, 10, 15, 20, 25, 30, 50, 100];
|
||||
|
||||
const DataTable_TablePagination = ({ position = "bottom", table, ...rest }) => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery("(max-width: 720px)");
|
||||
|
||||
const {
|
||||
getState,
|
||||
options: {
|
||||
enableToolbarInternalActions,
|
||||
icons: { ChevronLeftIcon, ChevronRightIcon, FirstPageIcon, LastPageIcon },
|
||||
localization,
|
||||
muiPaginationProps,
|
||||
paginationDisplayMode,
|
||||
},
|
||||
} = table;
|
||||
|
||||
const {
|
||||
pagination: { pageIndex = 0, pageSize = 10 },
|
||||
showGlobalFilter,
|
||||
} = getState();
|
||||
|
||||
const paginationProps = {
|
||||
...parseFromValuesOrFunc(muiPaginationProps, {
|
||||
table,
|
||||
}),
|
||||
...rest,
|
||||
};
|
||||
|
||||
const totalRowCount = table.getRowCount();
|
||||
const numberOfPages = table.getPageCount();
|
||||
const showFirstLastPageButtons = numberOfPages > 2;
|
||||
const firstRowIndex = pageIndex * pageSize;
|
||||
const lastRowIndex = Math.min(pageIndex * pageSize + pageSize, totalRowCount);
|
||||
|
||||
const {
|
||||
SelectProps = {},
|
||||
disabled = false,
|
||||
rowsPerPageOptions = defaultRowsPerPage,
|
||||
showFirstButton = showFirstLastPageButtons,
|
||||
showLastButton = showFirstLastPageButtons,
|
||||
showRowsPerPage = true,
|
||||
...restPaginationProps
|
||||
} = paginationProps ?? {};
|
||||
|
||||
const disableBack = pageIndex <= 0 || disabled;
|
||||
const disableNext = lastRowIndex >= totalRowCount || disabled;
|
||||
|
||||
if (isMobile && SelectProps?.native !== false) {
|
||||
SelectProps.native = true;
|
||||
}
|
||||
|
||||
const tooltipProps = getCommonTooltipProps();
|
||||
|
||||
return (
|
||||
<Box
|
||||
className="MuiTablePagination-root"
|
||||
sx={{
|
||||
alignItems: "center",
|
||||
display: "flex",
|
||||
flexWrap: "wrap",
|
||||
gap: "8px",
|
||||
justifyContent: { md: "space-between", sm: "center" },
|
||||
justifySelf: "flex-end",
|
||||
mt: position === "top" && enableToolbarInternalActions && !showGlobalFilter ? "3rem" : undefined,
|
||||
position: "relative",
|
||||
px: "8px",
|
||||
py: "12px",
|
||||
zIndex: 2,
|
||||
}}
|
||||
>
|
||||
{showRowsPerPage && (
|
||||
<Box sx={{ alignItems: "center", display: "flex", gap: "8px" }}>
|
||||
<InputLabel htmlFor="mrt-rows-per-page" sx={{ mb: 0 }}>
|
||||
{localization.rowsPerPage}
|
||||
</InputLabel>
|
||||
<Select
|
||||
MenuProps={{ disableScrollLock: true }}
|
||||
disableUnderline
|
||||
disabled={disabled}
|
||||
inputProps={{
|
||||
"aria-label": localization.rowsPerPage,
|
||||
id: "mrt-rows-per-page",
|
||||
}}
|
||||
label={localization.rowsPerPage}
|
||||
onChange={(event) => table.setPageSize(+event.target.value)}
|
||||
sx={{ mb: 0 }}
|
||||
value={pageSize}
|
||||
variant="standard"
|
||||
{...SelectProps}
|
||||
>
|
||||
{rowsPerPageOptions.map((option) => {
|
||||
const value = typeof option !== "number" ? option.value : option;
|
||||
const label = typeof option !== "number" ? option.label : `${option}`;
|
||||
return (
|
||||
SelectProps?.children ??
|
||||
(SelectProps?.native ? (
|
||||
<option key={value} value={value}>
|
||||
{label}
|
||||
</option>
|
||||
) : (
|
||||
<MenuItem key={value} sx={{ m: 0 }} value={value}>
|
||||
{label}
|
||||
</MenuItem>
|
||||
))
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
</Box>
|
||||
)}
|
||||
{paginationDisplayMode === "pages" ? (
|
||||
<Pagination
|
||||
count={numberOfPages}
|
||||
disabled={disabled}
|
||||
onChange={(_e, newPageIndex) => table.setPageIndex(newPageIndex - 1)}
|
||||
page={pageIndex + 1}
|
||||
renderItem={(item) => (
|
||||
<PaginationItem
|
||||
slots={{
|
||||
first: FirstPageIcon,
|
||||
last: LastPageIcon,
|
||||
next: ChevronRightIcon,
|
||||
previous: ChevronLeftIcon,
|
||||
}}
|
||||
{...item}
|
||||
/>
|
||||
)}
|
||||
showFirstButton={showFirstButton}
|
||||
showLastButton={showLastButton}
|
||||
{...restPaginationProps}
|
||||
/>
|
||||
) : paginationDisplayMode === "default" ? (
|
||||
<>
|
||||
<Typography
|
||||
align="center"
|
||||
component="span"
|
||||
sx={{ m: "0 4px", minWidth: "8ch" }}
|
||||
variant="body2"
|
||||
>{`${
|
||||
lastRowIndex === 0 ? 0 : (firstRowIndex + 1).toLocaleString()
|
||||
}-${lastRowIndex.toLocaleString()} ${
|
||||
localization.of
|
||||
} ${totalRowCount.toLocaleString()}`}</Typography>
|
||||
<Box gap="xs">
|
||||
{showFirstButton && (
|
||||
<Tooltip {...tooltipProps} title={localization.goToFirstPage}>
|
||||
<span>
|
||||
<IconButton
|
||||
aria-label={localization.goToFirstPage}
|
||||
disabled={disableBack}
|
||||
onClick={() => table.firstPage()}
|
||||
size="small"
|
||||
>
|
||||
<FirstPageIcon {...flipIconStyles(theme)} />
|
||||
</IconButton>
|
||||
</span>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Tooltip {...tooltipProps} title={localization.goToPreviousPage}>
|
||||
<span>
|
||||
<IconButton
|
||||
aria-label={localization.goToPreviousPage}
|
||||
disabled={disableBack}
|
||||
onClick={() => table.previousPage()}
|
||||
size="small"
|
||||
>
|
||||
<ChevronLeftIcon {...flipIconStyles(theme)} />
|
||||
</IconButton>
|
||||
</span>
|
||||
</Tooltip>
|
||||
<Tooltip {...tooltipProps} title={localization.goToNextPage}>
|
||||
<span>
|
||||
<IconButton
|
||||
aria-label={localization.goToNextPage}
|
||||
disabled={disableNext}
|
||||
onClick={() => table.nextPage()}
|
||||
size="small"
|
||||
>
|
||||
<ChevronRightIcon {...flipIconStyles(theme)} />
|
||||
</IconButton>
|
||||
</span>
|
||||
</Tooltip>
|
||||
{showLastButton && (
|
||||
<Tooltip {...tooltipProps} title={localization.goToLastPage}>
|
||||
<span>
|
||||
<IconButton
|
||||
aria-label={localization.goToLastPage}
|
||||
disabled={disableNext}
|
||||
onClick={() => table.lastPage()}
|
||||
size="small"
|
||||
>
|
||||
<LastPageIcon {...flipIconStyles(theme)} />
|
||||
</IconButton>
|
||||
</span>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Box>
|
||||
</>
|
||||
) : null}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
export default DataTable_TablePagination;
|
||||
|
||||
@@ -1,138 +1,138 @@
|
||||
import { getCommonToolbarStyles, parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||
import { Box, Typography, useMediaQuery } from "@mui/material";
|
||||
import DataTable_LinearProgressBar from "./LinearProgressBar";
|
||||
import DataTable_TablePagination from "./TablePagination";
|
||||
import FilterColumn from "@/core/components/DataTable/filter";
|
||||
import UpdateTable from "@/core/components/DataTable/update/UpdateTable";
|
||||
import ResetStorage from "@/core/components/DataTable/reset/ResetStorage";
|
||||
import HideColumn from "@/core/components/DataTable/hide";
|
||||
import FilterCustom from "../filter/FilterCustom";
|
||||
|
||||
const DataTable_TopToolbar = ({
|
||||
mutate,
|
||||
need_filter,
|
||||
table,
|
||||
columns,
|
||||
table_url,
|
||||
user_id,
|
||||
page_name,
|
||||
table_name,
|
||||
table_title,
|
||||
special_data,
|
||||
special_filter,
|
||||
setFilterData,
|
||||
}) => {
|
||||
const {
|
||||
getState,
|
||||
options: {
|
||||
enablePagination,
|
||||
enableToolbarInternalActions,
|
||||
muiTopToolbarProps,
|
||||
positionPagination,
|
||||
renderTopToolbarCustomActions,
|
||||
},
|
||||
refs: { topToolbarRef },
|
||||
} = table;
|
||||
const { isFullScreen, showGlobalFilter } = getState();
|
||||
|
||||
const isMobile = useMediaQuery("(max-width:720px)");
|
||||
const isTablet = useMediaQuery("(max-width:1024px)");
|
||||
|
||||
const toolbarProps = parseFromValuesOrFunc(muiTopToolbarProps, { table });
|
||||
|
||||
const stackAlertBanner = isMobile || !!renderTopToolbarCustomActions || (showGlobalFilter && isTablet);
|
||||
|
||||
return (
|
||||
<Box
|
||||
{...toolbarProps}
|
||||
ref={(ref) => {
|
||||
topToolbarRef.current = ref;
|
||||
if (toolbarProps?.ref) {
|
||||
// @ts-ignore
|
||||
toolbarProps.ref.current = ref;
|
||||
}
|
||||
}}
|
||||
sx={(theme) => ({
|
||||
...getCommonToolbarStyles({ table, theme }),
|
||||
position: isFullScreen ? "sticky" : "relative",
|
||||
top: isFullScreen ? "0" : undefined,
|
||||
...parseFromValuesOrFunc(toolbarProps?.sx, theme),
|
||||
})}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
flexWrap: { xs: "wrap", sm: "no-wrap" },
|
||||
boxSizing: "border-box",
|
||||
display: "flex",
|
||||
gap: "0.5rem",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
p: "0.5rem",
|
||||
position: stackAlertBanner ? "relative" : "absolute",
|
||||
right: 0,
|
||||
top: 0,
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
order: { xs: 2, sm: 1 },
|
||||
}}
|
||||
>
|
||||
{renderTopToolbarCustomActions?.({ table })}
|
||||
</Box>
|
||||
<Typography
|
||||
sx={{ textAlign: "center", order: { xs: 1, sm: 2 }, width: { xs: "100%", sm: "unset" } }}
|
||||
fontWeight={600}
|
||||
>
|
||||
{table_title || ""}
|
||||
</Typography>
|
||||
{enableToolbarInternalActions && (
|
||||
<Box
|
||||
sx={{
|
||||
gap: "0.5rem",
|
||||
flexWrap: "wrap-reverse",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-end",
|
||||
order: 3,
|
||||
}}
|
||||
>
|
||||
{!special_data && (
|
||||
<>
|
||||
<ResetStorage user_id={user_id} page_name={page_name} table_name={table_name} />
|
||||
<UpdateTable mutate={mutate} />
|
||||
</>
|
||||
)}
|
||||
<HideColumn
|
||||
columns={table.options.columns}
|
||||
user_id={user_id}
|
||||
page_name={page_name}
|
||||
table_name={table_name}
|
||||
/>
|
||||
{need_filter && (
|
||||
<FilterColumn
|
||||
columns={columns}
|
||||
table_url={table_url}
|
||||
user_id={user_id}
|
||||
page_name={page_name}
|
||||
table_name={table_name}
|
||||
/>
|
||||
)}
|
||||
{special_filter && setFilterData && (
|
||||
<FilterCustom filterData={special_filter} setFilterData={setFilterData} />
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
{enablePagination && ["both", "top"].includes(positionPagination ?? "") && (
|
||||
<DataTable_TablePagination position="top" table={table} />
|
||||
)}
|
||||
<DataTable_LinearProgressBar isTopToolbar table={table} />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default DataTable_TopToolbar;
|
||||
import { getCommonToolbarStyles, parseFromValuesOrFunc } from "@/core/utils/utils";
|
||||
import { Box, Typography, useMediaQuery } from "@mui/material";
|
||||
import DataTable_LinearProgressBar from "./LinearProgressBar";
|
||||
import DataTable_TablePagination from "./TablePagination";
|
||||
import FilterColumn from "@/core/components/DataTable/filter";
|
||||
import UpdateTable from "@/core/components/DataTable/update/UpdateTable";
|
||||
import ResetStorage from "@/core/components/DataTable/reset/ResetStorage";
|
||||
import HideColumn from "@/core/components/DataTable/hide";
|
||||
import FilterCustom from "../filter/FilterCustom";
|
||||
|
||||
const DataTable_TopToolbar = ({
|
||||
mutate,
|
||||
need_filter,
|
||||
table,
|
||||
columns,
|
||||
table_url,
|
||||
user_id,
|
||||
page_name,
|
||||
table_name,
|
||||
table_title,
|
||||
special_data,
|
||||
special_filter,
|
||||
setFilterData,
|
||||
}) => {
|
||||
const {
|
||||
getState,
|
||||
options: {
|
||||
enablePagination,
|
||||
enableToolbarInternalActions,
|
||||
muiTopToolbarProps,
|
||||
positionPagination,
|
||||
renderTopToolbarCustomActions,
|
||||
},
|
||||
refs: { topToolbarRef },
|
||||
} = table;
|
||||
const { isFullScreen, showGlobalFilter } = getState();
|
||||
|
||||
const isMobile = useMediaQuery("(max-width:720px)");
|
||||
const isTablet = useMediaQuery("(max-width:1024px)");
|
||||
|
||||
const toolbarProps = parseFromValuesOrFunc(muiTopToolbarProps, { table });
|
||||
|
||||
const stackAlertBanner = isMobile || !!renderTopToolbarCustomActions || (showGlobalFilter && isTablet);
|
||||
|
||||
return (
|
||||
<Box
|
||||
{...toolbarProps}
|
||||
ref={(ref) => {
|
||||
topToolbarRef.current = ref;
|
||||
if (toolbarProps?.ref) {
|
||||
// @ts-ignore
|
||||
toolbarProps.ref.current = ref;
|
||||
}
|
||||
}}
|
||||
sx={(theme) => ({
|
||||
...getCommonToolbarStyles({ table, theme }),
|
||||
position: isFullScreen ? "sticky" : "relative",
|
||||
top: isFullScreen ? "0" : undefined,
|
||||
...parseFromValuesOrFunc(toolbarProps?.sx, theme),
|
||||
})}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
flexWrap: { xs: "wrap", sm: "no-wrap" },
|
||||
boxSizing: "border-box",
|
||||
display: "flex",
|
||||
gap: "0.5rem",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
p: "0.5rem",
|
||||
position: stackAlertBanner ? "relative" : "absolute",
|
||||
right: 0,
|
||||
top: 0,
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
order: { xs: 2, sm: 1 },
|
||||
}}
|
||||
>
|
||||
{renderTopToolbarCustomActions?.({ table })}
|
||||
</Box>
|
||||
<Typography
|
||||
sx={{ textAlign: "center", order: { xs: 1, sm: 2 }, width: { xs: "100%", sm: "unset" } }}
|
||||
fontWeight={600}
|
||||
>
|
||||
{table_title || ""}
|
||||
</Typography>
|
||||
{enableToolbarInternalActions && (
|
||||
<Box
|
||||
sx={{
|
||||
gap: "0.5rem",
|
||||
flexWrap: "wrap-reverse",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-end",
|
||||
order: 3,
|
||||
}}
|
||||
>
|
||||
{!special_data && (
|
||||
<>
|
||||
<ResetStorage user_id={user_id} page_name={page_name} table_name={table_name} />
|
||||
<UpdateTable mutate={mutate} />
|
||||
</>
|
||||
)}
|
||||
<HideColumn
|
||||
columns={table.options.columns}
|
||||
user_id={user_id}
|
||||
page_name={page_name}
|
||||
table_name={table_name}
|
||||
/>
|
||||
{need_filter && (
|
||||
<FilterColumn
|
||||
columns={columns}
|
||||
table_url={table_url}
|
||||
user_id={user_id}
|
||||
page_name={page_name}
|
||||
table_name={table_name}
|
||||
/>
|
||||
)}
|
||||
{special_filter && setFilterData && (
|
||||
<FilterCustom filterData={special_filter} setFilterData={setFilterData} />
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
{enablePagination && ["both", "top"].includes(positionPagination ?? "") && (
|
||||
<DataTable_TablePagination position="top" table={table} />
|
||||
)}
|
||||
<DataTable_LinearProgressBar isTopToolbar table={table} />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default DataTable_TopToolbar;
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
"use client";
|
||||
import { IconButton, Tooltip } from "@mui/material";
|
||||
import RestartAltIcon from "@mui/icons-material/RestartAlt";
|
||||
|
||||
function UpdateTable({ mutate }) {
|
||||
const update = () => {
|
||||
mutate();
|
||||
};
|
||||
|
||||
return (
|
||||
<Tooltip title="بروزرسانی اطلاعات">
|
||||
<IconButton onClick={update} aria-label="refactor table">
|
||||
<RestartAltIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
export default UpdateTable;
|
||||
"use client";
|
||||
import { IconButton, Tooltip } from "@mui/material";
|
||||
import RestartAltIcon from "@mui/icons-material/RestartAlt";
|
||||
|
||||
function UpdateTable({ mutate }) {
|
||||
const update = () => {
|
||||
mutate();
|
||||
};
|
||||
|
||||
return (
|
||||
<Tooltip title="بروزرسانی اطلاعات">
|
||||
<IconButton onClick={update} aria-label="refactor table">
|
||||
<RestartAltIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
export default UpdateTable;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import DataTable from "@/core/components/DataTable";
|
||||
import { useAuth } from "@/lib/contexts/auth";
|
||||
|
||||
const DataTableWithAuth = (props) => {
|
||||
const { user } = useAuth();
|
||||
return <DataTable user_id={user.username} {...props} />;
|
||||
};
|
||||
export default DataTableWithAuth;
|
||||
import DataTable from "@/core/components/DataTable";
|
||||
import { useAuth } from "@/lib/contexts/auth";
|
||||
|
||||
const DataTableWithAuth = (props) => {
|
||||
const { user } = useAuth();
|
||||
return <DataTable user_id={user.username} {...props} />;
|
||||
};
|
||||
export default DataTableWithAuth;
|
||||
|
||||
@@ -1,36 +1,36 @@
|
||||
import { Box, Skeleton } from "@mui/material";
|
||||
|
||||
const DialogLoading = ({ loadingOpen }) => {
|
||||
return (
|
||||
<Box sx={{ display: "flex", flexDirection: "column", gap: 2, p: 2 }}>
|
||||
<Skeleton variant="text" sx={{ fontSize: "1.5rem", width: "70%" }} />
|
||||
<Skeleton variant="text" sx={{ fontSize: "1rem", width: "90%" }} />
|
||||
<Skeleton variant="rectangular" sx={{ borderRadius: "8px", height: "180px", width: "100%" }} />
|
||||
<Box sx={{ display: "flex", flexDirection: "column", gap: 1 }}>
|
||||
<Skeleton variant="text" sx={{ fontSize: "1rem", width: "80%" }} />
|
||||
<Skeleton variant="text" sx={{ fontSize: "1rem", width: "60%" }} />
|
||||
<Skeleton variant="text" sx={{ fontSize: "1rem", width: "70%" }} />
|
||||
</Box>
|
||||
<Box sx={{ display: "flex", gap: 2, mt: 2 }}>
|
||||
<Skeleton
|
||||
variant="rectangular"
|
||||
sx={{
|
||||
borderRadius: "8px",
|
||||
height: "40px",
|
||||
width: "30%",
|
||||
}}
|
||||
/>
|
||||
<Skeleton
|
||||
variant="rectangular"
|
||||
sx={{
|
||||
borderRadius: "8px",
|
||||
height: "40px",
|
||||
width: "20%",
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default DialogLoading;
|
||||
import { Box, Skeleton } from "@mui/material";
|
||||
|
||||
const DialogLoading = ({ loadingOpen }) => {
|
||||
return (
|
||||
<Box sx={{ display: "flex", flexDirection: "column", gap: 2, p: 2 }}>
|
||||
<Skeleton variant="text" sx={{ fontSize: "1.5rem", width: "70%" }} />
|
||||
<Skeleton variant="text" sx={{ fontSize: "1rem", width: "90%" }} />
|
||||
<Skeleton variant="rectangular" sx={{ borderRadius: "8px", height: "180px", width: "100%" }} />
|
||||
<Box sx={{ display: "flex", flexDirection: "column", gap: 1 }}>
|
||||
<Skeleton variant="text" sx={{ fontSize: "1rem", width: "80%" }} />
|
||||
<Skeleton variant="text" sx={{ fontSize: "1rem", width: "60%" }} />
|
||||
<Skeleton variant="text" sx={{ fontSize: "1rem", width: "70%" }} />
|
||||
</Box>
|
||||
<Box sx={{ display: "flex", gap: 2, mt: 2 }}>
|
||||
<Skeleton
|
||||
variant="rectangular"
|
||||
sx={{
|
||||
borderRadius: "8px",
|
||||
height: "40px",
|
||||
width: "30%",
|
||||
}}
|
||||
/>
|
||||
<Skeleton
|
||||
variant="rectangular"
|
||||
sx={{
|
||||
borderRadius: "8px",
|
||||
height: "40px",
|
||||
width: "20%",
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default DialogLoading;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Slide } from "@mui/material";
|
||||
import React from "react";
|
||||
|
||||
const DialogTransition = React.forwardRef(function Transition(props, ref) {
|
||||
return <Slide direction="up" ref={ref} {...props} />;
|
||||
});
|
||||
|
||||
export default DialogTransition;
|
||||
import { Slide } from "@mui/material";
|
||||
import React from "react";
|
||||
|
||||
const DialogTransition = React.forwardRef(function Transition(props, ref) {
|
||||
return <Slide direction="up" ref={ref} {...props} />;
|
||||
});
|
||||
|
||||
export default DialogTransition;
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
import { Controller } from "react-hook-form";
|
||||
import FilterField from "./FilterField";
|
||||
|
||||
const FilterController = ({ item, control, reset, errors }) => {
|
||||
return (
|
||||
<Controller
|
||||
name={`${item.id}`}
|
||||
control={control}
|
||||
render={({ field: { value, onBlur, onChange } }) => {
|
||||
return (
|
||||
<FilterField
|
||||
item={item}
|
||||
filterParameters={value}
|
||||
handleChange={onChange}
|
||||
handleBlur={onBlur}
|
||||
dependencyFieldValue={null}
|
||||
resetForm={() => reset()}
|
||||
errors={errors}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export default FilterController;
|
||||
import { Controller } from "react-hook-form";
|
||||
import FilterField from "./FilterField";
|
||||
|
||||
const FilterController = ({ item, control, reset, errors }) => {
|
||||
return (
|
||||
<Controller
|
||||
name={`${item.id}`}
|
||||
control={control}
|
||||
render={({ field: { value, onBlur, onChange } }) => {
|
||||
return (
|
||||
<FilterField
|
||||
item={item}
|
||||
filterParameters={value}
|
||||
handleChange={onChange}
|
||||
handleBlur={onBlur}
|
||||
dependencyFieldValue={null}
|
||||
resetForm={() => reset()}
|
||||
errors={errors}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export default FilterController;
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
import { Controller, useWatch } from "react-hook-form";
|
||||
import FilterField from "./FilterField";
|
||||
|
||||
const FilterControllerWithDependency = ({ item, control, reset, errors }) => {
|
||||
const dependencyField = useWatch({ control, name: item.dependencyId });
|
||||
|
||||
return (
|
||||
<Controller
|
||||
name={`${item.id}`}
|
||||
control={control}
|
||||
render={({ field: { value, name, onBlur, onChange } }) => {
|
||||
return (
|
||||
<FilterField
|
||||
item={item}
|
||||
filterParameters={value}
|
||||
handleChange={onChange}
|
||||
handleBlur={onBlur}
|
||||
dependencyFieldValue={dependencyField}
|
||||
resetForm={() => reset()}
|
||||
errors={errors}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export default FilterControllerWithDependency;
|
||||
import { Controller, useWatch } from "react-hook-form";
|
||||
import FilterField from "./FilterField";
|
||||
|
||||
const FilterControllerWithDependency = ({ item, control, reset, errors }) => {
|
||||
const dependencyField = useWatch({ control, name: item.dependencyId });
|
||||
|
||||
return (
|
||||
<Controller
|
||||
name={`${item.id}`}
|
||||
control={control}
|
||||
render={({ field: { value, name, onBlur, onChange } }) => {
|
||||
return (
|
||||
<FilterField
|
||||
item={item}
|
||||
filterParameters={value}
|
||||
handleChange={onChange}
|
||||
handleBlur={onBlur}
|
||||
dependencyFieldValue={dependencyField}
|
||||
resetForm={() => reset()}
|
||||
errors={errors}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export default FilterControllerWithDependency;
|
||||
|
||||
@@ -1,103 +1,103 @@
|
||||
import { useState } from "react";
|
||||
import CustomTextFieldRange from "./fieldsType/CustomTextFieldRange";
|
||||
import CustomSelect from "./fieldsType/CustomSelect";
|
||||
import CustomDatePicker from "../CustomDatePicker";
|
||||
import CustomDatePickerRange from "./fieldsType/CustomDate/CustomDatePickerRange";
|
||||
import CustomSelectMultiple from "./fieldsType/CustomSelectMultiple";
|
||||
import CustomTextField from "./fieldsType/CustomTextField";
|
||||
import FilterOptionList from "./FilterOptionList";
|
||||
|
||||
const filterModeOptionFa = {
|
||||
equals: "برابر",
|
||||
notEquals: "نابرابر",
|
||||
contains: "شامل",
|
||||
lessThan: "کوچکتر",
|
||||
greaterThan: "بزرگتر",
|
||||
fuzzy: "فازی",
|
||||
between: "مابین",
|
||||
};
|
||||
|
||||
function FilterField({
|
||||
item,
|
||||
filterParameters,
|
||||
handleChange,
|
||||
handleBlur,
|
||||
dependencyFieldValue,
|
||||
setFieldValue,
|
||||
resetForm,
|
||||
errors,
|
||||
}) {
|
||||
const [anchorEl, setAnchorEl] = useState(null);
|
||||
const defaultFilterTranslation = filterModeOptionFa[filterParameters.filterMode] || filterParameters.filterMode;
|
||||
|
||||
const handleOpenFilterBox = (event) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
|
||||
const renderField = () => {
|
||||
const commonProps = {
|
||||
item,
|
||||
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 item.SelectComponent ? (
|
||||
<item.SelectComponent {...commonProps} />
|
||||
) : (
|
||||
<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 (
|
||||
<>
|
||||
{renderField()}
|
||||
{Array.isArray(item.filterModeOptions) && (
|
||||
<FilterOptionList
|
||||
item={item}
|
||||
anchorEl={anchorEl}
|
||||
setAnchorEl={setAnchorEl}
|
||||
filterParameters={filterParameters}
|
||||
filterType={filterParameters.filterMode}
|
||||
handleChange={handleChange}
|
||||
resetForm={resetForm}
|
||||
filterOption={item.filterModeOptions}
|
||||
filterModeOptionFa={filterModeOptionFa}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default FilterField;
|
||||
import { useState } from "react";
|
||||
import CustomTextFieldRange from "./fieldsType/CustomTextFieldRange";
|
||||
import CustomSelect from "./fieldsType/CustomSelect";
|
||||
import CustomDatePicker from "../CustomDatePicker";
|
||||
import CustomDatePickerRange from "./fieldsType/CustomDate/CustomDatePickerRange";
|
||||
import CustomSelectMultiple from "./fieldsType/CustomSelectMultiple";
|
||||
import CustomTextField from "./fieldsType/CustomTextField";
|
||||
import FilterOptionList from "./FilterOptionList";
|
||||
|
||||
const filterModeOptionFa = {
|
||||
equals: "برابر",
|
||||
notEquals: "نابرابر",
|
||||
contains: "شامل",
|
||||
lessThan: "کوچکتر",
|
||||
greaterThan: "بزرگتر",
|
||||
fuzzy: "فازی",
|
||||
between: "مابین",
|
||||
};
|
||||
|
||||
function FilterField({
|
||||
item,
|
||||
filterParameters,
|
||||
handleChange,
|
||||
handleBlur,
|
||||
dependencyFieldValue,
|
||||
setFieldValue,
|
||||
resetForm,
|
||||
errors,
|
||||
}) {
|
||||
const [anchorEl, setAnchorEl] = useState(null);
|
||||
const defaultFilterTranslation = filterModeOptionFa[filterParameters.filterMode] || filterParameters.filterMode;
|
||||
|
||||
const handleOpenFilterBox = (event) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
|
||||
const renderField = () => {
|
||||
const commonProps = {
|
||||
item,
|
||||
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 item.SelectComponent ? (
|
||||
<item.SelectComponent {...commonProps} />
|
||||
) : (
|
||||
<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 (
|
||||
<>
|
||||
{renderField()}
|
||||
{Array.isArray(item.filterModeOptions) && (
|
||||
<FilterOptionList
|
||||
item={item}
|
||||
anchorEl={anchorEl}
|
||||
setAnchorEl={setAnchorEl}
|
||||
filterParameters={filterParameters}
|
||||
filterType={filterParameters.filterMode}
|
||||
handleChange={handleChange}
|
||||
resetForm={resetForm}
|
||||
filterOption={item.filterModeOptions}
|
||||
filterModeOptionFa={filterModeOptionFa}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default FilterField;
|
||||
|
||||
@@ -1,47 +1,47 @@
|
||||
"use client";
|
||||
|
||||
import { ListItem, Menu } from "@mui/material";
|
||||
import { useState } from "react";
|
||||
|
||||
function FilterOptionList({
|
||||
filterType,
|
||||
filterOption,
|
||||
filterParameters,
|
||||
anchorEl,
|
||||
filterModeOptionFa,
|
||||
setAnchorEl,
|
||||
handleChange,
|
||||
}) {
|
||||
const [selectedFilter, setSelectedFilter] = useState(filterType);
|
||||
|
||||
const handleChangeItem = (event, index) => {
|
||||
handleChange({
|
||||
...filterParameters,
|
||||
value: filterOption[index] === "between" ? ["", ""] : "",
|
||||
filterMode: filterOption[index],
|
||||
});
|
||||
setSelectedFilter(filterOption[index]);
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
const handleCloseFilterBox = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<Menu id="simple-menu" anchorEl={anchorEl} keepMounted open={Boolean(anchorEl)} onClose={handleCloseFilterBox}>
|
||||
{filterOption.map((option, index) => (
|
||||
<ListItem
|
||||
key={index}
|
||||
onClick={(event) => handleChangeItem(event, index)}
|
||||
selected={option === selectedFilter}
|
||||
sx={{ cursor: "pointer", width: "100px", display: "flex", justifyContent: "center" }}
|
||||
>
|
||||
{filterModeOptionFa[option]}
|
||||
</ListItem>
|
||||
))}
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
|
||||
export default FilterOptionList;
|
||||
"use client";
|
||||
|
||||
import { ListItem, Menu } from "@mui/material";
|
||||
import { useState } from "react";
|
||||
|
||||
function FilterOptionList({
|
||||
filterType,
|
||||
filterOption,
|
||||
filterParameters,
|
||||
anchorEl,
|
||||
filterModeOptionFa,
|
||||
setAnchorEl,
|
||||
handleChange,
|
||||
}) {
|
||||
const [selectedFilter, setSelectedFilter] = useState(filterType);
|
||||
|
||||
const handleChangeItem = (event, index) => {
|
||||
handleChange({
|
||||
...filterParameters,
|
||||
value: filterOption[index] === "between" ? ["", ""] : "",
|
||||
filterMode: filterOption[index],
|
||||
});
|
||||
setSelectedFilter(filterOption[index]);
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
const handleCloseFilterBox = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<Menu id="simple-menu" anchorEl={anchorEl} keepMounted open={Boolean(anchorEl)} onClose={handleCloseFilterBox}>
|
||||
{filterOption.map((option, index) => (
|
||||
<ListItem
|
||||
key={index}
|
||||
onClick={(event) => handleChangeItem(event, index)}
|
||||
selected={option === selectedFilter}
|
||||
sx={{ cursor: "pointer", width: "100px", display: "flex", justifyContent: "center" }}
|
||||
>
|
||||
{filterModeOptionFa[option]}
|
||||
</ListItem>
|
||||
))}
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
|
||||
export default FilterOptionList;
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
import React from "react";
|
||||
import MuiDatePicker from "@/core/components/DataTable/filter/fieldsType/CustomDate/MuiDatePicker";
|
||||
import { Typography } from "@mui/material";
|
||||
|
||||
function CustomDatePicker({ column, filterParameters, defaultFilterTranslation, handleChange }) {
|
||||
return (
|
||||
<MuiDatePicker
|
||||
name={`${column.id}.value`}
|
||||
value={filterParameters.value}
|
||||
setFieldValue={(name, formattedDate) => {
|
||||
handleChange({ ...filterParameters, value: formattedDate });
|
||||
}}
|
||||
placeholder={column.header}
|
||||
helperText={
|
||||
<Typography variant="button" sx={{ color: "#155175" }}>
|
||||
نوع فیلتر: {defaultFilterTranslation} (تاریخ)
|
||||
</Typography>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default CustomDatePicker;
|
||||
import React from "react";
|
||||
import MuiDatePicker from "@/core/components/DataTable/filter/fieldsType/CustomDate/MuiDatePicker";
|
||||
import { Typography } from "@mui/material";
|
||||
|
||||
function CustomDatePicker({ column, filterParameters, defaultFilterTranslation, handleChange }) {
|
||||
return (
|
||||
<MuiDatePicker
|
||||
name={`${column.id}.value`}
|
||||
value={filterParameters.value}
|
||||
setFieldValue={(name, formattedDate) => {
|
||||
handleChange({ ...filterParameters, value: formattedDate });
|
||||
}}
|
||||
placeholder={column.header}
|
||||
helperText={
|
||||
<Typography variant="button" sx={{ color: "#155175" }}>
|
||||
نوع فیلتر: {defaultFilterTranslation} (تاریخ)
|
||||
</Typography>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default CustomDatePicker;
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { Box, Typography } from "@mui/material";
|
||||
import MuiDatePicker from "@/core/components/DataTable/filter/fieldsType/CustomDate/MuiDatePicker";
|
||||
|
||||
function CustomDatePickerRange({ item, filterParameters, defaultFilterTranslation, handleChange, errors }) {
|
||||
return (
|
||||
<Box sx={{ display: "flex", gap: 1 }}>
|
||||
<MuiDatePicker
|
||||
label={item.header}
|
||||
name={`${item.id}.value[0]`}
|
||||
value={filterParameters.value[0]}
|
||||
setFieldValue={(name, formattedDate) => {
|
||||
handleChange({ ...filterParameters, value: [formattedDate, filterParameters.value[1]] });
|
||||
}}
|
||||
maxDate={filterParameters.value[1]}
|
||||
placeholder={`از تاریخ`}
|
||||
helperText={
|
||||
<Typography variant="button" sx={{ color: "#155175" }}>
|
||||
نوع فیلتر: {defaultFilterTranslation} (تاریخ)
|
||||
</Typography>
|
||||
}
|
||||
/>
|
||||
<MuiDatePicker
|
||||
label={item.header}
|
||||
name={`${item.id}.value[1]`}
|
||||
value={filterParameters.value[1]}
|
||||
setFieldValue={(name, formattedDate) => {
|
||||
handleChange({ ...filterParameters, value: [filterParameters.value[0], formattedDate] });
|
||||
}}
|
||||
minDate={filterParameters.value[0]}
|
||||
placeholder={`تا تاریخ`}
|
||||
helperText={errors?.[`${item.id}`]?.value ? errors?.[`${item.id}`]?.value.message : null}
|
||||
error={Boolean(errors?.[`${item.id}`]?.value)}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default CustomDatePickerRange;
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { Box, Typography } from "@mui/material";
|
||||
import MuiDatePicker from "@/core/components/DataTable/filter/fieldsType/CustomDate/MuiDatePicker";
|
||||
|
||||
function CustomDatePickerRange({ item, filterParameters, defaultFilterTranslation, handleChange, errors }) {
|
||||
return (
|
||||
<Box sx={{ display: "flex", gap: 1 }}>
|
||||
<MuiDatePicker
|
||||
label={item.header}
|
||||
name={`${item.id}.value[0]`}
|
||||
value={filterParameters.value[0]}
|
||||
setFieldValue={(name, formattedDate) => {
|
||||
handleChange({ ...filterParameters, value: [formattedDate, filterParameters.value[1]] });
|
||||
}}
|
||||
maxDate={filterParameters.value[1]}
|
||||
placeholder={`از تاریخ`}
|
||||
helperText={
|
||||
<Typography variant="button" sx={{ color: "#155175" }}>
|
||||
نوع فیلتر: {defaultFilterTranslation} (تاریخ)
|
||||
</Typography>
|
||||
}
|
||||
/>
|
||||
<MuiDatePicker
|
||||
label={item.header}
|
||||
name={`${item.id}.value[1]`}
|
||||
value={filterParameters.value[1]}
|
||||
setFieldValue={(name, formattedDate) => {
|
||||
handleChange({ ...filterParameters, value: [filterParameters.value[0], formattedDate] });
|
||||
}}
|
||||
minDate={filterParameters.value[0]}
|
||||
placeholder={`تا تاریخ`}
|
||||
helperText={errors?.[`${item.id}`]?.value ? errors?.[`${item.id}`]?.value.message : null}
|
||||
error={Boolean(errors?.[`${item.id}`]?.value)}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default CustomDatePickerRange;
|
||||
|
||||
@@ -1,72 +1,72 @@
|
||||
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";
|
||||
|
||||
function MuiDatePicker({ label, value, setFieldValue, name, minDate, maxDate, helperText, placeholder, error }) {
|
||||
return (
|
||||
<Box sx={{ display: "flex", flexDirection: "column", my: 1 }}>
|
||||
<LocalizationProvider
|
||||
dateAdapter={AdapterDateFnsJalali}
|
||||
localeText={faIR.components.MuiLocalizationProvider.defaultProps.localeText}
|
||||
>
|
||||
<MobileDatePicker
|
||||
value={value ? new Date(value) : null}
|
||||
sx={{ width: "100%" }}
|
||||
id={name}
|
||||
label={label}
|
||||
name={name}
|
||||
closeOnSelect={true}
|
||||
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: {
|
||||
size: "small",
|
||||
error: error,
|
||||
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>
|
||||
),
|
||||
},
|
||||
InputLabelProps: {
|
||||
shrink: true,
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
{/* <FormHelperText id="component-helper-text" variant="caption" sx={{ ml: 2, color: error ? "#d32f2f" : "unset", fontWeight: 'bold' }}>
|
||||
{helperText ? helperText : ""}
|
||||
</FormHelperText> */}
|
||||
</LocalizationProvider>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default MuiDatePicker;
|
||||
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";
|
||||
|
||||
function MuiDatePicker({ label, value, setFieldValue, name, minDate, maxDate, helperText, placeholder, error }) {
|
||||
return (
|
||||
<Box sx={{ display: "flex", flexDirection: "column", my: 1 }}>
|
||||
<LocalizationProvider
|
||||
dateAdapter={AdapterDateFnsJalali}
|
||||
localeText={faIR.components.MuiLocalizationProvider.defaultProps.localeText}
|
||||
>
|
||||
<MobileDatePicker
|
||||
value={value ? new Date(value) : null}
|
||||
sx={{ width: "100%" }}
|
||||
id={name}
|
||||
label={label}
|
||||
name={name}
|
||||
closeOnSelect={true}
|
||||
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: {
|
||||
size: "small",
|
||||
error: error,
|
||||
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>
|
||||
),
|
||||
},
|
||||
InputLabelProps: {
|
||||
shrink: true,
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
{/* <FormHelperText id="component-helper-text" variant="caption" sx={{ ml: 2, color: error ? "#d32f2f" : "unset", fontWeight: 'bold' }}>
|
||||
{helperText ? helperText : ""}
|
||||
</FormHelperText> */}
|
||||
</LocalizationProvider>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default MuiDatePicker;
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
"use client";
|
||||
|
||||
import { FormControl, InputLabel, MenuItem, OutlinedInput, Select } from "@mui/material";
|
||||
function CustomSelect({ item, filterParameters, handleChange }) {
|
||||
return (
|
||||
<FormControl fullWidth sx={{ marginY: 1 }} size="small">
|
||||
<InputLabel id={`label${item.id}`} shrink>
|
||||
{item.header}
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId={`label${item.id}`}
|
||||
id={item.id}
|
||||
name={`${item.id}.value`}
|
||||
value={filterParameters.value}
|
||||
input={<OutlinedInput label={item.header} />}
|
||||
size="small"
|
||||
onChange={(e) => handleChange({ ...filterParameters, value: e.target.value })}
|
||||
displayEmpty
|
||||
>
|
||||
{item.selectOption().map((option) => (
|
||||
<MenuItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
|
||||
export default CustomSelect;
|
||||
"use client";
|
||||
|
||||
import { FormControl, InputLabel, MenuItem, OutlinedInput, Select } from "@mui/material";
|
||||
function CustomSelect({ item, filterParameters, handleChange }) {
|
||||
return (
|
||||
<FormControl fullWidth sx={{ marginY: 1 }} size="small">
|
||||
<InputLabel id={`label${item.id}`} shrink>
|
||||
{item.header}
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId={`label${item.id}`}
|
||||
id={item.id}
|
||||
name={`${item.id}.value`}
|
||||
value={filterParameters.value}
|
||||
input={<OutlinedInput label={item.header} />}
|
||||
size="small"
|
||||
onChange={(e) => handleChange({ ...filterParameters, value: e.target.value })}
|
||||
displayEmpty
|
||||
>
|
||||
{item.selectOption().map((option) => (
|
||||
<MenuItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
|
||||
export default CustomSelect;
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
"use client";
|
||||
|
||||
import { FormControl, InputLabel, MenuItem, OutlinedInput, Select } from "@mui/material";
|
||||
function CustomSelectByDependency({ item, filterParameters, value, handleChange, selectOption }) {
|
||||
return (
|
||||
<FormControl fullWidth sx={{ my: 1 }} size="small">
|
||||
<InputLabel id={`label${item.id}`} shrink>
|
||||
{item.header}
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId={`label${item.id}`}
|
||||
id={item.id}
|
||||
name={`${item.id}.value`}
|
||||
value={value}
|
||||
size="small"
|
||||
input={<OutlinedInput label={item.header} />}
|
||||
onChange={(e) => handleChange({ ...filterParameters, value: e.target.value })}
|
||||
displayEmpty
|
||||
>
|
||||
{selectOption.map((option) => (
|
||||
<MenuItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
|
||||
export default CustomSelectByDependency;
|
||||
"use client";
|
||||
|
||||
import { FormControl, InputLabel, MenuItem, OutlinedInput, Select } from "@mui/material";
|
||||
function CustomSelectByDependency({ item, filterParameters, value, handleChange, selectOption }) {
|
||||
return (
|
||||
<FormControl fullWidth sx={{ my: 1 }} size="small">
|
||||
<InputLabel id={`label${item.id}`} shrink>
|
||||
{item.header}
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId={`label${item.id}`}
|
||||
id={item.id}
|
||||
name={`${item.id}.value`}
|
||||
value={value}
|
||||
size="small"
|
||||
input={<OutlinedInput label={item.header} />}
|
||||
onChange={(e) => handleChange({ ...filterParameters, value: e.target.value })}
|
||||
displayEmpty
|
||||
>
|
||||
{selectOption.map((option) => (
|
||||
<MenuItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
|
||||
export default CustomSelectByDependency;
|
||||
|
||||
@@ -1,55 +1,55 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Box,
|
||||
Chip,
|
||||
FormControl,
|
||||
FormHelperText,
|
||||
InputLabel,
|
||||
MenuItem,
|
||||
OutlinedInput,
|
||||
Select,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
|
||||
function CustomSelectMultiple({ item, filterParameters, handleChange }) {
|
||||
const selectOption = item.selectOption;
|
||||
|
||||
const getLabelForValue = (value) => {
|
||||
const option = selectOption.find((opt) => opt.value === value);
|
||||
return option ? option.label : value;
|
||||
};
|
||||
|
||||
return (
|
||||
<FormControl fullWidth sx={{ marginY: 1 }} size="small">
|
||||
<InputLabel htmlFor="uncontrolled-native" id={`label${item.id}`}>
|
||||
{item.header}
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId={`label${item.id}`}
|
||||
id={item.id}
|
||||
value={filterParameters.value}
|
||||
multiple
|
||||
size="small"
|
||||
onChange={(e) => handleChange({ ...filterParameters, value: e.target.value })}
|
||||
renderValue={(selected) => (
|
||||
<Box sx={{ display: "flex", flexWrap: "wrap", gap: 0.5 }}>
|
||||
{selected.map((value) => (
|
||||
<Chip key={value} label={getLabelForValue(value)} />
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
input={<OutlinedInput label={item.header} />}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
>
|
||||
{selectOption.map((option) => (
|
||||
<MenuItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
|
||||
export default CustomSelectMultiple;
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Box,
|
||||
Chip,
|
||||
FormControl,
|
||||
FormHelperText,
|
||||
InputLabel,
|
||||
MenuItem,
|
||||
OutlinedInput,
|
||||
Select,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
|
||||
function CustomSelectMultiple({ item, filterParameters, handleChange }) {
|
||||
const selectOption = item.selectOption;
|
||||
|
||||
const getLabelForValue = (value) => {
|
||||
const option = selectOption.find((opt) => opt.value === value);
|
||||
return option ? option.label : value;
|
||||
};
|
||||
|
||||
return (
|
||||
<FormControl fullWidth sx={{ marginY: 1 }} size="small">
|
||||
<InputLabel htmlFor="uncontrolled-native" id={`label${item.id}`}>
|
||||
{item.header}
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId={`label${item.id}`}
|
||||
id={item.id}
|
||||
value={filterParameters.value}
|
||||
multiple
|
||||
size="small"
|
||||
onChange={(e) => handleChange({ ...filterParameters, value: e.target.value })}
|
||||
renderValue={(selected) => (
|
||||
<Box sx={{ display: "flex", flexWrap: "wrap", gap: 0.5 }}>
|
||||
{selected.map((value) => (
|
||||
<Chip key={value} label={getLabelForValue(value)} />
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
input={<OutlinedInput label={item.header} />}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
>
|
||||
{selectOption.map((option) => (
|
||||
<MenuItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
|
||||
export default CustomSelectMultiple;
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
import { InputAdornment, TextField, Typography } from "@mui/material";
|
||||
import FilterListIcon from "@mui/icons-material/FilterList";
|
||||
|
||||
function CustomTextField({ item, filterParameters, handleOpenFilterBox, handleBlur, handleChange }) {
|
||||
return (
|
||||
<TextField
|
||||
id={item.id}
|
||||
name={item.id}
|
||||
label={item.header}
|
||||
value={filterParameters.value}
|
||||
onChange={(e) => handleChange({ ...filterParameters, value: e.target.value })}
|
||||
onBlur={handleBlur}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
size="small"
|
||||
sx={{ my: 1 }}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position="end" sx={{ cursor: "pointer" }} onClick={handleOpenFilterBox}>
|
||||
<FilterListIcon />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default CustomTextField;
|
||||
import { InputAdornment, TextField, Typography } from "@mui/material";
|
||||
import FilterListIcon from "@mui/icons-material/FilterList";
|
||||
|
||||
function CustomTextField({ item, filterParameters, handleOpenFilterBox, handleBlur, handleChange }) {
|
||||
return (
|
||||
<TextField
|
||||
id={item.id}
|
||||
name={item.id}
|
||||
label={item.header}
|
||||
value={filterParameters.value}
|
||||
onChange={(e) => handleChange({ ...filterParameters, value: e.target.value })}
|
||||
onBlur={handleBlur}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
size="small"
|
||||
sx={{ my: 1 }}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position="end" sx={{ cursor: "pointer" }} onClick={handleOpenFilterBox}>
|
||||
<FilterListIcon />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default CustomTextField;
|
||||
|
||||
@@ -1,62 +1,62 @@
|
||||
import { Box, InputAdornment, TextField, Typography } from "@mui/material";
|
||||
import FilterListIcon from "@mui/icons-material/FilterList";
|
||||
|
||||
function CustomTextFieldRange({
|
||||
item,
|
||||
defaultFilterTranslation,
|
||||
handleOpenFilterBox,
|
||||
handleChange,
|
||||
filterParameters,
|
||||
handleBlur,
|
||||
errors,
|
||||
}) {
|
||||
return (
|
||||
<Box sx={{ display: "flex" }}>
|
||||
<TextField
|
||||
id={`${item.id}.value[0]`}
|
||||
name={`${item.id}.value[0]`}
|
||||
onChange={(e) =>
|
||||
handleChange({ ...filterParameters, value: [e.target.value, filterParameters.value[1]] })
|
||||
}
|
||||
onBlur={handleBlur}
|
||||
label={<Typography>از {item.header}</Typography>}
|
||||
value={filterParameters.value[0]}
|
||||
fullWidth
|
||||
error={touched?.[`${item.id}`]?.value && Boolean(errors?.[`${item.id}`]?.value)}
|
||||
helperText={
|
||||
<Typography variant="caption" sx={{ color: "#155175", fontWeight: "bold" }}>
|
||||
نوع فیلتر: {defaultFilterTranslation}
|
||||
</Typography>
|
||||
}
|
||||
variant="outlined"
|
||||
size="small"
|
||||
sx={{ my: 1, marginRight: 1 }}
|
||||
/>
|
||||
<TextField
|
||||
id={`${item.id}.value[1]`}
|
||||
name={`${item.id}.value[1]`}
|
||||
onChange={(e) =>
|
||||
handleChange({ ...filterParameters, value: [filterParameters.value[0], e.target.value] })
|
||||
}
|
||||
onBlur={handleBlur}
|
||||
error={Boolean(errors?.[`${item.id}`]?.value)}
|
||||
helperText={errors?.[`${item.id}`]?.value ? errors?.[`${item.id}`]?.value.message : null}
|
||||
label={<Typography>تا {item.header}</Typography>}
|
||||
value={filterParameters.value[1]}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
size="small"
|
||||
sx={{ my: 1 }}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position="end" sx={{ cursor: "pointer" }} onClick={handleOpenFilterBox}>
|
||||
<FilterListIcon />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default CustomTextFieldRange;
|
||||
import { Box, InputAdornment, TextField, Typography } from "@mui/material";
|
||||
import FilterListIcon from "@mui/icons-material/FilterList";
|
||||
|
||||
function CustomTextFieldRange({
|
||||
item,
|
||||
defaultFilterTranslation,
|
||||
handleOpenFilterBox,
|
||||
handleChange,
|
||||
filterParameters,
|
||||
handleBlur,
|
||||
errors,
|
||||
}) {
|
||||
return (
|
||||
<Box sx={{ display: "flex" }}>
|
||||
<TextField
|
||||
id={`${item.id}.value[0]`}
|
||||
name={`${item.id}.value[0]`}
|
||||
onChange={(e) =>
|
||||
handleChange({ ...filterParameters, value: [e.target.value, filterParameters.value[1]] })
|
||||
}
|
||||
onBlur={handleBlur}
|
||||
label={<Typography>از {item.header}</Typography>}
|
||||
value={filterParameters.value[0]}
|
||||
fullWidth
|
||||
error={touched?.[`${item.id}`]?.value && Boolean(errors?.[`${item.id}`]?.value)}
|
||||
helperText={
|
||||
<Typography variant="caption" sx={{ color: "#155175", fontWeight: "bold" }}>
|
||||
نوع فیلتر: {defaultFilterTranslation}
|
||||
</Typography>
|
||||
}
|
||||
variant="outlined"
|
||||
size="small"
|
||||
sx={{ my: 1, marginRight: 1 }}
|
||||
/>
|
||||
<TextField
|
||||
id={`${item.id}.value[1]`}
|
||||
name={`${item.id}.value[1]`}
|
||||
onChange={(e) =>
|
||||
handleChange({ ...filterParameters, value: [filterParameters.value[0], e.target.value] })
|
||||
}
|
||||
onBlur={handleBlur}
|
||||
error={Boolean(errors?.[`${item.id}`]?.value)}
|
||||
helperText={errors?.[`${item.id}`]?.value ? errors?.[`${item.id}`]?.value.message : null}
|
||||
label={<Typography>تا {item.header}</Typography>}
|
||||
value={filterParameters.value[1]}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
size="small"
|
||||
sx={{ my: 1 }}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position="end" sx={{ cursor: "pointer" }} onClick={handleOpenFilterBox}>
|
||||
<FilterListIcon />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default CustomTextFieldRange;
|
||||
|
||||
@@ -1,153 +1,153 @@
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import { Cancel, FilterAlt } from "@mui/icons-material";
|
||||
import { Box, Button, IconButton, Typography } from "@mui/material";
|
||||
import { useForm } from "react-hook-form";
|
||||
import * as Yup from "yup";
|
||||
import ScrollBox from "../ScrollBox";
|
||||
import FilterController from "./FilterController";
|
||||
import FilterControllerWithDependency from "./FilterControllerWithDependency";
|
||||
|
||||
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 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 FilterDrawer = ({ defaultValues, setFilterData, closeDrawer }) => {
|
||||
const {
|
||||
control,
|
||||
errors,
|
||||
reset,
|
||||
handleSubmit,
|
||||
formState: { isDirty },
|
||||
} = useForm({
|
||||
defaultValues,
|
||||
resolver: yupResolver(
|
||||
Yup.object(
|
||||
Object.keys(defaultValues).reduce((acc, key) => {
|
||||
const initialValue = defaultValues[key];
|
||||
if (initialValue.filterMode === "between") {
|
||||
acc[key] = Yup.object().shape({
|
||||
value: Yup.array()
|
||||
.of(Yup.string().nullable())
|
||||
.test({
|
||||
test(value, ctx) {
|
||||
const [start, end] = value || ["", ""];
|
||||
if (
|
||||
initialValue.datatype === "numeric" &&
|
||||
parseInt(end, 10) <= parseInt(start, 10)
|
||||
) {
|
||||
return ctx.createError({
|
||||
message: `مقدار وارده باید بیشتر از (${start}) باشد`,
|
||||
});
|
||||
} else if ((start && !end) || (!start && end)) {
|
||||
return ctx.createError({
|
||||
message: "این بخش را تکمیل نمایید",
|
||||
});
|
||||
}
|
||||
return true;
|
||||
},
|
||||
}),
|
||||
});
|
||||
}
|
||||
return acc;
|
||||
}, {})
|
||||
)
|
||||
),
|
||||
mode: "all",
|
||||
});
|
||||
|
||||
const onSubmit = (data) => {
|
||||
setFilterData(data);
|
||||
closeDrawer();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box sx={headerSx}>
|
||||
<Box sx={headerTitleSx}>
|
||||
<FilterAlt sx={headerIconSx} />
|
||||
<Typography variant="h6" sx={{ color: "#fff" }}>
|
||||
فیلتر
|
||||
</Typography>
|
||||
</Box>
|
||||
<IconButton sx={iconButtonSx} onClick={closeDrawer}>
|
||||
<Cancel sx={{ fontSize: "1em" }} />
|
||||
</IconButton>
|
||||
</Box>
|
||||
<ScrollBox>
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<Box sx={formContainerSx}>
|
||||
{Object.values(defaultValues).map(
|
||||
(item) =>
|
||||
item.enable &&
|
||||
(item.dependencyId ? (
|
||||
<FilterControllerWithDependency
|
||||
key={item.id}
|
||||
item={item}
|
||||
control={control}
|
||||
errors={errors}
|
||||
reset={reset}
|
||||
/>
|
||||
) : (
|
||||
<FilterController
|
||||
key={item.id}
|
||||
item={item}
|
||||
control={control}
|
||||
errors={errors}
|
||||
reset={reset}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
|
||||
<Box sx={footerSx}>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
size="large"
|
||||
disabled={!isDirty}
|
||||
sx={submitButtonSx}
|
||||
>
|
||||
اعمال فیلتر
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</form>
|
||||
</ScrollBox>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default FilterDrawer;
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import { Cancel, FilterAlt } from "@mui/icons-material";
|
||||
import { Box, Button, IconButton, Typography } from "@mui/material";
|
||||
import { useForm } from "react-hook-form";
|
||||
import * as Yup from "yup";
|
||||
import ScrollBox from "../ScrollBox";
|
||||
import FilterController from "./FilterController";
|
||||
import FilterControllerWithDependency from "./FilterControllerWithDependency";
|
||||
|
||||
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 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 FilterDrawer = ({ defaultValues, setFilterData, closeDrawer }) => {
|
||||
const {
|
||||
control,
|
||||
errors,
|
||||
reset,
|
||||
handleSubmit,
|
||||
formState: { isDirty },
|
||||
} = useForm({
|
||||
defaultValues,
|
||||
resolver: yupResolver(
|
||||
Yup.object(
|
||||
Object.keys(defaultValues).reduce((acc, key) => {
|
||||
const initialValue = defaultValues[key];
|
||||
if (initialValue.filterMode === "between") {
|
||||
acc[key] = Yup.object().shape({
|
||||
value: Yup.array()
|
||||
.of(Yup.string().nullable())
|
||||
.test({
|
||||
test(value, ctx) {
|
||||
const [start, end] = value || ["", ""];
|
||||
if (
|
||||
initialValue.datatype === "numeric" &&
|
||||
parseInt(end, 10) <= parseInt(start, 10)
|
||||
) {
|
||||
return ctx.createError({
|
||||
message: `مقدار وارده باید بیشتر از (${start}) باشد`,
|
||||
});
|
||||
} else if ((start && !end) || (!start && end)) {
|
||||
return ctx.createError({
|
||||
message: "این بخش را تکمیل نمایید",
|
||||
});
|
||||
}
|
||||
return true;
|
||||
},
|
||||
}),
|
||||
});
|
||||
}
|
||||
return acc;
|
||||
}, {})
|
||||
)
|
||||
),
|
||||
mode: "all",
|
||||
});
|
||||
|
||||
const onSubmit = (data) => {
|
||||
setFilterData(data);
|
||||
closeDrawer();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box sx={headerSx}>
|
||||
<Box sx={headerTitleSx}>
|
||||
<FilterAlt sx={headerIconSx} />
|
||||
<Typography variant="h6" sx={{ color: "#fff" }}>
|
||||
فیلتر
|
||||
</Typography>
|
||||
</Box>
|
||||
<IconButton sx={iconButtonSx} onClick={closeDrawer}>
|
||||
<Cancel sx={{ fontSize: "1em" }} />
|
||||
</IconButton>
|
||||
</Box>
|
||||
<ScrollBox>
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<Box sx={formContainerSx}>
|
||||
{Object.values(defaultValues).map(
|
||||
(item) =>
|
||||
item.enable &&
|
||||
(item.dependencyId ? (
|
||||
<FilterControllerWithDependency
|
||||
key={item.id}
|
||||
item={item}
|
||||
control={control}
|
||||
errors={errors}
|
||||
reset={reset}
|
||||
/>
|
||||
) : (
|
||||
<FilterController
|
||||
key={item.id}
|
||||
item={item}
|
||||
control={control}
|
||||
errors={errors}
|
||||
reset={reset}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
|
||||
<Box sx={footerSx}>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
size="large"
|
||||
disabled={!isDirty}
|
||||
sx={submitButtonSx}
|
||||
>
|
||||
اعمال فیلتر
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</form>
|
||||
</ScrollBox>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default FilterDrawer;
|
||||
|
||||
@@ -1,32 +1,32 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useMap } from "react-leaflet";
|
||||
|
||||
const FlyToUserLocation = ({ Component }) => {
|
||||
const [userPosition, setUserPosition] = useState(null);
|
||||
const map = useMap();
|
||||
|
||||
const handleLocateUser = () => {
|
||||
if (navigator.geolocation) {
|
||||
navigator.geolocation.getCurrentPosition(
|
||||
(position) => {
|
||||
setUserPosition([position.coords.latitude, position.coords.longitude]);
|
||||
},
|
||||
(error) => {
|
||||
console.error("Location error:", error);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
alert("مرورگر شما از موقعیتیاب پشتیبانی نمیکند.");
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (userPosition) {
|
||||
map.flyTo(userPosition, 16);
|
||||
}
|
||||
}, [userPosition, map]);
|
||||
|
||||
return <Component handleLocateUser={handleLocateUser} />;
|
||||
};
|
||||
|
||||
export default FlyToUserLocation;
|
||||
import { useEffect, useState } from "react";
|
||||
import { useMap } from "react-leaflet";
|
||||
|
||||
const FlyToUserLocation = ({ Component }) => {
|
||||
const [userPosition, setUserPosition] = useState(null);
|
||||
const map = useMap();
|
||||
|
||||
const handleLocateUser = () => {
|
||||
if (navigator.geolocation) {
|
||||
navigator.geolocation.getCurrentPosition(
|
||||
(position) => {
|
||||
setUserPosition([position.coords.latitude, position.coords.longitude]);
|
||||
},
|
||||
(error) => {
|
||||
console.error("Location error:", error);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
alert("مرورگر شما از موقعیتیاب پشتیبانی نمیکند.");
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (userPosition) {
|
||||
map.flyTo(userPosition, 16);
|
||||
}
|
||||
}, [userPosition, map]);
|
||||
|
||||
return <Component handleLocateUser={handleLocateUser} />;
|
||||
};
|
||||
|
||||
export default FlyToUserLocation;
|
||||
|
||||
@@ -1,35 +1,35 @@
|
||||
import { FormControl } from "@mui/material";
|
||||
import CustomDatePicker from "@/core/components/CustomDatePicker";
|
||||
import moment from "jalali-moment";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
const DateType = ({ itemInfo, defaultValues, setDefaultValues }) => {
|
||||
const [dateValue, setDateValue] = useState(null);
|
||||
const handleDateChange = (newValue) => {
|
||||
const date = new Date(newValue);
|
||||
const formattedDate = moment(date).locale("fa").format("YYYY/MM/DD");
|
||||
setDefaultValues((prev) => ({
|
||||
...prev,
|
||||
[itemInfo.id]: newValue ? formattedDate : "",
|
||||
}));
|
||||
setDateValue(moment(date).format("YYYY/MM/DD"));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (defaultValues[itemInfo.id]) {
|
||||
setDateValue(moment.from(defaultValues[itemInfo.id], "fa", "YYYY/MM/DD"));
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<FormControl fullWidth size="small" variant="outlined">
|
||||
<CustomDatePicker
|
||||
dateValue={dateValue ? new Date(dateValue) : null}
|
||||
setDateValue={handleDateChange}
|
||||
placeholder={itemInfo.name}
|
||||
/>
|
||||
</FormControl>
|
||||
);
|
||||
};
|
||||
|
||||
export default DateType;
|
||||
import { FormControl } from "@mui/material";
|
||||
import CustomDatePicker from "@/core/components/CustomDatePicker";
|
||||
import moment from "jalali-moment";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
const DateType = ({ itemInfo, defaultValues, setDefaultValues }) => {
|
||||
const [dateValue, setDateValue] = useState(null);
|
||||
const handleDateChange = (newValue) => {
|
||||
const date = new Date(newValue);
|
||||
const formattedDate = moment(date).locale("fa").format("YYYY/MM/DD");
|
||||
setDefaultValues((prev) => ({
|
||||
...prev,
|
||||
[itemInfo.id]: newValue ? formattedDate : "",
|
||||
}));
|
||||
setDateValue(moment(date).format("YYYY/MM/DD"));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (defaultValues[itemInfo.id]) {
|
||||
setDateValue(moment.from(defaultValues[itemInfo.id], "fa", "YYYY/MM/DD"));
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<FormControl fullWidth size="small" variant="outlined">
|
||||
<CustomDatePicker
|
||||
dateValue={dateValue ? new Date(dateValue) : null}
|
||||
setDateValue={handleDateChange}
|
||||
placeholder={itemInfo.name}
|
||||
/>
|
||||
</FormControl>
|
||||
);
|
||||
};
|
||||
|
||||
export default DateType;
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
import { FormControl, InputLabel, OutlinedInput } from "@mui/material";
|
||||
import React from "react";
|
||||
|
||||
const InputType = ({ itemInfo, defaultValues, setDefaultValues }) => {
|
||||
const handleChange = (event) => {
|
||||
const { value } = event.target;
|
||||
setDefaultValues((prevValues) => ({
|
||||
...prevValues,
|
||||
[itemInfo.id]: value,
|
||||
}));
|
||||
};
|
||||
const label = itemInfo.unit ? `${itemInfo.name} (${itemInfo.unit})` : itemInfo.name;
|
||||
|
||||
return (
|
||||
<FormControl size="small" fullWidth variant="outlined">
|
||||
<InputLabel htmlFor={itemInfo.id}>{label}</InputLabel>
|
||||
<OutlinedInput
|
||||
id={itemInfo.id}
|
||||
label={label}
|
||||
autoComplete="off"
|
||||
size="small"
|
||||
fullWidth
|
||||
type="text"
|
||||
onChange={handleChange}
|
||||
value={defaultValues[itemInfo.id] || ""}
|
||||
/>
|
||||
</FormControl>
|
||||
);
|
||||
};
|
||||
|
||||
export default InputType;
|
||||
import { FormControl, InputLabel, OutlinedInput } from "@mui/material";
|
||||
import React from "react";
|
||||
|
||||
const InputType = ({ itemInfo, defaultValues, setDefaultValues }) => {
|
||||
const handleChange = (event) => {
|
||||
const { value } = event.target;
|
||||
setDefaultValues((prevValues) => ({
|
||||
...prevValues,
|
||||
[itemInfo.id]: value,
|
||||
}));
|
||||
};
|
||||
const label = itemInfo.unit ? `${itemInfo.name} (${itemInfo.unit})` : itemInfo.name;
|
||||
|
||||
return (
|
||||
<FormControl size="small" fullWidth variant="outlined">
|
||||
<InputLabel htmlFor={itemInfo.id}>{label}</InputLabel>
|
||||
<OutlinedInput
|
||||
id={itemInfo.id}
|
||||
label={label}
|
||||
autoComplete="off"
|
||||
size="small"
|
||||
fullWidth
|
||||
type="text"
|
||||
onChange={handleChange}
|
||||
value={defaultValues[itemInfo.id] || ""}
|
||||
/>
|
||||
</FormControl>
|
||||
);
|
||||
};
|
||||
|
||||
export default InputType;
|
||||
|
||||
@@ -1,34 +1,34 @@
|
||||
import { FormControl, InputLabel, MenuItem, Select } from "@mui/material";
|
||||
import React from "react";
|
||||
|
||||
const SelectType = ({ itemInfo, defaultValues, setDefaultValues }) => {
|
||||
const handleChange = (event) => {
|
||||
const newValue = event.target.value;
|
||||
setDefaultValues((prev) => ({
|
||||
...prev,
|
||||
[itemInfo.id]: newValue,
|
||||
}));
|
||||
};
|
||||
const label = itemInfo.unit ? `${itemInfo.name} (${itemInfo.unit})` : itemInfo.name;
|
||||
return (
|
||||
<FormControl size="small" fullWidth variant="outlined">
|
||||
<InputLabel id={itemInfo.id}>{label}</InputLabel>
|
||||
<Select
|
||||
labelId={itemInfo.id}
|
||||
id={itemInfo.id}
|
||||
value={defaultValues[itemInfo.id] || ""}
|
||||
label={label}
|
||||
onChange={handleChange}
|
||||
>
|
||||
<MenuItem value="">{itemInfo.name}</MenuItem>
|
||||
{itemInfo.option?.map((item, index) => (
|
||||
<MenuItem key={index} value={item}>
|
||||
{item}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
);
|
||||
};
|
||||
|
||||
export default SelectType;
|
||||
import { FormControl, InputLabel, MenuItem, Select } from "@mui/material";
|
||||
import React from "react";
|
||||
|
||||
const SelectType = ({ itemInfo, defaultValues, setDefaultValues }) => {
|
||||
const handleChange = (event) => {
|
||||
const newValue = event.target.value;
|
||||
setDefaultValues((prev) => ({
|
||||
...prev,
|
||||
[itemInfo.id]: newValue,
|
||||
}));
|
||||
};
|
||||
const label = itemInfo.unit ? `${itemInfo.name} (${itemInfo.unit})` : itemInfo.name;
|
||||
return (
|
||||
<FormControl size="small" fullWidth variant="outlined">
|
||||
<InputLabel id={itemInfo.id}>{label}</InputLabel>
|
||||
<Select
|
||||
labelId={itemInfo.id}
|
||||
id={itemInfo.id}
|
||||
value={defaultValues[itemInfo.id] || ""}
|
||||
label={label}
|
||||
onChange={handleChange}
|
||||
>
|
||||
<MenuItem value="">{itemInfo.name}</MenuItem>
|
||||
{itemInfo.option?.map((item, index) => (
|
||||
<MenuItem key={index} value={item}>
|
||||
{item}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
);
|
||||
};
|
||||
|
||||
export default SelectType;
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
import InputType from "./InputType";
|
||||
import SelectType from "./SelectType";
|
||||
import DateType from "./DateType";
|
||||
import { Grid } from "@mui/material";
|
||||
|
||||
const componentMap = {
|
||||
input: InputType,
|
||||
select: SelectType,
|
||||
date: DateType,
|
||||
};
|
||||
|
||||
const FormMaker = ({ formInfo, defaultValues, setDefaultValues }) => {
|
||||
return (
|
||||
<Grid container spacing={1}>
|
||||
{formInfo?.map((itemInfo) => {
|
||||
const Component = componentMap[itemInfo.type] || null;
|
||||
return (
|
||||
<Grid item xs={12} sm={6} md={4} key={itemInfo.id}>
|
||||
{Component ? (
|
||||
<Component
|
||||
itemInfo={itemInfo}
|
||||
defaultValues={defaultValues}
|
||||
setDefaultValues={setDefaultValues}
|
||||
/>
|
||||
) : null}
|
||||
</Grid>
|
||||
);
|
||||
})}
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export default FormMaker;
|
||||
import InputType from "./InputType";
|
||||
import SelectType from "./SelectType";
|
||||
import DateType from "./DateType";
|
||||
import { Grid } from "@mui/material";
|
||||
|
||||
const componentMap = {
|
||||
input: InputType,
|
||||
select: SelectType,
|
||||
date: DateType,
|
||||
};
|
||||
|
||||
const FormMaker = ({ formInfo, defaultValues, setDefaultValues }) => {
|
||||
return (
|
||||
<Grid container spacing={1}>
|
||||
{formInfo?.map((itemInfo) => {
|
||||
const Component = componentMap[itemInfo.type] || null;
|
||||
return (
|
||||
<Grid item xs={12} sm={6} md={4} key={itemInfo.id}>
|
||||
{Component ? (
|
||||
<Component
|
||||
itemInfo={itemInfo}
|
||||
defaultValues={defaultValues}
|
||||
setDefaultValues={setDefaultValues}
|
||||
/>
|
||||
) : null}
|
||||
</Grid>
|
||||
);
|
||||
})}
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export default FormMaker;
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
import { FormControl, FormHelperText, InputLabel, MenuItem, Select } from "@mui/material";
|
||||
|
||||
const HourSelect = ({ label, error, name, value, onChange }) => {
|
||||
return (
|
||||
<FormControl fullWidth size="small" error={!!error}>
|
||||
<InputLabel>{label}</InputLabel>
|
||||
<Select
|
||||
label={label}
|
||||
name={name}
|
||||
size="small"
|
||||
value={value}
|
||||
onChange={(e) => {
|
||||
onChange(e.target.value);
|
||||
}}
|
||||
>
|
||||
{Array.from({ length: 24 }, (_, i) => (
|
||||
<MenuItem key={i} value={i}>
|
||||
{`${i.toString().padStart(2, "0")}:00`}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
<FormHelperText>{error?.message}</FormHelperText>
|
||||
</FormControl>
|
||||
);
|
||||
};
|
||||
export default HourSelect;
|
||||
import { FormControl, FormHelperText, InputLabel, MenuItem, Select } from "@mui/material";
|
||||
|
||||
const HourSelect = ({ label, error, name, value, onChange }) => {
|
||||
return (
|
||||
<FormControl fullWidth size="small" error={!!error}>
|
||||
<InputLabel>{label}</InputLabel>
|
||||
<Select
|
||||
label={label}
|
||||
name={name}
|
||||
size="small"
|
||||
value={value}
|
||||
onChange={(e) => {
|
||||
onChange(e.target.value);
|
||||
}}
|
||||
>
|
||||
{Array.from({ length: 24 }, (_, i) => (
|
||||
<MenuItem key={i} value={i}>
|
||||
{`${i.toString().padStart(2, "0")}:00`}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
<FormHelperText>{error?.message}</FormHelperText>
|
||||
</FormControl>
|
||||
);
|
||||
};
|
||||
export default HourSelect;
|
||||
|
||||
@@ -1,61 +1,61 @@
|
||||
"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";
|
||||
|
||||
const LoadingImage = styled(Box)({
|
||||
"@keyframes load": {
|
||||
"0%": {
|
||||
transform: "scale(1)",
|
||||
},
|
||||
"50%": {
|
||||
transform: "scale(.5)",
|
||||
},
|
||||
"100%": {
|
||||
transform: "scale(1)",
|
||||
},
|
||||
},
|
||||
animation: "load 2s infinite",
|
||||
});
|
||||
|
||||
const LoadingHardPage = ({
|
||||
children,
|
||||
loading,
|
||||
authState,
|
||||
sx = {},
|
||||
icon = null,
|
||||
width = 200,
|
||||
height = 200,
|
||||
label = "",
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<>
|
||||
<Backdrop sx={{ bgcolor: "#fff", zIndex: (theme) => theme.zIndex.drawer + 1, ...sx }} open={loading}>
|
||||
<Stack alignItems={"center"} spacing={2}>
|
||||
{authState ? (
|
||||
<Box>
|
||||
{icon ? (
|
||||
<Box sx={{ color: "primary.main", width: width, height: height }}>{icon}</Box>
|
||||
) : (
|
||||
<SvgError color={theme.palette.error.main} width={width} height={height} />
|
||||
)}
|
||||
</Box>
|
||||
) : (
|
||||
<LoadingImage>
|
||||
{icon ? (
|
||||
<Box sx={{ color: "primary.main", width: width, height: height }}>{icon}</Box>
|
||||
) : (
|
||||
<SvgLoading width={width} height={height} />
|
||||
)}
|
||||
</LoadingImage>
|
||||
)}
|
||||
<Stack sx={{ color: authState ? "error.main" : "primary.main" }}>{label}</Stack>
|
||||
</Stack>
|
||||
</Backdrop>
|
||||
{children}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoadingHardPage;
|
||||
"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";
|
||||
|
||||
const LoadingImage = styled(Box)({
|
||||
"@keyframes load": {
|
||||
"0%": {
|
||||
transform: "scale(1)",
|
||||
},
|
||||
"50%": {
|
||||
transform: "scale(.5)",
|
||||
},
|
||||
"100%": {
|
||||
transform: "scale(1)",
|
||||
},
|
||||
},
|
||||
animation: "load 2s infinite",
|
||||
});
|
||||
|
||||
const LoadingHardPage = ({
|
||||
children,
|
||||
loading,
|
||||
authState,
|
||||
sx = {},
|
||||
icon = null,
|
||||
width = 200,
|
||||
height = 200,
|
||||
label = "",
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<>
|
||||
<Backdrop sx={{ bgcolor: "#fff", zIndex: (theme) => theme.zIndex.drawer + 1, ...sx }} open={loading}>
|
||||
<Stack alignItems={"center"} spacing={2}>
|
||||
{authState ? (
|
||||
<Box>
|
||||
{icon ? (
|
||||
<Box sx={{ color: "primary.main", width: width, height: height }}>{icon}</Box>
|
||||
) : (
|
||||
<SvgError color={theme.palette.error.main} width={width} height={height} />
|
||||
)}
|
||||
</Box>
|
||||
) : (
|
||||
<LoadingImage>
|
||||
{icon ? (
|
||||
<Box sx={{ color: "primary.main", width: width, height: height }}>{icon}</Box>
|
||||
) : (
|
||||
<SvgLoading width={width} height={height} />
|
||||
)}
|
||||
</LoadingImage>
|
||||
)}
|
||||
<Stack sx={{ color: authState ? "error.main" : "primary.main" }}>{label}</Stack>
|
||||
</Stack>
|
||||
</Backdrop>
|
||||
{children}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoadingHardPage;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { TextField, styled } from "@mui/material";
|
||||
|
||||
const LtrTextField = styled(TextField)`
|
||||
.MuiInputBase-input {
|
||||
/* @noflip */
|
||||
direction: ltr;
|
||||
}
|
||||
`;
|
||||
|
||||
export default LtrTextField;
|
||||
import { TextField, styled } from "@mui/material";
|
||||
|
||||
const LtrTextField = styled(TextField)`
|
||||
.MuiInputBase-input {
|
||||
/* @noflip */
|
||||
direction: ltr;
|
||||
}
|
||||
`;
|
||||
|
||||
export default LtrTextField;
|
||||
|
||||
@@ -1,181 +1,181 @@
|
||||
import HereIcon from "@/assets/images/examine_marker_active.png";
|
||||
import MapLoading from "@/core/components/MapLayer/Loading";
|
||||
import { MyLocation } from "@mui/icons-material";
|
||||
import { Alert, Box, Button, Stack, Typography, Zoom } from "@mui/material";
|
||||
import L from "leaflet";
|
||||
import "leaflet/dist/leaflet.css";
|
||||
import dynamic from "next/dynamic";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { Marker, useMapEvents } from "react-leaflet";
|
||||
import FlyToUserLocation from "./FlyToLocation";
|
||||
|
||||
const MapLayer = dynamic(() => import("@/core/components/MapLayer"), {
|
||||
loading: () => <MapLoading />,
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
const createCustomIcon = (size, iconUrl, labelText, color) => {
|
||||
if (labelText) {
|
||||
return L.divIcon({
|
||||
className: "custom-marker", // Apply your custom CSS class
|
||||
html: `
|
||||
<div style="position: relative; text-align: center; width: 50px;">
|
||||
<div style="background-color: ${color}; color: white; border-radius: 20px; padding: 5px;">
|
||||
<span style="font-family: 'IRANSansFaNum', sans-serif;">${labelText}</span>
|
||||
</div>
|
||||
<div style="width: 5px; height: 20px; background: ${color}; margin: auto;border-bottom-left-radius: 50%; border-bottom-right-radius: 50%;"></div>
|
||||
</div>`,
|
||||
iconSize: [50, 50],
|
||||
iconAnchor: [25, 50],
|
||||
});
|
||||
}
|
||||
|
||||
return L.icon({
|
||||
iconUrl: iconUrl,
|
||||
iconSize: size,
|
||||
iconAnchor: [size[0] / 2, size[1]],
|
||||
popupAnchor: [0, -size[1]],
|
||||
});
|
||||
};
|
||||
const MAX_ZOOM_FOR_MARKER = 13;
|
||||
|
||||
const MapInteraction = ({ setValue, startLat, startLng, title }) => {
|
||||
const [isMarkerLocked, setIsMarkerLocked] = useState(!!(startLat && startLng));
|
||||
const [enableSend, setEnableSend] = useState(false);
|
||||
const [startIconColor, setStartIconColor] = useState(startLat ? "#1CAC66" : "#003d4f");
|
||||
const [markerPosition, setMarkerPosition] = useState(
|
||||
startLat && startLng ? { lat: startLat, lng: startLng } : null
|
||||
);
|
||||
const markerRef = useRef();
|
||||
|
||||
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());
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (startLat && startLng) {
|
||||
const position = { lat: startLat, lng: startLng };
|
||||
map.setView(position, 15);
|
||||
}
|
||||
}, [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() });
|
||||
setMarkerPosition({ lat: center.lat, lng: center.lng }); // بهروزرسانی موقعیت مارکر
|
||||
setIsMarkerLocked(true);
|
||||
setStartIconColor("#1CAC66");
|
||||
}
|
||||
};
|
||||
|
||||
const handleUnlockMarker = () => {
|
||||
setValue("start_point", null); // حذف مقدار قبلی
|
||||
setIsMarkerLocked(false); // باز کردن قفل مارکر
|
||||
setMarkerPosition(null); // تنظیم مارکر به مرکز نقشه
|
||||
setStartIconColor("#003d4f");
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Marker
|
||||
position={markerPosition || map.getCenter()} // استفاده از موقعیت
|
||||
ref={markerRef}
|
||||
icon={createCustomIcon([35, 35], HereIcon.src, "اینجا", startIconColor)}
|
||||
eventHandlers={{
|
||||
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"}>
|
||||
{title}
|
||||
</Alert>
|
||||
</Zoom>
|
||||
</Stack>
|
||||
<Stack
|
||||
direction={"row"}
|
||||
justifyContent={"space-between"}
|
||||
sx={{
|
||||
zIndex: "1000",
|
||||
width: "100%",
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
}}
|
||||
>
|
||||
<Zoom in={isMarkerLocked}>
|
||||
<Button variant="contained" size="small" color="warning" sx={{ m: 1 }} onClick={handleUnlockMarker}>
|
||||
تغییر مکان
|
||||
</Button>
|
||||
</Zoom>
|
||||
<FlyToUserLocation
|
||||
Component={(props) => (
|
||||
<Button
|
||||
sx={{ m: 1 }}
|
||||
variant="contained"
|
||||
size="small"
|
||||
startIcon={<MyLocation />}
|
||||
onClick={props.handleLocateUser}
|
||||
>
|
||||
موقعیت من
|
||||
</Button>
|
||||
)}
|
||||
/>
|
||||
</Stack>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const MapInfoOneMarker = ({
|
||||
setValue,
|
||||
errors,
|
||||
StartPoint = null,
|
||||
title = "برای ثبت محل فعالیت، لطفاً نقشه را بیشتر زوم کنید.",
|
||||
}) => {
|
||||
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}
|
||||
title={title}
|
||||
/>
|
||||
</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;
|
||||
import HereIcon from "@/assets/images/examine_marker_active.png";
|
||||
import MapLoading from "@/core/components/MapLayer/Loading";
|
||||
import { MyLocation } from "@mui/icons-material";
|
||||
import { Alert, Box, Button, Stack, Typography, Zoom } from "@mui/material";
|
||||
import L from "leaflet";
|
||||
import "leaflet/dist/leaflet.css";
|
||||
import dynamic from "next/dynamic";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { Marker, useMapEvents } from "react-leaflet";
|
||||
import FlyToUserLocation from "./FlyToLocation";
|
||||
|
||||
const MapLayer = dynamic(() => import("@/core/components/MapLayer"), {
|
||||
loading: () => <MapLoading />,
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
const createCustomIcon = (size, iconUrl, labelText, color) => {
|
||||
if (labelText) {
|
||||
return L.divIcon({
|
||||
className: "custom-marker", // Apply your custom CSS class
|
||||
html: `
|
||||
<div style="position: relative; text-align: center; width: 50px;">
|
||||
<div style="background-color: ${color}; color: white; border-radius: 20px; padding: 5px;">
|
||||
<span style="font-family: 'IRANSansFaNum', sans-serif;">${labelText}</span>
|
||||
</div>
|
||||
<div style="width: 5px; height: 20px; background: ${color}; margin: auto;border-bottom-left-radius: 50%; border-bottom-right-radius: 50%;"></div>
|
||||
</div>`,
|
||||
iconSize: [50, 50],
|
||||
iconAnchor: [25, 50],
|
||||
});
|
||||
}
|
||||
|
||||
return L.icon({
|
||||
iconUrl: iconUrl,
|
||||
iconSize: size,
|
||||
iconAnchor: [size[0] / 2, size[1]],
|
||||
popupAnchor: [0, -size[1]],
|
||||
});
|
||||
};
|
||||
const MAX_ZOOM_FOR_MARKER = 13;
|
||||
|
||||
const MapInteraction = ({ setValue, startLat, startLng, title }) => {
|
||||
const [isMarkerLocked, setIsMarkerLocked] = useState(!!(startLat && startLng));
|
||||
const [enableSend, setEnableSend] = useState(false);
|
||||
const [startIconColor, setStartIconColor] = useState(startLat ? "#1CAC66" : "#003d4f");
|
||||
const [markerPosition, setMarkerPosition] = useState(
|
||||
startLat && startLng ? { lat: startLat, lng: startLng } : null
|
||||
);
|
||||
const markerRef = useRef();
|
||||
|
||||
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());
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (startLat && startLng) {
|
||||
const position = { lat: startLat, lng: startLng };
|
||||
map.setView(position, 15);
|
||||
}
|
||||
}, [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() });
|
||||
setMarkerPosition({ lat: center.lat, lng: center.lng }); // بهروزرسانی موقعیت مارکر
|
||||
setIsMarkerLocked(true);
|
||||
setStartIconColor("#1CAC66");
|
||||
}
|
||||
};
|
||||
|
||||
const handleUnlockMarker = () => {
|
||||
setValue("start_point", null); // حذف مقدار قبلی
|
||||
setIsMarkerLocked(false); // باز کردن قفل مارکر
|
||||
setMarkerPosition(null); // تنظیم مارکر به مرکز نقشه
|
||||
setStartIconColor("#003d4f");
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Marker
|
||||
position={markerPosition || map.getCenter()} // استفاده از موقعیت
|
||||
ref={markerRef}
|
||||
icon={createCustomIcon([35, 35], HereIcon.src, "اینجا", startIconColor)}
|
||||
eventHandlers={{
|
||||
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"}>
|
||||
{title}
|
||||
</Alert>
|
||||
</Zoom>
|
||||
</Stack>
|
||||
<Stack
|
||||
direction={"row"}
|
||||
justifyContent={"space-between"}
|
||||
sx={{
|
||||
zIndex: "1000",
|
||||
width: "100%",
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
}}
|
||||
>
|
||||
<Zoom in={isMarkerLocked}>
|
||||
<Button variant="contained" size="small" color="warning" sx={{ m: 1 }} onClick={handleUnlockMarker}>
|
||||
تغییر مکان
|
||||
</Button>
|
||||
</Zoom>
|
||||
<FlyToUserLocation
|
||||
Component={(props) => (
|
||||
<Button
|
||||
sx={{ m: 1 }}
|
||||
variant="contained"
|
||||
size="small"
|
||||
startIcon={<MyLocation />}
|
||||
onClick={props.handleLocateUser}
|
||||
>
|
||||
موقعیت من
|
||||
</Button>
|
||||
)}
|
||||
/>
|
||||
</Stack>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const MapInfoOneMarker = ({
|
||||
setValue,
|
||||
errors,
|
||||
StartPoint = null,
|
||||
title = "برای ثبت محل فعالیت، لطفاً نقشه را بیشتر زوم کنید.",
|
||||
}) => {
|
||||
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}
|
||||
title={title}
|
||||
/>
|
||||
</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;
|
||||
|
||||
@@ -1,267 +1,267 @@
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { Marker, useMapEvents } from "react-leaflet";
|
||||
import "leaflet/dist/leaflet.css";
|
||||
import L from "leaflet";
|
||||
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";
|
||||
import StartIcon from "@/assets/images/examine_marker.png";
|
||||
import FlyToUserLocation from "./FlyToLocation";
|
||||
import { MyLocation } from "@mui/icons-material";
|
||||
|
||||
const MapLayer = dynamic(() => import("@/core/components/MapLayer"), {
|
||||
loading: () => <MapLoading />,
|
||||
ssr: false,
|
||||
});
|
||||
const createCustomIcon = (size, iconUrl, labelText, color) => {
|
||||
if (labelText) {
|
||||
return L.divIcon({
|
||||
className: "custom-marker", // Apply your custom CSS class
|
||||
html: `
|
||||
<div style="position: relative; text-align: center; width: 50px;">
|
||||
<div style="background-color: ${color}; color: white; border-radius: 20px; padding: 5px;">
|
||||
<span style="font-family: 'IRANSansFaNum', sans-serif;">${labelText}</span>
|
||||
</div>
|
||||
<div style="width: 5px; height: 20px; background: ${color}; margin: auto;border-bottom-left-radius: 50%; border-bottom-right-radius: 50%;"></div>
|
||||
</div>`,
|
||||
iconSize: [50, 50],
|
||||
iconAnchor: [25, 50],
|
||||
});
|
||||
}
|
||||
return L.icon({
|
||||
iconUrl: iconUrl,
|
||||
iconSize: size,
|
||||
iconAnchor: [size[0] / 2, size[1]],
|
||||
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 [startIconColor, setStartIconColor] = useState(startLat ? "#1CAC66" : "#003d4f");
|
||||
const [endIconColor, setEndIconColor] = useState(endLat ? "#D13131" : "#003d4f");
|
||||
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) {
|
||||
endRef.current.setLatLng(e.target.getCenter());
|
||||
}
|
||||
},
|
||||
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) {
|
||||
endRef.current.setLatLng(e.target.getCenter());
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (startLat && endLng) {
|
||||
map.fitBounds(
|
||||
[
|
||||
{ lat: startLat, lng: startLng },
|
||||
{ lat: endLat, lng: endLng },
|
||||
],
|
||||
{ paddingTopLeft: [64, 64], paddingBottomRight: [64, 64] }
|
||||
);
|
||||
}
|
||||
}, [startLat, map, endLng]);
|
||||
|
||||
const handleUnlockStart = () => {
|
||||
setIsStartLocked(false);
|
||||
setStartPosition(null);
|
||||
setValue("start_point", null);
|
||||
setStartIconColor("#003d4f");
|
||||
};
|
||||
|
||||
const handleUnlockEnd = () => {
|
||||
setIsEndLocked(false);
|
||||
setIsStartLocked(false);
|
||||
setEndPosition(null);
|
||||
setStartPosition(null);
|
||||
setValue("end_point", "");
|
||||
setValue("start_point", null);
|
||||
setStartIconColor("#003d4f");
|
||||
setEndIconColor("#003d4f");
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Marker
|
||||
position={startPosition || map.getCenter()}
|
||||
ref={startRef}
|
||||
icon={createCustomIcon([35, 35], StartIcon.src, "شروع", startIconColor)}
|
||||
eventHandlers={{
|
||||
click: () => {
|
||||
if (!enableSend) return;
|
||||
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);
|
||||
setStartIconColor("#1CAC66");
|
||||
map.panBy([25, 25]);
|
||||
}
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<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={{
|
||||
position: "absolute",
|
||||
bottom: 8,
|
||||
left: 8,
|
||||
zIndex: 1000,
|
||||
}}
|
||||
>
|
||||
<Button variant={"contained"} color="warning" size="small" onClick={handleUnlockStart}>
|
||||
ویرایش نقطه شروع
|
||||
</Button>
|
||||
</Box>
|
||||
)}
|
||||
{isStartLocked && (
|
||||
<>
|
||||
<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, "پایان", endIconColor)}
|
||||
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);
|
||||
setEndIconColor("#D13131");
|
||||
map.fitBounds([startPosition, map.getCenter()], {
|
||||
paddingTopLeft: [64, 64],
|
||||
paddingBottomRight: [64, 64],
|
||||
});
|
||||
}
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{isEndLocked && (
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
bottom: 8,
|
||||
left: 8,
|
||||
zIndex: 1000,
|
||||
}}
|
||||
>
|
||||
<Button variant={"contained"} color="warning" size="small" onClick={handleUnlockEnd}>
|
||||
ویرایش نقاط
|
||||
</Button>
|
||||
</Box>
|
||||
)}
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
zIndex: 1000,
|
||||
}}
|
||||
>
|
||||
<FlyToUserLocation
|
||||
Component={(props) => (
|
||||
<Button
|
||||
sx={{ m: 1 }}
|
||||
variant="contained"
|
||||
size="small"
|
||||
startIcon={<MyLocation />}
|
||||
onClick={props.handleLocateUser}
|
||||
>
|
||||
موقعیت من
|
||||
</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;
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { Marker, useMapEvents } from "react-leaflet";
|
||||
import "leaflet/dist/leaflet.css";
|
||||
import L from "leaflet";
|
||||
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";
|
||||
import StartIcon from "@/assets/images/examine_marker.png";
|
||||
import FlyToUserLocation from "./FlyToLocation";
|
||||
import { MyLocation } from "@mui/icons-material";
|
||||
|
||||
const MapLayer = dynamic(() => import("@/core/components/MapLayer"), {
|
||||
loading: () => <MapLoading />,
|
||||
ssr: false,
|
||||
});
|
||||
const createCustomIcon = (size, iconUrl, labelText, color) => {
|
||||
if (labelText) {
|
||||
return L.divIcon({
|
||||
className: "custom-marker", // Apply your custom CSS class
|
||||
html: `
|
||||
<div style="position: relative; text-align: center; width: 50px;">
|
||||
<div style="background-color: ${color}; color: white; border-radius: 20px; padding: 5px;">
|
||||
<span style="font-family: 'IRANSansFaNum', sans-serif;">${labelText}</span>
|
||||
</div>
|
||||
<div style="width: 5px; height: 20px; background: ${color}; margin: auto;border-bottom-left-radius: 50%; border-bottom-right-radius: 50%;"></div>
|
||||
</div>`,
|
||||
iconSize: [50, 50],
|
||||
iconAnchor: [25, 50],
|
||||
});
|
||||
}
|
||||
return L.icon({
|
||||
iconUrl: iconUrl,
|
||||
iconSize: size,
|
||||
iconAnchor: [size[0] / 2, size[1]],
|
||||
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 [startIconColor, setStartIconColor] = useState(startLat ? "#1CAC66" : "#003d4f");
|
||||
const [endIconColor, setEndIconColor] = useState(endLat ? "#D13131" : "#003d4f");
|
||||
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) {
|
||||
endRef.current.setLatLng(e.target.getCenter());
|
||||
}
|
||||
},
|
||||
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) {
|
||||
endRef.current.setLatLng(e.target.getCenter());
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (startLat && endLng) {
|
||||
map.fitBounds(
|
||||
[
|
||||
{ lat: startLat, lng: startLng },
|
||||
{ lat: endLat, lng: endLng },
|
||||
],
|
||||
{ paddingTopLeft: [64, 64], paddingBottomRight: [64, 64] }
|
||||
);
|
||||
}
|
||||
}, [startLat, map, endLng]);
|
||||
|
||||
const handleUnlockStart = () => {
|
||||
setIsStartLocked(false);
|
||||
setStartPosition(null);
|
||||
setValue("start_point", null);
|
||||
setStartIconColor("#003d4f");
|
||||
};
|
||||
|
||||
const handleUnlockEnd = () => {
|
||||
setIsEndLocked(false);
|
||||
setIsStartLocked(false);
|
||||
setEndPosition(null);
|
||||
setStartPosition(null);
|
||||
setValue("end_point", "");
|
||||
setValue("start_point", null);
|
||||
setStartIconColor("#003d4f");
|
||||
setEndIconColor("#003d4f");
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Marker
|
||||
position={startPosition || map.getCenter()}
|
||||
ref={startRef}
|
||||
icon={createCustomIcon([35, 35], StartIcon.src, "شروع", startIconColor)}
|
||||
eventHandlers={{
|
||||
click: () => {
|
||||
if (!enableSend) return;
|
||||
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);
|
||||
setStartIconColor("#1CAC66");
|
||||
map.panBy([25, 25]);
|
||||
}
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<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={{
|
||||
position: "absolute",
|
||||
bottom: 8,
|
||||
left: 8,
|
||||
zIndex: 1000,
|
||||
}}
|
||||
>
|
||||
<Button variant={"contained"} color="warning" size="small" onClick={handleUnlockStart}>
|
||||
ویرایش نقطه شروع
|
||||
</Button>
|
||||
</Box>
|
||||
)}
|
||||
{isStartLocked && (
|
||||
<>
|
||||
<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, "پایان", endIconColor)}
|
||||
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);
|
||||
setEndIconColor("#D13131");
|
||||
map.fitBounds([startPosition, map.getCenter()], {
|
||||
paddingTopLeft: [64, 64],
|
||||
paddingBottomRight: [64, 64],
|
||||
});
|
||||
}
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{isEndLocked && (
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
bottom: 8,
|
||||
left: 8,
|
||||
zIndex: 1000,
|
||||
}}
|
||||
>
|
||||
<Button variant={"contained"} color="warning" size="small" onClick={handleUnlockEnd}>
|
||||
ویرایش نقاط
|
||||
</Button>
|
||||
</Box>
|
||||
)}
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
zIndex: 1000,
|
||||
}}
|
||||
>
|
||||
<FlyToUserLocation
|
||||
Component={(props) => (
|
||||
<Button
|
||||
sx={{ m: 1 }}
|
||||
variant="contained"
|
||||
size="small"
|
||||
startIcon={<MyLocation />}
|
||||
onClick={props.handleLocateUser}
|
||||
>
|
||||
موقعیت من
|
||||
</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;
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { Box, LinearProgress, Stack } from "@mui/material";
|
||||
|
||||
function MapLoading() {
|
||||
return (
|
||||
<Stack sx={{ height: "100%" }} alignItems={"center"} justifyContent={"center"}>
|
||||
<Box sx={{ width: 200 }}>
|
||||
<LinearProgress />
|
||||
</Box>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
export default MapLoading;
|
||||
import { Box, LinearProgress, Stack } from "@mui/material";
|
||||
|
||||
function MapLoading() {
|
||||
return (
|
||||
<Stack sx={{ height: "100%" }} alignItems={"center"} justifyContent={"center"}>
|
||||
<Box sx={{ width: 200 }}>
|
||||
<LinearProgress />
|
||||
</Box>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
export default MapLoading;
|
||||
|
||||
@@ -1,43 +1,43 @@
|
||||
"use client";
|
||||
import { MapContainer, TileLayer, useMap } from "react-leaflet";
|
||||
|
||||
export const IRAN_CENTER = L.latLng(32.4279, 53.688);
|
||||
|
||||
const MapOptions = ({ options }) => {
|
||||
const map = useMap();
|
||||
if (!options) return;
|
||||
|
||||
if (options.mapDrag) {
|
||||
map.dragging.enable();
|
||||
map.scrollWheelZoom.enable();
|
||||
map.touchZoom.enable();
|
||||
map.doubleClickZoom.enable();
|
||||
} else {
|
||||
map.dragging.disable();
|
||||
map.scrollWheelZoom.disable();
|
||||
map.touchZoom.disable();
|
||||
map.doubleClickZoom.disable();
|
||||
}
|
||||
};
|
||||
|
||||
function MapLayer({ children, style, otherLayers, options }) {
|
||||
return (
|
||||
<>
|
||||
<MapContainer
|
||||
style={{ width: "100%", height: "100%", ...style }}
|
||||
center={IRAN_CENTER}
|
||||
zoom={6}
|
||||
zoomControl={false}
|
||||
attributionControl={false}
|
||||
{...options}
|
||||
>
|
||||
<MapOptions options={options} />
|
||||
<TileLayer url={`${process.env.NEXT_PUBLIC_MAPTILE_ENDPOINT}/{z}/{x}/{y}.png`} />
|
||||
{children}
|
||||
</MapContainer>
|
||||
{otherLayers}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default MapLayer;
|
||||
"use client";
|
||||
import { MapContainer, TileLayer, useMap } from "react-leaflet";
|
||||
|
||||
export const IRAN_CENTER = L.latLng(32.4279, 53.688);
|
||||
|
||||
const MapOptions = ({ options }) => {
|
||||
const map = useMap();
|
||||
if (!options) return;
|
||||
|
||||
if (options.mapDrag) {
|
||||
map.dragging.enable();
|
||||
map.scrollWheelZoom.enable();
|
||||
map.touchZoom.enable();
|
||||
map.doubleClickZoom.enable();
|
||||
} else {
|
||||
map.dragging.disable();
|
||||
map.scrollWheelZoom.disable();
|
||||
map.touchZoom.disable();
|
||||
map.doubleClickZoom.disable();
|
||||
}
|
||||
};
|
||||
|
||||
function MapLayer({ children, style, otherLayers, options }) {
|
||||
return (
|
||||
<>
|
||||
<MapContainer
|
||||
style={{ width: "100%", height: "100%", ...style }}
|
||||
center={IRAN_CENTER}
|
||||
zoom={6}
|
||||
zoomControl={false}
|
||||
attributionControl={false}
|
||||
{...options}
|
||||
>
|
||||
<MapOptions options={options} />
|
||||
<TileLayer url={`${process.env.NEXT_PUBLIC_MAPTILE_ENDPOINT}/{z}/{x}/{y}.png`} />
|
||||
{children}
|
||||
</MapContainer>
|
||||
{otherLayers}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default MapLayer;
|
||||
|
||||
@@ -1,77 +1,77 @@
|
||||
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";
|
||||
|
||||
function MuiDatePicker({ value, setFieldValue, name, minDate, maxDate, helperText, placeholder, error, label }) {
|
||||
return (
|
||||
<>
|
||||
<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={label}
|
||||
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,
|
||||
InputLabelProps: {
|
||||
shrink: true,
|
||||
},
|
||||
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>
|
||||
</Box>
|
||||
{helperText && (
|
||||
<FormHelperText id="component-helper-text" sx={{ color: error ? "#d32f2f" : "unset", ml: 2 }}>
|
||||
{helperText}
|
||||
</FormHelperText>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default MuiDatePicker;
|
||||
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";
|
||||
|
||||
function MuiDatePicker({ value, setFieldValue, name, minDate, maxDate, helperText, placeholder, error, label }) {
|
||||
return (
|
||||
<>
|
||||
<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={label}
|
||||
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,
|
||||
InputLabelProps: {
|
||||
shrink: true,
|
||||
},
|
||||
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>
|
||||
</Box>
|
||||
{helperText && (
|
||||
<FormHelperText id="component-helper-text" sx={{ color: error ? "#d32f2f" : "unset", ml: 2 }}>
|
||||
{helperText}
|
||||
</FormHelperText>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default MuiDatePicker;
|
||||
|
||||
@@ -1,81 +1,84 @@
|
||||
import React from "react";
|
||||
import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider";
|
||||
import { MobileTimePicker } from "@mui/x-date-pickers/MobileTimePicker";
|
||||
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";
|
||||
|
||||
function MuiTimePicker({ value, setFieldValue, name, minTime, maxTime, helperText, placeholder, error, label }) {
|
||||
// Ensure value, minTime, maxTime are Date objects
|
||||
const parsedValue = value || null;
|
||||
const parsedMinTime = minTime || null;
|
||||
const parsedMaxTime = 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={label}
|
||||
ampm={false}
|
||||
timeSteps={{ hours: 1, minutes: 5 }}
|
||||
views={["hours", "minutes"]}
|
||||
name={name}
|
||||
onChange={(newValue) => {
|
||||
setFieldValue(name, newValue);
|
||||
}}
|
||||
minTime={parsedMinTime}
|
||||
maxTime={parsedMaxTime}
|
||||
slotProps={{
|
||||
textField: {
|
||||
placeholder: placeholder,
|
||||
size: "small",
|
||||
error: error,
|
||||
InputLabelProps: {
|
||||
shrink: true,
|
||||
},
|
||||
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>
|
||||
</Box>
|
||||
{helperText && (
|
||||
<FormHelperText id="component-helper-text" sx={{ color: error ? "#d32f2f" : "unset", ml: 2 }}>
|
||||
{helperText}
|
||||
</FormHelperText>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default MuiTimePicker;
|
||||
import ClearIcon from "@mui/icons-material/Clear";
|
||||
import { Box, FormHelperText, IconButton, InputAdornment } from "@mui/material";
|
||||
import { AdapterDateFnsJalali } from "@mui/x-date-pickers/AdapterDateFnsJalali";
|
||||
import { faIR } from "@mui/x-date-pickers/locales";
|
||||
import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider";
|
||||
import { MobileTimePicker } from "@mui/x-date-pickers/MobileTimePicker";
|
||||
import { parseISO } from "date-fns";
|
||||
|
||||
const parseIfString = (val) =>
|
||||
val instanceof Date ? val : typeof val === "string" && val !== "" ? parseISO(val) : null;
|
||||
|
||||
function MuiTimePicker({ value, setFieldValue, name, minTime, maxTime, helperText, placeholder, error, label }) {
|
||||
// Ensure value, minTime, maxTime are Date objects
|
||||
const parsedValue = parseIfString(value);
|
||||
const parsedMinTime = parseIfString(minTime);
|
||||
const parsedMaxTime = parseIfString(maxTime);
|
||||
|
||||
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={label}
|
||||
ampm={false}
|
||||
timeSteps={{ hours: 1, minutes: 5 }}
|
||||
views={["hours", "minutes"]}
|
||||
name={name}
|
||||
onChange={(newValue) => {
|
||||
setFieldValue(name, newValue);
|
||||
}}
|
||||
minTime={parsedMinTime}
|
||||
maxTime={parsedMaxTime}
|
||||
slotProps={{
|
||||
textField: {
|
||||
placeholder: placeholder,
|
||||
size: "small",
|
||||
error: error,
|
||||
InputLabelProps: {
|
||||
shrink: true,
|
||||
},
|
||||
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>
|
||||
</Box>
|
||||
{helperText && (
|
||||
<FormHelperText id="component-helper-text" sx={{ color: error ? "#d32f2f" : "unset", ml: 2 }}>
|
||||
{helperText}
|
||||
</FormHelperText>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default MuiTimePicker;
|
||||
|
||||
@@ -1,65 +1,65 @@
|
||||
"use client";
|
||||
|
||||
import SaveIcon from "@mui/icons-material/Save";
|
||||
import { Box, Button, Typography } from "@mui/material";
|
||||
import DownloadIcon from "@mui/icons-material/Download";
|
||||
import { toast } from "react-toastify";
|
||||
import useTableSetting from "@/lib/hooks/useTableSetting";
|
||||
import { flattenObjectOfObjects } from "@/core/utils/flattenObjectOfObjects";
|
||||
|
||||
function AskForKeepData({ filterData, sortData, hideData, user_id, page_name, table_name, columns }) {
|
||||
const { filterAction, sortAction, hideAction } = useTableSetting();
|
||||
|
||||
const filteredHideData = Object.fromEntries(Object.entries(hideData).filter(([key, value]) => value === false));
|
||||
|
||||
const flattenHideData = flattenObjectOfObjects(filteredHideData);
|
||||
|
||||
const onSaveFilter = () => {
|
||||
const filteredItems = Object.keys(filterData)
|
||||
.map((key) => {
|
||||
const value = filterData[key].value;
|
||||
if (
|
||||
value !== "" &&
|
||||
!(
|
||||
Array.isArray(value) &&
|
||||
(value.length === 0 || (value.length === 2 && value[0] === "" && value[1] === ""))
|
||||
)
|
||||
) {
|
||||
return filterData[key];
|
||||
}
|
||||
})
|
||||
.filter(Boolean);
|
||||
filterAction(user_id, page_name, table_name, filteredItems, columns);
|
||||
sortAction(user_id, page_name, table_name, sortData, columns);
|
||||
hideAction(user_id, page_name, table_name, flattenHideData, columns);
|
||||
toast.dismiss({ containerId: "datatable" });
|
||||
};
|
||||
|
||||
const handleDismiss = () => {
|
||||
toast.dismiss({ containerId: "datatable" });
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ width: "100%" }}>
|
||||
<Box sx={{ display: "flex", alignItems: "center", justifyContent: "space-between", mb: 1 }}>
|
||||
<Typography color="primary.main" variant="subtitle1" sx={{ fontWeight: "500" }}>
|
||||
ذخیره سازی تغییرات
|
||||
</Typography>
|
||||
<SaveIcon sx={{ color: "primary.main" }} />
|
||||
</Box>
|
||||
<Box sx={{ display: "flex", flexDirection: "column" }}>
|
||||
<Typography variant="caption">آیا مایل به ذخیره تغییر های اعمال شده برای دفعات بعد هستید؟</Typography>
|
||||
</Box>
|
||||
<Box sx={{ display: "flex", gap: 1, mt: 2 }}>
|
||||
<Button variant="contained" size="small" endIcon={<DownloadIcon />} onClick={onSaveFilter}>
|
||||
ذخیره
|
||||
</Button>
|
||||
<Button variant="outlined" size="small" onClick={handleDismiss}>
|
||||
رد کردن
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default AskForKeepData;
|
||||
"use client";
|
||||
|
||||
import SaveIcon from "@mui/icons-material/Save";
|
||||
import { Box, Button, Typography } from "@mui/material";
|
||||
import DownloadIcon from "@mui/icons-material/Download";
|
||||
import { toast } from "react-toastify";
|
||||
import useTableSetting from "@/lib/hooks/useTableSetting";
|
||||
import { flattenObjectOfObjects } from "@/core/utils/flattenObjectOfObjects";
|
||||
|
||||
function AskForKeepData({ filterData, sortData, hideData, user_id, page_name, table_name, columns }) {
|
||||
const { filterAction, sortAction, hideAction } = useTableSetting();
|
||||
|
||||
const filteredHideData = Object.fromEntries(Object.entries(hideData).filter(([key, value]) => value === false));
|
||||
|
||||
const flattenHideData = flattenObjectOfObjects(filteredHideData);
|
||||
|
||||
const onSaveFilter = () => {
|
||||
const filteredItems = Object.keys(filterData)
|
||||
.map((key) => {
|
||||
const value = filterData[key].value;
|
||||
if (
|
||||
value !== "" &&
|
||||
!(
|
||||
Array.isArray(value) &&
|
||||
(value.length === 0 || (value.length === 2 && value[0] === "" && value[1] === ""))
|
||||
)
|
||||
) {
|
||||
return filterData[key];
|
||||
}
|
||||
})
|
||||
.filter(Boolean);
|
||||
filterAction(user_id, page_name, table_name, filteredItems, columns);
|
||||
sortAction(user_id, page_name, table_name, sortData, columns);
|
||||
hideAction(user_id, page_name, table_name, flattenHideData, columns);
|
||||
toast.dismiss({ containerId: "datatable" });
|
||||
};
|
||||
|
||||
const handleDismiss = () => {
|
||||
toast.dismiss({ containerId: "datatable" });
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ width: "100%" }}>
|
||||
<Box sx={{ display: "flex", alignItems: "center", justifyContent: "space-between", mb: 1 }}>
|
||||
<Typography color="primary.main" variant="subtitle1" sx={{ fontWeight: "500" }}>
|
||||
ذخیره سازی تغییرات
|
||||
</Typography>
|
||||
<SaveIcon sx={{ color: "primary.main" }} />
|
||||
</Box>
|
||||
<Box sx={{ display: "flex", flexDirection: "column" }}>
|
||||
<Typography variant="caption">آیا مایل به ذخیره تغییر های اعمال شده برای دفعات بعد هستید؟</Typography>
|
||||
</Box>
|
||||
<Box sx={{ display: "flex", gap: 1, mt: 2 }}>
|
||||
<Button variant="contained" size="small" endIcon={<DownloadIcon />} onClick={onSaveFilter}>
|
||||
ذخیره
|
||||
</Button>
|
||||
<Button variant="outlined" size="small" onClick={handleDismiss}>
|
||||
رد کردن
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default AskForKeepData;
|
||||
|
||||
@@ -1,29 +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;
|
||||
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;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
"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;
|
||||
"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;
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
"use client";
|
||||
|
||||
import { Chip, Divider, Typography } from "@mui/material";
|
||||
|
||||
const PageTitle = ({ title }) => {
|
||||
return (
|
||||
<Divider>
|
||||
<Chip
|
||||
label={
|
||||
<Typography fontSize={14} fontWeight={500}>
|
||||
{title}
|
||||
</Typography>
|
||||
}
|
||||
/>
|
||||
</Divider>
|
||||
);
|
||||
};
|
||||
export default PageTitle;
|
||||
"use client";
|
||||
|
||||
import { Chip, Divider, Typography } from "@mui/material";
|
||||
|
||||
const PageTitle = ({ title }) => {
|
||||
return (
|
||||
<Divider>
|
||||
<Chip
|
||||
label={
|
||||
<Typography fontSize={14} fontWeight={500}>
|
||||
{title}
|
||||
</Typography>
|
||||
}
|
||||
/>
|
||||
</Divider>
|
||||
);
|
||||
};
|
||||
export default PageTitle;
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import usePersianInput from "@/lib/hooks/usePersianInput";
|
||||
import { TextField } from "@mui/material";
|
||||
import { useRef } from "react";
|
||||
|
||||
const PersianTextField = (props) => {
|
||||
const { onChange, ...rest } = props;
|
||||
const inputRef = useRef(null);
|
||||
|
||||
const handleChange = (value) => {
|
||||
onChange(value);
|
||||
};
|
||||
|
||||
usePersianInput(inputRef, handleChange);
|
||||
|
||||
return <TextField ref={inputRef} {...rest} />;
|
||||
};
|
||||
|
||||
export default PersianTextField;
|
||||
import usePersianInput from "@/lib/hooks/usePersianInput";
|
||||
import { TextField } from "@mui/material";
|
||||
import { useRef } from "react";
|
||||
|
||||
const PersianTextField = (props) => {
|
||||
const { onChange, ...rest } = props;
|
||||
const inputRef = useRef(null);
|
||||
|
||||
const handleChange = (value) => {
|
||||
onChange(value);
|
||||
};
|
||||
|
||||
usePersianInput(inputRef, handleChange);
|
||||
|
||||
return <TextField ref={inputRef} {...rest} />;
|
||||
};
|
||||
|
||||
export default PersianTextField;
|
||||
|
||||
@@ -1,197 +1,197 @@
|
||||
import { Box, Button, Drawer, FormHelperText, Stack, TextField } from "@mui/material";
|
||||
import AccessibleIcon from "@mui/icons-material/Accessible";
|
||||
import { Controller, useFormState, useWatch } from "react-hook-form";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
const plate_words = [
|
||||
{ id: 1, value: "الف", name: "الف" },
|
||||
{ id: 2, value: "ب", name: "ب" },
|
||||
{ id: 3, value: "پ", name: "پ" },
|
||||
{ id: 4, value: "ت", name: "ت" },
|
||||
{ id: 5, value: "ث", name: "ث" },
|
||||
{ id: 6, value: "ج", name: "ج" },
|
||||
{ id: 7, value: "د", name: "د" },
|
||||
{ id: 8, value: "ز", name: "ز" },
|
||||
{ id: 9, value: "س", name: "س" },
|
||||
{ id: 10, value: "ش", name: "ش" },
|
||||
{ id: 11, value: "ص", name: "ص" },
|
||||
{ id: 12, value: "ط", name: "ط" },
|
||||
{ id: 13, value: "ع", name: "ع" },
|
||||
{ id: 14, value: "ف", name: "ف" },
|
||||
{ id: 15, value: "ق", name: "ق" },
|
||||
{ id: 16, value: "ک", name: "گ" },
|
||||
{ id: 17, value: "ل", name: "ل" },
|
||||
{ id: 18, value: "م", name: "م" },
|
||||
{ id: 19, value: "ن", name: "ن" },
|
||||
{ id: 20, value: "و", name: "و" },
|
||||
{ id: 21, value: "ه", name: "ه" },
|
||||
{ id: 22, value: "ی", name: "ی" },
|
||||
{ id: 23, value: "*", name: <AccessibleIcon /> },
|
||||
];
|
||||
|
||||
const PlateNumber = ({ control }) => {
|
||||
const platePart2 = useWatch({ control, name: "plate_part2" });
|
||||
const [plateDrawer, setPlateDrawer] = useState(false);
|
||||
const { errors } = useFormState({ control });
|
||||
const plateErrors = ["plate_part1", "plate_part2", "plate_part3", "plate_part4"].some((part) => errors[part]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack
|
||||
direction={"row"}
|
||||
sx={{
|
||||
border: 1,
|
||||
borderColor: plateErrors ? "error.main" : "divider",
|
||||
overflow: "hidden",
|
||||
borderRadius: 1,
|
||||
}}
|
||||
>
|
||||
<Controller
|
||||
name="plate_part4"
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<TextField
|
||||
{...field}
|
||||
type="tel"
|
||||
inputProps={{
|
||||
inputMode: "number",
|
||||
min: 0,
|
||||
pattern: "[0-9]*",
|
||||
sx: { textAlign: "center" },
|
||||
}}
|
||||
onChange={(event) => {
|
||||
const inputValue = event.target.value;
|
||||
if (isNaN(Number(inputValue))) {
|
||||
return;
|
||||
}
|
||||
inputValue.length <= 2 ? field.onChange(inputValue) : null;
|
||||
}}
|
||||
size="small"
|
||||
placeholder="xx"
|
||||
sx={{ flexGrow: 2, "& fieldset": { border: "none" } }}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="plate_part3"
|
||||
control={control}
|
||||
defaultValue=""
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<TextField
|
||||
{...field}
|
||||
type="tel"
|
||||
inputProps={{
|
||||
inputMode: "number",
|
||||
min: 0,
|
||||
pattern: "[0-9]*",
|
||||
sx: { textAlign: "center" },
|
||||
}}
|
||||
onChange={(event) => {
|
||||
const inputValue = event.target.value;
|
||||
if (isNaN(Number(inputValue))) {
|
||||
return;
|
||||
}
|
||||
inputValue.length <= 3 ? field.onChange(inputValue) : null;
|
||||
}}
|
||||
size="small"
|
||||
placeholder="xxx"
|
||||
sx={{
|
||||
flexGrow: 3,
|
||||
borderLeft: 1,
|
||||
borderRadius: 0,
|
||||
borderColor: "divider",
|
||||
"& fieldset": { border: "none" },
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Box sx={{ flexGrow: 1 }}>
|
||||
<Button
|
||||
onClick={() => setPlateDrawer(true)}
|
||||
sx={{ height: "100%", borderRadius: 0, borderColor: "divider" }}
|
||||
size={"small"}
|
||||
>
|
||||
{plate_words.find((p) => p.value === platePart2)?.name || "الف"}
|
||||
</Button>
|
||||
</Box>
|
||||
<Drawer
|
||||
sx={{ zIndex: 9999 }}
|
||||
anchor={"bottom"}
|
||||
open={plateDrawer}
|
||||
onClose={() => setPlateDrawer(false)}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexWrap: "wrap",
|
||||
mx: "auto",
|
||||
my: 2,
|
||||
gap: 2,
|
||||
width: "60%",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
{plate_words.map((item) => (
|
||||
<Controller
|
||||
key={item.id}
|
||||
name="plate_part2"
|
||||
control={control}
|
||||
defaultValue=""
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<Button
|
||||
onClick={() => {
|
||||
setPlateDrawer(false);
|
||||
field.onChange(item.value);
|
||||
}}
|
||||
sx={{ width: "fit-content" }}
|
||||
variant="contained"
|
||||
>
|
||||
{item.name}
|
||||
</Button>
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
</Drawer>
|
||||
<Controller
|
||||
name="plate_part1"
|
||||
control={control}
|
||||
defaultValue=""
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<TextField
|
||||
{...field}
|
||||
type="tel"
|
||||
size="small"
|
||||
placeholder="xx"
|
||||
sx={{ flexGrow: 2, "& fieldset": { border: "none" } }}
|
||||
inputProps={{
|
||||
inputMode: "number",
|
||||
min: 0,
|
||||
pattern: "[0-9]*",
|
||||
sx: { textAlign: "center" },
|
||||
}}
|
||||
onChange={(event) => {
|
||||
const inputValue = event.target.value;
|
||||
if (isNaN(Number(inputValue))) {
|
||||
return;
|
||||
}
|
||||
inputValue.length <= 2 ? field.onChange(inputValue) : null;
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Stack sx={{ width: 24, borderLeft: 1, borderColor: "divider" }}>
|
||||
<Box sx={{ flexGrow: 1, bgcolor: "#1ebc1e" }}></Box>
|
||||
<Box sx={{ flexGrow: 1, bgcolor: "white" }}></Box>
|
||||
<Box sx={{ flexGrow: 1, bgcolor: "#f52121" }}></Box>
|
||||
</Stack>
|
||||
</Stack>
|
||||
{plateErrors && (
|
||||
<FormHelperText sx={{ ml: 2 }} error>
|
||||
پلاک نامعتبر است!!!
|
||||
</FormHelperText>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default PlateNumber;
|
||||
import { Box, Button, Drawer, FormHelperText, Stack, TextField } from "@mui/material";
|
||||
import AccessibleIcon from "@mui/icons-material/Accessible";
|
||||
import { Controller, useFormState, useWatch } from "react-hook-form";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
const plate_words = [
|
||||
{ id: 1, value: "الف", name: "الف" },
|
||||
{ id: 2, value: "ب", name: "ب" },
|
||||
{ id: 3, value: "پ", name: "پ" },
|
||||
{ id: 4, value: "ت", name: "ت" },
|
||||
{ id: 5, value: "ث", name: "ث" },
|
||||
{ id: 6, value: "ج", name: "ج" },
|
||||
{ id: 7, value: "د", name: "د" },
|
||||
{ id: 8, value: "ز", name: "ز" },
|
||||
{ id: 9, value: "س", name: "س" },
|
||||
{ id: 10, value: "ش", name: "ش" },
|
||||
{ id: 11, value: "ص", name: "ص" },
|
||||
{ id: 12, value: "ط", name: "ط" },
|
||||
{ id: 13, value: "ع", name: "ع" },
|
||||
{ id: 14, value: "ف", name: "ف" },
|
||||
{ id: 15, value: "ق", name: "ق" },
|
||||
{ id: 16, value: "ک", name: "گ" },
|
||||
{ id: 17, value: "ل", name: "ل" },
|
||||
{ id: 18, value: "م", name: "م" },
|
||||
{ id: 19, value: "ن", name: "ن" },
|
||||
{ id: 20, value: "و", name: "و" },
|
||||
{ id: 21, value: "ه", name: "ه" },
|
||||
{ id: 22, value: "ی", name: "ی" },
|
||||
{ id: 23, value: "*", name: <AccessibleIcon /> },
|
||||
];
|
||||
|
||||
const PlateNumber = ({ control }) => {
|
||||
const platePart2 = useWatch({ control, name: "plate_part2" });
|
||||
const [plateDrawer, setPlateDrawer] = useState(false);
|
||||
const { errors } = useFormState({ control });
|
||||
const plateErrors = ["plate_part1", "plate_part2", "plate_part3", "plate_part4"].some((part) => errors[part]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack
|
||||
direction={"row"}
|
||||
sx={{
|
||||
border: 1,
|
||||
borderColor: plateErrors ? "error.main" : "divider",
|
||||
overflow: "hidden",
|
||||
borderRadius: 1,
|
||||
}}
|
||||
>
|
||||
<Controller
|
||||
name="plate_part4"
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<TextField
|
||||
{...field}
|
||||
type="tel"
|
||||
inputProps={{
|
||||
inputMode: "number",
|
||||
min: 0,
|
||||
pattern: "[0-9]*",
|
||||
sx: { textAlign: "center" },
|
||||
}}
|
||||
onChange={(event) => {
|
||||
const inputValue = event.target.value;
|
||||
if (isNaN(Number(inputValue))) {
|
||||
return;
|
||||
}
|
||||
inputValue.length <= 2 ? field.onChange(inputValue) : null;
|
||||
}}
|
||||
size="small"
|
||||
placeholder="xx"
|
||||
sx={{ flexGrow: 2, "& fieldset": { border: "none" } }}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="plate_part3"
|
||||
control={control}
|
||||
defaultValue=""
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<TextField
|
||||
{...field}
|
||||
type="tel"
|
||||
inputProps={{
|
||||
inputMode: "number",
|
||||
min: 0,
|
||||
pattern: "[0-9]*",
|
||||
sx: { textAlign: "center" },
|
||||
}}
|
||||
onChange={(event) => {
|
||||
const inputValue = event.target.value;
|
||||
if (isNaN(Number(inputValue))) {
|
||||
return;
|
||||
}
|
||||
inputValue.length <= 3 ? field.onChange(inputValue) : null;
|
||||
}}
|
||||
size="small"
|
||||
placeholder="xxx"
|
||||
sx={{
|
||||
flexGrow: 3,
|
||||
borderLeft: 1,
|
||||
borderRadius: 0,
|
||||
borderColor: "divider",
|
||||
"& fieldset": { border: "none" },
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Box sx={{ flexGrow: 1 }}>
|
||||
<Button
|
||||
onClick={() => setPlateDrawer(true)}
|
||||
sx={{ height: "100%", borderRadius: 0, borderColor: "divider" }}
|
||||
size={"small"}
|
||||
>
|
||||
{plate_words.find((p) => p.value === platePart2)?.name || "الف"}
|
||||
</Button>
|
||||
</Box>
|
||||
<Drawer
|
||||
sx={{ zIndex: 9999 }}
|
||||
anchor={"bottom"}
|
||||
open={plateDrawer}
|
||||
onClose={() => setPlateDrawer(false)}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexWrap: "wrap",
|
||||
mx: "auto",
|
||||
my: 2,
|
||||
gap: 2,
|
||||
width: "60%",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
{plate_words.map((item) => (
|
||||
<Controller
|
||||
key={item.id}
|
||||
name="plate_part2"
|
||||
control={control}
|
||||
defaultValue=""
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<Button
|
||||
onClick={() => {
|
||||
setPlateDrawer(false);
|
||||
field.onChange(item.value);
|
||||
}}
|
||||
sx={{ width: "fit-content" }}
|
||||
variant="contained"
|
||||
>
|
||||
{item.name}
|
||||
</Button>
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
</Drawer>
|
||||
<Controller
|
||||
name="plate_part1"
|
||||
control={control}
|
||||
defaultValue=""
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<TextField
|
||||
{...field}
|
||||
type="tel"
|
||||
size="small"
|
||||
placeholder="xx"
|
||||
sx={{ flexGrow: 2, "& fieldset": { border: "none" } }}
|
||||
inputProps={{
|
||||
inputMode: "number",
|
||||
min: 0,
|
||||
pattern: "[0-9]*",
|
||||
sx: { textAlign: "center" },
|
||||
}}
|
||||
onChange={(event) => {
|
||||
const inputValue = event.target.value;
|
||||
if (isNaN(Number(inputValue))) {
|
||||
return;
|
||||
}
|
||||
inputValue.length <= 2 ? field.onChange(inputValue) : null;
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Stack sx={{ width: 24, borderLeft: 1, borderColor: "divider" }}>
|
||||
<Box sx={{ flexGrow: 1, bgcolor: "#1ebc1e" }}></Box>
|
||||
<Box sx={{ flexGrow: 1, bgcolor: "white" }}></Box>
|
||||
<Box sx={{ flexGrow: 1, bgcolor: "#f52121" }}></Box>
|
||||
</Stack>
|
||||
</Stack>
|
||||
{plateErrors && (
|
||||
<FormHelperText sx={{ ml: 2 }} error>
|
||||
پلاک نامعتبر است!!!
|
||||
</FormHelperText>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default PlateNumber;
|
||||
|
||||
@@ -1,152 +1,152 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Chip,
|
||||
Divider,
|
||||
FormControl,
|
||||
FormHelperText,
|
||||
Grid,
|
||||
IconButton,
|
||||
InputAdornment,
|
||||
InputLabel,
|
||||
OutlinedInput,
|
||||
} from "@mui/material";
|
||||
import StyledForm from "@/core/components/StyledForm";
|
||||
import { useForm } from "react-hook-form";
|
||||
import useRequest from "@/lib/hooks/useRequest";
|
||||
import { CHANGE_USER_PASSWORD } from "@/core/utils/routes";
|
||||
import { object, string } from "yup";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import { useEffect, useState } from "react";
|
||||
import Visibility from "@mui/icons-material/Visibility";
|
||||
import VisibilityOff from "@mui/icons-material/VisibilityOff";
|
||||
|
||||
const validationSchema = object({
|
||||
current_password: string().required("اجباری"),
|
||||
new_password: string().required("اجباری"),
|
||||
repeat_new_password: string()
|
||||
.required("اجباری")
|
||||
.test("password-match", "رمز عبور جدید و تکرار آن باید یکسان باشند", function (value) {
|
||||
return this.parent.new_password === value;
|
||||
}),
|
||||
});
|
||||
|
||||
const Form = ({ handleCloseForm, setLoading }) => {
|
||||
const request = useRequest();
|
||||
const [showNewPassword, setShowNewPassword] = useState();
|
||||
const [showRepeatNewPassword, setShowRepeatNewPassword] = useState();
|
||||
|
||||
const defaultValues = {
|
||||
current_password: "",
|
||||
new_password: "",
|
||||
repeat_new_password: "",
|
||||
};
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { isSubmitting, errors },
|
||||
} = useForm({ defaultValues, resolver: yupResolver(validationSchema) });
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(isSubmitting);
|
||||
}, [isSubmitting]);
|
||||
|
||||
const onSubmit = async (data) => {
|
||||
const formData = new FormData();
|
||||
formData.append("current_password", data.current_password);
|
||||
formData.append("new_password", data.new_password);
|
||||
try {
|
||||
await request(CHANGE_USER_PASSWORD, "post", { data: formData, signal: controller.signal });
|
||||
handleCloseForm();
|
||||
} catch (error) {}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Divider sx={{ mb: 3 }}>
|
||||
<Chip color="primary" variant="outlined" label="تغییر رمز عبور" />
|
||||
</Divider>
|
||||
<StyledForm onSubmit={handleSubmit(onSubmit)} id={"ChangePassword"}>
|
||||
<Grid container columns={1} spacing={3}>
|
||||
<Grid item xs={1}>
|
||||
<FormControl error={!!errors.current_password} size="small" fullWidth variant="outlined">
|
||||
<InputLabel htmlFor="current_password">رمز عبور فعلی</InputLabel>
|
||||
<OutlinedInput
|
||||
id="current_password"
|
||||
label="رمز عبور فعلی"
|
||||
autoComplete="off"
|
||||
{...register("current_password")}
|
||||
size="small"
|
||||
fullWidth
|
||||
type="text"
|
||||
/>
|
||||
<FormHelperText id="current_password">
|
||||
{errors.current_password ? errors.current_password.message : null}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
<Grid item xs={1}>
|
||||
<FormControl error={!!errors.new_password} size="small" fullWidth variant="outlined">
|
||||
<InputLabel htmlFor="new_password">رمز عبور جدید</InputLabel>
|
||||
<OutlinedInput
|
||||
id="new_password"
|
||||
autoComplete="off"
|
||||
{...register("new_password")}
|
||||
label="رمز عبور جدید"
|
||||
size="small"
|
||||
fullWidth
|
||||
type={showNewPassword ? "text" : "password"}
|
||||
endAdornment={
|
||||
<InputAdornment position="end">
|
||||
<IconButton
|
||||
aria-label="toggle password visibility"
|
||||
onClick={() => setShowNewPassword(!showNewPassword)}
|
||||
onMouseDown={(event) => event.preventDefault()}
|
||||
edge="end"
|
||||
>
|
||||
{showNewPassword ? <VisibilityOff /> : <Visibility />}
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
}
|
||||
/>
|
||||
<FormHelperText id="new_password">
|
||||
{errors.new_password ? errors.new_password.message : null}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
<Grid item xs={1}>
|
||||
<FormControl error={!!errors.repeat_new_password} size="small" fullWidth variant="outlined">
|
||||
<InputLabel htmlFor="repeat_new_password">تکرار رمز عبور جدید</InputLabel>
|
||||
<OutlinedInput
|
||||
id="repeat_new_password"
|
||||
autoComplete="off"
|
||||
{...register("repeat_new_password")}
|
||||
size="small"
|
||||
fullWidth
|
||||
label="تکرار رمز عبور جدید"
|
||||
type={showRepeatNewPassword ? "text" : "password"}
|
||||
endAdornment={
|
||||
<InputAdornment position="end">
|
||||
<IconButton
|
||||
aria-label="toggle password visibility"
|
||||
onClick={() => setShowRepeatNewPassword(!showRepeatNewPassword)}
|
||||
onMouseDown={(event) => event.preventDefault()}
|
||||
edge="end"
|
||||
>
|
||||
{showRepeatNewPassword ? <VisibilityOff /> : <Visibility />}
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
}
|
||||
/>
|
||||
<FormHelperText id="repeat_new_password">
|
||||
{errors.repeat_new_password ? errors.repeat_new_password.message : null}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</StyledForm>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default Form;
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Chip,
|
||||
Divider,
|
||||
FormControl,
|
||||
FormHelperText,
|
||||
Grid,
|
||||
IconButton,
|
||||
InputAdornment,
|
||||
InputLabel,
|
||||
OutlinedInput,
|
||||
} from "@mui/material";
|
||||
import StyledForm from "@/core/components/StyledForm";
|
||||
import { useForm } from "react-hook-form";
|
||||
import useRequest from "@/lib/hooks/useRequest";
|
||||
import { CHANGE_USER_PASSWORD } from "@/core/utils/routes";
|
||||
import { object, string } from "yup";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import { useEffect, useState } from "react";
|
||||
import Visibility from "@mui/icons-material/Visibility";
|
||||
import VisibilityOff from "@mui/icons-material/VisibilityOff";
|
||||
|
||||
const validationSchema = object({
|
||||
current_password: string().required("اجباری"),
|
||||
new_password: string().required("اجباری"),
|
||||
repeat_new_password: string()
|
||||
.required("اجباری")
|
||||
.test("password-match", "رمز عبور جدید و تکرار آن باید یکسان باشند", function (value) {
|
||||
return this.parent.new_password === value;
|
||||
}),
|
||||
});
|
||||
|
||||
const Form = ({ handleCloseForm, setLoading }) => {
|
||||
const request = useRequest();
|
||||
const [showNewPassword, setShowNewPassword] = useState();
|
||||
const [showRepeatNewPassword, setShowRepeatNewPassword] = useState();
|
||||
|
||||
const defaultValues = {
|
||||
current_password: "",
|
||||
new_password: "",
|
||||
repeat_new_password: "",
|
||||
};
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { isSubmitting, errors },
|
||||
} = useForm({ defaultValues, resolver: yupResolver(validationSchema) });
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(isSubmitting);
|
||||
}, [isSubmitting]);
|
||||
|
||||
const onSubmit = async (data) => {
|
||||
const formData = new FormData();
|
||||
formData.append("current_password", data.current_password);
|
||||
formData.append("new_password", data.new_password);
|
||||
try {
|
||||
await request(CHANGE_USER_PASSWORD, "post", { data: formData, signal: controller.signal });
|
||||
handleCloseForm();
|
||||
} catch (error) {}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Divider sx={{ mb: 3 }}>
|
||||
<Chip color="primary" variant="outlined" label="تغییر رمز عبور" />
|
||||
</Divider>
|
||||
<StyledForm onSubmit={handleSubmit(onSubmit)} id={"ChangePassword"}>
|
||||
<Grid container columns={1} spacing={3}>
|
||||
<Grid item xs={1}>
|
||||
<FormControl error={!!errors.current_password} size="small" fullWidth variant="outlined">
|
||||
<InputLabel htmlFor="current_password">رمز عبور فعلی</InputLabel>
|
||||
<OutlinedInput
|
||||
id="current_password"
|
||||
label="رمز عبور فعلی"
|
||||
autoComplete="off"
|
||||
{...register("current_password")}
|
||||
size="small"
|
||||
fullWidth
|
||||
type="text"
|
||||
/>
|
||||
<FormHelperText id="current_password">
|
||||
{errors.current_password ? errors.current_password.message : null}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
<Grid item xs={1}>
|
||||
<FormControl error={!!errors.new_password} size="small" fullWidth variant="outlined">
|
||||
<InputLabel htmlFor="new_password">رمز عبور جدید</InputLabel>
|
||||
<OutlinedInput
|
||||
id="new_password"
|
||||
autoComplete="off"
|
||||
{...register("new_password")}
|
||||
label="رمز عبور جدید"
|
||||
size="small"
|
||||
fullWidth
|
||||
type={showNewPassword ? "text" : "password"}
|
||||
endAdornment={
|
||||
<InputAdornment position="end">
|
||||
<IconButton
|
||||
aria-label="toggle password visibility"
|
||||
onClick={() => setShowNewPassword(!showNewPassword)}
|
||||
onMouseDown={(event) => event.preventDefault()}
|
||||
edge="end"
|
||||
>
|
||||
{showNewPassword ? <VisibilityOff /> : <Visibility />}
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
}
|
||||
/>
|
||||
<FormHelperText id="new_password">
|
||||
{errors.new_password ? errors.new_password.message : null}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
<Grid item xs={1}>
|
||||
<FormControl error={!!errors.repeat_new_password} size="small" fullWidth variant="outlined">
|
||||
<InputLabel htmlFor="repeat_new_password">تکرار رمز عبور جدید</InputLabel>
|
||||
<OutlinedInput
|
||||
id="repeat_new_password"
|
||||
autoComplete="off"
|
||||
{...register("repeat_new_password")}
|
||||
size="small"
|
||||
fullWidth
|
||||
label="تکرار رمز عبور جدید"
|
||||
type={showRepeatNewPassword ? "text" : "password"}
|
||||
endAdornment={
|
||||
<InputAdornment position="end">
|
||||
<IconButton
|
||||
aria-label="toggle password visibility"
|
||||
onClick={() => setShowRepeatNewPassword(!showRepeatNewPassword)}
|
||||
onMouseDown={(event) => event.preventDefault()}
|
||||
edge="end"
|
||||
>
|
||||
{showRepeatNewPassword ? <VisibilityOff /> : <Visibility />}
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
}
|
||||
/>
|
||||
<FormHelperText id="repeat_new_password">
|
||||
{errors.repeat_new_password ? errors.repeat_new_password.message : null}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</StyledForm>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default Form;
|
||||
|
||||
@@ -1,47 +1,47 @@
|
||||
"use client";
|
||||
|
||||
import { Dialog, DialogActions, DialogContent } from "@mui/material";
|
||||
import Form from "./Form";
|
||||
import SaveIcon from "@mui/icons-material/Save";
|
||||
import { LoadingButton } from "@mui/lab";
|
||||
import { useState } from "react";
|
||||
|
||||
const ChangePass = ({ open, setOpen }) => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const handleCloseForm = () => {
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog maxWidth="xs" fullWidth={true} open={open}>
|
||||
<DialogContent dividers>
|
||||
<Form handleCloseForm={handleCloseForm} setLoading={setLoading} />
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<LoadingButton
|
||||
loading={loading}
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
size="large"
|
||||
onClick={handleCloseForm}
|
||||
>
|
||||
بستن
|
||||
</LoadingButton>
|
||||
<LoadingButton
|
||||
type="submit"
|
||||
loading={loading}
|
||||
form="ChangePassword"
|
||||
size="large"
|
||||
autoFocus
|
||||
variant="contained"
|
||||
loadingPosition="end"
|
||||
endIcon={<SaveIcon />}
|
||||
>
|
||||
تغییر
|
||||
</LoadingButton>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
export default ChangePass;
|
||||
"use client";
|
||||
|
||||
import { Dialog, DialogActions, DialogContent } from "@mui/material";
|
||||
import Form from "./Form";
|
||||
import SaveIcon from "@mui/icons-material/Save";
|
||||
import { LoadingButton } from "@mui/lab";
|
||||
import { useState } from "react";
|
||||
|
||||
const ChangePass = ({ open, setOpen }) => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const handleCloseForm = () => {
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog maxWidth="xs" fullWidth={true} open={open}>
|
||||
<DialogContent dividers>
|
||||
<Form handleCloseForm={handleCloseForm} setLoading={setLoading} />
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<LoadingButton
|
||||
loading={loading}
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
size="large"
|
||||
onClick={handleCloseForm}
|
||||
>
|
||||
بستن
|
||||
</LoadingButton>
|
||||
<LoadingButton
|
||||
type="submit"
|
||||
loading={loading}
|
||||
form="ChangePassword"
|
||||
size="large"
|
||||
autoFocus
|
||||
variant="contained"
|
||||
loadingPosition="end"
|
||||
endIcon={<SaveIcon />}
|
||||
>
|
||||
تغییر
|
||||
</LoadingButton>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
export default ChangePass;
|
||||
|
||||
@@ -1,61 +1,61 @@
|
||||
"use client";
|
||||
import { Box, Divider, IconButton, Tooltip } from "@mui/material";
|
||||
import PowerSettingsNewIcon from "@mui/icons-material/PowerSettingsNew";
|
||||
import ModeEditIcon from "@mui/icons-material/ModeEdit";
|
||||
import VpnKeyIcon from "@mui/icons-material/VpnKey";
|
||||
import { useState } from "react";
|
||||
import Update from "@/core/components/Profile/Update";
|
||||
import ChangePass from "@/core/components/Profile/ChangePass";
|
||||
import { useAuth } from "@/lib/contexts/auth";
|
||||
import useRequest from "@/lib/hooks/useRequest";
|
||||
import { LOGOUT_USER_ROUTE } from "@/core/utils/routes";
|
||||
|
||||
const ProfileActions = () => {
|
||||
const { getUser, logout } = useAuth();
|
||||
const requestServer = useRequest();
|
||||
const [openUpdate, setOpenUpdate] = useState(false);
|
||||
const [openChangePass, setOpenChangePass] = useState(false);
|
||||
|
||||
const openUpdateProfile = () => {
|
||||
getUser();
|
||||
setOpenUpdate(true);
|
||||
};
|
||||
const handleLogout = () => {
|
||||
requestServer(LOGOUT_USER_ROUTE, "post").then(() => {
|
||||
logout();
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box sx={{ display: "flex", justifyContent: "center" }}>
|
||||
<Tooltip title="ویرایش پروفایل" arrow>
|
||||
<IconButton aria-label="ویرایش پروفایل" color="success" onClick={openUpdateProfile}>
|
||||
<ModeEditIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Divider orientation="vertical" variant="middle" flexItem sx={{ mx: 1 }} />
|
||||
<Tooltip title="خروج" arrow>
|
||||
<IconButton onClick={handleLogout} aria-label="خروج" color="error">
|
||||
<PowerSettingsNewIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Divider orientation="vertical" variant="middle" flexItem sx={{ mx: 1 }} />
|
||||
<Tooltip title="تغییر رمز عبور" arrow>
|
||||
<IconButton
|
||||
aria-label="تغییر رمز عبور"
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
setOpenChangePass(true);
|
||||
}}
|
||||
>
|
||||
<VpnKeyIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
<Update open={openUpdate} setOpen={setOpenUpdate} />
|
||||
<ChangePass open={openChangePass} setOpen={setOpenChangePass} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default ProfileActions;
|
||||
"use client";
|
||||
import { Box, Divider, IconButton, Tooltip } from "@mui/material";
|
||||
import PowerSettingsNewIcon from "@mui/icons-material/PowerSettingsNew";
|
||||
import ModeEditIcon from "@mui/icons-material/ModeEdit";
|
||||
import VpnKeyIcon from "@mui/icons-material/VpnKey";
|
||||
import { useState } from "react";
|
||||
import Update from "@/core/components/Profile/Update";
|
||||
import ChangePass from "@/core/components/Profile/ChangePass";
|
||||
import { useAuth } from "@/lib/contexts/auth";
|
||||
import useRequest from "@/lib/hooks/useRequest";
|
||||
import { LOGOUT_USER_ROUTE } from "@/core/utils/routes";
|
||||
|
||||
const ProfileActions = () => {
|
||||
const { getUser, logout } = useAuth();
|
||||
const requestServer = useRequest();
|
||||
const [openUpdate, setOpenUpdate] = useState(false);
|
||||
const [openChangePass, setOpenChangePass] = useState(false);
|
||||
|
||||
const openUpdateProfile = () => {
|
||||
getUser();
|
||||
setOpenUpdate(true);
|
||||
};
|
||||
const handleLogout = () => {
|
||||
requestServer(LOGOUT_USER_ROUTE, "post").then(() => {
|
||||
logout();
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box sx={{ display: "flex", justifyContent: "center" }}>
|
||||
<Tooltip title="ویرایش پروفایل" arrow>
|
||||
<IconButton aria-label="ویرایش پروفایل" color="success" onClick={openUpdateProfile}>
|
||||
<ModeEditIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Divider orientation="vertical" variant="middle" flexItem sx={{ mx: 1 }} />
|
||||
<Tooltip title="خروج" arrow>
|
||||
<IconButton onClick={handleLogout} aria-label="خروج" color="error">
|
||||
<PowerSettingsNewIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Divider orientation="vertical" variant="middle" flexItem sx={{ mx: 1 }} />
|
||||
<Tooltip title="تغییر رمز عبور" arrow>
|
||||
<IconButton
|
||||
aria-label="تغییر رمز عبور"
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
setOpenChangePass(true);
|
||||
}}
|
||||
>
|
||||
<VpnKeyIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
<Update open={openUpdate} setOpen={setOpenUpdate} />
|
||||
<ChangePass open={openChangePass} setOpen={setOpenChangePass} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default ProfileActions;
|
||||
|
||||
@@ -1,38 +1,38 @@
|
||||
"use client";
|
||||
|
||||
import { useAuth } from "@/lib/contexts/auth";
|
||||
import { Avatar, Box, Chip, Divider, Stack, Typography } from "@mui/material";
|
||||
import moment from "jalali-moment";
|
||||
|
||||
const ProfileInfo = () => {
|
||||
const { user } = useAuth();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box sx={{ display: "flex", alignItems: "center", my: 0.5 }}>
|
||||
<Avatar alt="User Image" src={user?.avatar} sx={{ width: 56, height: 56 }} />
|
||||
<Stack sx={{ ml: 1 }}>
|
||||
<Box sx={{ display: "flex", alignItems: "end", gap: 0.7 }}>
|
||||
<Typography variant="h6">{`${user.first_name} ${user.last_name}`}</Typography>
|
||||
</Box>
|
||||
<Typography variant="caption">.: {user.name} :.</Typography>
|
||||
</Stack>
|
||||
</Box>
|
||||
<Box sx={{ display: "flex", alignItems: "center", my: 0.5 }}>
|
||||
<Typography variant="button" color="#757575">
|
||||
آخرین ورود
|
||||
</Typography>
|
||||
<Divider sx={{ mx: 2, flexGrow: 1 }} />
|
||||
<Typography variant="subtitle2" color="#757575">
|
||||
{moment(user.last_login).locale("fa").fromNow()}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ my: 0.5 }}>
|
||||
<Divider>
|
||||
<Chip size="small" label={user.username} color="success" variant="outlined" />
|
||||
</Divider>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default ProfileInfo;
|
||||
"use client";
|
||||
|
||||
import { useAuth } from "@/lib/contexts/auth";
|
||||
import { Avatar, Box, Chip, Divider, Stack, Typography } from "@mui/material";
|
||||
import moment from "jalali-moment";
|
||||
|
||||
const ProfileInfo = () => {
|
||||
const { user } = useAuth();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box sx={{ display: "flex", alignItems: "center", my: 0.5 }}>
|
||||
<Avatar alt="User Image" src={user?.avatar} sx={{ width: 56, height: 56 }} />
|
||||
<Stack sx={{ ml: 1 }}>
|
||||
<Box sx={{ display: "flex", alignItems: "end", gap: 0.7 }}>
|
||||
<Typography variant="h6">{`${user.first_name} ${user.last_name}`}</Typography>
|
||||
</Box>
|
||||
<Typography variant="caption">.: {user.name} :.</Typography>
|
||||
</Stack>
|
||||
</Box>
|
||||
<Box sx={{ display: "flex", alignItems: "center", my: 0.5 }}>
|
||||
<Typography variant="button" color="#757575">
|
||||
آخرین ورود
|
||||
</Typography>
|
||||
<Divider sx={{ mx: 2, flexGrow: 1 }} />
|
||||
<Typography variant="subtitle2" color="#757575">
|
||||
{moment(user.last_login).locale("fa").fromNow()}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ my: 0.5 }}>
|
||||
<Divider>
|
||||
<Chip size="small" label={user.username} color="success" variant="outlined" />
|
||||
</Divider>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default ProfileInfo;
|
||||
|
||||
@@ -1,257 +1,257 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Autocomplete,
|
||||
Avatar,
|
||||
Backdrop,
|
||||
Box,
|
||||
Button,
|
||||
Chip,
|
||||
Divider,
|
||||
FormControl,
|
||||
FormHelperText,
|
||||
Grid,
|
||||
InputLabel,
|
||||
OutlinedInput,
|
||||
TextField,
|
||||
} from "@mui/material";
|
||||
import StyledForm from "@/core/components/StyledForm";
|
||||
import useRequest from "@/lib/hooks/useRequest";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { useAuth } from "@/lib/contexts/auth";
|
||||
import CloudUploadIcon from "@mui/icons-material/CloudUpload";
|
||||
import { styled } from "@mui/material/styles";
|
||||
import { useEffect, useState } from "react";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import { object, string } from "yup";
|
||||
import { UPDATE_USER_ROUTE } from "@/core/utils/routes";
|
||||
|
||||
const degreeItems = ["دیپلم", "کارشناسی", "کارشناسی ارشد", "دکترا"];
|
||||
|
||||
const VisuallyHiddenInput = styled("input")({
|
||||
clip: "rect(0 0 0 0)",
|
||||
clipPath: "inset(50%)",
|
||||
height: 1,
|
||||
overflow: "hidden",
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
whiteSpace: "nowrap",
|
||||
width: 1,
|
||||
});
|
||||
|
||||
const validationSchema = object({
|
||||
first_name: string().required("اجباری"),
|
||||
last_name: string().required("اجباری"),
|
||||
degree: string().required("اجباری"),
|
||||
major: string().required("اجباری"),
|
||||
mobile: string().required("اجباری"),
|
||||
});
|
||||
|
||||
const Form = ({ handleCloseForm, setLoading }) => {
|
||||
const request = useRequest();
|
||||
const { user, getUser } = useAuth();
|
||||
const [uploadBackDrop, setUploadBackDrop] = useState(false);
|
||||
const [avatarUrl, setAvatarUrl] = useState(user?.avatar || "");
|
||||
const defaultValues = {
|
||||
first_name: user.first_name,
|
||||
last_name: user.last_name,
|
||||
major: user.major,
|
||||
degree: user.degree,
|
||||
mobile: user.mobile,
|
||||
avatar: null,
|
||||
};
|
||||
|
||||
const {
|
||||
control,
|
||||
watch,
|
||||
register,
|
||||
setValue,
|
||||
handleSubmit,
|
||||
formState: { isSubmitting, errors },
|
||||
} = useForm({ defaultValues, resolver: yupResolver(validationSchema) });
|
||||
|
||||
const handleFileChange = (event) => {
|
||||
const file = event.target.files[0];
|
||||
if (file) {
|
||||
const objectUrl = URL.createObjectURL(file);
|
||||
setAvatarUrl(objectUrl);
|
||||
setValue("avatar", file);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(isSubmitting);
|
||||
}, [isSubmitting]);
|
||||
|
||||
const onSubmit = async (data) => {
|
||||
const formData = new FormData();
|
||||
formData.append("first_name", data.first_name);
|
||||
formData.append("last_name", data.last_name);
|
||||
formData.append("major", data.major);
|
||||
formData.append("degree", data.degree);
|
||||
formData.append("mobile", data.mobile);
|
||||
if (data.avatar) {
|
||||
formData.append("avatar", data.avatar);
|
||||
}
|
||||
|
||||
try {
|
||||
await request(UPDATE_USER_ROUTE, "post", { data: formData });
|
||||
getUser();
|
||||
handleCloseForm();
|
||||
} catch (error) {}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Divider sx={{ mb: 3 }}>
|
||||
<Chip color="success" variant="outlined" label="ویرایش پروفایل" />
|
||||
</Divider>
|
||||
<StyledForm onSubmit={handleSubmit(onSubmit)} id={"UpdateProfile"}>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
mb: 4,
|
||||
position: "relative",
|
||||
borderRadius: "50%",
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
component="label"
|
||||
sx={{
|
||||
borderRadius: "50%",
|
||||
padding: "unset",
|
||||
"&:hover": {
|
||||
backgroundColor: "unset",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{ borderRadius: "50%" }}
|
||||
onMouseEnter={() => setUploadBackDrop(true)}
|
||||
onMouseLeave={() => setUploadBackDrop(false)}
|
||||
>
|
||||
<Avatar src={avatarUrl} sx={{ width: 120, height: 120, border: "1px solid #e1e1e1" }} />
|
||||
<Backdrop
|
||||
sx={{
|
||||
color: "#fff",
|
||||
zIndex: (theme) => theme.zIndex.drawer + 1,
|
||||
position: "absolute",
|
||||
width: 120,
|
||||
height: 120,
|
||||
marginLeft: "auto",
|
||||
marginRight: "auto",
|
||||
borderRadius: "50%",
|
||||
}}
|
||||
open={uploadBackDrop}
|
||||
>
|
||||
<CloudUploadIcon sx={{ fontSize: "2rem" }} color="inherit" />
|
||||
</Backdrop>
|
||||
</Box>
|
||||
<VisuallyHiddenInput type="file" accept="image/*" onChange={handleFileChange} />
|
||||
</Button>
|
||||
</Box>
|
||||
<Grid container columns={{ xs: 1, sm: 2 }} spacing={2}>
|
||||
<Grid item xs={1}>
|
||||
<FormControl error={!!errors.first_name} size="small" fullWidth variant="outlined">
|
||||
<InputLabel htmlFor="first_name">نام</InputLabel>
|
||||
<OutlinedInput
|
||||
id="first_name"
|
||||
label="نام"
|
||||
autoComplete="off"
|
||||
{...register("first_name")}
|
||||
size="small"
|
||||
fullWidth
|
||||
type="text"
|
||||
/>
|
||||
<FormHelperText id="first_name">
|
||||
{errors.first_name ? errors.first_name.message : null}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
<Grid item xs={1}>
|
||||
<FormControl error={!!errors.last_name} size="small" fullWidth variant="outlined">
|
||||
<InputLabel htmlFor="last_name">نام خانوادگی</InputLabel>
|
||||
<OutlinedInput
|
||||
id="last_name"
|
||||
label="نام خانوادگی"
|
||||
autoComplete="off"
|
||||
{...register("last_name")}
|
||||
size="small"
|
||||
fullWidth
|
||||
type="text"
|
||||
/>
|
||||
<FormHelperText id="last_name">
|
||||
{errors.last_name ? errors.last_name.message : null}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
<Grid item xs={1}>
|
||||
<FormControl error={!!errors.major} size="small" fullWidth variant="outlined">
|
||||
<InputLabel htmlFor="major">رشته تحصیلی</InputLabel>
|
||||
<OutlinedInput
|
||||
id="major"
|
||||
label="رشته تحصیلی"
|
||||
autoComplete="off"
|
||||
{...register("major")}
|
||||
size="small"
|
||||
fullWidth
|
||||
type="text"
|
||||
/>
|
||||
<FormHelperText id="major">{errors.major ? errors.major.message : null}</FormHelperText>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
<Grid item xs={1}>
|
||||
<Controller
|
||||
name="degree"
|
||||
control={control}
|
||||
render={({ field: { onChange, value }, fieldState: { error } }) => (
|
||||
<FormControl error={!!error} size="small" fullWidth variant="outlined">
|
||||
<Autocomplete
|
||||
size="small"
|
||||
disablePortal
|
||||
value={value || null}
|
||||
onChange={(event, newValue) => {
|
||||
onChange(newValue);
|
||||
}}
|
||||
options={degreeItems}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
label="مدرک تحصیلی"
|
||||
placeholder="مدرک تحصیلی را انتخاب کنید"
|
||||
autoComplete="off"
|
||||
error={!!error}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<FormHelperText>{error ? error.message : null}</FormHelperText>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={2}>
|
||||
<FormControl error={!!errors.mobile} size="small" fullWidth variant="outlined">
|
||||
<InputLabel htmlFor="mobile">موبایل</InputLabel>
|
||||
<OutlinedInput
|
||||
id="mobile"
|
||||
label="موبایل"
|
||||
autoComplete="off"
|
||||
{...register("mobile")}
|
||||
size="small"
|
||||
fullWidth
|
||||
type="text"
|
||||
/>
|
||||
<FormHelperText id="mobile">{errors.mobile ? errors.mobile.message : null}</FormHelperText>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</StyledForm>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default Form;
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Autocomplete,
|
||||
Avatar,
|
||||
Backdrop,
|
||||
Box,
|
||||
Button,
|
||||
Chip,
|
||||
Divider,
|
||||
FormControl,
|
||||
FormHelperText,
|
||||
Grid,
|
||||
InputLabel,
|
||||
OutlinedInput,
|
||||
TextField,
|
||||
} from "@mui/material";
|
||||
import StyledForm from "@/core/components/StyledForm";
|
||||
import useRequest from "@/lib/hooks/useRequest";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { useAuth } from "@/lib/contexts/auth";
|
||||
import CloudUploadIcon from "@mui/icons-material/CloudUpload";
|
||||
import { styled } from "@mui/material/styles";
|
||||
import { useEffect, useState } from "react";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import { object, string } from "yup";
|
||||
import { UPDATE_USER_ROUTE } from "@/core/utils/routes";
|
||||
|
||||
const degreeItems = ["دیپلم", "کارشناسی", "کارشناسی ارشد", "دکترا"];
|
||||
|
||||
const VisuallyHiddenInput = styled("input")({
|
||||
clip: "rect(0 0 0 0)",
|
||||
clipPath: "inset(50%)",
|
||||
height: 1,
|
||||
overflow: "hidden",
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
whiteSpace: "nowrap",
|
||||
width: 1,
|
||||
});
|
||||
|
||||
const validationSchema = object({
|
||||
first_name: string().required("اجباری"),
|
||||
last_name: string().required("اجباری"),
|
||||
degree: string().required("اجباری"),
|
||||
major: string().required("اجباری"),
|
||||
mobile: string().required("اجباری"),
|
||||
});
|
||||
|
||||
const Form = ({ handleCloseForm, setLoading }) => {
|
||||
const request = useRequest();
|
||||
const { user, getUser } = useAuth();
|
||||
const [uploadBackDrop, setUploadBackDrop] = useState(false);
|
||||
const [avatarUrl, setAvatarUrl] = useState(user?.avatar || "");
|
||||
const defaultValues = {
|
||||
first_name: user.first_name,
|
||||
last_name: user.last_name,
|
||||
major: user.major,
|
||||
degree: user.degree,
|
||||
mobile: user.mobile,
|
||||
avatar: null,
|
||||
};
|
||||
|
||||
const {
|
||||
control,
|
||||
watch,
|
||||
register,
|
||||
setValue,
|
||||
handleSubmit,
|
||||
formState: { isSubmitting, errors },
|
||||
} = useForm({ defaultValues, resolver: yupResolver(validationSchema) });
|
||||
|
||||
const handleFileChange = (event) => {
|
||||
const file = event.target.files[0];
|
||||
if (file) {
|
||||
const objectUrl = URL.createObjectURL(file);
|
||||
setAvatarUrl(objectUrl);
|
||||
setValue("avatar", file);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(isSubmitting);
|
||||
}, [isSubmitting]);
|
||||
|
||||
const onSubmit = async (data) => {
|
||||
const formData = new FormData();
|
||||
formData.append("first_name", data.first_name);
|
||||
formData.append("last_name", data.last_name);
|
||||
formData.append("major", data.major);
|
||||
formData.append("degree", data.degree);
|
||||
formData.append("mobile", data.mobile);
|
||||
if (data.avatar) {
|
||||
formData.append("avatar", data.avatar);
|
||||
}
|
||||
|
||||
try {
|
||||
await request(UPDATE_USER_ROUTE, "post", { data: formData });
|
||||
getUser();
|
||||
handleCloseForm();
|
||||
} catch (error) {}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Divider sx={{ mb: 3 }}>
|
||||
<Chip color="success" variant="outlined" label="ویرایش پروفایل" />
|
||||
</Divider>
|
||||
<StyledForm onSubmit={handleSubmit(onSubmit)} id={"UpdateProfile"}>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
mb: 4,
|
||||
position: "relative",
|
||||
borderRadius: "50%",
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
component="label"
|
||||
sx={{
|
||||
borderRadius: "50%",
|
||||
padding: "unset",
|
||||
"&:hover": {
|
||||
backgroundColor: "unset",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{ borderRadius: "50%" }}
|
||||
onMouseEnter={() => setUploadBackDrop(true)}
|
||||
onMouseLeave={() => setUploadBackDrop(false)}
|
||||
>
|
||||
<Avatar src={avatarUrl} sx={{ width: 120, height: 120, border: "1px solid #e1e1e1" }} />
|
||||
<Backdrop
|
||||
sx={{
|
||||
color: "#fff",
|
||||
zIndex: (theme) => theme.zIndex.drawer + 1,
|
||||
position: "absolute",
|
||||
width: 120,
|
||||
height: 120,
|
||||
marginLeft: "auto",
|
||||
marginRight: "auto",
|
||||
borderRadius: "50%",
|
||||
}}
|
||||
open={uploadBackDrop}
|
||||
>
|
||||
<CloudUploadIcon sx={{ fontSize: "2rem" }} color="inherit" />
|
||||
</Backdrop>
|
||||
</Box>
|
||||
<VisuallyHiddenInput type="file" accept="image/*" onChange={handleFileChange} />
|
||||
</Button>
|
||||
</Box>
|
||||
<Grid container columns={{ xs: 1, sm: 2 }} spacing={2}>
|
||||
<Grid item xs={1}>
|
||||
<FormControl error={!!errors.first_name} size="small" fullWidth variant="outlined">
|
||||
<InputLabel htmlFor="first_name">نام</InputLabel>
|
||||
<OutlinedInput
|
||||
id="first_name"
|
||||
label="نام"
|
||||
autoComplete="off"
|
||||
{...register("first_name")}
|
||||
size="small"
|
||||
fullWidth
|
||||
type="text"
|
||||
/>
|
||||
<FormHelperText id="first_name">
|
||||
{errors.first_name ? errors.first_name.message : null}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
<Grid item xs={1}>
|
||||
<FormControl error={!!errors.last_name} size="small" fullWidth variant="outlined">
|
||||
<InputLabel htmlFor="last_name">نام خانوادگی</InputLabel>
|
||||
<OutlinedInput
|
||||
id="last_name"
|
||||
label="نام خانوادگی"
|
||||
autoComplete="off"
|
||||
{...register("last_name")}
|
||||
size="small"
|
||||
fullWidth
|
||||
type="text"
|
||||
/>
|
||||
<FormHelperText id="last_name">
|
||||
{errors.last_name ? errors.last_name.message : null}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
<Grid item xs={1}>
|
||||
<FormControl error={!!errors.major} size="small" fullWidth variant="outlined">
|
||||
<InputLabel htmlFor="major">رشته تحصیلی</InputLabel>
|
||||
<OutlinedInput
|
||||
id="major"
|
||||
label="رشته تحصیلی"
|
||||
autoComplete="off"
|
||||
{...register("major")}
|
||||
size="small"
|
||||
fullWidth
|
||||
type="text"
|
||||
/>
|
||||
<FormHelperText id="major">{errors.major ? errors.major.message : null}</FormHelperText>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
<Grid item xs={1}>
|
||||
<Controller
|
||||
name="degree"
|
||||
control={control}
|
||||
render={({ field: { onChange, value }, fieldState: { error } }) => (
|
||||
<FormControl error={!!error} size="small" fullWidth variant="outlined">
|
||||
<Autocomplete
|
||||
size="small"
|
||||
disablePortal
|
||||
value={value || null}
|
||||
onChange={(event, newValue) => {
|
||||
onChange(newValue);
|
||||
}}
|
||||
options={degreeItems}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
label="مدرک تحصیلی"
|
||||
placeholder="مدرک تحصیلی را انتخاب کنید"
|
||||
autoComplete="off"
|
||||
error={!!error}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<FormHelperText>{error ? error.message : null}</FormHelperText>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={2}>
|
||||
<FormControl error={!!errors.mobile} size="small" fullWidth variant="outlined">
|
||||
<InputLabel htmlFor="mobile">موبایل</InputLabel>
|
||||
<OutlinedInput
|
||||
id="mobile"
|
||||
label="موبایل"
|
||||
autoComplete="off"
|
||||
{...register("mobile")}
|
||||
size="small"
|
||||
fullWidth
|
||||
type="text"
|
||||
/>
|
||||
<FormHelperText id="mobile">{errors.mobile ? errors.mobile.message : null}</FormHelperText>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</StyledForm>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default Form;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user